1 # SPDX-License-Identifier: GPL-2.0+
2 # Copyright 2022 Google LLC
3 # Written by Simon Glass <sjg@chromium.org>
6 """Tests for the Bintool class"""
16 from binman import bintool
17 from binman.bintool import Bintool
19 from patman import command
20 from patman import terminal
21 from patman import test_util
22 from patman import tools
24 # pylint: disable=R0904
25 class TestBintool(unittest.TestCase):
26 """Tests for the Bintool class"""
28 # Create a temporary directory for test files
29 self._indir = tempfile.mkdtemp(prefix='bintool.')
36 """Remove the temporary input directory and its contents"""
38 shutil.rmtree(self._indir)
41 def test_missing_btype(self):
42 """Test that unknown bintool types are detected"""
43 with self.assertRaises(ValueError) as exc:
44 Bintool.create('missing')
45 self.assertIn("No module named 'binman.btool.missing'",
48 def test_fresh_bintool(self):
49 """Check that the _testing bintool is not cached"""
50 btest = Bintool.create('_testing')
52 btest2 = Bintool.create('_testing')
53 self.assertFalse(btest2.present)
55 def test_version(self):
56 """Check handling of a tool being present or absent"""
57 btest = Bintool.create('_testing')
58 with test_util.capture_sys_output() as (stdout, _):
60 self.assertFalse(btest.is_present())
61 self.assertIn('-', stdout.getvalue())
63 self.assertTrue(btest.is_present())
64 self.assertEqual('123', btest.version())
65 with test_util.capture_sys_output() as (stdout, _):
67 self.assertIn('123', stdout.getvalue())
69 def test_fetch_present(self):
70 """Test fetching of a tool"""
71 btest = Bintool.create('_testing')
73 col = terminal.Color()
74 self.assertEqual(bintool.PRESENT,
75 btest.fetch_tool(bintool.FETCH_ANY, col, True))
78 def check_fetch_url(cls, fake_download, method):
79 """Check the output from fetching a tool
82 fake_download (function): Function to call instead of
84 method (bintool.FETCH_...: Fetch method to use
87 str: Contents of stdout
89 btest = Bintool.create('_testing')
90 col = terminal.Color()
91 with unittest.mock.patch.object(tools, 'Download',
92 side_effect=fake_download):
93 with test_util.capture_sys_output() as (stdout, _):
94 btest.fetch_tool(method, col, False)
95 return stdout.getvalue()
97 def test_fetch_url_err(self):
98 """Test an error while fetching a tool from a URL"""
99 def fail_download(url):
100 """Take the tools.Download() function by raising an exception"""
101 raise urllib.error.URLError('my error')
103 stdout = self.check_fetch_url(fail_download, bintool.FETCH_ANY)
104 self.assertIn('my error', stdout)
106 def test_fetch_url_exception(self):
107 """Test an exception while fetching a tool from a URL"""
109 raise ValueError('exc error')
111 stdout = self.check_fetch_url(cause_exc, bintool.FETCH_ANY)
112 self.assertIn('exc error', stdout)
114 def test_fetch_method(self):
115 """Test fetching using a particular method"""
116 def fail_download(url):
117 """Take the tools.Download() function by raising an exception"""
118 raise urllib.error.URLError('my error')
120 stdout = self.check_fetch_url(fail_download, bintool.FETCH_BIN)
121 self.assertIn('my error', stdout)
123 def test_fetch_pass_fail(self):
124 """Test fetching multiple tools with some passing and some failing"""
125 def handle_download(_):
126 """Take the tools.Download() function by writing a file"""
128 raise urllib.error.URLError('not found')
130 tools.WriteFile(fname, expected)
131 return fname, dirname
133 expected = b'this is a test'
134 dirname = os.path.join(self._indir, 'download_dir')
136 fname = os.path.join(dirname, 'downloaded')
137 destdir = os.path.join(self._indir, 'dest_dir')
139 dest_fname = os.path.join(destdir, '_testing')
142 with unittest.mock.patch.object(bintool, 'DOWNLOAD_DESTDIR', destdir):
143 with unittest.mock.patch.object(tools, 'Download',
144 side_effect=handle_download):
145 with test_util.capture_sys_output() as (stdout, _):
146 Bintool.fetch_tools(bintool.FETCH_ANY, ['_testing'] * 2)
147 self.assertTrue(os.path.exists(dest_fname))
148 data = tools.ReadFile(dest_fname)
149 self.assertEqual(expected, data)
151 lines = stdout.getvalue().splitlines()
152 self.assertTrue(len(lines) > 2)
153 self.assertEqual('Tools fetched: 1: _testing', lines[-2])
154 self.assertEqual('Failures: 1: _testing', lines[-1])
156 def test_tool_list(self):
157 """Test listing available tools"""
158 self.assertGreater(len(Bintool.get_tool_list()), 3)
160 def check_fetch_all(self, method):
161 """Helper to check the operation of fetching all tools"""
163 # pylint: disable=W0613
164 def fake_fetch(method, col, skip_present):
165 """Fakes the Binutils.fetch() function
167 Returns FETCHED and FAIL on alternate calls
170 result = bintool.FETCHED if self.seq & 1 else bintool.FAIL
171 self.count[result] += 1
175 self.count = collections.defaultdict(int)
176 with unittest.mock.patch.object(bintool.Bintool, 'fetch_tool',
177 side_effect=fake_fetch):
178 with test_util.capture_sys_output() as (stdout, _):
179 Bintool.fetch_tools(method, ['all'])
180 lines = stdout.getvalue().splitlines()
181 self.assertIn(f'{self.count[bintool.FETCHED]}: ', lines[-2])
182 self.assertIn(f'{self.count[bintool.FAIL]}: ', lines[-1])
184 def test_fetch_all(self):
185 """Test fetching all tools"""
186 self.check_fetch_all(bintool.FETCH_ANY)
188 def test_fetch_all_specific(self):
189 """Test fetching all tools with a specific method"""
190 self.check_fetch_all(bintool.FETCH_BIN)
192 def test_fetch_missing(self):
193 """Test fetching missing tools"""
194 # pylint: disable=W0613
195 def fake_fetch2(method, col, skip_present):
196 """Fakes the Binutils.fetch() function
198 Returns PRESENT only for the '_testing' bintool
200 btool = list(self.btools.values())[self.seq]
202 print('fetch', btool.name)
203 if btool.name == '_testing':
204 return bintool.PRESENT
205 return bintool.FETCHED
207 # Preload a list of tools to return when get_tool_list() and create()
209 all_tools = Bintool.get_tool_list(True)
210 self.btools = collections.OrderedDict()
211 for name in all_tools:
212 self.btools[name] = Bintool.create(name)
214 with unittest.mock.patch.object(bintool.Bintool, 'fetch_tool',
215 side_effect=fake_fetch2):
216 with unittest.mock.patch.object(bintool.Bintool,
218 side_effect=[all_tools]):
219 with unittest.mock.patch.object(bintool.Bintool, 'create',
220 side_effect=self.btools.values()):
221 with test_util.capture_sys_output() as (stdout, _):
222 Bintool.fetch_tools(bintool.FETCH_ANY, ['missing'])
223 lines = stdout.getvalue().splitlines()
224 num_tools = len(self.btools)
225 fetched = [line for line in lines if 'Tools fetched:' in line].pop()
226 present = [line for line in lines if 'Already present:' in line].pop()
227 self.assertIn(f'{num_tools - 1}: ', fetched)
228 self.assertIn('1: ', present)
230 def check_build_method(self, write_file):
231 """Check the output from fetching using the BUILD method
234 write_file (bool): True to write the output file when 'make' is
239 str: Filename of written file (or missing 'make' output)
240 str: Contents of stdout
244 # See Bintool.build_from_git()
246 self.fname = os.path.join(tmpdir, 'pathname')
248 tools.WriteFile(self.fname, b'hello')
250 btest = Bintool.create('_testing')
251 col = terminal.Color()
253 with unittest.mock.patch.object(bintool, 'DOWNLOAD_DESTDIR',
255 with unittest.mock.patch.object(tools, 'Run', side_effect=fake_run):
256 with test_util.capture_sys_output() as (stdout, _):
257 btest.fetch_tool(bintool.FETCH_BUILD, col, False)
258 fname = os.path.join(self._indir, '_testing')
259 return fname if write_file else self.fname, stdout.getvalue()
261 def test_build_method(self):
262 """Test fetching using the build method"""
263 fname, stdout = self.check_build_method(write_file=True)
264 self.assertTrue(os.path.exists(fname))
265 self.assertIn(f"writing to '{fname}", stdout)
267 def test_build_method_fail(self):
268 """Test fetching using the build method when no file is produced"""
269 fname, stdout = self.check_build_method(write_file=False)
270 self.assertFalse(os.path.exists(fname))
271 self.assertIn(f"File '{fname}' was not produced", stdout)
273 def test_install(self):
274 """Test fetching using the install method"""
275 btest = Bintool.create('_testing')
277 col = terminal.Color()
278 with unittest.mock.patch.object(tools, 'Run', return_value=None):
279 with test_util.capture_sys_output() as _:
280 result = btest.fetch_tool(bintool.FETCH_BIN, col, False)
281 self.assertEqual(bintool.FETCHED, result)
283 def test_no_fetch(self):
284 """Test fetching when there is no method"""
285 btest = Bintool.create('_testing')
287 col = terminal.Color()
288 with test_util.capture_sys_output() as _:
289 result = btest.fetch_tool(bintool.FETCH_BIN, col, False)
290 self.assertEqual(bintool.FAIL, result)
292 def test_all_bintools(self):
293 """Test that all bintools can handle all available fetch types"""
294 def handle_download(_):
295 """Take the tools.Download() function by writing a file"""
296 tools.WriteFile(fname, expected)
297 return fname, dirname
301 # See Bintool.build_from_git()
303 self.fname = os.path.join(tmpdir, 'pathname')
304 tools.WriteFile(self.fname, b'hello')
306 expected = b'this is a test'
307 dirname = os.path.join(self._indir, 'download_dir')
309 fname = os.path.join(dirname, 'downloaded')
311 with unittest.mock.patch.object(tools, 'Run', side_effect=fake_run):
312 with unittest.mock.patch.object(tools, 'Download',
313 side_effect=handle_download):
314 with test_util.capture_sys_output() as _:
315 for name in Bintool.get_tool_list():
316 btool = Bintool.create(name)
317 for method in range(bintool.FETCH_COUNT):
318 result = btool.fetch(method)
319 self.assertTrue(result is not False)
320 if result is not True and result is not None:
321 result_fname, _ = result
322 self.assertTrue(os.path.exists(result_fname))
323 data = tools.ReadFile(result_fname)
324 self.assertEqual(expected, data)
325 os.remove(result_fname)
327 def test_all_bintool_versions(self):
328 """Test handling of bintool version when it cannot be run"""
329 all_tools = Bintool.get_tool_list()
330 for name in all_tools:
331 btool = Bintool.create(name)
332 with unittest.mock.patch.object(
333 btool, 'run_cmd_result', return_value=command.CommandResult()):
334 self.assertEqual('unknown', btool.version())
336 def test_force_missing(self):
337 btool = Bintool.create('_testing')
339 self.assertTrue(btool.is_present())
342 Bintool.set_missing_list(['_testing'])
343 self.assertFalse(btool.is_present())
345 def test_failed_command(self):
346 """Check that running a command that does not exist returns None"""
347 btool = Bintool.create('_testing')
348 result = btool.run_cmd_result('fred')
349 self.assertIsNone(result)
352 if __name__ == "__main__":