From: Simon Glass <simon.glass@canonical.com> Add TestBuilderFuncs class with 9 new tests to improve coverage of builder.py methods that were previously uncovered: - Parsing nm output for function sizes - Combining static function symbols with the same name - Parsing .config style defconfig files - Parsing autoconf.h header files - Handling missing config files gracefully - Squashing y-values in config parsing - Parsing uboot.env environment files - Handling missing environment files - Handling malformed environment entries Co-developed-by: Claude Opus 4.5 <noreply@anthropic.com> Signed-off-by: Simon Glass <simon.glass@canonical.com> --- tools/buildman/main.py | 3 +- tools/buildman/test.py | 185 +++++++++++++++++++++++++++++++++++++++++ 2 files changed, 187 insertions(+), 1 deletion(-) diff --git a/tools/buildman/main.py b/tools/buildman/main.py index d85e8dd428d..04993dcd87b 100755 --- a/tools/buildman/main.py +++ b/tools/buildman/main.py @@ -49,7 +49,8 @@ def run_tests(skip_net_tests, debug, verbose, args): result = test_util.run_test_suites( 'buildman', debug, verbose, False, False, args.threads, test_name, [], [test.TestBuildOutput, test.TestBuildBoards, test.TestBuild, - test.TestBuildConfig, test.TestBuildMisc, func_test.TestFunctional, + test.TestBuildConfig, test.TestBuildMisc, test.TestBuilderFuncs, + func_test.TestFunctional, test_boards.TestBoards, test_bsettings.TestBsettings, 'buildman.toolchain']) diff --git a/tools/buildman/test.py b/tools/buildman/test.py index fb5dcd09555..828d05781cc 100644 --- a/tools/buildman/test.py +++ b/tools/buildman/test.py @@ -1269,5 +1269,190 @@ class TestBuildMisc(TestBuildBase): 'other_board')) +class TestBuilderFuncs(TestBuildBase): + """Tests for individual Builder methods""" + + def test_read_func_sizes(self): + """Test read_func_sizes() function""" + build = builder.Builder(self.toolchains, self.base_dir, None, 0, 2) + + # Create test data simulating 'nm' output + # NM_SYMBOL_TYPES = 'tTdDbBr' - text, data, bss, rodata + nm_output = '''00000100 T func_one +00000200 t func_two +00000050 T func_three +00000030 d data_var +00000010 W weak_func +''' + with tempfile.NamedTemporaryFile(mode='w', delete=False) as tmp: + tmp.write(nm_output) + tmp_name = tmp.name + + try: + with open(tmp_name, 'r', encoding='utf-8') as fhandle: + sizes = build.read_func_sizes(tmp_name, fhandle) + + # T, t, d are in NM_SYMBOL_TYPES, W is not + self.assertEqual(0x100, sizes['func_one']) + self.assertEqual(0x200, sizes['func_two']) + self.assertEqual(0x50, sizes['func_three']) + self.assertEqual(0x30, sizes['data_var']) + self.assertNotIn('weak_func', sizes) + finally: + os.unlink(tmp_name) + + def test_read_func_sizes_static(self): + """Test read_func_sizes() with static function symbols""" + build = builder.Builder(self.toolchains, self.base_dir, None, 0, 2) + + # Test static functions (have . in name after first char) + nm_output = '''00000100 t func.1234 +00000050 t func.5678 +''' + with tempfile.NamedTemporaryFile(mode='w', delete=False) as tmp: + tmp.write(nm_output) + tmp_name = tmp.name + + try: + with open(tmp_name, 'r', encoding='utf-8') as fhandle: + sizes = build.read_func_sizes(tmp_name, fhandle) + + # Static functions should be combined under 'static.func' + self.assertEqual(0x100 + 0x50, sizes['static.func']) + finally: + os.unlink(tmp_name) + + def test_process_config_defconfig(self): + """Test _process_config() with .config style file""" + build = builder.Builder(self.toolchains, self.base_dir, None, 0, 2) + + config_data = '''# This is a comment +CONFIG_OPTION_A=y +CONFIG_OPTION_B="string" +CONFIG_OPTION_C=123 +# CONFIG_OPTION_D is not set +''' + with tempfile.NamedTemporaryFile(mode='w', delete=False, + suffix='.config') as tmp: + tmp.write(config_data) + tmp_name = tmp.name + + try: + config = build._process_config(tmp_name) + + self.assertEqual('y', config['CONFIG_OPTION_A']) + self.assertEqual('"string"', config['CONFIG_OPTION_B']) + self.assertEqual('123', config['CONFIG_OPTION_C']) + # Comments should be ignored + self.assertNotIn('CONFIG_OPTION_D', config) + finally: + os.unlink(tmp_name) + + def test_process_config_autoconf_h(self): + """Test _process_config() with autoconf.h style file""" + build = builder.Builder(self.toolchains, self.base_dir, None, 0, 2) + + config_data = '''/* Auto-generated header */ +#define CONFIG_OPTION_A 1 +#define CONFIG_OPTION_B "value" +#define CONFIG_OPTION_C +#define NOT_CONFIG 1 +''' + with tempfile.NamedTemporaryFile(mode='w', delete=False, + suffix='.h') as tmp: + tmp.write(config_data) + tmp_name = tmp.name + + try: + config = build._process_config(tmp_name) + + self.assertEqual('1', config['CONFIG_OPTION_A']) + self.assertEqual('"value"', config['CONFIG_OPTION_B']) + # #define without value gets empty string (squash_config_y=False) + self.assertEqual('', config['CONFIG_OPTION_C']) + # Non-CONFIG_ defines should be ignored + self.assertNotIn('NOT_CONFIG', config) + finally: + os.unlink(tmp_name) + + def test_process_config_nonexistent(self): + """Test _process_config() with non-existent file""" + build = builder.Builder(self.toolchains, self.base_dir, None, 0, 2) + + config = build._process_config('/nonexistent/path/config') + self.assertEqual({}, config) + + def test_process_config_squash_y(self): + """Test _process_config() with squash_config_y enabled""" + build = builder.Builder(self.toolchains, self.base_dir, None, 0, 2) + build.squash_config_y = True + + config_data = '''CONFIG_OPTION_A=y +CONFIG_OPTION_B=n +#define CONFIG_OPTION_C +''' + with tempfile.NamedTemporaryFile(mode='w', delete=False) as tmp: + tmp.write(config_data) + tmp_name = tmp.name + + try: + config = build._process_config(tmp_name) + + # y should be squashed to 1 + self.assertEqual('1', config['CONFIG_OPTION_A']) + # n should remain n + self.assertEqual('n', config['CONFIG_OPTION_B']) + # Empty #define should get '1' when squash_config_y is True + self.assertEqual('1', config['CONFIG_OPTION_C']) + finally: + os.unlink(tmp_name) + + def test_process_environment(self): + """Test _process_environment() function""" + build = builder.Builder(self.toolchains, self.base_dir, None, 0, 2) + + # Environment file uses null-terminated strings + env_data = 'bootcmd=run bootm\x00bootdelay=3\x00console=ttyS0\x00' + with tempfile.NamedTemporaryFile(mode='w', delete=False) as tmp: + tmp.write(env_data) + tmp_name = tmp.name + + try: + env = build._process_environment(tmp_name) + + self.assertEqual('run bootm', env['bootcmd']) + self.assertEqual('3', env['bootdelay']) + self.assertEqual('ttyS0', env['console']) + finally: + os.unlink(tmp_name) + + def test_process_environment_nonexistent(self): + """Test _process_environment() with non-existent file""" + build = builder.Builder(self.toolchains, self.base_dir, None, 0, 2) + + env = build._process_environment('/nonexistent/path/uboot.env') + self.assertEqual({}, env) + + def test_process_environment_invalid_lines(self): + """Test _process_environment() handles invalid lines gracefully""" + build = builder.Builder(self.toolchains, self.base_dir, None, 0, 2) + + # Include lines without '=' which should be ignored + env_data = 'valid=value\x00invalid_no_equals\x00another=good\x00' + with tempfile.NamedTemporaryFile(mode='w', delete=False) as tmp: + tmp.write(env_data) + tmp_name = tmp.name + + try: + env = build._process_environment(tmp_name) + + self.assertEqual('value', env['valid']) + self.assertEqual('good', env['another']) + # Invalid line should be silently ignored + self.assertNotIn('invalid_no_equals', env) + finally: + os.unlink(tmp_name) + + if __name__ == "__main__": unittest.main() -- 2.43.0