From: Simon Glass <sjg@chromium.org>
To: U-Boot Mailing List <u-boot@lists.denx.de>
Cc: Simon Glass <sjg@chromium.org>
Subject: [PATCH v2 16/38] binman: Add tests for bintool
Date: Sun, 9 Jan 2022 20:13:51 -0700 [thread overview]
Message-ID: <20220110031413.1970836-17-sjg@chromium.org> (raw)
In-Reply-To: <20220110031413.1970836-1-sjg@chromium.org>
Add tests to cover the bintool functionality.
Signed-off-by: Simon Glass <sjg@chromium.org>
---
(no changes since v1)
tools/binman/bintool_test.py | 353 +++++++++++++++++++++++++++++++++
tools/binman/btool/_testing.py | 36 ++++
2 files changed, 389 insertions(+)
create mode 100644 tools/binman/bintool_test.py
create mode 100644 tools/binman/btool/_testing.py
diff --git a/tools/binman/bintool_test.py b/tools/binman/bintool_test.py
new file mode 100644
index 00000000000..3d6bcdab9d1
--- /dev/null
+++ b/tools/binman/bintool_test.py
@@ -0,0 +1,353 @@
+# SPDX-License-Identifier: GPL-2.0+
+# Copyright 2022 Google LLC
+# Written by Simon Glass <sjg@chromium.org>
+#
+
+"""Tests for the Bintool class"""
+
+import collections
+import os
+import shutil
+import tempfile
+import unittest
+import unittest.mock
+import urllib.error
+
+from binman import bintool
+from binman.bintool import Bintool
+
+from patman import command
+from patman import terminal
+from patman import test_util
+from patman import tools
+
+# pylint: disable=R0904
+class TestBintool(unittest.TestCase):
+ """Tests for the Bintool class"""
+ def setUp(self):
+ # Create a temporary directory for test files
+ self._indir = tempfile.mkdtemp(prefix='bintool.')
+ self.seq = None
+ self.count = None
+ self.fname = None
+ self.btools = None
+
+ def tearDown(self):
+ """Remove the temporary input directory and its contents"""
+ if self._indir:
+ shutil.rmtree(self._indir)
+ self._indir = None
+
+ def test_missing_btype(self):
+ """Test that unknown bintool types are detected"""
+ with self.assertRaises(ValueError) as exc:
+ Bintool.create('missing')
+ self.assertIn("No module named 'binman.btool.missing'",
+ str(exc.exception))
+
+ def test_fresh_bintool(self):
+ """Check that the _testing bintool is not cached"""
+ btest = Bintool.create('_testing')
+ btest.present = True
+ btest2 = Bintool.create('_testing')
+ self.assertFalse(btest2.present)
+
+ def test_version(self):
+ """Check handling of a tool being present or absent"""
+ btest = Bintool.create('_testing')
+ with test_util.capture_sys_output() as (stdout, _):
+ btest.show()
+ self.assertFalse(btest.is_present())
+ self.assertIn('-', stdout.getvalue())
+ btest.present = True
+ self.assertTrue(btest.is_present())
+ self.assertEqual('123', btest.version())
+ with test_util.capture_sys_output() as (stdout, _):
+ btest.show()
+ self.assertIn('123', stdout.getvalue())
+
+ def test_fetch_present(self):
+ """Test fetching of a tool"""
+ btest = Bintool.create('_testing')
+ btest.present = True
+ col = terminal.Color()
+ self.assertEqual(bintool.PRESENT,
+ btest.fetch_tool(bintool.FETCH_ANY, col, True))
+
+ @classmethod
+ def check_fetch_url(cls, fake_download, method):
+ """Check the output from fetching a tool
+
+ Args:
+ fake_download (function): Function to call instead of
+ tools.Download()
+ method (bintool.FETCH_...: Fetch method to use
+
+ Returns:
+ str: Contents of stdout
+ """
+ btest = Bintool.create('_testing')
+ col = terminal.Color()
+ with unittest.mock.patch.object(tools, 'Download',
+ side_effect=fake_download):
+ with test_util.capture_sys_output() as (stdout, _):
+ btest.fetch_tool(method, col, False)
+ return stdout.getvalue()
+
+ def test_fetch_url_err(self):
+ """Test an error while fetching a tool from a URL"""
+ def fail_download(url):
+ """Take the tools.Download() function by raising an exception"""
+ raise urllib.error.URLError('my error')
+
+ stdout = self.check_fetch_url(fail_download, bintool.FETCH_ANY)
+ self.assertIn('my error', stdout)
+
+ def test_fetch_url_exception(self):
+ """Test an exception while fetching a tool from a URL"""
+ def cause_exc(url):
+ raise ValueError('exc error')
+
+ stdout = self.check_fetch_url(cause_exc, bintool.FETCH_ANY)
+ self.assertIn('exc error', stdout)
+
+ def test_fetch_method(self):
+ """Test fetching using a particular method"""
+ def fail_download(url):
+ """Take the tools.Download() function by raising an exception"""
+ raise urllib.error.URLError('my error')
+
+ stdout = self.check_fetch_url(fail_download, bintool.FETCH_BIN)
+ self.assertIn('my error', stdout)
+
+ def test_fetch_pass_fail(self):
+ """Test fetching multiple tools with some passing and some failing"""
+ def handle_download(_):
+ """Take the tools.Download() function by writing a file"""
+ if self.seq:
+ raise urllib.error.URLError('not found')
+ self.seq += 1
+ tools.WriteFile(fname, expected)
+ return fname, dirname
+
+ expected = b'this is a test'
+ dirname = os.path.join(self._indir, 'download_dir')
+ os.mkdir(dirname)
+ fname = os.path.join(dirname, 'downloaded')
+ destdir = os.path.join(self._indir, 'dest_dir')
+ os.mkdir(destdir)
+ dest_fname = os.path.join(destdir, '_testing')
+ self.seq = 0
+
+ with unittest.mock.patch.object(bintool, 'DOWNLOAD_DESTDIR', destdir):
+ with unittest.mock.patch.object(tools, 'Download',
+ side_effect=handle_download):
+ with test_util.capture_sys_output() as (stdout, _):
+ Bintool.fetch_tools(bintool.FETCH_ANY, ['_testing'] * 2)
+ self.assertTrue(os.path.exists(dest_fname))
+ data = tools.ReadFile(dest_fname)
+ self.assertEqual(expected, data)
+
+ lines = stdout.getvalue().splitlines()
+ self.assertTrue(len(lines) > 2)
+ self.assertEqual('Tools fetched: 1: _testing', lines[-2])
+ self.assertEqual('Failures: 1: _testing', lines[-1])
+
+ def test_tool_list(self):
+ """Test listing available tools"""
+ self.assertGreater(len(Bintool.get_tool_list()), 3)
+
+ def check_fetch_all(self, method):
+ """Helper to check the operation of fetching all tools"""
+
+ # pylint: disable=W0613
+ def fake_fetch(method, col, skip_present):
+ """Fakes the Binutils.fetch() function
+
+ Returns FETCHED and FAIL on alternate calls
+ """
+ self.seq += 1
+ result = bintool.FETCHED if self.seq & 1 else bintool.FAIL
+ self.count[result] += 1
+ return result
+
+ self.seq = 0
+ self.count = collections.defaultdict(int)
+ with unittest.mock.patch.object(bintool.Bintool, 'fetch_tool',
+ side_effect=fake_fetch):
+ with test_util.capture_sys_output() as (stdout, _):
+ Bintool.fetch_tools(method, ['all'])
+ lines = stdout.getvalue().splitlines()
+ self.assertIn(f'{self.count[bintool.FETCHED]}: ', lines[-2])
+ self.assertIn(f'{self.count[bintool.FAIL]}: ', lines[-1])
+
+ def test_fetch_all(self):
+ """Test fetching all tools"""
+ self.check_fetch_all(bintool.FETCH_ANY)
+
+ def test_fetch_all_specific(self):
+ """Test fetching all tools with a specific method"""
+ self.check_fetch_all(bintool.FETCH_BIN)
+
+ def test_fetch_missing(self):
+ """Test fetching missing tools"""
+ # pylint: disable=W0613
+ def fake_fetch2(method, col, skip_present):
+ """Fakes the Binutils.fetch() function
+
+ Returns PRESENT only for the '_testing' bintool
+ """
+ btool = list(self.btools.values())[self.seq]
+ self.seq += 1
+ print('fetch', btool.name)
+ if btool.name == '_testing':
+ return bintool.PRESENT
+ return bintool.FETCHED
+
+ # Preload a list of tools to return when get_tool_list() and create()
+ # are called
+ all_tools = Bintool.get_tool_list(True)
+ self.btools = collections.OrderedDict()
+ for name in all_tools:
+ self.btools[name] = Bintool.create(name)
+ self.seq = 0
+ with unittest.mock.patch.object(bintool.Bintool, 'fetch_tool',
+ side_effect=fake_fetch2):
+ with unittest.mock.patch.object(bintool.Bintool,
+ 'get_tool_list',
+ side_effect=[all_tools]):
+ with unittest.mock.patch.object(bintool.Bintool, 'create',
+ side_effect=self.btools.values()):
+ with test_util.capture_sys_output() as (stdout, _):
+ Bintool.fetch_tools(bintool.FETCH_ANY, ['missing'])
+ lines = stdout.getvalue().splitlines()
+ num_tools = len(self.btools)
+ fetched = [line for line in lines if 'Tools fetched:' in line].pop()
+ present = [line for line in lines if 'Already present:' in line].pop()
+ self.assertIn(f'{num_tools - 1}: ', fetched)
+ self.assertIn('1: ', present)
+
+ def check_build_method(self, write_file):
+ """Check the output from fetching using the BUILD method
+
+ Args:
+ write_file (bool): True to write the output file when 'make' is
+ called
+
+ Returns:
+ tuple:
+ str: Filename of written file (or missing 'make' output)
+ str: Contents of stdout
+ """
+ def fake_run(*cmd):
+ if cmd[0] == 'make':
+ # See Bintool.build_from_git()
+ tmpdir = cmd[2]
+ self.fname = os.path.join(tmpdir, 'pathname')
+ if write_file:
+ tools.WriteFile(self.fname, b'hello')
+
+ btest = Bintool.create('_testing')
+ col = terminal.Color()
+ self.fname = None
+ with unittest.mock.patch.object(bintool, 'DOWNLOAD_DESTDIR',
+ self._indir):
+ with unittest.mock.patch.object(tools, 'Run', side_effect=fake_run):
+ with test_util.capture_sys_output() as (stdout, _):
+ btest.fetch_tool(bintool.FETCH_BUILD, col, False)
+ fname = os.path.join(self._indir, '_testing')
+ return fname if write_file else self.fname, stdout.getvalue()
+
+ def test_build_method(self):
+ """Test fetching using the build method"""
+ fname, stdout = self.check_build_method(write_file=True)
+ self.assertTrue(os.path.exists(fname))
+ self.assertIn(f"writing to '{fname}", stdout)
+
+ def test_build_method_fail(self):
+ """Test fetching using the build method when no file is produced"""
+ fname, stdout = self.check_build_method(write_file=False)
+ self.assertFalse(os.path.exists(fname))
+ self.assertIn(f"File '{fname}' was not produced", stdout)
+
+ def test_install(self):
+ """Test fetching using the install method"""
+ btest = Bintool.create('_testing')
+ btest.install = True
+ col = terminal.Color()
+ with unittest.mock.patch.object(tools, 'Run', return_value=None):
+ with test_util.capture_sys_output() as _:
+ result = btest.fetch_tool(bintool.FETCH_BIN, col, False)
+ self.assertEqual(bintool.FETCHED, result)
+
+ def test_no_fetch(self):
+ """Test fetching when there is no method"""
+ btest = Bintool.create('_testing')
+ btest.disable = True
+ col = terminal.Color()
+ with test_util.capture_sys_output() as _:
+ result = btest.fetch_tool(bintool.FETCH_BIN, col, False)
+ self.assertEqual(bintool.FAIL, result)
+
+ def test_all_bintools(self):
+ """Test that all bintools can handle all available fetch types"""
+ def handle_download(_):
+ """Take the tools.Download() function by writing a file"""
+ tools.WriteFile(fname, expected)
+ return fname, dirname
+
+ def fake_run(*cmd):
+ if cmd[0] == 'make':
+ # See Bintool.build_from_git()
+ tmpdir = cmd[2]
+ self.fname = os.path.join(tmpdir, 'pathname')
+ tools.WriteFile(self.fname, b'hello')
+
+ expected = b'this is a test'
+ dirname = os.path.join(self._indir, 'download_dir')
+ os.mkdir(dirname)
+ fname = os.path.join(dirname, 'downloaded')
+
+ with unittest.mock.patch.object(tools, 'Run', side_effect=fake_run):
+ with unittest.mock.patch.object(tools, 'Download',
+ side_effect=handle_download):
+ with test_util.capture_sys_output() as _:
+ for name in Bintool.get_tool_list():
+ btool = Bintool.create(name)
+ for method in range(bintool.FETCH_COUNT):
+ result = btool.fetch(method)
+ self.assertTrue(result is not False)
+ if result is not True and result is not None:
+ result_fname, _ = result
+ self.assertTrue(os.path.exists(result_fname))
+ data = tools.ReadFile(result_fname)
+ self.assertEqual(expected, data)
+ os.remove(result_fname)
+
+ def test_all_bintool_versions(self):
+ """Test handling of bintool version when it cannot be run"""
+ all_tools = Bintool.get_tool_list()
+ for name in all_tools:
+ btool = Bintool.create(name)
+ with unittest.mock.patch.object(
+ btool, 'run_cmd_result', return_value=command.CommandResult()):
+ self.assertEqual('unknown', btool.version())
+
+ def test_force_missing(self):
+ btool = Bintool.create('_testing')
+ btool.present = True
+ self.assertTrue(btool.is_present())
+
+ btool.present = None
+ Bintool.set_missing_list(['_testing'])
+ self.assertFalse(btool.is_present())
+
+ def test_failed_command(self):
+ """Check that running a command that does not exist returns None"""
+ btool = Bintool.create('_testing')
+ result = btool.run_cmd_result('fred')
+ self.assertIsNone(result)
+
+
+if __name__ == "__main__":
+ unittest.main()
diff --git a/tools/binman/btool/_testing.py b/tools/binman/btool/_testing.py
new file mode 100644
index 00000000000..4005e8a8a5d
--- /dev/null
+++ b/tools/binman/btool/_testing.py
@@ -0,0 +1,36 @@
+# SPDX-License-Identifier: GPL-2.0+
+# Copyright 2022 Google LLC
+#
+"""Bintool used for testing
+
+This is not a real bintool, just one used for testing"""
+
+from binman import bintool
+
+# pylint: disable=C0103
+class Bintool_testing(bintool.Bintool):
+ """Bintool used for testing"""
+ def __init__(self, name):
+ super().__init__(name, 'testing')
+ self.present = False
+ self.install = False
+ self.disable = False
+
+ def is_present(self):
+ if self.present is None:
+ return super().is_present()
+ return self.present
+
+ def version(self):
+ return '123'
+
+ def fetch(self, method):
+ if self.disable:
+ return super().fetch(method)
+ if method == bintool.FETCH_BIN:
+ if self.install:
+ return self.apt_install('package')
+ return self.fetch_from_drive('junk')
+ if method == bintool.FETCH_BUILD:
+ return self.build_from_git('url', 'target', 'pathname')
+ return None
--
2.34.1.575.g55b058a8bb-goog
next prev parent reply other threads:[~2022-01-10 3:18 UTC|newest]
Thread overview: 83+ messages / expand[flat|nested] mbox.gz Atom feed top
2022-01-10 3:13 [PATCH v2 00/38] binman: Add support for bintools and missing tools Simon Glass
2022-01-10 3:13 ` [PATCH v2 01/38] Makefile: Fake external blobs by default with binman Simon Glass
2022-01-26 15:37 ` Simon Glass
2022-10-10 10:15 ` Rasmus Villemoes
2022-10-10 14:25 ` Tom Rini
2022-10-10 15:19 ` Simon Glass
2022-10-10 15:24 ` Tom Rini
2022-10-10 15:33 ` Simon Glass
2022-10-10 15:36 ` Tom Rini
2022-10-10 15:43 ` Simon Glass
2022-01-10 3:13 ` [PATCH v2 02/38] binman: Tweak elf tests for a toolchain change Simon Glass
2022-01-26 15:37 ` Simon Glass
2022-01-10 3:13 ` [PATCH v2 03/38] mkimage: Show the external-offset error Simon Glass
2022-01-26 15:37 ` Simon Glass
2022-01-10 3:13 ` [PATCH v2 04/38] binman: Expand the external FIT test a little Simon Glass
2022-01-26 15:37 ` Simon Glass
2022-01-10 3:13 ` [PATCH v2 05/38] patman: Allow running a tool and returning the full result Simon Glass
2022-01-26 15:37 ` Simon Glass
2022-01-10 3:13 ` [PATCH v2 06/38] buildman: Move the download function to tools Simon Glass
2022-01-26 15:37 ` Simon Glass
2022-01-10 3:13 ` [PATCH v2 07/38] patman: Tidy up the download function a little Simon Glass
2022-01-26 15:37 ` Simon Glass
2022-01-10 3:13 ` [PATCH v2 08/38] patman: Add a function to find a tool on the path Simon Glass
2022-01-26 15:37 ` Simon Glass
2022-01-10 3:13 ` [PATCH v2 09/38] binman: Write fake blobs to the output directory Simon Glass
2022-01-10 3:13 ` [PATCH v2 10/38] binman: Drop the image name from the fake-blob message Simon Glass
2022-01-26 15:37 ` Simon Glass
2022-01-10 3:13 ` [PATCH v2 11/38] binman: Allow faked blobs in blob-ext-list Simon Glass
2022-01-26 15:37 ` Simon Glass
2022-01-10 3:13 ` [PATCH v2 12/38] binman: Correct path for fip_util Simon Glass
2022-01-26 15:37 ` Simon Glass
2022-01-10 3:13 ` [PATCH v2 13/38] binman: Add installation instructions Simon Glass
2022-01-26 15:37 ` Simon Glass
2022-01-10 3:13 ` [PATCH v2 14/38] binman: Add support for bintools Simon Glass
2022-01-26 15:37 ` Simon Glass
2022-01-10 3:13 ` [PATCH v2 15/38] binman: Plumb in " Simon Glass
2022-01-26 15:37 ` Simon Glass
2022-01-10 3:13 ` Simon Glass [this message]
2022-01-26 15:37 ` [PATCH v2 16/38] binman: Add tests for bintool Simon Glass
2022-01-10 3:13 ` [PATCH v2 17/38] binman: Add a bintool implementation for cbfstool Simon Glass
2022-01-26 15:37 ` Simon Glass
2022-01-10 3:13 ` [PATCH v2 18/38] binman: Add a bintool implementation for fiptool Simon Glass
2022-01-26 15:36 ` Simon Glass
2022-01-10 3:13 ` [PATCH v2 19/38] binman: Add a bintool implementation for futility Simon Glass
2022-01-26 15:36 ` Simon Glass
2022-01-10 3:13 ` [PATCH v2 20/38] binman: Add a bintool implementation for ifwitool Simon Glass
2022-01-26 15:36 ` Simon Glass
2022-01-10 3:13 ` [PATCH v2 21/38] binman: Add a bintool implementation for mkimage Simon Glass
2022-01-26 15:36 ` Simon Glass
2022-01-10 3:13 ` [PATCH v2 22/38] binman: Enable bintool tests including cmdline processing Simon Glass
2022-01-26 15:36 ` Simon Glass
2022-01-10 3:13 ` [PATCH v2 23/38] binman: Convert to using the CBFS bintool Simon Glass
2022-01-26 15:36 ` Simon Glass
2022-01-10 3:13 ` [PATCH v2 24/38] binman: Convert to using the FIP bintool Simon Glass
2022-01-26 15:36 ` Simon Glass
2022-01-10 3:14 ` [PATCH v2 25/38] binman: Convert to using the futility bintool Simon Glass
2022-01-26 15:36 ` Simon Glass
2022-01-10 3:14 ` [PATCH v2 26/38] binman: Convert to using the ifwitool bintool Simon Glass
2022-01-26 15:36 ` Simon Glass
2022-01-10 3:14 ` [PATCH v2 27/38] binman: Convert to using the mkimage bintool Simon Glass
2022-01-26 15:36 ` Simon Glass
2022-01-10 3:14 ` [PATCH v2 28/38] binman: Move compression into binman Simon Glass
2022-01-26 15:36 ` Simon Glass
2022-01-10 3:14 ` [PATCH v2 29/38] binman: Tidy up pylint warnings in comp_util Simon Glass
2022-01-26 15:36 ` Simon Glass
2022-01-10 3:14 ` [PATCH v2 30/38] binman: Add a bintool implementation for lz4 Simon Glass
2022-01-26 15:36 ` Simon Glass
2022-01-10 3:14 ` [PATCH v2 31/38] binman: Convert to using the lz4 bintool Simon Glass
2022-01-26 15:36 ` Simon Glass
2022-01-10 3:14 ` [PATCH v2 32/38] binman: Add a bintool implementation for lzma_alone Simon Glass
2022-01-26 15:36 ` Simon Glass
2022-01-10 3:14 ` [PATCH v2 33/38] binman: Convert to using the lzma_alone bintool Simon Glass
2022-01-26 15:36 ` Simon Glass
2022-01-10 3:14 ` [PATCH v2 34/38] binman: Plumb in support for missing bintools Simon Glass
2022-01-26 15:36 ` Simon Glass
2022-01-10 3:14 ` [PATCH v2 35/38] binman: Complete test coverage of comp_util Simon Glass
2022-01-26 15:36 ` Simon Glass
2022-01-10 3:14 ` [PATCH v2 36/38] binman: Add a command to generate bintool docs Simon Glass
2022-01-26 15:36 ` Simon Glass
2022-01-10 3:14 ` [PATCH v2 37/38] binman: Add documentation for bintools Simon Glass
2022-01-26 15:36 ` Simon Glass
2022-01-10 3:14 ` [PATCH v2 38/38] RFC: Move Odroid-C2 to use binman to produce the image Simon Glass
2022-01-21 2:02 ` [PATCH v2 00/38] binman: Add support for bintools and missing tools Simon Glass
Reply instructions:
You may reply publicly to this message via plain-text email
using any one of the following methods:
* Save the following mbox file, import it into your mail client,
and reply-to-all from there: mbox
Avoid top-posting and favor interleaved quoting:
https://en.wikipedia.org/wiki/Posting_style#Interleaved_style
* Reply using the --to, --cc, and --in-reply-to
switches of git-send-email(1):
git send-email \
--in-reply-to=20220110031413.1970836-17-sjg@chromium.org \
--to=sjg@chromium.org \
--cc=u-boot@lists.denx.de \
/path/to/YOUR_REPLY
https://kernel.org/pub/software/scm/git/docs/git-send-email.html
* If your mail client supports setting the In-Reply-To header
via mailto: links, try the mailto: link
Be sure your reply has a Subject: header at the top and a blank line
before the message body.
This is a public inbox, see mirroring instructions
for how to clone and mirror all data and code used for this inbox