From patchwork Fri Jan 9 18:30:54 2026 Content-Type: text/plain; charset="utf-8" MIME-Version: 1.0 Content-Transfer-Encoding: 7bit X-Patchwork-Submitter: Simon Glass X-Patchwork-Id: 1380 Return-Path: X-Original-To: u-boot-concept@u-boot.org Delivered-To: u-boot-concept@u-boot.org DKIM-Signature: v=1; a=rsa-sha256; c=relaxed/simple; d=u-boot.org; s=default; t=1767983503; bh=60dYpSe4hZ5HhS2YokSmTmQlROXGRU70+hgQyWT0qME=; h=From:To:Date:In-Reply-To:References:CC:Subject:List-Id: List-Archive:List-Help:List-Owner:List-Post:List-Subscribe: List-Unsubscribe:From; b=iP7QPSFcAOY4gNcky73NtlO4c60UQWoZU4hlv8B9U8yFJnRpEp2/I0o1z/aLiYzbY uJauf5qf8OohK6mXmRNh14zhe/FCh+VclDu/wv1qcw0E6gscwnhItmtudY8ql3CdVK g8IVr5okFrchAMKcI3hoP2+snFg459NwP7D2g2Tbk2T3hbtRBmFSquzLw0s/t+VbnB JRWqXNX7g1IRZknI1DM2/tve1QvLROoAseW9O3b9C4sUXpTREItbVZ9InVST2VXBiu Z45EV9Py7vlF690XXTJQcfpMe5l7YL8BYaXLkthlRG48uRGOhsAb0CAb242N/hdG3F +u/WJFWYCXiDw== Received: from localhost (localhost [127.0.0.1]) by mail.u-boot.org (Postfix) with ESMTP id 4F15469216 for ; Fri, 9 Jan 2026 11:31:43 -0700 (MST) X-Virus-Scanned: Debian amavis at Received: from mail.u-boot.org ([127.0.0.1]) by localhost (mail.u-boot.org [127.0.0.1]) (amavis, port 10024) with ESMTP id IHzwJnlgjetp for ; Fri, 9 Jan 2026 11:31:43 -0700 (MST) DKIM-Signature: v=1; a=rsa-sha256; c=relaxed/simple; d=u-boot.org; s=default; t=1767983503; bh=60dYpSe4hZ5HhS2YokSmTmQlROXGRU70+hgQyWT0qME=; h=From:To:Date:In-Reply-To:References:CC:Subject:List-Id: List-Archive:List-Help:List-Owner:List-Post:List-Subscribe: List-Unsubscribe:From; b=iP7QPSFcAOY4gNcky73NtlO4c60UQWoZU4hlv8B9U8yFJnRpEp2/I0o1z/aLiYzbY uJauf5qf8OohK6mXmRNh14zhe/FCh+VclDu/wv1qcw0E6gscwnhItmtudY8ql3CdVK g8IVr5okFrchAMKcI3hoP2+snFg459NwP7D2g2Tbk2T3hbtRBmFSquzLw0s/t+VbnB JRWqXNX7g1IRZknI1DM2/tve1QvLROoAseW9O3b9C4sUXpTREItbVZ9InVST2VXBiu Z45EV9Py7vlF690XXTJQcfpMe5l7YL8BYaXLkthlRG48uRGOhsAb0CAb242N/hdG3F +u/WJFWYCXiDw== Received: from mail.u-boot.org (localhost [127.0.0.1]) by mail.u-boot.org (Postfix) with ESMTP id 3D8AC6920C for ; Fri, 9 Jan 2026 11:31:43 -0700 (MST) DKIM-Signature: v=1; a=rsa-sha256; c=relaxed/simple; d=u-boot.org; s=default; t=1767983501; bh=m0jfyUrxoWkh02/pCvj7SxSfuUu+brSsDcG+UX3K2CI=; h=From:To:Cc:Subject:Date:In-Reply-To:References:From; b=vKksuK+9pNgKOG3nAbPsDUc6TWWf6EtP1yzKBYzIECY+z9ylhHpAlvg4b/+FEmDiw vSdqdr+Grahnres9G24qnd8sf71bfTfvHXHFsbCVBOESeUAxnYE4RiQXhEikR1wVFY 0OP071LyJfZUolsAnObZDOQl1Boj+d8+Ruu0ov4j7+YlmvlwrD0lIrBnuA94f6gIM7 F7UWFPV6mOpPUuNz77QQ0iesfLmP3x/Sqlk9XM2NnTUuu478Qge0Tol/3RJvS75S7Y L6bLUbVRIKxOdpfqqAhgrjD4m0T9YPXU1o/fPkoq+A6EzeRq2iBjgCHh4G6IegE6hH fJ8ju2NEz84ag== Received: from localhost (localhost [127.0.0.1]) by mail.u-boot.org (Postfix) with ESMTP id C430F69208; Fri, 9 Jan 2026 11:31:41 -0700 (MST) X-Virus-Scanned: Debian amavis at Received: from mail.u-boot.org ([127.0.0.1]) by localhost (mail.u-boot.org [127.0.0.1]) (amavis, port 10026) with ESMTP id Hyvhtu6MtspR; Fri, 9 Jan 2026 11:31:41 -0700 (MST) DKIM-Signature: v=1; a=rsa-sha256; c=relaxed/simple; d=u-boot.org; s=default; t=1767983497; bh=1e6BsRFikYneUir4AwiF8MPdYdUDebNazmOt3AVuy40=; h=From:To:Cc:Subject:Date:In-Reply-To:References:From; b=R3sRDmjXq9eH9c0GmLABe5BDozH6xwjSenuJ0h1eXLtskQNFZmOqtVSqPVSBrw+T7 1oTaWvbftk2cn3FzCTJIqUzcM6ISZPEuTvAyPlSgw8S9JBZhyeHDN1Ajgs07mYqmy9 lgNzHEpYEQgD94usfP26YRZwk2UtRScOztN43GV/FW0E99wkijIImD+g3IP1vqPl5G yfZOlIwRkoB+V/rK+vJiSXcw4IqKFsj+EsOmqGUAMaSX0UHIY/tPrjZeNmazmam3Yh panio4HyfipjwBcSrPOEKNBkef1rXhBaGVkGAo7IZP+KOU3CPwDLopEEAtyBn/uPty XDDiRvG03Bk8A== Received: from u-boot.org (unknown [73.34.74.121]) by mail.u-boot.org (Postfix) with ESMTPSA id 372EA69209; Fri, 9 Jan 2026 11:31:37 -0700 (MST) From: Simon Glass To: U-Boot Concept Date: Fri, 9 Jan 2026 11:30:54 -0700 Message-ID: <20260109183116.3262115-3-sjg@u-boot.org> X-Mailer: git-send-email 2.43.0 In-Reply-To: <20260109183116.3262115-1-sjg@u-boot.org> References: <20260109183116.3262115-1-sjg@u-boot.org> MIME-Version: 1.0 Message-ID-Hash: 7HL44XGCEK22LCZGEVFUJGW7QVU6VAWE X-Message-ID-Hash: 7HL44XGCEK22LCZGEVFUJGW7QVU6VAWE X-MailFrom: sjg@u-boot.org X-Mailman-Rule-Misses: dmarc-mitigation; no-senders; approved; loop; banned-address; emergency; member-moderation; nonmember-moderation; administrivia; implicit-dest; max-recipients; max-size; news-moderation; no-subject; digests; suspicious-header CC: Heinrich Schuchardt , Simon Glass , "Claude Opus 4 . 5" X-Mailman-Version: 3.3.10 Precedence: list Subject: [Concept] [PATCH 02/18] buildman: Add tests for builder helper functions List-Id: Discussion and patches related to U-Boot Concept Archived-At: List-Archive: List-Help: List-Owner: List-Post: List-Subscribe: List-Unsubscribe: From: Simon Glass 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 Signed-off-by: Simon Glass --- 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()