blob: 5df622226720496a2edac14a7a782c3768d59815 [file] [log] [blame]
#!/usr/bin/env python3
# Copyright 2023 The ChromiumOS Authors
# Use of this source code is governed by a BSD-style license that can be
# found in the LICENSE file.
"""Unit tests for check_zephyr_project_config.py"""
import os
import pathlib
import site
import unittest
import check_zephyr_project_config
try:
from unittest import mock
except ImportError:
import mock # pylint:disable=import-error
site.addsitedir(check_zephyr_project_config.ZEPHYR_BASE / "scripts" / "kconfig")
# pylint:disable=import-error,wrong-import-position
import kconfiglib
# pylint:disable=protected-access
class TestKconfigCheck(unittest.TestCase):
"""Tests for KconfigCheck."""
@mock.patch("check_zephyr_project_config.KconfigCheck._init_kconfig")
def setUp(self, init_kconfig_mock): # pylint:disable=arguments-differ
mock_kconfig = mock.Mock()
init_kconfig_mock.return_value = mock_kconfig
self.kcheck = check_zephyr_project_config.KconfigCheck(True)
self.assertEqual(self.kcheck.fail_count, 0)
self.assertEqual(self.kcheck.program_kconf, {None: mock_kconfig})
@mock.patch("zephyr_module.parse_modules")
@mock.patch("zephyr_module.process_kconfig")
@mock.patch("kconfiglib.Kconfig")
def test_init_kconfig(
self, kconfig_mock, process_kconfig_mock, parse_modules_mock
):
"""Initialize a Kconfig with the upstream Kconfig file."""
mock_module = mock.Mock()
mock_module.project = "project"
mock_module.meta = "meta"
parse_modules_mock.return_value = [mock_module]
process_kconfig_mock.return_value = "fake kconfig"
os.environ["ZEPHYR_BASE"] = "/totally/not/zephyr/base"
os.environ["srctree"] = "/also/not/zephyr/base"
os.environ["ARCH_DIR"] = "not_an_arch_dir"
os.environ["ARCH"] = "not_a_star"
os.environ["BOARD_DIR"] = "something_else"
self.kcheck._init_kconfig(None)
parse_modules_mock.assert_called_once_with(
check_zephyr_project_config.ZEPHYR_BASE,
modules=[check_zephyr_project_config.EC_BASE],
)
process_kconfig_mock.assert_called_once_with("project", "meta")
kconfig_mock.assert_called_once_with(
str(check_zephyr_project_config.ZEPHYR_BASE / "Kconfig")
)
self.assertEqual(
os.environ["ZEPHYR_BASE"],
str(check_zephyr_project_config.ZEPHYR_BASE),
)
self.assertEqual(
os.environ["srctree"], str(check_zephyr_project_config.ZEPHYR_BASE)
)
self.assertEqual(os.environ["ARCH_DIR"], "arch")
self.assertEqual(os.environ["ARCH"], "*")
self.assertEqual(os.environ["BOARD_DIR"], "boards/posix/native_posix")
@mock.patch("zephyr_module.parse_modules")
@mock.patch("zephyr_module.process_kconfig")
@mock.patch("kconfiglib.Kconfig")
def test_init_kconfig_filename(
self, kconfig_mock, process_kconfig_mock, parse_modules_mock
):
"""Initialize a Kconfig with a specific path."""
kconfig_path = "my/project/Kconfig"
self.kcheck._init_kconfig(kconfig_path)
kconfig_mock.assert_called_once_with(kconfig_path)
self.assertEqual(process_kconfig_mock.call_count, 0)
parse_modules_mock.assert_called_once_with(mock.ANY, modules=mock.ANY)
@mock.patch("pathlib.Path.is_file")
@mock.patch("check_zephyr_project_config.KconfigCheck._init_kconfig")
def test_kconf_from_path(self, kconfig_mock, is_file_mock):
"""Test the cached Kconfig load from path."""
fake_kconfig_upstream = mock.Mock()
fake_kconfig_program = mock.Mock()
fake_kconfig_program_new = mock.Mock()
kconfig_mock.return_value = fake_kconfig_program_new
self.kcheck.program_kconf = {
None: fake_kconfig_upstream,
"cached_program": fake_kconfig_program,
}
# random project
out = self.kcheck._kconf_from_path("random/path")
self.assertEqual(out, fake_kconfig_upstream)
self.assertEqual(kconfig_mock.call_count, 0)
self.assertEqual(is_file_mock.call_count, 0)
# already loaded
is_file_mock.return_value = True
fake_path = pathlib.Path(
check_zephyr_project_config.EC_BASE,
"zephyr",
"program",
"cached_program",
)
out = self.kcheck._kconf_from_path(fake_path)
self.assertEqual(out, fake_kconfig_program)
self.assertEqual(kconfig_mock.call_count, 0)
is_file_mock.assert_called_once_with()
is_file_mock.reset_mock()
# project with no kconfig
is_file_mock.return_value = False
fake_path = pathlib.Path(
check_zephyr_project_config.EC_BASE,
"zephyr",
"program",
"program_with_no_kconfig",
)
out = self.kcheck._kconf_from_path(fake_path)
self.assertEqual(out, fake_kconfig_upstream)
self.assertEqual(kconfig_mock.call_count, 0)
is_file_mock.assert_called_once_with()
is_file_mock.reset_mock()
# project with kconfig
is_file_mock.return_value = True
fake_path = pathlib.Path(
check_zephyr_project_config.EC_BASE,
"zephyr",
"program",
"program_with_kconfig",
)
out = self.kcheck._kconf_from_path(fake_path)
self.assertEqual(out, fake_kconfig_program_new)
kconfig_mock.assert_called_once_with(pathlib.Path(fake_path, "Kconfig"))
is_file_mock.assert_called_once_with()
is_file_mock.reset_mock()
# final cache state
self.assertEqual(
self.kcheck.program_kconf,
{
None: fake_kconfig_upstream,
"cached_program": fake_kconfig_program,
"program_with_kconfig": fake_kconfig_program_new,
"program_with_no_kconfig": fake_kconfig_upstream,
},
)
def test_fail(self):
"""Test the fail method."""
with mock.patch.object(self.kcheck, "log") as log_mock:
self.assertEqual(self.kcheck.fail_count, 0)
self.kcheck._fail("broken")
self.assertEqual(self.kcheck.fail_count, 1)
log_mock.error.assert_called_once_with("broken")
def test_filter_config_files(self):
"""Test the config file filter."""
file_no_exist = mock.Mock()
file_no_exist.exists.return_value = False
file_exists = mock.Mock()
file_exists.exists.return_value = True
file_exists.name = "definitely_not_a_conf"
file_conf = mock.Mock()
file_conf.exists.return_value = True
file_conf.name = "blah.conf"
files = [file_no_exist, file_exists, file_conf]
out = list(self.kcheck._filter_config_files(files))
self.assertEqual(out, [file_conf])
@mock.patch("check_zephyr_project_config._default_y_if_ztest")
@mock.patch("check_zephyr_project_config._default_y")
@mock.patch("kconfiglib.expr_str")
@mock.patch("check_zephyr_project_config.KconfigCheck._kconf_from_path")
def test_check_dt_has(
self,
kconf_from_path_mock,
expr_str_mock,
def_y_mock,
def_y_if_ztest_mock,
):
"""Test the DT_HAS_ check."""
fake_symbol_no_dt = mock.Mock()
fake_symbol_no_dt.direct_dep = "nothing with devicetree"
fake_symbol_dt = mock.Mock()
fake_symbol_dt.direct_dep = (
"a bunch of stuff with DT_HAS_something in the middle"
)
fake_symbol_dt_default_n = mock.Mock()
fake_symbol_dt_default_n.direct_dep = (
"a bunch of stuff with DT_HAS_something in the middle"
)
fake_symbol_dt_default_n.orig_defaults = mock.Mock()
fake_symbol_dt_emul = mock.Mock()
fake_symbol_dt_emul.direct_dep = (
"a bunch of stuff with DT_HAS_something in the middle"
)
fake_symbol_dt_emul.orig_defaults = mock.Mock()
fake_kconf = mock.Mock()
fake_kconf.syms = {
"NO_DT": fake_symbol_no_dt,
"DT": fake_symbol_dt,
"DT_BUT_DEFAULT_N": fake_symbol_dt_default_n,
"DT_BUT_EMUL": fake_symbol_dt_emul,
}
def_y_mock.side_effect = (
lambda deps: deps != fake_symbol_dt_default_n.orig_defaults
)
def_y_if_ztest_mock.side_effect = (
lambda deps: deps == fake_symbol_dt_emul.orig_defaults
)
kconf_from_path_mock.return_value = fake_kconf
expr_str_mock.side_effect = lambda val: val
data = """
# some comment
CONFIG_NOT_RELATED=n
CONFIG_NO_DT=y
CONFIG_NO_DT=n
CONFIG_DT=y
CONFIG_DT=y # and a comment
CONFIG_DT=n
CONFIG_SOMETHING_ELSE=y
CONFIG_DT_BUT_DEFAULT_N=y
CONFIG_DT_BUT_EMUL=y
"""
with mock.patch("builtins.open", mock.mock_open(read_data=data)):
with mock.patch.object(self.kcheck, "log") as log_mock:
self.kcheck._check_dt_has("the_file")
self.assertEqual(log_mock.warning.call_count, 2)
log_mock.warning.assert_has_calls(
[
mock.call(
"%s:%d: unnecessary config option %s (depends on %s)",
"the_file",
6,
"CONFIG_DT=y",
fake_symbol_dt.direct_dep,
),
mock.call(
"%s:%d: unnecessary config option %s (depends on %s)",
"the_file",
7,
"CONFIG_DT=y",
fake_symbol_dt.direct_dep,
),
]
)
@mock.patch("check_zephyr_project_config.KconfigCheck._check_dt_has")
@mock.patch("check_zephyr_project_config.KconfigCheck._filter_config_files")
def test_run_checks_dt_has(
self, filter_config_files_mock, check_dt_has_mock
):
"""Test the run_check method for dt_has."""
filter_config_files_mock.return_value = ["a", "b"]
self.kcheck.run_checks([], True)
self.assertListEqual(
check_dt_has_mock.call_args_list, [mock.call("a"), mock.call("b")]
)
class TestKconfigSymbolChecks(unittest.TestCase):
"""Tests for symbol checks."""
def setUp(self): # pylint:disable=arguments-differ
self.sym_y = kconfiglib.Symbol()
self.sym_y.name = "y"
self.sym_y.is_constant = True
self.sym_n = kconfiglib.Symbol()
self.sym_n.name = "n"
self.sym_n.is_constant = True
self.sym_ztest = kconfiglib.Symbol()
self.sym_ztest.name = "ZTEST"
self.sym_not_ztest = (kconfiglib.NOT, self.sym_ztest)
def test_default_y(self):
"""Test the _default_y function."""
self.assertTrue(
check_zephyr_project_config._default_y([[self.sym_y, self.sym_y]])
)
self.assertTrue(
check_zephyr_project_config._default_y(
[[self.sym_y, self.sym_not_ztest]]
)
)
self.assertTrue(
check_zephyr_project_config._default_y(
[[self.sym_y, self.sym_ztest]]
)
)
self.assertFalse(
check_zephyr_project_config._default_y([[self.sym_n, self.sym_n]])
)
def test_default_y_if_ztest(self):
"""Test the _default_y_if_ztest function."""
self.assertFalse(
check_zephyr_project_config._default_y_if_ztest(
[[self.sym_y, self.sym_y]]
)
)
self.assertFalse(
check_zephyr_project_config._default_y_if_ztest(
[[self.sym_y, self.sym_not_ztest]]
)
)
self.assertTrue(
check_zephyr_project_config._default_y_if_ztest(
[[self.sym_y, self.sym_ztest]]
)
)
self.assertFalse(
check_zephyr_project_config._default_y_if_ztest(
[[self.sym_n, self.sym_n]]
)
)
if __name__ == "__main__":
unittest.main()