# 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 configuation (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 to ulfalizer a.t Google's email # service. from kconfiglib import Kconfig, Symbol, Choice, COMMENT, MENU, MenuNode, \ BOOL, TRISTATE, HEX, STRING, \ TRI_TO_STR, \ escape, unescape, \ expr_str, expr_value, expr_items, split_expr, \ _ordered_unique, \ OR, AND, \ KconfigError import difflib import errno import os import platform import re import shutil import subprocess import sys import tempfile import textwrap 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, 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.disable_warnings() 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 prompt "basic" """) verify_str(c.syms["ADVANCED"], """ config ADVANCED tristate prompt "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 "prompt 2" menuconfig ADVANCED tristate prompt "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 """) verify_custom_str(c.syms["ADVANCED"], """ config ADVANCED tristate prompt "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 "prompt 2" menuconfig ADVANCED tristate prompt "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 """) 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 prompt "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 "prompt 1" if LOC_1 default DEFAULT_1 if LOC_1 default DEFAULT_2 if LOC_1 select SELECT_1 if LOC_1 select SELECT_2 if LOC_1 imply IMPLY_1 if LOC_1 imply IMPLY_2 if LOC_1 depends on LOC_1 help help 1 menuconfig CORRECT_PROP_LOCS_BOOL bool prompt "prompt 2" if LOC_2 default DEFAULT_3 if LOC_2 default DEFAULT_4 if LOC_2 select SELECT_3 if LOC_2 select SELECT_4 if LOC_2 imply IMPLY_3 if LOC_2 imply IMPLY_4 if LOC_2 depends on LOC_2 help help 2 config CORRECT_PROP_LOCS_BOOL bool prompt "prompt 3" if LOC_3 default DEFAULT_5 if LOC_3 default DEFAULT_6 if LOC_3 select SELECT_5 if LOC_3 select SELECT_6 if LOC_3 imply IMPLY_5 if LOC_3 imply IMPLY_6 if LOC_3 depends on LOC_3 help help 2 """) verify_str(c.syms["CORRECT_PROP_LOCS_INT"], """ config CORRECT_PROP_LOCS_INT int range 1 2 if LOC_1 range 3 4 if LOC_1 depends on LOC_1 config CORRECT_PROP_LOCS_INT int range 5 6 if LOC_2 range 7 8 if LOC_2 depends on LOC_2 """) verify_custom_str(c.syms["CORRECT_PROP_LOCS_INT"], """ config CORRECT_PROP_LOCS_INT int range [1] [2] if [LOC_1] range [3] [4] if [LOC_1] depends on [LOC_1] config CORRECT_PROP_LOCS_INT int range [5] [6] if [LOC_2] range [7] [8] if [LOC_2] depends on [LOC_2] """) print("Testing Choice.__str__()/custom_str()") verify_str(c.named_choices["CHOICE"], """ choice CHOICE tristate prompt "foo" default CHOICE_1 default CHOICE_2 if dep """) verify_str(c.named_choices["CHOICE"].nodes[0].next.item, """ choice tristate prompt "no name" optional """) verify_str(c.named_choices["CORRECT_PROP_LOCS_CHOICE"], """ choice CORRECT_PROP_LOCS_CHOICE bool default CHOICE_3 if LOC_1 depends on LOC_1 choice CORRECT_PROP_LOCS_CHOICE bool default CHOICE_4 if LOC_2 depends on LOC_2 choice CORRECT_PROP_LOCS_CHOICE bool default CHOICE_5 if LOC_3 depends on LOC_3 """) verify_custom_str(c.named_choices["CORRECT_PROP_LOCS_CHOICE"], """ choice CORRECT_PROP_LOCS_CHOICE bool default [CHOICE_3] if [LOC_1] depends on [LOC_1] choice CORRECT_PROP_LOCS_CHOICE bool default [CHOICE_4] if [LOC_2] depends on [LOC_2] choice CORRECT_PROP_LOCS_CHOICE bool default [CHOICE_5] if [LOC_3] 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 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) verify_repr(c.syms["VISIBLE"], """ """) 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.enable_warnings() c.disable_stderr_warnings() c.disable_redun_warnings() c.enable_undef_warnings() 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, 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["SINGLE_DEF"].nodes, "tests/Klocation:4") verify_locations(c.syms["MULTI_DEF"].nodes, "tests/Klocation:7", "tests/Klocation:37", "tests/Klocation:39", "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:72") verify_locations(c.named_choices["CHOICE"].nodes, "tests/Klocation_sourced:5") verify_locations([c.syms["MENU_HOOK"].nodes[0].next], "tests/Klocation_sourced:12") verify_locations([c.syms["COMMENT_HOOK"].nodes[0].next], "tests/Klocation_sourced:18") # 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") 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. c = Kconfig("tests/Klocation", warn=False) verify_equal( [node.item.name for node in c.node_iter() if isinstance(node.item, Symbol)], ["SINGLE_DEF", "MULTI_DEF", "HELP_1", "HELP_2", "HELP_3", "MULTI_DEF", "MULTI_DEF", "MULTI_DEF", "MENU_HOOK", "COMMENT_HOOK"] + \ 10*["MULTI_DEF"]) verify_equal( [node.item.name for node in c.node_iter(True) if isinstance(node.item, Symbol)], ["SINGLE_DEF", "MULTI_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)], ["choice", "menu", "comment"]) verify_equal( [node.prompt[0] for node in c.node_iter(True) if not isinstance(node.item, Symbol)], ["choice", "menu", "comment"]) # Get rid of custom 'srctree' from Klocation test os.environ.pop("srctree", None) 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 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.disable_warnings() 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.disable_warnings() def verify_range(sym_name, low, high, default): """ Tests that the values in the range 'low'-'high' can be assigned, and that assigning values outside this 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.disable_warnings() 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", header="") 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", header="") 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, header="") 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, header="") 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:]) 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 = [node for node in 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"].selects, "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 prompt "H" if I && depends on I && ...depends on the choice symbol I (defined at Kconfiglib/tests/Kdeploop10:41), with definition... config I bool prompt "I" if depends on ...depends on (defined at Kconfiglib/tests/Kdeploop10:38), with definition... choice bool prompt "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 prompt "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 prompt "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("location-res", "Kconfiglib/tests/Kpreprocess:125", "Kconfiglib/tests/Kpreprocess:125", 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:130: 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) 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) # This test can fail on older Python 3.x versions, because they don't # preserve dict insertion order during iteration. The output is still # correct, just different. if not (3, 0) <= sys.version_info <= (3, 5): 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 prompt "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 prompt "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. # # The str() makes the type of the value 'str' on both Python 2 and Python 3, # which is nice for some later dictionary key sanity checks. 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 test_allnoconfig(arch, srcarch): """ Verify that allnoconfig.py generates the same .config as 'make allnoconfig', for each architecture. Runs the script via 'make scriptconfig'. """ shell("make scriptconfig SCRIPT=Kconfiglib/allnoconfig.py " "PYTHONCMD='{}'".format(sys.executable)) shell("mv .config ._config") shell("scripts/kconfig/conf --allnoconfig Kconfig") compare_configs(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'. """ shell("make scriptconfig SCRIPT=Kconfiglib/examples/allnoconfig_walk.py " "PYTHONCMD='{}'".format(sys.executable)) shell("mv .config ._config") shell("scripts/kconfig/conf --allnoconfig Kconfig") compare_configs(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'. """ shell("make scriptconfig SCRIPT=Kconfiglib/allmodconfig.py " "PYTHONCMD='{}'".format(sys.executable)) shell("mv .config ._config") shell("scripts/kconfig/conf --allmodconfig Kconfig") compare_configs(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'. """ shell("make scriptconfig SCRIPT=Kconfiglib/allyesconfig.py " "PYTHONCMD='{}'".format(sys.executable)) shell("mv .config ._config") shell("scripts/kconfig/conf --allyesconfig Kconfig") compare_configs(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 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") # No tempfile.TemporaryDirectory in Python 2 tmpdir = tempfile.mkdtemp() kconf.sync_deps(os.path.join(tmpdir, "deps")) # Create kconf.sync_deps(os.path.join(tmpdir, "deps")) # Update shutil.rmtree(tmpdir) # Python 2/3 compatible 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() 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() 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'. """ shell("make scriptconfig SCRIPT=Kconfiglib/alldefconfig.py " "PYTHONCMD='{}'".format(sys.executable)) shell("mv .config ._config") shell("scripts/kconfig/conf --alldefconfig Kconfig") compare_configs(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. """ def rm_if_exists(f): if os.path.exists(f): os.remove(f) rm_if_exists(".config") rm_if_exists("._config") 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' i = 0 for line in their: if not line.startswith("#") or \ re.match(r"# CONFIG_(\w+) is not set", line): break i += 1 their = their[i:] try: f = open("._config") except IOError as e: if e.errno != errno.ENOENT: raise print("._config not found. Did you forget to apply the Makefile patch?") return False else: with f: # [1:] strips the default header our = f.readlines()[1:] 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()