diff --git a/.github/workflows/test.yml b/.github/workflows/test.yml index a6d60b1..aa6a0ea 100644 --- a/.github/workflows/test.yml +++ b/.github/workflows/test.yml @@ -18,9 +18,9 @@ jobs: strategy: fail-fast: false matrix: - # NOTE: Full testsuite on Windows is disabled because the test script - # requires Linux kernel source tree. However, headless mode tests - # are now enabled for Windows. + # NOTE: Windows runs only headless + menuconfig import tests (no kernel + # tree, no Unix shell). Linux/macOS run selftests, compatibility + # tests, and example scripts. target: # Python 3.12 - python: '3.12' @@ -57,14 +57,12 @@ jobs: - name: Install Python dependencies run: | - pip install --user setuptools wheel + python -m pip install --user setuptools wheel pytest - name: Check out Linux source code # Skip for Windows (headless-only mode) if: ${{ matrix.target.headless-only != true }} uses: actions/checkout@v6 - # On Windows, checkout of 'aux.c' is expected to fail because ... Windows. - continue-on-error: true with: repository: torvalds/linux ref: v5.4 @@ -76,18 +74,31 @@ jobs: # Linux/macOS (full test): checkout to Kconfiglib subdirectory path: ${{ matrix.target.headless-only && '.' || 'Kconfiglib' }} + - name: Run pytest selftests + # Skip on Windows: several tests depend on Unix shell ($(shell,...)), + # gcc, and forward-slash paths that are unavailable on Windows CI. + if: ${{ matrix.target.headless-only != true }} + working-directory: ${{ matrix.target.headless-only && '.' || 'Kconfiglib' }} + run: | + python -m pytest tests/ -v --tb=short --ignore=tests/test_compat.py + - name: Apply Linux Kconfig Makefile patch # Skip for Windows (headless-only mode) if: ${{ matrix.target.headless-only != true }} run: | git apply Kconfiglib/makefile.patch - - name: Run testsuite + - name: Run compatibility tests and example scripts # Skip for Windows (headless-only mode) if: ${{ matrix.target.headless-only != true }} run: | Kconfiglib/tests/reltest python + - name: Install windows-curses (Windows only) + if: matrix.target.os == 'Windows' + run: | + python -m pip install windows-curses + - name: Test headless mode # Use root dir for Windows, Kconfiglib subdir for Linux/macOS working-directory: ${{ matrix.target.headless-only && '.' || 'Kconfiglib' }} @@ -101,11 +112,6 @@ jobs: print('Headless mode test passed') EOF - - name: Install windows-curses (Windows only) - if: matrix.target.os == 'Windows' - run: | - pip install windows-curses - - name: Test menuconfig import (Windows Python 3.12) if: matrix.target.os == 'Windows' # Use root dir for Windows (headless-only mode) diff --git a/CHANGELOG.md b/CHANGELOG.md new file mode 100644 index 0000000..25fd55b --- /dev/null +++ b/CHANGELOG.md @@ -0,0 +1,63 @@ +# Changelog + +## Integrated from Zephyr Upstream + +| Zephyr Commit | Description | This Project | +|---------------|-------------|--------------| +| `9b1ae78` | Fix file leaks (try/finally) | Implemented at lines 1140-1151, 3115-3118 | +| `0380400` | Menu visibility fix when leaving | Implemented at lines 1328-1333 (try/except) | +| `1e6f644` | Fix crash from unsupported locales | `23b7e61` | +| `407b92b` | NULL character input handling | `d546545` | +| `c3f7865` | Macro expansion empty string check | `a8c4929` | +| `82fdbda` | Modules property support | `bdfe32c` | +| `f58717e` | Symbol value origin tracking | `9938058` | +| `601f63d`, `6eae2bf` | Symbol.ranges 4-tuple unpacking | `a5b6ecd` | +| `ffb5459` | Dark mode support | `1dc26ef` (more comprehensive) | + +## Features Unique to This Project + +### guiconfig Enhancements +- Dark/light theme toggle with keyboard shortcut (Ctrl+T) +- Blue gear icon (replacing green X) +- Interactive search in jump-to dialog (`54aa1c9`) +- Enhanced UI experience (`b228109`) +- Select/imply origin display in menu (`b69b31e`) +- Missing `import re` fixed (ulfalizer#105, ulfalizer#136 resolved) + +### menuconfig Enhancements +- Scrollbar support (`029e3d1`) +- Enhanced UI layout + +### Testing +- Replaced testsuite.py with pytest: 120 tests across 10 modules under tests/. + Added `pytest.ini`, `tests/conftest.py`, and `tests/test_*.py` modules. + Compatibility tests (kernel C tool comparison) in `tests/test_compat.py`. + CI runs pytest on all platforms including Windows. +- Added tests for previously-untested public APIs: `expr_value()`, + `Symbol.config_string`, `Symbol.rev_dep`, `Symbol.weak_rev_dep`, + `Kconfig.missing_syms`, and `Symbol.user_loc`. + +### API Cleanup +- Removed deprecated `KconfigSyntaxError` alias (use `KconfigError`). +- Removed `InternalError` exception (was never raised). +- Removed 10 `enable_*/disable_*` warning methods on `Kconfig` + (use the `warn`, `warn_to_stderr`, `warn_assign_undef`, + `warn_assign_override`, `warn_assign_redun` attributes directly). +- Removed deprecated module-level `load_allconfig()` function + (use `Kconfig.load_allconfig()` method). + +### Core Features +- Kbuild toolchain test functions (`ced27d6`) +- Headless mode support (`27a8a0d`) +- Windows Python 3.12 compatibility (`15d3d98`) +- `transitional` keyword parsing (zephyr#25 parser support; full behavior parity tracked in TODO P1) +- `modules` keyword support (both `option modules` and bare `modules` forms) + +## Version History + +| Date | Action | Commits | +|------|--------|---------| +| 2025-10 | Symbol.ranges fix | `a5b6ecd` | +| 2025-10 | Kbuild toolchain support | `ced27d6` | +| 2025-10 | Dark/light theme | `1dc26ef` | +| 2025-10 | Interactive search | `54aa1c9` | diff --git a/README.md b/README.md index e146507..3f2dfe3 100644 --- a/README.md +++ b/README.md @@ -300,7 +300,7 @@ This warning can also be toggled by setting `Kconfig.warn_assign_undef` to `True - Robust and highly compatible with the C Kconfig tools - The [test suite](testsuite.py) automatically compares output from Kconfiglib and + The [test suite](tests/) automatically compares output from Kconfiglib and the C tools by diffing the generated `.config` files for the real kernel Kconfig and defconfig files across all ARCHes. Currently, this involves comparing output for 36 ARCHes and 498 defconfig files (or over 18,000 ARCH/defconfig combinations in "obsessive" test suite mode). All tests are expected to pass. @@ -462,32 +462,33 @@ Some examples include: ## Test suite -The test suite is run with: +The self-tests can be run from the project root with [pytest](https://docs.pytest.org/): ```shell -python Kconfiglib/testsuite.py +python -m pytest tests/ -v ``` -[pypy](https://pypy.org/) also works and is much faster for most tasks, -except for `allnoconfig.py`, `allnoconfig_simpler.py`, and `allyesconfig.py`, -where it has no time to warm up because those scripts are invoked via `make scriptconfig`. - -The test suite must be run from the top-level kernel directory. -It requires that the Kconfiglib Git repository has been cloned into it and that the Makefile patch has been applied. +To run the full suite -- self-tests, compatibility tests against the C Kconfig tools, and example scripts -- use +[tests/reltest](tests/reltest) from the top-level kernel directory (requires the Makefile patch): +```shell +Kconfiglib/tests/reltest python +``` To suppress warnings generated for the kernel `Kconfig` files, redirect `stderr` to `/dev/null`: ``` -python Kconfiglib/testsuite.py 2>/dev/null +Kconfiglib/tests/reltest python 2>/dev/null ``` -Note: Forgetting to apply the Makefile patch will cause some tests that compare generated configurations to fail. +[pypy](https://pypy.org/) also works and is much faster for most tasks, +except for `allnoconfig.py`, `allnoconfig_simpler.py`, and `allyesconfig.py`, +where it has no time to warm up because those scripts are invoked via `make scriptconfig`. -Note: The test suite overwrites `.config` in the kernel root, so make sure to back it up. +Note: Forgetting to apply the Makefile patch will cause some compatibility tests that compare generated configurations to fail. -The test suite consists of a set of self-tests and a set of compatibility tests that compare configurations -generated by Kconfiglib with those generated by the C tools across various scenarios. -See [testsuite.py](testsuite.py) for available options. +Note: The compatibility tests overwrite `.config` in the kernel root, so make sure to back it up. -The [tests/reltest](tests/reltest) script runs the test suite and all example scripts to verify that everything works as expected. +The test suite consists of self-tests (under [tests/](tests/)) and compatibility tests +([tests/test_compat.py](tests/test_compat.py)) that compare configurations generated by Kconfiglib +with those generated by the C tools across various scenarios. Occasionally, the C tools' output may change slightly (for example, due to a [recent change](https://www.spinics.net/lists/linux-kbuild/msg17074.html)). If the test suite reports failures, try running it again against the [linux-next tree](https://www.kernel.org/doc/man-pages/linux-next.html), diff --git a/kconfiglib.py b/kconfiglib.py index 50f53a5..8b9dc76 100644 --- a/kconfiglib.py +++ b/kconfiglib.py @@ -1456,7 +1456,7 @@ def load_allconfig(self, filename): Disables warnings for duplicated assignments within configuration files for the duration of the call - (kconf.warn_assign_override/warn_assign_redun = False), and restores + (warn_assign_override/warn_assign_redun = False), and restores the previous warning settings at the end. The KCONFIG_ALLCONFIG configuration file is expected to override symbols. @@ -1468,7 +1468,47 @@ def load_allconfig(self, filename): Command-specific configuration filename - "allyes.config", "allno.config", etc. """ - load_allconfig(self, filename) + allconfig = os.getenv("KCONFIG_ALLCONFIG") + if allconfig is None: + return + + def std_msg(e): + # "Upcasts" a _KconfigIOError to an IOError, removing the custom + # __str__() message. The standard message is better here. + # + # This might also convert an OSError to an IOError in obscure + # cases, but it's probably not a big deal. The distinction is + # shaky (see PEP-3151). + return IOError(e.errno, e.strerror, e.filename) + + old_warn_assign_override = self.warn_assign_override + old_warn_assign_redun = self.warn_assign_redun + self.warn_assign_override = self.warn_assign_redun = False + + if allconfig in ("", "1"): + try: + print(self.load_config(filename, False)) + except EnvironmentError as e1: + try: + print(self.load_config("all.config", False)) + except EnvironmentError as e2: + sys.exit( + "error: KCONFIG_ALLCONFIG is set, but neither {} " + "nor all.config could be opened: {}, {}".format( + filename, std_msg(e1), std_msg(e2) + ) + ) + else: + try: + print(self.load_config(allconfig, False)) + except EnvironmentError as e: + sys.exit( + "error: KCONFIG_ALLCONFIG is set to '{}', which " + "could not be opened: {}".format(allconfig, std_msg(e)) + ) + + self.warn_assign_override = old_warn_assign_override + self.warn_assign_redun = old_warn_assign_redun def write_autoconf(self, filename=None, header=None): r""" @@ -2069,76 +2109,6 @@ def unset_values(self): finally: self._warn_assign_no_prompt = True - def enable_warnings(self): - """ - Do 'Kconfig.warn = True' instead. Maintained for backwards - compatibility. - """ - self.warn = True - - def disable_warnings(self): - """ - Do 'Kconfig.warn = False' instead. Maintained for backwards - compatibility. - """ - self.warn = False - - def enable_stderr_warnings(self): - """ - Do 'Kconfig.warn_to_stderr = True' instead. Maintained for backwards - compatibility. - """ - self.warn_to_stderr = True - - def disable_stderr_warnings(self): - """ - Do 'Kconfig.warn_to_stderr = False' instead. Maintained for backwards - compatibility. - """ - self.warn_to_stderr = False - - def enable_undef_warnings(self): - """ - Do 'Kconfig.warn_assign_undef = True' instead. Maintained for backwards - compatibility. - """ - self.warn_assign_undef = True - - def disable_undef_warnings(self): - """ - Do 'Kconfig.warn_assign_undef = False' instead. Maintained for - backwards compatibility. - """ - self.warn_assign_undef = False - - def enable_override_warnings(self): - """ - Do 'Kconfig.warn_assign_override = True' instead. Maintained for - backwards compatibility. - """ - self.warn_assign_override = True - - def disable_override_warnings(self): - """ - Do 'Kconfig.warn_assign_override = False' instead. Maintained for - backwards compatibility. - """ - self.warn_assign_override = False - - def enable_redun_warnings(self): - """ - Do 'Kconfig.warn_assign_redun = True' instead. Maintained for backwards - compatibility. - """ - self.warn_assign_redun = True - - def disable_redun_warnings(self): - """ - Do 'Kconfig.warn_assign_redun = False' instead. Maintained for - backwards compatibility. - """ - self.warn_assign_redun = False - def __repr__(self): """ Returns a string with information about the Kconfig object when it is @@ -6335,19 +6305,9 @@ def __repr__(self): class KconfigError(Exception): """ Exception raised for Kconfig-related errors. - - KconfigError and KconfigSyntaxError are the same class. The - KconfigSyntaxError alias is only maintained for backwards compatibility. """ -KconfigSyntaxError = KconfigError # Backwards compatibility - - -class InternalError(Exception): - "Never raised. Kept around for backwards compatibility." - - # Workaround: # # If 'errno' and 'strerror' are set on IOError, then __str__() always returns @@ -6688,55 +6648,6 @@ def _extract_controlling_symbols(expr_list): return sym_names -def load_allconfig(kconf, filename): - """ - Use Kconfig.load_allconfig() instead, which was added in Kconfiglib 13.4.0. - Supported for backwards compatibility. Might be removed at some point after - a long period of deprecation warnings. - """ - allconfig = os.getenv("KCONFIG_ALLCONFIG") - if allconfig is None: - return - - def std_msg(e): - # "Upcasts" a _KconfigIOError to an IOError, removing the custom - # __str__() message. The standard message is better here. - # - # This might also convert an OSError to an IOError in obscure cases, - # but it's probably not a big deal. The distinction is shaky (see - # PEP-3151). - return IOError(e.errno, e.strerror, e.filename) - - old_warn_assign_override = kconf.warn_assign_override - old_warn_assign_redun = kconf.warn_assign_redun - kconf.warn_assign_override = kconf.warn_assign_redun = False - - if allconfig in ("", "1"): - try: - print(kconf.load_config(filename, False)) - except EnvironmentError as e1: - try: - print(kconf.load_config("all.config", False)) - except EnvironmentError as e2: - sys.exit( - "error: KCONFIG_ALLCONFIG is set, but neither {} " - "nor all.config could be opened: {}, {}".format( - filename, std_msg(e1), std_msg(e2) - ) - ) - else: - try: - print(kconf.load_config(allconfig, False)) - except EnvironmentError as e: - sys.exit( - "error: KCONFIG_ALLCONFIG is set to '{}', which " - "could not be opened: {}".format(allconfig, std_msg(e)) - ) - - kconf.warn_assign_override = old_warn_assign_override - kconf.warn_assign_redun = old_warn_assign_redun - - # # Internal functions # diff --git a/pytest.ini b/pytest.ini new file mode 100644 index 0000000..6a1c386 --- /dev/null +++ b/pytest.ini @@ -0,0 +1,4 @@ +[pytest] +testpaths = tests +markers = + compat: compatibility tests requiring Linux kernel source tree diff --git a/tests/Krevdep b/tests/Krevdep new file mode 100644 index 0000000..4ca830c --- /dev/null +++ b/tests/Krevdep @@ -0,0 +1,66 @@ +# For testing Symbol.rev_dep (select) and Symbol.weak_rev_dep (imply) + +config MODULES + def_bool y + +config PLAIN + bool "plain" + +# Single select +config SEL_A + bool "sel_a" + select SINGLE_TARGET + +config SINGLE_TARGET + bool + +# Multiple selectors -> OR expression +config SEL_B + bool "sel_b" + select MULTI_TARGET + +config SEL_C + bool "sel_c" + select MULTI_TARGET + +config MULTI_TARGET + bool + +# Conditional select -> AND expression +config COND_DEP + bool "cond_dep" + +config COND_SEL + bool "cond_sel" + select COND_TARGET if COND_DEP + +config COND_TARGET + bool + +# Single imply +config IMP_A + bool "imp_a" + imply IMP_SINGLE_TARGET + +config IMP_SINGLE_TARGET + bool + +# Multiple impliers -> OR expression +config IMP_B + bool "imp_b" + imply IMP_MULTI_TARGET + +config IMP_C + bool "imp_c" + imply IMP_MULTI_TARGET + +config IMP_MULTI_TARGET + bool + +# Conditional imply +config IMP_COND + bool "imp_cond" + imply IMP_COND_TARGET if COND_DEP + +config IMP_COND_TARGET + bool diff --git a/tests/conftest.py b/tests/conftest.py new file mode 100644 index 0000000..ee1bf12 --- /dev/null +++ b/tests/conftest.py @@ -0,0 +1,86 @@ +# Copyright (c) 2011-2019 Ulf Magnusson +# SPDX-License-Identifier: ISC +# +# Shared fixtures and assertion helpers for the Kconfiglib pytest suite. + +import glob +import os +import sys + +import pytest + +# Ensure kconfiglib is importable from the project root +sys.path.insert(0, os.path.join(os.path.dirname(__file__), "..")) + +from kconfiglib import TRI_TO_STR # noqa: E402 + +# --------------------------------------------------------------------------- +# Fixtures +# --------------------------------------------------------------------------- + + +@pytest.fixture(autouse=True) +def _clean_env(monkeypatch): + """Save and restore environment variables between tests. + + Also removes KCONFIG_ALLCONFIG to prevent accidental config loading. + """ + monkeypatch.delenv("KCONFIG_ALLCONFIG", raising=False) + yield + + +@pytest.fixture(autouse=True) +def _cleanup_config_files(): + """Remove config_test* files after each test.""" + yield + tests_dir = os.path.join(os.path.dirname(__file__)) + for f in glob.glob(os.path.join(tests_dir, "config_test*")): + os.remove(f) + # Also clean from project root (some tests write there) + project_root = os.path.join(os.path.dirname(__file__), "..") + for f in glob.glob(os.path.join(project_root, "config_test*")): + os.remove(f) + + +# --------------------------------------------------------------------------- +# Assertion helpers +# +# These take an explicit Kconfig instance `c` rather than closing over one. +# --------------------------------------------------------------------------- + + +def verify_value(c, sym_name, val): + """Verify that a symbol has a particular value.""" + if isinstance(val, int): + val = TRI_TO_STR[val] + + sym = c.syms[sym_name] + assert sym.str_value == val, f"{sym_name} value mismatch" + + +def assign_and_verify_value(c, sym_name, val, new_val): + """Assign val to a symbol and verify its value becomes new_val.""" + if isinstance(new_val, int): + new_val = TRI_TO_STR[new_val] + + sym = c.syms[sym_name] + assert sym.set_value(val), f"Failed to assign '{val}' to {sym_name}" + assert sym.str_value == new_val, f"{sym_name} value after assignment" + + +def assign_and_verify(c, sym_name, user_val): + """Like assign_and_verify_value(), with the expected value being the + value just set.""" + assign_and_verify_value(c, sym_name, user_val, user_val) + + +def assign_and_verify_user_value(c, sym_name, val, user_val, valid): + """Assign a user value and verify the new user value and validity.""" + sym = c.syms[sym_name] + assert sym.set_value(val) == valid, f"{sym_name} validity mismatch for '{val}'" + assert sym.user_value == user_val, f"{sym_name} user_value mismatch" + + +def verify_str(item, expected): + """Verify str(item) matches expected (strip leading/trailing newline).""" + assert str(item) == expected[1:-1] diff --git a/tests/reltest b/tests/reltest index f5f8fa2..91da6df 100755 --- a/tests/reltest +++ b/tests/reltest @@ -1,10 +1,14 @@ #!/usr/bin/env bash -# Runs the test suite and all examples scripts, bailing immediately if anything -# fails. For the examples that aren't tested in the test suite, we just confirm -# that they at least run. +# Runs compatibility tests and example scripts from a kernel tree root, +# bailing immediately if anything fails. For the examples that aren't +# tested by the test suite, we just confirm that they at least run. # # Should be run from the kernel root with $ Kconfiglib/tests/reltest +# +# Selftests (tests/test_*.py excluding test_compat.py) should be run +# separately from the Kconfiglib project root: +# $ cd Kconfiglib && python -m pytest tests/ -v test_script() { echo -e "\n================= $1 with $py =================\n" @@ -26,11 +30,15 @@ else py_execs=$@ fi +kconfiglib_dir="$(cd "$(dirname "$0")/.." && pwd)" + for py in $py_execs; do - echo -e "\n================= Test suite with $py =================\n" + echo -e "\n================= Compatibility tests with $py =================\n" - if ! $py Kconfiglib/testsuite.py; then - echo "test suite failed for $py" + # Compat tests run from the kernel root (cwd) where they compare output + # against the C Kconfig tools. + if ! $py -m pytest "$kconfiglib_dir/tests/test_compat.py" -x -v --tb=short; then + echo "compatibility tests failed for $py" exit 1 fi diff --git a/tests/test_compat.py b/tests/test_compat.py new file mode 100644 index 0000000..edadf72 --- /dev/null +++ b/tests/test_compat.py @@ -0,0 +1,473 @@ +# Copyright (c) 2011-2019 Ulf Magnusson +# SPDX-License-Identifier: ISC +# +# Compatibility tests that compare Kconfiglib output against the C Kconfig +# tools (scripts/kconfig/conf) in a Linux kernel source tree. +# +# These tests must be run from the root of a Linux kernel tree that has +# Kconfiglib checked out (or symlinked) as a subdirectory. The C conf +# tool must already be built (or the test fixture will attempt to build +# it via 'make allnoconfig'). +# +# Usage: +# cd /path/to/linux +# python -m pytest Kconfiglib/tests/test_compat.py -v +# +# The entire module is skipped when scripts/kconfig/conf does not exist. + +import difflib +import os +import re +import shutil +import subprocess +import sys +import tempfile + +import pytest + +from kconfiglib import Kconfig, Symbol, Choice, BOOL, TRISTATE, MENU, COMMENT + +# --------------------------------------------------------------------------- +# Module-level skip: these tests only make sense inside a kernel tree. +# --------------------------------------------------------------------------- + +pytestmark = [ + pytest.mark.skipif( + not os.path.exists("scripts/kconfig/conf"), + reason="Requires Linux kernel source tree with scripts/kconfig/conf built", + ), + pytest.mark.compat, +] + +# --------------------------------------------------------------------------- +# Configuration flags. +# Override via environment variables if needed: +# KCONFIGLIB_OBSESSIVE=1 -- test every arch/defconfig combination +# KCONFIGLIB_OBSESSIVE_MIN_CONFIG=1 +# KCONFIGLIB_LOG=1 -- log defconfig failures to a file +# --------------------------------------------------------------------------- + +obsessive = os.environ.get("KCONFIGLIB_OBSESSIVE", "") == "1" +obsessive_min_config = os.environ.get("KCONFIGLIB_OBSESSIVE_MIN_CONFIG", "") == "1" +log = os.environ.get("KCONFIGLIB_LOG", "") == "1" + + +# --------------------------------------------------------------------------- +# Fixtures +# --------------------------------------------------------------------------- + + +@pytest.fixture(autouse=True, scope="module") +def kernel_env(): + """Set up kernel build environment variables. + + These are referenced inside the kernel Kconfig files and must be present + before any Kconfig object is instantiated. + """ + os.environ["KERNELVERSION"] = ( + subprocess.check_output("make kernelversion", shell=True) + .decode("utf-8") + .rstrip() + ) + os.environ["CC_VERSION_TEXT"] = ( + subprocess.check_output("gcc --version | head -n1", shell=True) + .decode("utf-8") + .rstrip() + ) + os.environ["srctree"] = "." + os.environ["CC"] = "gcc" + os.environ["LD"] = "ld" + yield + + +# --------------------------------------------------------------------------- +# Helper functions +# --------------------------------------------------------------------------- + + +def shell(cmd): + """Run a shell command, suppressing stdout and stderr.""" + subprocess.call( + cmd, shell=True, stdout=subprocess.DEVNULL, stderr=subprocess.DEVNULL + ) + + +def all_arch_srcarch(): + """Yield (arch, srcarch) pairs for every architecture in the tree. + + Some architectures are skipped because they are broken with the C tools + or require a non-standard testing setup (user-mode Linux). + """ + for srcarch in os.listdir("arch"): + # arc and h8300 are currently broken with the C tools on linux-next + # as well. Perhaps they require cross-compilers to be installed. + # + # User-mode Linux has an unorthodox Kconfig setup that would require + # a different testing setup. Skip it too. + if srcarch in ("arc", "h8300", "um"): + continue + + if os.path.exists(os.path.join("arch", srcarch, "Kconfig")): + yield (srcarch, srcarch) + + # Some arches define additional ARCH settings with ARCH != SRCARCH + # (search for "Additional ARCH settings for" in the top-level Makefile) + + yield ("i386", "x86") + yield ("x86_64", "x86") + + yield ("sparc32", "sparc") + yield ("sparc64", "sparc") + + yield ("sh64", "sh") + + +def run_conf_and_compare(script, conf_flag, arch): + """Run a Kconfiglib script via 'make scriptconfig', then run the C + implementation with *conf_flag*, and compare the resulting .config files. + """ + shell(f"make scriptconfig SCRIPT={script} PYTHONCMD='{sys.executable}'") + shell("mv .config ._config") + shell(f"scripts/kconfig/conf --{conf_flag} Kconfig") + compare_configs(arch) + + +def defconfig_files(srcarch): + """Yield defconfig file paths for a particular srcarch subdirectory + (arch//). + """ + srcarch_dir = os.path.join("arch", srcarch) + + root_defconfig = os.path.join(srcarch_dir, "defconfig") + if os.path.exists(root_defconfig): + yield root_defconfig + + defconfigs_dir = os.path.join(srcarch_dir, "configs") + if not os.path.isdir(defconfigs_dir): + return + + for dirpath, _, filenames in os.walk(defconfigs_dir): + for filename in filenames: + yield os.path.join(dirpath, filename) + + +def collect_defconfigs(srcarch, use_obsessive): + """Collect defconfig paths, optionally from all architectures.""" + if use_obsessive: + configs = [] + for sa in os.listdir("arch"): + configs.extend(defconfig_files(sa)) + return configs + return defconfig_files(srcarch) + + +def rm_configs(): + """Delete any old '.config' and '._config', if present.""" + for name in (".config", "._config"): + try: + os.remove(name) + except FileNotFoundError: + pass + + +def compare_configs(arch): + """Compare .config (C tool) with ._config (Kconfiglib) and assert they + are identical. + """ + assert equal_configs(), f"Mismatched .config for arch {arch}" + + +def equal_configs(): + """Return True if .config and ._config are equivalent (ignoring the + header comment generated by the C conf tool). + + On mismatch, prints a unified diff to aid debugging. + """ + with open(".config") as f: + their = f.readlines() + + # Strip the header generated by 'conf'. Stop at the first non-comment + # line, or at a "# CONFIG_... is not set" comment (which is config data). + for i, line in enumerate(their): + if not line.startswith("#") or re.match(r"# CONFIG_(\w+) is not set", line): + break + else: + i = len(their) + their = their[i:] + + try: + with open("._config") as f: + our = f.readlines() + except FileNotFoundError: + print("._config not found. Did you forget to apply the Makefile patch?") + return False + + if their == our: + return True + + print("Mismatched .config's! Unified diff:") + sys.stdout.writelines( + difflib.unified_diff(their, our, fromfile="their", tofile="our") + ) + return False + + +def _exercise_sym_api(kconf, sym): + """Call all public API methods/properties on a symbol to verify nothing + crashes or hangs. + """ + repr(sym) + str(sym) + sym.assignable + kconf.warn = False + sym.set_value(2) + sym.set_value("foo") + sym.unset_value() + kconf.warn = True + sym.str_value + sym.tri_value + sym.type + sym.visibility + + +# --------------------------------------------------------------------------- +# Tests +# --------------------------------------------------------------------------- + +# The five all*config tests share the same structure: iterate architectures, +# run a Kconfiglib script, then compare against the C tool output. A single +# parametrized test covers all of them without losing per-mode granularity +# in pytest output. + +_ALLCONFIG_CASES = [ + ("Kconfiglib/allnoconfig.py", "allnoconfig"), + ("Kconfiglib/examples/allnoconfig_walk.py", "allnoconfig"), + ("Kconfiglib/allmodconfig.py", "allmodconfig"), + ("Kconfiglib/allyesconfig.py", "allyesconfig"), + ("Kconfiglib/alldefconfig.py", "alldefconfig"), +] + + +@pytest.mark.parametrize( + "script,conf_flag", + _ALLCONFIG_CASES, + ids=[ + c[1] if i == 0 or c[1] != _ALLCONFIG_CASES[i - 1][1] else f"{c[1]}_walk" + for i, c in enumerate(_ALLCONFIG_CASES) + ], +) +def test_allconfig(script, conf_flag): + """Verify that a Kconfiglib *config script generates the same .config + as the corresponding 'make ', for each architecture. + """ + for arch, srcarch in all_arch_srcarch(): + os.environ["ARCH"] = arch + os.environ["SRCARCH"] = srcarch + rm_configs() + run_conf_and_compare(script, conf_flag, arch) + + +def test_defconfig(): + """Verify that Kconfiglib generates the same .config as + scripts/kconfig/conf, for each architecture/defconfig pair. + + In obsessive mode (KCONFIGLIB_OBSESSIVE=1), this test includes + nonsensical groupings of arches with defconfigs from other arches + (every arch/defconfig combination) and takes an order of magnitude + longer to run. + + With logging enabled (KCONFIGLIB_LOG=1), failures are appended to + test_defconfig_fails in the kernel root. + """ + for arch, srcarch in all_arch_srcarch(): + os.environ["ARCH"] = arch + os.environ["SRCARCH"] = srcarch + rm_configs() + + kconf = Kconfig() + + for defconfig in collect_defconfigs(srcarch, obsessive): + rm_configs() + + kconf.load_config(defconfig) + kconf.write_config("._config") + shell(f"scripts/kconfig/conf --defconfig='{defconfig}' Kconfig") + + label = f" {arch:14}with {defconfig:60} " + + if equal_configs(): + print(label + "OK") + else: + if log: + with open("test_defconfig_fails", "a") as fail_log: + fail_log.write(f"{arch} with {defconfig} did not match\n") + pytest.fail(label + "FAIL") + + +def test_min_config(): + """Verify that Kconfiglib generates the same .config as + 'make savedefconfig' for each architecture/defconfig pair. + + NOTE: This test is disabled in the original suite due to a bug in the + C tools for a few defconfigs. It is included here for completeness + and can be run explicitly. + """ + for arch, srcarch in all_arch_srcarch(): + os.environ["ARCH"] = arch + os.environ["SRCARCH"] = srcarch + rm_configs() + + kconf = Kconfig() + + for defconfig in collect_defconfigs(srcarch, obsessive_min_config): + rm_configs() + + kconf.load_config(defconfig) + kconf.write_min_config("._config") + + shell(f"cp {defconfig} .config") + shell("scripts/kconfig/conf --savedefconfig=.config Kconfig") + + label = f" {arch:14}with {defconfig:60} " + + if equal_configs(): + print(label + "OK") + else: + print(label + "FAIL") + pytest.fail(label + "FAIL") + + +def test_sanity(): + """Do sanity checks on each configuration and call all public methods + on all symbols, choices, and menu nodes for all architectures to make + sure we never crash or hang. + """ + for arch, srcarch in all_arch_srcarch(): + os.environ["ARCH"] = arch + os.environ["SRCARCH"] = srcarch + rm_configs() + + print(f"For {arch}...") + + kconf = Kconfig() + + for sym in kconf.defined_syms: + assert sym._visited == 2, ( + f"{sym.name} has broken dependency loop detection " + f"(_visited = {sym._visited})" + ) + + kconf.modules + kconf.defconfig_list + kconf.defconfig_filename + + # Exercise warning attribute toggles + kconf.warn_assign_redun = True + kconf.warn_assign_redun = False + kconf.warn_assign_undef = True + kconf.warn_assign_undef = False + kconf.warn = True + kconf.warn = False + kconf.warn_to_stderr = True + kconf.warn_to_stderr = False + + kconf.mainmenu_text + kconf.unset_values() + + kconf.write_autoconf("/dev/null") + + tmpdir = tempfile.mkdtemp() + kconf.sync_deps(os.path.join(tmpdir, "deps")) # Create + kconf.sync_deps(os.path.join(tmpdir, "deps")) # Update + shutil.rmtree(tmpdir) + + # -- Verify non-constant symbols (kconf.syms) -- + + for key, sym in kconf.syms.items(): + assert isinstance(key, str), f"weird key '{key}' in syms dict" + assert not sym.is_constant, f"{sym.name} in 'syms' and constant" + assert ( + sym not in kconf.const_syms + ), f"{sym.name} in both 'syms' and 'const_syms'" + + for dep in sym._dependents: + assert ( + not dep.is_constant + ), f"the constant symbol {dep.name} depends on {sym.name}" + + _exercise_sym_api(kconf, sym) + sym.user_value + + # -- Verify defined symbols have nodes and correct choice types -- + + for sym in kconf.defined_syms: + assert sym.nodes, f"{sym.name} is defined but lacks menu nodes" + + if sym.choice: + assert sym.orig_type in ( + BOOL, + TRISTATE, + ), f"{sym.name} is a choice symbol but not bool/tristate" + + # -- Verify constant symbols (kconf.const_syms) -- + + for key, sym in kconf.const_syms.items(): + assert isinstance(key, str), f"weird key '{key}' in const_syms dict" + assert ( + sym.is_constant + ), f'"{sym.name}" is in const_syms but not marked constant' + assert not sym.nodes, f'"{sym.name}" is constant but has menu nodes' + assert ( + not sym._dependents + ), f'"{sym.name}" is constant but is a dependency of some symbol' + assert not sym.choice, f'"{sym.name}" is constant and a choice symbol' + + _exercise_sym_api(kconf, sym) + + # -- Verify choices -- + + for choice in kconf.choices: + for sym in choice.syms: + assert sym.choice is choice, ( + f"{sym.name} is in choice.syms but 'sym.choice' is not " + "the choice" + ) + assert sym.type in ( + BOOL, + TRISTATE, + ), f"{sym.name} is a choice symbol but is not a bool/tristate" + + str(choice) + repr(choice) + choice.str_value + choice.tri_value + choice.user_value + choice.assignable + choice.selection + choice.type + choice.visibility + + # -- Walk all menu nodes -- + + node = kconf.top_node + + while True: + repr(node) + str(node) + assert isinstance(node.item, (Symbol, Choice)) or node.item in ( + MENU, + COMMENT, + ), f"'{node.item}' appeared as a menu item" + + if node.list is not None: + node = node.list + elif node.next is not None: + node = node.next + else: + while node.parent is not None: + node = node.parent + if node.next is not None: + node = node.next + break + else: + break diff --git a/tests/test_config_io.py b/tests/test_config_io.py new file mode 100644 index 0000000..4e627ba --- /dev/null +++ b/tests/test_config_io.py @@ -0,0 +1,288 @@ +# Copyright (c) 2011-2019 Ulf Magnusson +# SPDX-License-Identifier: ISC +# +# Config I/O tests: load_config, write_config, write_min_config, +# header strings, symbol order in generated files, config_string, +# and missing_syms. + +import os +import tempfile + +from kconfiglib import Kconfig +from conftest import verify_value + + +def verify_file_contents(fname, expected): + with open(fname) as f: + actual = f.read() + assert actual == expected, f"{fname} contains '{actual}'. Expected '{expected}'." + + +# -- .config escape roundtrip -------------------------------------------------- + + +def test_config_escape_roundtrip(): + config_test_file = "tests/config_test" + + c = Kconfig("tests/Kescape") + + # Test the default value + c.write_config(config_test_file + "_from_def") + verify_file_contents( + config_test_file + "_from_def", r'''CONFIG_STRING="\"\\"''' "\n" + ) + + # Write our own value + c.syms["STRING"].set_value(r"""\"a'\\""") + c.write_config(config_test_file + "_from_user") + verify_file_contents( + config_test_file + "_from_user", r'''CONFIG_STRING="\\\"a'\\\\"''' "\n" + ) + + # Read back the two configs and verify the respective values + c.load_config(config_test_file + "_from_def") + verify_value(c, "STRING", '"\\') + c.load_config(config_test_file + "_from_user") + verify_value(c, "STRING", r"""\"a'\\""") + + +# -- .config append loading ---------------------------------------------------- + + +def test_config_append(): + c = Kconfig("tests/Kappend") + + # Values before assigning + verify_value(c, "BOOL", "n") + verify_value(c, "STRING", "") + + # Assign BOOL + c.load_config("tests/config_set_bool", replace=False) + verify_value(c, "BOOL", "y") + verify_value(c, "STRING", "") + + # Assign STRING + c.load_config("tests/config_set_string", replace=False) + verify_value(c, "BOOL", "y") + verify_value(c, "STRING", "foo bar") + + # Reset BOOL + c.load_config("tests/config_set_string") + verify_value(c, "BOOL", "n") + verify_value(c, "STRING", "foo bar") + + # Loading a completely empty .config should reset values + c.load_config("tests/empty") + verify_value(c, "STRING", "") + + # An indented assignment in a .config should be ignored + c.load_config("tests/config_indented") + verify_value(c, "IGNOREME", "y") + + +# -- symbol order in autoconf and minimal config ------------------------------- + + +def test_symbol_order(): + config_test_file = "tests/config_test" + + c = Kconfig("tests/Korder") + + c.write_autoconf(config_test_file) + verify_file_contents( + config_test_file, + """\ +#define CONFIG_O 0 +#define CONFIG_R 1 +#define CONFIG_D 2 +#define CONFIG_E 3 +#define CONFIG_R2 4 +#define CONFIG_I 5 +#define CONFIG_N 6 +#define CONFIG_G 7 +""", + ) + + # Differs from defaults + c.syms["O"].set_value("-1") + c.syms["R"].set_value("-1") + c.syms["E"].set_value("-1") + c.syms["R2"].set_value("-1") + c.syms["N"].set_value("-1") + c.syms["G"].set_value("-1") + c.write_min_config(config_test_file) + verify_file_contents( + config_test_file, + """\ +CONFIG_O=-1 +CONFIG_R=-1 +CONFIG_E=-1 +CONFIG_R2=-1 +CONFIG_N=-1 +CONFIG_G=-1 +""", + ) + + +# -- header strings in configuration files ------------------------------------ + + +def test_header_strings(monkeypatch): + config_test_file = "tests/config_test" + + monkeypatch.setenv("KCONFIG_CONFIG_HEADER", "config header from env.\n") + monkeypatch.setenv("KCONFIG_AUTOHEADER_HEADER", "header header from env.\n") + + c = Kconfig("tests/Kheader") + + c.write_config(config_test_file, header="config header from param\n") + verify_file_contents( + config_test_file, + """\ +config header from param +CONFIG_FOO=y +""", + ) + + c.write_min_config(config_test_file, header="min. config header from param\n") + verify_file_contents( + config_test_file, + """\ +min. config header from param +""", + ) + + c.write_config(config_test_file) + verify_file_contents( + config_test_file, + """\ +config header from env. +CONFIG_FOO=y +""", + ) + + c.write_min_config(config_test_file) + verify_file_contents( + config_test_file, + """\ +config header from env. +""", + ) + + c.write_autoconf(config_test_file, header="header header from param\n") + verify_file_contents( + config_test_file, + """\ +header header from param +#define CONFIG_FOO 1 +""", + ) + + c.write_autoconf(config_test_file) + verify_file_contents( + config_test_file, + """\ +header header from env. +#define CONFIG_FOO 1 +""", + ) + + +# -- Kconfig fetching and separation ------------------------------------------ + + +def test_kconfig_fetching(): + for c in ( + Kconfig("tests/Kmisc", warn=False), + Kconfig("tests/Kmisc", warn=False), + ): + for item in ( + c.syms["BOOL"], + c.syms["BOOL"].nodes[0], + c.named_choices["OPTIONAL"], + c.named_choices["OPTIONAL"].nodes[0], + c.syms["MENU_HOOK"].nodes[0].next, + c.syms["COMMENT_HOOK"].nodes[0].next, + ): + assert item.kconfig is c, f".kconfig not properly set for {item!r}" + + +# -- Symbol.config_string ---------------------------------------------------- + + +def test_config_string(): + c = Kconfig("tests/Kassignable", warn=False) + c.modules.set_value(2) + + # Bool y -> "CONFIG_...=y" + c.syms["Y_VIS_BOOL"].set_value(2) + assert c.syms["Y_VIS_BOOL"].config_string == "CONFIG_Y_VIS_BOOL=y\n" + + # Bool n -> "# CONFIG_... is not set" + c.syms["Y_VIS_BOOL"].set_value(0) + assert c.syms["Y_VIS_BOOL"].config_string == "# CONFIG_Y_VIS_BOOL is not set\n" + + # Tristate m -> "CONFIG_...=m" + c.syms["Y_VIS_TRI"].set_value(1) + assert c.syms["Y_VIS_TRI"].config_string == "CONFIG_Y_VIS_TRI=m\n" + + # Symbol with no visibility -> empty string (_write_to_conf false) + assert c.syms["N_VIS_BOOL"].config_string == "" + + # String symbol: "CONFIG_...=\"value\"" + c2 = Kconfig("tests/Kescape") + c2.syms["STRING"].set_value("hello world") + assert c2.syms["STRING"].config_string == 'CONFIG_STRING="hello world"\n' + + # String with characters needing escaping + c2.syms["STRING"].set_value('a"b\\c') + assert c2.syms["STRING"].config_string == 'CONFIG_STRING="a\\"b\\\\c"\n' + + # Int symbol: "CONFIG_...=value" + c3 = Kconfig("tests/Krange", warn=False) + c3.syms["INT_RANGE_10_20"].set_value("15") + assert c3.syms["INT_RANGE_10_20"].config_string == "CONFIG_INT_RANGE_10_20=15\n" + + # Hex symbol: "CONFIG_...=value" + c3.syms["HEX_RANGE_10_20"].set_value("0x15") + assert c3.syms["HEX_RANGE_10_20"].config_string == "CONFIG_HEX_RANGE_10_20=0x15\n" + + +# -- Kconfig.missing_syms --------------------------------------------------- + + +def test_missing_syms(): + c = Kconfig("tests/Kappend", warn=False) + + # Initially empty + assert c.missing_syms == [] + + # Write configs with unknown symbols + with tempfile.NamedTemporaryFile(mode="w", suffix=".config", delete=False) as f: + f.write("CONFIG_UNKNOWN_A=y\n") + f.write("CONFIG_UNKNOWN_B=42\n") + tmppath = f.name + + with tempfile.NamedTemporaryFile(mode="w", suffix=".config", delete=False) as f: + f.write("CONFIG_UNKNOWN_C=m\n") + tmppath2 = f.name + + try: + c.load_config(tmppath) + assert ("UNKNOWN_A", "y") in c.missing_syms + assert ("UNKNOWN_B", "42") in c.missing_syms + assert len(c.missing_syms) == 2 + + # replace=True (default) clears missing_syms before loading + c.load_config(tmppath2) + assert c.missing_syms == [("UNKNOWN_C", "m")] + + # replace=False appends to missing_syms + c.load_config(tmppath, replace=False) + assert ("UNKNOWN_C", "m") in c.missing_syms + assert ("UNKNOWN_A", "y") in c.missing_syms + assert ("UNKNOWN_B", "42") in c.missing_syms + assert len(c.missing_syms) == 3 + finally: + os.unlink(tmppath) + os.unlink(tmppath2) diff --git a/tests/test_deps.py b/tests/test_deps.py new file mode 100644 index 0000000..dad6c99 --- /dev/null +++ b/tests/test_deps.py @@ -0,0 +1,241 @@ +# Copyright (c) 2011-2019 Ulf Magnusson +# SPDX-License-Identifier: ISC +# +# Dependency-related tests: +# direct_dep, conditional dependencies, referenced, 'if' node removal, +# multi-def property copying, and dependency loop detection. + +import pytest + +from kconfiglib import Kconfig, KconfigError, expr_str + +# -- Symbol/Choice.direct_dep ------------------------------------------------ + + +def test_direct_dep(): + c = Kconfig("tests/Kdirdep") + + assert expr_str(c.syms["NO_DEP_SYM"].direct_dep) == "y" + assert expr_str(c.syms["DEP_SYM"].direct_dep) == "A || (B && C) || !D" + + assert expr_str(c.named_choices["NO_DEP_CHOICE"].direct_dep) == "y" + assert expr_str(c.named_choices["DEP_CHOICE"].direct_dep) == "A || B || C" + + +# -- Conditional dependencies ------------------------------------------------ + + +def test_conditional_deps(): + c = Kconfig("tests/Kconddep") + + assert expr_str(c.syms["COND_DEP_1"].direct_dep) == "!B || A" + assert expr_str(c.syms["COND_DEP_2"].direct_dep) == "!E || (C && D)" + assert expr_str(c.syms["COND_DEP_MIXED"].direct_dep) == "A && (!C || B) && D" + assert expr_str(c.named_choices["COND_CHOICE"].direct_dep) == "!Y || X" + assert expr_str(c.syms["MULTI_COND"].direct_dep) == "(!B || A) && (!D || C)" + + +# -- MenuNode/Symbol/Choice.referenced --------------------------------------- + + +def test_referenced(): + c = Kconfig("tests/Kreferenced", warn=False) + + def verify_refs(item, *dep_names): + assert tuple(sorted(item.name for item in item.referenced)) == dep_names + + verify_refs(c.top_node, "y") + + verify_refs(c.syms["NO_REFS"].nodes[0], "y") + + verify_refs(c.syms["JUST_DEPENDS_ON_REFS"].nodes[0], "A", "B") + + verify_refs( + c.syms["LOTS_OF_REFS"].nodes[0], + *(chr(n) for n in range(ord("A"), ord("Z") + 1)), + ) + + verify_refs( + c.syms["INT_REFS"].nodes[0], "A", "B", "C", "D", "E", "F", "G", "H", "y" + ) + + verify_refs(c.syms["CHOICE_REF"].nodes[0], "CHOICE") + + verify_refs(c.menus[0], "A", "B", "C", "D") + + verify_refs(c.comments[0], "A", "B") + + verify_refs(c.syms["MULTI_DEF_SYM"], "A", "B", "C", "y") + verify_refs(c.named_choices["MULTI_DEF_CHOICE"], "A", "B", "C") + + +# -- 'if' node removal ------------------------------------------------------- + + +def test_if_removal(): + c = Kconfig("tests/Kifremoval", warn=False) + + nodes = tuple(c.node_iter()) + + expected_names = ["A", "B", "C", "D"] + for i, name in enumerate(expected_names): + assert nodes[i].item.name == name + + expected_prompts = ["E", "F", "G"] + for i, prompt in enumerate(expected_prompts): + assert nodes[4 + i].prompt[0] == prompt + + expected_tail = ["H", "I", "J"] + for i, name in enumerate(expected_tail): + assert nodes[7 + i].item.name == name + + assert len(nodes) == 10, "Wrong number of nodes after 'if' removal" + + +# -- Multi-def property copying ----------------------------------------------- + + +def test_multidef_property_copying(): + c = Kconfig("tests/Kdepcopy", warn=False) + + def verify_props(desc, props, prop_names): + actual = [prop[0].name for prop in props] + expected = prop_names.split() + assert actual == expected, f"{desc} properties" + + verify_props( + "default", c.syms["MULTIDEF"].defaults, "A B C D E F G H I J K L M N O P Q R" + ) + + verify_props("select", c.syms["MULTIDEF"].selects, "AA BB CC DD EE FF GG HH II JJ") + + verify_props("imply", c.syms["MULTIDEF"].implies, "AA BB CC DD EE FF GG HH II JJ") + + verify_props("select", c.syms["MULTIDEF_CHOICE"].selects, "A B C") + + verify_props("range", c.syms["MULTIDEF_RANGE"].ranges, "A B C D E F") + + verify_props("default", c.choices[1].defaults, "A B C D E") + + +# -- Dependency loop detection ------------------------------------------------ + + +def test_deploop_detection(): + for i in range(11): + filename = "tests/Kdeploop" + str(i) + with pytest.raises(KconfigError, match="Dependency loop"): + Kconfig(filename) + + +# -- Symbol.rev_dep (select) ------------------------------------------------- + + +def test_rev_dep(): + c = Kconfig("tests/Krevdep", warn=False) + + # Symbol with no selectors: rev_dep is the constant 'n' + assert expr_str(c.syms["PLAIN"].rev_dep) == "n" + + # Single select: rev_dep is the selector symbol + assert expr_str(c.syms["SINGLE_TARGET"].rev_dep) == "SEL_A" + + # Multiple selectors: rev_dep is OR of all selectors + assert expr_str(c.syms["MULTI_TARGET"].rev_dep) == "SEL_B || SEL_C" + + # Conditional select: rev_dep is (selector AND condition) + assert expr_str(c.syms["COND_TARGET"].rev_dep) == "COND_SEL && COND_DEP" + + +# -- Symbol.weak_rev_dep (imply) --------------------------------------------- + + +def test_weak_rev_dep(): + c = Kconfig("tests/Krevdep", warn=False) + + # Symbol with no impliers: weak_rev_dep is 'n' + assert expr_str(c.syms["PLAIN"].weak_rev_dep) == "n" + + # Single imply + assert expr_str(c.syms["IMP_SINGLE_TARGET"].weak_rev_dep) == "IMP_A" + + # Multiple impliers + assert expr_str(c.syms["IMP_MULTI_TARGET"].weak_rev_dep) == "IMP_B || IMP_C" + + # Conditional imply + assert expr_str(c.syms["IMP_COND_TARGET"].weak_rev_dep) == "IMP_COND && COND_DEP" + + +# -- Dependency loop detection ------------------------------------------------ + + +def test_deploop_message(): + with pytest.raises(KconfigError) as exc_info: + Kconfig("tests/Kdeploop10") + + assert str(exc_info.value) == """ +Dependency loop +=============== + +A (defined at tests/Kdeploop10:1), with definition... + +config A +\tbool +\tdepends on B + +...depends on B (defined at tests/Kdeploop10:5), with definition... + +config B +\tbool +\tdepends on C = 7 + +...depends on C (defined at tests/Kdeploop10:9), with definition... + +config C +\tint +\trange D 8 + +...depends on D (defined at tests/Kdeploop10:13), with definition... + +config D +\tint +\tdefault 3 if E +\tdefault 8 + +...depends on E (defined at tests/Kdeploop10:18), with definition... + +config E +\tbool + +(select-related dependencies: F && G) + +...depends on G (defined at tests/Kdeploop10:25), with definition... + +config G +\tbool +\tdepends on H + +...depends on the choice symbol H (defined at tests/Kdeploop10:32), with definition... + +config H +\tbool "H" +\tdepends on I && + +...depends on the choice symbol I (defined at tests/Kdeploop10:41), with definition... + +config I +\tbool "I" +\tdepends on + +...depends on (defined at tests/Kdeploop10:38), with definition... + +choice +\tbool "choice" if J + +...depends on J (defined at tests/Kdeploop10:46), with definition... + +config J +\tbool +\tdepends on A + +...depends again on A (defined at tests/Kdeploop10:1)""" diff --git a/tests/test_expressions.py b/tests/test_expressions.py new file mode 100644 index 0000000..41bd500 --- /dev/null +++ b/tests/test_expressions.py @@ -0,0 +1,409 @@ +# Copyright (c) 2011-2019 Ulf Magnusson +# SPDX-License-Identifier: ISC +# +# Expression evaluation, split_expr(), and expr_items() tests. + +import pytest + +from kconfiglib import ( + AND, + OR, + Kconfig, + KconfigError, + expr_items, + expr_str, + expr_value, + split_expr, +) + +# --------------------------------------------------------------------------- +# Helpers +# --------------------------------------------------------------------------- + + +def _eval(c, expr, expected): + """Assert that c.eval_string(expr) returns *expected*.""" + res = c.eval_string(expr) + assert res == expected, f"Expression '{expr}' evaluation" + + +def _parse_expr(c, text): + """Use the internal tokenizer to parse an expression string.""" + c._tokens = c._tokenize("if " + text)[1:] + c._tokens_i = 0 + return c._parse_expr(False) + + +def _verify_split(c, cases, op): + """Verify split_expr() for a list of (input, expected_parts) cases.""" + op_name = "OR" if op is OR else "AND" + for to_split, expected_strs in cases: + operands = split_expr(_parse_expr(c, to_split), op) + assert len(operands) == len( + expected_strs + ), f"split_expr '{to_split}' by {op_name}" + for operand, operand_str in zip(operands, expected_strs): + assert expr_str(operand) == operand_str + + +# --------------------------------------------------------------------------- +# Expression evaluation: no modules +# --------------------------------------------------------------------------- + + +def test_eval_no_modules(): + c = Kconfig("tests/Keval", warn=False) + + _eval(c, "n", 0) + _eval(c, "m", 0) + _eval(c, "y", 2) + _eval(c, "'n'", 0) + _eval(c, "'m'", 0) + _eval(c, "'y'", 2) + _eval(c, "M", 2) + + +# --------------------------------------------------------------------------- +# Expression evaluation: modules enabled +# --------------------------------------------------------------------------- + + +def test_eval_with_modules(): + c = Kconfig("tests/Keval", warn=False) + c.modules.set_value(2) + + # Basic tristate + _eval(c, "n", 0) + _eval(c, "m", 1) + _eval(c, "y", 2) + _eval(c, "'n'", 0) + _eval(c, "'m'", 1) + _eval(c, "'y'", 2) + _eval(c, "M", 1) + _eval(c, "(Y || N) && (m && y)", 1) + + # Non-bool/non-tristate symbols are always n in a tristate sense + _eval(c, "Y_STRING", 0) + _eval(c, "Y_STRING || m", 1) + + # Constants besides y and m + _eval(c, '"foo"', 0) + _eval(c, '"foo" || "bar"', 0) + _eval(c, '"foo" || m', 1) + + # --- equality for N --- + _eval(c, "N = N", 2) + _eval(c, "N = n", 2) + _eval(c, "N = 'n'", 2) + _eval(c, "N != N", 0) + _eval(c, "N != n", 0) + _eval(c, "N != 'n'", 0) + + # --- equality for M --- + _eval(c, "M = M", 2) + _eval(c, "M = m", 2) + _eval(c, "M = 'm'", 2) + _eval(c, "M != M", 0) + _eval(c, "M != m", 0) + _eval(c, "M != 'm'", 0) + + # --- equality for Y --- + _eval(c, "Y = Y", 2) + _eval(c, "Y = y", 2) + _eval(c, "Y = 'y'", 2) + _eval(c, "Y != Y", 0) + _eval(c, "Y != y", 0) + _eval(c, "Y != 'y'", 0) + + # --- cross inequalities --- + _eval(c, "N != M", 2) + _eval(c, "N != Y", 2) + _eval(c, "M != Y", 2) + + # --- string / int / hex equality --- + _eval(c, "Y_STRING = y", 2) + _eval(c, "Y_STRING = 'y'", 2) + _eval(c, 'FOO_BAR_STRING = "foo bar"', 2) + _eval(c, 'FOO_BAR_STRING != "foo bar baz"', 2) + _eval(c, "INT_37 = 37", 2) + _eval(c, "INT_37 = '37'", 2) + _eval(c, "HEX_0X37 = 0x37", 2) + _eval(c, "HEX_0X37 = '0x37'", 2) + + # After 31847b67 (kconfig: allow use of relations other than (in)equality) + _eval(c, "HEX_0X37 = '0x037'", 2) + _eval(c, "HEX_0X37 = '0x0037'", 2) + + # --- constant symbol comparisons --- + _eval(c, '"foo" != "bar"', 2) + _eval(c, '"foo" = "bar"', 0) + _eval(c, '"foo" = "foo"', 2) + + # --- undefined symbols (get their name as their value) --- + c.warn = False + _eval(c, "'not_defined' = not_defined", 2) + _eval(c, "not_defined_2 = not_defined_2", 2) + _eval(c, "not_defined_1 != not_defined_2", 2) + + # --- less than / greater than: basic --- + _eval(c, "INT_37 < 38", 2) + _eval(c, "38 < INT_37", 0) + _eval(c, "INT_37 < '38'", 2) + _eval(c, "'38' < INT_37", 0) + _eval(c, "INT_37 < 138", 2) + _eval(c, "138 < INT_37", 0) + _eval(c, "INT_37 < '138'", 2) + _eval(c, "'138' < INT_37", 0) + _eval(c, "INT_37 < -138", 0) + _eval(c, "-138 < INT_37", 2) + _eval(c, "INT_37 < '-138'", 0) + _eval(c, "'-138' < INT_37", 2) + _eval(c, "INT_37 < 37", 0) + _eval(c, "37 < INT_37", 0) + _eval(c, "INT_37 < 36", 0) + _eval(c, "36 < INT_37", 2) + + # --- different formats in comparison --- + _eval(c, "INT_37 < 0x26", 2) # 0x26 == 38 + _eval(c, "INT_37 < 0x25", 0) # 0x25 == 37 + _eval(c, "INT_37 < 0x24", 0) # 0x24 == 36 + _eval(c, "HEX_0X37 < 56", 2) # 56 == 0x38 + _eval(c, "HEX_0X37 < 55", 0) # 55 == 0x37 + _eval(c, "HEX_0X37 < 54", 0) # 54 == 0x36 + + # --- other int comparisons --- + _eval(c, "INT_37 <= 38", 2) + _eval(c, "INT_37 <= 37", 2) + _eval(c, "INT_37 <= 36", 0) + _eval(c, "INT_37 > 38", 0) + _eval(c, "INT_37 > 37", 0) + _eval(c, "INT_37 > 36", 2) + _eval(c, "INT_37 >= 38", 0) + _eval(c, "INT_37 >= 37", 2) + _eval(c, "INT_37 >= 36", 2) + + # --- other hex comparisons --- + _eval(c, "HEX_0X37 <= 0x38", 2) + _eval(c, "HEX_0X37 <= 0x37", 2) + _eval(c, "HEX_0X37 <= 0x36", 0) + _eval(c, "HEX_0X37 > 0x38", 0) + _eval(c, "HEX_0X37 > 0x37", 0) + _eval(c, "HEX_0X37 > 0x36", 2) + _eval(c, "HEX_0X37 >= 0x38", 0) + _eval(c, "HEX_0X37 >= 0x37", 2) + _eval(c, "HEX_0X37 >= 0x36", 2) + + # --- hex without 0x prefix --- + _eval(c, "HEX_37 < 0x38", 2) + _eval(c, "HEX_37 < 0x37", 0) + _eval(c, "HEX_37 < 0x36", 0) + + # --- symbol-to-symbol comparisons --- + _eval(c, "INT_37 < HEX_0X37", 2) + _eval(c, "INT_37 > HEX_0X37", 0) + _eval(c, "HEX_0X37 < INT_37 ", 0) + _eval(c, "HEX_0X37 > INT_37 ", 2) + _eval(c, "INT_37 < INT_37 ", 0) + _eval(c, "INT_37 <= INT_37 ", 2) + _eval(c, "INT_37 > INT_37 ", 0) + _eval(c, "INT_37 >= INT_37 ", 2) + + # --- tristate value comparisons --- + _eval(c, "n < n", 0) + _eval(c, "n < m", 2) + _eval(c, "n < y", 2) + _eval(c, "n < N", 0) + _eval(c, "n < M", 2) + _eval(c, "n < Y", 2) + _eval(c, "0 > n", 0) + _eval(c, "1 > n", 2) + _eval(c, "2 > n", 2) + _eval(c, "m < n", 0) + _eval(c, "m < m", 0) + _eval(c, "m < y", 2) + + # --- strings compare lexicographically --- + _eval(c, "'aa' < 'ab'", 2) + _eval(c, "'aa' > 'ab'", 0) + _eval(c, "'ab' < 'aa'", 0) + _eval(c, "'ab' > 'aa'", 2) + + # --- non-number operand falls back to lexicographic --- + _eval(c, "INT_37 < '37a' ", 2) + _eval(c, "'37a' > INT_37", 2) + _eval(c, "INT_37 <= '37a' ", 2) + _eval(c, "'37a' >= INT_37", 2) + _eval(c, "INT_37 >= '37a' ", 0) + _eval(c, "INT_37 > '37a' ", 0) + _eval(c, "'37a' < INT_37", 0) + _eval(c, "'37a' <= INT_37", 0) + + +# --------------------------------------------------------------------------- +# Bad expression evaluation +# --------------------------------------------------------------------------- + +_BAD_EXPRS = [ + "", + "&", + "|", + "!", + "(", + ")", + "=", + "(X", + "X)", + "X X", + "!X X", + "X !X", + "(X) X", + "X &&", + "&& X", + "X && && X", + "X && !&&", + "X ||", + "|| X", +] + + +def test_eval_bad(): + c = Kconfig("tests/Keval", warn=False) + c.modules.set_value(2) + + for expr in _BAD_EXPRS: + with pytest.raises(KconfigError): + c.eval_string(expr) + + +# --------------------------------------------------------------------------- +# split_expr() +# --------------------------------------------------------------------------- + + +def test_split_expr_or(): + c = Kconfig("tests/empty") + c.warn = False + + _verify_split( + c, + [ + ("A", ("A",)), + ("!A", ("!A",)), + ("A = B", ("A = B",)), + ("A && B", ("A && B",)), + ("A || B", ("A", "B")), + ("(A || B) || C", ("A", "B", "C")), + ("A || (B || C)", ("A", "B", "C")), + ("A || !(B || C)", ("A", "!(B || C)")), + ("A || (B && (C || D))", ("A", "B && (C || D)")), + ("(A && (B || C)) || D", ("A && (B || C)", "D")), + ], + OR, + ) + + +def test_split_expr_and(): + c = Kconfig("tests/empty") + c.warn = False + + _verify_split( + c, + [ + ("A", ("A",)), + ("!A", ("!A",)), + ("A = B", ("A = B",)), + ("A || B", ("A || B",)), + ("A && B", ("A", "B")), + ("(A && B) && C", ("A", "B", "C")), + ("A && (B && C)", ("A", "B", "C")), + ("A && !(B && C)", ("A", "!(B && C)")), + ("A && (B || (C && D))", ("A", "B || (C && D)")), + ("(A || (B && C)) && D", ("A || (B && C)", "D")), + ], + AND, + ) + + +# --------------------------------------------------------------------------- +# expr_items() +# --------------------------------------------------------------------------- + + +def test_expr_items(): + c = Kconfig("tests/Kexpr_items") + + items = expr_items(c.syms["TEST"].defaults[0][0]) + assert tuple(sorted(item.name for item in items)) == ( + "A", + "B", + "C", + "D", + "E", + "F", + "G", + "H", + ) + + items = expr_items(c.syms["TEST_CHOICE"].nodes[0].prompt[1]) + assert tuple(sorted(item.name for item in items)) == ("A", "CHOICE") + + +# --------------------------------------------------------------------------- +# expr_value() on parsed expressions +# --------------------------------------------------------------------------- + + +def test_expr_value(): + c = Kconfig("tests/Keval", warn=False) + c.modules.set_value(2) + + # Direct symbol tristate values + assert expr_value(c.syms["N"]) == 0 + assert expr_value(c.syms["M"]) == 1 + assert expr_value(c.syms["Y"]) == 2 + + # AND/OR/NOT on parsed expression trees + and_expr = _parse_expr(c, "Y && M") + assert expr_value(and_expr) == 1 # min(2, 1) + + or_expr = _parse_expr(c, "N || M") + assert expr_value(or_expr) == 1 # max(0, 1) + + not_expr = _parse_expr(c, "!M") + assert expr_value(not_expr) == 1 # 2 - 1 + + not_n = _parse_expr(c, "!N") + assert expr_value(not_n) == 2 # 2 - 0 + + not_y = _parse_expr(c, "!Y") + assert expr_value(not_y) == 0 # 2 - 2 + + # Nested: (Y && M) || N -> max(min(2,1), 0) = 1 + nested = _parse_expr(c, "(Y && M) || N") + assert expr_value(nested) == 1 + + # Relation operators on int/hex symbols + eq_expr = _parse_expr(c, "INT_37 = 37") + assert expr_value(eq_expr) == 2 + + neq_expr = _parse_expr(c, "INT_37 != 37") + assert expr_value(neq_expr) == 0 + + lt_expr = _parse_expr(c, "INT_37 < 38") + assert expr_value(lt_expr) == 2 + + gt_expr = _parse_expr(c, "INT_37 > 38") + assert expr_value(gt_expr) == 0 + + le_expr = _parse_expr(c, "INT_37 <= 37") + assert expr_value(le_expr) == 2 + + ge_expr = _parse_expr(c, "INT_37 >= 38") + assert expr_value(ge_expr) == 0 + + # Comparison against constant (quoted) symbol + str_eq = _parse_expr(c, 'FOO_BAR_STRING = "foo bar"') + assert expr_value(str_eq) == 2 + + str_neq = _parse_expr(c, 'FOO_BAR_STRING = "wrong"') + assert expr_value(str_neq) == 0 diff --git a/tests/test_lexer.py b/tests/test_lexer.py new file mode 100644 index 0000000..694f453 --- /dev/null +++ b/tests/test_lexer.py @@ -0,0 +1,129 @@ +# Copyright (c) 2011-2019 Ulf Magnusson +# SPDX-License-Identifier: ISC +# +# Tests for string literal lexing, escape/unescape, and _ordered_unique. +# Lexer tests: escape/unescape, _ordered_unique. + +import pytest + +from kconfiglib import Kconfig, KconfigError, escape, unescape, _ordered_unique + +# --------------------------------------------------------------------------- +# Helpers +# --------------------------------------------------------------------------- + + +def _verify_string_lex(c, s, expected): + """Tokenize a string literal and check the resulting constant symbol name.""" + res = c._tokenize("if " + s)[1].name + assert ( + res == expected + ), f"expected <{s[1:-1]}> to produce the constant symbol <{expected}>, got <{res}>" + + +def _verify_string_bad(c, s): + """Assert that evaluating a malformed string literal raises KconfigError.""" + with pytest.raises(KconfigError): + c.eval_string(s) + + +def _verify_escape_unescape(s, sesc): + """Assert that escape(s) == sesc and unescape(sesc) == s.""" + assert escape(s) == sesc, f"escape({s!r}) == {escape(s)!r}, expected {sesc!r}" + assert ( + unescape(sesc) == s + ), f"unescape({sesc!r}) == {unescape(sesc)!r}, expected {s!r}" + + +# --------------------------------------------------------------------------- +# Tests +# --------------------------------------------------------------------------- + + +def test_string_literal_lexing(): + """Verify that string literals are lexed into the expected constant + symbols.""" + c = Kconfig("tests/empty") + lex = _verify_string_lex + + # Empty strings + lex(c, r""" "" """, "") + lex(c, r""" '' """, "") + + # Simple content + lex(c, r""" "a" """, "a") + lex(c, r""" 'a' """, "a") + lex(c, r""" "ab" """, "ab") + lex(c, r""" 'ab' """, "ab") + lex(c, r""" "abc" """, "abc") + lex(c, r""" 'abc' """, "abc") + + # Opposite quote inside + lex(c, r""" "'" """, "'") + lex(c, r""" '"' """, '"') + + # Escaped own quote + lex(c, r""" "\"" """, '"') + lex(c, r""" '\'' """, "'") + + # Double escaped own quote + lex(c, r""" "\"\"" """, '""') + lex(c, r""" '\'\'' """, "''") + + # Escaped opposite quote (treated as literal) + lex(c, r""" "\'" """, "'") + lex(c, r""" '\"' """, '"') + + # Escaped backslash + lex(c, r""" "\\" """, "\\") + lex(c, r""" '\\' """, "\\") + + # Mixed escapes + lex(c, r""" "\a\\'\b\c\"'d" """, "a\\'bc\"'d") + lex(c, r""" '\a\\"\b\c\'"d' """, 'a\\"bc\'"d') + + +def test_string_bad_lexing(): + """Verify that malformed string literals raise KconfigError.""" + c = Kconfig("tests/empty") + + for s in [ + r""" " """, + r""" ' """, + r""" "' """, + r""" '" """, + r""" "\" """, + r""" '\' """, + r""" "foo """, + r""" 'foo """, + ]: + _verify_string_bad(c, s) + + +def test_escape_unescape(): + """Verify that escape() and unescape() are inverses and handle edge + cases correctly.""" + _verify_escape_unescape(r"", r"") + _verify_escape_unescape(r"foo", r"foo") + _verify_escape_unescape(r'"', r"\"") + _verify_escape_unescape(r'""', r"\"\"") + _verify_escape_unescape("\\", r"\\") + _verify_escape_unescape(r"\\", r"\\\\") + _verify_escape_unescape(r"\"", r"\\\"") + _verify_escape_unescape(r'"ab\cd"ef"', r"\"ab\\cd\"ef\"") + + # Backslashes before any character should be unescaped, not just " and \ + assert unescape(r"\afoo\b\c\\d\\\e\\\\f") == r"afoobc\d\e\\f" + + +def test_ordered_unique(): + """Verify _ordered_unique() preserves first-occurrence order and removes + duplicates.""" + assert _ordered_unique([]) == [] + assert _ordered_unique([1]) == [1] + assert _ordered_unique([1, 2]) == [1, 2] + assert _ordered_unique([1, 1]) == [1] + assert _ordered_unique([1, 1, 2]) == [1, 2] + assert _ordered_unique([1, 2, 1]) == [1, 2] + assert _ordered_unique([1, 2, 2]) == [1, 2] + assert _ordered_unique([1, 2, 3, 2, 1, 2, 3, 4, 3, 2, 1, 0]) == [1, 2, 3, 4, 0] diff --git a/tests/test_preprocess.py b/tests/test_preprocess.py new file mode 100644 index 0000000..5e5b0db --- /dev/null +++ b/tests/test_preprocess.py @@ -0,0 +1,333 @@ +# Copyright (c) 2011-2019 Ulf Magnusson +# SPDX-License-Identifier: ISC +# +# Preprocessor tests: variable expansion, user-defined functions, Kbuild +# toolchain test functions, and KCONFIG_WARN_UNDEF. + +import pytest + +from kconfiglib import Kconfig, KconfigError +from conftest import verify_value, verify_str + + +def _verify_variable(c, name, unexp_value, exp_value, recursive, *args): + """Check a preprocessor variable's unexpanded value, expanded value, + expanded_value_w_args(), and is_recursive flag.""" + var = c.variables[name] + + assert var.value == unexp_value, f"{name} unexpanded value" + + if not args: + assert var.expanded_value == exp_value, f"{name} expanded_value" + + assert ( + var.expanded_value_w_args(*args) == exp_value + ), f"{name} expanded_value_w_args" + + assert var.is_recursive == recursive, f"{name} is_recursive flag" + + +# --------------------------------------------------------------------------- +# Shared fixture: load Kpreprocess with the required environment variables +# --------------------------------------------------------------------------- + + +@pytest.fixture +def preprocess_kconfig(monkeypatch): + """Load Kpreprocess with the expected environment variables set.""" + monkeypatch.setenv("ENV_1", "env_1") + monkeypatch.setenv("ENV_2", "env_2") + monkeypatch.setenv("ENV_3", "env_3") + monkeypatch.setenv("ENV_4", "env_4") + monkeypatch.setenv("ENV_5", "n") + monkeypatch.setenv("ENV_6", "tests/empty") + monkeypatch.setenv("ENV_7", "env_7") + return Kconfig("tests/Kpreprocess", warn_to_stderr=False) + + +# =========================================================================== +# Preprocessor variable expansion +# =========================================================================== + + +def test_preprocessor_variables(preprocess_kconfig): + c = preprocess_kconfig + + _verify_variable(c, "simple-recursive", "foo", "foo", True) + _verify_variable(c, "simple-immediate", "bar", "bar", False) + _verify_variable(c, "simple-recursive-2", "baz", "baz", True) + + _verify_variable(c, "whitespaced", "foo", "foo", True) + + _verify_variable(c, "preserve-recursive", "foo bar", "foo bar", True) + _verify_variable(c, "preserve-immediate", "foo bar", "foo bar", False) + + _verify_variable( + c, + "recursive", + "$(foo) $(bar) $($(b-char)a$(z-char)) $(indir)", + "abc def ghi jkl mno", + True, + ) + + _verify_variable(c, "immediate", "foofoo", "foofoo", False) + + _verify_variable( + c, + "messy-fn-res", + "$($(fn-indir)-unused-arg, a b (,) , c d )", + 'surround-rev-quote " c d " " a b (,) " surround-rev-quote ', + True, + ) + + _verify_variable( + c, + "special-chars-fn-res", + "$(fn,$(comma)$(dollar)$(left-paren)foo$(right-paren))", + '",$(foo)"', + True, + ) + + _verify_variable(c, "quote", '"$(1)" "$(2)"', '"" ""', True) + _verify_variable(c, "quote", '"$(1)" "$(2)"', '"one" ""', True, "one") + _verify_variable(c, "quote", '"$(1)" "$(2)"', '"one" "two"', True, "one", "two") + _verify_variable( + c, "quote", '"$(1)" "$(2)"', '"one" "two"', True, "one", "two", "three" + ) + + +# =========================================================================== +# Preprocessor symbol __str__() output +# =========================================================================== + + +def test_preprocessor_symbols(preprocess_kconfig): + c = preprocess_kconfig + + verify_str( + c.syms["PRINT_ME"], + r""" +config PRINT_ME + string "env_1" if (FOO && BAR) || !BAZ || !QAZ + default "\"foo\"" if "foo \"bar\" baz" = "" +""", + ) + + verify_str( + c.syms["PRINT_ME_TOO"], + r""" +config PRINT_ME_TOO + bool "foo" + default FOOBARBAZQAZ if QAZ && QAZFOO && xxx +""", + ) + + +# =========================================================================== +# Preprocessor variable __repr__() +# =========================================================================== + + +def test_preprocessor_variable_repr(preprocess_kconfig): + c = preprocess_kconfig + + assert ( + repr(c.variables["simple-immediate"]) + == "" + ) + + assert ( + repr(c.variables["messy-fn-res"]) + == "" + ) + + +# =========================================================================== +# Recursive expansion detection +# =========================================================================== + + +def test_preprocessor_recursive(preprocess_kconfig): + c = preprocess_kconfig + + with pytest.raises(KconfigError): + c.variables["rec-1"].expanded_value + + # Indirectly verifies that it's not recursive + _verify_variable(c, "safe-fn-rec-res", "$(safe-fn-rec,safe-fn-rec-2)", "foo", True) + + with pytest.raises(KconfigError): + c.variables["unsafe-fn-rec"].expanded_value + + +# =========================================================================== +# Miscellaneous preprocessor variables (shell, parens, location, warnings, +# errors, env_vars) +# =========================================================================== + + +def test_preprocessor_misc(preprocess_kconfig): + c = preprocess_kconfig + + _verify_variable(c, "foo-bar-baz", "$(rhs)", "value", True) + + _verify_variable(c, "space-var-res", "$(foo bar)", "value", True) + + _verify_variable( + c, + "shell-res", + "$(shell,false && echo foo bar || echo baz qaz)", + "baz qaz", + True, + ) + + _verify_variable(c, "shell-stderr-res", "", "", False) + + _verify_variable( + c, + "parens-res", + "pre-$(shell,echo '(a,$(b-char),(c,d),e)')-post", + "pre-(a,b,(c,d),e)-post", + True, + ) + + _verify_variable( + c, + "location-res", + "tests/Kpreprocess:129", + "tests/Kpreprocess:129", + False, + ) + + _verify_variable(c, "warning-res", "", "", False) + _verify_variable(c, "error-n-res", "", "", False) + + with pytest.raises(KconfigError): + c.variables["error-y-res"].expanded_value + + # Check Kconfig.env_vars + assert c.env_vars == {"ENV_1", "ENV_2", "ENV_3", "ENV_4", "ENV_5", "ENV_6"} + + # Check that the expected warnings were generated + assert c.warnings == [ + "tests/Kpreprocess:122: warning: 'echo message on stderr >&2' wrote to stderr: message on stderr", + "tests/Kpreprocess:134: warning: a warning", + ] + + +# =========================================================================== +# User-defined preprocessor functions +# =========================================================================== + + +def test_user_defined_functions(monkeypatch): + # Make tests/kconfigfunctions.py importable + monkeypatch.syspath_prepend("tests") + c = Kconfig("tests/Kuserfunctions") + + _verify_variable(c, "add-zero", "$(add)", "0", True) + _verify_variable(c, "add-one", "$(add,1)", "1", True) + _verify_variable(c, "add-three", "$(add,1,-1,2,1)", "3", True) + + _verify_variable(c, "one-one", "$(one,foo bar)", "onefoo barfoo bar", True) + + _verify_variable(c, "one-or-more-one", "$(one-or-more,foo)", "foo + ", True) + _verify_variable( + c, "one-or-more-three", "$(one-or-more,foo,bar,baz)", "foo + bar,baz", True + ) + + _verify_variable( + c, + "location-1", + "tests/Kuserfunctions:13", + "tests/Kuserfunctions:13", + False, + ) + _verify_variable( + c, + "location-2", + "tests/Kuserfunctions:14", + "tests/Kuserfunctions:14", + False, + ) + + with pytest.raises(KconfigError): + c.variables["one-zero"].expanded_value + + with pytest.raises(KconfigError): + c.variables["one-two"].expanded_value + + with pytest.raises(KconfigError): + c.variables["one-or-more-zero"].expanded_value + + +# =========================================================================== +# Kbuild toolchain test functions +# =========================================================================== + + +def test_kbuild_functions(): + c = Kconfig("tests/Kbuild_functions") + + verify_value(c, "TEST_SUCCESS", "y") + verify_value(c, "TEST_FAILURE", "y") + verify_value(c, "TEST_IF_SUCCESS", "y") + + verify_value(c, "CC_HAS_WALL", "y") + verify_value(c, "CC_HAS_WERROR", "y") + + verify_value(c, "TEST_INVALID_OPTION", "n") + verify_value(c, "TEST_FAILURE_TRUE", "n") + + verify_value(c, "AS_HAS_NOP", "y") + + verify_value(c, "TEST_NESTED_SUCCESS_SHELL", "y") + + +# =========================================================================== +# KCONFIG_WARN_UNDEF +# =========================================================================== + + +def test_kconfig_warn_undef(monkeypatch): + monkeypatch.setenv("KCONFIG_WARN_UNDEF", "y") + c = Kconfig("tests/Kundef", warn_to_stderr=False) + + assert "\n".join(c.warnings) == """ +warning: the int symbol INT (defined at tests/Kundef:8) has a non-int range [UNDEF_2 (undefined), 8 (undefined)] +warning: undefined symbol UNDEF_1: + +- Referenced at tests/Kundef:4: + +config BOOL +\tbool "foo" if DEF || !UNDEF_1 +\tdefault UNDEF_2 + +- Referenced at tests/Kundef:19: + +menu "menu" +\tdepends on UNDEF_1 +\tvisible if UNDEF_3 +warning: undefined symbol UNDEF_2: + +- Referenced at tests/Kundef:4: + +config BOOL +\tbool "foo" if DEF || !UNDEF_1 +\tdefault UNDEF_2 + +- Referenced at tests/Kundef:8: + +config INT +\tint +\trange UNDEF_2 8 +\trange 5 15 +\tdefault 10 +warning: undefined symbol UNDEF_3: + +- Referenced at tests/Kundef:19: + +menu "menu" +\tdepends on UNDEF_1 +\tvisible if UNDEF_3 +"""[1:-1] diff --git a/tests/test_properties.py b/tests/test_properties.py new file mode 100644 index 0000000..2a5f5c2 --- /dev/null +++ b/tests/test_properties.py @@ -0,0 +1,391 @@ +# Copyright (c) 2011-2019 Ulf Magnusson +# SPDX-License-Identifier: ISC +# +# Property tests: help strings, +# locations, origins, source/rsource/gsource/grsource, symlinks, +# node_iter(), include_path, and item lists. + +import os + +import pytest + +from kconfiglib import Kconfig, KconfigError, MenuNode, Symbol + +# -- helpers ----------------------------------------------------------------- + + +def _verify_help(node, s): + """Assert that a node's help text matches *s* with leading/trailing + newlines stripped (the ``s[1:-1]`` idiom from the original suite).""" + assert node.help == s[1:-1] + + +def _verify_locations(nodes, *expected_locs): + """Assert that *nodes* map to exactly *expected_locs* file:line strings.""" + actual = [f"{n.filename}:{n.linenr}" for n in nodes] + assert actual == list(expected_locs), "node locations" + + +def _verify_node_path(node, *expected): + """Assert that *node.include_path* matches *expected*.""" + assert node.include_path == expected, "node include_path" + + +def _verify_sym_path(c, sym_name, node_i, *expected): + """Assert include_path for the *node_i*-th node of symbol *sym_name*.""" + _verify_node_path(c.syms[sym_name].nodes[node_i], *expected) + + +def _verify_prompts(items, *expected_prompts): + """Assert that *items* carry exactly *expected_prompts*.""" + actual = [] + for item in items: + node = item if isinstance(item, MenuNode) else item.nodes[0] + actual.append(node.prompt[0]) + assert actual == list(expected_prompts), "item prompts" + + +# -- tricky help strings ----------------------------------------------------- + + +def test_help_strings(): + c = Kconfig("tests/Khelp") + + _verify_help( + c.syms["TWO_HELP_STRINGS"].nodes[0], + """ +first help string +""", + ) + + _verify_help( + c.syms["TWO_HELP_STRINGS"].nodes[1], + """ +second help string +""", + ) + + _verify_help( + c.syms["NO_BLANK_AFTER_HELP"].nodes[0], + """ +help for +NO_BLANK_AFTER_HELP +""", + ) + + _verify_help( + c.named_choices["CHOICE_HELP"].nodes[0], + """ +help for +CHOICE_HELP +""", + ) + + _verify_help( + c.syms["HELP_TERMINATED_BY_COMMENT"].nodes[0], + """ +a +b +c +""", + ) + + _verify_help( + c.syms["TRICKY_HELP"].nodes[0], + """ +a + b + c + + d + e + f + + +g + h + i +""", + ) + + +# -- locations, origins, source/rsource/gsource/grsource -------------------- + + +def test_locations_and_origins(monkeypatch): + # Expanded in the 'source' statement in Klocation + monkeypatch.setenv("TESTS_DIR_FROM_ENV", "tests") + monkeypatch.setenv("SUB_DIR_FROM_ENV", "sub") + + monkeypatch.setenv("_SOURCED", "_sourced") + monkeypatch.setenv("_RSOURCED", "_rsourced") + monkeypatch.setenv("_GSOURCED", "_gsourced") + monkeypatch.setenv("_GRSOURCED", "_grsourced") + + # Test twice, with $srctree as a relative and an absolute path, + # respectively + for srctree in ".", os.path.abspath("."): + monkeypatch.setenv("srctree", srctree) + + # Has symbol with empty help text, so disable warnings + c = Kconfig("tests/Klocation", warn=False) + + _verify_locations(c.syms["UNDEFINED"].nodes) + assert c.syms["UNDEFINED"].name_and_loc == "UNDEFINED (undefined)" + + _verify_locations(c.syms["ONE_DEF"].nodes, "tests/Klocation:4") + assert ( + c.syms["ONE_DEF"].name_and_loc == "ONE_DEF (defined at tests/Klocation:4)" + ) + + _verify_locations( + c.syms["TWO_DEF"].nodes, "tests/Klocation:7", "tests/Klocation:10" + ) + assert ( + c.syms["TWO_DEF"].name_and_loc + == "TWO_DEF (defined at tests/Klocation:7, tests/Klocation:10)" + ) + + _verify_locations( + c.syms["MANY_DEF"].nodes, + "tests/Klocation:13", + "tests/Klocation:43", + "tests/Klocation:45", + "tests/Klocation_sourced:3", + "tests/sub/Klocation_rsourced:2", + "tests/sub/Klocation_gsourced1:1", + "tests/sub/Klocation_gsourced2:1", + "tests/sub/Klocation_gsourced1:1", + "tests/sub/Klocation_gsourced2:1", + "tests/sub/Klocation_grsourced1:1", + "tests/sub/Klocation_grsourced2:1", + "tests/sub/Klocation_grsourced1:1", + "tests/sub/Klocation_grsourced2:1", + "tests/Klocation:78", + ) + + _verify_locations( + c.named_choices["CHOICE_ONE_DEF"].nodes, "tests/Klocation_sourced:5" + ) + assert ( + c.named_choices["CHOICE_ONE_DEF"].name_and_loc + == " (defined at tests/Klocation_sourced:5)" + ) + + _verify_locations( + c.named_choices["CHOICE_TWO_DEF"].nodes, + "tests/Klocation_sourced:9", + "tests/Klocation_sourced:13", + ) + assert ( + c.named_choices["CHOICE_TWO_DEF"].name_and_loc + == " (defined at tests/Klocation_sourced:9, tests/Klocation_sourced:13)" + ) + + _verify_locations( + [c.syms["MENU_HOOK"].nodes[0].next], "tests/Klocation_sourced:20" + ) + _verify_locations( + [c.syms["COMMENT_HOOK"].nodes[0].next], "tests/Klocation_sourced:26" + ) + + # Test Kconfig.kconfig_filenames + + assert c.kconfig_filenames == [ + "tests/Klocation", + "tests/Klocation_sourced", + "tests/sub/Klocation_rsourced", + "tests/sub/Klocation_gsourced1", + "tests/sub/Klocation_gsourced2", + "tests/sub/Klocation_gsourced1", + "tests/sub/Klocation_gsourced2", + "tests/sub/Klocation_grsourced1", + "tests/sub/Klocation_grsourced2", + "tests/sub/Klocation_grsourced1", + "tests/sub/Klocation_grsourced2", + ] + + # Test recursive 'source' detection + + with pytest.raises(KconfigError, match="recursive 'source'"): + Kconfig("tests/Krecursive1") + + # Verify that source and rsource throw exceptions for missing files + + with pytest.raises(KconfigError, match="not found"): + Kconfig("tests/Kmissingsource") + + with pytest.raises(KconfigError, match="not found"): + Kconfig("tests/Kmissingrsource") + + # Tests origins + + c = Kconfig("tests/Korigins", warn=False) + c.syms["MAIN_FLAG_SELECT"].set_value(2, "here") + + expected = [ + ("MAIN_FLAG", ("select", ["MAIN_FLAG_SELECT"])), + ( + "MAIN_FLAG_DEPENDENCY", + ("default", (os.path.abspath("tests/Korigins"), 6)), + ), + ("MAIN_FLAG_SELECT", ("assign", "here")), + ("SECOND_CHOICE", ("default", None)), + ("UNSET_FLAG", ("unset", None)), + ] + + for node in c.node_iter(True): + if not isinstance(node.item, Symbol): + continue + + if node.item.origin is None: + continue + + exp_name, exp_origin = expected.pop(0) + assert node.item.name == exp_name + assert node.item.origin == exp_origin + + assert len(expected) == 0, "origin test mismatch" + + +# -- symlink + rsource ------------------------------------------------------- + + +def test_symlink_rsource(monkeypatch): + # Test a tricky case involving symlinks. $srctree is tests/symlink, which + # points to tests/sub/sub, meaning tests/symlink/.. != tests/. Previously, + # using 'rsource' from a file sourced with an absolute path triggered an + # unsafe relpath() with tests/symlink/.. in it, crashing. + + monkeypatch.setenv("srctree", "tests/symlink") + monkeypatch.setenv( + "KCONFIG_SYMLINK_2", + os.path.abspath("tests/sub/Kconfig_symlink_2"), + ) + assert os.path.isabs( + Kconfig("Kconfig_symlink_1").syms["FOUNDME"].nodes[0].filename + ), "Symlink + rsource issues" + + +# -- Kconfig.node_iter() ----------------------------------------------------- + + +def test_node_iter(monkeypatch): + # Reuse tests/Klocation. The node_iter(unique_syms=True) case already gets + # plenty of testing from write_config() as well. + + monkeypatch.setenv("TESTS_DIR_FROM_ENV", "tests") + monkeypatch.setenv("SUB_DIR_FROM_ENV", "sub") + monkeypatch.setenv("_SOURCED", "_sourced") + monkeypatch.setenv("_RSOURCED", "_rsourced") + monkeypatch.setenv("_GSOURCED", "_gsourced") + monkeypatch.setenv("_GRSOURCED", "_grsourced") + + c = Kconfig("tests/Klocation", warn=False) + + assert [ + node.item.name for node in c.node_iter() if isinstance(node.item, Symbol) + ] == [ + "ONE_DEF", + "TWO_DEF", + "TWO_DEF", + "MANY_DEF", + "HELP_1", + "HELP_2", + "HELP_3", + "MANY_DEF", + "MANY_DEF", + "MANY_DEF", + "MENU_HOOK", + "COMMENT_HOOK", + ] + 10 * [ + "MANY_DEF" + ] + + assert [ + node.item.name for node in c.node_iter(True) if isinstance(node.item, Symbol) + ] == [ + "ONE_DEF", + "TWO_DEF", + "MANY_DEF", + "HELP_1", + "HELP_2", + "HELP_3", + "MENU_HOOK", + "COMMENT_HOOK", + ] + + assert [ + node.prompt[0] for node in c.node_iter() if not isinstance(node.item, Symbol) + ] == [ + "one-def choice", + "two-def choice 1", + "two-def choice 2", + "menu", + "comment", + ] + + assert [ + node.prompt[0] + for node in c.node_iter(True) + if not isinstance(node.item, Symbol) + ] == [ + "one-def choice", + "two-def choice 1", + "two-def choice 2", + "menu", + "comment", + ] + + +# -- MenuNode.include_path -------------------------------------------------- + + +def test_include_path(monkeypatch): + monkeypatch.setenv("srctree", "tests") + + c = Kconfig("Kinclude_path") + + _verify_sym_path(c, "TOP", 0) + _verify_sym_path(c, "TOP", 1) + _verify_sym_path(c, "TOP", 2) + + _verify_sym_path(c, "ONE_DOWN", 0, ("Kinclude_path", 4)) + _verify_sym_path(c, "ONE_DOWN", 1, ("Kinclude_path", 4)) + _verify_sym_path(c, "ONE_DOWN", 2, ("Kinclude_path", 4)) + _verify_sym_path(c, "ONE_DOWN", 3, ("Kinclude_path", 9)) + _verify_sym_path(c, "ONE_DOWN", 4, ("Kinclude_path", 9)) + _verify_sym_path(c, "ONE_DOWN", 5, ("Kinclude_path", 9)) + + _verify_sym_path( + c, "TWO_DOWN", 0, ("Kinclude_path", 4), ("Kinclude_path_sourced_1", 4) + ) + _verify_sym_path( + c, "TWO_DOWN", 1, ("Kinclude_path", 4), ("Kinclude_path_sourced_1", 9) + ) + _verify_sym_path( + c, "TWO_DOWN", 2, ("Kinclude_path", 9), ("Kinclude_path_sourced_1", 4) + ) + _verify_sym_path( + c, "TWO_DOWN", 3, ("Kinclude_path", 9), ("Kinclude_path_sourced_1", 9) + ) + + _verify_node_path(c.top_node) + _verify_node_path(c.menus[0], ("Kinclude_path", 4), ("Kinclude_path_sourced_1", 4)) + _verify_node_path( + c.comments[0], ("Kinclude_path", 4), ("Kinclude_path_sourced_1", 4) + ) + _verify_node_path( + c.choices[0].nodes[0], ("Kinclude_path", 4), ("Kinclude_path_sourced_1", 4) + ) + + +# -- Kconfig.choices/menus/comments ----------------------------------------- + + +def test_item_lists(): + c = Kconfig("tests/Kitemlists") + + _verify_prompts(c.choices, "choice 1", "choice 2", "choice 3", "choice 2") + _verify_prompts(c.menus, "menu 1", "menu 2", "menu 3", "menu 4", "menu 5") + _verify_prompts(c.comments, "comment 1", "comment 2", "comment 3") diff --git a/tests/test_repr.py b/tests/test_repr.py new file mode 100644 index 0000000..c07ca6c --- /dev/null +++ b/tests/test_repr.py @@ -0,0 +1,724 @@ +# Copyright (c) 2011-2019 Ulf Magnusson +# SPDX-License-Identifier: ISC +# +# Tests for Symbol/Choice/MenuNode __str__(), custom_str(), orig_*, +# and __repr__(). + +import pytest + +from kconfiglib import Kconfig, expr_str +from conftest import verify_str + +BRACKET_FMT = lambda sc: f"[{sc.name}]" + + +def verify_custom_str(item, expected): + """Verify item.custom_str() with bracket formatter matches expected.""" + assert item.custom_str(BRACKET_FMT) == expected[1:-1] + + +def verify_deps(elms, dep_index, expected): + """Join the expr_str of element[dep_index] across all elements.""" + assert " ".join(expr_str(elm[dep_index]) for elm in elms) == expected + + +@pytest.fixture +def kstr_config(): + """Load tests/Kstr with modules=m.""" + c = Kconfig("tests/Kstr", warn=False) + c.modules.set_value(2) + return c + + +@pytest.fixture +def krepr_config(): + """Load tests/Krepr.""" + return Kconfig("tests/Krepr", warn=False) + + +# -- Symbol.__str__() / custom_str() ---------------------------------------- + + +class TestSymbolStr: + @pytest.fixture(autouse=True) + def _setup(self, kstr_config): + self.c = kstr_config + + def test_undefined(self): + assert str(self.c.syms["UNDEFINED"]) == "" + + def test_basic_no_prompt(self): + # Blank help lines contain tab + two spaces; build explicitly to + # prevent editors from stripping trailing whitespace. + expected = ( + "config BASIC_NO_PROMPT\n" + "\tbool\n" + "\thelp\n" + "\t blah blah\n" + "\t \n" + "\t blah blah blah\n" + "\t \n" + "\t blah" + ) + assert str(self.c.syms["BASIC_NO_PROMPT"]) == expected + + def test_basic_prompt(self): + verify_str( + self.c.syms["BASIC_PROMPT"], + """ +config BASIC_PROMPT +\tbool "basic" +""", + ) + + def test_advanced(self): + verify_str( + self.c.syms["ADVANCED"], + """ +config ADVANCED +\ttristate "prompt" if DEP +\tdefault DEFAULT_1 +\tdefault DEFAULT_2 if DEP +\tselect SELECTED_1 +\tselect SELECTED_2 if DEP +\timply IMPLIED_1 +\timply IMPLIED_2 if DEP +\thelp +\t first help text + +config ADVANCED +\ttristate "prompt 2" + +menuconfig ADVANCED +\ttristate "prompt 3" + +config ADVANCED +\ttristate +\tdepends on (A || !B || (C && D) || !(E && F) || G = H || (I && !J && (K || L) && !(M || N) && O = P)) && DEP4 && DEP3 +\thelp +\t second help text + +config ADVANCED +\ttristate "prompt 4" if VIS +\tdepends on DEP4 && DEP3 +""", + ) + + def test_advanced_custom_str(self): + verify_custom_str( + self.c.syms["ADVANCED"], + """ +config ADVANCED +\ttristate "prompt" if [DEP] +\tdefault [DEFAULT_1] +\tdefault [DEFAULT_2] if [DEP] +\tselect [SELECTED_1] +\tselect [SELECTED_2] if [DEP] +\timply [IMPLIED_1] +\timply [IMPLIED_2] if [DEP] +\thelp +\t first help text + +config ADVANCED +\ttristate "prompt 2" + +menuconfig ADVANCED +\ttristate "prompt 3" + +config ADVANCED +\ttristate +\tdepends on ([A] || ![B] || ([C] && [D]) || !([E] && [F]) || [G] = [H] || ([I] && ![J] && ([K] || [L]) && !([M] || [N]) && [O] = [P])) && [DEP4] && [DEP3] +\thelp +\t second help text + +config ADVANCED +\ttristate "prompt 4" if [VIS] +\tdepends on [DEP4] && [DEP3] +""", + ) + + def test_only_direct_deps(self): + verify_str( + self.c.syms["ONLY_DIRECT_DEPS"], + """ +config ONLY_DIRECT_DEPS +\tint +\tdepends on DEP1 && DEP2 +""", + ) + + def test_string(self): + verify_str( + self.c.syms["STRING"], + """ +config STRING +\tstring +\tdefault "foo" +\tdefault "bar" if DEP +\tdefault STRING2 +\tdefault STRING3 if DEP +""", + ) + + def test_int(self): + verify_str( + self.c.syms["INT"], + """ +config INT +\tint +\trange 1 2 +\trange FOO BAR +\trange BAZ QAZ if DEP +\tdefault 7 if DEP +""", + ) + + def test_hex(self): + verify_str( + self.c.syms["HEX"], + """ +config HEX +\thex +\trange 0x100 0x200 +\trange FOO BAR +\trange BAZ QAZ if DEP +\tdefault 0x123 +""", + ) + + def test_modules(self): + verify_str( + self.c.modules, + """ +config MODULES +\tbool "MODULES" +\toption modules +""", + ) + + def test_options(self): + verify_str( + self.c.syms["OPTIONS"], + """ +config OPTIONS +\toption allnoconfig_y +\toption defconfig_list +\toption env="ENV" +""", + ) + + def test_correct_prop_locs_bool(self): + verify_str( + self.c.syms["CORRECT_PROP_LOCS_BOOL"], + """ +config CORRECT_PROP_LOCS_BOOL +\tbool "prompt 1" +\tdefault DEFAULT_1 +\tdefault DEFAULT_2 +\tselect SELECT_1 +\tselect SELECT_2 +\timply IMPLY_1 +\timply IMPLY_2 +\tdepends on LOC_1 +\thelp +\t help 1 + +menuconfig CORRECT_PROP_LOCS_BOOL +\tbool "prompt 2" +\tdefault DEFAULT_3 +\tdefault DEFAULT_4 +\tselect SELECT_3 +\tselect SELECT_4 +\timply IMPLY_3 +\timply IMPLY_4 +\tdepends on LOC_2 +\thelp +\t help 2 + +config CORRECT_PROP_LOCS_BOOL +\tbool "prompt 3" +\tdefault DEFAULT_5 +\tdefault DEFAULT_6 +\tselect SELECT_5 +\tselect SELECT_6 +\timply IMPLY_5 +\timply IMPLY_6 +\tdepends on LOC_3 +\thelp +\t help 2 +""", + ) + + def test_correct_prop_locs_int(self): + verify_str( + self.c.syms["CORRECT_PROP_LOCS_INT"], + """ +config CORRECT_PROP_LOCS_INT +\tint +\trange 1 2 +\trange 3 4 +\tdepends on LOC_1 + +config CORRECT_PROP_LOCS_INT +\tint +\trange 5 6 +\trange 7 8 +\tdepends on LOC_2 +""", + ) + + def test_prompt_only(self): + verify_str( + self.c.syms["PROMPT_ONLY"], + """ +config PROMPT_ONLY +\tprompt "prompt only" +""", + ) + + def test_correct_prop_locs_int_custom_str(self): + verify_custom_str( + self.c.syms["CORRECT_PROP_LOCS_INT"], + """ +config CORRECT_PROP_LOCS_INT +\tint +\trange [1] [2] +\trange [3] [4] +\tdepends on [LOC_1] + +config CORRECT_PROP_LOCS_INT +\tint +\trange [5] [6] +\trange [7] [8] +\tdepends on [LOC_2] +""", + ) + + +# -- Choice.__str__() / custom_str() ---------------------------------------- + + +class TestChoiceStr: + @pytest.fixture(autouse=True) + def _setup(self, kstr_config): + self.c = kstr_config + + def test_choice_named(self): + verify_str( + self.c.named_choices["CHOICE"], + """ +choice CHOICE +\ttristate "foo" +\tdefault CHOICE_1 +\tdefault CHOICE_2 if dep +""", + ) + + def test_choice_unnamed(self): + verify_str( + self.c.named_choices["CHOICE"].nodes[0].next.item, + """ +choice +\ttristate "no name" +\toptional +""", + ) + + def test_choice_correct_prop_locs(self): + verify_str( + self.c.named_choices["CORRECT_PROP_LOCS_CHOICE"], + """ +choice CORRECT_PROP_LOCS_CHOICE +\tbool +\tdefault CHOICE_3 +\tdepends on LOC_1 + +choice CORRECT_PROP_LOCS_CHOICE +\tbool +\tdefault CHOICE_4 +\tdepends on LOC_2 + +choice CORRECT_PROP_LOCS_CHOICE +\tbool +\tdefault CHOICE_5 +\tdepends on LOC_3 +""", + ) + + def test_choice_correct_prop_locs_custom_str(self): + verify_custom_str( + self.c.named_choices["CORRECT_PROP_LOCS_CHOICE"], + """ +choice CORRECT_PROP_LOCS_CHOICE +\tbool +\tdefault [CHOICE_3] +\tdepends on [LOC_1] + +choice CORRECT_PROP_LOCS_CHOICE +\tbool +\tdefault [CHOICE_4] +\tdepends on [LOC_2] + +choice CORRECT_PROP_LOCS_CHOICE +\tbool +\tdefault [CHOICE_5] +\tdepends on [LOC_3] +""", + ) + + +# -- MenuNode.__str__() / custom_str() for menus and comments --------------- + + +class TestMenuNodeStr: + @pytest.fixture(autouse=True) + def _setup(self, kstr_config): + self.c = kstr_config + + def test_simple_menu(self): + verify_str( + self.c.syms["SIMPLE_MENU_HOOK"].nodes[0].next, + """ +menu "simple menu" +""", + ) + + def test_advanced_menu(self): + verify_str( + self.c.syms["ADVANCED_MENU_HOOK"].nodes[0].next, + """ +menu "advanced menu" +\tdepends on A +\tvisible if B && (C || D) +""", + ) + + def test_advanced_menu_custom_str(self): + verify_custom_str( + self.c.syms["ADVANCED_MENU_HOOK"].nodes[0].next, + """ +menu "advanced menu" +\tdepends on [A] +\tvisible if [B] && ([C] || [D]) +""", + ) + + def test_simple_comment(self): + verify_str( + self.c.syms["SIMPLE_COMMENT_HOOK"].nodes[0].next, + """ +comment "simple comment" +""", + ) + + def test_advanced_comment(self): + verify_str( + self.c.syms["ADVANCED_COMMENT_HOOK"].nodes[0].next, + """ +comment "advanced comment" +\tdepends on A && B +""", + ) + + def test_advanced_comment_custom_str(self): + verify_custom_str( + self.c.syms["ADVANCED_COMMENT_HOOK"].nodes[0].next, + """ +comment "advanced comment" +\tdepends on [A] && [B] +""", + ) + + +# -- {MenuNode,Symbol,Choice}.orig_* ---------------------------------------- + + +class TestOrigProperties: + @pytest.fixture(autouse=True) + def _setup(self, kstr_config): + self.c = kstr_config + + def test_dep_rem_corner_cases(self): + verify_str( + self.c.syms["DEP_REM_CORNER_CASES"], + """ +config DEP_REM_CORNER_CASES +\tbool +\tdefault A +\tdepends on n + +config DEP_REM_CORNER_CASES +\tbool +\tdefault B if n + +config DEP_REM_CORNER_CASES +\tbool +\tdefault C +\tdepends on m && MODULES + +config DEP_REM_CORNER_CASES +\tbool +\tdefault D if A + +config DEP_REM_CORNER_CASES +\tbool +\tdefault E if !E1 +\tdefault F if F1 = F2 +\tdefault G if G1 || H1 +\tdepends on !H + +config DEP_REM_CORNER_CASES +\tbool +\tdefault H +\tdepends on "foo" = "bar" + +config DEP_REM_CORNER_CASES +\tbool "prompt" if FOO || BAR +\tdepends on BAZ && QAZ +""", + ) + + def test_symbol_orig_defaults(self): + verify_deps(self.c.syms["BOOL_SYM_ORIG"].orig_defaults, 1, "DEP y y") + + def test_symbol_orig_selects(self): + verify_deps(self.c.syms["BOOL_SYM_ORIG"].orig_selects, 1, "y DEP y") + + def test_symbol_orig_implies(self): + verify_deps(self.c.syms["BOOL_SYM_ORIG"].orig_implies, 1, "y y DEP") + + def test_int_sym_orig_ranges(self): + verify_deps(self.c.syms["INT_SYM_ORIG"].orig_ranges, 2, "DEP y DEP") + + def test_choice_orig_defaults(self): + verify_deps(self.c.named_choices["CHOICE_ORIG"].orig_defaults, 1, "y DEP DEP") + + +# -- Symbol.__repr__() ------------------------------------------------------ + + +class TestSymbolRepr: + @pytest.fixture(autouse=True) + def _setup(self, krepr_config): + self.c = krepr_config + + def test_n(self): + assert repr(self.c.n) == "" + + def test_m(self): + assert repr(self.c.m) == "" + + def test_y(self): + assert repr(self.c.y) == "" + + def test_undefined(self): + assert ( + repr(self.c.syms["UNDEFINED"]) + == '' + ) + + def test_basic(self): + assert ( + repr(self.c.syms["BASIC"]) + == "" + ) + + def test_visible(self): + assert ( + repr(self.c.syms["VISIBLE"]) + == '' + ) + + def test_visible_set_value(self): + self.c.syms["VISIBLE"].set_value(2) + assert ( + repr(self.c.syms["VISIBLE"]) + == '' + ) + + def test_string_set_value(self): + self.c.syms["STRING"].set_value("foo") + assert ( + repr(self.c.syms["STRING"]) + == '' + ) + + def test_dir_dep_n(self): + assert ( + repr(self.c.syms["DIR_DEP_N"]) + == '' + ) + + def test_options(self): + assert ( + repr(self.c.syms["OPTIONS"]) + == '' + ) + + def test_multi_def(self): + assert ( + repr(self.c.syms["MULTI_DEF"]) + == '' + ) + + def test_choice_sym(self): + assert ( + repr(self.c.syms["CHOICE_1"]) + == '' + ) + + def test_modules(self): + assert ( + repr(self.c.modules) + == "" + ) + + +# -- Choice.__repr__() ------------------------------------------------------ + + +class TestChoiceRepr: + @pytest.fixture(autouse=True) + def _setup(self, krepr_config): + self.c = krepr_config + + def test_choice_basic(self): + assert ( + repr(self.c.named_choices["CHOICE"]) + == '' + ) + + def test_choice_set_value_y(self): + self.c.named_choices["CHOICE"].set_value(2) + assert ( + repr(self.c.named_choices["CHOICE"]) + == '' + ) + + def test_choice_user_selection(self): + self.c.named_choices["CHOICE"].set_value(2) + self.c.syms["CHOICE_2"].set_value(2) + assert ( + repr(self.c.named_choices["CHOICE"]) + == '' + ) + + def test_choice_user_selection_overridden(self): + self.c.named_choices["CHOICE"].set_value(2) + self.c.syms["CHOICE_2"].set_value(2) + self.c.named_choices["CHOICE"].set_value(1) + assert ( + repr(self.c.named_choices["CHOICE"]) + == '' + ) + + def test_choice_optional_unnamed(self): + assert ( + repr(self.c.syms["CHOICE_HOOK"].nodes[0].next.item) + == '' + ) + + +# -- MenuNode.__repr__() ---------------------------------------------------- + + +class TestMenuNodeRepr: + @pytest.fixture(autouse=True) + def _setup(self, krepr_config): + self.c = krepr_config + + def test_basic_node(self): + assert ( + repr(self.c.syms["BASIC"].nodes[0]) + == "" + ) + + def test_dir_dep_n_node(self): + assert ( + repr(self.c.syms["DIR_DEP_N"].nodes[0]) + == "" + ) + + def test_multi_def_node_0(self): + assert ( + repr(self.c.syms["MULTI_DEF"].nodes[0]) + == "" + ) + + def test_multi_def_node_1(self): + assert ( + repr(self.c.syms["MULTI_DEF"].nodes[1]) + == "" + ) + + def test_menuconfig_node(self): + assert ( + repr(self.c.syms["MENUCONFIG"].nodes[0]) + == "" + ) + + def test_choice_node(self): + assert ( + repr(self.c.named_choices["CHOICE"].nodes[0]) + == '' + ) + + def test_optional_choice_node(self): + assert ( + repr(self.c.syms["CHOICE_HOOK"].nodes[0].next) + == '' + ) + + def test_menu_no_visible_if(self): + expected = ( + '" + ) + assert repr(self.c.syms["NO_VISIBLE_IF_HOOK"].nodes[0].next) == expected + + def test_menu_visible_if(self): + expected = ( + '" + ) + assert repr(self.c.syms["VISIBLE_IF_HOOK"].nodes[0].next) == expected + + def test_comment_node(self): + assert ( + repr(self.c.syms["COMMENT_HOOK"].nodes[0].next) + == '' + ) + + +# -- Kconfig.__repr__() ----------------------------------------------------- + + +class TestKconfigRepr: + def test_kconfig_repr_default(self): + c = Kconfig("tests/Krepr", warn=False) + assert repr(c) == ( + '" + ) + + def test_kconfig_repr_with_srctree(self, monkeypatch): + monkeypatch.setenv("srctree", ".") + monkeypatch.setenv("CONFIG_", "CONFIG_ value") + c = Kconfig("tests/Krepr", warn=False) + c.warn = True + c.warn_to_stderr = False + c.warn_assign_override = False + c.warn_assign_redun = False + c.warn_assign_undef = True + assert repr(c) == ( + '" + ) diff --git a/tests/test_semantics.py b/tests/test_semantics.py new file mode 100644 index 0000000..c459e40 --- /dev/null +++ b/tests/test_semantics.py @@ -0,0 +1,281 @@ +# Copyright (c) 2011-2019 Ulf Magnusson +# SPDX-License-Identifier: ISC +# +# Imply and choice semantics tests. + +from kconfiglib import Kconfig, BOOL, TRISTATE +from conftest import ( + verify_value, + assign_and_verify, + assign_and_verify_value, +) + +# --------------------------------------------------------------------------- +# Imply semantics +# --------------------------------------------------------------------------- + + +def test_imply_default_values(): + c = Kconfig("tests/Kimply") + + verify_value(c, "IMPLY_DIRECT_DEPS", "y") + verify_value(c, "UNMET_DIRECT_1", "n") + verify_value(c, "UNMET_DIRECT_2", "n") + verify_value(c, "UNMET_DIRECT_3", "n") + verify_value(c, "MET_DIRECT_1", "y") + verify_value(c, "MET_DIRECT_2", "y") + verify_value(c, "MET_DIRECT_3", "y") + verify_value(c, "MET_DIRECT_4", "y") + verify_value(c, "IMPLY_COND", "y") + verify_value(c, "IMPLIED_N_COND", "n") + verify_value(c, "IMPLIED_M_COND", "m") + verify_value(c, "IMPLIED_Y_COND", "y") + verify_value(c, "IMPLY_N_1", "n") + verify_value(c, "IMPLY_N_2", "n") + verify_value(c, "IMPLIED_FROM_N_1", "n") + verify_value(c, "IMPLIED_FROM_N_2", "n") + verify_value(c, "IMPLY_M", "m") + verify_value(c, "IMPLIED_M", "m") + verify_value(c, "IMPLIED_M_BOOL", "y") + verify_value(c, "IMPLY_M_TO_Y", "y") + verify_value(c, "IMPLIED_M_TO_Y", "y") + + +def test_imply_user_values(): + c = Kconfig("tests/Kimply") + + # Verify that IMPLIED_TRISTATE is invalidated if the direct + # dependencies change + + assign_and_verify(c, "IMPLY", 2) + assign_and_verify(c, "DIRECT_DEP", 2) + verify_value(c, "IMPLIED_TRISTATE", 2) + assign_and_verify(c, "DIRECT_DEP", 0) + verify_value(c, "IMPLIED_TRISTATE", 0) + # Set back for later tests + assign_and_verify(c, "DIRECT_DEP", 2) + + # Verify that IMPLIED_TRISTATE can be set to anything when IMPLY has value + # n, and that it gets the value n by default (for non-imply-related + # reasons) + + assign_and_verify(c, "IMPLY", 0) + assign_and_verify(c, "IMPLIED_TRISTATE", 0) + assign_and_verify(c, "IMPLIED_TRISTATE", 1) + assign_and_verify(c, "IMPLIED_TRISTATE", 2) + c.syms["IMPLIED_TRISTATE"].unset_value() + verify_value(c, "IMPLIED_TRISTATE", "n") + + # Same as above for m. Anything still goes, but m by default now. + + assign_and_verify(c, "IMPLY", 1) + assign_and_verify(c, "IMPLIED_TRISTATE", 0) + assign_and_verify(c, "IMPLIED_TRISTATE", 1) + assign_and_verify(c, "IMPLIED_TRISTATE", 2) + c.syms["IMPLIED_TRISTATE"].unset_value() + verify_value(c, "IMPLIED_TRISTATE", 1) + + # Same as above for y. Only n and y should be accepted. m gets promoted to + # y. Default should be y. + + assign_and_verify(c, "IMPLY", 2) + assign_and_verify(c, "IMPLIED_TRISTATE", 0) + assign_and_verify_value(c, "IMPLIED_TRISTATE", 1, 2) + assign_and_verify(c, "IMPLIED_TRISTATE", 2) + c.syms["IMPLIED_TRISTATE"].unset_value() + verify_value(c, "IMPLIED_TRISTATE", 2) + + # Being implied to either m or y should give a bool the value y + + c.syms["IMPLY"].unset_value() + verify_value(c, "IMPLIED_BOOL", 0) + assign_and_verify(c, "IMPLY", 0) + verify_value(c, "IMPLIED_BOOL", 0) + assign_and_verify(c, "IMPLY", 1) + verify_value(c, "IMPLIED_BOOL", 2) + assign_and_verify(c, "IMPLY", 2) + verify_value(c, "IMPLIED_BOOL", 2) + + # A bool implied to m or y can take the values n and y + + c.syms["IMPLY"].set_value(1) + assign_and_verify(c, "IMPLIED_BOOL", 0) + assign_and_verify(c, "IMPLIED_BOOL", 2) + + c.syms["IMPLY"].set_value(2) + assign_and_verify(c, "IMPLIED_BOOL", 0) + assign_and_verify(c, "IMPLIED_BOOL", 2) + + +# --------------------------------------------------------------------------- +# Choice semantics +# --------------------------------------------------------------------------- + + +def test_choice_types(): + c = Kconfig("tests/Kchoice", warn=False) + + for name in "BOOL", "BOOL_OPT", "BOOL_M", "DEFAULTS": + assert c.named_choices[name].orig_type == BOOL, f"choice {name} type" + + for name in "TRISTATE", "TRISTATE_OPT", "TRISTATE_M": + assert c.named_choices[name].orig_type == TRISTATE, f"choice {name} type" + + +def test_choice_modes(): + c = Kconfig("tests/Kchoice", warn=False) + + def verify_mode(choice_name, no_modules_mode, modules_mode): + choice = c.named_choices[choice_name] + c.modules.set_value(0) + assert ( + choice.tri_value == no_modules_mode + ), f"{choice.name} mode without modules" + c.modules.set_value(2) + assert choice.tri_value == modules_mode, f"{choice.name} mode with modules" + + verify_mode("BOOL", 2, 2) + verify_mode("BOOL_OPT", 0, 0) + verify_mode("TRISTATE", 2, 1) + verify_mode("TRISTATE_OPT", 0, 0) + verify_mode("BOOL_M", 0, 2) + verify_mode("TRISTATE_M", 0, 1) + + +def test_choice_defaults(): + c = Kconfig("tests/Kchoice", warn=False) + + choice = c.named_choices["DEFAULTS"] + + c.syms["TRISTATE_SYM"].set_value(0) + assert choice.selection is c.syms["OPT_4"], "choice default with TRISTATE_SYM = n" + + c.syms["TRISTATE_SYM"].set_value(2) + assert choice.selection is c.syms["OPT_2"], "choice default with TRISTATE_SYM = y" + + c.syms["OPT_1"].set_value(2) + assert choice.selection is c.syms["OPT_1"], "user selection override" + + assert ( + c.named_choices["DEFAULTS_NOT_VISIBLE"].selection is c.syms["OPT_8"] + ), "non-visible choice symbols default" + + +def test_choice_selection(): + c = Kconfig("tests/Kchoice", warn=False) + + def select_and_verify(sym): + choice = sym.nodes[0].parent.item + choice.set_value(2) + sym.set_value(2) + assert sym.choice.selection is sym, f"{sym.name} selected symbol" + assert choice.user_selection is sym, f"{sym.name} user selection" + assert sym.tri_value == 2, f"{sym.name} value when selected" + assert sym.user_value == 2, f"{sym.name} user value when selected" + for sibling in choice.syms: + if sibling is not sym: + assert sibling.tri_value == 0, f"{sibling.name} not selected" + + def select_and_verify_all(choice_name): + choice = c.named_choices[choice_name] + for sym in choice.syms: + select_and_verify(sym) + for sym in reversed(choice.syms): + select_and_verify(sym) + + c.modules.set_value(2) + + select_and_verify_all("BOOL") + select_and_verify_all("BOOL_OPT") + select_and_verify_all("TRISTATE") + select_and_verify_all("TRISTATE_OPT") + # For BOOL_M, the mode should have been promoted + select_and_verify_all("BOOL_M") + + +def test_choice_m_mode(): + c = Kconfig("tests/Kchoice", warn=False) + tristate = c.named_choices["TRISTATE"] + + c.modules.set_value(2) + tristate.set_value(1) + + assert ( + tristate.tri_value == 1 + ), "TRISTATE choice should have mode m after explicit mode assignment" + + assign_and_verify_value(c, "T_1", 0, 0) + assign_and_verify_value(c, "T_2", 0, 0) + assign_and_verify_value(c, "T_1", 1, 1) + assign_and_verify_value(c, "T_2", 1, 1) + assign_and_verify_value(c, "T_1", 2, 1) + assign_and_verify_value(c, "T_2", 2, 1) + + # Switching to y mode should cause T_2 to become selected + tristate.set_value(2) + verify_value(c, "T_1", 0) + verify_value(c, "T_2", 2) + + +def test_choice_no_explicit_type(): + c = Kconfig("tests/Kchoice", warn=False) + + assert ( + c.named_choices["NO_TYPE_BOOL"].orig_type == BOOL + ), "Expected first choice without explicit type to have type bool" + + assert ( + c.named_choices["NO_TYPE_TRISTATE"].orig_type == TRISTATE + ), "Expected second choice without explicit type to have type tristate" + + +def test_choice_symbol_types(): + c = Kconfig("tests/Kchoice", warn=False) + + for name in "MMT_1", "MMT_2", "MMT_4", "MMT_5": + assert c.syms[name].orig_type == BOOL, f"{name} type" + + assert c.syms["MMT_3"].orig_type == TRISTATE, "MMT_3 type" + + +def test_choice_default_with_dep(): + c = Kconfig("tests/Kchoice", warn=False) + choice = c.named_choices["DEFAULT_WITH_DEP"] + + assert choice.selection is c.syms["B"], "choice default with unsatisfied deps" + + c.syms["DEP"].set_value("y") + assert choice.selection is c.syms["A"], "choice default with satisfied deps" + + c.syms["DEP"].set_value("n") + assert choice.selection is c.syms["B"], "choice default with unsatisfied deps again" + + +def test_choice_weird_symbols(): + c = Kconfig("tests/Kchoice", warn=False) + + weird_choice = c.named_choices["WEIRD_SYMS"] + + def verify_is_normal_choice_symbol(name): + sym = c.syms[name] + assert ( + sym.choice is not None + and sym in weird_choice.syms + and sym.nodes[0].parent.item is weird_choice + ), f"{sym.name} normal choice symbol" + + def verify_is_weird_choice_symbol(name): + sym = c.syms[name] + assert ( + sym.choice is None and sym not in weird_choice.syms + ), f"{sym.name} weird choice symbol" + + verify_is_normal_choice_symbol("WS1") + verify_is_weird_choice_symbol("WS2") + verify_is_weird_choice_symbol("WS3") + verify_is_weird_choice_symbol("WS4") + verify_is_weird_choice_symbol("WS5") + verify_is_normal_choice_symbol("WS6") + verify_is_weird_choice_symbol("WS7") + verify_is_weird_choice_symbol("WS8") + verify_is_normal_choice_symbol("WS9") diff --git a/tests/test_symbols.py b/tests/test_symbols.py new file mode 100644 index 0000000..581d5c5 --- /dev/null +++ b/tests/test_symbols.py @@ -0,0 +1,619 @@ +# Copyright (c) 2011-2019 Ulf Magnusson +# SPDX-License-Identifier: ISC +# +# Symbol tests: visibility, assignable, +# object relations, hex/int ranges, defconfig_filename, mainmenu_text, +# user_value, is_menuconfig, option env, defined/undefined, Symbol.choice, +# and is_allnoconfig_y. + +import os + +import pytest + +from kconfiglib import Kconfig, MENU, HEX +from conftest import ( + verify_value, + assign_and_verify_value, + assign_and_verify, + assign_and_verify_user_value, +) + +# -- visibility ------------------------------------------------------------- + + +def test_visibility(): + c = Kconfig("tests/Kvisibility") + + def verify_visibility(item, no_module_vis, module_vis): + c.modules.set_value(0) + assert ( + item.visibility == no_module_vis + ), f"{item.name} visibility without modules" + + c.modules.set_value(2) + assert item.visibility == module_vis, f"{item.name} visibility with modules" + + # Symbol visibility + + verify_visibility(c.syms["NO_PROMPT"], 0, 0) + verify_visibility(c.syms["BOOL_N"], 0, 0) + verify_visibility(c.syms["BOOL_M"], 0, 2) + verify_visibility(c.syms["BOOL_MOD"], 2, 2) + verify_visibility(c.syms["BOOL_Y"], 2, 2) + verify_visibility(c.syms["TRISTATE_M"], 0, 1) + verify_visibility(c.syms["TRISTATE_MOD"], 2, 1) + verify_visibility(c.syms["TRISTATE_Y"], 2, 2) + verify_visibility(c.syms["BOOL_IF_N"], 0, 0) + verify_visibility(c.syms["BOOL_IF_M"], 0, 2) + verify_visibility(c.syms["BOOL_IF_Y"], 2, 2) + verify_visibility(c.syms["BOOL_MENU_N"], 0, 0) + verify_visibility(c.syms["BOOL_MENU_M"], 0, 2) + verify_visibility(c.syms["BOOL_MENU_Y"], 2, 2) + verify_visibility(c.syms["BOOL_CHOICE_N"], 0, 0) + + # Non-tristate symbols in tristate choices are only visible if the choice + # is in y mode + + # The choice can't be brought to y mode because of the 'if m' + verify_visibility(c.syms["BOOL_CHOICE_M"], 0, 0) + c.syms["BOOL_CHOICE_M"].choice.set_value(2) + verify_visibility(c.syms["BOOL_CHOICE_M"], 0, 0) + + # The choice gets y mode only when running without modules, because it + # defaults to m mode + verify_visibility(c.syms["BOOL_CHOICE_Y"], 2, 0) + c.syms["BOOL_CHOICE_Y"].choice.set_value(2) + # When set to y mode, the choice symbol becomes visible both with and + # without modules + verify_visibility(c.syms["BOOL_CHOICE_Y"], 2, 2) + + verify_visibility(c.syms["TRISTATE_IF_N"], 0, 0) + verify_visibility(c.syms["TRISTATE_IF_M"], 0, 1) + verify_visibility(c.syms["TRISTATE_IF_Y"], 2, 2) + verify_visibility(c.syms["TRISTATE_MENU_N"], 0, 0) + verify_visibility(c.syms["TRISTATE_MENU_M"], 0, 1) + verify_visibility(c.syms["TRISTATE_MENU_Y"], 2, 2) + verify_visibility(c.syms["TRISTATE_CHOICE_N"], 0, 0) + verify_visibility(c.syms["TRISTATE_CHOICE_M"], 0, 1) + verify_visibility(c.syms["TRISTATE_CHOICE_Y"], 2, 2) + + verify_visibility(c.named_choices["BOOL_CHOICE_N"], 0, 0) + verify_visibility(c.named_choices["BOOL_CHOICE_M"], 0, 2) + verify_visibility(c.named_choices["BOOL_CHOICE_Y"], 2, 2) + verify_visibility(c.named_choices["TRISTATE_CHOICE_N"], 0, 0) + verify_visibility(c.named_choices["TRISTATE_CHOICE_M"], 0, 1) + verify_visibility(c.named_choices["TRISTATE_CHOICE_Y"], 2, 2) + + verify_visibility(c.named_choices["TRISTATE_CHOICE_IF_M_AND_Y"], 0, 1) + verify_visibility(c.named_choices["TRISTATE_CHOICE_MENU_N_AND_Y"], 0, 0) + + # Verify that 'visible if' visibility gets propagated to prompts + + verify_visibility(c.syms["VISIBLE_IF_N"], 0, 0) + verify_visibility(c.syms["VISIBLE_IF_M"], 0, 1) + verify_visibility(c.syms["VISIBLE_IF_Y"], 2, 2) + verify_visibility(c.syms["VISIBLE_IF_M_2"], 0, 1) + + # Verify that string/int/hex symbols with m visibility accept a user value + + assign_and_verify(c, "STRING_m", "foo bar") + assign_and_verify(c, "INT_m", "123") + assign_and_verify(c, "HEX_m", "0x123") + + +# -- .assignable ------------------------------------------------------------ + + +def test_assignable(): + c = Kconfig("tests/Kassignable") + + def verify_assignable_imp(item, assignable_no_modules, assignable_modules): + for modules_val, assignable in ( + (0, assignable_no_modules), + (2, assignable_modules), + ): + c.modules.set_value(modules_val) + module_msg = "without modules" if modules_val == 0 else "with modules" + + assert item.assignable == assignable, f"{item.name} assignable {module_msg}" + + # Verify that the values can actually be assigned too + for val in item.assignable: + item.set_value(val) + assert item.tri_value == val, f"{item.name} set to {val} {module_msg}" + + def verify_assignable(sym_name, assignable_no_modules, assignable_modules): + verify_assignable_imp( + c.syms[sym_name], assignable_no_modules, assignable_modules + ) + + def verify_const_unassignable(sym_name): + verify_assignable_imp(c.const_syms[sym_name], (), ()) + + # Things that shouldn't be .assignable + verify_const_unassignable("n") + verify_const_unassignable("m") + verify_const_unassignable("y") + verify_const_unassignable("const") + verify_assignable("UNDEFINED", (), ()) + verify_assignable("NO_PROMPT", (), ()) + verify_assignable("STRING", (), ()) + verify_assignable("INT", (), ()) + verify_assignable("HEX", (), ()) + + # Non-selected symbols + verify_assignable("Y_VIS_BOOL", (0, 2), (0, 2)) + verify_assignable("M_VIS_BOOL", (), (0, 2)) # Vis. promoted + verify_assignable("N_VIS_BOOL", (), ()) + verify_assignable("Y_VIS_TRI", (0, 2), (0, 1, 2)) + verify_assignable("M_VIS_TRI", (), (0, 1)) + verify_assignable("N_VIS_TRI", (), ()) + + # Symbols selected to y + verify_assignable("Y_SEL_Y_VIS_BOOL", (2,), (2,)) + verify_assignable("Y_SEL_M_VIS_BOOL", (), (2,)) # Vis. promoted + verify_assignable("Y_SEL_N_VIS_BOOL", (), ()) + verify_assignable("Y_SEL_Y_VIS_TRI", (2,), (2,)) + verify_assignable("Y_SEL_M_VIS_TRI", (), (2,)) + verify_assignable("Y_SEL_N_VIS_TRI", (), ()) + + # Symbols selected to m + verify_assignable("M_SEL_Y_VIS_BOOL", (2,), (2,)) # Value promoted + verify_assignable("M_SEL_M_VIS_BOOL", (), (2,)) # Vis./value promoted + verify_assignable("M_SEL_N_VIS_BOOL", (), ()) + verify_assignable("M_SEL_Y_VIS_TRI", (2,), (1, 2)) + verify_assignable("M_SEL_M_VIS_TRI", (), (1,)) + verify_assignable("M_SEL_N_VIS_TRI", (), ()) + + # Symbols implied to y + verify_assignable("Y_IMP_Y_VIS_BOOL", (0, 2), (0, 2)) + verify_assignable("Y_IMP_M_VIS_BOOL", (), (0, 2)) # Vis. promoted + verify_assignable("Y_IMP_N_VIS_BOOL", (), ()) + verify_assignable("Y_IMP_Y_VIS_TRI", (0, 2), (0, 2)) # m removed by imply + verify_assignable("Y_IMP_M_VIS_TRI", (), (0, 2)) # m promoted to y by imply + verify_assignable("Y_IMP_N_VIS_TRI", (), ()) + + # Symbols implied to m (never affects assignable values) + verify_assignable("M_IMP_Y_VIS_BOOL", (0, 2), (0, 2)) + verify_assignable("M_IMP_M_VIS_BOOL", (), (0, 2)) # Vis. promoted + verify_assignable("M_IMP_N_VIS_BOOL", (), ()) + verify_assignable("M_IMP_Y_VIS_TRI", (0, 2), (0, 1, 2)) + verify_assignable("M_IMP_M_VIS_TRI", (), (0, 1)) + verify_assignable("M_IMP_N_VIS_TRI", (), ()) + + # Symbols in y-mode choice + verify_assignable("Y_CHOICE_BOOL", (2,), (2,)) + verify_assignable("Y_CHOICE_TRISTATE", (2,), (2,)) + verify_assignable("Y_CHOICE_N_VIS_TRISTATE", (), ()) + + # Symbols in m/y-mode choice, starting out in m mode, or y mode when + # running without modules + verify_assignable("MY_CHOICE_BOOL", (2,), ()) + verify_assignable("MY_CHOICE_TRISTATE", (2,), (0, 1)) + verify_assignable("MY_CHOICE_N_VIS_TRISTATE", (), ()) + + c.named_choices["MY_CHOICE"].set_value(2) + + # Symbols in m/y-mode choice, now in y mode + verify_assignable("MY_CHOICE_BOOL", (2,), (2,)) + verify_assignable("MY_CHOICE_TRISTATE", (2,), (2,)) + verify_assignable("MY_CHOICE_N_VIS_TRISTATE", (), ()) + + def verify_choice_assignable( + choice_name, assignable_no_modules, assignable_modules + ): + verify_assignable_imp( + c.named_choices[choice_name], assignable_no_modules, assignable_modules + ) + + # Choices with various possible modes + verify_choice_assignable("Y_CHOICE", (2,), (2,)) + verify_choice_assignable("MY_CHOICE", (2,), (1, 2)) + verify_choice_assignable("NMY_CHOICE", (0, 2), (0, 1, 2)) + verify_choice_assignable("NY_CHOICE", (0, 2), (0, 2)) + verify_choice_assignable("NM_CHOICE", (), (0, 1)) + verify_choice_assignable("M_CHOICE", (), (1,)) + verify_choice_assignable("N_CHOICE", (), ()) + + +# -- object relations ------------------------------------------------------- + + +def test_object_relations(): + c = Kconfig("tests/Krelation") + + assert ( + c.syms["A"].nodes[0].parent is c.top_node + ), "A's parent should be the top node" + + assert ( + c.syms["B"].nodes[0].parent.item is c.named_choices["CHOICE_1"] + ), "B's parent should be the first choice" + + assert ( + c.syms["C"].nodes[0].parent.item is c.syms["B"] + ), "C's parent should be B (due to auto menus)" + + assert c.syms["E"].nodes[0].parent.item == MENU, "E's parent should be a menu" + + assert ( + c.syms["E"].nodes[0].parent.parent is c.top_node + ), "E's grandparent should be the top node" + + assert ( + c.syms["G"].nodes[0].parent.item is c.named_choices["CHOICE_2"] + ), "G's parent should be the second choice" + + assert ( + c.syms["G"].nodes[0].parent.parent.item == MENU + ), "G's grandparent should be a menu" + + +# -- hex/int ranges --------------------------------------------------------- + + +def test_ranges(): + c = Kconfig("tests/Krange", warn=False) + + for sym_name in "HEX_NO_RANGE", "INT_NO_RANGE", "HEX_40", "INT_40": + assert not c.syms[sym_name].ranges, f"{sym_name} should not have ranges" + + for sym_name in ( + "HEX_ALL_RANGES_DISABLED", + "INT_ALL_RANGES_DISABLED", + "HEX_RANGE_10_20_LOW_DEFAULT", + "INT_RANGE_10_20_LOW_DEFAULT", + ): + assert c.syms[sym_name].ranges, f"{sym_name} should have ranges" + + # hex/int symbols without defaults should get no default value + verify_value(c, "HEX_NO_RANGE", "") + verify_value(c, "INT_NO_RANGE", "") + # And neither if all ranges are disabled + verify_value(c, "HEX_ALL_RANGES_DISABLED", "") + verify_value(c, "INT_ALL_RANGES_DISABLED", "") + # Make sure they are assignable though, and test that the form of the user + # value is reflected in the value for hex symbols + assign_and_verify(c, "HEX_NO_RANGE", "0x123") + assign_and_verify(c, "HEX_NO_RANGE", "123") + assign_and_verify(c, "INT_NO_RANGE", "123") + + # Defaults outside of the valid range should be clamped + verify_value(c, "HEX_RANGE_10_20_LOW_DEFAULT", "0x10") + verify_value(c, "HEX_RANGE_10_20_HIGH_DEFAULT", "0x20") + verify_value(c, "INT_RANGE_10_20_LOW_DEFAULT", "10") + verify_value(c, "INT_RANGE_10_20_HIGH_DEFAULT", "20") + # Defaults inside the valid range should be preserved. For hex symbols, + # they should additionally use the same form as in the assignment. + verify_value(c, "HEX_RANGE_10_20_OK_DEFAULT", "0x15") + verify_value(c, "HEX_RANGE_10_20_OK_DEFAULT_ALTERNATE", "15") + verify_value(c, "INT_RANGE_10_20_OK_DEFAULT", "15") + + # hex/int symbols with no defaults but valid ranges should default to the + # lower end of the range if it's > 0 + verify_value(c, "HEX_RANGE_10_20", "0x10") + verify_value(c, "HEX_RANGE_0_10", "") + verify_value(c, "INT_RANGE_10_20", "10") + verify_value(c, "INT_RANGE_0_10", "") + verify_value(c, "INT_RANGE_NEG_10_10", "") + + # User values and dependent ranges + + # Avoid warnings for assigning values outside the active range + c.warn = False + + def verify_range(sym_name, low, high, default): + # Verifies that all values in the range low-high can be assigned, + # and that assigning values outside the range reverts the value back to + # default (None if it should revert back to ""). + + is_hex = c.syms[sym_name].type == HEX + + for i in range(low, high + 1): + assign_and_verify_user_value(c, sym_name, str(i), str(i), True) + if is_hex: + # The form of the user value should be preserved for hex + # symbols + assign_and_verify_user_value(c, sym_name, hex(i), hex(i), True) + + # Verify that assigning a user value just outside the range causes + # defaults to be used + + if default is None: + default_str = "" + elif is_hex: + default_str = hex(default) + else: + default_str = str(default) + + if is_hex: + too_low_str = hex(low - 1) + too_high_str = hex(high + 1) + else: + too_low_str = str(low - 1) + too_high_str = str(high + 1) + + assign_and_verify_value(c, sym_name, too_low_str, default_str) + assign_and_verify_value(c, sym_name, too_high_str, default_str) + + verify_range("HEX_RANGE_10_20_LOW_DEFAULT", 0x10, 0x20, 0x10) + verify_range("HEX_RANGE_10_20_HIGH_DEFAULT", 0x10, 0x20, 0x20) + verify_range("HEX_RANGE_10_20_OK_DEFAULT", 0x10, 0x20, 0x15) + + verify_range("INT_RANGE_10_20_LOW_DEFAULT", 10, 20, 10) + verify_range("INT_RANGE_10_20_HIGH_DEFAULT", 10, 20, 20) + verify_range("INT_RANGE_10_20_OK_DEFAULT", 10, 20, 15) + + verify_range("HEX_RANGE_10_20", 0x10, 0x20, 0x10) + + verify_range("INT_RANGE_10_20", 10, 20, 10) + verify_range("INT_RANGE_0_10", 0, 10, None) + verify_range("INT_RANGE_NEG_10_10", -10, 10, None) + + # Dependent ranges + + verify_value(c, "HEX_40", "40") + verify_value(c, "INT_40", "40") + + c.syms["HEX_RANGE_10_20"].unset_value() + c.syms["INT_RANGE_10_20"].unset_value() + verify_value(c, "HEX_RANGE_10_40_DEPENDENT", "0x10") + verify_value(c, "INT_RANGE_10_40_DEPENDENT", "10") + c.syms["HEX_RANGE_10_20"].set_value("15") + c.syms["INT_RANGE_10_20"].set_value("15") + verify_value(c, "HEX_RANGE_10_40_DEPENDENT", "0x15") + verify_value(c, "INT_RANGE_10_40_DEPENDENT", "15") + c.unset_values() + verify_range("HEX_RANGE_10_40_DEPENDENT", 0x10, 0x40, 0x10) + verify_range("INT_RANGE_10_40_DEPENDENT", 10, 40, 10) + + # Ranges and symbols defined in multiple locations + + verify_value(c, "INACTIVE_RANGE", "2") + verify_value(c, "ACTIVE_RANGE", "1") + + +# -- defconfig_filename ----------------------------------------------------- + + +def test_defconfig_filename(monkeypatch): + # The Kconfig test-data files (Kdefconfig_existent, etc.) contain hardcoded + # "Kconfiglib/tests/..." paths. Compat tests run from a kernel tree root + # where those paths resolve. Running from the project root we need a + # "Kconfiglib" symlink pointing here. + kconfiglib_link = os.path.join(os.getcwd(), "Kconfiglib") + created_link = False + if not os.path.lexists(kconfiglib_link): + try: + os.symlink(".", kconfiglib_link) + except OSError: + pytest.skip("os.symlink() not supported on this platform") + created_link = True + + try: + c = Kconfig("tests/empty") + assert ( + c.defconfig_filename is None + ), "defconfig_filename should be None with no defconfig_list symbol" + + c = Kconfig("tests/Kdefconfig_nonexistent") + assert ( + c.defconfig_filename is None + ), "defconfig_filename should be None when no listed files exist" + + # Referenced in Kdefconfig_existent(_but_n) + monkeypatch.setenv("FOO", "defconfig_2") + + c = Kconfig("tests/Kdefconfig_existent_but_n") + assert ( + c.defconfig_filename is None + ), "defconfig_filename should be None when all default conditions are n" + + c = Kconfig("tests/Kdefconfig_existent") + assert ( + c.defconfig_filename == "Kconfiglib/tests/defconfig_2" + ), "defconfig_filename should return Kconfiglib/tests/defconfig_2" + + # Should also look relative to $srctree if the specified defconfig is a + # relative path and can't be opened + + c = Kconfig("tests/Kdefconfig_srctree") + assert ( + c.defconfig_filename == "Kconfiglib/tests/defconfig_2" + ), "defconfig_filename gave wrong file with $srctree unset" + + monkeypatch.setenv("srctree", "Kconfiglib/tests") + c = Kconfig("Kdefconfig_srctree") + assert ( + c.defconfig_filename == "Kconfiglib/tests/sub/defconfig_in_sub" + ), "defconfig_filename gave wrong file with $srctree set" + + monkeypatch.delenv("srctree", raising=False) + finally: + if created_link: + os.remove(kconfiglib_link) + + +# -- mainmenu_text ---------------------------------------------------------- + + +def test_mainmenu_text(monkeypatch): + c = Kconfig("tests/empty") + assert ( + c.mainmenu_text == "Main menu" + ), "An empty Kconfig should get a default main menu prompt" + + # Expanded in the mainmenu text + monkeypatch.setenv("FOO", "bar baz") + c = Kconfig("tests/Kmainmenu") + assert c.mainmenu_text == "---bar baz---", "Wrong mainmenu text" + + +# -- user_value ------------------------------------------------------------- + + +def test_user_value(): + # References undefined env. var. Disable warnings. + c = Kconfig("tests/Kmisc", warn=False) + + syms = [c.syms[name] for name in ("BOOL", "TRISTATE", "STRING", "INT", "HEX")] + + for sym in syms: + assert sym.user_value is None, f"{sym.name} initial user_value" + + # Assign valid values for the types + + assign_and_verify_user_value(c, "BOOL", 0, 0, True) + assign_and_verify_user_value(c, "BOOL", 2, 2, True) + assign_and_verify_user_value(c, "TRISTATE", 0, 0, True) + assign_and_verify_user_value(c, "TRISTATE", 1, 1, True) + assign_and_verify_user_value(c, "TRISTATE", 2, 2, True) + assign_and_verify_user_value(c, "STRING", "foo bar", "foo bar", True) + assign_and_verify_user_value(c, "INT", "123", "123", True) + assign_and_verify_user_value(c, "HEX", "0x123", "0x123", True) + + # Assign invalid values for the types. They should retain their old user + # value. + + assign_and_verify_user_value(c, "BOOL", 1, 2, False) + assign_and_verify_user_value(c, "BOOL", "foo", 2, False) + assign_and_verify_user_value(c, "BOOL", "1", 2, False) + assign_and_verify_user_value(c, "TRISTATE", "foo", 2, False) + assign_and_verify_user_value(c, "TRISTATE", "1", 2, False) + assign_and_verify_user_value(c, "STRING", 0, "foo bar", False) + assign_and_verify_user_value(c, "INT", "foo", "123", False) + assign_and_verify_user_value(c, "INT", 0, "123", False) + assign_and_verify_user_value(c, "HEX", "foo", "0x123", False) + assign_and_verify_user_value(c, "HEX", 0, "0x123", False) + assign_and_verify_user_value(c, "HEX", "-0x1", "0x123", False) + + for s in syms: + s.unset_value() + assert s.user_value is None, f"{s.name} user_value after reset" + + +# -- is_menuconfig ---------------------------------------------------------- + + +def test_is_menuconfig(): + c = Kconfig("tests/Kmenuconfig") + + for not_menuconfig in ( + c.syms["NOT_MENUCONFIG_1"].nodes[0], + c.syms["NOT_MENUCONFIG_2"].nodes[0], + c.syms["MENUCONFIG_MULTI_DEF"].nodes[0], + c.syms["COMMENT_HOOK"].nodes[0].next, + ): + assert not not_menuconfig.is_menuconfig, f"{not_menuconfig} is_menuconfig" + + for menuconfig in ( + c.top_node, + c.syms["MENUCONFIG_1"].nodes[0], + c.syms["MENUCONFIG_MULTI_DEF"].nodes[1], + c.syms["MENU_HOOK"].nodes[0].next, + c.syms["CHOICE_HOOK"].nodes[0].next, + ): + assert menuconfig.is_menuconfig, f"{menuconfig} is_menuconfig" + + +# -- option env semantics --------------------------------------------------- + + +def test_option_env(monkeypatch): + monkeypatch.setenv("ENV_VAR", "ENV_VAR value") + + # References undefined env. var., so disable warnings + c = Kconfig("tests/Kmisc", warn=False) + + # Verify that 'option env' is treated like a default + verify_value(c, "FROM_ENV", "ENV_VAR value") + verify_value(c, "FROM_ENV_MISSING", "missing") + verify_value(c, "FROM_ENV_WEIRD", "weird") + + +# -- defined vs undefined symbols ------------------------------------------- + + +def test_defined_undefined(): + # References undefined env. var., so disable warnings + c = Kconfig("tests/Kmisc", warn=False) + + for name in "A", "B", "C", "D", "BOOL", "TRISTATE", "STRING", "INT", "HEX": + assert c.syms[name].nodes, f"{name} should be defined" + + for name in "NOT_DEFINED_1", "NOT_DEFINED_2", "NOT_DEFINED_3", "NOT_DEFINED_4": + assert not c.syms[name].nodes, f"{name} should not be defined" + + +# -- Symbol.choice ---------------------------------------------------------- + + +def test_symbol_choice(): + # References undefined env. var., so disable warnings + c = Kconfig("tests/Kmisc", warn=False) + + for name in "A", "B", "C", "D": + assert c.syms[name].choice is not None, f"{name} should be choice symbol" + + for name in ( + "Q1", + "Q2", + "Q3", + "BOOL", + "TRISTATE", + "STRING", + "INT", + "HEX", + "FROM_ENV", + "FROM_ENV_MISSING", + "NOT_DEFINED_1", + "NOT_DEFINED_2", + "NOT_DEFINED_3", + "NOT_DEFINED_4", + ): + assert c.syms[name].choice is None, f"{name} should not be choice symbol" + + +# -- is_allnoconfig_y ------------------------------------------------------- + + +def test_is_allnoconfig_y(): + # References undefined env. var., so disable warnings + c = Kconfig("tests/Kmisc", warn=False) + + assert not c.syms["NOT_ALLNOCONFIG_Y"].is_allnoconfig_y, "NOT_ALLNOCONFIG_Y flag" + assert c.syms["ALLNOCONFIG_Y"].is_allnoconfig_y, "ALLNOCONFIG_Y flag" + + +# -- user_loc --------------------------------------------------------------- + + +def test_user_loc(): + # References undefined env. var., so disable warnings + c = Kconfig("tests/Kmisc", warn=False) + + sym = c.syms["STRING"] + + # Before any assignment, user_loc should be None + assert sym.user_loc is None, "user_loc should be None before set_value()" + + # After set_value() with an explicit loc, user_loc reflects that loc + sym.set_value("hello", loc=("test_file", 42)) + assert sym.user_loc == ( + "test_file", + 42, + ), "user_loc should reflect the loc passed to set_value()" + + # After set_value() without loc, user_loc is None (the default) + sym.set_value("world") + assert ( + sym.user_loc is None + ), "user_loc should be None when set_value() called without loc" + + # After set_value() with loc again + sym.set_value("again", loc=("another_file", 7)) + assert sym.user_loc == ("another_file", 7) + + # After unset_value(), user_loc should be None + sym.unset_value() + assert sym.user_loc is None, "user_loc should be None after unset_value()" diff --git a/testsuite.py b/testsuite.py deleted file mode 100644 index 2d9c213..0000000 --- a/testsuite.py +++ /dev/null @@ -1,3647 +0,0 @@ -# Copyright (c) 2011-2019 Ulf Magnusson -# SPDX-License-Identifier: ISC - -# This is the Kconfiglib test suite. It runs selftests on Kconfigs provided by -# us and tests compatibility with the C Kconfig implementation by comparing the -# output of Kconfiglib with the output of the scripts/kconfig/*conf utilities -# for different targets and defconfigs. It should be run from the top-level -# kernel directory with -# -# $ python Kconfiglib/testsuite.py -# -# Some additional options can be turned on by passing them as arguments. They -# default to off. -# -# - obsessive: -# By default, only valid arch/defconfig pairs are tested. In obsessive mode, -# every arch will be tested with every defconfig. Increases the testing time -# by an order of magnitude. Occasionally finds (usually obscure) bugs, and I -# make sure everything passes with it. -# -# - obsessive-min-config: -# Like obsessive, for the minimal configuration (defconfig) tests. -# -# - log: -# Log timestamped defconfig test failures to the file test_defconfig_fails. -# Handy in obsessive mode. -# -# For example, this commands runs the test suite in obsessive mode with logging -# enabled: -# -# $ python(3) Kconfiglib/testsuite.py obsessive log -# -# pypy works too, and runs most tests much faster than CPython. -# -# All tests should pass. Report regressions on the GitHub page. - -import difflib -import errno -import os -import re -import shutil -import subprocess -import sys -import tempfile -import textwrap - -from kconfiglib import ( - Kconfig, - Symbol, - Choice, - COMMENT, - MENU, - MenuNode, - BOOL, - TRISTATE, - HEX, - TRI_TO_STR, - escape, - unescape, - expr_str, - expr_items, - split_expr, - _ordered_unique, - OR, - AND, - KconfigError, -) - - -def shell(cmd): - with open(os.devnull, "w") as devnull: - subprocess.call(cmd, shell=True, stdout=devnull, stderr=devnull) - - -all_passed = True - - -def fail(msg=None): - global all_passed - all_passed = False - if msg is not None: - print("fail: " + msg) - - -def verify(cond, msg): - if not cond: - fail(msg) - - -def verify_equal(x, y): - if x != y: - fail("'{}' does not equal '{}'".format(x, y)) - - -# Prevent accidental loading of configuration files by removing -# KCONFIG_ALLCONFIG from the environment -os.environ.pop("KCONFIG_ALLCONFIG", None) - -obsessive = False -obsessive_min_config = False -log = False - - -def run_tests(): - global obsessive, obsessive_min_config, log - for s in sys.argv[1:]: - if s == "obsessive": - obsessive = True - print("Obsessive mode enabled") - elif s == "obsessive-min-config": - obsessive_min_config = True - print("Obsessive minimal config mode enabled") - elif s == "log": - log = True - print("Log mode enabled") - else: - print("Unrecognized option '{}'".format(s)) - return - - run_selftests() - run_compatibility_tests() - - -def run_selftests(): - # - # Common helper functions. These all expect 'c' to hold the current - # configuration. - # - - def verify_value(sym_name, val): - # Verifies that a symbol has a particular value. - - if isinstance(val, int): - val = TRI_TO_STR[val] - - sym = c.syms[sym_name] - verify( - sym.str_value == val, - 'expected {} to have the value "{}", had the value "{}"'.format( - sym_name, val, sym.str_value - ), - ) - - def assign_and_verify_value(sym_name, val, new_val): - # Assigns 'val' to a symbol and verifies that its value becomes - # 'new_val'. Assumes (and tests) that 'val' is valid for the - # symbol type. - - if isinstance(new_val, int): - new_val = TRI_TO_STR[new_val] - - sym = c.syms[sym_name] - old_val = sym.str_value - verify( - sym.set_value(val), - "assigning '{}' to {} unexpectedly failed".format(val, sym_name), - ) - verify( - sym.str_value == new_val, - "expected {} to have the value '{}' after being assigned the " - "value '{}'. Instead, the value is '{}'. The old value was " - "'{}'.".format(sym_name, new_val, val, sym.str_value, old_val), - ) - - def assign_and_verify(sym_name, user_val): - # Like assign_and_verify_value(), with the expected value being the - # value just set. - - assign_and_verify_value(sym_name, user_val, user_val) - - def assign_and_verify_user_value(sym_name, val, user_val, valid): - # Assigns a user value to the symbol and verifies the new user value. - # If valid is True, the user value is valid for the type, otherwise - # not. This is used to test the set_value() return value. - - sym = c.syms[sym_name] - sym_old_user_val = sym.user_value - - verify( - sym.set_value(val) == valid, - "expected the user value '{}' to be {} for {}, was not".format( - val, "valid" if valid else "invalid", sym_name - ), - ) - verify( - sym.user_value == user_val, - "the assigned user value '{}' wasn't reflected in user_value " - "on the symbol {}. Instead, the new user_value was '{}'. The " - "old user value was '{}'.".format( - user_val, sym_name, sym.user_value, sym_old_user_val - ), - ) - - # - # Selftests - # - - print("Testing string literal lexing") - - # Dummy empty configuration just to get a Kconfig object - c = Kconfig("Kconfiglib/tests/empty") - - def verify_string_lex(s, expected): - # Verifies that a constant symbol with the name 'res' is produced from - # lexing 's' - - res = c._tokenize("if " + s)[1].name - verify( - res == expected, - "expected <{}> to produced the constant symbol <{}>, " - "produced <{}>".format(s[1:-1], expected, res), - ) - - verify_string_lex(r""" "" """, "") - verify_string_lex(r""" '' """, "") - - verify_string_lex(r""" "a" """, "a") - verify_string_lex(r""" 'a' """, "a") - verify_string_lex(r""" "ab" """, "ab") - verify_string_lex(r""" 'ab' """, "ab") - verify_string_lex(r""" "abc" """, "abc") - verify_string_lex(r""" 'abc' """, "abc") - - verify_string_lex(r""" "'" """, "'") - verify_string_lex(r""" '"' """, '"') - - verify_string_lex(r""" "\"" """, '"') - verify_string_lex(r""" '\'' """, "'") - - verify_string_lex(r""" "\"\"" """, '""') - verify_string_lex(r""" '\'\'' """, "''") - - verify_string_lex(r""" "\'" """, "'") - verify_string_lex(r""" '\"' """, '"') - - verify_string_lex(r""" "\\" """, "\\") - verify_string_lex(r""" '\\' """, "\\") - - verify_string_lex(r""" "\a\\'\b\c\"'d" """, "a\\'bc\"'d") - verify_string_lex(r""" '\a\\"\b\c\'"d' """, 'a\\"bc\'"d') - - def verify_string_bad(s): - # Verifies that tokenizing 's' throws a KconfigError. Strips the first - # and last characters from 's' so we can use readable raw strings as - # input. - - try: - c.eval_string(s) - except KconfigError: - pass - else: - fail("expected tokenization of {} to fail, didn't".format(s[1:-1])) - - verify_string_bad(r""" " """) - verify_string_bad(r""" ' """) - verify_string_bad(r""" "' """) - verify_string_bad(r""" '" """) - verify_string_bad(r""" "\" """) - verify_string_bad(r""" '\' """) - verify_string_bad(r""" "foo """) - verify_string_bad(r""" 'foo """) - - print("Testing escape() and unescape()") - - def verify_escape_unescape(s, sesc): - # Verify that 's' escapes to 'sesc' and that 'sesc' unescapes to 's' - verify_equal(escape(s), sesc) - verify_equal(unescape(sesc), s) - - verify_escape_unescape(r"", r"") - verify_escape_unescape(r"foo", r"foo") - verify_escape_unescape(r'"', r"\"") - verify_escape_unescape(r'""', r"\"\"") - verify_escape_unescape("\\", r"\\") - verify_escape_unescape(r"\\", r"\\\\") - verify_escape_unescape(r"\"", r"\\\"") - verify_escape_unescape(r'"ab\cd"ef"', r"\"ab\\cd\"ef\"") - - # Backslashes before any character should be unescaped, not just before " - # and \ - verify_equal(unescape(r"\afoo\b\c\\d\\\e\\\\f"), r"afoobc\d\e\\f") - - print("Testing _ordered_unique()") - - verify_equal(_ordered_unique([]), []) - verify_equal(_ordered_unique([1]), [1]) - verify_equal(_ordered_unique([1, 2]), [1, 2]) - verify_equal(_ordered_unique([1, 1]), [1]) - verify_equal(_ordered_unique([1, 1, 2]), [1, 2]) - verify_equal(_ordered_unique([1, 2, 1]), [1, 2]) - verify_equal(_ordered_unique([1, 2, 2]), [1, 2]) - verify_equal(_ordered_unique([1, 2, 3, 2, 1, 2, 3, 4, 3, 2, 1, 0]), [1, 2, 3, 4, 0]) - - print("Testing expression evaluation") - - c = Kconfig("Kconfiglib/tests/Keval", warn=False) - - def verify_eval(expr, val): - res = c.eval_string(expr) - verify(res == val, "'{}' evaluated to {}, expected {}".format(expr, res, val)) - - # No modules - verify_eval("n", 0) - verify_eval("m", 0) - verify_eval("y", 2) - verify_eval("'n'", 0) - verify_eval("'m'", 0) - verify_eval("'y'", 2) - verify_eval("M", 2) - - # Modules - c.modules.set_value(2) - verify_eval("n", 0) - verify_eval("m", 1) - verify_eval("y", 2) - verify_eval("'n'", 0) - verify_eval("'m'", 1) - verify_eval("'y'", 2) - verify_eval("M", 1) - verify_eval("(Y || N) && (m && y)", 1) - - # Non-bool/non-tristate symbols are always n in a tristate sense - verify_eval("Y_STRING", 0) - verify_eval("Y_STRING || m", 1) - - # As are all constants besides y and m - verify_eval('"foo"', 0) - verify_eval('"foo" || "bar"', 0) - verify_eval('"foo" || m', 1) - - # Test equality for symbols - - verify_eval("N = N", 2) - verify_eval("N = n", 2) - verify_eval("N = 'n'", 2) - verify_eval("N != N", 0) - verify_eval("N != n", 0) - verify_eval("N != 'n'", 0) - - verify_eval("M = M", 2) - verify_eval("M = m", 2) - verify_eval("M = 'm'", 2) - verify_eval("M != M", 0) - verify_eval("M != m", 0) - verify_eval("M != 'm'", 0) - - verify_eval("Y = Y", 2) - verify_eval("Y = y", 2) - verify_eval("Y = 'y'", 2) - verify_eval("Y != Y", 0) - verify_eval("Y != y", 0) - verify_eval("Y != 'y'", 0) - - verify_eval("N != M", 2) - verify_eval("N != Y", 2) - verify_eval("M != Y", 2) - - verify_eval("Y_STRING = y", 2) - verify_eval("Y_STRING = 'y'", 2) - verify_eval('FOO_BAR_STRING = "foo bar"', 2) - verify_eval('FOO_BAR_STRING != "foo bar baz"', 2) - verify_eval("INT_37 = 37", 2) - verify_eval("INT_37 = '37'", 2) - verify_eval("HEX_0X37 = 0x37", 2) - verify_eval("HEX_0X37 = '0x37'", 2) - - # These should also hold after 31847b67 (kconfig: allow use of relations - # other than (in)equality) - verify_eval("HEX_0X37 = '0x037'", 2) - verify_eval("HEX_0X37 = '0x0037'", 2) - - # Constant symbol comparisons - verify_eval('"foo" != "bar"', 2) - verify_eval('"foo" = "bar"', 0) - verify_eval('"foo" = "foo"', 2) - - # Undefined symbols get their name as their value - c.warn = False - verify_eval("'not_defined' = not_defined", 2) - verify_eval("not_defined_2 = not_defined_2", 2) - verify_eval("not_defined_1 != not_defined_2", 2) - - # Test less than/greater than - - # Basic evaluation - verify_eval("INT_37 < 38", 2) - verify_eval("38 < INT_37", 0) - verify_eval("INT_37 < '38'", 2) - verify_eval("'38' < INT_37", 0) - verify_eval("INT_37 < 138", 2) - verify_eval("138 < INT_37", 0) - verify_eval("INT_37 < '138'", 2) - verify_eval("'138' < INT_37", 0) - verify_eval("INT_37 < -138", 0) - verify_eval("-138 < INT_37", 2) - verify_eval("INT_37 < '-138'", 0) - verify_eval("'-138' < INT_37", 2) - verify_eval("INT_37 < 37", 0) - verify_eval("37 < INT_37", 0) - verify_eval("INT_37 < 36", 0) - verify_eval("36 < INT_37", 2) - - # Different formats in comparison - verify_eval("INT_37 < 0x26", 2) # 38 - verify_eval("INT_37 < 0x25", 0) # 37 - verify_eval("INT_37 < 0x24", 0) # 36 - verify_eval("HEX_0X37 < 56", 2) # 0x38 - verify_eval("HEX_0X37 < 55", 0) # 0x37 - verify_eval("HEX_0X37 < 54", 0) # 0x36 - - # Other int comparisons - verify_eval("INT_37 <= 38", 2) - verify_eval("INT_37 <= 37", 2) - verify_eval("INT_37 <= 36", 0) - verify_eval("INT_37 > 38", 0) - verify_eval("INT_37 > 37", 0) - verify_eval("INT_37 > 36", 2) - verify_eval("INT_37 >= 38", 0) - verify_eval("INT_37 >= 37", 2) - verify_eval("INT_37 >= 36", 2) - - # Other hex comparisons - verify_eval("HEX_0X37 <= 0x38", 2) - verify_eval("HEX_0X37 <= 0x37", 2) - verify_eval("HEX_0X37 <= 0x36", 0) - verify_eval("HEX_0X37 > 0x38", 0) - verify_eval("HEX_0X37 > 0x37", 0) - verify_eval("HEX_0X37 > 0x36", 2) - verify_eval("HEX_0X37 >= 0x38", 0) - verify_eval("HEX_0X37 >= 0x37", 2) - verify_eval("HEX_0X37 >= 0x36", 2) - - # A hex holding a value without a "0x" prefix should still be treated as - # hexadecimal - verify_eval("HEX_37 < 0x38", 2) - verify_eval("HEX_37 < 0x37", 0) - verify_eval("HEX_37 < 0x36", 0) - - # Symbol comparisons - verify_eval("INT_37 < HEX_0X37", 2) - verify_eval("INT_37 > HEX_0X37", 0) - verify_eval("HEX_0X37 < INT_37 ", 0) - verify_eval("HEX_0X37 > INT_37 ", 2) - verify_eval("INT_37 < INT_37 ", 0) - verify_eval("INT_37 <= INT_37 ", 2) - verify_eval("INT_37 > INT_37 ", 0) - verify_eval("INT_37 <= INT_37 ", 2) - - # Tristate value comparisons - verify_eval("n < n", 0) - verify_eval("n < m", 2) - verify_eval("n < y", 2) - verify_eval("n < N", 0) - verify_eval("n < M", 2) - verify_eval("n < Y", 2) - verify_eval("0 > n", 0) - verify_eval("1 > n", 2) - verify_eval("2 > n", 2) - verify_eval("m < n", 0) - verify_eval("m < m", 0) - verify_eval("m < y", 2) - - # Strings compare lexicographically - verify_eval("'aa' < 'ab'", 2) - verify_eval("'aa' > 'ab'", 0) - verify_eval("'ab' < 'aa'", 0) - verify_eval("'ab' > 'aa'", 2) - - # Comparisons where one of the operands doesn't parse as a number also give - # a lexicographic comparison - verify_eval("INT_37 < '37a' ", 2) - verify_eval("'37a' > INT_37", 2) - verify_eval("INT_37 <= '37a' ", 2) - verify_eval("'37a' >= INT_37", 2) - verify_eval("INT_37 >= '37a' ", 0) - verify_eval("INT_37 > '37a' ", 0) - verify_eval("'37a' < INT_37", 0) - verify_eval("'37a' <= INT_37", 0) - - def verify_eval_bad(expr): - try: - c.eval_string(expr) - except KconfigError: - pass - else: - fail( - 'expected eval_string("{}") to throw KconfigError, ' - "didn't".format(expr) - ) - - # Verify that some bad stuff throws KconfigError's - verify_eval_bad("") - verify_eval_bad("&") - verify_eval_bad("|") - verify_eval_bad("!") - verify_eval_bad("(") - verify_eval_bad(")") - verify_eval_bad("=") - verify_eval_bad("(X") - verify_eval_bad("X)") - verify_eval_bad("X X") - verify_eval_bad("!X X") - verify_eval_bad("X !X") - verify_eval_bad("(X) X") - verify_eval_bad("X &&") - verify_eval_bad("&& X") - verify_eval_bad("X && && X") - verify_eval_bad("X && !&&") - verify_eval_bad("X ||") - verify_eval_bad("|| X") - - print("Testing Symbol.__str__()/custom_str() and def_{int,hex,string}") - - def verify_str(item, s): - verify_equal(str(item), s[1:-1]) - - def verify_custom_str(item, s): - verify_equal(item.custom_str(lambda sc: "[{}]".format(sc.name)), s[1:-1]) - - c = Kconfig("Kconfiglib/tests/Kstr", warn=False) - - c.modules.set_value(2) - - verify_str( - c.syms["UNDEFINED"], - """ -""", - ) - - verify_str( - c.syms["BASIC_NO_PROMPT"], - """ -config BASIC_NO_PROMPT - bool - help - blah blah - - blah blah blah - - blah -""", - ) - - verify_str( - c.syms["BASIC_PROMPT"], - """ -config BASIC_PROMPT - bool "basic" -""", - ) - - verify_str( - c.syms["ADVANCED"], - """ -config ADVANCED - tristate "prompt" if DEP - default DEFAULT_1 - default DEFAULT_2 if DEP - select SELECTED_1 - select SELECTED_2 if DEP - imply IMPLIED_1 - imply IMPLIED_2 if DEP - help - first help text - -config ADVANCED - tristate "prompt 2" - -menuconfig ADVANCED - tristate "prompt 3" - -config ADVANCED - tristate - depends on (A || !B || (C && D) || !(E && F) || G = H || (I && !J && (K || L) && !(M || N) && O = P)) && DEP4 && DEP3 - help - second help text - -config ADVANCED - tristate "prompt 4" if VIS - depends on DEP4 && DEP3 -""", - ) - - verify_custom_str( - c.syms["ADVANCED"], - """ -config ADVANCED - tristate "prompt" if [DEP] - default [DEFAULT_1] - default [DEFAULT_2] if [DEP] - select [SELECTED_1] - select [SELECTED_2] if [DEP] - imply [IMPLIED_1] - imply [IMPLIED_2] if [DEP] - help - first help text - -config ADVANCED - tristate "prompt 2" - -menuconfig ADVANCED - tristate "prompt 3" - -config ADVANCED - tristate - depends on ([A] || ![B] || ([C] && [D]) || !([E] && [F]) || [G] = [H] || ([I] && ![J] && ([K] || [L]) && !([M] || [N]) && [O] = [P])) && [DEP4] && [DEP3] - help - second help text - -config ADVANCED - tristate "prompt 4" if [VIS] - depends on [DEP4] && [DEP3] -""", - ) - - verify_str( - c.syms["ONLY_DIRECT_DEPS"], - """ -config ONLY_DIRECT_DEPS - int - depends on DEP1 && DEP2 -""", - ) - - verify_str( - c.syms["STRING"], - """ -config STRING - string - default "foo" - default "bar" if DEP - default STRING2 - default STRING3 if DEP -""", - ) - - verify_str( - c.syms["INT"], - """ -config INT - int - range 1 2 - range FOO BAR - range BAZ QAZ if DEP - default 7 if DEP -""", - ) - - verify_str( - c.syms["HEX"], - """ -config HEX - hex - range 0x100 0x200 - range FOO BAR - range BAZ QAZ if DEP - default 0x123 -""", - ) - - verify_str( - c.modules, - """ -config MODULES - bool "MODULES" - option modules -""", - ) - - verify_str( - c.syms["OPTIONS"], - """ -config OPTIONS - option allnoconfig_y - option defconfig_list - option env="ENV" -""", - ) - - verify_str( - c.syms["CORRECT_PROP_LOCS_BOOL"], - """ -config CORRECT_PROP_LOCS_BOOL - bool "prompt 1" - default DEFAULT_1 - default DEFAULT_2 - select SELECT_1 - select SELECT_2 - imply IMPLY_1 - imply IMPLY_2 - depends on LOC_1 - help - help 1 - -menuconfig CORRECT_PROP_LOCS_BOOL - bool "prompt 2" - default DEFAULT_3 - default DEFAULT_4 - select SELECT_3 - select SELECT_4 - imply IMPLY_3 - imply IMPLY_4 - depends on LOC_2 - help - help 2 - -config CORRECT_PROP_LOCS_BOOL - bool "prompt 3" - default DEFAULT_5 - default DEFAULT_6 - select SELECT_5 - select SELECT_6 - imply IMPLY_5 - imply IMPLY_6 - depends on LOC_3 - help - help 2 -""", - ) - - verify_str( - c.syms["CORRECT_PROP_LOCS_INT"], - """ -config CORRECT_PROP_LOCS_INT - int - range 1 2 - range 3 4 - depends on LOC_1 - -config CORRECT_PROP_LOCS_INT - int - range 5 6 - range 7 8 - depends on LOC_2 -""", - ) - - verify_str( - c.syms["PROMPT_ONLY"], - """ -config PROMPT_ONLY - prompt "prompt only" -""", - ) - - verify_custom_str( - c.syms["CORRECT_PROP_LOCS_INT"], - """ -config CORRECT_PROP_LOCS_INT - int - range [1] [2] - range [3] [4] - depends on [LOC_1] - -config CORRECT_PROP_LOCS_INT - int - range [5] [6] - range [7] [8] - depends on [LOC_2] -""", - ) - - print("Testing Choice.__str__()/custom_str()") - - verify_str( - c.named_choices["CHOICE"], - """ -choice CHOICE - tristate "foo" - default CHOICE_1 - default CHOICE_2 if dep -""", - ) - - verify_str( - c.named_choices["CHOICE"].nodes[0].next.item, - """ -choice - tristate "no name" - optional -""", - ) - - verify_str( - c.named_choices["CORRECT_PROP_LOCS_CHOICE"], - """ -choice CORRECT_PROP_LOCS_CHOICE - bool - default CHOICE_3 - depends on LOC_1 - -choice CORRECT_PROP_LOCS_CHOICE - bool - default CHOICE_4 - depends on LOC_2 - -choice CORRECT_PROP_LOCS_CHOICE - bool - default CHOICE_5 - depends on LOC_3 -""", - ) - - verify_custom_str( - c.named_choices["CORRECT_PROP_LOCS_CHOICE"], - """ -choice CORRECT_PROP_LOCS_CHOICE - bool - default [CHOICE_3] - depends on [LOC_1] - -choice CORRECT_PROP_LOCS_CHOICE - bool - default [CHOICE_4] - depends on [LOC_2] - -choice CORRECT_PROP_LOCS_CHOICE - bool - default [CHOICE_5] - depends on [LOC_3] -""", - ) - - print("Testing MenuNode.__str__()/custom_str() for menus and comments") - - verify_str( - c.syms["SIMPLE_MENU_HOOK"].nodes[0].next, - """ -menu "simple menu" -""", - ) - - verify_str( - c.syms["ADVANCED_MENU_HOOK"].nodes[0].next, - """ -menu "advanced menu" - depends on A - visible if B && (C || D) -""", - ) - - verify_custom_str( - c.syms["ADVANCED_MENU_HOOK"].nodes[0].next, - """ -menu "advanced menu" - depends on [A] - visible if [B] && ([C] || [D]) -""", - ) - - verify_str( - c.syms["SIMPLE_COMMENT_HOOK"].nodes[0].next, - """ -comment "simple comment" -""", - ) - - verify_str( - c.syms["ADVANCED_COMMENT_HOOK"].nodes[0].next, - """ -comment "advanced comment" - depends on A && B -""", - ) - - verify_custom_str( - c.syms["ADVANCED_COMMENT_HOOK"].nodes[0].next, - """ -comment "advanced comment" - depends on [A] && [B] -""", - ) - - print("Testing {MenuNode,Symbol,Choice}.orig_*") - - # Just test some corner cases here re. MenuNode.orig_*. They are already - # indirectly tested above. Use MenuNode.__str__() as a proxy. - - verify_str( - c.syms["DEP_REM_CORNER_CASES"], - """ -config DEP_REM_CORNER_CASES - bool - default A - depends on n - -config DEP_REM_CORNER_CASES - bool - default B if n - -config DEP_REM_CORNER_CASES - bool - default C - depends on m && MODULES - -config DEP_REM_CORNER_CASES - bool - default D if A - -config DEP_REM_CORNER_CASES - bool - default E if !E1 - default F if F1 = F2 - default G if G1 || H1 - depends on !H - -config DEP_REM_CORNER_CASES - bool - default H - depends on "foo" = "bar" - -config DEP_REM_CORNER_CASES - bool "prompt" if FOO || BAR - depends on BAZ && QAZ -""", - ) - - # Test {Symbol,Choice}.orig_* - - def verify_deps(elms, dep_index, expected): - verify_equal(" ".join(expr_str(elm[dep_index]) for elm in elms), expected) - - verify_deps(c.syms["BOOL_SYM_ORIG"].orig_defaults, 1, "DEP y y") - verify_deps(c.syms["BOOL_SYM_ORIG"].orig_selects, 1, "y DEP y") - verify_deps(c.syms["BOOL_SYM_ORIG"].orig_implies, 1, "y y DEP") - verify_deps(c.syms["INT_SYM_ORIG"].orig_ranges, 2, "DEP y DEP") - verify_deps(c.named_choices["CHOICE_ORIG"].orig_defaults, 1, "y DEP DEP") - - print("Testing Symbol.__repr__()") - - def verify_repr(item, s): - verify_equal(repr(item) + "\n", s[1:]) - - c = Kconfig("Kconfiglib/tests/Krepr", warn=False) - - verify_repr( - c.n, - """ - -""", - ) - - verify_repr( - c.m, - """ - -""", - ) - - verify_repr( - c.y, - """ - -""", - ) - - verify_repr( - c.syms["UNDEFINED"], - """ - -""", - ) - - verify_repr( - c.syms["BASIC"], - """ - -""", - ) - - verify_repr( - c.syms["VISIBLE"], - """ - -""", - ) - - c.syms["VISIBLE"].set_value(2) - c.syms["STRING"].set_value("foo") - - verify_repr( - c.syms["VISIBLE"], - """ - -""", - ) - - verify_repr( - c.syms["STRING"], - """ - -""", - ) - - verify_repr( - c.syms["DIR_DEP_N"], - """ - -""", - ) - - verify_repr( - c.syms["OPTIONS"], - """ - -""", - ) - - verify_repr( - c.syms["MULTI_DEF"], - """ - -""", - ) - - verify_repr( - c.syms["CHOICE_1"], - """ - -""", - ) - - verify_repr( - c.modules, - """ - -""", - ) - - print("Testing Choice.__repr__()") - - verify_repr( - c.named_choices["CHOICE"], - """ - -""", - ) - - c.named_choices["CHOICE"].set_value(2) - - verify_repr( - c.named_choices["CHOICE"], - """ - -""", - ) - - c.syms["CHOICE_2"].set_value(2) - - verify_repr( - c.named_choices["CHOICE"], - """ - -""", - ) - - c.named_choices["CHOICE"].set_value(1) - - verify_repr( - c.named_choices["CHOICE"], - """ - -""", - ) - - verify_repr( - c.syms["CHOICE_HOOK"].nodes[0].next.item, - """ - -""", - ) - - print("Testing MenuNode.__repr__()") - - verify_repr( - c.syms["BASIC"].nodes[0], - """ - -""", - ) - - verify_repr( - c.syms["DIR_DEP_N"].nodes[0], - """ - -""", - ) - - verify_repr( - c.syms["MULTI_DEF"].nodes[0], - """ - -""", - ) - - verify_repr( - c.syms["MULTI_DEF"].nodes[1], - """ - -""", - ) - - verify_repr( - c.syms["MENUCONFIG"].nodes[0], - """ - -""", - ) - - verify_repr( - c.named_choices["CHOICE"].nodes[0], - """ - -""", - ) - - verify_repr( - c.syms["CHOICE_HOOK"].nodes[0].next, - """ - -""", - ) - - verify_repr( - c.syms["NO_VISIBLE_IF_HOOK"].nodes[0].next, - """ - -""", - ) - - verify_repr( - c.syms["VISIBLE_IF_HOOK"].nodes[0].next, - """ - -""", - ) - - verify_repr( - c.syms["COMMENT_HOOK"].nodes[0].next, - """ - -""", - ) - - print("Testing Kconfig.__repr__()") - - verify_repr( - c, - """ - -""", - ) - - os.environ["srctree"] = "Kconfiglib" - os.environ["CONFIG_"] = "CONFIG_ value" - - c = Kconfig("tests/Krepr", warn=False) - c.warn = True - c.warn_to_stderr = False - c.warn_assign_override = False - c.warn_assign_redun = False - c.warn_assign_undef = True - - verify_repr( - c, - """ - -""", - ) - - os.environ.pop("srctree", None) - os.environ.pop("CONFIG_", None) - - print("Testing tricky help strings") - - c = Kconfig("Kconfiglib/tests/Khelp") - - def verify_help(node, s): - verify_equal(node.help, s[1:-1]) - - verify_help( - c.syms["TWO_HELP_STRINGS"].nodes[0], - """ -first help string -""", - ) - - verify_help( - c.syms["TWO_HELP_STRINGS"].nodes[1], - """ -second help string -""", - ) - - verify_help( - c.syms["NO_BLANK_AFTER_HELP"].nodes[0], - """ -help for -NO_BLANK_AFTER_HELP -""", - ) - - verify_help( - c.named_choices["CHOICE_HELP"].nodes[0], - """ -help for -CHOICE_HELP -""", - ) - - verify_help( - c.syms["HELP_TERMINATED_BY_COMMENT"].nodes[0], - """ -a -b -c -""", - ) - - verify_help( - c.syms["TRICKY_HELP"].nodes[0], - """ -a - b - c - - d - e - f - - -g - h - i -""", - ) - - print( - "Testing locations, origins, source/rsource/gsource/grsource, " - "and Kconfig.kconfig_filenames" - ) - - def verify_locations(nodes, *expected_locs): - verify( - len(nodes) == len(expected_locs), - "Wrong number of locations for " + repr(nodes), - ) - - for node, expected_loc in zip(nodes, expected_locs): - node_loc = "{}:{}".format(node.filename, node.linenr) - verify( - node_loc == expected_loc, - "expected {} to have the location {}, had the location {}".format( - repr(node), expected_loc, node_loc - ), - ) - - # Expanded in the 'source' statement in Klocation - - os.environ["TESTS_DIR_FROM_ENV"] = "tests" - os.environ["SUB_DIR_FROM_ENV"] = "sub" - - os.environ["_SOURCED"] = "_sourced" - os.environ["_RSOURCED"] = "_rsourced" - os.environ["_GSOURCED"] = "_gsourced" - os.environ["_GRSOURCED"] = "_grsourced" - - # Test twice, with $srctree as a relative and an absolute path, - # respectively - for srctree in "Kconfiglib", os.path.abspath("Kconfiglib"): - os.environ["srctree"] = srctree - - # Has symbol with empty help text, so disable warnings - c = Kconfig("tests/Klocation", warn=False) - - verify_locations(c.syms["UNDEFINED"].nodes) - verify_equal(c.syms["UNDEFINED"].name_and_loc, "UNDEFINED (undefined)") - - verify_locations(c.syms["ONE_DEF"].nodes, "tests/Klocation:4") - verify_equal( - c.syms["ONE_DEF"].name_and_loc, "ONE_DEF (defined at tests/Klocation:4)" - ) - - verify_locations( - c.syms["TWO_DEF"].nodes, "tests/Klocation:7", "tests/Klocation:10" - ) - verify_equal( - c.syms["TWO_DEF"].name_and_loc, - "TWO_DEF (defined at tests/Klocation:7, tests/Klocation:10)", - ) - - verify_locations( - c.syms["MANY_DEF"].nodes, - "tests/Klocation:13", - "tests/Klocation:43", - "tests/Klocation:45", - "tests/Klocation_sourced:3", - "tests/sub/Klocation_rsourced:2", - "tests/sub/Klocation_gsourced1:1", - "tests/sub/Klocation_gsourced2:1", - "tests/sub/Klocation_gsourced1:1", - "tests/sub/Klocation_gsourced2:1", - "tests/sub/Klocation_grsourced1:1", - "tests/sub/Klocation_grsourced2:1", - "tests/sub/Klocation_grsourced1:1", - "tests/sub/Klocation_grsourced2:1", - "tests/Klocation:78", - ) - - verify_locations( - c.named_choices["CHOICE_ONE_DEF"].nodes, "tests/Klocation_sourced:5" - ) - verify_equal( - c.named_choices["CHOICE_ONE_DEF"].name_and_loc, - " (defined at tests/Klocation_sourced:5)", - ) - - verify_locations( - c.named_choices["CHOICE_TWO_DEF"].nodes, - "tests/Klocation_sourced:9", - "tests/Klocation_sourced:13", - ) - verify_equal( - c.named_choices["CHOICE_TWO_DEF"].name_and_loc, - " (defined at tests/Klocation_sourced:9, tests/Klocation_sourced:13)", - ) - - verify_locations( - [c.syms["MENU_HOOK"].nodes[0].next], "tests/Klocation_sourced:20" - ) - - verify_locations( - [c.syms["COMMENT_HOOK"].nodes[0].next], "tests/Klocation_sourced:26" - ) - - # Test Kconfig.kconfig_filenames - - verify_equal( - c.kconfig_filenames, - [ - "tests/Klocation", - "tests/Klocation_sourced", - "tests/sub/Klocation_rsourced", - "tests/sub/Klocation_gsourced1", - "tests/sub/Klocation_gsourced2", - "tests/sub/Klocation_gsourced1", - "tests/sub/Klocation_gsourced2", - "tests/sub/Klocation_grsourced1", - "tests/sub/Klocation_grsourced2", - "tests/sub/Klocation_grsourced1", - "tests/sub/Klocation_grsourced2", - ], - ) - - # Test recursive 'source' detection - - try: - Kconfig("tests/Krecursive1") - except KconfigError as e: - verify_equal( - str(e), - """ -tests/Krecursive2:1: recursive 'source' of 'tests/Krecursive1' detected. Check that environment variables are set correctly. -Include path: -tests/Krecursive1:1 -tests/Krecursive2:1 -"""[:-1], - ) - except: - fail("recursive 'source' raised wrong exception") - else: - fail("recursive 'source' did not raise exception") - - # Verify that source and rsource throw exceptions for missing files - - # TODO: Make an exception test helper - - try: - Kconfig("tests/Kmissingsource") - except KconfigError as e: - if "not found" not in str(e): - fail("'source' with missing file raised wrong KconfigError") - except: - fail("'source' with missing file raised wrong exception") - else: - fail("'source' with missing file did not raise exception") - - try: - Kconfig("tests/Kmissingrsource") - except KconfigError as e: - if "not found" not in str(e): - fail("'rsource' with missing file raised wrong KconfigError") - except: - fail("'rsource' with missing file raised wrong exception") - else: - fail("'rsource' with missing file did not raise exception") - - # Tests origins - - c = Kconfig("tests/Korigins", warn=False) - c.syms["MAIN_FLAG_SELECT"].set_value(2, "here") - - expected = [ - ("MAIN_FLAG", ("select", ["MAIN_FLAG_SELECT"])), - ( - "MAIN_FLAG_DEPENDENCY", - ("default", (os.path.abspath("Kconfiglib/tests/Korigins"), 6)), - ), - ("MAIN_FLAG_SELECT", ("assign", "here")), - ("SECOND_CHOICE", ("default", None)), - ("UNSET_FLAG", ("unset", None)), - ] - - for node in c.node_iter(True): - if not isinstance(node.item, Symbol): - continue - - if node.item.origin is None: - continue - - exp_name, exp_origin = expected.pop(0) - verify_equal(node.item.name, exp_name) - verify_equal(node.item.origin, exp_origin) - - if len(expected) != 0: - fail("origin test mismatch") - - # Test a tricky case involving symlinks. $srctree is tests/symlink, which - # points to tests/sub/sub, meaning tests/symlink/.. != tests/. Previously, - # using 'rsource' from a file sourced with an absolute path triggered an - # unsafe relpath() with tests/symlink/.. in it, crashing. - - os.environ["srctree"] = "Kconfiglib/tests/symlink" - os.environ["KCONFIG_SYMLINK_2"] = os.path.abspath( - "Kconfiglib/tests/sub/Kconfig_symlink_2" - ) - if not os.path.isabs( - Kconfig("Kconfig_symlink_1").syms["FOUNDME"].nodes[0].filename - ): - - fail("Symlink + rsource issues") - - print("Testing Kconfig.node_iter()") - - # Reuse tests/Klocation. The node_iter(unique_syms=True) case already gets - # plenty of testing from write_config() as well. - - os.environ["srctree"] = "Kconfiglib" - c = Kconfig("tests/Klocation", warn=False) - - verify_equal( - [node.item.name for node in c.node_iter() if isinstance(node.item, Symbol)], - [ - "ONE_DEF", - "TWO_DEF", - "TWO_DEF", - "MANY_DEF", - "HELP_1", - "HELP_2", - "HELP_3", - "MANY_DEF", - "MANY_DEF", - "MANY_DEF", - "MENU_HOOK", - "COMMENT_HOOK", - ] - + 10 * ["MANY_DEF"], - ) - - verify_equal( - [node.item.name for node in c.node_iter(True) if isinstance(node.item, Symbol)], - [ - "ONE_DEF", - "TWO_DEF", - "MANY_DEF", - "HELP_1", - "HELP_2", - "HELP_3", - "MENU_HOOK", - "COMMENT_HOOK", - ], - ) - - verify_equal( - [node.prompt[0] for node in c.node_iter() if not isinstance(node.item, Symbol)], - ["one-def choice", "two-def choice 1", "two-def choice 2", "menu", "comment"], - ) - - verify_equal( - [ - node.prompt[0] - for node in c.node_iter(True) - if not isinstance(node.item, Symbol) - ], - ["one-def choice", "two-def choice 1", "two-def choice 2", "menu", "comment"], - ) - - print("Testing MenuNode.include_path") - - os.environ["srctree"] = "Kconfiglib/tests" - - c = Kconfig("Kinclude_path") - - def verify_node_path(node, *expected): - if node.include_path != expected: - fail( - "Wrong include path for node {!r}. Got {}, expected {}.".format( - node, node.include_path, expected - ) - ) - - def verify_sym_path(sym_name, node_i, *expected): - verify_node_path(c.syms[sym_name].nodes[node_i], *expected) - - verify_sym_path("TOP", 0) - verify_sym_path("TOP", 1) - verify_sym_path("TOP", 2) - - verify_sym_path("ONE_DOWN", 0, ("Kinclude_path", 4)) - verify_sym_path("ONE_DOWN", 1, ("Kinclude_path", 4)) - verify_sym_path("ONE_DOWN", 2, ("Kinclude_path", 4)) - verify_sym_path("ONE_DOWN", 3, ("Kinclude_path", 9)) - verify_sym_path("ONE_DOWN", 4, ("Kinclude_path", 9)) - verify_sym_path("ONE_DOWN", 5, ("Kinclude_path", 9)) - - verify_sym_path("TWO_DOWN", 0, ("Kinclude_path", 4), ("Kinclude_path_sourced_1", 4)) - verify_sym_path("TWO_DOWN", 1, ("Kinclude_path", 4), ("Kinclude_path_sourced_1", 9)) - verify_sym_path("TWO_DOWN", 2, ("Kinclude_path", 9), ("Kinclude_path_sourced_1", 4)) - verify_sym_path("TWO_DOWN", 3, ("Kinclude_path", 9), ("Kinclude_path_sourced_1", 9)) - - verify_node_path(c.top_node) - verify_node_path(c.menus[0], ("Kinclude_path", 4), ("Kinclude_path_sourced_1", 4)) - verify_node_path( - c.comments[0], ("Kinclude_path", 4), ("Kinclude_path_sourced_1", 4) - ) - verify_node_path( - c.choices[0].nodes[0], ("Kinclude_path", 4), ("Kinclude_path_sourced_1", 4) - ) - - os.environ.pop("srctree", None) - - print("Testing Kconfig.choices/menus/comments") - - c = Kconfig("Kconfiglib/tests/Kitemlists") - - def verify_prompts(items, *expected_prompts): - verify( - len(items) == len(expected_prompts), - "Wrong number of prompts for {}".format(items), - ) - - for item, expected_prompt in zip(items, expected_prompts): - if not isinstance(item, MenuNode): - item = item.nodes[0] - - verify( - item.prompt[0] == expected_prompt, - "Wrong prompt for {}, expected '{}'".format( - repr(item), expected_prompt - ), - ) - - verify_prompts(c.choices, "choice 1", "choice 2", "choice 3", "choice 2") - verify_prompts(c.menus, "menu 1", "menu 2", "menu 3", "menu 4", "menu 5") - verify_prompts(c.comments, "comment 1", "comment 2", "comment 3") - - print("Testing Symbol/Choice.direct_dep") - - c = Kconfig("Kconfiglib/tests/Kdirdep") - - verify_equal(expr_str(c.syms["NO_DEP_SYM"].direct_dep), "y") - verify_equal(expr_str(c.syms["DEP_SYM"].direct_dep), "A || (B && C) || !D") - - verify_equal(expr_str(c.named_choices["NO_DEP_CHOICE"].direct_dep), "y") - verify_equal(expr_str(c.named_choices["DEP_CHOICE"].direct_dep), "A || B || C") - - print("Testing conditional dependencies (depends on A if B)") - - c = Kconfig("Kconfiglib/tests/Kconddep") - - # "depends on A if B" should become "!B || A" - verify_equal(expr_str(c.syms["COND_DEP_1"].direct_dep), "!B || A") - - # "depends on (C && D) if E" should become "!E || (C && D)" - verify_equal(expr_str(c.syms["COND_DEP_2"].direct_dep), "!E || (C && D)") - - # Multiple depends combined: "depends on A", "depends on B if C", "depends on D" - # Should become: "A && (!C || B) && D" - verify_equal(expr_str(c.syms["COND_DEP_MIXED"].direct_dep), "A && (!C || B) && D") - - # Test with choice - verify_equal(expr_str(c.named_choices["COND_CHOICE"].direct_dep), "!Y || X") - - # Multiple conditional dependencies: "depends on A if B" and "depends on C if D" - # Should become: "(!B || A) && (!D || C)" - verify_equal(expr_str(c.syms["MULTI_COND"].direct_dep), "(!B || A) && (!D || C)") - - print("Testing expr_items()") - - c = Kconfig("Kconfiglib/tests/Kexpr_items") - - def verify_expr_items(expr, *sym_names): - verify_equal(tuple(sorted(item.name for item in expr_items(expr))), sym_names) - - verify_expr_items( - c.syms["TEST"].defaults[0][0], "A", "B", "C", "D", "E", "F", "G", "H" - ) - - verify_expr_items(c.syms["TEST_CHOICE"].nodes[0].prompt[1], "A", "CHOICE") - - print("Testing MenuNode/Symbol/Choice.referenced") - - c = Kconfig("Kconfiglib/tests/Kreferenced", warn=False) - - def verify_deps(item, *dep_names): - verify_equal(tuple(sorted(item.name for item in item.referenced)), dep_names) - - verify_deps(c.top_node, "y") - - verify_deps(c.syms["NO_REFS"].nodes[0], "y") - - verify_deps(c.syms["JUST_DEPENDS_ON_REFS"].nodes[0], "A", "B") - - verify_deps( - c.syms["LOTS_OF_REFS"].nodes[0], - *(chr(n) for n in range(ord("A"), ord("Z") + 1)) - ) - - verify_deps( - c.syms["INT_REFS"].nodes[0], "A", "B", "C", "D", "E", "F", "G", "H", "y" - ) - - verify_deps(c.syms["CHOICE_REF"].nodes[0], "CHOICE") - - verify_deps(c.menus[0], "A", "B", "C", "D") - - verify_deps(c.comments[0], "A", "B") - - verify_deps(c.syms["MULTI_DEF_SYM"], "A", "B", "C", "y") - verify_deps(c.named_choices["MULTI_DEF_CHOICE"], "A", "B", "C") - - print("Testing split_expr()") - - c = Kconfig("Kconfiglib/tests/empty") - c.warn = False - - def verify_split(to_split, op, operand_strs): - # The same hackage as in Kconfig.eval_string() - c._tokens = c._tokenize("if " + to_split)[1:] - c._tokens_i = 0 - - operands = split_expr(c._parse_expr(False), op) - - verify( - len(operands) == len(operand_strs), - "Wrong number of operands when {} was split by {}".format( - to_split, "OR" if op == OR else "AND" - ), - ) - - for operand, operand_str in zip(operands, operand_strs): - verify_equal(expr_str(operand), operand_str) - - verify_split("A", OR, ("A",)) - verify_split("!A", OR, ("!A",)) - verify_split("A = B", OR, ("A = B",)) - verify_split("A && B", OR, ("A && B",)) - verify_split("A || B", OR, ("A", "B")) - verify_split("(A || B) || C", OR, ("A", "B", "C")) - verify_split("A || (B || C)", OR, ("A", "B", "C")) - verify_split("A || !(B || C)", OR, ("A", "!(B || C)")) - verify_split("A || (B && (C || D))", OR, ("A", "B && (C || D)")) - verify_split("(A && (B || C)) || D", OR, ("A && (B || C)", "D")) - - verify_split("A", AND, ("A",)) - verify_split("!A", AND, ("!A",)) - verify_split("A = B", AND, ("A = B",)) - verify_split("A || B", AND, ("A || B",)) - verify_split("A && B", AND, ("A", "B")) - verify_split("(A && B) && C", AND, ("A", "B", "C")) - verify_split("A && (B && C)", AND, ("A", "B", "C")) - verify_split("A && !(B && C)", AND, ("A", "!(B && C)")) - verify_split("A && (B || (C && D))", AND, ("A", "B || (C && D)")) - verify_split("(A || (B && C)) && D", AND, ("A || (B && C)", "D")) - - print("Testing visibility") - - c = Kconfig("Kconfiglib/tests/Kvisibility") - - def verify_visibility(item, no_module_vis, module_vis): - c.modules.set_value(0) - verify( - item.visibility == no_module_vis, - "expected {} to have visibility {} without modules, had " - "visibility {}".format(repr(item), no_module_vis, item.visibility), - ) - - c.modules.set_value(2) - verify( - item.visibility == module_vis, - "expected {} to have visibility {} with modules, had " - "visibility {}".format(repr(item), module_vis, item.visibility), - ) - - # Symbol visibility - - verify_visibility(c.syms["NO_PROMPT"], 0, 0) - verify_visibility(c.syms["BOOL_N"], 0, 0) - verify_visibility(c.syms["BOOL_M"], 0, 2) - verify_visibility(c.syms["BOOL_MOD"], 2, 2) - verify_visibility(c.syms["BOOL_Y"], 2, 2) - verify_visibility(c.syms["TRISTATE_M"], 0, 1) - verify_visibility(c.syms["TRISTATE_MOD"], 2, 1) - verify_visibility(c.syms["TRISTATE_Y"], 2, 2) - verify_visibility(c.syms["BOOL_IF_N"], 0, 0) - verify_visibility(c.syms["BOOL_IF_M"], 0, 2) - verify_visibility(c.syms["BOOL_IF_Y"], 2, 2) - verify_visibility(c.syms["BOOL_MENU_N"], 0, 0) - verify_visibility(c.syms["BOOL_MENU_M"], 0, 2) - verify_visibility(c.syms["BOOL_MENU_Y"], 2, 2) - verify_visibility(c.syms["BOOL_CHOICE_N"], 0, 0) - - # Non-tristate symbols in tristate choices are only visible if the choice - # is in y mode - - # The choice can't be brought to y mode because of the 'if m' - verify_visibility(c.syms["BOOL_CHOICE_M"], 0, 0) - c.syms["BOOL_CHOICE_M"].choice.set_value(2) - verify_visibility(c.syms["BOOL_CHOICE_M"], 0, 0) - - # The choice gets y mode only when running without modules, because it - # defaults to m mode - verify_visibility(c.syms["BOOL_CHOICE_Y"], 2, 0) - c.syms["BOOL_CHOICE_Y"].choice.set_value(2) - # When set to y mode, the choice symbol becomes visible both with and - # without modules - verify_visibility(c.syms["BOOL_CHOICE_Y"], 2, 2) - - verify_visibility(c.syms["TRISTATE_IF_N"], 0, 0) - verify_visibility(c.syms["TRISTATE_IF_M"], 0, 1) - verify_visibility(c.syms["TRISTATE_IF_Y"], 2, 2) - verify_visibility(c.syms["TRISTATE_MENU_N"], 0, 0) - verify_visibility(c.syms["TRISTATE_MENU_M"], 0, 1) - verify_visibility(c.syms["TRISTATE_MENU_Y"], 2, 2) - verify_visibility(c.syms["TRISTATE_CHOICE_N"], 0, 0) - verify_visibility(c.syms["TRISTATE_CHOICE_M"], 0, 1) - verify_visibility(c.syms["TRISTATE_CHOICE_Y"], 2, 2) - - verify_visibility(c.named_choices["BOOL_CHOICE_N"], 0, 0) - verify_visibility(c.named_choices["BOOL_CHOICE_M"], 0, 2) - verify_visibility(c.named_choices["BOOL_CHOICE_Y"], 2, 2) - verify_visibility(c.named_choices["TRISTATE_CHOICE_N"], 0, 0) - verify_visibility(c.named_choices["TRISTATE_CHOICE_M"], 0, 1) - verify_visibility(c.named_choices["TRISTATE_CHOICE_Y"], 2, 2) - - verify_visibility(c.named_choices["TRISTATE_CHOICE_IF_M_AND_Y"], 0, 1) - verify_visibility(c.named_choices["TRISTATE_CHOICE_MENU_N_AND_Y"], 0, 0) - - # Verify that 'visible if' visibility gets propagated to prompts - - verify_visibility(c.syms["VISIBLE_IF_N"], 0, 0) - verify_visibility(c.syms["VISIBLE_IF_M"], 0, 1) - verify_visibility(c.syms["VISIBLE_IF_Y"], 2, 2) - verify_visibility(c.syms["VISIBLE_IF_M_2"], 0, 1) - - # Verify that string/int/hex symbols with m visibility accept a user value - - assign_and_verify("STRING_m", "foo bar") - assign_and_verify("INT_m", "123") - assign_and_verify("HEX_m", "0x123") - - print("Testing .assignable") - - c = Kconfig("Kconfiglib/tests/Kassignable") - - def verify_assignable_imp(item, assignable_no_modules, assignable_modules): - # Verifies the assignable values for 'item', with and without modules. - - for modules_val, assignable in (0, assignable_no_modules), ( - 2, - assignable_modules, - ): - - c.modules.set_value(modules_val) - module_msg = "without modules" if modules_val == 0 else "with modules" - - verify( - item.assignable == assignable, - "Incorrect assignable values for {} {}. Should be {}, " - "was {}.".format(item.name, module_msg, assignable, item.assignable), - ) - - # Verify that the values can actually be assigned too - - for val in item.assignable: - item.set_value(val) - verify( - item.tri_value == val, - "Unable to set {} to {} {}, even though it was in " - ".assignable".format(item.name, val, module_msg), - ) - - def verify_assignable(sym_name, assignable_no_modules, assignable_modules): - verify_assignable_imp( - c.syms[sym_name], assignable_no_modules, assignable_modules - ) - - def verify_const_unassignable(sym_name): - verify_assignable_imp(c.const_syms[sym_name], (), ()) - - # Things that shouldn't be .assignable - verify_const_unassignable("n") - verify_const_unassignable("m") - verify_const_unassignable("y") - verify_const_unassignable("const") - verify_assignable("UNDEFINED", (), ()) - verify_assignable("NO_PROMPT", (), ()) - verify_assignable("STRING", (), ()) - verify_assignable("INT", (), ()) - verify_assignable("HEX", (), ()) - - # Non-selected symbols - verify_assignable("Y_VIS_BOOL", (0, 2), (0, 2)) - verify_assignable("M_VIS_BOOL", (), (0, 2)) # Vis. promoted - verify_assignable("N_VIS_BOOL", (), ()) - verify_assignable("Y_VIS_TRI", (0, 2), (0, 1, 2)) - verify_assignable("M_VIS_TRI", (), (0, 1)) - verify_assignable("N_VIS_TRI", (), ()) - - # Symbols selected to y - verify_assignable("Y_SEL_Y_VIS_BOOL", (2,), (2,)) - verify_assignable("Y_SEL_M_VIS_BOOL", (), (2,)) # Vis. promoted - verify_assignable("Y_SEL_N_VIS_BOOL", (), ()) - verify_assignable("Y_SEL_Y_VIS_TRI", (2,), (2,)) - verify_assignable("Y_SEL_M_VIS_TRI", (), (2,)) - verify_assignable("Y_SEL_N_VIS_TRI", (), ()) - - # Symbols selected to m - verify_assignable("M_SEL_Y_VIS_BOOL", (2,), (2,)) # Value promoted - verify_assignable("M_SEL_M_VIS_BOOL", (), (2,)) # Vis./value promoted - verify_assignable("M_SEL_N_VIS_BOOL", (), ()) - verify_assignable("M_SEL_Y_VIS_TRI", (2,), (1, 2)) - verify_assignable("M_SEL_M_VIS_TRI", (), (1,)) - verify_assignable("M_SEL_N_VIS_TRI", (), ()) - - # Symbols implied to y - verify_assignable("Y_IMP_Y_VIS_BOOL", (0, 2), (0, 2)) - verify_assignable("Y_IMP_M_VIS_BOOL", (), (0, 2)) # Vis. promoted - verify_assignable("Y_IMP_N_VIS_BOOL", (), ()) - verify_assignable("Y_IMP_Y_VIS_TRI", (0, 2), (0, 2)) # m removed by imply - verify_assignable("Y_IMP_M_VIS_TRI", (), (0, 2)) # m promoted to y by imply - verify_assignable("Y_IMP_N_VIS_TRI", (), ()) - - # Symbols implied to m (never affects assignable values) - verify_assignable("M_IMP_Y_VIS_BOOL", (0, 2), (0, 2)) - verify_assignable("M_IMP_M_VIS_BOOL", (), (0, 2)) # Vis. promoted - verify_assignable("M_IMP_N_VIS_BOOL", (), ()) - verify_assignable("M_IMP_Y_VIS_TRI", (0, 2), (0, 1, 2)) - verify_assignable("M_IMP_M_VIS_TRI", (), (0, 1)) - verify_assignable("M_IMP_N_VIS_TRI", (), ()) - - # Symbols in y-mode choice - verify_assignable("Y_CHOICE_BOOL", (2,), (2,)) - verify_assignable("Y_CHOICE_TRISTATE", (2,), (2,)) - verify_assignable("Y_CHOICE_N_VIS_TRISTATE", (), ()) - - # Symbols in m/y-mode choice, starting out in m mode, or y mode when - # running without modules - verify_assignable("MY_CHOICE_BOOL", (2,), ()) - verify_assignable("MY_CHOICE_TRISTATE", (2,), (0, 1)) - verify_assignable("MY_CHOICE_N_VIS_TRISTATE", (), ()) - - c.named_choices["MY_CHOICE"].set_value(2) - - # Symbols in m/y-mode choice, now in y mode - verify_assignable("MY_CHOICE_BOOL", (2,), (2,)) - verify_assignable("MY_CHOICE_TRISTATE", (2,), (2,)) - verify_assignable("MY_CHOICE_N_VIS_TRISTATE", (), ()) - - def verify_choice_assignable( - choice_name, assignable_no_modules, assignable_modules - ): - verify_assignable_imp( - c.named_choices[choice_name], assignable_no_modules, assignable_modules - ) - - # Choices with various possible modes - verify_choice_assignable("Y_CHOICE", (2,), (2,)) - verify_choice_assignable("MY_CHOICE", (2,), (1, 2)) - verify_choice_assignable("NMY_CHOICE", (0, 2), (0, 1, 2)) - verify_choice_assignable("NY_CHOICE", (0, 2), (0, 2)) - verify_choice_assignable("NM_CHOICE", (), (0, 1)) - verify_choice_assignable("M_CHOICE", (), (1,)) - verify_choice_assignable("N_CHOICE", (), ()) - - print("Testing object relations") - - c = Kconfig("Kconfiglib/tests/Krelation") - - verify( - c.syms["A"].nodes[0].parent is c.top_node, "A's parent should be the top node" - ) - - verify( - c.syms["B"].nodes[0].parent.item is c.named_choices["CHOICE_1"], - "B's parent should be the first choice", - ) - - verify( - c.syms["C"].nodes[0].parent.item is c.syms["B"], - "C's parent should be B (due to auto menus)", - ) - - verify(c.syms["E"].nodes[0].parent.item == MENU, "E's parent should be a menu") - - verify( - c.syms["E"].nodes[0].parent.parent is c.top_node, - "E's grandparent should be the top node", - ) - - verify( - c.syms["G"].nodes[0].parent.item is c.named_choices["CHOICE_2"], - "G's parent should be the second choice", - ) - - verify( - c.syms["G"].nodes[0].parent.parent.item == MENU, - "G's grandparent should be a menu", - ) - - print("Testing hex/int ranges") - - c = Kconfig("Kconfiglib/tests/Krange", warn=False) - - for sym_name in "HEX_NO_RANGE", "INT_NO_RANGE", "HEX_40", "INT_40": - sym = c.syms[sym_name] - verify(not sym.ranges, "{} should not have ranges".format(sym_name)) - - for sym_name in ( - "HEX_ALL_RANGES_DISABLED", - "INT_ALL_RANGES_DISABLED", - "HEX_RANGE_10_20_LOW_DEFAULT", - "INT_RANGE_10_20_LOW_DEFAULT", - ): - sym = c.syms[sym_name] - verify(sym.ranges, "{} should have ranges".format(sym_name)) - - # hex/int symbols without defaults should get no default value - verify_value("HEX_NO_RANGE", "") - verify_value("INT_NO_RANGE", "") - # And neither if all ranges are disabled - verify_value("HEX_ALL_RANGES_DISABLED", "") - verify_value("INT_ALL_RANGES_DISABLED", "") - # Make sure they are assignable though, and test that the form of the user - # value is reflected in the value for hex symbols - assign_and_verify("HEX_NO_RANGE", "0x123") - assign_and_verify("HEX_NO_RANGE", "123") - assign_and_verify("INT_NO_RANGE", "123") - - # Defaults outside of the valid range should be clamped - verify_value("HEX_RANGE_10_20_LOW_DEFAULT", "0x10") - verify_value("HEX_RANGE_10_20_HIGH_DEFAULT", "0x20") - verify_value("INT_RANGE_10_20_LOW_DEFAULT", "10") - verify_value("INT_RANGE_10_20_HIGH_DEFAULT", "20") - # Defaults inside the valid range should be preserved. For hex symbols, - # they should additionally use the same form as in the assignment. - verify_value("HEX_RANGE_10_20_OK_DEFAULT", "0x15") - verify_value("HEX_RANGE_10_20_OK_DEFAULT_ALTERNATE", "15") - verify_value("INT_RANGE_10_20_OK_DEFAULT", "15") - - # hex/int symbols with no defaults but valid ranges should default to the - # lower end of the range if it's > 0 - verify_value("HEX_RANGE_10_20", "0x10") - verify_value("HEX_RANGE_0_10", "") - verify_value("INT_RANGE_10_20", "10") - verify_value("INT_RANGE_0_10", "") - verify_value("INT_RANGE_NEG_10_10", "") - - # User values and dependent ranges - - # Avoid warnings for assigning values outside the active range - c.warn = False - - def verify_range(sym_name, low, high, default): - # Verifies that all values in the range 'low'-'high' can be assigned, - # and that assigning values outside the range reverts the value back to - # 'default' (None if it should revert back to ""). - - is_hex = c.syms[sym_name].type == HEX - - for i in range(low, high + 1): - assign_and_verify_user_value(sym_name, str(i), str(i), True) - if is_hex: - # The form of the user value should be preserved for hex - # symbols - assign_and_verify_user_value(sym_name, hex(i), hex(i), True) - - # Verify that assigning a user value just outside the range causes - # defaults to be used - - if default is None: - default_str = "" - else: - default_str = hex(default) if is_hex else str(default) - - if is_hex: - too_low_str = hex(low - 1) - too_high_str = hex(high + 1) - else: - too_low_str = str(low - 1) - too_high_str = str(high + 1) - - assign_and_verify_value(sym_name, too_low_str, default_str) - assign_and_verify_value(sym_name, too_high_str, default_str) - - verify_range("HEX_RANGE_10_20_LOW_DEFAULT", 0x10, 0x20, 0x10) - verify_range("HEX_RANGE_10_20_HIGH_DEFAULT", 0x10, 0x20, 0x20) - verify_range("HEX_RANGE_10_20_OK_DEFAULT", 0x10, 0x20, 0x15) - - verify_range("INT_RANGE_10_20_LOW_DEFAULT", 10, 20, 10) - verify_range("INT_RANGE_10_20_HIGH_DEFAULT", 10, 20, 20) - verify_range("INT_RANGE_10_20_OK_DEFAULT", 10, 20, 15) - - verify_range("HEX_RANGE_10_20", 0x10, 0x20, 0x10) - - verify_range("INT_RANGE_10_20", 10, 20, 10) - verify_range("INT_RANGE_0_10", 0, 10, None) - verify_range("INT_RANGE_NEG_10_10", -10, 10, None) - - # Dependent ranges - - verify_value("HEX_40", "40") - verify_value("INT_40", "40") - - c.syms["HEX_RANGE_10_20"].unset_value() - c.syms["INT_RANGE_10_20"].unset_value() - verify_value("HEX_RANGE_10_40_DEPENDENT", "0x10") - verify_value("INT_RANGE_10_40_DEPENDENT", "10") - c.syms["HEX_RANGE_10_20"].set_value("15") - c.syms["INT_RANGE_10_20"].set_value("15") - verify_value("HEX_RANGE_10_40_DEPENDENT", "0x15") - verify_value("INT_RANGE_10_40_DEPENDENT", "15") - c.unset_values() - verify_range("HEX_RANGE_10_40_DEPENDENT", 0x10, 0x40, 0x10) - verify_range("INT_RANGE_10_40_DEPENDENT", 10, 40, 10) - - # Ranges and symbols defined in multiple locations - - verify_value("INACTIVE_RANGE", "2") - verify_value("ACTIVE_RANGE", "1") - - print("Testing defconfig_filename") - - c = Kconfig("Kconfiglib/tests/empty") - verify( - c.defconfig_filename is None, - "defconfig_filename should be None with no defconfig_list symbol", - ) - - c = Kconfig("Kconfiglib/tests/Kdefconfig_nonexistent") - verify( - c.defconfig_filename is None, - "defconfig_filename should be None when none of the files in the " - "defconfig_list symbol exist", - ) - - # Referenced in Kdefconfig_existent(_but_n) - os.environ["FOO"] = "defconfig_2" - - c = Kconfig("Kconfiglib/tests/Kdefconfig_existent_but_n") - verify( - c.defconfig_filename is None, - "defconfig_filename should be None when the condition is n for all " - "the defaults", - ) - - c = Kconfig("Kconfiglib/tests/Kdefconfig_existent") - verify( - c.defconfig_filename == "Kconfiglib/tests/defconfig_2", - "defconfig_filename should return the existing file " - "Kconfiglib/tests/defconfig_2", - ) - - # Should also look relative to $srctree if the specified defconfig is a - # relative path and can't be opened - - c = Kconfig("Kconfiglib/tests/Kdefconfig_srctree") - verify( - c.defconfig_filename == "Kconfiglib/tests/defconfig_2", - "defconfig_filename gave wrong file with $srctree unset", - ) - - os.environ["srctree"] = "Kconfiglib/tests" - c = Kconfig("Kdefconfig_srctree") - verify( - c.defconfig_filename == "Kconfiglib/tests/sub/defconfig_in_sub", - "defconfig_filename gave wrong file with $srctree set", - ) - - os.environ.pop("srctree", None) - - print("Testing mainmenu_text") - - c = Kconfig("Kconfiglib/tests/empty") - verify( - c.mainmenu_text == "Main menu", - "An empty Kconfig should get a default main menu prompt", - ) - - # Expanded in the mainmenu text - os.environ["FOO"] = "bar baz" - c = Kconfig("Kconfiglib/tests/Kmainmenu") - verify(c.mainmenu_text == "---bar baz---", "Wrong mainmenu text") - - print("Testing user_value") - - # References undefined env. var. Disable warnings. - c = Kconfig("Kconfiglib/tests/Kmisc", warn=False) - - # Avoid warnings from assigning invalid user values and assigning user - # values to symbols without prompts - c.warn = False - - syms = [c.syms[name] for name in ("BOOL", "TRISTATE", "STRING", "INT", "HEX")] - - for sym in syms: - verify(sym.user_value is None, "{} should not have a user value to begin with") - - # Assign valid values for the types - - assign_and_verify_user_value("BOOL", 0, 0, True) - assign_and_verify_user_value("BOOL", 2, 2, True) - assign_and_verify_user_value("TRISTATE", 0, 0, True) - assign_and_verify_user_value("TRISTATE", 1, 1, True) - assign_and_verify_user_value("TRISTATE", 2, 2, True) - assign_and_verify_user_value("STRING", "foo bar", "foo bar", True) - assign_and_verify_user_value("INT", "123", "123", True) - assign_and_verify_user_value("HEX", "0x123", "0x123", True) - - # Assign invalid values for the types. They should retain their old user - # value. - - assign_and_verify_user_value("BOOL", 1, 2, False) - assign_and_verify_user_value("BOOL", "foo", 2, False) - assign_and_verify_user_value("BOOL", "1", 2, False) - assign_and_verify_user_value("TRISTATE", "foo", 2, False) - assign_and_verify_user_value("TRISTATE", "1", 2, False) - assign_and_verify_user_value("STRING", 0, "foo bar", False) - assign_and_verify_user_value("INT", "foo", "123", False) - assign_and_verify_user_value("INT", 0, "123", False) - assign_and_verify_user_value("HEX", "foo", "0x123", False) - assign_and_verify_user_value("HEX", 0, "0x123", False) - assign_and_verify_user_value("HEX", "-0x1", "0x123", False) - - for s in syms: - s.unset_value() - verify( - s.user_value is None, - "{} should not have a user value after being reset".format(s.name), - ) - - print("Testing is_menuconfig") - - c = Kconfig("Kconfiglib/tests/Kmenuconfig") - - for not_menuconfig in ( - c.syms["NOT_MENUCONFIG_1"].nodes[0], - c.syms["NOT_MENUCONFIG_2"].nodes[0], - c.syms["MENUCONFIG_MULTI_DEF"].nodes[0], - c.syms["COMMENT_HOOK"].nodes[0].next, - ): - - verify( - not not_menuconfig.is_menuconfig, - "'{}' should have is_menuconfig False".format(not_menuconfig), - ) - - for menuconfig in ( - c.top_node, - c.syms["MENUCONFIG_1"].nodes[0], - c.syms["MENUCONFIG_MULTI_DEF"].nodes[1], - c.syms["MENU_HOOK"].nodes[0].next, - c.syms["CHOICE_HOOK"].nodes[0].next, - ): - - verify( - menuconfig.is_menuconfig, - "'{}' should have is_menuconfig True".format(menuconfig), - ) - - print("Testing 'option env' semantics") - - os.environ["ENV_VAR"] = "ENV_VAR value" - - # References undefined env. var., so disable warnings - c = Kconfig("Kconfiglib/tests/Kmisc", warn=False) - - # Verify that 'option env' is treated like a default - verify_value("FROM_ENV", "ENV_VAR value") - verify_value("FROM_ENV_MISSING", "missing") - - verify_value("FROM_ENV_WEIRD", "weird") - - print("Testing defined vs undefined symbols") - - for name in "A", "B", "C", "D", "BOOL", "TRISTATE", "STRING", "INT", "HEX": - verify(c.syms[name].nodes, "{} should be defined".format(name)) - - for name in "NOT_DEFINED_1", "NOT_DEFINED_2", "NOT_DEFINED_3", "NOT_DEFINED_4": - sym = c.syms[name] - verify(not c.syms[name].nodes, "{} should not be defined".format(name)) - - print("Testing Symbol.choice") - - for name in "A", "B", "C", "D": - verify( - c.syms[name].choice is not None, "{} should be a choice symbol".format(name) - ) - - for name in ( - "Q1", - "Q2", - "Q3", - "BOOL", - "TRISTATE", - "STRING", - "INT", - "HEX", - "FROM_ENV", - "FROM_ENV_MISSING", - "NOT_DEFINED_1", - "NOT_DEFINED_2", - "NOT_DEFINED_3", - "NOT_DEFINED_4", - ): - verify( - c.syms[name].choice is None, "{} should not be a choice symbol".format(name) - ) - - print("Testing is_allnoconfig_y") - - verify( - not c.syms["NOT_ALLNOCONFIG_Y"].is_allnoconfig_y, - "NOT_ALLNOCONFIG_Y should not be allnoconfig_y", - ) - verify( - c.syms["ALLNOCONFIG_Y"].is_allnoconfig_y, - "ALLNOCONFIG_Y should be allnoconfig_y", - ) - - print("Testing .config reading and writing") - - config_test_file = "Kconfiglib/tests/config_test" - - def verify_file_contents(fname, contents): - with open(fname, "r") as f: - file_contents = f.read() - verify( - file_contents == contents, - "{} contains '{}'. Expected '{}'.".format( - fname, file_contents, contents - ), - ) - - # Writing/reading strings with characters that need to be escaped - - c = Kconfig("Kconfiglib/tests/Kescape") - - # Test the default value - c.write_config(config_test_file + "_from_def") - verify_file_contents( - config_test_file + "_from_def", r'''CONFIG_STRING="\"\\"''' "\n" - ) - # Write our own value - c.syms["STRING"].set_value(r"""\"a'\\""") - c.write_config(config_test_file + "_from_user") - verify_file_contents( - config_test_file + "_from_user", r'''CONFIG_STRING="\\\"a'\\\\"''' "\n" - ) - - # Read back the two configs and verify the respective values - c.load_config(config_test_file + "_from_def") - verify_value("STRING", '"\\') - c.load_config(config_test_file + "_from_user") - verify_value("STRING", r"""\"a'\\""") - - # Appending values from a .config - - c = Kconfig("Kconfiglib/tests/Kappend") - - # Values before assigning - verify_value("BOOL", "n") - verify_value("STRING", "") - - # Assign BOOL - c.load_config("Kconfiglib/tests/config_set_bool", replace=False) - verify_value("BOOL", "y") - verify_value("STRING", "") - - # Assign STRING - c.load_config("Kconfiglib/tests/config_set_string", replace=False) - verify_value("BOOL", "y") - verify_value("STRING", "foo bar") - - # Reset BOOL - c.load_config("Kconfiglib/tests/config_set_string") - verify_value("BOOL", "n") - verify_value("STRING", "foo bar") - - # Loading a completely empty .config should reset values - c.load_config("Kconfiglib/tests/empty") - verify_value("STRING", "") - - # An indented assignment in a .config should be ignored - c.load_config("Kconfiglib/tests/config_indented") - verify_value("IGNOREME", "y") - - # Symbol order in headers and minimal configuration files should match - # definition order, like in .config files - - c = Kconfig("Kconfiglib/tests/Korder") - - c.write_autoconf(config_test_file) - verify_file_contents( - config_test_file, - """ -#define CONFIG_O 0 -#define CONFIG_R 1 -#define CONFIG_D 2 -#define CONFIG_E 3 -#define CONFIG_R2 4 -#define CONFIG_I 5 -#define CONFIG_N 6 -#define CONFIG_G 7 -"""[1:], - ) - - # Differs from defaults - c.syms["O"].set_value("-1") - c.syms["R"].set_value("-1") - c.syms["E"].set_value("-1") - c.syms["R2"].set_value("-1") - c.syms["N"].set_value("-1") - c.syms["G"].set_value("-1") - c.write_min_config(config_test_file) - verify_file_contents( - config_test_file, - """ -CONFIG_O=-1 -CONFIG_R=-1 -CONFIG_E=-1 -CONFIG_R2=-1 -CONFIG_N=-1 -CONFIG_G=-1 -"""[1:], - ) - - # Test header strings in configuration files and headers - - os.environ["KCONFIG_CONFIG_HEADER"] = "config header from env.\n" - os.environ["KCONFIG_AUTOHEADER_HEADER"] = "header header from env.\n" - - c = Kconfig("Kconfiglib/tests/Kheader") - c.write_config(config_test_file, header="config header from param\n") - verify_file_contents( - config_test_file, - """\ -config header from param -CONFIG_FOO=y -""", - ) - c.write_min_config(config_test_file, header="min. config header from param\n") - verify_file_contents( - config_test_file, - """\ -min. config header from param -""", - ) - c.write_config(config_test_file) - verify_file_contents( - config_test_file, - """\ -config header from env. -CONFIG_FOO=y -""", - ) - c.write_min_config(config_test_file) - verify_file_contents( - config_test_file, - """\ -config header from env. -""", - ) - c.write_autoconf(config_test_file, header="header header from param\n") - verify_file_contents( - config_test_file, - """\ -header header from param -#define CONFIG_FOO 1 -""", - ) - c.write_autoconf(config_test_file) - verify_file_contents( - config_test_file, - """\ -header header from env. -#define CONFIG_FOO 1 -""", - ) - - del os.environ["KCONFIG_CONFIG_HEADER"] - del os.environ["KCONFIG_AUTOHEADER_HEADER"] - - print("Testing Kconfig fetching and separation") - - for c in Kconfig("Kconfiglib/tests/Kmisc", warn=False), Kconfig( - "Kconfiglib/tests/Kmisc", warn=False - ): - for item in ( - c.syms["BOOL"], - c.syms["BOOL"].nodes[0], - c.named_choices["OPTIONAL"], - c.named_choices["OPTIONAL"].nodes[0], - c.syms["MENU_HOOK"].nodes[0].next, - c.syms["COMMENT_HOOK"].nodes[0].next, - ): - verify(item.kconfig is c, ".kconfig not properly set for " + repr(item)) - - print("Testing imply semantics") - - c = Kconfig("Kconfiglib/tests/Kimply") - - verify_value("IMPLY_DIRECT_DEPS", "y") - verify_value("UNMET_DIRECT_1", "n") - verify_value("UNMET_DIRECT_2", "n") - verify_value("UNMET_DIRECT_3", "n") - verify_value("MET_DIRECT_1", "y") - verify_value("MET_DIRECT_2", "y") - verify_value("MET_DIRECT_3", "y") - verify_value("MET_DIRECT_4", "y") - - verify_value("IMPLY_COND", "y") - verify_value("IMPLIED_N_COND", "n") - verify_value("IMPLIED_M_COND", "m") - verify_value("IMPLIED_Y_COND", "y") - - verify_value("IMPLY_N_1", "n") - verify_value("IMPLY_N_2", "n") - verify_value("IMPLIED_FROM_N_1", "n") - verify_value("IMPLIED_FROM_N_2", "n") - - verify_value("IMPLY_M", "m") - verify_value("IMPLIED_M", "m") - verify_value("IMPLIED_M_BOOL", "y") - - verify_value("IMPLY_M_TO_Y", "y") - verify_value("IMPLIED_M_TO_Y", "y") - - # Test user value semantics - - # Verify that IMPLIED_TRISTATE is invalidated if the direct - # dependencies change - - assign_and_verify("IMPLY", 2) - assign_and_verify("DIRECT_DEP", 2) - verify_value("IMPLIED_TRISTATE", 2) - assign_and_verify("DIRECT_DEP", 0) - verify_value("IMPLIED_TRISTATE", 0) - # Set back for later tests - assign_and_verify("DIRECT_DEP", 2) - - # Verify that IMPLIED_TRISTATE can be set to anything when IMPLY has value - # n, and that it gets the value n by default (for non-imply-related - # reasons) - - assign_and_verify("IMPLY", 0) - assign_and_verify("IMPLIED_TRISTATE", 0) - assign_and_verify("IMPLIED_TRISTATE", 1) - assign_and_verify("IMPLIED_TRISTATE", 2) - c.syms["IMPLIED_TRISTATE"].unset_value() - verify_value("IMPLIED_TRISTATE", "n") - - # Same as above for m. Anything still goes, but m by default now. - - assign_and_verify("IMPLY", 1) - assign_and_verify("IMPLIED_TRISTATE", 0) - assign_and_verify("IMPLIED_TRISTATE", 1) - assign_and_verify("IMPLIED_TRISTATE", 2) - c.syms["IMPLIED_TRISTATE"].unset_value() - verify_value("IMPLIED_TRISTATE", 1) - - # Same as above for y. Only n and y should be accepted. m gets promoted to - # y. Default should be y. - - assign_and_verify("IMPLY", 2) - assign_and_verify("IMPLIED_TRISTATE", 0) - assign_and_verify_value("IMPLIED_TRISTATE", 1, 2) - assign_and_verify("IMPLIED_TRISTATE", 2) - c.syms["IMPLIED_TRISTATE"].unset_value() - verify_value("IMPLIED_TRISTATE", 2) - - # Being implied to either m or y should give a bool the value y - - c.syms["IMPLY"].unset_value() - verify_value("IMPLIED_BOOL", 0) - assign_and_verify("IMPLY", 0) - verify_value("IMPLIED_BOOL", 0) - assign_and_verify("IMPLY", 1) - verify_value("IMPLIED_BOOL", 2) - assign_and_verify("IMPLY", 2) - verify_value("IMPLIED_BOOL", 2) - - # A bool implied to m or y can take the values n and y - - c.syms["IMPLY"].set_value(1) - assign_and_verify("IMPLIED_BOOL", 0) - assign_and_verify("IMPLIED_BOOL", 2) - - c.syms["IMPLY"].set_value(2) - assign_and_verify("IMPLIED_BOOL", 0) - assign_and_verify("IMPLIED_BOOL", 2) - - print("Testing choice semantics") - - # Would warn for choice value symbols defined without a type, even - # though the type is automatically derived. This is probably more - # helpful than ignoring those cases, as this feature isn't used - # deliberately anywhere from what I've seen. - c = Kconfig("Kconfiglib/tests/Kchoice", warn=False) - - for name in "BOOL", "BOOL_OPT", "BOOL_M", "DEFAULTS": - verify( - c.named_choices[name].orig_type == BOOL, - "choice {} should have type bool".format(name), - ) - - for name in "TRISTATE", "TRISTATE_OPT", "TRISTATE_M": - verify( - c.named_choices[name].orig_type == TRISTATE, - "choice {} should have type tristate".format(name), - ) - - def select_and_verify(sym): - choice = sym.nodes[0].parent.item - choice.set_value(2) - - sym.set_value(2) - - verify(sym.choice.selection is sym, sym.name + " should be the selected symbol") - - verify( - choice.user_selection is sym, - sym.name + " should be the user selection of the choice", - ) - - verify(sym.tri_value == 2, sym.name + " should have value y when selected") - - verify( - sym.user_value == 2, sym.name + " should have user value y when selected" - ) - - for sibling in choice.syms: - if sibling is not sym: - verify( - sibling.tri_value == 0, - sibling.name + " should be n when not selected", - ) - - def select_and_verify_all(choice_name): - choice = c.named_choices[choice_name] - - # Select in forward order - for sym in choice.syms: - select_and_verify(sym) - - # Select in reverse order - for sym in reversed(choice.syms): - select_and_verify(sym) - - def verify_mode(choice_name, no_modules_mode, modules_mode): - choice = c.named_choices[choice_name] - - c.modules.set_value(0) - verify( - choice.tri_value == no_modules_mode, - "Wrong mode for choice {} with no modules. Expected {}, got {}.".format( - choice.name, no_modules_mode, choice.tri_value - ), - ) - - c.modules.set_value(2) - verify( - choice.tri_value == modules_mode, - "Wrong mode for choice {} with modules. Expected {}, got {}.".format( - choice.name, modules_mode, choice.tri_value - ), - ) - - verify_mode("BOOL", 2, 2) - verify_mode("BOOL_OPT", 0, 0) - verify_mode("TRISTATE", 2, 1) - verify_mode("TRISTATE_OPT", 0, 0) - verify_mode("BOOL_M", 0, 2) - verify_mode("TRISTATE_M", 0, 1) - - # Test defaults - - choice = c.named_choices["DEFAULTS"] - - c.syms["TRISTATE_SYM"].set_value(0) - verify( - choice.selection is c.syms["OPT_4"], - "Wrong choice default with TRISTATE_SYM = n", - ) - - c.syms["TRISTATE_SYM"].set_value(2) - verify( - choice.selection is c.syms["OPT_2"], - "Wrong choice default with TRISTATE_SYM = y", - ) - - c.syms["OPT_1"].set_value(2) - verify( - choice.selection is c.syms["OPT_1"], "User selection should override defaults" - ) - - verify( - c.named_choices["DEFAULTS_NOT_VISIBLE"].selection is c.syms["OPT_8"], - "Non-visible choice symbols should cause the next default to be " "considered", - ) - - # Test y mode selection - - c.modules.set_value(2) - - select_and_verify_all("BOOL") - select_and_verify_all("BOOL_OPT") - select_and_verify_all("TRISTATE") - select_and_verify_all("TRISTATE_OPT") - # For BOOL_M, the mode should have been promoted - select_and_verify_all("BOOL_M") - - # Test m mode selection - - c.named_choices["TRISTATE"].set_value(1) - - verify( - c.named_choices["TRISTATE"].tri_value == 1, - "TRISTATE choice should have mode m after explicit mode assignment", - ) - - assign_and_verify_value("T_1", 0, 0) - assign_and_verify_value("T_2", 0, 0) - assign_and_verify_value("T_1", 1, 1) - assign_and_verify_value("T_2", 1, 1) - assign_and_verify_value("T_1", 2, 1) - assign_and_verify_value("T_2", 2, 1) - - # Switching to y mode should cause T_2 to become selected - c.named_choices["TRISTATE"].set_value(2) - verify_value("T_1", 0) - verify_value("T_2", 2) - - # Verify that choices with no explicitly specified type get the type of the - # first contained symbol with a type - - verify( - c.named_choices["NO_TYPE_BOOL"].orig_type == BOOL, - "Expected first choice without explicit type to have type bool", - ) - - verify( - c.named_choices["NO_TYPE_TRISTATE"].orig_type == TRISTATE, - "Expected second choice without explicit type to have type " "tristate", - ) - - # Verify that symbols without a type in the choice get the type of the - # choice - - for name in "MMT_1", "MMT_2", "MMT_4", "MMT_5": - verify( - c.syms[name].orig_type == BOOL, "Expected {} to get type bool".format(name) - ) - - verify( - c.syms["MMT_3"].orig_type == TRISTATE, "Expected MMT_3 to have type tristate" - ) - - # Verify that the default selection can change depending on the - # visibility of the choice symbols - - default_with_dep_choice = c.named_choices["DEFAULT_WITH_DEP"] - - verify( - default_with_dep_choice.selection is c.syms["B"], - "Wrong choice default with unsatisfied deps on default", - ) - - c.syms["DEP"].set_value("y") - - verify( - default_with_dep_choice.selection is c.syms["A"], - "Wrong choice default with satisfied deps on default", - ) - - c.syms["DEP"].set_value("n") - - verify( - default_with_dep_choice.selection is c.syms["B"], - "Wrong choice default with unsatisfied deps on default (round two)", - ) - - # Verify that symbols in choices that depend on the preceding symbol aren't - # considered choice symbols - - weird_choice = c.named_choices["WEIRD_SYMS"] - - def verify_is_normal_choice_symbol(name): - sym = c.syms[name] - verify( - sym.choice is not None - and sym in weird_choice.syms - and sym.nodes[0].parent.item is weird_choice, - "{} should be a normal choice symbol".format(sym.name), - ) - - def verify_is_weird_choice_symbol(name): - sym = c.syms[name] - verify( - sym.choice is None and sym not in weird_choice.syms, - "{} should be a weird (non-)choice symbol".format(sym.name), - ) - - verify_is_normal_choice_symbol("WS1") - verify_is_weird_choice_symbol("WS2") - verify_is_weird_choice_symbol("WS3") - verify_is_weird_choice_symbol("WS4") - verify_is_weird_choice_symbol("WS5") - verify_is_normal_choice_symbol("WS6") - verify_is_weird_choice_symbol("WS7") - verify_is_weird_choice_symbol("WS8") - verify_is_normal_choice_symbol("WS9") - - print("Testing 'if' node removal") - - c = Kconfig("Kconfiglib/tests/Kifremoval", warn=False) - - nodes = tuple(c.node_iter()) - verify_equal(nodes[0].item.name, "A") - verify_equal(nodes[1].item.name, "B") - verify_equal(nodes[2].item.name, "C") - verify_equal(nodes[3].item.name, "D") - verify_equal(nodes[4].prompt[0], "E") - verify_equal(nodes[5].prompt[0], "F") - verify_equal(nodes[6].prompt[0], "G") - verify_equal(nodes[7].item.name, "H") - verify_equal(nodes[8].item.name, "I") - verify_equal(nodes[9].item.name, "J") - verify(len(nodes) == 10, "Wrong number of nodes after 'if' removal") - - print("Testing multi.def. property copying") - - c = Kconfig("Kconfiglib/tests/Kdepcopy", warn=False) - - def verify_props(desc, props, prop_names): - actual = [prop[0].name for prop in props] - expected = prop_names.split() - - verify( - actual == expected, - "Wrong {} properties, expected '{}', got '{}'".format( - desc, expected, actual - ), - ) - - verify_props( - "default", c.syms["MULTIDEF"].defaults, "A B C D E F G H I J K L M N O P Q R" - ) - - verify_props("select", c.syms["MULTIDEF"].selects, "AA BB CC DD EE FF GG HH II JJ") - - verify_props("imply", c.syms["MULTIDEF"].implies, "AA BB CC DD EE FF GG HH II JJ") - - verify_props("select", c.syms["MULTIDEF_CHOICE"].selects, "A B C") - - verify_props("range", c.syms["MULTIDEF_RANGE"].ranges, "A B C D E F") - - verify_props("default", c.choices[1].defaults, "A B C D E") - - print("Testing dependency loop detection") - - # These are all expected to raise dependency loop errors - for i in range(11): - filename = "Kconfiglib/tests/Kdeploop" + str(i) - try: - Kconfig(filename) - except KconfigError as e: - if "Dependency loop" not in str(e): - fail("dependency loop in {} raised wrong KconfigError".format(filename)) - except: - fail("dependency loop in {} raised wrong exception".format(filename)) - else: - fail("dependency loop in {} not detected".format(filename)) - - # Check the most complicated message completely - try: - Kconfig("Kconfiglib/tests/Kdeploop10") - except KconfigError as e: - verify_equal( - str(e), - """ -Dependency loop -=============== - -A (defined at Kconfiglib/tests/Kdeploop10:1), with definition... - -config A - bool - depends on B - -...depends on B (defined at Kconfiglib/tests/Kdeploop10:5), with definition... - -config B - bool - depends on C = 7 - -...depends on C (defined at Kconfiglib/tests/Kdeploop10:9), with definition... - -config C - int - range D 8 - -...depends on D (defined at Kconfiglib/tests/Kdeploop10:13), with definition... - -config D - int - default 3 if E - default 8 - -...depends on E (defined at Kconfiglib/tests/Kdeploop10:18), with definition... - -config E - bool - -(select-related dependencies: F && G) - -...depends on G (defined at Kconfiglib/tests/Kdeploop10:25), with definition... - -config G - bool - depends on H - -...depends on the choice symbol H (defined at Kconfiglib/tests/Kdeploop10:32), with definition... - -config H - bool "H" - depends on I && - -...depends on the choice symbol I (defined at Kconfiglib/tests/Kdeploop10:41), with definition... - -config I - bool "I" - depends on - -...depends on (defined at Kconfiglib/tests/Kdeploop10:38), with definition... - -choice - bool "choice" if J - -...depends on J (defined at Kconfiglib/tests/Kdeploop10:46), with definition... - -config J - bool - depends on A - -...depends again on A (defined at Kconfiglib/tests/Kdeploop10:1) -"""[:-1], - ) - except: - fail("Loop detection message check raised wrong exception") - else: - fail("Loop detection message check did not raise exception") - - print("Testing preprocessor") - - os.environ["ENV_1"] = "env_1" - os.environ["ENV_2"] = "env_2" - os.environ["ENV_3"] = "env_3" - os.environ["ENV_4"] = "env_4" - os.environ["ENV_5"] = "n" - os.environ["ENV_6"] = "Kconfiglib/tests/empty" - os.environ["ENV_7"] = "env_7" - # We verify warnings manually - c = Kconfig("Kconfiglib/tests/Kpreprocess", warn_to_stderr=False) - - def verify_variable(name, unexp_value, exp_value, recursive, *args): - var = c.variables[name] - - verify( - var.value == unexp_value, - "expected variable '{}' to have the unexpanded value '{}', had " - "the value '{}'".format(name, unexp_value, var.value), - ) - - if not args: - verify( - var.expanded_value == exp_value, - "expected expanded_value for {} to be '{}', was '{}'".format( - name, exp_value, var.expanded_value - ), - ) - - verify( - var.expanded_value_w_args(*args) == exp_value, - "expected expanded_value_w_args() for '{}' to be '{}', was '{}'".format( - name, exp_value, var.expanded_value_w_args(*args) - ), - ) - - verify( - var.is_recursive == recursive, - "{} was {}, shouldn't be".format( - name, "recursive" if var.is_recursive else "simple" - ), - ) - - verify_variable("simple-recursive", "foo", "foo", True) - verify_variable("simple-immediate", "bar", "bar", False) - verify_variable("simple-recursive-2", "baz", "baz", True) - - verify_variable("whitespaced", "foo", "foo", True) - - verify_variable("preserve-recursive", "foo bar", "foo bar", True) - verify_variable("preserve-immediate", "foo bar", "foo bar", False) - - verify_variable( - "recursive", - "$(foo) $(bar) $($(b-char)a$(z-char)) $(indir)", - "abc def ghi jkl mno", - True, - ) - - verify_variable("immediate", "foofoo", "foofoo", False) - - verify_variable( - "messy-fn-res", - "$($(fn-indir)-unused-arg, a b (,) , c d )", - 'surround-rev-quote " c d " " a b (,) " surround-rev-quote ', - True, - ) - - verify_variable( - "special-chars-fn-res", - "$(fn,$(comma)$(dollar)$(left-paren)foo$(right-paren))", - '",$(foo)"', - True, - ) - - verify_variable("quote", '"$(1)" "$(2)"', '"" ""', True) - verify_variable("quote", '"$(1)" "$(2)"', '"one" ""', True, "one") - verify_variable("quote", '"$(1)" "$(2)"', '"one" "two"', True, "one", "two") - verify_variable( - "quote", '"$(1)" "$(2)"', '"one" "two"', True, "one", "two", "three" - ) - - verify_str( - c.syms["PRINT_ME"], - r""" -config PRINT_ME - string "env_1" if (FOO && BAR) || !BAZ || !QAZ - default "\"foo\"" if "foo \"bar\" baz" = "" -""", - ) - - verify_str( - c.syms["PRINT_ME_TOO"], - r""" -config PRINT_ME_TOO - bool "foo" - default FOOBARBAZQAZ if QAZ && QAZFOO && xxx -""", - ) - - def verify_repr(name, s): - verify_equal(repr(c.variables[name]), s) - - verify_repr( - "simple-immediate", "" - ) - - verify_repr( - "messy-fn-res", - "", - ) - - def verify_recursive(name): - try: - c.variables[name].expanded_value - except KconfigError: - pass - else: - fail( - "Expected '{}' expansion to flag recursive expansion, didn't".format( - name - ) - ) - - verify_recursive("rec-1") - # Indirectly verifies that it's not recursive - verify_variable("safe-fn-rec-res", "$(safe-fn-rec,safe-fn-rec-2)", "foo", True) - verify_recursive("unsafe-fn-rec") - - verify_variable("foo-bar-baz", "$(rhs)", "value", True) - - verify_variable("space-var-res", "$(foo bar)", "value", True) - - verify_variable( - "shell-res", "$(shell,false && echo foo bar || echo baz qaz)", "baz qaz", True - ) - - verify_variable("shell-stderr-res", "", "", False) - - verify_variable( - "parens-res", - "pre-$(shell,echo '(a,$(b-char),(c,d),e)')-post", - "pre-(a,b,(c,d),e)-post", - True, - ) - - verify_variable( - "location-res", - "Kconfiglib/tests/Kpreprocess:129", - "Kconfiglib/tests/Kpreprocess:129", - False, - ) - - verify_variable("warning-res", "", "", False) - verify_variable("error-n-res", "", "", False) - - try: - c.variables["error-y-res"].expanded_value - except KconfigError: - pass - else: - fail("expanding error-y-res didn't raise an exception") - - # Check Kconfig.env_vars - verify_equal( - c.env_vars, set(("ENV_1", "ENV_2", "ENV_3", "ENV_4", "ENV_5", "ENV_6")) - ) - - # Check that the expected warnings were generated - verify_equal( - c.warnings, - [ - "Kconfiglib/tests/Kpreprocess:122: warning: 'echo message on stderr >&2' wrote to stderr: message on stderr", - "Kconfiglib/tests/Kpreprocess:134: warning: a warning", - ], - ) - - print("Testing user-defined preprocessor functions") - - # Make Kconfiglib/tests/kconfigfunctions.py importable - sys.path.insert(0, "Kconfiglib/tests") - - c = Kconfig("Kconfiglib/tests/Kuserfunctions") - - verify_variable("add-zero", "$(add)", "0", True) - verify_variable("add-one", "$(add,1)", "1", True) - verify_variable("add-three", "$(add,1,-1,2,1)", "3", True) - - verify_variable("one-one", "$(one,foo bar)", "onefoo barfoo bar", True) - - verify_variable("one-or-more-one", "$(one-or-more,foo)", "foo + ", True) - verify_variable( - "one-or-more-three", "$(one-or-more,foo,bar,baz)", "foo + bar,baz", True - ) - - verify_variable( - "location-1", - "Kconfiglib/tests/Kuserfunctions:13", - "Kconfiglib/tests/Kuserfunctions:13", - False, - ) - verify_variable( - "location-2", - "Kconfiglib/tests/Kuserfunctions:14", - "Kconfiglib/tests/Kuserfunctions:14", - False, - ) - - def verify_bad_argno(name): - try: - c.variables[name].expanded_value - except KconfigError: - pass - else: - fail( - "Expected '{}' expansion to flag wrong number of arguments, " - "didn't".format(name) - ) - - verify_bad_argno("one-zero") - verify_bad_argno("one-two") - verify_bad_argno("one-or-more-zero") - - sys.path.pop(0) - - print("Testing Kbuild toolchain test functions") - - c = Kconfig("Kconfiglib/tests/Kbuild_functions") - - # Test basic success/failure functions - verify_value("TEST_SUCCESS", "y") - verify_value("TEST_FAILURE", "y") - verify_value("TEST_IF_SUCCESS", "y") - - # Test compiler flag detection - verify_value("CC_HAS_WALL", "y") - verify_value("CC_HAS_WERROR", "y") - - # Test invalid options return 'n' - verify_value("TEST_INVALID_OPTION", "n") - verify_value("TEST_FAILURE_TRUE", "n") - - # Test assembler support - verify_value("AS_HAS_NOP", "y") - - # Test nested function calls - verify_value("TEST_NESTED_SUCCESS_SHELL", "y") - - print("Testing KCONFIG_WARN_UNDEF") - - os.environ["KCONFIG_WARN_UNDEF"] = "y" - c = Kconfig("Kconfiglib/tests/Kundef", warn_to_stderr=False) - - verify_equal( - "\n".join(c.warnings), - """ -warning: the int symbol INT (defined at Kconfiglib/tests/Kundef:8) has a non-int range [UNDEF_2 (undefined), 8 (undefined)] -warning: undefined symbol UNDEF_1: - -- Referenced at Kconfiglib/tests/Kundef:4: - -config BOOL - bool "foo" if DEF || !UNDEF_1 - default UNDEF_2 - -- Referenced at Kconfiglib/tests/Kundef:19: - -menu "menu" - depends on UNDEF_1 - visible if UNDEF_3 -warning: undefined symbol UNDEF_2: - -- Referenced at Kconfiglib/tests/Kundef:4: - -config BOOL - bool "foo" if DEF || !UNDEF_1 - default UNDEF_2 - -- Referenced at Kconfiglib/tests/Kundef:8: - -config INT - int - range UNDEF_2 8 - range 5 15 - default 10 -warning: undefined symbol UNDEF_3: - -- Referenced at Kconfiglib/tests/Kundef:19: - -menu "menu" - depends on UNDEF_1 - visible if UNDEF_3 -"""[1:-1], - ) - - os.environ.pop("KCONFIG_WARN_UNDEF") - - print("\nAll selftests passed\n" if all_passed else "\nSome selftests failed\n") - - -def run_compatibility_tests(): - # Runs tests on configurations from the kernel. Tests compability with the - # C implementation by comparing outputs. - - # Referenced inside the kernel Kconfig files. - os.environ["KERNELVERSION"] = str( - subprocess.check_output("make kernelversion", shell=True) - .decode("utf-8") - .rstrip() - ) - - os.environ["CC_VERSION_TEXT"] = str( - subprocess.check_output("gcc --version | head -n1", shell=True) - .decode("utf-8") - .rstrip() - ) - - os.environ["srctree"] = "." - os.environ["CC"] = "gcc" - os.environ["LD"] = "ld" - - if not os.path.exists("scripts/kconfig/conf"): - print( - "\nscripts/kconfig/conf does not exist -- running " - "'make allnoconfig' to build it..." - ) - shell("make allnoconfig") - - print("Running compatibility tests...\n") - - test_fns = ( - test_defconfig, - # Fails for a few defconfigs due to a bug in the C tools. Will - # be enabled once patches get in. - # test_min_config, - test_alldefconfig, - test_allnoconfig, - test_allnoconfig_walk, - test_allmodconfig, - test_allyesconfig, - test_sanity, - ) - - for test_fn in test_fns: - # The test description is taken from the docstring of the corresponding - # function - print(textwrap.dedent(test_fn.__doc__)) - - for arch, srcarch in all_arch_srcarch(): - # Referenced inside the Kconfig files - os.environ["ARCH"] = arch - os.environ["SRCARCH"] = srcarch - - rm_configs() - - test_fn(arch, srcarch) - - if all_passed: - print("All selftests and compatibility tests passed") - else: - sys.exit("Some tests failed") - - -def all_arch_srcarch(): - for srcarch in os.listdir("arch"): - # arc and h8300 are currently broken with the C tools on linux-next as - # well. Perhaps they require cross-compilers to be installed. - # - # User-mode Linux has an unorthodox Kconfig setup that would require a - # different testing setup. Skip it too. - if srcarch in ("arc", "h8300", "um"): - continue - - if os.path.exists(os.path.join("arch", srcarch, "Kconfig")): - yield (srcarch, srcarch) - - # Some arches define additional ARCH settings with ARCH != SRCARCH - # (search for "Additional ARCH settings for" in the top-level Makefile) - - yield ("i386", "x86") - yield ("x86_64", "x86") - - yield ("sparc32", "sparc") - yield ("sparc64", "sparc") - - yield ("sh64", "sh") - - -def run_conf_and_compare(script, conf_flag, arch): - # Runs a Kconfiglib script via 'make scriptconfig', then runs the C - # implementation with 'conf_flag', and compares the resulting .config files. - - shell( - "make scriptconfig SCRIPT={} " "PYTHONCMD='{}'".format(script, sys.executable) - ) - shell("mv .config ._config") - shell("scripts/kconfig/conf --{} Kconfig".format(conf_flag)) - compare_configs(arch) - - -def test_allnoconfig(arch, srcarch): - """ - Verify that allnoconfig.py generates the same .config as - 'make allnoconfig', for each architecture. Runs the script via - 'make scriptconfig'. - """ - run_conf_and_compare("Kconfiglib/allnoconfig.py", "allnoconfig", arch) - - -def test_allnoconfig_walk(arch, srcarch): - """ - Verify that examples/allnoconfig_walk.py generates the same .config as - 'make allnoconfig', for each architecture. Runs the script via - 'make scriptconfig'. - """ - run_conf_and_compare("Kconfiglib/examples/allnoconfig_walk.py", "allnoconfig", arch) - - -def test_allmodconfig(arch, srcarch): - """ - Verify that allmodconfig.py generates the same .config as - 'make allmodconfig', for each architecture. Runs the script via - 'make scriptconfig'. - """ - run_conf_and_compare("Kconfiglib/allmodconfig.py", "allmodconfig", arch) - - -def test_allyesconfig(arch, srcarch): - """ - Verify that allyesconfig.py generates the same .config as - 'make allyesconfig', for each architecture. Runs the script via - 'make scriptconfig'. - """ - run_conf_and_compare("Kconfiglib/allyesconfig.py", "allyesconfig", arch) - - -def test_sanity(arch, srcarch): - """ - Do sanity checks on each configuration and call all public methods on all - symbols, choices, and menu nodes for all architectures to make sure we - never crash or hang. - """ - print("For {}...".format(arch)) - - kconf = Kconfig() - - for sym in kconf.defined_syms: - verify( - sym._visited == 2, - "{} has broken dependency loop detection (_visited = {})".format( - sym.name, sym._visited - ), - ) - - kconf.modules - kconf.defconfig_list - kconf.defconfig_filename - - # Legacy warning functions - kconf.enable_redun_warnings() - kconf.disable_redun_warnings() - kconf.enable_undef_warnings() - kconf.disable_undef_warnings() - kconf.enable_warnings() - kconf.disable_warnings() - kconf.enable_stderr_warnings() - kconf.disable_stderr_warnings() - - kconf.mainmenu_text - kconf.unset_values() - - kconf.write_autoconf("/dev/null") - - tmpdir = tempfile.mkdtemp() - kconf.sync_deps(os.path.join(tmpdir, "deps")) # Create - kconf.sync_deps(os.path.join(tmpdir, "deps")) # Update - shutil.rmtree(tmpdir) - - for key, sym in kconf.syms.items(): - verify(isinstance(key, str), "weird key '{}' in syms dict".format(key)) - - verify(not sym.is_constant, sym.name + " in 'syms' and constant") - - verify( - sym not in kconf.const_syms, sym.name + " in both 'syms' and 'const_syms'" - ) - - for dep in sym._dependents: - verify( - not dep.is_constant, - "the constant symbol {} depends on {}".format(dep.name, sym.name), - ) - - sym.__repr__() - sym.__str__() - sym.assignable - kconf.disable_warnings() - sym.set_value(2) - sym.set_value("foo") - sym.unset_value() - kconf.enable_warnings() # Legacy warning function - sym.str_value - sym.tri_value - sym.type - sym.user_value - sym.visibility - - for sym in kconf.defined_syms: - verify(sym.nodes, sym.name + " is defined but lacks menu nodes") - - verify( - not (sym.orig_type not in (BOOL, TRISTATE) and sym.choice), - sym.name + " is a choice symbol but not bool/tristate", - ) - - for key, sym in kconf.const_syms.items(): - verify(isinstance(key, str), "weird key '{}' in const_syms dict".format(key)) - - verify( - sym.is_constant, - '"{}" is in const_syms but not marked constant'.format(sym.name), - ) - - verify(not sym.nodes, '"{}" is constant but has menu nodes'.format(sym.name)) - - verify( - not sym._dependents, - '"{}" is constant but is a dependency of some symbol'.format(sym.name), - ) - - verify(not sym.choice, '"{}" is constant and a choice symbol'.format(sym.name)) - - sym.__repr__() - sym.__str__() - sym.assignable - kconf.disable_warnings() - sym.set_value(2) - sym.set_value("foo") - sym.unset_value() - kconf.enable_warnings() # Legacy warning function - sym.str_value - sym.tri_value - sym.type - sym.visibility - - for choice in kconf.choices: - for sym in choice.syms: - verify( - sym.choice is choice, - "{0} is in choice.syms but 'sym.choice' is not the choice".format( - sym.name - ), - ) - - verify( - sym.type in (BOOL, TRISTATE), - "{} is a choice symbol but is not a bool/tristate".format(sym.name), - ) - - choice.__str__() - choice.__repr__() - choice.str_value - choice.tri_value - choice.user_value - choice.assignable - choice.selection - choice.type - choice.visibility - - # Menu nodes - - node = kconf.top_node - - while 1: - # Everything else should be well exercised elsewhere - node.__repr__() - node.__str__() - verify( - isinstance(node.item, (Symbol, Choice)) or node.item in (MENU, COMMENT), - "'{}' appeared as a menu item".format(node.item), - ) - - if node.list is not None: - node = node.list - - elif node.next is not None: - node = node.next - - else: - while node.parent is not None: - node = node.parent - if node.next is not None: - node = node.next - break - else: - break - - -def test_alldefconfig(arch, srcarch): - """ - Verify that alldefconfig.py generates the same .config as - 'make alldefconfig', for each architecture. Runs the script via - 'make scriptconfig'. - """ - run_conf_and_compare("Kconfiglib/alldefconfig.py", "alldefconfig", arch) - - -def test_defconfig(arch, srcarch): - """ - Verify that Kconfiglib generates the same .config as scripts/kconfig/conf, - for each architecture/defconfig pair. In obsessive mode, this test includes - nonsensical groupings of arches with defconfigs from other arches (every - arch/defconfig combination) and takes an order of magnitude longer time to - run. - - With logging enabled, this test appends any failures to a file - test_defconfig_fails in the root. - """ - kconf = Kconfig() - - if obsessive: - defconfigs = [] - - # Collect all defconfigs. This could be done once instead, but it's - # a speedy operation comparatively. - for srcarch_ in os.listdir("arch"): - defconfigs.extend(defconfig_files(srcarch_)) - else: - defconfigs = defconfig_files(srcarch) - - # Test architecture for each defconfig - - for defconfig in defconfigs: - rm_configs() - - kconf.load_config(defconfig) - kconf.write_config("._config") - shell("scripts/kconfig/conf --defconfig='{}' Kconfig".format(defconfig)) - - arch_defconfig_str = " {:14}with {:60} ".format(arch, defconfig) - - if equal_configs(): - print(arch_defconfig_str + "OK") - else: - print(arch_defconfig_str + "FAIL") - fail() - if log: - with open("test_defconfig_fails", "a") as fail_log: - fail_log.write("{} with {} did not match\n".format(arch, defconfig)) - - -def test_min_config(arch, srcarch): - """ - Verify that Kconfiglib generates the same .config as 'make savedefconfig' - for each architecture/defconfig pair. - """ - kconf = Kconfig() - - if obsessive_min_config: - defconfigs = [] - for srcarch_ in os.listdir("arch"): - defconfigs.extend(defconfig_files(srcarch_)) - else: - defconfigs = defconfig_files(srcarch) - - for defconfig in defconfigs: - rm_configs() - - kconf.load_config(defconfig) - kconf.write_min_config("._config") - - shell("cp {} .config".format(defconfig)) - - shell("scripts/kconfig/conf --savedefconfig=.config Kconfig") - - arch_defconfig_str = " {:14}with {:60} ".format(arch, defconfig) - - if equal_configs(): - print(arch_defconfig_str + "OK") - else: - print(arch_defconfig_str + "FAIL") - - -# -# Helper functions -# - - -def defconfig_files(srcarch): - # Yields a list of defconfig file filenames for a particular srcarch - # subdirectory (arch//) - - srcarch_dir = os.path.join("arch", srcarch) - - # Some arches have a defconfig in the root of their arch// directory - root_defconfig = os.path.join(srcarch_dir, "defconfig") - if os.path.exists(root_defconfig): - yield root_defconfig - - # Assume all files in the arch//configs/ directory (if it exists) are - # configurations - defconfigs_dir = os.path.join(srcarch_dir, "configs") - - if not os.path.isdir(defconfigs_dir): - return - - for dirpath, _, filenames in os.walk(defconfigs_dir): - for filename in filenames: - yield os.path.join(dirpath, filename) - - -def rm_configs(): - # Delete any old ".config" (generated by the C implementation) and - # "._config" (generated by us), if present. - - for f in ".config", "._config": - if os.path.exists(f): - os.remove(f) - - -def compare_configs(arch): - if equal_configs(): - print("{:14}OK".format(arch)) - else: - print("{:14}FAIL".format(arch)) - fail() - - -def equal_configs(): - with open(".config") as f: - their = f.readlines() - - # Strip the header generated by 'conf'. Stop at the first non-comment - # line, or at a "# CONFIG_... is not set" comment (which is config data). - for i, line in enumerate(their): - if not line.startswith("#") or re.match(r"# CONFIG_(\w+) is not set", line): - break - else: - i = len(their) - their = their[i:] - - try: - with open("._config") as f: - our = f.readlines() - except EnvironmentError as e: - if e.errno != errno.ENOENT: - raise - print("._config not found. Did you forget to apply the Makefile patch?") - return False - - if their == our: - return True - - # Print a unified diff to help debugging - print("Mismatched .config's! Unified diff:") - sys.stdout.writelines( - difflib.unified_diff(their, our, fromfile="their", tofile="our") - ) - - return False - - -if __name__ == "__main__": - run_tests()