mirror of
https://github.com/AsahiLinux/u-boot
synced 2024-11-25 14:10:43 +00:00
binman: Add tests for bintool
Add tests to cover the bintool functionality. Signed-off-by: Simon Glass <sjg@chromium.org>
This commit is contained in:
parent
386c63cfad
commit
3b47dfa506
2 changed files with 389 additions and 0 deletions
353
tools/binman/bintool_test.py
Normal file
353
tools/binman/bintool_test.py
Normal file
|
@ -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()
|
36
tools/binman/btool/_testing.py
Normal file
36
tools/binman/btool/_testing.py
Normal file
|
@ -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
|
Loading…
Reference in a new issue