diff options
author | Tom Rini <trini@konsulko.com> | 2022-07-08 14:39:07 -0400 |
---|---|---|
committer | Tom Rini <trini@konsulko.com> | 2022-07-08 14:39:07 -0400 |
commit | 9ff4ce8abc627b8696c9bd6fd726dd1dbf4b9a5c (patch) | |
tree | d28c5d99d4996b080ec0c38f24a0232d611ea847 /tools | |
parent | 7bc0be96f79344d7b103dd64c31be0574f7b39e9 (diff) | |
parent | e87da5704ffa6fc782d93d137fa30a37a5df3566 (diff) |
Merge tag 'dm-pull-28jun22' of https://source.denx.de/u-boot/custodians/u-boot-dm into next
nman external-symbol improvements
Driver model memory-usage reporting
patman test-reporting improvements
Add bloblist design goals
Diffstat (limited to 'tools')
24 files changed, 274 insertions, 132 deletions
diff --git a/tools/binman/elf.py b/tools/binman/elf.py index afa05e58fd..6d440ddf21 100644 --- a/tools/binman/elf.py +++ b/tools/binman/elf.py @@ -25,6 +25,9 @@ try: except: # pragma: no cover ELF_TOOLS = False +# BSYM in little endian, keep in sync with include/binman_sym.h +BINMAN_SYM_MAGIC_VALUE = 0x4d595342 + # Information about an EFL symbol: # section (str): Name of the section containing this symbol # address (int): Address of the symbol (its value) @@ -223,9 +226,12 @@ def LookupAndWriteSymbols(elf_fname, entry, section): raise ValueError('%s has size %d: only 4 and 8 are supported' % (msg, sym.size)) - # Look up the symbol in our entry tables. - value = section.GetImage().LookupImageSymbol(name, sym.weak, msg, - base.address) + if name == '_binman_sym_magic': + value = BINMAN_SYM_MAGIC_VALUE + else: + # Look up the symbol in our entry tables. + value = section.GetImage().LookupImageSymbol(name, sym.weak, + msg, base.address) if value is None: value = -1 pack_string = pack_string.lower() diff --git a/tools/binman/elf_test.py b/tools/binman/elf_test.py index 02bc108374..5a51c64cfe 100644 --- a/tools/binman/elf_test.py +++ b/tools/binman/elf_test.py @@ -127,7 +127,7 @@ class TestElf(unittest.TestCase): elf_fname = self.ElfTestFile('u_boot_binman_syms') with self.assertRaises(ValueError) as e: elf.LookupAndWriteSymbols(elf_fname, entry, section) - self.assertIn('entry_path has offset 4 (size 8) but the contents size ' + self.assertIn('entry_path has offset 8 (size 8) but the contents size ' 'is a', str(e.exception)) def testMissingImageStart(self): @@ -161,18 +161,20 @@ class TestElf(unittest.TestCase): This should produce -1 values for all thress symbols, taking up the first 16 bytes of the image. """ - entry = FakeEntry(24) + entry = FakeEntry(28) section = FakeSection(sym_value=None) elf_fname = self.ElfTestFile('u_boot_binman_syms') elf.LookupAndWriteSymbols(elf_fname, entry, section) - self.assertEqual(tools.get_bytes(255, 20) + tools.get_bytes(ord('a'), 4), - entry.data) + expected = (struct.pack('<L', elf.BINMAN_SYM_MAGIC_VALUE) + + tools.get_bytes(255, 20) + + tools.get_bytes(ord('a'), 4)) + self.assertEqual(expected, entry.data) def testDebug(self): """Check that enabling debug in the elf module produced debug output""" try: tout.init(tout.DEBUG) - entry = FakeEntry(20) + entry = FakeEntry(24) section = FakeSection() elf_fname = self.ElfTestFile('u_boot_binman_syms') with test_util.capture_sys_output() as (stdout, stderr): diff --git a/tools/binman/ftest.py b/tools/binman/ftest.py index b5cf549703..fa1f421c05 100644 --- a/tools/binman/ftest.py +++ b/tools/binman/ftest.py @@ -43,8 +43,8 @@ from patman import tout # Contents of test files, corresponding to different entry types U_BOOT_DATA = b'1234' U_BOOT_IMG_DATA = b'img' -U_BOOT_SPL_DATA = b'56780123456789abcdefghi' -U_BOOT_TPL_DATA = b'tpl9876543210fedcbazyw' +U_BOOT_SPL_DATA = b'56780123456789abcdefghijklm' +U_BOOT_TPL_DATA = b'tpl9876543210fedcbazywvuts' BLOB_DATA = b'89' ME_DATA = b'0abcd' VGA_DATA = b'vga' @@ -1406,8 +1406,9 @@ class TestFunctional(unittest.TestCase): elf_fname = self.ElfTestFile('u_boot_binman_syms') syms = elf.GetSymbols(elf_fname, ['binman', 'image']) addr = elf.GetSymbolAddress(elf_fname, '__image_copy_start') + self.assertEqual(syms['_binman_sym_magic'].address, addr) self.assertEqual(syms['_binman_u_boot_spl_any_prop_offset'].address, - addr) + addr + 4) self._SetupSplElf('u_boot_binman_syms') data = self._DoReadFileDtb(dts, entry_args=entry_args, @@ -1415,17 +1416,17 @@ class TestFunctional(unittest.TestCase): # The image should contain the symbols from u_boot_binman_syms.c # Note that image_pos is adjusted by the base address of the image, # which is 0x10 in our test image - sym_values = struct.pack('<LQLL', 0x00, - u_boot_offset + len(U_BOOT_DATA), + sym_values = struct.pack('<LLQLL', elf.BINMAN_SYM_MAGIC_VALUE, + 0x00, u_boot_offset + len(U_BOOT_DATA), 0x10 + u_boot_offset, 0x04) - expected = (sym_values + base_data[20:] + + expected = (sym_values + base_data[24:] + tools.get_bytes(0xff, 1) + U_BOOT_DATA + sym_values + - base_data[20:]) + base_data[24:]) self.assertEqual(expected, data) def testSymbols(self): """Test binman can assign symbols embedded in U-Boot""" - self.checkSymbols('053_symbols.dts', U_BOOT_SPL_DATA, 0x18) + self.checkSymbols('053_symbols.dts', U_BOOT_SPL_DATA, 0x1c) def testSymbolsNoDtb(self): """Test binman can assign symbols embedded in U-Boot SPL""" @@ -3610,20 +3611,20 @@ class TestFunctional(unittest.TestCase): def _CheckSymbolsTplSection(self, dts, expected_vals): data = self._DoReadFile(dts) - sym_values = struct.pack('<LQLL', *expected_vals) + sym_values = struct.pack('<LLQLL', elf.BINMAN_SYM_MAGIC_VALUE, *expected_vals) upto1 = 4 + len(U_BOOT_SPL_DATA) - expected1 = tools.get_bytes(0xff, 4) + sym_values + U_BOOT_SPL_DATA[20:] + expected1 = tools.get_bytes(0xff, 4) + sym_values + U_BOOT_SPL_DATA[24:] self.assertEqual(expected1, data[:upto1]) upto2 = upto1 + 1 + len(U_BOOT_SPL_DATA) - expected2 = tools.get_bytes(0xff, 1) + sym_values + U_BOOT_SPL_DATA[20:] + expected2 = tools.get_bytes(0xff, 1) + sym_values + U_BOOT_SPL_DATA[24:] self.assertEqual(expected2, data[upto1:upto2]) - upto3 = 0x34 + len(U_BOOT_DATA) + upto3 = 0x3c + len(U_BOOT_DATA) expected3 = tools.get_bytes(0xff, 1) + U_BOOT_DATA self.assertEqual(expected3, data[upto2:upto3]) - expected4 = sym_values + U_BOOT_TPL_DATA[20:] + expected4 = sym_values + U_BOOT_TPL_DATA[24:] self.assertEqual(expected4, data[upto3:upto3 + len(U_BOOT_TPL_DATA)]) def testSymbolsTplSection(self): @@ -3631,14 +3632,14 @@ class TestFunctional(unittest.TestCase): self._SetupSplElf('u_boot_binman_syms') self._SetupTplElf('u_boot_binman_syms') self._CheckSymbolsTplSection('149_symbols_tpl.dts', - [0x04, 0x1c, 0x10 + 0x34, 0x04]) + [0x04, 0x20, 0x10 + 0x3c, 0x04]) def testSymbolsTplSectionX86(self): """Test binman can assign symbols in a section with end-at-4gb""" self._SetupSplElf('u_boot_binman_syms_x86') self._SetupTplElf('u_boot_binman_syms_x86') self._CheckSymbolsTplSection('155_symbols_tpl_x86.dts', - [0xffffff04, 0xffffff1c, 0xffffff34, + [0xffffff04, 0xffffff20, 0xffffff3c, 0x04]) def testPackX86RomIfwiSectiom(self): @@ -4488,7 +4489,7 @@ class TestFunctional(unittest.TestCase): def testSymbolsSubsection(self): """Test binman can assign symbols from a subsection""" - self.checkSymbols('187_symbols_sub.dts', U_BOOT_SPL_DATA, 0x18) + self.checkSymbols('187_symbols_sub.dts', U_BOOT_SPL_DATA, 0x1c) def testReadImageEntryArg(self): """Test reading an image that would need an entry arg to generate""" diff --git a/tools/binman/main.py b/tools/binman/main.py index 5fb9404ef6..14432a8d0d 100755 --- a/tools/binman/main.py +++ b/tools/binman/main.py @@ -13,7 +13,6 @@ import os import site import sys import traceback -import unittest # Get the absolute path to this file at run-time our_path = os.path.dirname(os.path.realpath(__file__)) @@ -73,19 +72,18 @@ def RunTests(debug, verbosity, processes, test_preserve_dirs, args, toolpath): from binman import image_test import doctest - result = unittest.TestResult() test_name = args and args[0] or None # Run the entry tests first ,since these need to be the first to import the # 'entry' module. - test_util.run_test_suites( - result, debug, verbosity, test_preserve_dirs, processes, test_name, + result = test_util.run_test_suites( + 'binman', debug, verbosity, test_preserve_dirs, processes, test_name, toolpath, [bintool_test.TestBintool, entry_test.TestEntry, ftest.TestFunctional, fdt_test.TestFdt, elf_test.TestElf, image_test.TestImage, cbfs_util_test.TestCbfs, fip_util_test.TestFip]) - return test_util.report_result('binman', test_name, result) + return (0 if result.wasSuccessful() else 1) def RunTestCoverage(toolpath): """Run the tests and check that we get 100% coverage""" diff --git a/tools/binman/test/021_image_pad.dts b/tools/binman/test/021_image_pad.dts index 1ff8dab296..c5abbbcdd6 100644 --- a/tools/binman/test/021_image_pad.dts +++ b/tools/binman/test/021_image_pad.dts @@ -10,7 +10,7 @@ }; u-boot { - offset = <24>; + offset = <28>; }; }; }; diff --git a/tools/binman/test/024_sorted.dts b/tools/binman/test/024_sorted.dts index b79d9adf68..b54f9b1419 100644 --- a/tools/binman/test/024_sorted.dts +++ b/tools/binman/test/024_sorted.dts @@ -7,7 +7,7 @@ binman { sort-by-offset; u-boot { - offset = <26>; + offset = <30>; }; u-boot-spl { diff --git a/tools/binman/test/028_pack_4gb_outside.dts b/tools/binman/test/028_pack_4gb_outside.dts index 11a1f6059e..b6ad7fb56a 100644 --- a/tools/binman/test/028_pack_4gb_outside.dts +++ b/tools/binman/test/028_pack_4gb_outside.dts @@ -13,7 +13,7 @@ }; u-boot-spl { - offset = <0xffffffe7>; + offset = <0xffffffe3>; }; }; }; diff --git a/tools/binman/test/029_x86_rom.dts b/tools/binman/test/029_x86_rom.dts index 88aa007bba..ad8f9d6e1b 100644 --- a/tools/binman/test/029_x86_rom.dts +++ b/tools/binman/test/029_x86_rom.dts @@ -7,13 +7,13 @@ binman { sort-by-offset; end-at-4gb; - size = <32>; + size = <36>; u-boot { - offset = <0xffffffe0>; + offset = <0xffffffdc>; }; u-boot-spl { - offset = <0xffffffe7>; + offset = <0xffffffe3>; }; }; }; diff --git a/tools/binman/test/053_symbols.dts b/tools/binman/test/053_symbols.dts index 2965809276..b28f34a72f 100644 --- a/tools/binman/test/053_symbols.dts +++ b/tools/binman/test/053_symbols.dts @@ -10,7 +10,7 @@ }; u-boot { - offset = <0x18>; + offset = <0x1c>; }; u-boot-spl2 { diff --git a/tools/binman/test/149_symbols_tpl.dts b/tools/binman/test/149_symbols_tpl.dts index 0a4ab3f1fa..4e649c4597 100644 --- a/tools/binman/test/149_symbols_tpl.dts +++ b/tools/binman/test/149_symbols_tpl.dts @@ -11,12 +11,12 @@ }; u-boot-spl2 { - offset = <0x1c>; + offset = <0x20>; type = "u-boot-spl"; }; u-boot { - offset = <0x34>; + offset = <0x3c>; }; section { diff --git a/tools/binman/test/155_symbols_tpl_x86.dts b/tools/binman/test/155_symbols_tpl_x86.dts index 9d7dc51b3d..e1ce33e67f 100644 --- a/tools/binman/test/155_symbols_tpl_x86.dts +++ b/tools/binman/test/155_symbols_tpl_x86.dts @@ -14,12 +14,12 @@ }; u-boot-spl2 { - offset = <0xffffff1c>; + offset = <0xffffff20>; type = "u-boot-spl"; }; u-boot { - offset = <0xffffff34>; + offset = <0xffffff3c>; }; section { diff --git a/tools/binman/test/187_symbols_sub.dts b/tools/binman/test/187_symbols_sub.dts index 54511a7371..3ab62d3721 100644 --- a/tools/binman/test/187_symbols_sub.dts +++ b/tools/binman/test/187_symbols_sub.dts @@ -11,7 +11,7 @@ }; u-boot { - offset = <24>; + offset = <28>; }; }; diff --git a/tools/binman/test/Makefile b/tools/binman/test/Makefile index 57057e2d58..bea8567c9b 100644 --- a/tools/binman/test/Makefile +++ b/tools/binman/test/Makefile @@ -21,7 +21,7 @@ CC = $(CROSS_COMPILE)gcc OBJCOPY = $(CROSS_COMPILE)objcopy VPATH := $(SRC) -CFLAGS := -march=i386 -m32 -nostdlib -I $(SRC)../../../include \ +CFLAGS := -march=i386 -m32 -nostdlib -I $(SRC)../../../include -I $(SRC) \ -Wl,--no-dynamic-linker LDS_UCODE := -T $(SRC)u_boot_ucode_ptr.lds diff --git a/tools/binman/test/generated/autoconf.h b/tools/binman/test/generated/autoconf.h new file mode 100644 index 0000000000..6a23039f46 --- /dev/null +++ b/tools/binman/test/generated/autoconf.h @@ -0,0 +1,3 @@ +#define CONFIG_BINMAN 1 +#define CONFIG_SPL_BUILD 1 +#define CONFIG_SPL_BINMAN_SYMBOLS 1 diff --git a/tools/binman/test/u_boot_binman_syms.c b/tools/binman/test/u_boot_binman_syms.c index 37fc339ce8..ed761246ae 100644 --- a/tools/binman/test/u_boot_binman_syms.c +++ b/tools/binman/test/u_boot_binman_syms.c @@ -5,9 +5,13 @@ * Simple program to create some binman symbols. This is used by binman tests. */ -#define CONFIG_BINMAN +typedef unsigned long ulong; + +#include <linux/kconfig.h> #include <binman_sym.h> +DECLARE_BINMAN_MAGIC_SYM; + binman_sym_declare(unsigned long, u_boot_spl_any, offset); binman_sym_declare(unsigned long long, u_boot_spl2, offset); binman_sym_declare(unsigned long, u_boot_any, image_pos); diff --git a/tools/binman/test/u_boot_binman_syms_size.c b/tools/binman/test/u_boot_binman_syms_size.c index 7224bc1863..fa41b3d9a3 100644 --- a/tools/binman/test/u_boot_binman_syms_size.c +++ b/tools/binman/test/u_boot_binman_syms_size.c @@ -5,7 +5,11 @@ * Simple program to create some binman symbols. This is used by binman tests. */ -#define CONFIG_BINMAN +typedef unsigned long ulong; + +#include <linux/kconfig.h> #include <binman_sym.h> +DECLARE_BINMAN_MAGIC_SYM; + binman_sym_declare(char, u_boot_spl, pos); diff --git a/tools/buildman/main.py b/tools/buildman/main.py index 3b6af24080..67c560c48d 100755 --- a/tools/buildman/main.py +++ b/tools/buildman/main.py @@ -11,7 +11,6 @@ import multiprocessing import os import re import sys -import unittest # Bring in the patman libraries our_path = os.path.dirname(os.path.realpath(__file__)) @@ -34,19 +33,18 @@ def RunTests(skip_net_tests, verboose, args): from buildman import test import doctest - result = unittest.TestResult() test_name = args and args[0] or None if skip_net_tests: test.use_network = False # Run the entry tests first ,since these need to be the first to import the # 'entry' module. - test_util.run_test_suites( - result, False, verboose, False, None, test_name, [], + result = test_util.run_test_suites( + 'buildman', False, verboose, False, None, test_name, [], [test.TestBuild, func_test.TestFunctional, 'buildman.toolchain', 'patman.gitutil']) - return test_util.report_result('buildman', test_name, result) + return (0 if result.wasSuccessful() else 1) options, args = cmdline.ParseArgs() diff --git a/tools/concurrencytest/concurrencytest.py b/tools/concurrencytest/concurrencytest.py index 5e88b94f41..1c4f03f37e 100644 --- a/tools/concurrencytest/concurrencytest.py +++ b/tools/concurrencytest/concurrencytest.py @@ -31,6 +31,7 @@ from subunit import ProtocolTestCase, TestProtocolClient from subunit.test_results import AutoTimingTestResultDecorator from testtools import ConcurrentTestSuite, iterate_tests +from testtools.content import TracebackContent, text_content _all__ = [ @@ -43,11 +44,81 @@ _all__ = [ CPU_COUNT = cpu_count() -def fork_for_tests(concurrency_num=CPU_COUNT): +class BufferingTestProtocolClient(TestProtocolClient): + """A TestProtocolClient which can buffer the test outputs + + This class captures the stdout and stderr output streams of the + tests as it runs them, and includes the output texts in the subunit + stream as additional details. + + Args: + stream: A file-like object to write a subunit stream to + buffer (bool): True to capture test stdout/stderr outputs and + include them in the test details + """ + def __init__(self, stream, buffer=True): + super().__init__(stream) + self.buffer = buffer + + def _addOutcome(self, outcome, test, error=None, details=None, + error_permitted=True): + """Report a test outcome to the subunit stream + + The parent class uses this function as a common implementation + for various methods that report successes, errors, failures, etc. + + This version automatically upgrades the error tracebacks to the + new 'details' format by wrapping them in a Content object, so + that we can include the captured test output in the test result + details. + + Args: + outcome: A string describing the outcome - used as the + event name in the subunit stream. + test: The test case whose outcome is to be reported + error: Standard unittest positional argument form - an + exc_info tuple. + details: New Testing-in-python drafted API; a dict from + string to subunit.Content objects. + error_permitted: If True then one and only one of error or + details must be supplied. If False then error must not + be supplied and details is still optional. + """ + if details is None: + details = {} + + # Parent will raise an exception if error_permitted is False but + # error is not None. We want that exception in that case, so + # don't touch error when error_permitted is explicitly False. + if error_permitted and error is not None: + # Parent class prefers error over details + details['traceback'] = TracebackContent(error, test) + error_permitted = False + error = None + + if self.buffer: + stdout = sys.stdout.getvalue() + if stdout: + details['stdout'] = text_content(stdout) + + stderr = sys.stderr.getvalue() + if stderr: + details['stderr'] = text_content(stderr) + + return super()._addOutcome(outcome, test, error=error, + details=details, error_permitted=error_permitted) + + +def fork_for_tests(concurrency_num=CPU_COUNT, buffer=False): """Implementation of `make_tests` used to construct `ConcurrentTestSuite`. :param concurrency_num: number of processes to use. """ + if buffer: + test_protocol_client_class = BufferingTestProtocolClient + else: + test_protocol_client_class = TestProtocolClient + def do_fork(suite): """Take suite and start up multiple runners by forking (Unix only). @@ -76,7 +147,7 @@ def fork_for_tests(concurrency_num=CPU_COUNT): # child actually gets keystrokes for pdb etc). sys.stdin.close() subunit_result = AutoTimingTestResultDecorator( - TestProtocolClient(stream) + test_protocol_client_class(stream) ) process_suite.run(subunit_result) except: @@ -93,7 +164,13 @@ def fork_for_tests(concurrency_num=CPU_COUNT): else: os.close(c2pwrite) stream = os.fdopen(c2pread, 'rb') - test = ProtocolTestCase(stream) + # If we don't pass the second argument here, it defaults + # to sys.stdout.buffer down the line. But if we don't + # pass it *now*, it may be resolved after sys.stdout is + # replaced with a StringIO (to capture tests' outputs) + # which doesn't have a buffer attribute and can end up + # occasionally causing a 'broken-runner' error. + test = ProtocolTestCase(stream, sys.stdout.buffer) result.append(test) return result return do_fork diff --git a/tools/dtoc/main.py b/tools/dtoc/main.py index fac9db9c78..5508759d4d 100755 --- a/tools/dtoc/main.py +++ b/tools/dtoc/main.py @@ -24,7 +24,6 @@ see doc/driver-model/of-plat.rst from argparse import ArgumentParser import os import sys -import unittest # Bring in the patman libraries our_path = os.path.dirname(os.path.realpath(__file__)) @@ -49,18 +48,18 @@ def run_tests(processes, args): from dtoc import test_src_scan from dtoc import test_dtoc - result = unittest.TestResult() sys.argv = [sys.argv[0]] test_name = args.files and args.files[0] or None test_dtoc.setup() - test_util.run_test_suites( - result, debug=True, verbosity=1, test_preserve_dirs=False, + result = test_util.run_test_suites( + toolname='dtoc', debug=True, verbosity=1, test_preserve_dirs=False, processes=processes, test_name=test_name, toolpath=[], class_and_module_list=[test_dtoc.TestDtoc,test_src_scan.TestSrcScan]) - return test_util.report_result('binman', test_name, result) + return (0 if result.wasSuccessful() else 1) + def RunTestCoverage(): """Run the tests and check that we get 100% coverage""" diff --git a/tools/dtoc/test_dtoc.py b/tools/dtoc/test_dtoc.py index c81bcc9c32..879ca2ab2b 100755 --- a/tools/dtoc/test_dtoc.py +++ b/tools/dtoc/test_dtoc.py @@ -616,8 +616,11 @@ struct dm_test_pdata __attribute__ ((section (".priv_data"))) u8 _denx_u_boot_test_bus_priv_some_bus[sizeof(struct dm_test_priv)] \t__attribute__ ((section (".priv_data"))); #include <dm/test.h> -u8 _denx_u_boot_test_bus_ucplat_some_bus[sizeof(struct dm_test_uclass_priv)] +u8 _denx_u_boot_test_bus_ucplat_some_bus[sizeof(struct dm_test_uclass_plat)] \t__attribute__ ((section (".priv_data"))); +#include <dm/test.h> +u8 _denx_u_boot_test_bus_uc_priv_some_bus[sizeof(struct dm_test_uclass_priv)] + __attribute__ ((section (".priv_data"))); #include <test.h> DM_DEVICE_INST(some_bus) = { @@ -628,6 +631,7 @@ DM_DEVICE_INST(some_bus) = { \t.driver_data\t= DM_TEST_TYPE_FIRST, \t.priv_\t\t= _denx_u_boot_test_bus_priv_some_bus, \t.uclass\t\t= DM_UCLASS_REF(testbus), +\t.uclass_priv_ = _denx_u_boot_test_bus_uc_priv_some_bus, \t.uclass_node\t= { \t\t.prev = &DM_UCLASS_REF(testbus)->dev_head, \t\t.next = &DM_UCLASS_REF(testbus)->dev_head, diff --git a/tools/dtoc/test_fdt.py b/tools/dtoc/test_fdt.py index 914ed6aed5..3baf4437cd 100755 --- a/tools/dtoc/test_fdt.py +++ b/tools/dtoc/test_fdt.py @@ -780,25 +780,17 @@ def RunTests(args): Args: args: List of positional args provided to fdt. This can hold a test name to execute (as in 'fdt -t testFdt', for example) + + Returns: + Return code, 0 on success """ - result = unittest.TestResult() - sys.argv = [sys.argv[0]] test_name = args and args[0] or None - for module in (TestFdt, TestNode, TestProp, TestFdtUtil): - if test_name: - try: - suite = unittest.TestLoader().loadTestsFromName(test_name, module) - except AttributeError: - continue - else: - suite = unittest.TestLoader().loadTestsFromTestCase(module) - suite.run(result) - - print(result) - for _, err in result.errors: - print(err) - for _, err in result.failures: - print(err) + result = test_util.run_test_suites( + 'test_fdt', False, False, False, None, test_name, None, + [TestFdt, TestNode, TestProp, TestFdtUtil]) + + return (0 if result.wasSuccessful() else 1) + if __name__ != '__main__': sys.exit(1) @@ -816,6 +808,7 @@ parser.add_option('-T', '--test-coverage', action='store_true', # Run our meagre tests if options.test: - RunTests(args) + ret_code = RunTests(args) + sys.exit(ret_code) elif options.test_coverage: RunTestCoverage() diff --git a/tools/patman/main.py b/tools/patman/main.py index 2a2ac45709..66d4806c8d 100755 --- a/tools/patman/main.py +++ b/tools/patman/main.py @@ -12,7 +12,6 @@ import re import shutil import sys import traceback -import unittest if __name__ == "__main__": # Allow 'from patman import xxx to work' @@ -134,13 +133,12 @@ if args.cmd == 'test': import doctest from patman import func_test - result = unittest.TestResult() - test_util.run_test_suites( - result, False, False, False, None, None, None, + result = test_util.run_test_suites( + 'patman', False, False, False, None, None, None, [test_checkpatch.TestPatch, func_test.TestFunctional, 'gitutil', 'settings', 'terminal']) - sys.exit(test_util.report_result('patman', args.testname, result)) + sys.exit(0 if result.wasSuccessful() else 1) # Process commits, produce patches files, check them, email them elif args.cmd == 'send': diff --git a/tools/patman/settings.py b/tools/patman/settings.py index 7c2b5c196c..4c847fe88f 100644 --- a/tools/patman/settings.py +++ b/tools/patman/settings.py @@ -246,8 +246,10 @@ def _UpdateDefaults(main_parser, config): # Collect the defaults from each parser defaults = {} + parser_defaults = [] for parser in parsers: pdefs = parser.parse_known_args()[0] + parser_defaults.append(pdefs) defaults.update(vars(pdefs)) # Go through the settings and collect defaults @@ -264,8 +266,11 @@ def _UpdateDefaults(main_parser, config): else: print("WARNING: Unknown setting %s" % name) - # Set all the defaults (this propagates through all subparsers) + # Set all the defaults and manually propagate them to subparsers main_parser.set_defaults(**defaults) + for parser, pdefs in zip(parsers, parser_defaults): + parser.set_defaults(**{ k: v for k, v in defaults.items() + if k in pdefs }) def _ReadAliasFile(fname): """Read in the U-Boot git alias file if it exists. diff --git a/tools/patman/test_util.py b/tools/patman/test_util.py index c60eb3628e..c27e0b39e5 100644 --- a/tools/patman/test_util.py +++ b/tools/patman/test_util.py @@ -15,6 +15,7 @@ from patman import command from io import StringIO +buffer_outputs = True use_concurrent = True try: from concurrencytest.concurrencytest import ConcurrentTestSuite @@ -102,49 +103,85 @@ def capture_sys_output(): sys.stdout, sys.stderr = old_out, old_err -def report_result(toolname:str, test_name: str, result: unittest.TestResult): - """Report the results from a suite of tests +class FullTextTestResult(unittest.TextTestResult): + """A test result class that can print extended text results to a stream + + This is meant to be used by a TestRunner as a result class. Like + TextTestResult, this prints out the names of tests as they are run, + errors as they occur, and a summary of the results at the end of the + test run. Beyond those, this prints information about skipped tests, + expected failures and unexpected successes. Args: - toolname: Name of the tool that ran the tests - test_name: Name of test that was run, or None for all - result: A unittest.TestResult object containing the results + stream: A file-like object to write results to + descriptions (bool): True to print descriptions with test names + verbosity (int): Detail of printed output per test as they run + Test stdout and stderr always get printed when buffering + them is disabled by the test runner. In addition to that, + 0: Print nothing + 1: Print a dot per test + 2: Print test names + 3: Print test names, and buffered outputs for failing tests """ - # Remove errors which just indicate a missing test. Since Python v3.5 If an - # ImportError or AttributeError occurs while traversing name then a - # synthetic test that raises that error when run will be returned. These - # errors are included in the errors accumulated by result.errors. - if test_name: - errors = [] - - for test, err in result.errors: - if ("has no attribute '%s'" % test_name) not in err: - errors.append((test, err)) - result.testsRun -= 1 - result.errors = errors - - print(result) - for test, err in result.errors: - print(test.id(), err) - for test, err in result.failures: - print(err, result.failures) - if result.skipped: - print('%d %s test%s SKIPPED:' % (len(result.skipped), toolname, - 's' if len(result.skipped) > 1 else '')) - for skip_info in result.skipped: - print('%s: %s' % (skip_info[0], skip_info[1])) - if result.errors or result.failures: - print('%s tests FAILED' % toolname) - return 1 - return 0 - - -def run_test_suites(result, debug, verbosity, test_preserve_dirs, processes, + def __init__(self, stream, descriptions, verbosity): + self.verbosity = verbosity + super().__init__(stream, descriptions, verbosity) + + def printErrors(self): + "Called by TestRunner after test run to summarize the tests" + # The parent class doesn't keep unexpected successes in the same + # format as the rest. Adapt it to what printErrorList expects. + unexpected_successes = [ + (test, 'Test was expected to fail, but succeeded.\n') + for test in self.unexpectedSuccesses + ] + + super().printErrors() # FAIL and ERROR + self.printErrorList('SKIP', self.skipped) + self.printErrorList('XFAIL', self.expectedFailures) + self.printErrorList('XPASS', unexpected_successes) + + def addError(self, test, err): + """Called when an error has occurred.""" + super().addError(test, err) + self._mirrorOutput &= self.verbosity >= 3 + + def addFailure(self, test, err): + """Called when a test has failed.""" + super().addFailure(test, err) + self._mirrorOutput &= self.verbosity >= 3 + + def addSubTest(self, test, subtest, err): + """Called at the end of a subtest.""" + super().addSubTest(test, subtest, err) + self._mirrorOutput &= self.verbosity >= 3 + + def addSuccess(self, test): + """Called when a test has completed successfully""" + super().addSuccess(test) + # Don't print stdout/stderr for successful tests + self._mirrorOutput = False + + def addSkip(self, test, reason): + """Called when a test is skipped.""" + # Add empty line to keep spacing consistent with other results + if not reason.endswith('\n'): + reason += '\n' + super().addSkip(test, reason) + self._mirrorOutput &= self.verbosity >= 3 + + def addExpectedFailure(self, test, err): + """Called when an expected failure/error occurred.""" + super().addExpectedFailure(test, err) + self._mirrorOutput &= self.verbosity >= 3 + + +def run_test_suites(toolname, debug, verbosity, test_preserve_dirs, processes, test_name, toolpath, class_and_module_list): """Run a series of test suites and collect the results Args: - result: A unittest.TestResult object to add the results to + toolname: Name of the tool that ran the tests debug: True to enable debugging, which shows a full stack trace on error verbosity: Verbosity level to use (0-4) test_preserve_dirs: True to preserve the input directory used by tests @@ -158,11 +195,6 @@ def run_test_suites(result, debug, verbosity, test_preserve_dirs, processes, class_and_module_list: List of test classes (type class) and module names (type str) to run """ - for module in class_and_module_list: - if isinstance(module, str) and (not test_name or test_name == module): - suite = doctest.DocTestSuite(module) - suite.run(result) - sys.argv = [sys.argv[0]] if debug: sys.argv.append('-D') @@ -174,6 +206,22 @@ def run_test_suites(result, debug, verbosity, test_preserve_dirs, processes, suite = unittest.TestSuite() loader = unittest.TestLoader() + runner = unittest.TextTestRunner( + stream=sys.stdout, + verbosity=(1 if verbosity is None else verbosity), + buffer=buffer_outputs, + resultclass=FullTextTestResult, + ) + + if use_concurrent and processes != 1: + suite = ConcurrentTestSuite(suite, + fork_for_tests(processes or multiprocessing.cpu_count(), + buffer=buffer_outputs)) + + for module in class_and_module_list: + if isinstance(module, str) and (not test_name or test_name == module): + suite.addTests(doctest.DocTestSuite(module)) + for module in class_and_module_list: if isinstance(module, str): continue @@ -184,15 +232,17 @@ def run_test_suites(result, debug, verbosity, test_preserve_dirs, processes, preserve_outdirs=test_preserve_dirs and test_name is not None, toolpath=toolpath, verbosity=verbosity) if test_name: - try: + # Since Python v3.5 If an ImportError or AttributeError occurs + # while traversing a name then a synthetic test that raises that + # error when run will be returned. Check that the requested test + # exists, otherwise these errors are included in the results. + if test_name in loader.getTestCaseNames(module): suite.addTests(loader.loadTestsFromName(test_name, module)) - except AttributeError: - continue else: suite.addTests(loader.loadTestsFromTestCase(module)) - if use_concurrent and processes != 1: - concurrent_suite = ConcurrentTestSuite(suite, - fork_for_tests(processes or multiprocessing.cpu_count())) - concurrent_suite.run(result) - else: - suite.run(result) + + print(f" Running {toolname} tests ".center(70, "=")) + result = runner.run(suite) + print() + + return result |