buildman: Increase test coverage for boards.py
Add a new test_boards.py with 26 tests to achieve 100% coverage for boards.py. Co-developed-by: Claude <noreply@anthropic.com> Signed-off-by: Simon Glass <simon.glass@canonical.com>
This commit is contained in:
@@ -1267,3 +1267,4 @@ something: me
|
||||
boards.ExtendedParser.parse_data('bert', 'name: katie was here')
|
||||
self.assertEqual('bert:1: Invalid name',
|
||||
str(exc.exception))
|
||||
|
||||
|
||||
@@ -41,6 +41,7 @@ def run_tests(skip_net_tests, debug, verbose, args):
|
||||
# pylint: disable=C0415
|
||||
from buildman import func_test
|
||||
from buildman import test
|
||||
from buildman import test_boards
|
||||
|
||||
test_name = args.terms and args.terms[0] or None
|
||||
if skip_net_tests:
|
||||
@@ -50,7 +51,8 @@ def run_tests(skip_net_tests, debug, verbose, args):
|
||||
# 'entry' module.
|
||||
result = test_util.run_test_suites(
|
||||
'buildman', debug, verbose, False, False, args.threads, test_name, [],
|
||||
[test.TestBuild, func_test.TestFunctional, 'buildman.toolchain'])
|
||||
[test.TestBuild, func_test.TestFunctional, test_boards.TestBoards,
|
||||
'buildman.toolchain'])
|
||||
|
||||
return (0 if result.wasSuccessful() else 1)
|
||||
|
||||
|
||||
739
tools/buildman/test_boards.py
Normal file
739
tools/buildman/test_boards.py
Normal file
@@ -0,0 +1,739 @@
|
||||
# SPDX-License-Identifier: GPL-2.0+
|
||||
# Copyright (c) 2024 Google, Inc
|
||||
#
|
||||
|
||||
"""Tests for boards.py"""
|
||||
|
||||
import errno
|
||||
import multiprocessing
|
||||
import os
|
||||
from pathlib import Path
|
||||
import shutil
|
||||
import tempfile
|
||||
import time
|
||||
import unittest
|
||||
from unittest import mock
|
||||
|
||||
from buildman import board
|
||||
from buildman import boards
|
||||
from buildman.boards import Extended
|
||||
from u_boot_pylib import terminal
|
||||
from u_boot_pylib import tools
|
||||
|
||||
|
||||
BOARDS = [
|
||||
['Active', 'arm', 'armv7', '', 'Tester', 'ARM Board 0', 'board0', ''],
|
||||
['Active', 'arm', 'armv7', '', 'Tester', 'ARM Board 1', 'board1', ''],
|
||||
['Active', 'powerpc', 'powerpc', '', 'Tester', 'PowerPC board 1', 'board2', ''],
|
||||
['Active', 'sandbox', 'sandbox', '', 'Tester', 'Sandbox board', 'board4', ''],
|
||||
]
|
||||
|
||||
|
||||
class TestBoards(unittest.TestCase):
|
||||
"""Test boards.py functionality"""
|
||||
|
||||
def setUp(self):
|
||||
self._base_dir = tempfile.mkdtemp()
|
||||
self._output_dir = tempfile.mkdtemp()
|
||||
self._git_dir = os.path.join(self._base_dir, 'src')
|
||||
self._buildman_dir = os.path.dirname(os.path.realpath(__file__))
|
||||
self._test_dir = os.path.join(self._buildman_dir, 'test')
|
||||
|
||||
# Set up some fake source files
|
||||
shutil.copytree(self._test_dir, self._git_dir)
|
||||
|
||||
# Avoid sending any output and clear all terminal output
|
||||
terminal.set_print_test_mode()
|
||||
terminal.get_print_test_lines()
|
||||
|
||||
self._boards = boards.Boards()
|
||||
for brd in BOARDS:
|
||||
self._boards.add_board(board.Board(*brd))
|
||||
|
||||
def tearDown(self):
|
||||
shutil.rmtree(self._base_dir)
|
||||
shutil.rmtree(self._output_dir)
|
||||
|
||||
def test_try_remove(self):
|
||||
"""Test try_remove() function"""
|
||||
# Test removing a file that doesn't exist - should not raise
|
||||
boards.try_remove('/nonexistent/path/to/file')
|
||||
|
||||
# Test removing a file that does exist
|
||||
fname = os.path.join(self._base_dir, 'test_remove')
|
||||
tools.write_file(fname, b'test')
|
||||
self.assertTrue(os.path.exists(fname))
|
||||
boards.try_remove(fname)
|
||||
self.assertFalse(os.path.exists(fname))
|
||||
|
||||
def test_read_boards(self):
|
||||
"""Test Boards.read_boards() with various field counts"""
|
||||
# Test normal boards.cfg file
|
||||
boards_cfg = os.path.join(self._base_dir, 'boards.cfg')
|
||||
content = '''# Comment line
|
||||
Active arm armv7 - Tester ARM_Board_0 board0 config0 maint@test.com
|
||||
Active powerpc ppc mpc85xx Tester PPC_Board_1 board2 config2 maint2@test.com
|
||||
|
||||
'''
|
||||
tools.write_file(boards_cfg, content.encode('utf-8'))
|
||||
|
||||
brds = boards.Boards()
|
||||
brds.read_boards(boards_cfg)
|
||||
board_list = brds.get_list()
|
||||
self.assertEqual(2, len(board_list))
|
||||
self.assertEqual('board0', board_list[0].target)
|
||||
self.assertEqual('arm', board_list[0].arch)
|
||||
self.assertEqual('', board_list[0].soc) # '-' converted to ''
|
||||
self.assertEqual('mpc85xx', board_list[1].soc)
|
||||
|
||||
# Test with fewer than 8 fields
|
||||
boards_cfg = os.path.join(self._base_dir, 'boards_short.cfg')
|
||||
content = '''Active arm armv7 - Tester Board target config
|
||||
'''
|
||||
tools.write_file(boards_cfg, content.encode('utf-8'))
|
||||
brds = boards.Boards()
|
||||
brds.read_boards(boards_cfg)
|
||||
self.assertEqual(1, len(brds.get_list()))
|
||||
|
||||
# Test with more than 8 fields (extra fields ignored)
|
||||
boards_cfg = os.path.join(self._base_dir, 'boards_extra.cfg')
|
||||
content = '''Active arm armv7 soc Tester Board target config maint extra
|
||||
'''
|
||||
tools.write_file(boards_cfg, content.encode('utf-8'))
|
||||
brds = boards.Boards()
|
||||
brds.read_boards(boards_cfg)
|
||||
self.assertEqual('config', brds.get_list()[0].cfg_name)
|
||||
|
||||
def test_boards_methods(self):
|
||||
"""Test Boards helper methods: get_dict, get_selected_names, find_by_target"""
|
||||
brds = boards.Boards()
|
||||
for brd in BOARDS:
|
||||
brds.add_board(board.Board(*brd))
|
||||
|
||||
# Test get_dict()
|
||||
board_dict = brds.get_dict()
|
||||
self.assertEqual(4, len(board_dict))
|
||||
self.assertEqual('arm', board_dict['board0'].arch)
|
||||
self.assertEqual('sandbox', board_dict['board4'].arch)
|
||||
|
||||
# Test get_selected_names()
|
||||
brds.select_boards(['arm'])
|
||||
self.assertEqual(['board0', 'board1'], brds.get_selected_names())
|
||||
|
||||
# Test select_boards warning for missing board
|
||||
brds2 = boards.Boards()
|
||||
for brd in BOARDS:
|
||||
brds2.add_board(board.Board(*brd))
|
||||
result, warnings = brds2.select_boards([], brds=['nonexistent', 'board0'])
|
||||
self.assertEqual(1, len(warnings))
|
||||
self.assertIn('nonexistent', warnings[0])
|
||||
|
||||
# Test find_by_target()
|
||||
found = brds.find_by_target('board0')
|
||||
self.assertEqual('arm', found.arch)
|
||||
|
||||
with terminal.capture() as (stdout, stderr):
|
||||
with self.assertRaises(ValueError) as exc:
|
||||
brds.find_by_target('nonexistent')
|
||||
self.assertIn('nonexistent', str(exc.exception))
|
||||
|
||||
def test_kconfig_riscv(self):
|
||||
"""Test KconfigScanner riscv architecture detection"""
|
||||
src = self._git_dir
|
||||
kc_file = os.path.join(src, 'Kconfig')
|
||||
orig_kc_data = tools.read_file(kc_file)
|
||||
|
||||
riscv_kconfig = orig_kc_data + b'''
|
||||
|
||||
config RISCV
|
||||
\tbool
|
||||
|
||||
config ARCH_RV32I
|
||||
\tbool
|
||||
|
||||
config TARGET_RISCV_BOARD
|
||||
\tbool "RISC-V Board"
|
||||
\tselect RISCV
|
||||
\tdefault n
|
||||
|
||||
if TARGET_RISCV_BOARD
|
||||
config SYS_ARCH
|
||||
\tdefault "riscv"
|
||||
|
||||
config SYS_CPU
|
||||
\tdefault "generic"
|
||||
|
||||
config SYS_VENDOR
|
||||
\tdefault "RiscVendor"
|
||||
|
||||
config SYS_BOARD
|
||||
\tdefault "RISC-V Board"
|
||||
|
||||
config SYS_CONFIG_NAME
|
||||
\tdefault "riscv_config"
|
||||
endif
|
||||
'''
|
||||
tools.write_file(kc_file, riscv_kconfig)
|
||||
|
||||
try:
|
||||
scanner = boards.KconfigScanner(src)
|
||||
defconfig = os.path.join(src, 'riscv64_defconfig')
|
||||
tools.write_file(defconfig, 'CONFIG_TARGET_RISCV_BOARD=y\n', False)
|
||||
|
||||
# Test riscv64 (no RV32I)
|
||||
res, warnings = scanner.scan(defconfig, False)
|
||||
self.assertEqual('riscv64', res['arch'])
|
||||
|
||||
# Test riscv32 (with RV32I)
|
||||
riscv32_kconfig = riscv_kconfig + b'''
|
||||
config ARCH_RV32I
|
||||
\tdefault y if TARGET_RISCV_BOARD
|
||||
'''
|
||||
tools.write_file(kc_file, riscv32_kconfig)
|
||||
scanner = boards.KconfigScanner(src)
|
||||
res, warnings = scanner.scan(defconfig, False)
|
||||
self.assertEqual('riscv32', res['arch'])
|
||||
finally:
|
||||
tools.write_file(kc_file, orig_kc_data)
|
||||
|
||||
def test_maintainers_commented(self):
|
||||
"""Test MaintainersDatabase with commented maintainer lines"""
|
||||
src = self._git_dir
|
||||
main = os.path.join(src, 'boards', 'board0', 'MAINTAINERS')
|
||||
config_dir = os.path.join(src, 'configs')
|
||||
orig_data = tools.read_file(main, binary=False)
|
||||
|
||||
new_data = '#M: Commented Maintainer <comment@test.com>\n' + orig_data
|
||||
tools.write_file(main, new_data, binary=False)
|
||||
|
||||
try:
|
||||
params_list, warnings = self._boards.build_board_list(config_dir, src)
|
||||
self.assertEqual(2, len(params_list))
|
||||
finally:
|
||||
tools.write_file(main, orig_data, binary=False)
|
||||
|
||||
def test_ensure_board_list_options(self):
|
||||
"""Test ensure_board_list() with force and quiet flags"""
|
||||
outfile = os.path.join(self._output_dir, 'test-boards-opts.cfg')
|
||||
brds = boards.Boards()
|
||||
|
||||
# Test force=False, quiet=False (normal generation)
|
||||
with terminal.capture() as (stdout, stderr):
|
||||
brds.ensure_board_list(outfile, jobs=1, force=False, quiet=False)
|
||||
self.assertTrue(os.path.exists(outfile))
|
||||
|
||||
# Test force=True (regenerate even if current)
|
||||
with terminal.capture() as (stdout, stderr):
|
||||
brds.ensure_board_list(outfile, jobs=1, force=True, quiet=False)
|
||||
self.assertTrue(os.path.exists(outfile))
|
||||
|
||||
# Test quiet=True (minimal output)
|
||||
with terminal.capture() as (stdout, stderr):
|
||||
brds.ensure_board_list(outfile, jobs=1, force=False, quiet=True)
|
||||
self.assertNotIn('Checking', stdout.getvalue())
|
||||
|
||||
# Test quiet=True when up to date (no output)
|
||||
with terminal.capture() as (stdout, stderr):
|
||||
result = brds.ensure_board_list(outfile, jobs=1, force=False,
|
||||
quiet=True)
|
||||
self.assertTrue(result)
|
||||
self.assertEqual('', stdout.getvalue())
|
||||
|
||||
def test_output_is_new_old_format(self):
|
||||
"""Test output_is_new() with old format containing Options"""
|
||||
src = self._git_dir
|
||||
config_dir = os.path.join(src, 'configs')
|
||||
boards_cfg = os.path.join(self._base_dir, 'boards_old.cfg')
|
||||
|
||||
content = b'''#
|
||||
# List of boards
|
||||
#
|
||||
# Status, Arch, CPU, SoC, Vendor, Board, Target, Options, Maintainers
|
||||
|
||||
Active arm armv7 - Tester Board board0 options maint
|
||||
'''
|
||||
tools.write_file(boards_cfg, content)
|
||||
self.assertFalse(boards.output_is_new(boards_cfg, config_dir, src))
|
||||
|
||||
def test_maintainers_status(self):
|
||||
"""Test MaintainersDatabase.get_status() with various statuses"""
|
||||
database = boards.MaintainersDatabase()
|
||||
|
||||
# Test missing target
|
||||
self.assertEqual('-', database.get_status('missing'))
|
||||
self.assertIn("no status info for 'missing'", database.warnings[-1])
|
||||
|
||||
# Test 'Supported' maps to Active
|
||||
database.database['test1'] = ('Supported', ['maint@test.com'])
|
||||
self.assertEqual('Active', database.get_status('test1'))
|
||||
|
||||
# Test 'Orphan' status
|
||||
database.database['orphan'] = ('Orphan', ['maint@test.com'])
|
||||
self.assertEqual('Orphan', database.get_status('orphan'))
|
||||
|
||||
# Test unknown status
|
||||
database.database['test2'] = ('Unknown Status', ['maint@test.com'])
|
||||
self.assertEqual('-', database.get_status('test2'))
|
||||
self.assertIn("unknown status for 'test2'", database.warnings[-1])
|
||||
|
||||
def test_expr_term_str(self):
|
||||
"""Test Expr and Term __str__() methods"""
|
||||
expr = boards.Expr('arm.*')
|
||||
self.assertEqual('arm.*', str(expr))
|
||||
|
||||
term = boards.Term()
|
||||
term.add_expr('arm')
|
||||
term.add_expr('cortex')
|
||||
self.assertEqual('arm&cortex', str(term))
|
||||
|
||||
def test_kconfig_scanner_warnings(self):
|
||||
"""Test KconfigScanner.scan() TARGET_xxx warnings"""
|
||||
src = self._git_dir
|
||||
kc_file = os.path.join(src, 'Kconfig')
|
||||
orig_kc_data = tools.read_file(kc_file)
|
||||
|
||||
# Test missing TARGET_xxx warning
|
||||
defconfig = os.path.join(src, 'configs', 'no_target_defconfig')
|
||||
tools.write_file(defconfig, 'CONFIG_SYS_ARCH="arm"\n', False)
|
||||
try:
|
||||
scanner = boards.KconfigScanner(src)
|
||||
res, warnings = scanner.scan(defconfig, warn_targets=True)
|
||||
self.assertEqual(1, len(warnings))
|
||||
self.assertIn('No TARGET_NO_TARGET enabled', warnings[0])
|
||||
finally:
|
||||
if os.path.exists(defconfig):
|
||||
os.remove(defconfig)
|
||||
|
||||
# Test duplicate TARGET_xxx warning
|
||||
extra = b'''
|
||||
config TARGET_BOARD0_DUP
|
||||
\tbool "Duplicate target"
|
||||
\tdefault y if TARGET_BOARD0
|
||||
'''
|
||||
tools.write_file(kc_file, orig_kc_data + extra)
|
||||
try:
|
||||
scanner = boards.KconfigScanner(src)
|
||||
defconfig = os.path.join(src, 'configs', 'board0_defconfig')
|
||||
res, warnings = scanner.scan(defconfig, warn_targets=True)
|
||||
self.assertEqual(1, len(warnings))
|
||||
self.assertIn('Duplicate TARGET_xxx', warnings[0])
|
||||
finally:
|
||||
tools.write_file(kc_file, orig_kc_data)
|
||||
|
||||
def test_scan_extended(self):
|
||||
"""Test scan_extended() for finding boards matching extended criteria"""
|
||||
brds = boards.Boards()
|
||||
|
||||
# Test with CONFIG-based selection (value=y)
|
||||
ext = Extended(
|
||||
name='test_ext',
|
||||
desc='Test extended board',
|
||||
fragments=['test_frag'],
|
||||
targets=[['CONFIG_ARM', 'y']])
|
||||
|
||||
with mock.patch('qconfig.find_config') as mock_find, \
|
||||
mock.patch.object(tools, 'read_file', return_value='CONFIG_TEST=y'):
|
||||
mock_find.return_value = {'board0', 'board1'}
|
||||
result = brds.scan_extended(None, ext)
|
||||
self.assertEqual({'board0', 'board1'}, result)
|
||||
mock_find.assert_called_once_with(None, ['CONFIG_ARM'])
|
||||
|
||||
# Test with CONFIG-based selection (value=n)
|
||||
ext = Extended(
|
||||
name='test_ext2',
|
||||
desc='Test extended board 2',
|
||||
fragments=['test_frag'],
|
||||
targets=[['CONFIG_DEBUG', 'n']])
|
||||
|
||||
with mock.patch('qconfig.find_config') as mock_find, \
|
||||
mock.patch.object(tools, 'read_file', return_value=''):
|
||||
mock_find.return_value = {'board2'}
|
||||
result = brds.scan_extended(None, ext)
|
||||
self.assertEqual({'board2'}, result)
|
||||
mock_find.assert_called_once_with(None, ['~CONFIG_DEBUG'])
|
||||
|
||||
# Test with CONFIG-based selection (specific value)
|
||||
ext = Extended(
|
||||
name='test_ext3',
|
||||
desc='Test extended board 3',
|
||||
fragments=['test_frag'],
|
||||
targets=[['CONFIG_SYS_SOC', '"k3"']])
|
||||
|
||||
with mock.patch('qconfig.find_config') as mock_find, \
|
||||
mock.patch.object(tools, 'read_file', return_value=''):
|
||||
mock_find.return_value = {'board4'}
|
||||
result = brds.scan_extended(None, ext)
|
||||
self.assertEqual({'board4'}, result)
|
||||
mock_find.assert_called_once_with(None, ['CONFIG_SYS_SOC="k3"'])
|
||||
|
||||
# Test with regex pattern - intersection of glob and find_config
|
||||
ext = Extended(
|
||||
name='test_ext4',
|
||||
desc='Test extended board 4',
|
||||
fragments=['test_frag'],
|
||||
targets=[['regex', 'configs/board*_defconfig']])
|
||||
|
||||
with mock.patch('qconfig.find_config') as mock_find, \
|
||||
mock.patch.object(tools, 'read_file', return_value=''), \
|
||||
mock.patch('glob.glob') as mock_glob:
|
||||
mock_glob.return_value = ['configs/board0_defconfig',
|
||||
'configs/board2_defconfig']
|
||||
mock_find.return_value = {'board0', 'board1', 'board2'}
|
||||
result = brds.scan_extended(None, ext)
|
||||
# Should be intersection: {board0, board2} & {board0, board1, board2}
|
||||
self.assertEqual({'board0', 'board2'}, result)
|
||||
|
||||
def test_parse_extended(self):
|
||||
"""Test parse_extended() for creating extended board entries"""
|
||||
brds = boards.Boards()
|
||||
for brd in BOARDS:
|
||||
brds.add_board(board.Board(*brd))
|
||||
|
||||
# Create a .buildman file
|
||||
buildman_file = os.path.join(self._base_dir, 'test.buildman')
|
||||
content = '''name: test_acpi
|
||||
desc: Test ACPI boards
|
||||
fragment: acpi
|
||||
targets:
|
||||
CONFIG_ARM=y
|
||||
'''
|
||||
tools.write_file(buildman_file, content, binary=False)
|
||||
|
||||
# Mock scan_extended to return specific boards
|
||||
with mock.patch.object(brds, 'scan_extended') as mock_scan:
|
||||
mock_scan.return_value = {'board0', 'board1'}
|
||||
brds.parse_extended(None, buildman_file)
|
||||
|
||||
# Check that new extended boards were added
|
||||
board_list = brds.get_list()
|
||||
# Original 4 boards + 2 extended boards
|
||||
self.assertEqual(6, len(board_list))
|
||||
|
||||
# Find the extended boards
|
||||
ext_boards = [b for b in board_list if ',' in b.target]
|
||||
self.assertEqual(2, len(ext_boards))
|
||||
|
||||
# Check the extended board properties
|
||||
ext_board = next(b for b in ext_boards if 'board0' in b.target)
|
||||
self.assertEqual('test_acpi,board0', ext_board.target)
|
||||
self.assertEqual('arm', ext_board.arch)
|
||||
self.assertEqual('board0', ext_board.orig_target)
|
||||
self.assertIsNotNone(ext_board.extended)
|
||||
self.assertEqual('test_acpi', ext_board.extended.name)
|
||||
|
||||
def test_try_remove_other_error(self):
|
||||
"""Test try_remove() re-raises non-ENOENT errors"""
|
||||
with mock.patch('os.remove') as mock_remove:
|
||||
# Simulate a permission error (not ENOENT)
|
||||
err = OSError(errno.EACCES, 'Permission denied')
|
||||
mock_remove.side_effect = err
|
||||
with self.assertRaises(OSError) as exc:
|
||||
boards.try_remove('/some/file')
|
||||
self.assertEqual(errno.EACCES, exc.exception.errno)
|
||||
|
||||
def test_output_is_new_other_error(self):
|
||||
"""Test output_is_new() re-raises non-ENOENT errors"""
|
||||
with mock.patch('os.path.getctime') as mock_ctime:
|
||||
err = OSError(errno.EACCES, 'Permission denied')
|
||||
mock_ctime.side_effect = err
|
||||
with self.assertRaises(OSError) as exc:
|
||||
boards.output_is_new('/some/file', 'configs', '.')
|
||||
self.assertEqual(errno.EACCES, exc.exception.errno)
|
||||
|
||||
def test_output_is_new_hidden_files(self):
|
||||
"""Test output_is_new() skips hidden defconfig files"""
|
||||
base = self._base_dir
|
||||
src = self._git_dir
|
||||
config_dir = os.path.join(src, 'configs')
|
||||
|
||||
# Create boards.cfg
|
||||
boards_cfg = os.path.join(base, 'boards_hidden.cfg')
|
||||
content = b'''#
|
||||
# List of boards
|
||||
# Automatically generated by buildman/boards.py: don't edit
|
||||
#
|
||||
# Status, Arch, CPU, SoC, Vendor, Board, Target, Config, Maintainers
|
||||
|
||||
Active arm armv7 - Tester Board board0 config0 maint
|
||||
'''
|
||||
tools.write_file(boards_cfg, content)
|
||||
|
||||
# Create a hidden defconfig file (should be skipped)
|
||||
hidden = os.path.join(config_dir, '.hidden_defconfig')
|
||||
tools.write_file(hidden, b'# hidden')
|
||||
|
||||
try:
|
||||
# Touch boards.cfg to make it newer
|
||||
time.sleep(0.02)
|
||||
Path(boards_cfg).touch()
|
||||
# Should return True (hidden file skipped)
|
||||
self.assertTrue(boards.output_is_new(boards_cfg, config_dir, src))
|
||||
finally:
|
||||
os.remove(hidden)
|
||||
|
||||
def test_kconfig_scanner_destructor(self):
|
||||
"""Test KconfigScanner.__del__() cleans up leftover temp file"""
|
||||
src = self._git_dir
|
||||
scanner = boards.KconfigScanner(src)
|
||||
|
||||
# Simulate a leftover temp file
|
||||
tmpfile = os.path.join(self._base_dir, 'leftover.tmp')
|
||||
tools.write_file(tmpfile, b'temp')
|
||||
scanner._tmpfile = tmpfile
|
||||
|
||||
# Delete the scanner - should clean up the temp file
|
||||
del scanner
|
||||
self.assertFalse(os.path.exists(tmpfile))
|
||||
|
||||
def test_kconfig_scanner_aarch64(self):
|
||||
"""Test KconfigScanner.scan() aarch64 fix-up"""
|
||||
src = self._git_dir
|
||||
kc_file = os.path.join(src, 'Kconfig')
|
||||
orig_kc_data = tools.read_file(kc_file)
|
||||
|
||||
# Add aarch64 board to Kconfig
|
||||
aarch64_kconfig = orig_kc_data + b'''
|
||||
|
||||
config TARGET_AARCH64_BOARD
|
||||
\tbool "AArch64 Board"
|
||||
\tdefault n
|
||||
|
||||
if TARGET_AARCH64_BOARD
|
||||
config SYS_ARCH
|
||||
\tdefault "arm"
|
||||
|
||||
config SYS_CPU
|
||||
\tdefault "armv8"
|
||||
|
||||
config SYS_VENDOR
|
||||
\tdefault "Test"
|
||||
|
||||
config SYS_BOARD
|
||||
\tdefault "AArch64 Board"
|
||||
|
||||
config SYS_CONFIG_NAME
|
||||
\tdefault "aarch64_config"
|
||||
endif
|
||||
'''
|
||||
tools.write_file(kc_file, aarch64_kconfig)
|
||||
|
||||
try:
|
||||
scanner = boards.KconfigScanner(src)
|
||||
defconfig = os.path.join(src, 'aarch64_defconfig')
|
||||
tools.write_file(defconfig, 'CONFIG_TARGET_AARCH64_BOARD=y\n', False)
|
||||
res, warnings = scanner.scan(defconfig, False)
|
||||
# Should be fixed up to aarch64
|
||||
self.assertEqual('aarch64', res['arch'])
|
||||
finally:
|
||||
tools.write_file(kc_file, orig_kc_data)
|
||||
if os.path.exists(defconfig):
|
||||
os.remove(defconfig)
|
||||
|
||||
def test_read_boards_short_line(self):
|
||||
"""Test Boards.read_boards() pads short lines to 8 fields"""
|
||||
boards_cfg = os.path.join(self._base_dir, 'boards_veryshort.cfg')
|
||||
# Create a board with only 7 fields (missing maintainers)
|
||||
content = '''Active arm armv7 soc Tester Board target
|
||||
'''
|
||||
tools.write_file(boards_cfg, content.encode('utf-8'))
|
||||
|
||||
brds = boards.Boards()
|
||||
brds.read_boards(boards_cfg)
|
||||
board_list = brds.get_list()
|
||||
self.assertEqual(1, len(board_list))
|
||||
# cfg_name should be empty string (padded)
|
||||
self.assertEqual('', board_list[0].cfg_name)
|
||||
|
||||
def test_ensure_board_list_up_to_date_message(self):
|
||||
"""Test ensure_board_list() shows up-to-date message"""
|
||||
outfile = os.path.join(self._output_dir, 'test-boards-uptodate.cfg')
|
||||
brds = boards.Boards()
|
||||
|
||||
# First generate the file
|
||||
with terminal.capture() as (stdout, stderr):
|
||||
brds.ensure_board_list(outfile, jobs=1, force=False, quiet=False)
|
||||
|
||||
# Run again - should say "up to date"
|
||||
with terminal.capture() as (stdout, stderr):
|
||||
result = brds.ensure_board_list(outfile, jobs=1, force=False,
|
||||
quiet=False)
|
||||
self.assertTrue(result)
|
||||
self.assertIn('up to date', stdout.getvalue())
|
||||
|
||||
def test_ensure_board_list_warnings(self):
|
||||
"""Test ensure_board_list() prints warnings to stderr"""
|
||||
outfile = os.path.join(self._output_dir, 'test-boards-warn.cfg')
|
||||
brds = boards.Boards()
|
||||
|
||||
# Mock build_board_list to return warnings
|
||||
with mock.patch.object(brds, 'build_board_list') as mock_build:
|
||||
mock_build.return_value = ([], ['WARNING: test warning'])
|
||||
with terminal.capture() as (stdout, stderr):
|
||||
result = brds.ensure_board_list(outfile, jobs=1, force=True,
|
||||
quiet=False)
|
||||
self.assertFalse(result)
|
||||
self.assertIn('WARNING: test warning', stderr.getvalue())
|
||||
|
||||
def test_parse_all_extended(self):
|
||||
"""Test parse_all_extended() finds and parses .buildman files"""
|
||||
brds = boards.Boards()
|
||||
for brd in BOARDS:
|
||||
brds.add_board(board.Board(*brd))
|
||||
|
||||
# Mock glob to return a .buildman file and parse_extended
|
||||
with mock.patch('glob.glob') as mock_glob, \
|
||||
mock.patch.object(brds, 'parse_extended') as mock_parse:
|
||||
mock_glob.return_value = ['configs/test.buildman']
|
||||
brds.parse_all_extended(None)
|
||||
mock_glob.assert_called_once_with('configs/*.buildman')
|
||||
mock_parse.assert_called_once_with(None, 'configs/test.buildman')
|
||||
|
||||
def test_scan_extended_no_match_warning(self):
|
||||
"""Test scan_extended() warns when no configs match regex"""
|
||||
brds = boards.Boards()
|
||||
|
||||
ext = Extended(
|
||||
name='test_ext',
|
||||
desc='Test extended board',
|
||||
fragments=['test_frag'],
|
||||
targets=[['regex', 'nonexistent*_defconfig']])
|
||||
|
||||
with mock.patch('qconfig.find_config') as mock_find, \
|
||||
mock.patch.object(tools, 'read_file', return_value=''), \
|
||||
mock.patch('glob.glob') as mock_glob, \
|
||||
terminal.capture() as (stdout, stderr):
|
||||
mock_glob.return_value = [] # No matches
|
||||
mock_find.return_value = set()
|
||||
result = brds.scan_extended(None, ext)
|
||||
self.assertEqual(set(), result)
|
||||
# Warning should be printed
|
||||
self.assertIn('Warning', stdout.getvalue())
|
||||
|
||||
def test_kconfig_scanner_riscv_no_rv32i(self):
|
||||
"""Test KconfigScanner.scan() when ARCH_RV32I symbol doesn't exist"""
|
||||
src = self._git_dir
|
||||
kc_file = os.path.join(src, 'Kconfig')
|
||||
orig_kc_data = tools.read_file(kc_file)
|
||||
|
||||
# Add RISCV board WITHOUT defining ARCH_RV32I symbol
|
||||
# This will cause syms.get('ARCH_RV32I') to return None,
|
||||
# and accessing .str_value on None raises AttributeError
|
||||
riscv_kconfig = orig_kc_data + b'''
|
||||
|
||||
config RISCV
|
||||
\tbool
|
||||
|
||||
config TARGET_RISCV_TEST
|
||||
\tbool "RISC-V Test Board"
|
||||
\tdefault n
|
||||
|
||||
if TARGET_RISCV_TEST
|
||||
config SYS_ARCH
|
||||
\tdefault "riscv"
|
||||
|
||||
config SYS_CPU
|
||||
\tdefault "generic"
|
||||
|
||||
config SYS_VENDOR
|
||||
\tdefault "Test"
|
||||
|
||||
config SYS_BOARD
|
||||
\tdefault "RISCV Test"
|
||||
|
||||
config SYS_CONFIG_NAME
|
||||
\tdefault "riscv_test"
|
||||
endif
|
||||
'''
|
||||
tools.write_file(kc_file, riscv_kconfig)
|
||||
defconfig = os.path.join(src, 'riscv_test_defconfig')
|
||||
|
||||
try:
|
||||
# Create defconfig that enables the riscv board
|
||||
tools.write_file(defconfig, 'CONFIG_TARGET_RISCV_TEST=y\n', False)
|
||||
|
||||
scanner = boards.KconfigScanner(src)
|
||||
res, warnings = scanner.scan(defconfig, False)
|
||||
|
||||
# Should default to riscv64 when ARCH_RV32I lookup fails
|
||||
self.assertEqual('riscv64', res['arch'])
|
||||
finally:
|
||||
tools.write_file(kc_file, orig_kc_data)
|
||||
if os.path.exists(defconfig):
|
||||
os.remove(defconfig)
|
||||
|
||||
def test_scan_defconfigs_for_multiprocess(self):
|
||||
"""Test scan_defconfigs_for_multiprocess() function directly"""
|
||||
src = self._git_dir
|
||||
config_dir = os.path.join(src, 'configs')
|
||||
|
||||
# Get a list of defconfigs
|
||||
defconfigs = [os.path.join(config_dir, 'board0_defconfig')]
|
||||
|
||||
# Create a queue and call the function
|
||||
queue = multiprocessing.Queue()
|
||||
boards.Boards.scan_defconfigs_for_multiprocess(src, queue, defconfigs,
|
||||
False)
|
||||
|
||||
# Get the result from the queue
|
||||
result = queue.get(timeout=5)
|
||||
params, warnings = result
|
||||
self.assertEqual('board0', params['target'])
|
||||
self.assertEqual('arm', params['arch'])
|
||||
|
||||
def test_scan_defconfigs_hidden_files(self):
|
||||
"""Test scan_defconfigs() skips hidden defconfig files"""
|
||||
src = self._git_dir
|
||||
config_dir = os.path.join(src, 'configs')
|
||||
|
||||
# Create a hidden defconfig
|
||||
hidden = os.path.join(config_dir, '.hidden_defconfig')
|
||||
tools.write_file(hidden, b'CONFIG_TARGET_BOARD0=y')
|
||||
|
||||
try:
|
||||
brds = boards.Boards()
|
||||
params_list, warnings = brds.scan_defconfigs(config_dir, src, 1)
|
||||
|
||||
# Hidden file should not be in results
|
||||
targets = [p['target'] for p in params_list]
|
||||
self.assertNotIn('.hidden', targets)
|
||||
# But regular boards should be there
|
||||
self.assertIn('board0', targets)
|
||||
finally:
|
||||
os.remove(hidden)
|
||||
|
||||
def test_maintainers_n_tag_non_configs_path(self):
|
||||
"""Test MaintainersDatabase N: tag skips non-configs paths"""
|
||||
src = self._git_dir
|
||||
|
||||
# Create a MAINTAINERS file with N: tag
|
||||
maintainers_file = os.path.join(src, 'MAINTAINERS_TEST')
|
||||
maintainers_content = '''BOARD0
|
||||
M: Test <test@test.com>
|
||||
S: Active
|
||||
N: .*
|
||||
'''
|
||||
tools.write_file(maintainers_file, maintainers_content, binary=False)
|
||||
|
||||
# Mock os.walk to return a path that doesn't start with 'configs/'
|
||||
# when walking the configs directory. This tests line 443.
|
||||
def mock_walk(path):
|
||||
# Return paths with 'configs/' prefix (normal) and without (edge case)
|
||||
yield (os.path.join(src, 'configs'), [], ['board0_defconfig'])
|
||||
# This path will have 'other/' prefix after srcdir removal
|
||||
yield (os.path.join(src, 'other'), [], ['fred_defconfig'])
|
||||
|
||||
try:
|
||||
database = boards.MaintainersDatabase()
|
||||
with mock.patch('os.walk', mock_walk):
|
||||
database.parse_file(src, maintainers_file)
|
||||
|
||||
# board0 should be found (path starts with configs/)
|
||||
# fred should be skipped (path starts with other/, not configs/)
|
||||
self.assertIn('board0', database.database)
|
||||
self.assertNotIn('fred', database.database)
|
||||
finally:
|
||||
os.remove(maintainers_file)
|
||||
|
||||
|
||||
if __name__ == '__main__':
|
||||
unittest.main()
|
||||
Reference in New Issue
Block a user