kumquat-buildroot/utils/checksymbolslib/test_makefile.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

305 lines
10 KiB
Python

import pytest
from unittest.mock import Mock
from unittest.mock import call
from checksymbolslib.test_util import assert_db_calls
import checksymbolslib.makefile as m
handle_eval = [
('generic',
'package/foo/foo.mk',
5,
'$(eval $(generic-package))',
{'add_symbol_usage': [call('BR2_PACKAGE_FOO', 'package/foo/foo.mk', 5)]}),
('ignore trailing whitespace',
'package/foo/foo.mk',
5,
'$(eval $(generic-package)) ',
{'add_symbol_usage': [call('BR2_PACKAGE_FOO', 'package/foo/foo.mk', 5)]}),
('ignore indent',
'package/foo/foo.mk',
5,
'\t$(eval $(generic-package))',
{'add_symbol_usage': [call('BR2_PACKAGE_FOO', 'package/foo/foo.mk', 5)]}),
('rootfs',
'fs/foo/foo.mk',
5,
'$(eval $(rootfs))',
{'add_symbol_usage': [
call('BR2_TARGET_ROOTFS_FOO', 'fs/foo/foo.mk', 5),
call('BR2_TARGET_ROOTFS_FOO_BZIP2', 'fs/foo/foo.mk', 5),
call('BR2_TARGET_ROOTFS_FOO_GZIP', 'fs/foo/foo.mk', 5),
call('BR2_TARGET_ROOTFS_FOO_LZ4', 'fs/foo/foo.mk', 5),
call('BR2_TARGET_ROOTFS_FOO_LZMA', 'fs/foo/foo.mk', 5),
call('BR2_TARGET_ROOTFS_FOO_LZO', 'fs/foo/foo.mk', 5),
call('BR2_TARGET_ROOTFS_FOO_XZ', 'fs/foo/foo.mk', 5),
call('BR2_TARGET_ROOTFS_FOO_ZSTD', 'fs/foo/foo.mk', 5)]}),
('kernel module',
'package/foo/foo.mk',
6,
'$(eval $(kernel-module))',
{'add_symbol_usage': [call('BR2_PACKAGE_FOO', 'package/foo/foo.mk', 6)]}),
('not an eval for package infra',
'docs/manual/manual.mk',
10,
'$(eval $(call asciidoc-document))',
{}),
('linux',
'linux/linux.mk',
617,
'$(eval $(kconfig-package))',
{'add_symbol_usage': [call('BR2_LINUX_KERNEL', 'linux/linux.mk', 617)]}),
('virtual toolchain',
'toolchain/toolchain-external/toolchain-external.mk',
18,
'$(eval $(virtual-package))',
{'add_symbol_usage': [
call('BR2_PACKAGE_PROVIDES_TOOLCHAIN_EXTERNAL', 'toolchain/toolchain-external/toolchain-external.mk', 18),
call('BR2_PACKAGE_HAS_TOOLCHAIN_EXTERNAL', 'toolchain/toolchain-external/toolchain-external.mk', 18),
call('BR2_TOOLCHAIN_EXTERNAL', 'toolchain/toolchain-external/toolchain-external.mk', 18)],
'add_symbol_virtual': [call('BR2_TOOLCHAIN_EXTERNAL', 'toolchain/toolchain-external/toolchain-external.mk', 18)]}),
('virtual package',
'package/foo/foo.mk',
18,
'$(eval $(virtual-package))',
{'add_symbol_usage': [
call('BR2_PACKAGE_PROVIDES_FOO', 'package/foo/foo.mk', 18),
call('BR2_PACKAGE_HAS_FOO', 'package/foo/foo.mk', 18),
call('BR2_PACKAGE_FOO', 'package/foo/foo.mk', 18)],
'add_symbol_virtual': [call('BR2_PACKAGE_FOO', 'package/foo/foo.mk', 18)]}),
('host virtual package',
'package/foo/foo.mk',
18,
'$(eval $(host-virtual-package))',
{'add_symbol_usage': [
call('BR2_PACKAGE_PROVIDES_HOST_FOO', 'package/foo/foo.mk', 18),
call('BR2_PACKAGE_HAS_HOST_FOO', 'package/foo/foo.mk', 18),
call('BR2_PACKAGE_HOST_FOO', 'package/foo/foo.mk', 18)]}),
('host generic package',
'package/foo/foo.mk',
18,
'$(eval $(host-package))',
{'add_symbol_usage': [call('BR2_PACKAGE_HOST_FOO', 'package/foo/foo.mk', 18)]}),
('boot package',
'boot/foo/foo.mk',
18,
'$(eval $(generic-package))',
{'add_symbol_usage': [call('BR2_TARGET_FOO', 'boot/foo/foo.mk', 18)]}),
('toolchain package',
'toolchain/foo/foo.mk',
18,
'$(eval $(generic-package))',
{'add_symbol_usage': [call('BR2_FOO', 'toolchain/foo/foo.mk', 18)]}),
('generic package',
'package/foo/foo.mk',
18,
'$(eval $(generic-package))',
{'add_symbol_usage': [call('BR2_PACKAGE_FOO', 'package/foo/foo.mk', 18)]}),
('cmake package',
'package/foo/foo.mk',
18,
'$(eval $(cmake-package))',
{'add_symbol_usage': [call('BR2_PACKAGE_FOO', 'package/foo/foo.mk', 18)]}),
]
@pytest.mark.parametrize('testname,filename,lineno,line,expected_calls', handle_eval)
def test_handle_eval(testname, filename, lineno, line, expected_calls):
db = Mock()
m.handle_eval(db, filename, lineno, line)
assert_db_calls(db, expected_calls)
handle_definition = [
('legacy attribution',
'Makefile.legacy',
9,
'BR2_LEGACY_FOO := foo',
True,
{'add_symbol_legacy_definition': [call('BR2_LEGACY_FOO', 'Makefile.legacy', 9)]}),
('attribution 1',
'Makefile',
9,
'BR2_FOO ?= foo',
False,
{'add_symbol_definition': [call('BR2_FOO', 'Makefile', 9)]}),
('attribution 2',
'Makefile',
9,
'BR2_FOO = $(BR2_BAR)',
False,
{'add_symbol_definition': [call('BR2_FOO', 'Makefile', 9)]}),
('attribution 3',
'Makefile',
9,
'BR2_FOO := foo',
False,
{'add_symbol_definition': [call('BR2_FOO', 'Makefile', 9)]}),
('normal export',
'Makefile',
90,
'export BR2_FOO',
False,
{'add_symbol_definition': [call('BR2_FOO', 'Makefile', 90)]}),
('legacy export',
'Makefile.legacy',
90,
'export BR2_FOO',
True,
{'add_symbol_legacy_definition': [call('BR2_FOO', 'Makefile.legacy', 90)]}),
]
@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 = [
('legacy',
'Makefile.legacy',
8,
'ifeq ($(BR2_LEGACY),y)',
True,
{'add_symbol_usage_in_legacy': [call('BR2_LEGACY', 'Makefile.legacy', 8)]}),
('attribution',
'Makefile',
9,
'BR2_FOO = $(BR2_BAR)',
False,
{'add_symbol_usage': [call('BR2_BAR', 'Makefile', 9)]}),
('host virtual package',
'package/foo/foo.mk',
18,
'$(eval $(host-virtual-package))',
False,
{'add_symbol_usage': [
call('BR2_PACKAGE_PROVIDES_HOST_FOO', 'package/foo/foo.mk', 18),
call('BR2_PACKAGE_HAS_HOST_FOO', 'package/foo/foo.mk', 18),
call('BR2_PACKAGE_HOST_FOO', 'package/foo/foo.mk', 18)]}),
]
@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)
populate_db = [
('legacy',
'Makefile.legacy',
[[8, 'ifeq ($(BR2_LEGACY),y)'],
[9, 'BR2_LEGACY_FOO := foo'],
[34, 'ifneq ($(BUILDROOT_CONFIG),$(BR2_CONFIG))']],
{'add_symbol_usage_in_legacy': [
call('BR2_LEGACY', 'Makefile.legacy', 8),
call('BR2_CONFIG', 'Makefile.legacy', 34)],
'add_symbol_legacy_definition': [call('BR2_LEGACY_FOO', 'Makefile.legacy', 9)]}),
('attribution',
'Makefile',
[[9, 'BR2_FOO = $(BR2_BAR)']],
{'add_symbol_definition': [call('BR2_FOO', 'Makefile', 9)],
'add_symbol_usage': [call('BR2_BAR', 'Makefile', 9)]}),
('legacy attribution',
'Makefile.legacy',
[[9, 'BR2_FOO = $(BR2_BAR)']],
{'add_symbol_legacy_definition': [call('BR2_FOO', 'Makefile.legacy', 9)],
'add_symbol_usage_in_legacy': [call('BR2_BAR', 'Makefile.legacy', 9)]}),
('generic',
'package/foo/foo.mk',
[[3, 'ifeq ($(BR2_PACKAGE_FOO_BAR):$(BR2_BAR),y:)'],
[4, 'export BR2_PACKAGE_FOO_BAZ'],
[5, '$(eval $(generic-package))']],
{'add_symbol_usage': [
call('BR2_PACKAGE_FOO_BAR', 'package/foo/foo.mk', 3),
call('BR2_BAR', 'package/foo/foo.mk', 3),
call('BR2_PACKAGE_FOO', 'package/foo/foo.mk', 5)],
'add_symbol_definition': [call('BR2_PACKAGE_FOO_BAZ', 'package/foo/foo.mk', 4)]}),
('rootfs',
'fs/foo/foo.mk',
[[4, 'ifeq ($(BR2_TARGET_ROOTFS_FOO_LZ4),y)'],
[5, '$(eval $(rootfs))']],
{'add_symbol_usage': [
call('BR2_TARGET_ROOTFS_FOO', 'fs/foo/foo.mk', 5),
call('BR2_TARGET_ROOTFS_FOO_BZIP2', 'fs/foo/foo.mk', 5),
call('BR2_TARGET_ROOTFS_FOO_GZIP', 'fs/foo/foo.mk', 5),
call('BR2_TARGET_ROOTFS_FOO_LZ4', 'fs/foo/foo.mk', 4),
call('BR2_TARGET_ROOTFS_FOO_LZ4', 'fs/foo/foo.mk', 5),
call('BR2_TARGET_ROOTFS_FOO_LZMA', 'fs/foo/foo.mk', 5),
call('BR2_TARGET_ROOTFS_FOO_LZO', 'fs/foo/foo.mk', 5),
call('BR2_TARGET_ROOTFS_FOO_XZ', 'fs/foo/foo.mk', 5),
call('BR2_TARGET_ROOTFS_FOO_ZSTD', 'fs/foo/foo.mk', 5)]}),
]
@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 = [
('arch/arch.mk.riscv',
'arch/arch.mk.riscv',
True),
('boot/lpc32xxcdl/lpc32xxcdl.mk',
'boot/lpc32xxcdl/lpc32xxcdl.mk',
True),
('fs/cramfs/cramfs.mk',
'fs/cramfs/cramfs.mk',
True),
('linux/linux-ext-fbtft.mk',
'linux/linux-ext-fbtft.mk',
True),
('package/ace/ace.mk',
'package/ace/ace.mk',
True),
('package/linux-tools/linux-tool-hv.mk.in',
'package/linux-tools/linux-tool-hv.mk.in',
True),
('package/pkg-generic.mk',
'package/pkg-generic.mk',
True),
('package/x11r7/xlib_libXt/xlib_libXt.mk',
'package/x11r7/xlib_libXt/xlib_libXt.mk',
True),
('support/dependencies/check-host-make.mk',
'support/dependencies/check-host-make.mk',
True),
('toolchain/toolchain-external/toolchain-external-arm-aarch64-be/toolchain-external-arm-aarch64-be.mk',
'toolchain/toolchain-external/toolchain-external-arm-aarch64-be/toolchain-external-arm-aarch64-be.mk',
True),
('Makefile.legacy',
'Makefile.legacy',
True),
('boot/common.mk',
'boot/common.mk',
True),
('fs/common.mk',
'fs/common.mk',
True),
('Makefile',
'Makefile',
True),
('package/Makefile.in',
'package/Makefile.in',
True),
('Config.in',
'Config.in',
False),
('package/foo/0001-Makefile.patch',
'package/foo/0001-Makefile.patch',
False),
]
@pytest.mark.parametrize('testname,filename,expected', check_filename)
def test_check_filename(testname, filename, expected):
symbols = m.check_filename(filename)
assert symbols == expected