kumquat-buildroot/utils/checksymbolslib/test_kconfig.py
Ricardo Martincoski 0c5472ace2 utils/check-symbols: new script
This script checks for inconsistencies on symbols declared in Config.in
and used in .mk files.
Currently it checks only symbols following the pattern BR2_\w+ .

The script first gets the list of all files in the repository (using git
ls-files like 'make check-flake8' already do).

Then it parses all relevant files, searching for symbol definitions and
usages, and add entries into a database.

At the end, the database is searched for inconsistencies:
- symbol that is part of "choice" and is referenced with "select";
- legacy symbol being referenced in packages;
- legacy symbol being redefined in packages;
- symbol referenced but not defined;
- symbol defined but not referenced;
- legacy symbol that has a Note stating it is referenced by a package
  (for legacy handling) but is referenced in the package without a
  comment "# legacy";
- legacy symbol that has a Note stating it is referenced by a package
  but it is not actually referenced.

There is also a debug parameter --search that dumps any filename or
symbol entries from the database that matches a regexp.

Sample usages:
$ utils/check-symbols
$ utils/docker-run utils/check-symbols
$ utils/check-symbols --search 'GETTEXT\b|\/openssl'

At same time the script is created:
- add unit tests for it, they can be run using:
  utils/docker-run python3 -m pytest -v utils/checksymbolslib/
- add two more GitLab CI jobs: check-symbols (to check current tree
  using the script) and check-check-symbols (to check the script against
  its unit tests)

Cc: Thomas Petazzoni <thomas.petazzoni@bootlin.com>
Signed-off-by: Ricardo Martincoski <ricardo.martincoski@gmail.com>
[Peter: print warnings to stderr, rename change_current_dir() to
	change_to_top_dir()]
Signed-off-by: Peter Korsgaard <peter@korsgaard.com>
2023-02-06 16:30:20 +01:00

439 lines
17 KiB
Python

import pytest
from unittest.mock import Mock
from unittest.mock import call
from checksymbolslib.test_util import assert_db_calls
import checksymbolslib.kconfig as m
all_symbols_from = [
('no prefix',
'config PACKAGE_FOO',
[]),
('simple',
'config BR2_PACKAGE_FOO',
['BR2_PACKAGE_FOO']),
('ignore comment',
'config BR2_PACKAGE_FOO # BR2_PACKAGE_BAR',
['BR2_PACKAGE_FOO']),
('ignore whitespace',
'\tconfig BR2_PACKAGE_FOO\t # BR2_PACKAGE_BAR',
['BR2_PACKAGE_FOO']),
('2 occurrences',
'\tdefault BR2_PACKAGE_FOO_BAR if BR2_PACKAGE_FOO_BAR != ""',
['BR2_PACKAGE_FOO_BAR', 'BR2_PACKAGE_FOO_BAR']),
]
@pytest.mark.parametrize('testname,line,expected', all_symbols_from)
def test_all_symbols_from(testname, line, expected):
symbols = m.all_symbols_from(line)
assert symbols == expected
handle_definition = [
('config',
'package/foo/Config.in',
5,
'config BR2_PACKAGE_FOO',
False,
{'add_symbol_definition': [call('BR2_PACKAGE_FOO', 'package/foo/Config.in', 5)]}),
('ignore comment',
'package/foo/Config.in',
5,
'config BR2_PACKAGE_FOO # BR2_PACKAGE_BAR',
False,
{'add_symbol_definition': [call('BR2_PACKAGE_FOO', 'package/foo/Config.in', 5)]}),
('ignore whitespace',
'package/foo/Config.in',
5,
'\tconfig BR2_PACKAGE_FOO\t # BR2_PACKAGE_BAR',
False,
{'add_symbol_definition': [call('BR2_PACKAGE_FOO', 'package/foo/Config.in', 5)]}),
('menuconfig',
'package/gd/Config.in',
1,
'menuconfig BR2_PACKAGE_GD',
False,
{'add_symbol_definition': [call('BR2_PACKAGE_GD', 'package/gd/Config.in', 1)]}),
('menu',
'package/Config.in',
100,
'menu "Database"',
False,
{}),
('legacy config',
'Config.in.legacy',
50,
'config BR2_PACKAGE_FOO',
True,
{'add_symbol_legacy_definition': [call('BR2_PACKAGE_FOO', 'Config.in.legacy', 50)]}),
]
@pytest.mark.parametrize('testname,filename,lineno,line,legacy,expected_calls', handle_definition)
def test_handle_definition(testname, filename, lineno, line, legacy, expected_calls):
db = Mock()
m.handle_definition(db, filename, lineno, line, legacy)
assert_db_calls(db, expected_calls)
handle_usage = [
('default with comparison',
'package/openblas/Config.in',
60,
'\tdefault y if BR2_PACKAGE_OPENBLAS_DEFAULT_TARGET != ""',
False,
{'add_symbol_usage': [call('BR2_PACKAGE_OPENBLAS_DEFAULT_TARGET', 'package/openblas/Config.in', 60)]}),
('default with logical operators',
'toolchain/toolchain-external/toolchain-external-bootlin/Config.in.options',
47,
'\tdefault y if BR2_i386 && !BR2_x86_i486 && !BR2_x86_i586 && !BR2_x86_x1000 && !BR2_x86_pentium_mmx && !BR2_x86_geode '
'&& !BR2_x86_c3 && !BR2_x86_winchip_c6 && !BR2_x86_winchip2',
False,
{'add_symbol_usage': [
call('BR2_i386', 'toolchain/toolchain-external/toolchain-external-bootlin/Config.in.options', 47),
call('BR2_x86_c3', 'toolchain/toolchain-external/toolchain-external-bootlin/Config.in.options', 47),
call('BR2_x86_geode', 'toolchain/toolchain-external/toolchain-external-bootlin/Config.in.options', 47),
call('BR2_x86_i486', 'toolchain/toolchain-external/toolchain-external-bootlin/Config.in.options', 47),
call('BR2_x86_i586', 'toolchain/toolchain-external/toolchain-external-bootlin/Config.in.options', 47),
call('BR2_x86_pentium_mmx', 'toolchain/toolchain-external/toolchain-external-bootlin/Config.in.options', 47),
call('BR2_x86_winchip2', 'toolchain/toolchain-external/toolchain-external-bootlin/Config.in.options', 47),
call('BR2_x86_winchip_c6', 'toolchain/toolchain-external/toolchain-external-bootlin/Config.in.options', 47),
call('BR2_x86_x1000', 'toolchain/toolchain-external/toolchain-external-bootlin/Config.in.options', 47)]}),
('legacy depends on',
'Config.in.legacy',
3000,
'\tdepends on BR2_LINUX_KERNEL',
True,
{'add_symbol_usage_in_legacy': [call('BR2_LINUX_KERNEL', 'Config.in.legacy', 3000)]}),
('legacy if',
'Config.in.legacy',
97,
'if !BR2_SKIP_LEGACY',
True,
{'add_symbol_usage_in_legacy': [call('BR2_SKIP_LEGACY', 'Config.in.legacy', 97)]}),
('source',
'system/Config.in',
152,
'source "$BR2_BASE_DIR/.br2-external.in.init"',
False,
{'add_symbol_usage': [call('BR2_BASE_DIR', 'system/Config.in', 152)]}),
]
@pytest.mark.parametrize('testname,filename,lineno,line,legacy,expected_calls', handle_usage)
def test_handle_usage(testname, filename, lineno, line, legacy, expected_calls):
db = Mock()
m.handle_usage(db, filename, lineno, line, legacy)
assert_db_calls(db, expected_calls)
handle_default = [
('default with comparison',
'package/openblas/Config.in',
60,
'\tdefault y if BR2_PACKAGE_OPENBLAS_DEFAULT_TARGET != ""',
False,
{'add_symbol_usage': [call('BR2_PACKAGE_OPENBLAS_DEFAULT_TARGET', 'package/openblas/Config.in', 60)]}),
('default with logical operators',
'toolchain/toolchain-external/toolchain-external-bootlin/Config.in.options',
47,
'\tdefault y if BR2_i386 && !BR2_x86_i486 && !BR2_x86_i586 && !BR2_x86_x1000 && !BR2_x86_pentium_mmx && !BR2_x86_geode '
'&& !BR2_x86_c3 && !BR2_x86_winchip_c6 && !BR2_x86_winchip2',
False,
{'add_symbol_usage': [
call('BR2_i386', 'toolchain/toolchain-external/toolchain-external-bootlin/Config.in.options', 47),
call('BR2_x86_c3', 'toolchain/toolchain-external/toolchain-external-bootlin/Config.in.options', 47),
call('BR2_x86_geode', 'toolchain/toolchain-external/toolchain-external-bootlin/Config.in.options', 47),
call('BR2_x86_i486', 'toolchain/toolchain-external/toolchain-external-bootlin/Config.in.options', 47),
call('BR2_x86_i586', 'toolchain/toolchain-external/toolchain-external-bootlin/Config.in.options', 47),
call('BR2_x86_pentium_mmx', 'toolchain/toolchain-external/toolchain-external-bootlin/Config.in.options', 47),
call('BR2_x86_winchip2', 'toolchain/toolchain-external/toolchain-external-bootlin/Config.in.options', 47),
call('BR2_x86_winchip_c6', 'toolchain/toolchain-external/toolchain-external-bootlin/Config.in.options', 47),
call('BR2_x86_x1000', 'toolchain/toolchain-external/toolchain-external-bootlin/Config.in.options', 47)]}),
('legacy default',
'Config.in.legacy',
3000,
'default y if BR2_PACKAGE_REFPOLICY_POLICY_VERSION != ""',
True,
{'add_symbol_usage_in_legacy': [call('BR2_PACKAGE_REFPOLICY_POLICY_VERSION', 'Config.in.legacy', 3000)]}),
('legacy handling on package',
'package/uboot-tools/Config.in.host',
105,
'\tdefault BR2_TARGET_UBOOT_BOOT_SCRIPT_SOURCE if BR2_TARGET_UBOOT_BOOT_SCRIPT_SOURCE != "" # legacy',
False,
{'add_symbol_legacy_usage': [call('BR2_TARGET_UBOOT_BOOT_SCRIPT_SOURCE', 'package/uboot-tools/Config.in.host', 105)]}),
('default on package',
'package/uboot-tools/Config.in.host',
105,
'\tdefault BR2_TARGET_UBOOT_BOOT_SCRIPT_SOURCE if BR2_TARGET_UBOOT_BOOT_SCRIPT_SOURCE != ""',
False,
{'add_symbol_usage': [
call('BR2_TARGET_UBOOT_BOOT_SCRIPT_SOURCE', 'package/uboot-tools/Config.in.host', 105),
call('BR2_TARGET_UBOOT_BOOT_SCRIPT_SOURCE', 'package/uboot-tools/Config.in.host', 105)]}),
]
@pytest.mark.parametrize('testname,filename,lineno,line,legacy,expected_calls', handle_default)
def test_handle_default(testname, filename, lineno, line, legacy, expected_calls):
db = Mock()
m.handle_default(db, filename, lineno, line, legacy)
assert_db_calls(db, expected_calls)
handle_select = [
('select with comparison',
'package/bcusdk/Config.in',
6,
'\tselect BR2_PACKAGE_ARGP_STANDALONE if BR2_TOOLCHAIN_USES_UCLIBC || BR2_TOOLCHAIN_USES_MUSL',
False,
{'add_symbol_select': [call('BR2_PACKAGE_ARGP_STANDALONE', 'package/bcusdk/Config.in', 6)],
'add_symbol_usage': [
call('BR2_PACKAGE_ARGP_STANDALONE', 'package/bcusdk/Config.in', 6),
call('BR2_TOOLCHAIN_USES_UCLIBC', 'package/bcusdk/Config.in', 6),
call('BR2_TOOLCHAIN_USES_MUSL', 'package/bcusdk/Config.in', 6)]}),
('legacy select',
'Config.in.legacy',
100,
'\tselect BR2_PACKAGE_WPA_SUPPLICANT_DBUS if BR2_TOOLCHAIN_HAS_THREADS',
True,
{'add_symbol_select': [call('BR2_PACKAGE_WPA_SUPPLICANT_DBUS', 'Config.in.legacy', 100)],
'add_symbol_usage_in_legacy': [
call('BR2_PACKAGE_WPA_SUPPLICANT_DBUS', 'Config.in.legacy', 100),
call('BR2_TOOLCHAIN_HAS_THREADS', 'Config.in.legacy', 100)]}),
]
@pytest.mark.parametrize('testname,filename,lineno,line,legacy,expected_calls', handle_select)
def test_handle_select(testname, filename, lineno, line, legacy, expected_calls):
db = Mock()
m.handle_select(db, filename, lineno, line, legacy)
assert_db_calls(db, expected_calls)
handle_line = [
('select with comparison',
'package/bcusdk/Config.in',
6,
'\tselect BR2_PACKAGE_ARGP_STANDALONE if BR2_TOOLCHAIN_USES_UCLIBC || BR2_TOOLCHAIN_USES_MUSL',
False,
{'add_symbol_select': [call('BR2_PACKAGE_ARGP_STANDALONE', 'package/bcusdk/Config.in', 6)],
'add_symbol_usage': [
call('BR2_PACKAGE_ARGP_STANDALONE', 'package/bcusdk/Config.in', 6),
call('BR2_TOOLCHAIN_USES_UCLIBC', 'package/bcusdk/Config.in', 6),
call('BR2_TOOLCHAIN_USES_MUSL', 'package/bcusdk/Config.in', 6)]}),
('legacy select',
'Config.in.legacy',
100,
'\tselect BR2_PACKAGE_WPA_SUPPLICANT_DBUS if BR2_TOOLCHAIN_HAS_THREADS',
True,
{'add_symbol_select': [call('BR2_PACKAGE_WPA_SUPPLICANT_DBUS', 'Config.in.legacy', 100)],
'add_symbol_usage_in_legacy': [
call('BR2_PACKAGE_WPA_SUPPLICANT_DBUS', 'Config.in.legacy', 100),
call('BR2_TOOLCHAIN_HAS_THREADS', 'Config.in.legacy', 100)]}),
('comment with symbol',
'Config.in',
6,
'\tselect # BR2_PACKAGE_ARGP_STANDALONE if BR2_TOOLCHAIN_USES_UCLIBC || BR2_TOOLCHAIN_USES_MUSL',
False,
{}),
('comment',
'Config.in',
6,
'# just a comment',
False,
{}),
]
@pytest.mark.parametrize('testname,filename,lineno,line,legacy,expected_calls', handle_line)
def test_handle_line(testname, filename, lineno, line, legacy, expected_calls):
db = Mock()
m.handle_line(db, filename, lineno, line, legacy)
assert_db_calls(db, expected_calls)
handle_config_helper = [
('no select',
'package/foo/Config.in',
[[5, 'config BR2_PACKAGE_FOO']],
{}),
('select',
'package/foo/Config.in',
[[5, 'config BR2_PACKAGE_FOO'],
[6, '\tselect BR2_PACKAGE_BAR']],
{'add_symbol_helper': [call('BR2_PACKAGE_FOO', 'package/foo/Config.in', 5)]}),
('ignore comment',
'package/foo/Config.in',
[[5, 'config BR2_PACKAGE_FOO # BR2_PACKAGE_BAR'],
[6, '\tselect BR2_PACKAGE_BAR # BR2_PACKAGE_FOO']],
{'add_symbol_helper': [call('BR2_PACKAGE_FOO', 'package/foo/Config.in', 5)]}),
('correct symbol',
'package/foo/Config.in',
[[5, 'config BR2_PACKAGE_FOO'],
[6, 'config BR2_PACKAGE_BAR'],
[7, '\tselect BR2_PACKAGE_BAZ']],
{'add_symbol_helper': [call('BR2_PACKAGE_BAR', 'package/foo/Config.in', 6)]}),
('2 selects',
'package/foo/Config.in',
[[5, 'config BR2_PACKAGE_FOO'],
[6, '\tselect BR2_PACKAGE_BAR'],
[7, ' select BR2_PACKAGE_BAR']],
{'add_symbol_helper': [call('BR2_PACKAGE_FOO', 'package/foo/Config.in', 5)]}),
]
@pytest.mark.parametrize('testname,filename,file_content,expected_calls', handle_config_helper)
def test_handle_config_helper(testname, filename, file_content, expected_calls):
db = Mock()
m.handle_config_helper(db, filename, file_content)
assert_db_calls(db, expected_calls)
handle_config_choice = [
('no choice',
'package/foo/Config.in',
[[5, 'config BR2_PACKAGE_FOO']],
{}),
('after',
'package/foo/Config.in',
[[3, 'choice'],
[4, '\tprompt "your choice"'],
[5, 'config BR2_PACKAGE_FOO'],
[6, 'config BR2_PACKAGE_BAR'],
[10, 'endchoice'],
[19, 'config BR2_PACKAGE_BAZ']],
{'add_symbol_choice': [
call('BR2_PACKAGE_FOO', 'package/foo/Config.in', 5),
call('BR2_PACKAGE_BAR', 'package/foo/Config.in', 6)]}),
('before',
'package/foo/Config.in',
[[1, 'config BR2_PACKAGE_BAZ'],
[3, 'choice'],
[4, '\tprompt "your choice"'],
[5, 'config BR2_PACKAGE_FOO'],
[6, 'config BR2_PACKAGE_BAR'],
[10, 'endchoice']],
{'add_symbol_choice': [
call('BR2_PACKAGE_FOO', 'package/foo/Config.in', 5),
call('BR2_PACKAGE_BAR', 'package/foo/Config.in', 6)]}),
]
@pytest.mark.parametrize('testname,filename,file_content,expected_calls', handle_config_choice)
def test_handle_config_choice(testname, filename, file_content, expected_calls):
db = Mock()
m.handle_config_choice(db, filename, file_content)
assert_db_calls(db, expected_calls)
handle_note = [
('example',
'Config.in.legacy',
[[51, '# # Note: BR2_FOO_1 is still referenced from package/foo/Config.in']],
{}),
('ok',
'Config.in.legacy',
[[112, 'menu "Legacy config options"'],
[2132, '# Note: BR2_PACKAGE_FOO is still referenced from package/foo/Config.in'],
[4958, 'endmenu']],
{'add_symbol_legacy_note': [call('BR2_PACKAGE_FOO', 'Config.in.legacy', 2132)]}),
('before and after',
'Config.in.legacy',
[[100, '# Note: BR2_PACKAGE_BAR is still referenced from package/foo/Config.in'],
[112, 'menu "Legacy config options"'],
[2132, '# Note: BR2_PACKAGE_FOO is still referenced from package/foo/Config.in'],
[4958, 'endmenu'],
[5000, '# Note: BR2_PACKAGE_BAR is still referenced from package/foo/Config.in']],
{'add_symbol_legacy_note': [call('BR2_PACKAGE_FOO', 'Config.in.legacy', 2132)]}),
]
@pytest.mark.parametrize('testname,filename,file_content,expected_calls', handle_note)
def test_handle_note(testname, filename, file_content, expected_calls):
db = Mock()
m.handle_note(db, filename, file_content)
assert_db_calls(db, expected_calls)
populate_db = [
('legacy',
'Config.in.legacy',
[[112, 'menu "Legacy config options"'],
[2100, 'config BR2_PACKAGE_FOO'],
[2101, '\tselect BR2_PACKAGE_BAR'],
[2132, '# Note: BR2_PACKAGE_FOO is still referenced from package/foo/Config.in'],
[4958, 'endmenu']],
{'add_symbol_legacy_note': [call('BR2_PACKAGE_FOO', 'Config.in.legacy', 2132)],
'add_symbol_helper': [call('BR2_PACKAGE_FOO', 'Config.in.legacy', 2100)],
'add_symbol_legacy_definition': [call('BR2_PACKAGE_FOO', 'Config.in.legacy', 2100)],
'add_symbol_usage_in_legacy': [call('BR2_PACKAGE_BAR', 'Config.in.legacy', 2101)],
'add_symbol_select': [call('BR2_PACKAGE_BAR', 'Config.in.legacy', 2101)]}),
('normal',
'package/foo/Config.in',
[[1, 'config BR2_PACKAGE_BAZ'],
[3, 'choice'],
[4, '\tprompt "your choice"'],
[5, 'config BR2_PACKAGE_FOO'],
[6, 'config BR2_PACKAGE_BAR'],
[7, '\t select BR2_PACKAGE_FOO_BAR'],
[10, 'endchoice']],
{'add_symbol_choice': [
call('BR2_PACKAGE_FOO', 'package/foo/Config.in', 5),
call('BR2_PACKAGE_BAR', 'package/foo/Config.in', 6)],
'add_symbol_usage': [
call('BR2_PACKAGE_FOO_BAR', 'package/foo/Config.in', 7)],
'add_symbol_select': [
call('BR2_PACKAGE_FOO_BAR', 'package/foo/Config.in', 7)],
'add_symbol_definition': [
call('BR2_PACKAGE_BAZ', 'package/foo/Config.in', 1),
call('BR2_PACKAGE_FOO', 'package/foo/Config.in', 5),
call('BR2_PACKAGE_BAR', 'package/foo/Config.in', 6)],
'add_symbol_helper': [
call('BR2_PACKAGE_BAR', 'package/foo/Config.in', 6)]}),
]
@pytest.mark.parametrize('testname,filename,file_content,expected_calls', populate_db)
def test_populate_db(testname, filename, file_content, expected_calls):
db = Mock()
m.populate_db(db, filename, file_content)
assert_db_calls(db, expected_calls)
check_filename = [
('Config.in',
'Config.in',
True),
('Config.in.legacy',
'Config.in.legacy',
True),
('arch/Config.in.microblaze',
'arch/Config.in.microblaze',
True),
('package/php/Config.ext',
'package/php/Config.ext',
True),
('package/pru-software-support/Config.in.host',
'package/pru-software-support/Config.in.host',
True),
('toolchain/toolchain-external/toolchain-external-custom/Config.in.options',
'toolchain/toolchain-external/toolchain-external-custom/Config.in.options',
True),
('package/foo/0001-Config.patch',
'package/foo/0001-Config.patch',
False),
('package/pkg-generic.mk',
'package/pkg-generic.mk',
False),
('Makefile',
'Makefile',
False),
]
@pytest.mark.parametrize('testname,filename,expected', check_filename)
def test_check_filename(testname, filename, expected):
symbols = m.check_filename(filename)
assert symbols == expected