From dd0e227216e247d2040cdd40bf7397702880cdc4 Mon Sep 17 00:00:00 2001 From: Ulf Magnusson Date: Mon, 9 Oct 2017 23:05:00 +0200 Subject: Kconfiglib 2 backup WIP --- testsuite.py | 2371 +++++++++++++++++++++++----------------------------------- 1 file changed, 920 insertions(+), 1451 deletions(-) (limited to 'testsuite.py') diff --git a/testsuite.py b/testsuite.py index 11822ce..69eae6e 100644 --- a/testsuite.py +++ b/testsuite.py @@ -1,45 +1,40 @@ -# This is a test suite for Kconfiglib. 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 +# 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 +# $ python Kconfiglib/testsuite.py # -# Some additional options can be turned on by passing arguments. With no argument, -# they default to off. +# Some additional options can be turned on by passing them as arguments. They +# default to off. # # - speedy: -# Run scripts/kconfig/conf directly when comparing outputs instead of using -# 'make' targets. Makes things a lot faster, but could break if Kconfig -# files start depending on additional environment variables besides ARCH and -# SRCARCH. (These would be set in the Makefiles in that case.) Safe as of -# Linux 4.1.0-rc8. +# Run scripts/kconfig/conf directly instead of using 'make' targets. Makes +# things a lot faster, but could break if Kconfig files start referencing +# additional environment variables beyond ARCH, SRCARCH, and KERNELVERSION. +# Safe as of Linux 4.14-rc3. # # - obsessive: -# By default, only valid arch/defconfig pairs will be tested. With this -# enabled, every arch will be tested with every defconfig, which increases -# the test time by an order of magnitude. Occasionally finds (usually very -# obscure) bugs, and I make sure everything passes with it. +# 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. # # - log: -# Log timestamped failures of the defconfig test to test_defconfig_fails in -# the root. Especially handy in obsessive mode. +# Log timestamped defconfig test failures to the file test_defconfig_fails. +# Handy in obsessive mode. # -# For example, to run in speedy mode with logging, run +# For example, this commands runs the test suite in speedy mode with logging +# enabled: # -# $ python Kconfiglib/testsuite.py speedy log +# $ python(3) Kconfiglib/testsuite.py speedy log # -# (PyPy also works, and runs the defconfig tests roughly 20% faster on my -# machine. Some of the other tests get an even greater speed-up.) -# -# The tests have been roughly arranged in order of time needed. +# 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 __future__ import print_function - import difflib import errno import kconfiglib @@ -51,101 +46,179 @@ import sys import textwrap import time -speedy_mode = False -obsessive_mode = False -log_mode = False +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)) # Assign this to avoid warnings from Kconfiglib. Nothing in the kernel's # Kconfig files seems to actually look at the value as of 3.7.0-rc8. This is # only relevant for the test suite, as this will get set by the kernel Makefile # when using (i)scriptconfig. -os.environ["KERNELVERSION"] = "3.7.0" +os.environ["KERNELVERSION"] = "1" # Prevent accidental loading of configuration files by removing # KCONFIG_ALLCONFIG from the environment os.environ.pop("KCONFIG_ALLCONFIG", None) +speedy = False +obsessive = False +log = False + # Number of arch/defconfig pairs tested so far nconfigs = 0 def run_tests(): - global speedy_mode, obsessive_mode, log_mode + global speedy, obsessive, log for s in sys.argv[1:]: if s == "speedy": - speedy_mode = True + speedy = True print("Speedy mode enabled") elif s == "obsessive": - obsessive_mode = True + obsessive = True print("Obsessive mode enabled") elif s == "log": - log_mode = True + log = True print("Log mode enabled") else: print("Unrecognized option '{}'".format(s)) - return run_selftests() run_compatibility_tests() -def run_selftests(): - """Runs tests on specific configurations provided by us.""" +def get_items(config, type_): + items = [] + def rec(node): + if node is not None: + if isinstance(node.item, type_): + items.append(node.item) + rec(node.list) + rec(node.next) + rec(config.top_menu) + return items + +def get_comments(config): + items = [] + def rec(node): + if node is not None: + if node.item == kconfiglib.COMMENT: + items.append(node) + rec(node.list) + rec(node.next) + rec(config.top_menu) + return items + +def get_menus(config): + items = [] + def rec(node): + if node is not None: + if node.item == kconfiglib.MENU: + items.append(node) + rec(node.list) + rec(node.next) + rec(config.top_menu) + return items + +def get_choices(config): + choices = get_items(config, kconfiglib.Choice) + unique_choices = [] + for choice in choices: + if choice not in unique_choices: + unique_choices.append(choice) + return unique_choices + +def get_parent(item): + if isinstance(item, (kconfiglib.Symbol, kconfiglib.Choice)): + if not item.nodes: + return None + return item.nodes[0].parent.item + return item.node.parent.item + +def get_prompts(item): + prompts = [] + for node in item.nodes: + if node.prompt is not None: + prompts.append(node.prompt[0]) + return prompts +def run_selftests(): # - # Helper functions + # 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.""" - sym = c[sym_name] - sym_val = sym.get_value() - verify(sym_val == val, - "{} should have the value '{}' but has the value '{}'" - .format(sym_name, val, sym_val)) - - def assign_and_verify_new_value(sym_name, user_val, new_val): - """Assigns a user value to the symbol and verifies the new value.""" - sym = c[sym_name] - sym_old_val = sym.get_value() - sym.set_user_value(user_val) - sym_new_val = sym.get_value() - verify(sym_new_val == new_val, - "{} should have the new value '{}' after being assigned the " - "user value '{}'. Instead, the value is '{}'. The old " - "value was '{}'." - .format(sym_name, new_val, user_val, sym_new_val, sym_old_val)) + """ + Verifies that a symbol has a particular value. + """ + sym = c.syms[sym_name] + verify(sym.value == val, + 'expected {} to have the value "{}", had the value "{}"' + .format(sym_name, val, sym.value)) + + def assign_and_verify_value(sym_name, val, new_val): + """ + Assigns 'val' to a symbol and verifies that its value becomes + 'new_val'. + """ + sym = c.syms[sym_name] + old_val = sym.value + sym.set_value(val) + verify(sym.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.value, old_val)) def assign_and_verify(sym_name, user_val): - """Like assign_and_verify_new_value(), with the expected value being - the value just set.""" - assign_and_verify_new_value(sym_name, user_val, 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_new_user_value(sym_name, user_val, new_user_val): + def assign_and_verify_user_value(sym_name, val, user_val): """Assigns a user value to the symbol and verifies the new user value.""" - sym = c[sym_name] - sym_old_user_val = sym.get_user_value() - sym.set_user_value(user_val) - sym_new_user_val = sym.get_user_value() - verify(sym_new_user_val == new_user_val, + sym = c.syms[sym_name] + sym_old_user_val = sym.user_value + sym.set_value(val) + verify(sym.user_value == user_val, "{} should have the user value '{}' after being assigned " "the user value '{}'. Instead, the new user value was '{}'. " "The old user value was '{}'." - .format(sym_name, new_user_val, user_val, sym_new_user_val, + .format(sym_name, user_val, user_val, sym.user_value, sym_old_user_val)) - print("Running selftests...\n") + # + # Selftests + # - print("Testing tristate comparisons...") + print("Testing tristate comparisons") - def verify_truth_table(comp_fn, *bools): - bools_list = list(bools) - for (x, y) in (("n", "n"), ("n", "m"), ("n", "y"), - ("m", "n"), ("m", "m"), ("m", "y"), - ("y", "n"), ("y", "m"), ("y", "y")): - expected = bools_list.pop(0) + def verify_truth_table(comp_fn, *table): + for (x, y), expected in zip((("n", "n"), ("n", "m"), ("n", "y"), + ("m", "n"), ("m", "m"), ("m", "y"), + ("y", "n"), ("y", "m"), ("y", "y")), + table): verify(comp_fn(x, y) == expected, - "Expected {} on ('{}', '{}') to be {}". + "expected {} on ('{}', '{}') to be '{}'". format(comp_fn, x, y, expected)) verify_truth_table(kconfiglib.tri_less, @@ -168,23 +241,23 @@ def run_selftests(): True, True, False, True, True, True) - # - # String literal lexing. (This tests an internal API.) - # - print("Testing string literal (constant symbol) lexing...") + print("Testing string literal (constant symbol) lexing") + # Dummy empty configuration just to get a Config object c = kconfiglib.Config("Kconfiglib/tests/empty") def verify_string_lex(s, res): - """Verifies that the string token 'res' is produced from lexing 's'. - Strips the first and last characters from 's' so we can use readable - raw strings as input.""" + """ + Verifies that the string (constant symbol) token 'res' is produced from + lexing 's'. Strips the first and last characters from 's' so that + readable raw strings can be used as input + """ s = s[1:-1] - s_res = c._tokenize(s, for_eval = True).get_next() - verify(s_res == res, - "'{}' produced the string token '{}'. Expected '{}'." - .format(s, s_res, res)) + token = c._tokenize(s, for_eval = True).next() + verify(token == res, + "expected {} to produced the string token {}, produced {}" + .format(s, token, res)) verify_string_lex(r""" "" """, "") verify_string_lex(r""" '' """, "") @@ -215,16 +288,18 @@ def run_selftests(): verify_string_lex(r""" '\a\\"\b\c\'"d' """, "a\\\"bc'\"d") def verify_string_bad(s): - """Verifies that tokenizing 's' throws a Kconfig_Syntax_Error. Strips - the first and last characters from 's' so we can use readable raw - strings as input.""" + """ + Verifies that tokenizing 's' throws a KconfigSyntaxError. Strips the + first and last characters from 's' so we can use readable raw strings + as input. + """ s = s[1:-1] try: c._tokenize(s, for_eval = True) - except kconfiglib.Kconfig_Syntax_Error: + except kconfiglib.KconfigSyntaxError: pass else: - fail("Tokenization of '{}' should have failed.".format(s)) + fail("expected tokenization of {} to fail, didn't".format(s)) verify_string_bad(r""" " """) verify_string_bad(r""" ' """) @@ -235,115 +310,18 @@ def run_selftests(): verify_string_bad(r""" "foo """) verify_string_bad(r""" 'foo """) - # - # is_modifiable() - # - - print("Testing is_modifiable() and range queries...") - - c = kconfiglib.Config("Kconfiglib/tests/Kmodifiable") - - for sym_name in ("VISIBLE", "TRISTATE_SELECTED_TO_M", "VISIBLE_STRING", - "VISIBLE_INT", "VISIBLE_HEX"): - sym = c[sym_name] - verify(sym.is_modifiable(), - "{} should be modifiable".format(sym_name)) - - for sym_name in ("n", "m", "y", "NOT_VISIBLE", "SELECTED_TO_Y", - "BOOL_SELECTED_TO_M", "M_VISIBLE_TRISTATE_SELECTED_TO_M", - "NOT_VISIBLE_STRING", "NOT_VISIBLE_INT", "NOT_VISIBLE_HEX"): - sym = c[sym_name] - verify(not sym.is_modifiable(), - "{} should not be modifiable".format(sym_name)) - - # - # get_lower/upper_bound() and get_assignable_values() - # - - c = kconfiglib.Config("Kconfiglib/tests/Kbounds") - - def verify_bounds(sym_name, low, high): - sym = c[sym_name] - sym_low = sym.get_lower_bound() - sym_high = sym.get_upper_bound() - verify(sym_low == low and sym_high == high, - "Incorrectly calculated bounds for {}: {}-{}. " - "Expected {}-{}.".format(sym_name, sym_low, sym_high, - low, high)) - # See that we get back the corresponding range from - # get_assignable_values() - if sym_low is None: - vals = sym.get_assignable_values() - verify(vals == [], - "get_assignable_values() thinks there should be assignable " - "values for {} ({}) but not get_lower/upper_bound()". - format(sym_name, vals)) - if sym.get_type() in (kconfiglib.BOOL, kconfiglib.TRISTATE): - verify(not sym.is_modifiable(), - "get_lower_bound() thinks there should be no " - "assignable values for the bool/tristate {} but " - "is_modifiable() thinks it should be modifiable". - format(sym_name)) - else: - tri_to_int = { "n" : 0, "m" : 1, "y" : 2 } - bound_range = ["n", "m", "y"][tri_to_int[sym_low] : - tri_to_int[sym_high] + 1] - assignable_range = sym.get_assignable_values() - verify(bound_range == assignable_range, - "get_lower/upper_bound() thinks the range for {} should " - "be {} while get_assignable_values() thinks it should be " - "{}".format(sym_name, bound_range, assignable_range)) - if sym.get_type() in (kconfiglib.BOOL, kconfiglib.TRISTATE): - verify(sym.is_modifiable(), - "get_lower/upper_bound() thinks the range for the " - "bool/tristate {} should be {} while is_modifiable() " - "thinks the symbol should not be modifiable". - format(sym_name, bound_range)) - - verify_bounds("n", None, None) - verify_bounds("m", None, None) - verify_bounds("y", None, None) - verify_bounds("Y_VISIBLE_BOOL", "n", "y") - verify_bounds("Y_VISIBLE_TRISTATE", "n", "y") - verify_bounds("M_VISIBLE_BOOL", "n", "y") - verify_bounds("M_VISIBLE_TRISTATE", "n", "m") - verify_bounds("Y_SELECTED_BOOL", None, None) - verify_bounds("M_SELECTED_BOOL", None, None) - verify_bounds("Y_SELECTED_TRISTATE", None, None) - verify_bounds("M_SELECTED_TRISTATE", "m", "y") - verify_bounds("M_SELECTED_M_VISIBLE_TRISTATE", None, None) - verify_bounds("N_IMPLIED_BOOL", "n", "y") - verify_bounds("N_IMPLIED_TRISTATE", "n", "y") - verify_bounds("M_IMPLIED_BOOL", "n", "y") - verify_bounds("M_IMPLIED_TRISTATE", "n", "y") - verify_bounds("Y_IMPLIED_BOOL", "n", "y") - verify_bounds("Y_IMPLIED_TRISTATE", "n", "y") - verify_bounds("STRING", None, None) - verify_bounds("INT", None, None) - verify_bounds("HEX", None, None) + # TODO: Kmodifiable gone, test assignable - # - # eval() - # - print("Testing eval()...") + print("Testing expression evaluation") c = kconfiglib.Config("Kconfiglib/tests/Keval") def verify_eval(expr, val): - res = c.eval(expr) + res = c.eval_string(expr) verify(res == val, "'{}' evaluated to {}, expected {}".format(expr, res, val)) - def verify_eval_bad(expr): - try: - c.eval(expr) - except kconfiglib.Kconfig_Syntax_Error: - pass - else: - fail('eval("{}") should throw Kconfig_Syntax_Error' - .format(expr)) - # No modules verify_eval("n", "n") verify_eval("m", "n") @@ -352,8 +330,9 @@ def run_selftests(): verify_eval("'m'", "n") verify_eval("'y'", "y") verify_eval("M", "y") + # Modules - c["MODULES"].set_user_value("y") + c.syms["MODULES"].set_value("y") verify_eval("n", "n") verify_eval("m", "m") verify_eval("y", "y") @@ -370,6 +349,7 @@ def run_selftests(): # As are all constants besides "y" and "m" verify_eval('"foo"', "n") verify_eval('"foo" || "bar"', "n") + verify_eval('"foo" || m', "m") # Test equality for symbols @@ -398,7 +378,6 @@ def run_selftests(): verify_eval("N != Y", "y") verify_eval("M != Y", "y") - # string/int/hex verify_eval("Y_STRING = y", "y") verify_eval("Y_STRING = 'y'", "y") verify_eval('FOO_BAR_STRING = "foo bar"', "y") @@ -413,12 +392,13 @@ def run_selftests(): verify_eval("HEX_0X37 = '0x037'", "y") verify_eval("HEX_0X37 = '0x0037'", "y") - # Compare some constants... + # Constant symbol comparisons verify_eval('"foo" != "bar"', "y") verify_eval('"foo" = "bar"', "n") verify_eval('"foo" = "foo"', "y") + # Undefined symbols get their name as their value - c.set_print_warnings(False) + c.disable_warnings() verify_eval("'not_defined' = not_defined", "y") verify_eval("not_defined_2 = not_defined_2", "y") verify_eval("not_defined_1 != not_defined_2", "y") @@ -505,9 +485,18 @@ def run_selftests(): verify_eval("oops > INT_37", "n") verify_eval("oops >= INT_37", "n") + def verify_eval_bad(expr): + try: + c.eval_string(expr) + except kconfiglib.KconfigSyntaxError: + pass + else: + fail('expected eval_string("{}") to throw KconfigSyntaxError, ' \ + 'didn\'t'.format(expr)) + # The C implementation's parser can be pretty lax about syntax. Kconfiglib # sometimes needs to emulate that. Verify that some bad stuff throws - # Kconfig_Syntax_Error at least. + # KconfigSyntaxError at least. verify_eval_bad("") verify_eval_bad("&") verify_eval_bad("|") @@ -521,641 +510,498 @@ def run_selftests(): verify_eval_bad("X ||") verify_eval_bad("|| X") - # - # Text queries - # - print("Testing text queries...") + print("Testing Symbol.__str__()") + + def verify_str(item, s): + verify_equal(str(item), s[1:]) + + c = kconfiglib.Config("Kconfiglib/tests/Kstr", warn=False) + + 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 + prompt "prompt 2" + +menuconfig ADVANCED + prompt "prompt 3" if DEP2 + +config ADVANCED + help + second help text +""") + + 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 +""") - def verify_print(o, s): - verify_equals(str(o), textwrap.dedent(s[1:])) + # We still hardcode the modules symbol. Otherwise OPTIONS would have made + # more sense as a name here. + verify_str(c.syms["MODULES"], """ +config MODULES + option modules +""") - for var in ("ARCH", "SRCARCH", "srctree"): - os.environ.pop(var, None) + verify_str(c.syms["OPTIONS"], """ +config OPTIONS + option allnoconfig_y + option defconfig_list + option env="ENV" +""") - # The tests below aren't meant to imply that the format is set in stone. - # It's just to verify that the strings do not change unexpectedly. + print("Testing Choice.__str__()") - # Printing of Config + verify_str(c.named_choices["CHOICE"], """ +choice CHOICE + tristate + prompt "foo" + default CHOICE_1 + default CHOICE_2 if dep +""") - c = kconfiglib.Config("Kconfiglib/tests/Ktext") + verify_str(c.named_choices["CHOICE"].nodes[0].next.item, """ +choice + tristate + prompt "no name" +""") - verify_print(c, """ - Configuration - File : Kconfiglib/tests/Ktext - Base directory : . - Value of $ARCH at creation time : (not set) - Value of $SRCARCH at creation time : (not set) - Value of $srctree at creation time : (not set) - Most recently loaded .config : (no .config loaded) - Print warnings : True - Print assignments to undefined symbols : False""") - os.environ["ARCH"] = "foo" - os.environ["SRCARCH"] = "bar" - os.environ["srctree"] = "baz" + print("Testing Symbol.__repr__()") + + def verify_repr(item, s): + verify_equal(repr(item) + "\n", s[1:]) + + c = kconfiglib.Config("Kconfiglib/tests/Krepr", warn=False) + + verify_repr(c.syms["UNDEFINED"], """ + +""") + + verify_repr(c.syms["BASIC"], """ + +""") - c = kconfiglib.Config("Kconfiglib/tests/Ktext", base_dir="foobar") - c.load_config("Kconfiglib/tests/empty") - c.set_print_warnings(False) - c.set_print_undef_assign(True) - - choice_print, choice_no_help, choice_empty_help, choice_help = \ - c.get_choices() - - verify_print(c, """ - Configuration - File : Kconfiglib/tests/Ktext - Base directory : foobar - Value of $ARCH at creation time : foo - Value of $SRCARCH at creation time : bar - Value of $srctree at creation time : baz - Most recently loaded .config : Kconfiglib/tests/empty - Print warnings : False - Print assignments to undefined symbols : True""") - - # Printing of Symbol - - verify_print(c["BASIC"], """ - Symbol BASIC - Type : bool - Value : "n" - User value : (no user value) - Visibility : "n" - Is choice item : False - Is defined : True - Is from env. : False - Is special : False - Prompts: - (no prompts) - Default values: - (no default values) - Selects: - (no selects) - Implies: - (no implies) - Reverse (select-related) dependencies: - (no reverse dependencies) - Weak reverse (imply-related) dependencies: - (no weak reverse dependencies) - Additional dependencies from enclosing menus and ifs: - (no additional dependencies) - Locations: Kconfiglib/tests/Ktext:1""") - - c["ADVANCED"].set_user_value("m") - - verify_print(c["ADVANCED"], """ - Symbol ADVANCED - Type : tristate - Value : "y" - User value : "m" - Visibility : "y" - Is choice item : False - Is defined : True - Is from env. : False - Is special : False - Prompts: - "advanced prompt 1" if y || BASIC && BASIC (value: "y") - "advanced prompt 2" - Default values: - y (value: "y") - Condition: BASIC && !BASIC (value: "n") - n (value: "n") - Condition: BASIC = DUMMY && X < Y && X <= Y && X > Y && X >= Y (value: "n") - Selects: - SELECTED_1 if BASIC && DUMMY (value: "n") - SELECTED_2 if !(DUMMY || BASIC) (value: "y") - Implies: - IMPLIED_1 if BASIC || DUMMY (value: "n") - IMPLIED_2 if !(DUMMY && BASIC) (value: "y") - Reverse (select-related) dependencies: - SELECTING_1 && BASIC || SELECTING_2 && !BASIC (value: "n") - Weak reverse (imply-related) dependencies: - IMPLYING_1 && DUMMY || IMPLYING_2 && !DUMMY (value: "n") - Additional dependencies from enclosing menus and ifs: - !BASIC && !BASIC (value: "y") - Locations: Kconfiglib/tests/Ktext:6 Kconfiglib/tests/Ktext:15""") - - verify_print(c["STRING"], """ - Symbol STRING - Type : string - Value : "foo" - User value : (no user value) - Visibility : "n" - Is choice item : False - Is defined : True - Is from env. : False - Is special : False - Prompts: - (no prompts) - Default values: - "foo" - Condition: (none) - "bar" - Condition: BAR (value: "n") - STRING2 (value: "baz") - Condition: BAZ (value: "n") - Selects: - (no selects) - Implies: - (no implies) - Reverse (select-related) dependencies: - (no reverse dependencies) - Weak reverse (imply-related) dependencies: - (no weak reverse dependencies) - Additional dependencies from enclosing menus and ifs: - !BASIC && !BASIC (value: "y") - Locations: Kconfiglib/tests/Ktext:18""") - - verify_print(c["HAS_RANGES"], """ - Symbol HAS_RANGES - Type : int - Value : "1" - User value : (no user value) - Visibility : "y" - Is choice item : False - Is defined : True - Is from env. : False - Is special : False - Ranges: - [1, 2] if !DUMMY (value: "y") - [INT, INT] if DUMMY (value: "n") - [123, 456] - Prompts: - "ranged" - Default values: - (no default values) - Selects: - (no selects) - Implies: - (no implies) - Reverse (select-related) dependencies: - (no reverse dependencies) - Weak reverse (imply-related) dependencies: - (no weak reverse dependencies) - Additional dependencies from enclosing menus and ifs: - (no additional dependencies) - Locations: Kconfiglib/tests/Ktext:45""") - - # Printing of Choice - - verify_print(choice_print, """ - Choice - Name (for named choices): (no name) - Type : bool - Selected symbol : CHOICE_ITEM_1 - User value : (no user value) - Mode : "y" - Visibility : "y" - Optional : False - Prompts: - "choice" - Defaults: - (no default values) - Choice symbols: - CHOICE_ITEM_1 CHOICE_ITEM_2 CHOICE_ITEM_3 - Additional dependencies from enclosing menus and ifs: - (no additional dependencies) - Locations: Kconfiglib/tests/Ktext:51""") - - c["CHOICE_ITEM_2"].set_user_value("y") - - verify_print(choice_print, """ - Choice - Name (for named choices): (no name) - Type : bool - Selected symbol : CHOICE_ITEM_2 - User value : CHOICE_ITEM_2 - Mode : "y" - Visibility : "y" - Optional : False - Prompts: - "choice" - Defaults: - (no default values) - Choice symbols: - CHOICE_ITEM_1 CHOICE_ITEM_2 CHOICE_ITEM_3 - Additional dependencies from enclosing menus and ifs: - (no additional dependencies) - Locations: Kconfiglib/tests/Ktext:51""") - - # Printing of Menu - - verify_print(c.get_menus()[0], """ - Menu - Title : simple menu - 'depends on' dependencies : (no dependencies) - 'visible if' dependencies : (no dependencies) - Additional dependencies from enclosing menus and ifs: - (no additional dependencies) - Location: Kconfiglib/tests/Ktext:63""") - - verify_print(c.get_menus()[1], """ - Menu - Title : advanced menu - 'depends on' dependencies : !BASIC (value: "y") - 'visible if' dependencies : !DUMMY (value: "y") - Additional dependencies from enclosing menus and ifs: - !DUMMY (value: "y") - Location: Kconfiglib/tests/Ktext:67""") - - # Printing of Comment - - verify_print(c.get_comments()[0], """ - Comment - Text: simple comment - Dependencies: (no dependencies) - Additional dependencies from enclosing menus and ifs: - (no additional dependencies) - Location: Kconfiglib/tests/Ktext:73""") - - verify_print(c.get_comments()[1], """ - Comment - Text: advanced comment - Dependencies: !BASIC (value: "y") - Additional dependencies from enclosing menus and ifs: - !DUMMY (value: "y") - Location: Kconfiglib/tests/Ktext:76""") - - verify_equals(c["NO_HELP"].get_help(), None) - verify_equals(choice_no_help.get_help(), None) - verify_equals(c["EMPTY_HELP"].get_help(), "") - verify_equals(choice_empty_help.get_help(), "") - verify_equals(c["HELP_TERMINATED_BY_COMMENT"].get_help(), "a\nb\nc\n") - verify_equals(c["TRICKY_HELP"].get_help(), - "a\n b\n c\n\n d\n e\n f\n\n\ng\n h\n i\n") - verify_equals(c["S"].get_help(), "help for\nS\n") - verify_equals(choice_help.get_help(), "help for\nC\n") - - verify_equals(c["S"].get_name(), "S") - verify_equals(c.get_comments()[2].get_text(), "a comment") - verify_equals(c.get_menus()[2].get_title(), "a menu") + verify_repr(c.syms["VISIBLE"], """ + +""") - # - # Prompt queries - # + verify_repr(c.syms["DIR_DEP_N"], """ + +""") - print("Testing prompt queries...") + verify_repr(c.syms["OPTIONS"], """ + +""") - def verify_prompts(sym_or_choice, prompts): - sym_or_choice_prompts = sym_or_choice.get_prompts() - verify(len(sym_or_choice_prompts) == len(prompts), - "Wrong number of prompts for " + sym_or_choice.get_name()) - for i in range(0, len(sym_or_choice_prompts)): - verify(sym_or_choice_prompts[i] == prompts[i], - "Prompt {} wrong for {}: Was '{}', should be '{}'". - format(i, sym_or_choice.get_name(), sym_or_choice_prompts[i], - prompts[i])) + verify_repr(c.syms["MULTI_DEF"], """ + +""") - def verify_sym_prompts(sym_name, *prompts): - verify_prompts(c[sym_name], prompts) + verify_repr(c.syms["CHOICE_1"], """ + +""") - def verify_choice_prompts(choice, *prompts): - verify_prompts(choice, prompts) + verify_repr(c.syms["MODULES"], """ + +""") - c = kconfiglib.Config("Kconfiglib/tests/Kprompt") - # Symbols - verify_sym_prompts("NO_PROMPT") - verify_sym_prompts("SINGLE_PROMPT_1", "single prompt 1") - verify_sym_prompts("SINGLE_PROMPT_2", "single prompt 2") - verify_sym_prompts("MULTI_PROMPT", "prompt 1", "prompt 2", "prompt 3", "prompt 4") - no_prompt_choice, single_prompt_1_choice, single_prompt_2_choice, multi_prompt_choice = \ - c.get_choices() + print("Testing Choice.__repr__()") - # Choices - verify_choice_prompts(no_prompt_choice) - verify_choice_prompts(single_prompt_1_choice, "single prompt 1 choice") - verify_choice_prompts(single_prompt_2_choice, "single prompt 2 choice") - verify_choice_prompts(multi_prompt_choice, - "prompt 1 choice", "prompt 2 choice", "prompt 3 choice") + verify_repr(c.named_choices["CHOICE"], """ + +""") - # - # Location queries - # + c.named_choices["CHOICE"].set_value("y") + + verify_repr(c.named_choices["CHOICE"], """ + +""") + + c.syms["CHOICE_2"].set_value("y") + + 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 Config.__repr__()") + + verify_repr(c, """ + +""") + + os.environ["srctree"] = "srctree value" + os.environ["CONFIG_"] = "CONFIG_ value" + + c = kconfiglib.Config("Kconfiglib/tests/Krepr", warn=False) + c.enable_warnings() + c.enable_undef_warnings() + + verify_repr(c, """ + +""") + + os.environ.pop("srctree", None) + os.environ.pop("CONFIG_", None) + + + print("Testing tricky help strings") + + c = kconfiglib.Config("Kconfiglib/tests/Khelp") + + def verify_help(node, s): + verify_equal(node.help, s[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 location queries...") - def verify_def_locations(sym_name, *locs): - sym_locs = c[sym_name].get_def_locations() - verify(len(sym_locs) == len(locs), - "Wrong number of def. locations for " + sym_name) - for i in range(0, len(sym_locs)): - verify(sym_locs[i] == locs[i], - "Wrong def. location for {}: Was {}, should be {}". - format(sym_name, sym_locs[i], locs[i])) + print("Testing locations and 'source'") + + 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["FOO"] = "tests" - - c = kconfiglib.Config("Kconfiglib/tests/Klocation", base_dir="Kconfiglib/") - - verify_def_locations("n") - verify_def_locations("m") - verify_def_locations("y") - - verify_def_locations("A", - ("Kconfiglib/tests/Klocation", 4), - ("Kconfiglib/tests/Klocation", 28), - ("Kconfiglib/tests/Klocation_included", 1), - ("Kconfiglib/tests/Klocation_included", 3)) - verify_def_locations("C", - ("Kconfiglib/tests/Klocation", 18)) - verify_def_locations("M", - ("Kconfiglib/tests/Klocation_included", 6)) - verify_def_locations("N", - ("Kconfiglib/tests/Klocation_included", 19)) - verify_def_locations("O", - ("Kconfiglib/tests/Klocation_included", 21)) - verify_def_locations("NOT_DEFINED") # No locations - - def verify_ref_locations(sym_name, *locs): - sym_locs = c[sym_name].get_ref_locations() - verify(len(sym_locs) == len(locs), - "Wrong number of ref. locations for " + sym_name) - for i in range(0, len(sym_locs)): - verify(sym_locs[i] == locs[i], - "Wrong ref. location for {}: Was {}, should be {}". - format(sym_name, sym_locs[i], locs[i])) - - # Reload without the slash at the end of 'base_dir' to get coverage for - # that as well - c = kconfiglib.Config("Kconfiglib/tests/Klocation", base_dir="Kconfiglib") - - verify_ref_locations("A", - ("Kconfiglib/tests/Klocation", 10), - ("Kconfiglib/tests/Klocation", 12), - ("Kconfiglib/tests/Klocation", 16), - ("Kconfiglib/tests/Klocation", 34), - ("Kconfiglib/tests/Klocation", 35), - ("Kconfiglib/tests/Klocation_included", 7), - ("Kconfiglib/tests/Klocation_included", 8), - ("Kconfiglib/tests/Klocation_included", 9), - ("Kconfiglib/tests/Klocation_included", 12), - ("Kconfiglib/tests/Klocation_included", 13), - ("Kconfiglib/tests/Klocation_included", 14), - ("Kconfiglib/tests/Klocation_included", 15), - ("Kconfiglib/tests/Klocation_included", 35), - ("Kconfiglib/tests/Klocation_included", 40), - ("Kconfiglib/tests/Klocation", 65), - ("Kconfiglib/tests/Klocation", 66), - ("Kconfiglib/tests/Klocation", 67), - ("Kconfiglib/tests/Klocation", 68), - ("Kconfiglib/tests/Klocation", 69), - ("Kconfiglib/tests/Klocation", 70), - ("Kconfiglib/tests/Klocation", 71), - ("Kconfiglib/tests/Klocation", 72), - ("Kconfiglib/tests/Klocation", 73)) - verify_ref_locations("C") - verify_ref_locations("NOT_DEFINED", - ("Kconfiglib/tests/Klocation", 12), - ("Kconfiglib/tests/Klocation", 29), - ("Kconfiglib/tests/Klocation_included", 12), - ("Kconfiglib/tests/Klocation_included", 35), - ("Kconfiglib/tests/Klocation_included", 41)) - - # Location queries for choices - - def verify_choice_locations(choice, *locs): - choice_locs = choice.get_def_locations() - verify(len(choice_locs) == len(locs), - "Wrong number of def. locations for choice") - for i in range(0, len(choice_locs)): - verify(choice_locs[i] == locs[i], - "Wrong def. location for choice: Was {}, should be {}". - format(choice_locs[i], locs[i])) - - choice_1, choice_2 = c.get_choices() - - # Throw in named choice test - verify(choice_1.get_name() == "B", - "The first choice should be called B") - verify(choice_2.get_name() is None, - "The second choice should have no name") - - verify_choice_locations(choice_1, - ("Kconfiglib/tests/Klocation", 15), - ("Kconfiglib/tests/Klocation_included", 24)) - verify_choice_locations(choice_2, - ("Kconfiglib/tests/Klocation_included", 17)) - - # Location queries for menus and comments - - def verify_location(menu_or_comment, loc): - menu_or_comment_loc = menu_or_comment.get_location() - verify(menu_or_comment_loc == loc, - "Wrong location for {} with text '{}': Was {}, should be " - "{}".format("menu" if menu_or_comment.is_menu() else "comment", - menu_or_comment.get_title() if - menu_or_comment.is_menu() else - menu_or_comment.get_text(), - menu_or_comment_loc, - loc)) - - menu_1, menu_2 = c.get_menus()[:-1] - comment_1, comment_2 = c.get_comments() - - verify_location(menu_1, ("Kconfiglib/tests/Klocation", 9)) - verify_location(menu_2, ("Kconfiglib/tests/Klocation_included", 5)) - verify_location(comment_1, ("Kconfiglib/tests/Klocation", 31)) - verify_location(comment_2, ("Kconfiglib/tests/Klocation_included", 36)) + os.environ["EXPANDED_FROM_ENV"] = "tests" + os.environ["srctree"] = "Kconfiglib/" - # - # Visibility queries - # + c = kconfiglib.Config("tests/Klocation") - print("Testing visibility queries...") + os.environ.pop("EXPANDED_FROM_ENV", None) + os.environ.pop("srctree", None) - c = kconfiglib.Config("Kconfiglib/tests/Kvisibility") + verify_locations(c.syms["SINGLE_DEF"].nodes, "tests/Klocation:4") + + verify_locations(c.syms["MULTI_DEF"].nodes, + "tests/Klocation:6", + "tests/Klocation:16", + "tests/Klocation_included:3") - def verify_sym_visibility(sym_name, no_module_vis, module_vis): - sym = c[sym_name] + verify_locations(c.named_choices["CHOICE"].nodes, + "tests/Klocation_included:5") + + verify_locations([c.syms["MENU_HOOK"].nodes[0].next], + "tests/Klocation_included:10") + + verify_locations([c.syms["COMMENT_HOOK"].nodes[0].next], + "tests/Klocation_included:15") + + + print("Testing visibility") + + c = kconfiglib.Config("Kconfiglib/tests/Kvisibility") - c["MODULES"].set_user_value("n") - sym_vis = sym.get_visibility() - verify(sym_vis == no_module_vis, - "{} should have visibility '{}' without modules, had " - "visibility '{}'". - format(sym_name, no_module_vis, sym_vis)) + def verify_visibility(item, no_module_vis, module_vis): + c.syms["MODULES"].set_value("n") + 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_user_value("y") - sym_vis = sym.get_visibility() - verify(sym_vis == module_vis, - "{} should have visibility '{}' with modules, had " - "visibility '{}'". - format(sym_name, module_vis, sym_vis)) + c.syms["MODULES"].set_value("y") + verify(item.visibility == module_vis, + "expected {} to have visibility {} with modules, had " + "visibility {}". + format(repr(item), module_vis, item.visibility)) # Symbol visibility - verify_sym_visibility("NO_PROMPT", "n", "n") - verify_sym_visibility("BOOL_n", "n", "n") - verify_sym_visibility("BOOL_m", "n", "y") # Promoted - verify_sym_visibility("BOOL_MOD", "y", "y") # Promoted - verify_sym_visibility("BOOL_y", "y", "y") - verify_sym_visibility("TRISTATE_m", "n", "m") - verify_sym_visibility("TRISTATE_MOD", "y", "m") # Promoted - verify_sym_visibility("TRISTATE_y", "y", "y") - verify_sym_visibility("BOOL_if_n", "n", "n") - verify_sym_visibility("BOOL_if_m", "n", "y") # Promoted - verify_sym_visibility("BOOL_if_y", "y", "y") - verify_sym_visibility("BOOL_menu_n", "n", "n") - verify_sym_visibility("BOOL_menu_m", "n", "y") # Promoted - verify_sym_visibility("BOOL_menu_y", "y", "y") - verify_sym_visibility("BOOL_choice_n", "n", "n") + verify_visibility(c.syms["NO_PROMPT"], "n", "n") + verify_visibility(c.syms["BOOL_N"], "n", "n") + verify_visibility(c.syms["BOOL_M"], "n", "y") + verify_visibility(c.syms["BOOL_MOD"], "y", "y") + verify_visibility(c.syms["BOOL_Y"], "y", "y") + verify_visibility(c.syms["TRISTATE_M"], "n", "m") + verify_visibility(c.syms["TRISTATE_MOD"], "y", "m") + verify_visibility(c.syms["TRISTATE_Y"], "y", "y") + verify_visibility(c.syms["BOOL_IF_N"], "n", "n") + verify_visibility(c.syms["BOOL_IF_M"], "n", "y") + verify_visibility(c.syms["BOOL_IF_Y"], "y", "y") + verify_visibility(c.syms["BOOL_MENU_N"], "n", "n") + verify_visibility(c.syms["BOOL_MENU_M"], "n", "y") + verify_visibility(c.syms["BOOL_MENU_Y"], "y", "y") + verify_visibility(c.syms["BOOL_CHOICE_N"], "n", "n") # Non-tristate symbols in tristate choices are only visible if the choice # is in "y" mode - verify_sym_visibility("BOOL_choice_m", "n", "n") - verify_sym_visibility("BOOL_choice_y", "y", "n") - c["TRISTATE_choice_m"].set_user_value("y") - c["TRISTATE_choice_y"].set_user_value("y") - # Still limited by the visibility of the choice - verify_sym_visibility("BOOL_choice_m", "n", "n") - # This one should become visible now though - verify_sym_visibility("BOOL_choice_y", "y", "y") - - verify_sym_visibility("TRISTATE_if_n", "n", "n") - verify_sym_visibility("TRISTATE_if_m", "n", "m") - verify_sym_visibility("TRISTATE_if_y", "y", "y") - verify_sym_visibility("TRISTATE_menu_n", "n", "n") - verify_sym_visibility("TRISTATE_menu_m", "n", "m") - verify_sym_visibility("TRISTATE_menu_y", "y", "y") - verify_sym_visibility("TRISTATE_choice_n", "n", "n") - verify_sym_visibility("TRISTATE_choice_m", "n", "m") - verify_sym_visibility("TRISTATE_choice_y", "y", "y") - - # Choice visibility - - def verify_choice_visibility(choice, no_module_vis, module_vis): - c["MODULES"].set_user_value("n") - choice_vis = choice.get_visibility() - verify(choice_vis == no_module_vis, - "choice {} should have visibility '{}' without modules, " - "has visibility '{}'". - format(choice.get_name(), no_module_vis, choice_vis)) - - c["MODULES"].set_user_value("y") - choice_vis = choice.get_visibility() - verify(choice_vis == module_vis, - "choice {} should have visibility '{}' with modules, " - "has visibility '{}'". - format(choice.get_name(), module_vis, choice_vis)) + verify_visibility(c.syms["BOOL_CHOICE_M"], "n", "n") - choice_bool_n, choice_bool_m, choice_bool_y, choice_tristate_n, \ - choice_tristate_m, choice_tristate_y, choice_tristate_if_m_and_y, \ - choice_tristate_menu_n_and_y \ - = c.get_choices()[3:] + # Tristate choices start out in "m" mode. When running without modules, + # their type gets adjusted to bool. + verify_visibility(c.syms["BOOL_CHOICE_Y"], "y", "n") - verify_choice_visibility(choice_bool_n, "n", "n") - verify_choice_visibility(choice_bool_m, "n", "y") # Promoted - verify_choice_visibility(choice_bool_y, "y", "y") - verify_choice_visibility(choice_tristate_n, "n", "n") - verify_choice_visibility(choice_tristate_m, "n", "m") - verify_choice_visibility(choice_tristate_y, "y", "y") + c.syms["TRISTATE_CHOICE_M"].set_value("y") + c.syms["TRISTATE_CHOICE_Y"].set_value("y") - verify_choice_visibility(choice_tristate_if_m_and_y, "n", "m") - verify_choice_visibility(choice_tristate_menu_n_and_y, "n", "n") + # Still limited by the visibility of the choice + verify_visibility(c.syms["BOOL_CHOICE_M"], "n", "n") + + # This one should become visible now + verify_visibility(c.syms["BOOL_CHOICE_Y"], "y", "y") + + verify_visibility(c.syms["TRISTATE_IF_N"], "n", "n") + verify_visibility(c.syms["TRISTATE_IF_M"], "n", "m") + verify_visibility(c.syms["TRISTATE_IF_Y"], "y", "y") + verify_visibility(c.syms["TRISTATE_MENU_N"], "n", "n") + verify_visibility(c.syms["TRISTATE_MENU_M"], "n", "m") + verify_visibility(c.syms["TRISTATE_MENU_Y"], "y", "y") + verify_visibility(c.syms["TRISTATE_CHOICE_N"], "n", "n") + verify_visibility(c.syms["TRISTATE_CHOICE_M"], "n", "m") + verify_visibility(c.syms["TRISTATE_CHOICE_Y"], "y", "y") + + verify_visibility(c.named_choices["BOOL_CHOICE_N"], "n", "n") + verify_visibility(c.named_choices["BOOL_CHOICE_M"], "n", "y") + verify_visibility(c.named_choices["BOOL_CHOICE_Y"], "y", "y") + verify_visibility(c.named_choices["TRISTATE_CHOICE_N"], "n", "n") + verify_visibility(c.named_choices["TRISTATE_CHOICE_M"], "n", "m") + verify_visibility(c.named_choices["TRISTATE_CHOICE_Y"], "y", "y") + + verify_visibility(c.named_choices["TRISTATE_CHOICE_IF_M_AND_Y"], + "n", "m") + verify_visibility(c.named_choices["TRISTATE_CHOICE_MENU_N_AND_Y"], + "n", "n") # Menu visibility def verify_menu_visibility(menu, no_module_vis, module_vis): - c["MODULES"].set_user_value("n") - menu_vis = menu.get_visibility() + c["MODULES"].set_value("n") + menu_vis = kconfiglib.eval_expr(menu.node.dep) verify(menu_vis == no_module_vis, "menu \"{}\" should have visibility '{}' without modules, " - "has visibility '{}'". - format(menu.get_title(), no_module_vis, menu_vis)) + "has visibility '{}'" + .format(menu.title, no_module_vis, menu_vis)) - c["MODULES"].set_user_value("y") - menu_vis = menu.get_visibility() + c["MODULES"].set_value("y") + menu_vis = kconfiglib.eval_expr(menu.node.dep) verify(menu_vis == module_vis, "menu \"{}\" should have visibility '{}' with modules, " "has visibility '{}'". - format(menu.get_title(), module_vis, menu_vis)) + format(menu.title, module_vis, menu_vis)) - menu_n, menu_m, menu_y, menu_if_n, menu_if_m, menu_if_y, \ - menu_if_m_and_y = c.get_menus()[4:-5] - verify(menu_n.get_title() == "menu n", "Ops - testing the wrong menus") + # TODO: does this make sense anymore? - verify_menu_visibility(menu_n, "n", "n") - verify_menu_visibility(menu_m, "n", "m") - verify_menu_visibility(menu_y, "y", "y") - verify_menu_visibility(menu_if_n, "n", "n") - verify_menu_visibility(menu_if_m, "n", "m") - verify_menu_visibility(menu_if_y, "y", "y") - verify_menu_visibility(menu_if_m_and_y, "n", "m") + #menu_n, menu_m, menu_y, menu_if_n, menu_if_m, menu_if_y, \ + # menu_if_m_and_y = get_menus(c)[5:-5] + + #verify_menu_visibility(menu_n, "n", "n") + #verify_menu_visibility(menu_m, "n", "m") + #verify_menu_visibility(menu_y, "y", "y") + #verify_menu_visibility(menu_if_n, "n", "n") + #verify_menu_visibility(menu_if_m, "n", "m") + #verify_menu_visibility(menu_if_y, "y", "y") + #verify_menu_visibility(menu_if_m_and_y, "n", "m") # Menu 'visible if' visibility menu_visible_if_n, menu_visible_if_m, menu_visible_if_y, \ - menu_visible_if_m_2 = c.get_menus()[12:] + menu_visible_if_m_2 = get_menus(c)[13:] def verify_visible_if_visibility(menu, no_module_vis, module_vis): - c["MODULES"].set_user_value("n") + c["MODULES"].set_value("n") menu_vis = menu.get_visible_if_visibility() verify(menu_vis == no_module_vis, "menu \"{}\" should have 'visible if' visibility '{}' " "without modules, has 'visible if' visibility '{}'". - format(menu.get_title(), no_module_vis, menu_vis)) + format(menu.title, no_module_vis, menu_vis)) - c["MODULES"].set_user_value("y") + c["MODULES"].set_value("y") menu_vis = menu.get_visible_if_visibility() verify(menu_vis == module_vis, "menu \"{}\" should have 'visible if' visibility '{}' " "with modules, has 'visible if' visibility '{}'". - format(menu.get_title(), module_vis, menu_vis)) + format(menu.title, module_vis, menu_vis)) + + # TODO: verify the visible if stuff after unclassing # Ordinary visibility should not affect 'visible if' visibility - verify_visible_if_visibility(menu_n, "y", "y") - verify_visible_if_visibility(menu_if_n, "y", "y") - verify_visible_if_visibility(menu_m, "y", "y") - verify_visible_if_visibility(menu_if_m, "y", "y") - - verify_visible_if_visibility(menu_visible_if_n, "n", "n") - verify_visible_if_visibility(menu_visible_if_m, "n", "m") - verify_visible_if_visibility(menu_visible_if_y, "y", "y") - verify_visible_if_visibility(menu_visible_if_m_2, "n", "m") - - # Verify that 'visible if' visibility gets propagated to contained symbols - verify_sym_visibility("VISIBLE_IF_n", "n", "n") - verify_sym_visibility("VISIBLE_IF_m", "n", "m") - verify_sym_visibility("VISIBLE_IF_y", "y", "y") - verify_sym_visibility("VISIBLE_IF_m_2", "n", "m") + #verify_visible_if_visibility(menu_n, "y", "y") + #verify_visible_if_visibility(menu_if_n, "y", "y") + #verify_visible_if_visibility(menu_m, "y", "y") + #verify_visible_if_visibility(menu_if_m, "y", "y") + + #verify_visible_if_visibility(menu_visible_if_n, "n", "n") + #verify_visible_if_visibility(menu_visible_if_m, "n", "m") + #verify_visible_if_visibility(menu_visible_if_y, "y", "y") + #verify_visible_if_visibility(menu_visible_if_m_2, "n", "m") + + # Verify that 'visible if' visibility gets propagated to prompts + verify_visibility(c.syms["VISIBLE_IF_N"], "n", "n") + verify_visibility(c.syms["VISIBLE_IF_M"], "n", "m") + verify_visibility(c.syms["VISIBLE_IF_Y"], "y", "y") + verify_visibility(c.syms["VISIBLE_IF_M_2"], "n", "m") # Comment visibility def verify_comment_visibility(comment, no_module_vis, module_vis): - c["MODULES"].set_user_value("n") - comment_vis = comment.get_visibility() + c["MODULES"].set_value("n") + # TODO: uninternalize + comment_vis = kconfiglib.eval_expr(comment.node.dep) verify(comment_vis == no_module_vis, "comment \"{}\" should have visibility '{}' without " "modules, has visibility '{}'". - format(comment.get_text(), no_module_vis, comment_vis)) + format(comment.text, no_module_vis, comment_vis)) - c["MODULES"].set_user_value("y") - comment_vis = comment.get_visibility() + c["MODULES"].set_value("y") + comment_vis = kconfiglib.eval_expr(comment.node.dep) verify(comment_vis == module_vis, "comment \"{}\" should have visibility '{}' with " "modules, has visibility '{}'". - format(comment.get_text(), module_vis, comment_vis)) + format(comment.text, module_vis, comment_vis)) + + # TODO: verify the visibility stuff for comments - comment_n, comment_m, comment_y, comment_if_n, comment_if_m, \ - comment_if_y, comment_m_nested = c.get_comments() + #comment_n, comment_m, comment_y, comment_if_n, comment_if_m, \ + # comment_if_y, comment_m_nested = get_comments(c) - verify_comment_visibility(comment_n, "n", "n") - verify_comment_visibility(comment_m, "n", "m") - verify_comment_visibility(comment_y, "y", "y") - verify_comment_visibility(comment_if_n, "n", "n") - verify_comment_visibility(comment_if_m, "n", "m") - verify_comment_visibility(comment_if_y, "y", "y") - verify_comment_visibility(comment_m_nested, "n", "m") + #verify_comment_visibility(comment_n, "n", "n") + #verify_comment_visibility(comment_m, "n", "m") + #verify_comment_visibility(comment_y, "y", "y") + #verify_comment_visibility(comment_if_n, "n", "n") + #verify_comment_visibility(comment_if_m, "n", "m") + #verify_comment_visibility(comment_if_y, "y", "y") + #verify_comment_visibility(comment_m_nested, "n", "m") # Verify that string/int/hex symbols with m visibility accept a user value - assign_and_verify_new_value("STRING_m", "foo bar", "foo bar") - assign_and_verify_new_value("INT_m", "123", "123") - assign_and_verify_new_value("HEX_m", "0x123", "0x123") + assign_and_verify("STRING_m", "foo bar") + assign_and_verify("INT_m", "123") + assign_and_verify("HEX_m", "0x123") # # Object relations @@ -1163,61 +1009,41 @@ def run_selftests(): c = kconfiglib.Config("Kconfiglib/tests/Krelation") - A, B, C, D, E, F, G, H, I = c["A"], c["B"], c["C"], c["D"], c["E"], c["F"],\ - c["G"], c["H"], c["I"] - choice_1, choice_2 = c.get_choices() - verify([menu.get_title() for menu in c.get_menus()] == - ["m1", "m2", "m3", "m4"], - "menu ordering is broken") - menu_1, menu_2, menu_3, menu_4 = c.get_menus() + UNDEFINED, A, B, C, D, E, F, G, H, I = \ + c.syms["UNDEFINED"], c.syms["A"], c.syms["B"], c.syms["C"], \ + c.syms["D"], c.syms["E"], c.syms["F"], c.syms["G"], c.syms["H"], \ + c.syms["I"] + choice_1, choice_2 = get_choices(c) + + # TODO: test new prompts + #verify([menu.title for menu in get_menus(c)[1:]] == + # ["m1", "m2", "m3", "m4"], + # "menu ordering is broken") + #menu_1, menu_2, menu_3, menu_4 = get_menus(c)[1:] print("Testing object relations...") - verify(A.get_parent() is None, "A should not have a parent") - verify(B.get_parent() is choice_1, "B's parent should be the first choice") - verify(C.get_parent() is choice_1, "C's parent should be the first choice") - verify(E.get_parent() is menu_1, "E's parent should be the first menu") - verify(E.get_parent().get_parent() is None, - "E's grandparent should be None") - verify(G.get_parent() is choice_2, + # TODO: check parents for menus + + verify(get_parent(UNDEFINED) is None, + "Undefined symbols should have no parent") + # TODO: update this test (should be the main menu) + # TODO: test parents when automatic menus are involved + #verify(A.get_parent() is None, "A should not have a parent") + verify(get_parent(B) is choice_1, "B's parent should be the first choice") + # TODO: no longer true due to auto menus + #verify(get_parent(C) is choice_1, "C's parent should be the first choice") + #verify(get_parent(E) is menu_1, "E's parent should be the first menu") + # TODO: update this test + #verify(E.get_parent().get_parent() is None, + # "E's grandparent should be None") + verify(get_parent(G) is choice_2, "G's parent should be the second choice") - verify(G.get_parent().get_parent() is menu_2, - "G's grandparent should be the second menu") - - # - # Object fetching (same test file) - # + #verify(get_parent(get_parent(G)) is menu_2, + # "G's grandparent should be the second menu") - print("Testing object fetching...") - - verify_equals(c.get_symbol("NON_EXISTENT"), None) - verify(c.get_symbol("A") is A, "get_symbol() is broken") - - verify(c.get_top_level_items() == [A, choice_1, menu_1, menu_3, menu_4], - "Wrong items at top level") - verify(c.get_symbols(False) == [A, B, C, D, E, F, G, H, I], - "get_symbols() is broken") - - verify(choice_1.get_items() == [B, C, D], - "Wrong get_items() items in 'choice'") - # Test Kconfig quirk - verify(choice_1.get_symbols() == [B, D], - "Wrong get_symbols() symbols in 'choice'") - - verify(menu_1.get_items() == [E, menu_2, I], "Wrong items in first menu") - verify(menu_1.get_symbols() == [E, I], "Wrong symbols in first menu") - verify(menu_1.get_items(True) == [E, menu_2, F, choice_2, G, H, I], - "Wrong recursive items in first menu") - verify(menu_1.get_symbols(True) == [E, F, G, H, I], - "Wrong recursive symbols in first menu") - verify(menu_2.get_items() == [F, choice_2], - "Wrong items in second menu") - verify(menu_2.get_symbols() == [F], - "Wrong symbols in second menu") - verify(menu_2.get_items(True) == [F, choice_2, G, H], - "Wrong recursive items in second menu") - verify(menu_2.get_symbols(True) == [F, G, H], - "Wrong recursive symbols in second menu") + # TODO: test parents of comments + # TODO: test top node # # hex/int ranges @@ -1227,16 +1053,16 @@ def run_selftests(): c = kconfiglib.Config("Kconfiglib/tests/Krange") - for sym_name in ("HEX_NO_RANGE", "INT_NO_RANGE", "HEX_40", "INT_40"): - sym = c[sym_name] - verify(not sym.has_ranges(), + 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[sym_name] - verify(sym.has_ranges(), "{} should 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", "") @@ -1246,9 +1072,9 @@ def run_selftests(): 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_new_value("HEX_NO_RANGE", "0x123", "0x123") - assign_and_verify_new_value("HEX_NO_RANGE", "123", "123") - assign_and_verify_new_value("INT_NO_RANGE", "123", "123") + 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") @@ -1275,13 +1101,13 @@ def run_selftests(): """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[sym_name].get_type() == kconfiglib.HEX) + is_hex = (c.syms[sym_name].type == kconfiglib.HEX) for i in range(low, high + 1): - assign_and_verify_new_user_value(sym_name, str(i), str(i)) + assign_and_verify_user_value(sym_name, str(i), str(i)) if is_hex: # The form of the user value should be preserved for hex # symbols - assign_and_verify_new_user_value(sym_name, hex(i), hex(i)) + assign_and_verify_user_value(sym_name, hex(i), hex(i)) # Verify that assigning a user value just outside the range causes # defaults to be used @@ -1298,8 +1124,8 @@ def run_selftests(): too_low_str = str(low - 1) too_high_str = str(high + 1) - assign_and_verify_new_value(sym_name, too_low_str, default_str) - assign_and_verify_new_value(sym_name, too_high_str, default_str) + 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) @@ -1321,15 +1147,15 @@ def run_selftests(): verify_value("HEX_40", "40") verify_value("INT_40", "40") - c["HEX_RANGE_10_20"].unset_user_value() - c["INT_RANGE_10_20"].unset_user_value() + 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["HEX_RANGE_10_20"].set_user_value("15") - c["INT_RANGE_10_20"].set_user_value("15") + 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_user_values() + c.unset_values() verify_range("HEX_RANGE_10_40_DEPENDENT", 0x10, 0x40, 0x10) verify_range("INT_RANGE_10_40_DEPENDENT", 10, 40, 10) @@ -1338,184 +1164,64 @@ def run_selftests(): verify_value("INACTIVE_RANGE", "2") verify_value("ACTIVE_RANGE", "1") - # - # get_referenced_symbols() - # - - c = kconfiglib.Config("Kconfiglib/tests/Kref") - - # General function for checking get_referenced_symbols() output. - # Specialized for symbols below. - def verify_refs(item, refs_no_enclosing, refs_enclosing): - item_refs = item.get_referenced_symbols() - item_refs_enclosing = item.get_referenced_symbols(True) - - # For failure messages - if item.is_symbol(): - item_string = item.get_name() - elif item.is_choice(): - if item.get_name() is None: - item_string = "choice" - else: - item_string = "choice " + item.get_name() - elif item.is_menu(): - item_string = 'menu "{}"'.format(item.get_title()) - else: - # Comment - item_string = 'comment "{}"'.format(item.get_text()) - - verify(len(item_refs) == len(refs_no_enclosing), - "Wrong number of refs excluding enclosing for {}". - format(item_string)) - verify(len(item_refs_enclosing) == len(refs_enclosing), - "Wrong number of refs including enclosing for {}". - format(item_string)) - for r in [c[name] for name in refs_no_enclosing]: - verify(r in item_refs, - "{} should reference {} when excluding enclosing". - format(item_string, r.get_name())) - for r in [c[name] for name in refs_enclosing]: - verify(r in item_refs_enclosing, - "{} should reference {} when including enclosing". - format(item_string, r.get_name())) - - # Symbols referenced by symbols - - def verify_sym_refs(sym_name, refs_no_enclosing, refs_enclosing): - verify_refs(c[sym_name], refs_no_enclosing, refs_enclosing) - - verify_sym_refs("NO_REF", [], []) - verify_sym_refs("ONE_REF", ["A"], ["A"]) - own_refs = ["A", "B", "C", "D", "E", "F", "G", "H", "I", "J", "K", "L", - "M", "N", "O", "P", "Q", "R", "S", "T", "U", "V", "W", "X", - "Y", "Z", "AA"] - verify_sym_refs("MANY_REF", - own_refs, - own_refs + ["IF_REF_1", "IF_REF_2", "MENU_REF_1", - "MENU_REF_2"]) - - # Symbols referenced by choices - - own_refs = ["CHOICE_REF_4", "CHOICE_REF_5", "CHOICE_REF_6"] - verify_refs(c.get_choices()[0], - own_refs, - own_refs + ["CHOICE_REF_1", "CHOICE_REF_2", "CHOICE_REF_3"]) - - # Symbols referenced by menus - - own_refs = ["NO_REF", "MENU_REF_3"] - verify_refs(c.get_menus()[1], - own_refs, - own_refs + ["MENU_REF_1", "MENU_REF_2"]) - - # Symbols referenced by comments - - own_refs = ["COMMENT_REF_3", "COMMENT_REF_4", "COMMENT_REF_5"] - verify_refs(c.get_comments()[0], - own_refs, - own_refs + ["COMMENT_REF_1", "COMMENT_REF_2"]) - - # - # get_selected_symbols() (same test file) - # - - def verify_selects(sym_name, selection_names): - sym = c[sym_name] - sym_selections = sym.get_selected_symbols() - verify(len(sym_selections) == len(selection_names), - "Wrong number of selects for {}".format(sym_name)) - for sel_name in selection_names: - sel_sym = c[sel_name] - verify(sel_sym in sym_selections, - "{} should be selected by {}".format(sel_name, sym_name)) - - verify_selects("n", []) - verify_selects("m", []) - verify_selects("y", []) - verify_selects("UNAME_RELEASE", []) - - verify_selects("NO_REF", []) - verify_selects("MANY_REF", ["I", "N"]) + # TODO: test symbol references in some other way? + # TODO: test selects in some other way? + # TODO: test implies in some other way? # - # get_implied_symbols() (same test file) + # defconfig_filename # - def verify_implies(sym_name, imply_names): - sym = c[sym_name] - sym_implies = sym.get_implied_symbols() - verify(len(sym_implies) == len(imply_names), - "Wrong number of implies for {}".format(sym_name)) - for imply_name in imply_names: - implied_sym = c[imply_name] - verify(implied_sym in sym_implies, - "{} should be implied by {}".format(imply_name, sym_name)) - - verify_implies("n", []) - verify_implies("m", []) - verify_implies("y", []) - verify_implies("UNAME_RELEASE", []) - - verify_implies("NO_REF", []) - verify_implies("MANY_REF", ["P", "U"]) - - # - # get_defconfig_filename() - # - - print("Testing get_defconfig_filename()...") + print("Testing defconfig_filename...") c = kconfiglib.Config("Kconfiglib/tests/empty") - verify(c.get_defconfig_filename() is None, - "get_defconfig_filename() should be None with no defconfig_list " - "symbol") + verify(c.defconfig_filename is None, + "defconfig_filename should be None with no defconfig_list symbol") c = kconfiglib.Config("Kconfiglib/tests/Kdefconfig_nonexistent") - verify(c.get_defconfig_filename() is None, - "get_defconfig_filename() should be None when none of the files " - "in the defconfig_list symbol exist") + 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["BAR"] = "defconfig_2" c = kconfiglib.Config("Kconfiglib/tests/Kdefconfig_existent_but_n") - verify(c.get_defconfig_filename() is None, - "get_defconfig_filename() should be None when the condition is " - "n for all the defaults") + verify(c.defconfig_filename is None, + "defconfig_filename should be None when the condition is n for all " + "the defaults") c = kconfiglib.Config("Kconfiglib/tests/Kdefconfig_existent") - verify(c.get_defconfig_filename() == "Kconfiglib/tests/defconfig_2", - "get_defconfig_filename() should return the existent file " + verify(c.defconfig_filename == "Kconfiglib/tests/defconfig_2", + "defconfig_filename should return the existent file " "Kconfiglib/tests/defconfig_2") # Should also look relative to $srctree if the defconfig is an absolute # path and not found - del os.environ["srctree"] c = kconfiglib.Config("Kconfiglib/tests/Kdefconfig_srctree") - verify(c.get_defconfig_filename() == "Kconfiglib/tests/defconfig_2", - "get_defconfig_filename() returned wrong file with $srctree unset") + verify(c.defconfig_filename == "Kconfiglib/tests/defconfig_2", + "defconfig_filename gave wrong file with $srctree unset") os.environ["srctree"] = "Kconfiglib/tests" c = kconfiglib.Config("Kconfiglib/tests/Kdefconfig_srctree") - verify(c.get_defconfig_filename() == - "Kconfiglib/tests/sub/defconfig_in_sub", - "get_defconfig_filename() returned wrong file with $srctree set") + verify(c.defconfig_filename == "Kconfiglib/tests/sub/defconfig_in_sub", + "defconfig_filename gave wrong file with $srctree set") # - # get_mainmenu_text() + # mainmenu_text # - print("Testing get_mainmenu_text()...") + print("Testing mainmenu_text...") c = kconfiglib.Config("Kconfiglib/tests/empty") - verify(c.get_mainmenu_text() is None, - "An empty Kconfig should not have a mainmenu text") + verify(c.mainmenu_text == "Linux Kernel Configuration", + "An empty Kconfig should get a default main menu prompt") # Expanded in the mainmenu text os.environ["FOO"] = "bar baz" c = kconfiglib.Config("Kconfiglib/tests/Kmainmenu") - verify(c.get_mainmenu_text() == "---bar baz---", + verify(c.mainmenu_text == "---bar baz---", "Wrong mainmenu text") # @@ -1524,134 +1230,95 @@ def run_selftests(): os.environ["ENV_VAR"] = "foo" # Contains reference to undefined environment variable, so disable warnings - c = kconfiglib.Config("Kconfiglib/tests/Kmisc", print_warnings = False) + c = kconfiglib.Config("Kconfiglib/tests/Kmisc", warn = False) - print("Testing is_optional()...") + print("Testing is_optional...") - verify(not c.get_choices()[0].is_optional(), + verify(not get_choices(c)[0].is_optional, "First choice should not be optional") - verify(c.get_choices()[1].is_optional(), + verify(get_choices(c)[1].is_optional, "Second choice should be optional") - print("Testing get_user_value()...") + print("Testing user_value...") # Avoid warnings from assigning invalid user values and assigning user # values to symbols without prompts - c.set_print_warnings(False) + c.disable_warnings() - syms = [c[name] for name in \ + syms = [c.syms[name] for name in \ ("BOOL", "TRISTATE", "STRING", "INT", "HEX")] for sym in syms: - verify(sym.get_user_value() is None, + verify(sym.user_value is None, "{} should not have a user value to begin with") # Assign valid values for the types - assign_and_verify_new_user_value("BOOL", "n", "n") - assign_and_verify_new_user_value("BOOL", "y", "y") - assign_and_verify_new_user_value("TRISTATE", "n", "n") - assign_and_verify_new_user_value("TRISTATE", "m", "m") - assign_and_verify_new_user_value("TRISTATE", "y", "y") - assign_and_verify_new_user_value("STRING", "foo bar", "foo bar") - assign_and_verify_new_user_value("INT", "123", "123") - assign_and_verify_new_user_value("HEX", "0x123", "0x123") + assign_and_verify_user_value("BOOL", "n", "n") + assign_and_verify_user_value("BOOL", "y", "y") + assign_and_verify_user_value("TRISTATE", "n", "n") + assign_and_verify_user_value("TRISTATE", "m", "m") + assign_and_verify_user_value("TRISTATE", "y", "y") + assign_and_verify_user_value("STRING", "foo bar", "foo bar") + assign_and_verify_user_value("INT", "123", "123") + assign_and_verify_user_value("HEX", "0x123", "0x123") # Assign invalid values for the types. They should retain their old user # value. - assign_and_verify_new_user_value("BOOL", "m", "y") - assign_and_verify_new_user_value("BOOL", "foo", "y") - assign_and_verify_new_user_value("BOOL", "1", "y") - assign_and_verify_new_user_value("TRISTATE", "foo", "y") - assign_and_verify_new_user_value("TRISTATE", "1", "y") - assign_and_verify_new_user_value("INT", "foo", "123") - assign_and_verify_new_user_value("HEX", "foo", "0x123") + assign_and_verify_user_value("BOOL", "m", "y") + assign_and_verify_user_value("BOOL", "foo", "y") + assign_and_verify_user_value("BOOL", "1", "y") + assign_and_verify_user_value("TRISTATE", "foo", "y") + assign_and_verify_user_value("TRISTATE", "1", "y") + assign_and_verify_user_value("INT", "foo", "123") + assign_and_verify_user_value("HEX", "foo", "0x123") for s in syms: - s.unset_user_value() - verify(s.get_user_value() is None, + s.unset_value() + verify(s.user_value is None, "{} should not have a user value after being reset". - format(s.get_name())) - - print("Testing is_defined()...") - - for sym_name in ("n", "m", "y", "UNAME_RELEASE", "A", "B", "C", "D", - "BOOL", "TRISTATE", "STRING", "INT", "HEX"): - sym = c[sym_name] - verify(sym.is_defined(), - "{} should be defined".format(sym_name)) - - for sym_name in ("NOT_DEFINED_1", "NOT_DEFINED_2", "NOT_DEFINED_3", - "NOT_DEFINED_4"): - sym = c[sym_name] - verify(not sym.is_defined(), - "{} should not be defined".format(sym_name)) - - print("Testing is_special()...") - - for sym_name in ("n", "m", "y", "UNAME_RELEASE", "FROM_ENV", - "FROM_ENV_MISSING"): - sym = c[sym_name] - verify(sym.is_special(), - "{} should be special".format(sym_name)) - - for sym_name in ("A", "B", "C", "D", "BOOL", "TRISTATE", "STRING", - "INT", "HEX", "NOT_DEFINED_1", "NOT_DEFINED_2", - "NOT_DEFINED_3", "NOT_DEFINED_4"): - sym = c[sym_name] - verify(not sym.is_special(), - "{} should not be special".format(sym_name)) - - print("Testing is_from_environment()...") - - for sym_name in ("FROM_ENV", "FROM_ENV_MISSING"): - sym = c[sym_name] - verify(sym.is_from_environment(), - "{} should be from the environment".format(sym_name)) - - for sym_name in ("n", "m", "y", "UNAME_RELEASE", "A", "B", "C", "D", - "BOOL", "TRISTATE", "STRING", "INT", "HEX", - "NOT_DEFINED_1", "NOT_DEFINED_2", "NOT_DEFINED_3", - "NOT_DEFINED_4"): - sym = c[sym_name] - verify(not sym.is_from_environment(), - "{} should not be from the environment".format(sym_name)) - - print("Testing is_choice_symbol()...") - - for sym_name in ("A", "B", "C", "D"): - sym = c[sym_name] - verify(sym.is_choice_symbol(), - "{} should be a choice symbol".format(sym_name)) - - for sym_name in ("n", "m", "y", "UNAME_RELEASE", "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"): - sym = c[sym_name] - verify(not sym.is_choice_symbol(), - "{} should not be a choice symbol".format(sym_name)) - - print("Testing is_allnoconfig_y()...") - - verify(not c["NOT_ALLNOCONFIG_Y"].is_allnoconfig_y(), + format(s.name)) + + 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["ALLNOCONFIG_Y"].is_allnoconfig_y(), + verify(c.syms["ALLNOCONFIG_Y"].is_allnoconfig_y, "ALLNOCONFIG_Y should be allnoconfig_y") - print("Testing UNAME_RELEASE value...") + print("Testing UNAME_RELEASE...") verify_value("UNAME_RELEASE", platform.uname()[2]) - - # Expansion of environment variables in Config.__init__'s base_dir - # parameter. Just make sure we don't crash when Kbase_dir 'source's a file - # from the same directory. - - os.environ["EnV_VaR1"] = "Kconfigl" - os.environ["EnV_VaR2"] = "ib/tests" - kconfiglib.Config("Kconfiglib/tests/Kbase_dir", - base_dir="$EnV_VaR1$EnV_VaR2/") + ur = c.syms["UNAME_RELEASE"] + verify(ur.config is c and + ur.type == kconfiglib.STRING and + ur.env_var == "", + "UNAME_RELEASE has wrong fields") # # .config reading and writing @@ -1664,9 +1331,9 @@ def run_selftests(): def write_and_verify_header(header): c.write_config(config_test_file, header) c.load_config(config_test_file) - verify(c.get_config_header() == header, + verify(c.config_header == header, "The header {} morphed into {} on loading" - .format(repr(header), repr(c.get_config_header()))) + .format(repr(header), repr(c.config_header))) def verify_file_contents(fname, contents): with open(fname, "r") as f: @@ -1680,12 +1347,12 @@ def run_selftests(): c = kconfiglib.Config("Kconfiglib/tests/Kescape") # Test the default value - c.write_config(config_test_file + "_from_def") + 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["STRING"].set_user_value(r'''\"a'\\''') - c.write_config(config_test_file + "_from_user") + 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") @@ -1695,46 +1362,6 @@ def run_selftests(): c.load_config(config_test_file + "_from_user") verify_value("STRING", r'''\"a'\\''') - # Reading and writing of .config headers - - verify(c.get_config_header() is None, - "Expected no header before .config loaded, got '{}'". - format(c.get_config_header())) - - write_and_verify_header("") - write_and_verify_header(" ") - write_and_verify_header("\n") - write_and_verify_header("\n\n") - write_and_verify_header("#") - write_and_verify_header("a") - write_and_verify_header("a\n") - write_and_verify_header("a\n\n") - write_and_verify_header("abcdef") - write_and_verify_header("foo\nbar baz\n\n\n qaz#") - - c.load_config("Kconfiglib/tests/empty") - verify(c.get_config_header() is None, - "Expected no header in empty .config, got '{}'". - format(c.get_config_header())) - - c.load_config("Kconfiglib/tests/config_hash") - verify(c.get_config_header() == "", - "Expected empty header in file with just '#', got '{}'". - format(c.get_config_header())) - - # TODO: Line joining (which stems from _FileFeed reuse) probably doesn't - # make sense within .config files. (The C implementation has no notion of - # continuation lines within .config files.) It's harmless except for fairly - # obscure cases though. - # - # Add a test for now just to get test coverage for _FileFeed.peek_next(), - # which is only used while reading .config files as of writing. - - c.load_config("Kconfiglib/tests/config_continuation") - verify(c.get_config_header() == - " Foo # Bar\n Baz # Foo # Bar\n Baz\n Foo", - "Continuation line handling within .config headers is broken") - # Appending values from a .config c = kconfiglib.Config("Kconfiglib/tests/Kappend") @@ -1767,70 +1394,30 @@ def run_selftests(): verify_value("IGNOREME", "y") # - # get_config() + # .config # - print("Testing get_config()...") + print("Testing .config...") - c1 = kconfiglib.Config("Kconfiglib/tests/Kmisc", print_warnings = False) - c2 = kconfiglib.Config("Kconfiglib/tests/Kmisc", print_warnings = False) + c1 = kconfiglib.Config("Kconfiglib/tests/Kmisc", warn = False) + c2 = kconfiglib.Config("Kconfiglib/tests/Kmisc", warn = False) - c1_bool, c1_choice, c1_menu, c1_comment = c1["BOOL"], \ - c1.get_choices()[0], c1.get_menus()[0], c1.get_comments()[0] - c2_bool, c2_choice, c2_menu, c2_comment = c2["BOOL"], \ - c2.get_choices()[0], c2.get_menus()[0], c2.get_comments()[0] + c1_undef, c1_bool, c1_choice, c1_menu, c1_comment = c1.syms["BOOL"], \ + c1.syms["NOT_DEFINED_1"], get_choices(c1)[0], get_menus(c1)[0], \ + get_comments(c1)[0] + c2_undef, c2_bool, c2_choice, c2_menu, c2_comment = c2.syms["BOOL"], \ + c2.syms["NOT_DEFINED_1"], get_choices(c2)[0], get_menus(c2)[0], \ + get_comments(c2)[0] - verify((c1_bool is not c2_bool) and (c1_choice is not c2_choice) and - (c1_menu is not c2_menu) and (c1_comment is not c2_comment) and - (c1_bool.get_config() is c1) and (c2_bool.get_config() is c2) and - (c1_choice.get_config() is c1) and (c2_choice.get_config() is c2) and - (c1_menu.get_config() is c1) and (c2_menu.get_config() is c2) and - (c1_comment.get_config() is c1) and (c2_comment.get_config() is c2), - "Config instance state separation or get_config() is broken") - - # - # get_arch/srcarch/srctree/kconfig_filename() - # - - del os.environ["ARCH"] - del os.environ["SRCARCH"] - del os.environ["srctree"] - - c = kconfiglib.Config("Kconfiglib/tests/Kmisc", print_warnings = False) - arch = c.get_arch() - verify(arch is None, "Expected None arch, got '{}'".format(arch)) - srcarch = c.get_srcarch() - verify(srcarch is None, "Expected None srcarch, got '{}'".format(srcarch)) - srctree = c.get_srctree() - verify(srctree is None, "Expected None srctree, got '{}'".format(srctree)) - - os.environ["ARCH"] = "ARCH value" - os.environ["SRCARCH"] = "SRCARCH value" - os.environ["srctree"] = "srctree value" - c = kconfiglib.Config("Kconfiglib/tests/Kmisc", print_warnings = False) - c.load_config("Kconfiglib/tests/empty") - - arch = c.get_arch() - srcarch = c.get_srcarch() - srctree = c.get_srctree() - config_filename = c.get_config_filename() - kconfig_filename = c.get_kconfig_filename() - - print("Testing get_arch()...") - verify(arch == "ARCH value", - "Wrong arch value - got '{}'".format(arch)) - print("Testing get_srcarch()...") - verify(srcarch == "SRCARCH value", - "Wrong srcarch value - got '{}'".format(srcarch)) - print("Testing get_srctree()...") - verify(srctree == "srctree value", - "Wrong srctree value - got '{}'".format(srctree)) - print("Testing get_config_filename()...") - verify(config_filename == "Kconfiglib/tests/empty", - "Wrong config filename - got '{}'".format(config_filename)) - print("Testing get_kconfig_filename()...") - verify(kconfig_filename == "Kconfiglib/tests/Kmisc", - "Wrong Kconfig filename - got '{}'".format(kconfig_filename)) + verify((c1_undef is not c2_undef) and (c1_bool is not c2_bool) and + (c1_choice is not c2_choice) and (c1_menu is not c2_menu) and + (c1_comment is not c2_comment) and + (c1_undef.config is c1) and (c2_undef.config is c2) and + (c1_bool.config is c1) and (c2_bool.config is c2) and + (c1_choice.config is c1) and (c2_choice.config is c2) and + (c1_menu.config is c1) and (c2_menu.config is c2) and + (c1_comment.config is c1) and (c2_comment.config is c2), + "Config instance state separation or .config is broken") # # Imply semantics @@ -1887,7 +1474,7 @@ def run_selftests(): assign_and_verify("IMPLIED_TRISTATE", "n") assign_and_verify("IMPLIED_TRISTATE", "m") assign_and_verify("IMPLIED_TRISTATE", "y") - c["IMPLIED_TRISTATE"].unset_user_value() + c.syms["IMPLIED_TRISTATE"].unset_value() verify_value("IMPLIED_TRISTATE", "n") # Same as above for "m". Anything still goes, but "m" by default now. @@ -1896,7 +1483,7 @@ def run_selftests(): assign_and_verify("IMPLIED_TRISTATE", "n") assign_and_verify("IMPLIED_TRISTATE", "m") assign_and_verify("IMPLIED_TRISTATE", "y") - c["IMPLIED_TRISTATE"].unset_user_value() + c.syms["IMPLIED_TRISTATE"].unset_value() verify_value("IMPLIED_TRISTATE", "m") # Same as above for "y". Only "n" and "y" should be accepted. "m" gets @@ -1904,14 +1491,14 @@ def run_selftests(): assign_and_verify("IMPLY", "y") assign_and_verify("IMPLIED_TRISTATE", "n") - assign_and_verify_new_value("IMPLIED_TRISTATE", "m", "y") + assign_and_verify_value("IMPLIED_TRISTATE", "m", "y") assign_and_verify("IMPLIED_TRISTATE", "y") - c["IMPLIED_TRISTATE"].unset_user_value() + c.syms["IMPLIED_TRISTATE"].unset_value() verify_value("IMPLIED_TRISTATE", "y") # Being implied to either "m" or "y" should give a bool the value "y" - c["IMPLY"].unset_user_value() + c.syms["IMPLY"].unset_value() verify_value("IMPLIED_BOOL", "n") assign_and_verify("IMPLY", "n") verify_value("IMPLIED_BOOL", "n") @@ -1922,11 +1509,11 @@ def run_selftests(): # A bool implied to "m" or "y" can take the values "n" and "y" - c["IMPLY"].set_user_value("m") + c.syms["IMPLY"].set_value("m") assign_and_verify("IMPLIED_BOOL", "n") assign_and_verify("IMPLIED_BOOL", "y") - c["IMPLY"].set_user_value("y") + c.syms["IMPLY"].set_value("y") assign_and_verify("IMPLIED_BOOL", "n") assign_and_verify("IMPLIED_BOOL", "y") @@ -1942,90 +1529,82 @@ def run_selftests(): choice_bool_m, choice_tristate_m, choice_defaults, \ choice_defaults_not_visible, choice_no_type_bool, \ choice_no_type_tristate, choice_missing_member_type_1, \ - choice_missing_member_type_2, choice_weird_syms = c.get_choices() + choice_missing_member_type_2, choice_weird_syms = get_choices(c) for choice in (choice_bool, choice_bool_opt, choice_bool_m, choice_defaults): - verify(choice.get_type() == kconfiglib.BOOL, - "choice {} should have type bool".format(choice.get_name())) + verify(choice.type == kconfiglib.BOOL, + "choice {} should have type bool".format(choice.name)) - for choice in (choice_tristate, choice_tristate_opt, choice_tristate_m): - verify(choice.get_type() == kconfiglib.TRISTATE, - "choice {} should have type tristate" - .format(choice.get_name())) + # TODO: fix this laters. type automatically changed. + #for choice in (choice_tristate, choice_tristate_opt, choice_tristate_m): + # verify(choice.type == kconfiglib.TRISTATE, + # "choice {} should have type tristate" + # .format(choice.name)) def select_and_verify(sym): - choice = sym.get_parent() - sym.set_user_value("y") - verify(choice.get_mode() == "y", + choice = get_parent(sym) + sym.set_value("y") + verify(choice.value == "y", 'The mode of the choice should be "y" after selecting a ' "symbol") - verify(sym.is_choice_selection(), - "is_choice_selection() should be true for {}" - .format(sym.get_name())) - verify(choice.get_selection() is sym, - "{} should be the selected symbol".format(sym.get_name())) - verify(choice.get_user_selection() is sym, + verify(sym.choice.selection is sym, + "{} should be the selected choice symbol" + .format(sym.name)) + verify(choice.selection is sym, + "{} should be the selected symbol".format(sym.name)) + verify(choice.user_selection is sym, "{} should be the user selection of the choice" - .format(sym.get_name())) + .format(sym.name)) def select_and_verify_all(choice): - choice_syms = choice.get_symbols() # Select in forward order - for sym in choice_syms: + for sym in choice.syms: select_and_verify(sym) # Select in reverse order - for i in range(len(choice_syms) - 1, 0, -1): - select_and_verify(choice_syms[i]) + for i in range(len(choice.syms) - 1, 0, -1): + select_and_verify(choice.syms[i]) def verify_mode(choice, no_modules_mode, modules_mode): - c["MODULES"].set_user_value("n") - choice_mode = choice.get_mode() + c.syms["MODULES"].set_value("n") + choice_mode = choice.value verify(choice_mode == no_modules_mode, 'Wrong mode for choice {} with no modules. Expected "{}", ' - 'got "{}".'.format(choice.get_name(), no_modules_mode, - choice_mode)) + 'got "{}".'.format(choice.name, no_modules_mode, choice_mode)) - c["MODULES"].set_user_value("y") - choice_mode = choice.get_mode() + c.syms["MODULES"].set_value("y") + choice_mode = choice.value verify(choice_mode == modules_mode, 'Wrong mode for choice {} with modules. Expected "{}", ' - 'got "{}".'.format(choice.get_name(), modules_mode, + 'got "{}".'.format(choice.name, modules_mode, choice_mode)) verify_mode(choice_bool, "y", "y") verify_mode(choice_bool_opt, "n", "n") verify_mode(choice_tristate, "y", "m") verify_mode(choice_tristate_opt, "n", "n") - verify_mode(choice_bool_m, "n", "y") # Promoted - verify_mode(choice_tristate_m, "n", "m") + verify_mode(choice_bool_m, "y", "y") + verify_mode(choice_tristate_m, "y", "m") # Test defaults - c["TRISTATE_SYM"].set_user_value("n") - verify(choice_defaults.get_selection_from_defaults() is c["OPT_4"] and - choice_defaults.get_selection() is c["OPT_4"], + c.syms["TRISTATE_SYM"].set_value("n") + verify(choice_defaults.selection is c.syms["OPT_4"], "Wrong choice default with TRISTATE_SYM = n") - c["TRISTATE_SYM"].set_user_value("y") - verify(choice_defaults.get_selection_from_defaults() is c["OPT_2"] and - choice_defaults.get_selection() is c["OPT_2"], + c.syms["TRISTATE_SYM"].set_value("y") + verify(choice_defaults.selection is c.syms["OPT_2"], "Wrong choice default with TRISTATE_SYM = y") - c["OPT_1"].set_user_value("y") - verify(choice_defaults.get_selection_from_defaults() is c["OPT_2"], - "User selection changed default selection - shouldn't have") - verify(choice_defaults.get_selection() is c["OPT_1"], + c.syms["OPT_1"].set_value("y") + verify(choice_defaults.selection is c.syms["OPT_1"], "User selection should override defaults") - verify(choice_defaults_not_visible.get_selection_from_defaults() - is c["OPT_8"] and - choice_defaults_not_visible.get_selection() - is c["OPT_8"], + verify(choice_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_user_value("y") + c.syms["MODULES"].set_value("y") select_and_verify_all(choice_bool) select_and_verify_all(choice_bool_opt) @@ -2039,70 +1618,76 @@ def run_selftests(): # ...for a choice that can also be in "y" mode for sym_name in ("T_1", "T_2"): - assign_and_verify_new_value(sym_name, "m", "m") - verify(choice_tristate.get_mode() == "m", + assign_and_verify_value(sym_name, "m", "m") + verify(choice_tristate.value == "m", 'Selecting {} to "m" should have changed the mode of the ' 'choice to "m"'.format(sym_name)) - assign_and_verify_new_value(sym_name, "y", "y") - verify(choice_tristate.get_mode() == "y" and - choice_tristate.get_selection() is c[sym_name], + assign_and_verify_value(sym_name, "y", "y") + verify(choice_tristate.value == "y" and + choice_tristate.selection is c.syms[sym_name], 'Selecting {} to "y" should have changed the mode of the ' 'choice to "y" and made it the selection'.format(sym_name)) # ...for a choice that can only be in "m" mode for sym_name in ("TM_1", "TM_2"): - assign_and_verify_new_value(sym_name, "m", "m") - assign_and_verify_new_value(sym_name, "n", "n") + assign_and_verify_value(sym_name, "m", "m") + assign_and_verify_value(sym_name, "n", "n") # "y" should be truncated - assign_and_verify_new_value(sym_name, "y", "m") - verify(choice_tristate_m.get_mode() == "m", + assign_and_verify_value(sym_name, "y", "m") + verify(choice_tristate_m.value == "m", 'A choice that can only be in "m" mode was not') # Verify that choices with no explicitly specified type get the type of the # first contained symbol with a type - verify(choice_no_type_bool.get_type() == kconfiglib.BOOL, + verify(choice_no_type_bool.type == kconfiglib.BOOL, "Expected first choice without explicit type to have type bool") - verify(choice_no_type_tristate.get_type() == kconfiglib.TRISTATE, + verify(choice_no_type_tristate.type == kconfiglib.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 - verify((c["MMT_1"].get_type(), c["MMT_2"].get_type(), - c["MMT_3"].get_type()) == + verify((c.syms["MMT_1"]._type, c.syms["MMT_2"]._type, + c.syms["MMT_3"]._type) == (kconfiglib.BOOL, kconfiglib.BOOL, kconfiglib.TRISTATE), "Wrong types for first choice with missing member types") - verify((c["MMT_4"].get_type(), c["MMT_5"].get_type()) == + verify((c.syms["MMT_4"]._type, c.syms["MMT_5"]._type) == (kconfiglib.BOOL, kconfiglib.BOOL), "Wrong types for second choice with missing member types") # Verify that symbols in choices that depend on the preceding symbol aren't # considered choice symbols - def verify_is_normal_choice_symbol(sym): - verify(sym.is_choice_symbol() and - sym in choice_weird_syms.get_symbols() and - sym.get_parent() is choice_weird_syms, - "{} should be a normal choice symbol".format(sym.get_name())) - - def verify_is_weird_choice_symbol(sym): - verify(not sym.is_choice_symbol() and - sym not in choice_weird_syms.get_symbols() and - sym in choice_weird_syms.get_items() and - sym.get_parent() is choice_weird_syms, - "{} should be a weird (non-)choice symbol") - - verify_is_normal_choice_symbol(c["WS1"]) - verify_is_weird_choice_symbol(c["WS2"]) - verify_is_weird_choice_symbol(c["WS3"]) - verify_is_weird_choice_symbol(c["WS4"]) - verify_is_normal_choice_symbol(c["WS5"]) - verify_is_weird_choice_symbol(c["WS6"]) + def verify_is_normal_choice_symbol(name): + sym = c.syms[name] + verify(sym.choice is not None and + sym in choice_weird_syms.syms and + get_parent(sym) is choice_weird_syms, + "{} should be a normal choice symbol".format(sym.name)) + + # TODO: parent stuff + + def verify_is_weird_choice_symbol(name): + sym = c.syms[name] + verify(sym.choice is None and + sym not in choice_weird_syms.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") # # Object dependencies @@ -2115,27 +1700,29 @@ def run_selftests(): c = kconfiglib.Config("Kconfiglib/tests/Kdep") def verify_dependent(sym_name, deps_names): - sym = c[sym_name] - deps = [c[name] for name in deps_names] + sym = c.syms[sym_name] + deps = [c.syms[name] for name in deps_names] sym_deps = sym._get_dependent() - verify(len(sym_deps) == len(deps), - "Wrong number of dependent symbols for {}".format(sym_name)) verify(len(sym_deps) == len(set(sym_deps)), "{}'s dependencies contains duplicates".format(sym_name)) + sym_deps = [item for item in sym_deps + if not isinstance(item, kconfiglib.Choice)] + verify(len(sym_deps) == len(deps), + "Wrong number of dependent symbols for {}".format(sym_name)) for dep in deps: verify(dep in sym_deps, "{} should depend on {}". - format(dep.get_name(), sym_name)) + format(dep.name, sym_name)) # Test twice to cover dependency caching for i in range(0, 2): n_deps = 39 # Verify that D1, D2, .., D are dependent on D - verify_dependent("D", ["D{}".format(i) for i in range(1, n_deps + 1)]) + verify_dependent("D", ("D{}".format(i) for i in range(1, n_deps + 1))) # Choices - verify_dependent("A", ["B", "C"]) - verify_dependent("B", ["A", "C"]) - verify_dependent("C", ["A", "B"]) - verify_dependent("S", ["A", "B", "C"]) + verify_dependent("A", ("B", "C")) + verify_dependent("B", ("A", "C")) + verify_dependent("C", ("A", "B")) + verify_dependent("S", ("A", "B", "C")) # Verify that the last symbol depends on the first in a long chain of # dependencies. Test twice to cover dependency caching. @@ -2143,21 +1730,28 @@ def run_selftests(): c = kconfiglib.Config("Kconfiglib/tests/Kchain") for i in range(0, 2): - verify(c["CHAIN_26"] in c["CHAIN_1"]._get_dependent(), + verify(c.syms["CHAIN_26"] in c.syms["CHAIN_1"]._get_dependent(), "Dependency chain broken") - print("\nAll selftests passed\n" if _all_ok else + print("Testing compatibility with weird selects/implies...") + + # Check that Kconfiglib doesn't crash for stuff like 'select n' (seen in + # U-Boot). These probably originate from misunderstandings of how Kconfig + # works. + kconfiglib.Config("Kconfiglib/tests/Kwtf") + + 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.""" - del os.environ["ARCH"] - del os.environ["SRCARCH"] - del os.environ["srctree"] + os.environ.pop("ARCH", None) + os.environ.pop("SRCARCH", None) + os.environ.pop("srctree", None) - if speedy_mode and not os.path.exists("scripts/kconfig/conf"): + if speedy and not os.path.exists("scripts/kconfig/conf"): print("\nscripts/kconfig/conf does not exist -- running " "'make allnoconfig' to build it...") shell("make allnoconfig") @@ -2193,11 +1787,11 @@ def run_compatibility_tests(): # Previously we used to load all the arches once and keep them # around for the tests. That now uses a huge amount of memory (pypy # helps a bit), so reload them for each test instead. - test_fn(kconfiglib.Config(base_dir=".")) + test_fn(kconfiglib.Config(), arch) # Let kbuild infer SRCARCH from ARCH if we aren't in speedy mode. # This could detect issues with the test suite. - if not speedy_mode: + if not speedy: del os.environ["SRCARCH"] if compare_configs: @@ -2207,7 +1801,7 @@ def run_compatibility_tests(): print(" {:14}FAIL".format(arch)) fail() - if all_ok(): + if all_passed: print("All selftests and compatibility tests passed") print(nconfigs, "arch/defconfig pairs tested") else: @@ -2240,13 +1834,13 @@ def get_arch_srcarch_list(): return res -def test_load(conf): +def test_load(conf, arch): """Load all arch Kconfigs to make sure we don't throw any errors""" - print(" {:14}OK".format(conf.get_arch())) + print(" {:14}OK".format(arch)) # The weird docstring formatting is to get the format right when we print the # docstring ourselves -def test_all_no(conf): +def test_all_no(conf, arch): """ Verify that our examples/allnoconfig.py script generates the same .config as 'make allnoconfig', for each architecture. Runs the script via @@ -2256,12 +1850,12 @@ def test_all_no(conf): shell("make scriptconfig SCRIPT=Kconfiglib/examples/allnoconfig.py " "PYTHONCMD='{}'".format(sys.executable)) shell("mv .config ._config") - if speedy_mode: + if speedy: shell("scripts/kconfig/conf --allnoconfig Kconfig") else: shell("make allnoconfig") -def test_all_no_simpler(conf): +def test_all_no_simpler(conf, arch): """ Verify that our examples/allnoconfig_simpler.py script generates the same .config as 'make allnoconfig', for each architecture. Runs the script via @@ -2271,12 +1865,12 @@ def test_all_no_simpler(conf): shell("make scriptconfig SCRIPT=Kconfiglib/examples/allnoconfig_simpler.py " "PYTHONCMD='{}'".format(sys.executable)) shell("mv .config ._config") - if speedy_mode: + if speedy: shell("scripts/kconfig/conf --allnoconfig Kconfig") else: shell("make allnoconfig") -def test_all_yes(conf): +def test_all_yes(conf, arch): """ Verify that our examples/allyesconfig.py script generates the same .config as 'make allyesconfig', for each architecture. Runs the script via @@ -2286,157 +1880,59 @@ def test_all_yes(conf): shell("make scriptconfig SCRIPT=Kconfiglib/examples/allyesconfig.py " "PYTHONCMD='{}'".format(sys.executable)) shell("mv .config ._config") - if speedy_mode: + if speedy: shell("scripts/kconfig/conf --allyesconfig Kconfig") else: shell("make allyesconfig") -def test_call_all(conf): +def test_call_all(conf, arch): """ Call all public methods on all symbols, menus, choices, and comments for all architectures to make sure we never crash or hang. (Nearly all public methods: some are hard to test like this, but are exercised by other - tests.) Also do misc. sanity checks.""" - print(" For {}...".format(conf.get_arch())) - - conf.__str__() - conf.get_arch() - conf.get_base_dir() - conf.get_config_filename() - conf.get_config_header() - conf.get_defconfig_filename() - conf.get_kconfig_filename() - conf.get_mainmenu_text() - conf.get_srcarch() - conf.get_srctree() - conf.get_symbol("y") - conf.get_symbols(False) - conf.get_top_level_items() - conf.set_print_undef_assign(True) - conf.set_print_undef_assign(False) - conf.set_print_warnings(False) - conf.set_print_warnings(True) - conf.unset_user_values() - - conf.eval("y && ARCH") - - for s in conf.get_symbols(): + tests.)""" + print(" For {}...".format(arch)) + + conf.defconfig_filename + conf.mainmenu_text + conf.enable_undef_warnings() + conf.disable_undef_warnings() + conf.disable_warnings() + conf.enable_warnings() + conf.unset_values() + + # Python 2/3 compatible + for _, s in conf.syms.items(): s.__str__() - s.get_assignable_values() - s.get_config() - s.get_help() - s.get_implied_symbols() - s.get_lower_bound() - s.get_name() - s.get_parent() - s.get_prompts() - s.get_ref_locations() - s.get_referenced_symbols() - s.get_referenced_symbols(True) - s.get_selected_symbols() - s.get_type() - s.get_upper_bound() - s.get_user_value() - s.get_value() - s.get_visibility() - s.has_ranges() - s.is_choice_selection() - s.is_choice_symbol() - s.is_defined() - s.is_from_environment() - s.is_modifiable() - s.is_allnoconfig_y() - s.unset_user_value() - - # Check get_ref/def_location() sanity - - if s.is_special(): - if s.is_from_environment(): - # Special symbols from the environment should have define - # locations - verify(s.get_def_locations() != [], - "The symbol '{}' is from the environment but lacks " - "define locations".format(s.get_name())) - else: - # Special symbols that are not from the environment should be - # defined and have no define locations - verify(s.is_defined(), - "The special symbol '{}' is not defined". - format(s.get_name())) - verify(s.get_def_locations() == [], - "The special symbol '{}' has recorded def. locations". - format(s.get_name())) - else: - # Non-special symbols should have define locations iff they are - # defined - if s.is_defined(): - verify(s.get_def_locations() != [], - "'{}' defined but lacks recorded locations". - format(s.get_name())) - else: - verify(s.get_def_locations() == [], - "'{}' undefined but has recorded locations". - format(s.get_name())) - verify(s.get_ref_locations() != [], - "'{}' both undefined and unreferenced". - format(s.get_name())) - - for c in conf.get_choices(): + s.__repr__() + s.assignable + s.type + s.value + s.visibility + s.unset_value() + + # Cheat with internals + for c in conf._choices: c.__str__() - c.get_config() - c.get_def_locations() - c.get_help() - c.get_items() - c.get_mode() - c.get_name() - c.get_parent() - c.get_prompts() - c.get_referenced_symbols() - c.get_referenced_symbols(True) - c.get_selection() - c.get_selection_from_defaults() - c.get_symbols() - c.get_type() - c.get_user_selection() - c.get_visibility() - c.is_optional() - - for m in conf.get_menus(): - m.__str__() - m.get_config() - m.get_items() - m.get_items(True) - m.get_location() - m.get_parent() - m.get_referenced_symbols() - m.get_referenced_symbols(True) - m.get_symbols() - m.get_symbols(True) - m.get_title() - m.get_visibility() - m.get_visible_if_visibility() - - for c in conf.get_comments(): - c.__str__() - c.get_config() - c.get_location() - c.get_parent() - c.get_referenced_symbols() - c.get_referenced_symbols(True) - c.get_text() - c.get_visibility() - -def test_config_absent(conf): + c.__repr__() + c.value + c.assignable + c.selection + c.default_selection + c.type + c.visibility + +def test_config_absent(conf, arch): """ Verify that Kconfiglib generates the same .config as 'make alldefconfig', for each architecture""" conf.write_config("._config") - if speedy_mode: + if speedy: shell("scripts/kconfig/conf --alldefconfig Kconfig") else: shell("make alldefconfig") -def test_defconfig(conf): +def test_defconfig(conf, arch): """ Verify that Kconfiglib generates the same .config as scripts/kconfig/conf, for each architecture/defconfig pair. In obsessive mode, this test includes @@ -2450,8 +1946,8 @@ def test_defconfig(conf): global nconfigs defconfigs = [] - def add_configs_for_arch(arch): - arch_dir = os.path.join("arch", arch) + def add_configs_for_arch(arch_): + arch_dir = os.path.join("arch", arch_) # Some arches have a "defconfig" in the root of their arch// # directory root_defconfig = os.path.join(arch_dir, "defconfig") @@ -2470,13 +1966,13 @@ def test_defconfig(conf): for filename in filenames: defconfigs.append(os.path.join(dirpath, filename)) - if obsessive_mode: + if obsessive: # Collect all defconfigs. This could be done once instead, but it's # a speedy operation comparatively. - for arch in os.listdir("arch"): - add_configs_for_arch(arch) + for arch_ in os.listdir("arch"): + add_configs_for_arch(arch_) else: - add_configs_for_arch(conf.get_arch()) + add_configs_for_arch(arch) # Test architecture for each defconfig @@ -2487,7 +1983,7 @@ def test_defconfig(conf): conf.load_config(defconfig) conf.write_config("._config") - if speedy_mode: + if speedy: shell("scripts/kconfig/conf --defconfig='{}' Kconfig". format(defconfig)) else: @@ -2500,31 +1996,24 @@ def test_defconfig(conf): # bugs. shell("make kconfiglibtestconfig") - arch_defconfig_str = " {:14}with {:60} " \ - .format(conf.get_arch(), defconfig) + arch_defconfig_str = " {:14}with {:60} ".format(arch, defconfig) if equal_confs(): print(arch_defconfig_str + "OK") else: print(arch_defconfig_str + "FAIL") fail() - if log_mode: + if log: with open("test_defconfig_fails", "a") as fail_log: fail_log.write("{} {} with {} did not match\n" .format(time.strftime("%d %b %Y %H:%M:%S", time.localtime()), - conf.get_arch(), - defconfig)) + arch, defconfig)) # # Helper functions # -devnull = open(os.devnull, "w") - -def shell(cmd): - subprocess.call(cmd, shell = True, stdout = devnull, stderr = devnull) - def rm_configs(): """Delete any old ".config" (generated by the C implementation) and "._config" (generated by us), if present.""" @@ -2557,7 +2046,8 @@ def equal_confs(): return False else: with f: - our = f.readlines() + # [1:] strips the default header + our = f.readlines()[1:] if their == our: return True @@ -2569,26 +2059,5 @@ def equal_confs(): return False -_all_ok = True - -def verify(cond, msg): - """Fails and prints 'msg' if 'cond' is False.""" - if not cond: - fail(msg) - -def verify_equals(x, y): - """Fails if 'x' does not equal 'y'.""" - if x != y: - fail("'{}' does not equal '{}'".format(x, y)) - -def fail(msg = None): - global _all_ok - if msg is not None: - print("Fail: " + msg) - _all_ok = False - -def all_ok(): - return _all_ok - if __name__ == "__main__": run_tests() -- cgit v1.2.3 From 463cebd7f8bf26673732bf5afcd113ee5e2268b7 Mon Sep 17 00:00:00 2001 From: Ulf Magnusson Date: Wed, 25 Oct 2017 13:35:31 +0200 Subject: Backup --- examples/allnoconfig.py | 35 +-- examples/allyesconfig.py | 4 +- examples/help_grep.py | 2 +- examples/print_tree.py | 2 +- kconfiglib.py | 637 ++++++++++++++++++++++++++--------------------- testsuite.py | 8 +- 6 files changed, 388 insertions(+), 300 deletions(-) (limited to 'testsuite.py') diff --git a/examples/allnoconfig.py b/examples/allnoconfig.py index edc473d..8d41912 100644 --- a/examples/allnoconfig.py +++ b/examples/allnoconfig.py @@ -12,7 +12,7 @@ from kconfiglib import Config, Symbol, tri_less import sys def do_allnoconfig(node): - global no_changes + global changed # Walk the tree of menu nodes. You can imagine this as going down/into menu # entries in the menuconfig interface, setting each to 'n' (or the lowest @@ -22,14 +22,16 @@ def do_allnoconfig(node): if isinstance(node.item, Symbol): sym = node.item - # Is the symbol a non-choice symbol that can be set to a lower - # value than its current value? - if sym.choice is None and sym.assignable and \ - tri_less(sym.assignable[0], sym.value): + # Is the symbol a non-choice, non-allnoconfig_y symbol that can be + # set to a lower value than its current value? + if (sym.choice is None and + not sym.is_allnoconfig_y and + sym.assignable and + tri_less(sym.assignable[0], sym.value)): # Yup, lower it sym.set_value(sym.assignable[0]) - no_changes = False + changed = True # Recursively lower children if node.list is not None: @@ -39,19 +41,22 @@ def do_allnoconfig(node): conf = Config(sys.argv[1]) +# Do an initial pass to set 'option allnoconfig_y' symbols to 'y' +for sym in conf.defined_syms: + if sym.is_allnoconfig_y: + sym.set_value("y") + while 1: - # For tricky dependencies involving '!', setting later symbols to 'n' might - # actually raise the value of earlier symbols. To be super safe, we do - # additional passes until a pass no longer changes the value of any symbol. - # - # This isn't actually needed for any ARCH in the kernel as of 4.14. A - # single pass gives the correct result. - no_changes = True + # Changing later symbols in the configuration can sometimes allow earlier + # symbols to be lowered, e.g. if a later symbol 'select's an earlier + # symbol. To handle such situations, we do additional passes over the tree + # until we're no longer able to change the value of any symbol in a pass. + changed = False - do_allnoconfig(conf.top_menu) + do_allnoconfig(conf.top_node) # Did the pass change any symbols? - if no_changes: + if not changed: break conf.write_config(".config") diff --git a/examples/allyesconfig.py b/examples/allyesconfig.py index 4847c05..32b302c 100644 --- a/examples/allyesconfig.py +++ b/examples/allyesconfig.py @@ -27,7 +27,7 @@ conf = Config(sys.argv[1]) # tree can be walked iteratively by using the parent pointers. choices = [] -node = conf.top_menu +node = conf.top_node while 1: if isinstance(node.item, Choice): @@ -85,7 +85,7 @@ while 1: # Does the choice have a default selection that we haven't already # selected? if selection is not None and \ - selection is not choice.user_value: + selection is not choice.user_selection: # Yup, select it selection.set_value("y") diff --git a/examples/help_grep.py b/examples/help_grep.py index fed1731..6fcb08f 100644 --- a/examples/help_grep.py +++ b/examples/help_grep.py @@ -69,4 +69,4 @@ def search_tree(node): node = node.next conf = Config(sys.argv[1]) -search_tree(conf.top_menu) +search_tree(conf.top_node) diff --git a/examples/print_tree.py b/examples/print_tree.py index da795c9..8d18ce9 100644 --- a/examples/print_tree.py +++ b/examples/print_tree.py @@ -64,4 +64,4 @@ def print_items(node, indent): node = node.next conf = Config(sys.argv[1]) -print_items(conf.top_menu, 0) +print_items(conf.top_node, 0) diff --git a/kconfiglib.py b/kconfiglib.py index e4d0b58..04edb67 100644 --- a/kconfiglib.py +++ b/kconfiglib.py @@ -1,4 +1,7 @@ """ +Overview +======== + Kconfiglib is a Python 2/3 library for scripting and extracting information from Kconfig-based configuration systems. Features include the following: @@ -6,40 +9,79 @@ from Kconfig-based configuration systems. Features include the following: - Reading/writing of .config files - - Expression inspection and evaluation. All expressions are exposed and use a - simple format that can be processed manually if needed. + - Inspection of symbol properties: print()ing a symbol (which calls __str__()) + produces output which could be fed back into a Kconfig parser to redefine + the symbol, and __str__() is implemented with only public APIs. + + A helpful __repr__() is implemented on all objects as well. + + - Expression inspection and evaluation: All expressions are exposed and use a + simple tuple-based format that can be processed manually if needed. + + - Menu tree inspection: The underlying menu tree is exposed, including + submenus created implicitly from symbols depending on preceding symbols. + This can be used e.g. to implement menuconfig-like functionality. - - Menu tree inspection. The underlying menu tree is exposed, including - submenus created implicitly by symbols depending on preceding symbols. This - can be used e.g. to implement menuconfig-like functionality. + - Runs under both Python 2 and 3. The code mostly uses basic Python features + (the most advanced things used are probably @property and __slots__). - - Highly compatible with the standard Kconfig C tools: The test suite compares - outputs between Kconfiglib and the C tools on real-world kernel Kconfig and - defconfig files for a large number of cases (by diffing generated .configs). + - Robust and highly compatible with the standard Kconfig C tools: The test + suite automatically compares the output from Kconfiglib with the output from + the C tools on the real kernel Kconfig and defconfig files for all ARCHes. + The comparison is done by diffing the generated .config files to make sure + they're identical. All tests are expected to pass. + + A suite of self tests is also included. + + - Internals that (mostly) mirror the C implementation. A lot can indirectly be + learned about how it works by reading the Kconfiglib documentation and code. - Pretty speedy by pure Python standards: Parses the x86 Kconfigs in about a second on a Core i7 2600K (with a warm file cache). For long-running jobs, - PyPy gives a nice speedup. + PyPy gives a large performance boost. + + +Using Kconfiglib on the Linux kernel +==================================== For the Linux kernel, a handy interface is provided by the -scripts/kconfig/Makefile patch. For experimentation, you can use the -iscriptconfig target, which gives an interactive Python prompt where the -configuration for ARCH has been loaded: +scripts/kconfig/Makefile patch. - $ make [ARCH=] iscriptconfig +Use the 'iscriptconfig' target for experimentation. It gives an interactive +Python prompt where the configuration for ARCH has been preloaded. -To run a script, use the scriptconfig target: + $ make [ARCH=] [PYTHONCMD=] iscriptconfig - $ make [ARCH=] scriptconfig SCRIPT= [SCRIPT_ARG=] +To run a script, use the 'scriptconfig' target. + + $ make [ARCH=] [PYTHONCMD=] scriptconfig SCRIPT= [SCRIPT_ARG=] + +PYTHONCMD is the Python interpreter to use. It defaults to "python". + +Tip: IronPython (PYTHONCMD=ipython) autocompletion is handy when figuring out +the API. + + +Writing scriptconfig scripts +---------------------------- See the examples/ subdirectory for example scripts. +Scripts receive the name of the Kconfig file to load in sys.argv[1]. As far as +I can tell, this is always "Kconfig" from the kernel top-level directory as of +Linux 4.14. If an argument is provided with SCRIPT_ARG, it appears as +sys.argv[2]. + + +Running Kconfiglib without the Makefile patch +--------------------------------------------- The Makefile patch is used to pick up the ARCH, SRCARCH, and KERNELVERSION environment variables (and any future environment variables that might get used). If you want to run Kconfiglib without the Makefile patch, the following will probably work in practice (it's what the test suite does in 'speedy' mode, -except it tests all ARCHes): +except it tests all ARCHes and doesn't bother setting KERNELVERSION to a sane +value to save some time on startup). $ ARCH=x86 SRCARCH=x86 KERNELVERSION=`make kernelversion` python script.py @@ -49,18 +91,40 @@ possible variations. The value of KERNELVERSION doesn't seem to matter as of Linux 4.14. Kconfiglib will warn if you forget to set some environment variable that's -referenced in the configuration (via 'option env="ENV_VAR"'). +referenced in a Kconfig file (via 'option env="ENV_VAR"'). -When using scriptconfig, scripts receive the name of the Kconfig file to load -in sys.argv[1]. As far as I can tell, this is always "Kconfig" from the kernel -top-level directory as of Linux 4.14. If an argument is provided with -SCRIPT_ARG, it appears as sys.argv[2]. +Expression format +================= +The following table should help you figure out how expressions are represented. +A, B, C, ... are symbols (Symbol instances) or strings (which represent +constant symbols). NOT is the kconfiglib.NOT constant, etc. -Kconfiglib supports both Python 2 and Python 3 (and PyPy). For (i)scriptconfig, -the Python interpreter to use can be passed in PYTHONCMD, which defaults to -"python". +Expression Representation +---------- -------------- +A A +!A (NOT, A) +A && B (AND, A, B) +A || B (OR, A, B) +A = B (EQUAL, A, B) +A != "foo" (UNEQUAL, A, "foo") +A || (B && C && D) (OR, A, (AND, B, (AND, C, D))) +y "y" +"y" "y" + +As seen in the final two examples, n/m/y are always represented as the strings +(constant symbols) "n"/"m"/"y" in Kconfiglib, regardless of whether they're +written with or without quotes. This simplifies some internals. + +A missing expression (e.g. if the 'if ' part is removed from +'default A if ') is represented as "y". The standard __str__() functions +avoid printing 'if y' conditions to give cleaner output. + +Implementation note +------------------- + +TODO: blah blah constant symbols Send bug reports, suggestions, and questions to ulfalizer a.t Google's email @@ -97,58 +161,70 @@ class Config(object): The following attributes are available on Config instances. They should be viewed as read-only, and some are implemented through @property magic. - Modifying symbols is fine, but not the 'syms' dictionary itself. syms: A dictionary with all symbols in the configuration. The key is the name - of the symbol, so that e.g. conf.syms["MODULES"] returns the MODULES - symbol. Symbols that are referenced in expressions but never defined are - included as well. + of the symbol, so that e.g. conf.syms["FOO"] returns the Symbol instance + for the symbol FOO. Symbols that are referenced in expressions but never + defined also appear in 'syms'. + + Constant symbols, e.g. "foo" in 'A = "foo"', are not included in + Config.syms. defined_syms: A list of all defined symbols, in the same order as they appear in the - Kconfig files. Provided as a convenience (and also used internally). The - defined symbols are those whose 'nodes' attribute is non-empty. + Kconfig files. Provided as a convenience. The defined symbols are those + whose 'nodes' attribute is non-empty. named_choices: - A dictionary like 'syms' for named choices (choice FOO). This is mostly - for completeness. I've never seen named choices being used. + A dictionary like 'syms' for named choices (choice FOO). This is for + completeness. I've never seen named choices being used. - top_menu: - The menu node (see the MenuNode class) of the top-level menu. Acts as the - root of the menu tree. + modules: + The Symbol instance for the modules symbol. This is currently hardcoded + to MODULES, which is backwards compatible, and Kconfiglib will warn if + 'option modules' is specified on some other symbol. Tell me if you need + proper 'option modules' support. - mainmenu_text: - The prompt (title) of the top_menu menu, with Kconfig variable references - ("$FOO") expanded. Defaults to "Linux Kernel Configuration" (like in the - C tools). Can be changed with the 'mainmenu' statement (see - kconfig-language.txt). + Never None. + + defconfig_list: + The Symbol instance for the 'option defconfig_list' symbol, or None if no + defconfig_list symbol exists. The defconfig filename derived from this + symbol can be found in Config.defconfig_filename. + + Setting 'option defconfig_list' on multiple symbols ignores the setting + on all symbols after the first. defconfig_filename: - The filename given by the 'option defconfig_list' symbol. This is the - first existing file with a satisfied condition among the 'default' - properties of the symbol. If a file is not found at the given path, it is - also looked up relative to $srctree if set ($srctree/foo/defconfig is - looked up if foo/defconfig is not found). + The filename given by the 'option defconfig_list' symbol. This is taken + from the first 'default' with a satisfied condition where the file + specified by the 'default' exists. If a defconfig file foo/defconfig is + not found and $srctree was set when the Config was created, + $srctree/foo/defconfig is looked up as well. - Has the value None if either no defconfig_list symbol exists, or if it - has no 'default' with a satisfied dependency that points to an existing - file. + None if either no defconfig_list symbol exists, or if the defconfig_list + symbol has no 'default' with a satisfied condition that points to an + existing file. References to Kconfig symbols ("$FOO") are expanded in 'default' properties. - Setting 'option defconfig_list' on multiple symbols ignores symbols past - the first one. + Something to look out for is that scripts/kconfig/Makefile might pass + --defconfig= to scripts/kconfig/conf when running e.g. + 'make defconfig'. This option overrides the defconfig_list symbol, + meaning defconfig_filename might not match what 'make defconfig' would + use in those cases. - Do print(c.syms["DEFCONFIG_LIST"]) on a kernel configuration to see an - example of a defconfig_list symbol. + top_node: + The menu node (see the MenuNode class) of the top-level menu. Acts as the + root of the menu tree. - Something to look out for is that scripts/kconfig/Makefile might use the - --defconfig= option when calling the C tools of e.g. 'make - defconfig'. This option overrides the 'option defconfig_list' symbol, - meaning defconfig_filename might not match what 'make defconfig' would - use. + mainmenu_text: + The prompt (title) of the top_node menu, with Kconfig variable references + ("$FOO") expanded. Defaults to "Linux Kernel Configuration" (like in the + C tools). Can be changed with the 'mainmenu' statement (see + kconfig-language.txt). srctree: The value of the $srctree environment variable when the configuration was @@ -157,9 +233,9 @@ class Config(object): (unless absolute paths are specified). This is to support out-of-tree builds. The C tools use this variable in the same way. - Changing $srctree after loading the configuration has no effect. Only the - value when the configuration is loaded matters. This avoids surprises if - multiple configurations are loaded with different values for $srctree. + Changing $srctree after creating the Config instance has no effect. Only + the value when the configuration is loaded matters. This avoids surprises + if multiple configurations are loaded with different values for $srctree. config_prefix: The value of the $CONFIG_ environment variable when the configuration was @@ -183,7 +259,7 @@ class Config(object): "named_choices", "srctree", "syms", - "top_menu", + "top_node", ) # @@ -199,13 +275,15 @@ class Config(object): filename (default: "Kconfig"): The base Kconfig file. For the Linux kernel, you'll want "Kconfig" from the top-level directory, as environment variables will make sure - the right Kconfig is included from there - (arch//Kconfig). If you are using Kconfiglib via 'make - scriptconfig', the filename of the base base Kconfig file will be in - sys.argv[1] (always "Kconfig" in practice). + the right Kconfig is included from there (arch/$SRCARCH/Kconfig as of + writing). + + If you are using Kconfiglib via 'make scriptconfig', the filename of + the base base Kconfig file will be in sys.argv[1]. It's currently + always "Kconfig" in practice. - The $srctree environment variable is used if set (see the class - documentation). + The $srctree environment variable is used to look up Kconfig files if + set (see the class documentation). warn (default: True): True if warnings related to this configuration should be printed to @@ -214,67 +292,66 @@ class Config(object): argument since warnings might be generated during parsing. """ + self.srctree = os.environ.get("srctree") + + self.config_prefix = os.environ.get("CONFIG_") + if self.config_prefix is None: + self.config_prefix = "CONFIG_" + + # Regular expressions for parsing .config files + self._set_re = re.compile(r"{}(\w+)=(.*)" + .format(self.config_prefix)) + self._unset_re = re.compile(r"# {}(\w+) is not set" + .format(self.config_prefix)) + + self._print_warnings = warn + self._print_undef_assign = False + self.syms = {} self.defined_syms = [] self.named_choices = {} - # Used for quickly invalidating all choices self._choices = [] - # Predefined symbol. DEFCONFIG_LIST has been seen using this. + self.modules = self._lookup_sym("MODULES") + self.defconfig_list = None + + # Predefined symbol. DEFCONFIG_LIST uses this. uname_sym = Symbol() uname_sym._type = STRING uname_sym.name = "UNAME_RELEASE" uname_sym.config = self - uname_sym.defaults.append((platform.uname()[2], None)) + uname_sym.defaults.append((platform.uname()[2], "y")) # env_var doubles as the SYMBOL_AUTO flag from the C implementation, so # just set it to something. The naming breaks a bit here, but it's # pretty obscure. uname_sym.env_var = "" self.syms["UNAME_RELEASE"] = uname_sym - # The symbol with "option defconfig_list" set, containing a list of - # default .config files - self.defconfig_list = None - - self.config_prefix = os.environ.get("CONFIG_") - if self.config_prefix is None: - self.config_prefix = "CONFIG_" - - # Regular expressions for parsing .config files - self._set_re = re.compile(r"{}(\w+)=(.*)" - .format(self.config_prefix)) - self._unset_re = re.compile(r"# {}(\w+) is not set" - .format(self.config_prefix)) - - self.srctree = os.environ.get("srctree") - - self._print_warnings = warn - self._print_undef_assign = False - - self.top_menu = MenuNode() - self.top_menu.config = self - self.top_menu.item = MENU - self.top_menu.visibility = None - self.top_menu.prompt = ("Linux Kernel Configuration", None) - self.top_menu.parent = None - self.top_menu.dep = None - self.top_menu.filename = filename - self.top_menu.linenr = 1 - - # We hardcode MODULES for backwards compatibility. Proper support via - # 'option modules' wouldn't be that tricky to add with backwards - # compatibility either though. - self.modules = self._lookup_sym("MODULES") + self.top_node = MenuNode() + self.top_node.config = self + self.top_node.item = MENU + self.top_node.visibility = None + self.top_node.prompt = ("Linux Kernel Configuration", "y") + self.top_node.parent = None + self.top_node.dep = "y" + self.top_node.filename = filename + self.top_node.linenr = 1 # Parse the Kconfig files self._parse_block(_FileFeed(self._open(filename), filename), - None, self.top_menu, None, None, self.top_menu) + None, # end_token + self.top_node, # parent + "y", # visible_if_deps + None, # prev_line + self.top_node) # prev_node - self.top_menu.list = self.top_menu.next - self.top_menu.next = None + self.top_node.list = self.top_node.next + self.top_node.next = None - _finalize_tree(self.top_menu) + # Do various post-processing of the menu tree, e.g. to finalize + # choices, flatten ifs, and implicitly create menus + _finalize_tree(self.top_node) # Build Symbol._direct_dependents for all symbols self._build_dep() @@ -284,7 +361,7 @@ class Config(object): """ See the class documentation. """ - return self._expand_sym_refs(self.top_menu.prompt[0]) + return self._expand_sym_refs(self.top_node.prompt[0]) @property def defconfig_filename(self): @@ -727,7 +804,7 @@ class Config(object): return _Feed(tokens) - def _parse_block(self, line_feeder, end_marker, parent, visible_if_deps, + def _parse_block(self, line_feeder, end_token, parent, visible_if_deps, prev_line, prev_node): """ Parses a block, which is the contents of either a file or an if, menu, @@ -737,7 +814,7 @@ class Config(object): A _FileFeed instance feeding lines from a file. The Kconfig language is line-based in practice. - end_marker: + end_token: The token that ends the block, e.g. _T_ENDIF ("endif") for ifs. None for files. @@ -774,7 +851,7 @@ class Config(object): else: line = line_feeder.next() if line is None: - if end_marker is not None: + if end_token is not None: raise KconfigSyntaxError("Unexpected end of file " + line_feeder.filename) @@ -837,11 +914,14 @@ class Config(object): e.message)) prev_node = self._parse_block(_FileFeed(f, exp_kconfig_file), - None, parent, visible_if_deps, - None, prev_node) + None, # end_token + parent, + visible_if_deps, + None, # prev_line + prev_node) prev_line = None - elif t0 == end_marker: + elif t0 == end_token: # We have reached the end of the block. Terminate the final # node and return it. prev_node.next = None @@ -860,8 +940,12 @@ class Config(object): line_feeder.filename, line_feeder.linenr, True)) - self._parse_block(line_feeder, _T_ENDIF, node, visible_if_deps, - None, node) + self._parse_block(line_feeder, + _T_ENDIF, + node, # parent + visible_if_deps, + None, # prev_line + node) # prev_node node.list = node.next prev_line = None @@ -872,7 +956,7 @@ class Config(object): node = MenuNode() node.config = self node.item = MENU - node.visibility = None + node.visibility = "y" node.parent = parent node.filename = line_feeder.filename node.linenr = line_feeder.linenr @@ -881,9 +965,12 @@ class Config(object): visible_if_deps) node.prompt = (tokens.next(), node.dep) - self._parse_block(line_feeder, _T_ENDMENU, node, + self._parse_block(line_feeder, + _T_ENDMENU, + node, # parent _make_and(visible_if_deps, node.visibility), - prev_line, node) + prev_line, + node) # prev_node node.list = node.next prev_line = None @@ -931,8 +1018,12 @@ class Config(object): prev_line = self._parse_properties(line_feeder, node, visible_if_deps) - self._parse_block(line_feeder, _T_ENDCHOICE, node, - visible_if_deps, prev_line, node) + self._parse_block(line_feeder, + _T_ENDCHOICE, + node, # parent + visible_if_deps, + prev_line, + node) # prev_node node.list = node.next prev_line = None @@ -942,9 +1033,9 @@ class Config(object): prev_node.next = prev_node = node elif t0 == _T_MAINMENU: - self.top_menu.prompt = (tokens.next(), None) - self.top_menu.filename = line_feeder.filename - self.top_menu.linenr = line_feeder.linenr + self.top_node.prompt = (tokens.next(), "y") + self.top_node.filename = line_feeder.filename + self.top_node.linenr = line_feeder.linenr else: _parse_error(line, "unrecognized construct", @@ -953,15 +1044,15 @@ class Config(object): def _parse_cond(self, tokens, line, filename, linenr): """ Parses an optional 'if ' construct and returns the parsed , - or None if the next token is not _T_IF + or "y" if the next token is not _T_IF """ return self._parse_expr(tokens, line, filename, linenr, True) \ - if tokens.check(_T_IF) else None + if tokens.check(_T_IF) else "y" def _parse_val_and_cond(self, tokens, line, filename, linenr): """ Parses ' if ' constructs, where the 'if' part is - optional. Returns a tuple containing the parsed expressions, with None + optional. Returns a tuple containing the parsed expressions, with "y" as the second element if the 'if' part is missing. """ return (self._parse_expr(tokens, line, filename, linenr, False), @@ -1001,7 +1092,7 @@ class Config(object): # Menu node dependency from 'depends on'. Will get propagated to the # properties above. - node.dep = None + node.dep = "y" # The cached (line, tokens) tuple that we return last_line = None @@ -1145,7 +1236,7 @@ class Config(object): "service.".format(node.item.name, env_var), filename, linenr) else: - defaults.append((os.environ[env_var], None)) + defaults.append((os.environ[env_var], "y")) elif tokens.check(_T_DEFCONFIG_LIST): if self.defconfig_list is None: @@ -1272,12 +1363,12 @@ class Config(object): Parses an expression from the tokens in 'feed' using a simple top-down approach. The result has the form '( )' where is e.g. - kconfiglib._AND. If there is only one operand (i.e., no && or ||), then + kconfiglib.AND. If there is only one operand (i.e., no && or ||), then the operand is returned directly. This also goes for subexpressions. As an example, A && B && (!C || D == 3) is represented as the tuple - structure (_AND, A, (_AND, B, (_OR, (_NOT, C), (_EQUAL, D, 3)))), with - the Symbol objects stored directly in the expression. + structure (AND, A, (AND, B, (OR, (NOT, C), (EQUAL, D, 3)))), with the + Symbol objects stored directly in the expression. feed: _Feed instance containing the tokens for the expression. @@ -1326,24 +1417,23 @@ class Config(object): transform_m) # Return 'and_expr' directly if we have a "single-operand" OR. - # Otherwise, parse the expression on the right and make an _OR node. - # This turns A || B || C || D into - # (_OR, A, (_OR, B, (_OR, C, D))). + # Otherwise, parse the expression on the right and make an OR node. + # This turns A || B || C || D into (OR, A, (OR, B, (OR, C, D))). return and_expr \ if not feed.check(_T_OR) else \ - (_OR, and_expr, self._parse_expr(feed, line, filename, linenr, - transform_m)) + (OR, and_expr, self._parse_expr(feed, line, filename, linenr, + transform_m)) def _parse_and_expr(self, feed, line, filename, linenr, transform_m): factor = self._parse_factor(feed, line, filename, linenr, transform_m) # Return 'factor' directly if we have a "single-operand" AND. - # Otherwise, parse the right operand and make an _AND node. This turns - # A && B && C && D into (_AND, A, (_AND, B, (_AND, C, D))). + # Otherwise, parse the right operand and make an AND node. This turns + # A && B && C && D into (AND, A, (AND, B, (AND, C, D))). return factor \ if not feed.check(_T_AND) else \ - (_AND, factor, self._parse_and_expr(feed, line, filename, - linenr, transform_m)) + (AND, factor, self._parse_and_expr(feed, line, filename, + linenr, transform_m)) def _parse_factor(self, feed, line, filename, linenr, transform_m): token = feed.next() @@ -1359,7 +1449,7 @@ class Config(object): # '... if ', etc.), "m" and m are rewritten to # "m" && MODULES. if transform_m and token == "m": - return (_AND, "m", self.modules) + return (AND, "m", self.modules) return token @@ -1367,8 +1457,8 @@ class Config(object): return (_TOKEN_TO_REL[feed.next()], token, feed.next()) if token == _T_NOT: - return (_NOT, self._parse_factor(feed, line, filename, linenr, - transform_m)) + return (NOT, self._parse_factor(feed, line, filename, linenr, + transform_m)) if token == _T_OPEN_PAREN: expr_parse = self._parse_expr(feed, line, filename, @@ -1414,7 +1504,7 @@ class Config(object): config_strings = [] add_fn = config_strings.append - node = self.top_menu.list + node = self.top_node.list if node is None: # Empty configuration return config_strings @@ -1424,7 +1514,7 @@ class Config(object): # .config entry. We reset it prior to writing out a new .config. It # only needs to be reset for defined symbols, because undefined symbols # will never be written out (because they do not appear structure - # rooted at Config.top_menu). + # rooted at Config.top_node). # # The C tools reuse _write_to_conf for this, but we cache # _write_to_conf together with the value and don't invalidate cached @@ -1731,12 +1821,18 @@ class Symbol(object): locations, the dependencies at each location are ORed together. env_var: - If the Symbol is set from the environment via 'option env="FOO"', this - contains the name ("FOO") of the environment variable. None for symbols - that aren't set from the environment. + If the Symbol has an 'option env="FOO"' option, this contains the name + ("FOO") of the environment variable. None for symbols that aren't set + from the environment. + + 'option env="FOO"' acts as a 'default' property whose value is the value + of $FOO. - Internally, this is only used to print the symbol. The value of the - environment variable is looked up once when the configuration is parsed. + env_var is also set (to "") on the predefined symbol + UNAME_RELEASE, which holds the 'release' field from uname. + + Symbols with an 'option env' option are never written out to .config + files. is_allnoconfig_y: True if the symbol has 'option allnoconfig_y' set on it. This has no @@ -1815,7 +1911,7 @@ class Symbol(object): if vis != "n" and self.user_value is not None: # If the symbol is visible and has a user value, we use # that - val = _eval_min(self.user_value, vis) + val = _tri_min(self.user_value, vis) else: # Otherwise, we look at defaults and weak reverse @@ -1825,7 +1921,7 @@ class Symbol(object): cond_val = eval_expr(cond) if cond_val != "n": self._write_to_conf = True - val = _eval_min(default, cond_val) + val = _tri_min(eval_expr(default), cond_val) break # Weak reverse dependencies are only considered if our @@ -1835,13 +1931,13 @@ class Symbol(object): eval_expr(self.weak_rev_dep) if weak_rev_dep_val != "n": self._write_to_conf = True - val = _eval_max(val, weak_rev_dep_val) + val = _tri_max(val, weak_rev_dep_val) # Reverse (select-related) dependencies take precedence rev_dep_val = eval_expr(self.rev_dep) if rev_dep_val != "n": self._write_to_conf = True - val = _eval_max(val, rev_dep_val) + val = _tri_max(val, rev_dep_val) else: # (bool/tristate) symbol in choice. See _get_visibility() for @@ -2082,7 +2178,7 @@ class Symbol(object): if self.is_allnoconfig_y: fields.append("allnoconfig_y") - if self is self.config.defconfig_list: + if self is self.config.defconfig_list: fields.append("is the defconfig_list symbol") if self.env_var is not None: @@ -2239,8 +2335,8 @@ class Symbol(object): if self.choice is not None and self._type in (BOOL, TRISTATE): if value == "y": - self.choice.user_selection = self self.choice.user_value = "y" + self.choice.user_selection = self elif value == "m": self.choice.user_value = "m" @@ -2441,7 +2537,7 @@ class Choice(object): See the class documentation. """ if self.user_value is not None: - val = _eval_min(self.user_value, self.visibility) + val = _tri_min(self.user_value, self.visibility) else: val = "n" @@ -2592,8 +2688,8 @@ class Choice(object): self.nodes = [] - self.user_selection = None self.user_value = None + self.user_selection = None # The prompts and default values without any dependencies from # enclosing menus and ifs propagated @@ -2638,7 +2734,7 @@ class MenuNode(object): menu node for each location. The top-level menu node, corresponding to the implicit top-level menu, is - available in Config.top_menu. + available in Config.top_node. For symbols and choices, the menu nodes are available in the 'nodes' attribute. Menus and comments are represented as plain menu nodes, with @@ -2819,12 +2915,9 @@ def tri_greater_eq(v1, v2): def eval_expr(expr): """ - Evaluates an expression to "n", "m", or "y". Returns "y" for None, which - makes sense as None usually indicates a missing condition. + Evaluates an expression to "n", "m", or "y". """ - return "y" if expr is None else _eval_expr_rec(expr) -def _eval_expr_rec(expr): if isinstance(expr, Symbol): # Non-bool/tristate symbols are always "n" in a tristate sense, # regardless of their value @@ -2833,28 +2926,22 @@ def _eval_expr_rec(expr): if isinstance(expr, str): return expr if expr in ("m", "y") else "n" - if expr[0] == _AND: - ev1 = _eval_expr_rec(expr[1]) - if ev1 == "n": - # No need to look at expr[2] - return "n" - ev2 = _eval_expr_rec(expr[2]) - return ev2 if ev1 == "y" else \ - "m" if ev2 != "n" else \ - "n" - - if expr[0] == _OR: - ev1 = _eval_expr_rec(expr[1]) - if ev1 == "y": - # No need to look at expr[2] - return "y" - ev2 = _eval_expr_rec(expr[2]) - return ev2 if ev1 == "n" else \ - "y" if ev2 == "y" else \ - "m" + if expr[0] == AND: + ev1 = eval_expr(expr[1]) + + # Short-circuit the ev1 == "n" case + return "n" if ev1 == "n" else \ + _tri_min(ev1, eval_expr(expr[2])) - if expr[0] == _NOT: - ev = _eval_expr_rec(expr[1]) + if expr[0] == OR: + ev1 = eval_expr(expr[1]) + + # Short-circuit the ev1 == "y" case + return "y" if ev1 == "y" else \ + _tri_max(ev1, eval_expr(expr[2])) + + if expr[0] == NOT: + ev = eval_expr(expr[1]) return "n" if ev == "y" else \ "y" if ev == "n" else \ "m" @@ -2884,16 +2971,16 @@ def _eval_expr_rec(expr): # They're not both valid numbers. If the comparison is # anything but = or !=, return 'n'. Otherwise, reuse # _strcmp() to check for (in)equality. - if oper not in (_EQUAL, _UNEQUAL): + if oper not in (EQUAL, UNEQUAL): return "n" comp = _strcmp(op1_str, op2_str) - if oper == _EQUAL: res = comp == 0 - elif oper == _UNEQUAL: res = comp != 0 - elif oper == _LESS: res = comp < 0 - elif oper == _LESS_EQUAL: res = comp <= 0 - elif oper == _GREATER: res = comp > 0 - elif oper == _GREATER_EQUAL: res = comp >= 0 + if oper == EQUAL: res = comp == 0 + elif oper == UNEQUAL: res = comp != 0 + elif oper == LESS: res = comp < 0 + elif oper == LESS_EQUAL: res = comp <= 0 + elif oper == GREATER: res = comp > 0 + elif oper == GREATER_EQUAL: res = comp >= 0 return "y" if res else "n" @@ -2997,7 +3084,7 @@ def _get_visibility(sc): for node in sc.nodes: if node.prompt: - vis = _eval_max(vis, node.prompt[1]) + vis = _tri_max(vis, eval_expr(node.prompt[1])) if isinstance(sc, Symbol) and sc.choice is not None: if sc.choice._type == TRISTATE and sc._type != TRISTATE and \ @@ -3011,7 +3098,7 @@ def _get_visibility(sc): # choice has mode "y" return "n" - vis = _eval_min(vis, sc.choice.visibility) + vis = _tri_min(vis, sc.choice.visibility) # Promote "m" to "y" if we're dealing with a non-tristate. This might lead # to infinite recursion if something really weird is done with MODULES, but @@ -3024,48 +3111,45 @@ def _get_visibility(sc): def _make_and(e1, e2): """ - Constructs an _AND (&&) expression. Performs trivial simplification. Nones - equate to 'y'. - - Returns None if e1 == e2 == None, so that ANDing two nonexistent - expressions gives a nonexistent expression. + Constructs an AND (&&) expression. Performs trivial simplification. """ - if e1 is None or e1 == "y": + if e1 == "y": return e2 - if e2 is None or e2 == "y": + + if e2 == "y": return e1 - return (_AND, e1, e2) + + if e1 == "n" or e2 == "n": + return "n" + + return (AND, e1, e2) def _make_or(e1, e2): """ - Constructs an _OR (||) expression. Performs trivial simplification and - avoids Nones. Nones equate to 'y', which is usually what we want, but needs - to be kept in mind. + Constructs an OR (||) expression. Performs trivial simplification. """ - - # Perform trivial simplification and avoid None's (which - # correspond to y's) - if e1 is None or e2 is None or e1 == "y" or e2 == "y": - return "y" if e1 == "n": return e2 - return (_OR, e1, e2) -def _eval_min(e1, e2): + if e2 == "n": + return e1 + + if e1 == "y" or e2 == "y": + return "y" + + return (OR, e1, e2) + +def _tri_min(v1, v2): """ - Returns the minimum value of the two expressions. Equates None with 'y'. + Returns the smallest tristate value among v1 and v2. """ - e1_eval = eval_expr(e1) - e2_eval = eval_expr(e2) - return e1_eval if tri_less(e1_eval, e2_eval) else e2_eval + return v1 if _TRI_TO_INT[v1] <= _TRI_TO_INT[v2] else v2 -def _eval_max(e1, e2): +def _tri_max(v1, v2): """ - Returns the maximum value of the two expressions. Equates None with 'y'. + Returns the largest tristate value among v1 and v2. """ - e1_eval = eval_expr(e1) - e2_eval = eval_expr(e2) - return e1_eval if tri_greater(e1_eval, e2_eval) else e2_eval + return v1 if _TRI_TO_INT[v1] >= _TRI_TO_INT[v2] else v2 def _expr_syms_rec(expr, res): """ @@ -3075,10 +3159,10 @@ def _expr_syms_rec(expr, res): res.append(expr) elif isinstance(expr, str): return - elif expr[0] in (_AND, _OR): + elif expr[0] in (AND, OR): _expr_syms_rec(expr[1], res) _expr_syms_rec(expr[2], res) - elif expr[0] == _NOT: + elif expr[0] == NOT: _expr_syms_rec(expr[1], res) elif expr[0] in _RELATIONS: if isinstance(expr[1], Symbol): @@ -3106,9 +3190,9 @@ def _str_val(obj): def _format_and_op(expr): """ _expr_to_str() helper. Returns the string representation of 'expr', which - is assumed to be an operand to _AND, with parentheses added if needed. + is assumed to be an operand to AND, with parentheses added if needed. """ - if isinstance(expr, tuple) and expr[0] == _OR: + if isinstance(expr, tuple) and expr[0] == OR: return "({})".format(_expr_to_str(expr)) return _expr_to_str(expr) @@ -3122,16 +3206,16 @@ def _expr_to_str(expr): if isinstance(expr, Symbol): return expr.name - if expr[0] == _NOT: + if expr[0] == NOT: if isinstance(expr[1], (str, Symbol)): return "!" + _expr_to_str(expr[1]) return "!({})".format(_expr_to_str(expr[1])) - if expr[0] == _AND: + if expr[0] == AND: return "{} && {}".format(_format_and_op(expr[1]), _format_and_op(expr[2])) - if expr[0] == _OR: + if expr[0] == OR: return "{} || {}".format(_expr_to_str(expr[1]), _expr_to_str(expr[2])) @@ -3251,7 +3335,7 @@ def _sym_choice_str(sc): if node.prompt is not None: prompt_str = 'prompt "{}"'.format(node.prompt[0]) - if node.prompt[1] is not None: + if node.prompt[1] != "y": prompt_str += " if " + _expr_to_str(node.prompt[1]) indent_add(prompt_str) @@ -3259,11 +3343,11 @@ def _sym_choice_str(sc): if isinstance(sc, Symbol): if sc.is_allnoconfig_y: indent_add("option allnoconfig_y") - if sc is sc.config.defconfig_list: + if sc is sc.config.defconfig_list: indent_add("option defconfig_list") if sc.env_var is not None: indent_add('option env="{}"'.format(sc.env_var)) - if sc is sc.config.modules: + if sc is sc.config.modules: indent_add("option modules") if isinstance(sc, Symbol): @@ -3271,13 +3355,13 @@ def _sym_choice_str(sc): range_string = "range {} {}" \ .format(_expr_to_str(range_[0]), _expr_to_str(range_[1])) - if range_[2] is not None: + if range_[2] != "y": range_string += " if " + _expr_to_str(range_[2]) indent_add(range_string) for default in sc.defaults: default_string = "default " + _expr_to_str(default[0]) - if default[1] is not None: + if default[1] != "y": default_string += " if " + _expr_to_str(default[1]) indent_add(default_string) @@ -3287,13 +3371,13 @@ def _sym_choice_str(sc): if isinstance(sc, Symbol): for select in sc.selects: select_string = "select " + select[0].name - if select[1] is not None: + if select[1] != "y": select_string += " if " + _expr_to_str(select[1]) indent_add(select_string) for imply in sc.implies: imply_string = "imply " + imply[0].name - if imply[1] is not None: + if imply[1] != "y": imply_string += " if " + _expr_to_str(imply[1]) indent_add(imply_string) @@ -3322,8 +3406,8 @@ def _eq_to_sym(eq): left, right = right, left if not isinstance(left, Symbol): return None - if (relation == _EQUAL and right in ("m", "y")) or \ - (relation == _UNEQUAL and right == "n"): + if (relation == EQUAL and right in ("m", "y")) or \ + (relation == UNEQUAL and right == "n"): return left return None @@ -3342,9 +3426,9 @@ def _expr_depends_on(expr, sym): if isinstance(expr, Symbol): return expr is sym - if expr[0] in (_EQUAL, _UNEQUAL): + if expr[0] in (EQUAL, UNEQUAL): return _eq_to_sym(expr) is sym - if expr[0] == _AND: + if expr[0] == AND: return rec(expr[1]) or rec(expr[2]) return False @@ -3494,13 +3578,6 @@ def _finalize_tree(node): # Public global constants # -# Integers representing menu and comment nodes - -( - MENU, - COMMENT, -) = range(2) - # Integers representing symbol types ( BOOL, @@ -3511,6 +3588,25 @@ def _finalize_tree(node): UNKNOWN ) = range(6) +# Integers representing expression types +( + AND, + OR, + NOT, + EQUAL, + UNEQUAL, + LESS, + LESS_EQUAL, + GREATER, + GREATER_EQUAL, +) = range(9) + +# Integers representing menu and comment menu nodes +( + MENU, + COMMENT, +) = range(2) + # # Internal global constants # @@ -3679,19 +3775,6 @@ _DEFAULT_VALUE = { # will do) for it so we can test with 'is'. _NO_CACHED_SELECTION = object() -# Integers representing expression types -( - _AND, - _OR, - _NOT, - _EQUAL, - _UNEQUAL, - _LESS, - _LESS_EQUAL, - _GREATER, - _GREATER_EQUAL, -) = range(9) - # Used in comparisons. 0 means the base is inferred from the format of the # string. The entries for BOOL and TRISTATE are a convenience - they should # never convert to valid numbers. @@ -3712,29 +3795,29 @@ _TRI_TO_INT = { } _RELATIONS = frozenset(( - _EQUAL, - _UNEQUAL, - _LESS, - _LESS_EQUAL, - _GREATER, - _GREATER_EQUAL, + EQUAL, + UNEQUAL, + LESS, + LESS_EQUAL, + GREATER, + GREATER_EQUAL, )) # Token to relation (=, !=, <, ...) mapping _TOKEN_TO_REL = { - _T_EQUAL: _EQUAL, - _T_GREATER: _GREATER, - _T_GREATER_EQUAL: _GREATER_EQUAL, - _T_LESS: _LESS, - _T_LESS_EQUAL: _LESS_EQUAL, - _T_UNEQUAL: _UNEQUAL, + _T_EQUAL: EQUAL, + _T_GREATER: GREATER, + _T_GREATER_EQUAL: GREATER_EQUAL, + _T_LESS: LESS, + _T_LESS_EQUAL: LESS_EQUAL, + _T_UNEQUAL: UNEQUAL, } _RELATION_TO_STR = { - _EQUAL: "=", - _GREATER: ">", - _GREATER_EQUAL: ">=", - _LESS: "<", - _LESS_EQUAL: "<=", - _UNEQUAL: "!=", + EQUAL: "=", + GREATER: ">", + GREATER_EQUAL: ">=", + LESS: "<", + LESS_EQUAL: "<=", + UNEQUAL: "!=", } diff --git a/testsuite.py b/testsuite.py index 69eae6e..4eba62a 100644 --- a/testsuite.py +++ b/testsuite.py @@ -110,7 +110,7 @@ def get_items(config, type_): items.append(node.item) rec(node.list) rec(node.next) - rec(config.top_menu) + rec(config.top_node) return items def get_comments(config): @@ -121,7 +121,7 @@ def get_comments(config): items.append(node) rec(node.list) rec(node.next) - rec(config.top_menu) + rec(config.top_node) return items def get_menus(config): @@ -132,7 +132,7 @@ def get_menus(config): items.append(node) rec(node.list) rec(node.next) - rec(config.top_menu) + rec(config.top_node) return items def get_choices(config): @@ -1803,7 +1803,7 @@ def run_compatibility_tests(): if all_passed: print("All selftests and compatibility tests passed") - print(nconfigs, "arch/defconfig pairs tested") + print("{} arch/defconfig pairs tested".format(nconfigs)) else: print("Some tests failed") -- cgit v1.2.3 From a90d18d8dca4e50b6f305599caa96828a520be01 Mon Sep 17 00:00:00 2001 From: Ulf Magnusson Date: Wed, 25 Oct 2017 22:41:57 +0200 Subject: Backup --- kconfiglib.py | 648 ++++++++++++++++++++++++++++++---------------------------- testsuite.py | 65 +++--- 2 files changed, 372 insertions(+), 341 deletions(-) (limited to 'testsuite.py') diff --git a/kconfiglib.py b/kconfiglib.py index 04edb67..0d92b48 100644 --- a/kconfiglib.py +++ b/kconfiglib.py @@ -110,16 +110,17 @@ A || B (OR, A, B) A = B (EQUAL, A, B) A != "foo" (UNEQUAL, A, "foo") A || (B && C && D) (OR, A, (AND, B, (AND, C, D))) -y "y" -"y" "y" +y config.y +"y" config.y -As seen in the final two examples, n/m/y are always represented as the strings -(constant symbols) "n"/"m"/"y" in Kconfiglib, regardless of whether they're -written with or without quotes. This simplifies some internals. +As seen in the final two examples, n/m/y are always represented as the constant +symbols config.n/m/y, regardless of whether they're written with or without +quotes. 'config' represents the Config instance of the configuration the +expression appears in. A missing expression (e.g. if the 'if ' part is removed from -'default A if ') is represented as "y". The standard __str__() functions -avoid printing 'if y' conditions to give cleaner output. +'default A if ') is represented as config.y. The standard __str__() +functions avoid printing 'if y' conditions to give cleaner output. Implementation note ------------------- @@ -131,6 +132,8 @@ Send bug reports, suggestions, and questions to ulfalizer a.t Google's email service (or open a ticket on the GitHub page). """ +# TODO: document n/m/y + import errno import os import platform @@ -253,13 +256,17 @@ class Config(object): "_set_re", "_unset_re", "config_prefix", + "const_syms", "defconfig_list", "defined_syms", + "m", "modules", + "n", "named_choices", "srctree", "syms", "top_node", + "y", ) # @@ -308,20 +315,40 @@ class Config(object): self._print_undef_assign = False self.syms = {} + self.const_syms = {} self.defined_syms = [] self.named_choices = {} # Used for quickly invalidating all choices self._choices = [] - self.modules = self._lookup_sym("MODULES") + for nmy in "n", "m", "y": + sym = Symbol() + sym.config = self + sym.name = nmy + sym.is_constant = True + sym._type = TRISTATE + sym._cached_val = nmy + + self.const_syms[nmy] = sym + + self.n = self.const_syms["n"] + self.m = self.const_syms["m"] + self.y = self.const_syms["y"] + + # Just to make n/m/y well-formed symbols + for nmy in "n", "m", "y": + sym = self.const_syms[nmy] + sym.rev_dep = sym.weak_rev_dep = sym.direct_dep = self.n + + self.modules = self._lookup_sym("MODULES", False) self.defconfig_list = None # Predefined symbol. DEFCONFIG_LIST uses this. - uname_sym = Symbol() + uname_sym = self._lookup_const_sym("UNAME_RELEASE", False) uname_sym._type = STRING - uname_sym.name = "UNAME_RELEASE" - uname_sym.config = self - uname_sym.defaults.append((platform.uname()[2], "y")) + uname_sym.defaults.append( + (self._lookup_const_sym(platform.uname()[2], False), + self.y)) # env_var doubles as the SYMBOL_AUTO flag from the C implementation, so # just set it to something. The naming breaks a bit here, but it's # pretty obscure. @@ -331,10 +358,10 @@ class Config(object): self.top_node = MenuNode() self.top_node.config = self self.top_node.item = MENU - self.top_node.visibility = None - self.top_node.prompt = ("Linux Kernel Configuration", "y") + self.top_node.visibility = self.y + self.top_node.prompt = ("Linux Kernel Configuration", self.y) self.top_node.parent = None - self.top_node.dep = "y" + self.top_node.dep = self.y self.top_node.filename = filename self.top_node.linenr = 1 @@ -342,7 +369,7 @@ class Config(object): self._parse_block(_FileFeed(self._open(filename), filename), None, # end_token self.top_node, # parent - "y", # visible_if_deps + self.y, # visible_if_deps None, # prev_line self.top_node) # prev_node @@ -372,7 +399,7 @@ class Config(object): return None for filename, cond_expr in self.defconfig_list.defaults: if eval_expr(cond_expr) != "n": - filename = self._expand_sym_refs(filename) + filename = self._expand_sym_refs(filename.value) try: with self._open(filename) as f: return f.name @@ -502,11 +529,17 @@ class Config(object): conditional ('if ...') expressions in the configuration (as well as in the C tools). m is rewritten to 'm && MODULES'. """ - return eval_expr(self._parse_expr(self._tokenize(s, True), - s, - None, # filename - None, # linenr - True)) # transform_m + return eval_expr( + self._parse_expr( + self._tokenize( + s, + True, # for_eval_string + None, # filename + None), # linenr + s, + None, # filename + None, # linenr + True)) # transform_m def unset_values(self): """ @@ -612,7 +645,7 @@ class Config(object): # Kconfig parsing # - def _tokenize(self, s, for_eval, filename=None, linenr=None): + def _tokenize(self, s, for_eval_string, filename, linenr): """ Returns a _Feed instance containing tokens derived from the string 's'. Registers any new symbols encountered (via _lookup_sym()). @@ -620,7 +653,7 @@ class Config(object): Tries to be reasonably speedy by processing chunks of text via regexes and string operations where possible. This is a hotspot during parsing. - for_eval: + for_eval_string: True when parsing an expression for a call to Config.eval_string(), in which case we should not treat the first token specially nor register new symbols. @@ -629,7 +662,7 @@ class Config(object): # Tricky implementation detail: While parsing a token, 'token' refers # to the previous token. See _NOT_REF for why this is needed. - if for_eval: + if for_eval_string: token = None tokens = [] @@ -678,13 +711,14 @@ class Config(object): token = keyword elif token not in _STRING_LEX: - # It's a symbol + # It's a non-const symbol... if name in ("n", "m", "y"): - # Always represent n, m, y as strings (constant - # symbols). This simplifies the expression logic. - token = name + # ...except we translate n, m, and y into the + # corresponding constant symbols, like the C + # implementation + token = self._lookup_const_sym(name, for_eval_string) else: - token = self._lookup_sym(name, for_eval) + token = self._lookup_sym(name, for_eval_string) else: # It's a case of missing quotes. For example, the @@ -699,7 +733,7 @@ class Config(object): token = name else: - # Not an identifier/keyword + # Not keyword/non-const symbol # Note: _id_keyword_match and _initial_token_match strip # trailing whitespace, making it safe to assume s[i] is the @@ -722,7 +756,7 @@ class Config(object): end = s.find(c, i) if end == -1: _tokenization_error(s, filename, linenr) - token = s[i:end] + val = s[i:end] i = end + 1 else: # Slow path: This could probably be sped up, but it's a @@ -744,7 +778,17 @@ class Config(object): val += c i += 1 i += 1 - token = val + + # This is the only place where we don't survive with a + # single token of lookback, hence the kludge: + # 'option env="FOO"' does not refer to a constant symbol + # named "FOO". + token = \ + val \ + if token in _STRING_LEX or \ + (not for_eval_string and \ + tokens[0] == _T_OPTION) else \ + self._lookup_const_sym(val, for_eval_string) elif c == "&": # Invalid characters are ignored @@ -935,10 +979,10 @@ class Config(object): node.filename = line_feeder.filename node.linenr = line_feeder.linenr node.dep = \ - _make_and(parent.dep, - self._parse_expr(tokens, line, - line_feeder.filename, - line_feeder.linenr, True)) + self._make_and(parent.dep, + self._parse_expr(tokens, line, + line_feeder.filename, + line_feeder.linenr, True)) self._parse_block(line_feeder, _T_ENDIF, @@ -956,7 +1000,7 @@ class Config(object): node = MenuNode() node.config = self node.item = MENU - node.visibility = "y" + node.visibility = self.y node.parent = parent node.filename = line_feeder.filename node.linenr = line_feeder.linenr @@ -968,7 +1012,8 @@ class Config(object): self._parse_block(line_feeder, _T_ENDMENU, node, # parent - _make_and(visible_if_deps, node.visibility), + self._make_and(visible_if_deps, + node.visibility), prev_line, node) # prev_node node.list = node.next @@ -1033,7 +1078,7 @@ class Config(object): prev_node.next = prev_node = node elif t0 == _T_MAINMENU: - self.top_node.prompt = (tokens.next(), "y") + self.top_node.prompt = (tokens.next(), self.y) self.top_node.filename = line_feeder.filename self.top_node.linenr = line_feeder.linenr @@ -1044,16 +1089,16 @@ class Config(object): def _parse_cond(self, tokens, line, filename, linenr): """ Parses an optional 'if ' construct and returns the parsed , - or "y" if the next token is not _T_IF + or self.y if the next token is not _T_IF """ return self._parse_expr(tokens, line, filename, linenr, True) \ - if tokens.check(_T_IF) else "y" + if tokens.check(_T_IF) else self.y def _parse_val_and_cond(self, tokens, line, filename, linenr): """ Parses ' if ' constructs, where the 'if' part is - optional. Returns a tuple containing the parsed expressions, with "y" - as the second element if the 'if' part is missing. + optional. Returns a tuple containing the parsed expressions, with + config.y as the second element if the 'if' part is missing. """ return (self._parse_expr(tokens, line, filename, linenr, False), self._parse_cond(tokens, line, filename, linenr)) @@ -1092,7 +1137,7 @@ class Config(object): # Menu node dependency from 'depends on'. Will get propagated to the # properties above. - node.dep = "y" + node.dep = self.y # The cached (line, tokens) tuple that we return last_line = None @@ -1117,9 +1162,9 @@ class Config(object): filename, linenr) node.dep = \ - _make_and(node.dep, - self._parse_expr(tokens, line, filename, - linenr, True)) + self._make_and(node.dep, + self._parse_expr(tokens, line, filename, + linenr, True)) elif t0 == _T_HELP: # Find first non-blank (not all-space) line and get its @@ -1164,34 +1209,24 @@ class Config(object): _parse_error(line, "only symbols can select", filename, linenr) - # HACK: We always represent n/m/y using the constant symbol - # "n"/"m"/"y" forms, but that causes a crash if a Kconfig file - # does e.g. 'select n' (which is meaningless and probably stems - # from a misunderstanding). Seen in U-Boot. Just skip the - # select. - target = tokens.next() - if target not in ("n", "m", "y"): - selects.append( - (target, - self._parse_cond(tokens, line, filename, linenr))) + selects.append( + (tokens.next(), + self._parse_cond(tokens, line, filename, linenr))) elif t0 == _T_IMPLY: if not isinstance(node.item, Symbol): _parse_error(line, "only symbols can imply", filename, linenr) - # See above - target = tokens.next() - if target not in ("n", "m", "y"): - implies.append( - (target, - self._parse_cond(tokens, line, filename, linenr))) + implies.append( + (tokens.next(), + self._parse_cond(tokens, line, filename, linenr))) elif t0 in (_T_BOOL, _T_TRISTATE, _T_INT, _T_HEX, _T_STRING): node.item._type = _TOKEN_TO_TYPE[t0] if tokens.peek() is not None: - prompt = self._parse_val_and_cond(tokens, line, - filename, linenr) + prompt = (tokens.next(), + self._parse_cond(tokens, line, filename, linenr)) elif t0 == _T_DEFAULT: defaults.append( @@ -1199,17 +1234,16 @@ class Config(object): elif t0 in (_T_DEF_BOOL, _T_DEF_TRISTATE): node.item._type = _TOKEN_TO_TYPE[t0] - if tokens.peek() is not None: - defaults.append( - self._parse_val_and_cond(tokens, line, filename, - linenr)) + defaults.append( + self._parse_val_and_cond(tokens, line, filename, + linenr)) elif t0 == _T_PROMPT: # 'prompt' properties override each other within a single # definition of a symbol, but additional prompts can be added # by defining the symbol multiple times - prompt = self._parse_val_and_cond(tokens, line, filename, - linenr) + prompt = (tokens.next(), + self._parse_cond(tokens, line, filename, linenr)) elif t0 == _T_RANGE: ranges.append( @@ -1236,7 +1270,10 @@ class Config(object): "service.".format(node.item.name, env_var), filename, linenr) else: - defaults.append((os.environ[env_var], "y")) + defaults.append( + (self._lookup_const_sym(os.environ[env_var], + False), + self.y)) elif tokens.check(_T_DEFCONFIG_LIST): if self.defconfig_list is None: @@ -1283,9 +1320,9 @@ class Config(object): filename, linenr) node.visibility = \ - _make_and(node.visibility, - self._parse_expr(tokens, line, filename, linenr, - True)) + self._make_and(node.visibility, + self._parse_expr(tokens, line, filename, + linenr, True)) elif t0 == _T_OPTIONAL: if not isinstance(node.item, Choice): @@ -1306,54 +1343,57 @@ class Config(object): # from node.dep propagated. # First propagate parent dependencies to node.dep - node.dep = _make_and(node.dep, node.parent.dep) + node.dep = self._make_and(node.dep, node.parent.dep) if isinstance(node.item, (Symbol, Choice)): if isinstance(node.item, Symbol): - node.item.direct_deps = \ - _make_or(node.item.direct_deps, node.dep) + node.item.direct_dep = \ + self._make_or(node.item.direct_dep, node.dep) # Set the prompt, with dependencies propagated if prompt is not None: node.prompt = (prompt[0], - _make_and(_make_and(prompt[1], node.dep), - visible_if_deps)) + self._make_and(self._make_and(prompt[1], + node.dep), + visible_if_deps)) else: node.prompt = None # Add the new defaults, with dependencies propagated for val_expr, cond_expr in defaults: node.item.defaults.append( - (val_expr, _make_and(cond_expr, node.dep))) + (val_expr, self._make_and(cond_expr, node.dep))) # Add the new ranges, with dependencies propagated for low, high, cond_expr in ranges: node.item.ranges.append( - (low, high, _make_and(cond_expr, node.dep))) + (low, high, self._make_and(cond_expr, node.dep))) # Handle selects for target, cond_expr in selects: # Only stored for convenience. Not used during evaluation. node.item.selects.append( - (target, _make_and(cond_expr, node.dep))) + (target, self._make_and(cond_expr, node.dep))) # Modify the dependencies of the selected symbol target.rev_dep = \ - _make_or(target.rev_dep, - _make_and(node.item, - _make_and(cond_expr, node.dep))) + self._make_or(target.rev_dep, + self._make_and(node.item, + self._make_and(cond_expr, + node.dep))) # Handle implies for target, cond_expr in implies: # Only stored for convenience. Not used during evaluation. node.item.implies.append( - (target, _make_and(cond_expr, node.dep))) + (target, self._make_and(cond_expr, node.dep))) # Modify the dependencies of the implied symbol target.weak_rev_dep = \ - _make_or(target.weak_rev_dep, - _make_and(node.item, - _make_and(cond_expr, node.dep))) + self._make_or(target.weak_rev_dep, + self._make_and(node.item, + self._make_and(cond_expr, + node.dep))) # Return cached non-property line return last_line @@ -1438,7 +1478,7 @@ class Config(object): def _parse_factor(self, feed, line, filename, linenr, transform_m): token = feed.next() - if isinstance(token, (Symbol, str)): + if isinstance(token, Symbol): # Plain symbol or relation next_token = feed.peek() @@ -1448,8 +1488,8 @@ class Config(object): # For conditional expressions ('depends on ', # '... if ', etc.), "m" and m are rewritten to # "m" && MODULES. - if transform_m and token == "m": - return (AND, "m", self.modules) + if transform_m and token is self.m: + return (AND, self.m, self.modules) return token @@ -1473,12 +1513,12 @@ class Config(object): # Symbol lookup # - def _lookup_sym(self, name, for_eval=False): + def _lookup_sym(self, name, for_eval_string): """ Fetches the symbol 'name' from the symbol table, creating and - registering it if it does not exist. If 'for_eval' is True, the symbol - won't be added to the symbol table if it does not exist. This is for - Config.eval_string(). + registering it if it does not exist. TODO If 'for_eval_string' is True, + the symbol won't be added to the symbol table if it does not exist. + This is for Config.eval_string(). """ if name in self.syms: return self.syms[name] @@ -1486,10 +1526,32 @@ class Config(object): sym = Symbol() sym.config = self sym.name = name - if for_eval: + sym.is_constant = False + sym.rev_dep = sym.weak_rev_dep = sym.direct_dep = self.n + + if for_eval_string: self._warn("no symbol {} in configuration".format(name)) else: self.syms[name] = sym + + return sym + + def _lookup_const_sym(self, name, for_eval_string): + """ + TODO: say something + """ + if name in self.const_syms: + return self.const_syms[name] + + sym = Symbol() + sym.config = self + sym.name = name + sym.is_constant = True + sym.rev_dep = sym.weak_rev_dep = sym.direct_dep = self.n + + if not for_eval_string: + self.const_syms[name] = sym + return sym # @@ -1534,6 +1596,7 @@ class Config(object): elif (node.item == MENU and eval_expr(node.dep) != "n" and eval_expr(node.visibility) != "n") or \ (node.item == COMMENT and eval_expr(node.dep) != "n"): + add_fn("\n#\n# {}\n#\n".format(node.prompt[0])) # Iterative tree walk using parent pointers @@ -1557,21 +1620,14 @@ class Config(object): def _build_dep(self): """ - Populates the Symbol._direct_dependents sets, linking the symbol to the - symbols that immediately depend on it in the sense that changing the - value of the symbol might affect the values of those other symbols. - This is used for caching/invalidation purposes. The calculated sets - might be larger than necessary as we don't do any complicated analysis - of the expressions. - """ + Populates the Symbol._direct_dependents sets, which link symbols to the + symbols that immediately depend on them in the sense that changing the + value of the symbol might affect the values of the dependent symbols. + This is used for caching/invalidation purposes. - # Adds 'sym' as a directly dependent symbol to all symbols that appear - # in the expression 'expr' - def add_expr_deps(expr, sym): - res = [] - _expr_syms(expr, res) - for expr_sym in res: - expr_sym._direct_dependents.add(sym) + The calculated sets might be larger than necessary as we don't do any + complex analysis of the expressions. + """ # The directly dependent symbols of a symbol S are: # @@ -1579,13 +1635,12 @@ class Config(object): # condition), weak_rev_dep (imply condition), or ranges depend on S # # - Any symbol that has S as a direct dependency (has S in - # direct_deps). This is needed to get invalidation right for + # direct_dep). This is needed to get invalidation right for # 'imply'. # - # - Any symbols that belong to the same choice statement as S - # (these won't be included in S._direct_dependents as that makes the - # dependency graph unwieldy, but S._get_dependent() will include - # them) + # - Any symbols that belong to the same choice statement as S. These + # won't be included in S._direct_dependents as it creates dependency + # loops, but S._get_dependent() includes them. # # - Any symbols in a choice statement that depends on S @@ -1597,28 +1652,29 @@ class Config(object): for sym in self.defined_syms: for node in sym.nodes: if node.prompt is not None: - add_expr_deps(node.prompt[1], sym) + _make_depend_on(sym, node.prompt[1]) for value, cond in sym.defaults: - add_expr_deps(value, sym) - add_expr_deps(cond, sym) + _make_depend_on(sym, value) + _make_depend_on(sym, cond) - add_expr_deps(sym.rev_dep, sym) - add_expr_deps(sym.weak_rev_dep, sym) + _make_depend_on(sym, sym.rev_dep) + _make_depend_on(sym, sym.weak_rev_dep) - for l, u, e in sym.ranges: - add_expr_deps(l, sym) - add_expr_deps(u, sym) - add_expr_deps(e, sym) + for low, high, cond in sym.ranges: + _make_depend_on(sym, low) + _make_depend_on(sym, high) + _make_depend_on(sym, cond) - add_expr_deps(sym.direct_deps, sym) + _make_depend_on(sym, sym.direct_dep) if sym.choice is not None: for node in sym.choice.nodes: if node.prompt is not None: - add_expr_deps(node.prompt[1], sym) - for _, e in sym.choice.defaults: - add_expr_deps(e, sym) + _make_depend_on(sym, node.prompt[1]) + + for _, cond in sym.choice.defaults: + _make_depend_on(sym, cond) def _invalidate_all(self): # Undefined symbols never change value and don't need to be @@ -1674,6 +1730,36 @@ class Config(object): 'attempt to assign the value "{}" to the undefined symbol {}' \ .format(val, name), filename, linenr) + def _make_and(self, e1, e2): + """ + Constructs an AND (&&) expression. Performs trivial simplification. + """ + if e1 is self.y: + return e2 + + if e2 is self.y: + return e1 + + if e1 is self.n or e2 is self.n: + return self.n + + return (AND, e1, e2) + + def _make_or(self, e1, e2): + """ + Constructs an OR (||) expression. Performs trivial simplification. + """ + if e1 is self.n: + return e2 + + if e2 is self.n: + return e1 + + if e1 is self.y or e2 is self.y: + return self.y + + return (OR, e1, e2) + class Symbol(object): """ Represents a configuration symbol: @@ -1774,23 +1860,23 @@ class Symbol(object): defaults: List of (default, cond) tuples for the symbol's 'default's. For example, 'default A && B if C || D' is represented as ((AND, A, B), (OR, C, D)). - If there is no condition, 'cond' is None. + If no condition was given, 'cond' is self.config.y. Note that 'depends on' and parent dependencies are propagated to 'default' conditions. selects: List of (symbol, cond) tuples for the symbol's 'select's. For example, - 'select A if B' is represented as (A, B). If there is no condition, - 'cond' is None. + 'select A if B' is represented as (A, B). If no condition was given, + 'cond' is self.config.y. Note that 'depends on' and parent dependencies are propagated to 'select' conditions. implies: List of (symbol, cond) tuples for the symbol's 'imply's. For example, - 'imply A if B' is represented as (A, B). If there is no condition, 'cond' - is None. + 'imply A if B' is represented as (A, B). If no condition was given, + 'cond' is self.config.y. Note that 'depends on' and parent dependencies are propagated to 'imply' conditions. @@ -1798,7 +1884,7 @@ class Symbol(object): ranges: List of (low, high, cond) tuples for the symbol's 'range's. For example, 'range 1 2 if A' is represented as (1, 2, A). If there is no condition, - 'cond' is None. + 'cond' is self.config.y. Note that 'depends on' and parent dependencies are propagated to 'range' conditions. @@ -1816,7 +1902,7 @@ class Symbol(object): weak_rev_dep: Like rev_dep, for imply. - direct_deps: + direct_dep: The 'depends on' dependencies. If a symbol is defined in multiple locations, the dependencies at each location are ORed together. @@ -1854,10 +1940,11 @@ class Symbol(object): "choice", "config", "defaults", - "direct_deps", + "direct_dep", "env_var", "implies", "is_allnoconfig_y", + "is_constant", "name", "nodes", "ranges", @@ -1926,7 +2013,7 @@ class Symbol(object): # Weak reverse dependencies are only considered if our # direct dependencies are met - if eval_expr(self.direct_deps) != "n": + if eval_expr(self.direct_dep) != "n": weak_rev_dep_val = \ eval_expr(self.weak_rev_dep) if weak_rev_dep_val != "n": @@ -1974,13 +2061,10 @@ class Symbol(object): if eval_expr(cond_expr) != "n": has_active_range = True - low_str = _str_val(low_expr) - high_str = _str_val(high_expr) - - low = int(low_str, base) if \ - _is_base_n(low_str, base) else 0 - high = int(high_str, base) if \ - _is_base_n(high_str, base) else 0 + low = int(low_expr.value, base) if \ + _is_base_n(low_expr.value, base) else 0 + high = int(high_expr.value, base) if \ + _is_base_n(high_expr.value, base) else 0 break else: @@ -2009,7 +2093,7 @@ class Symbol(object): # preserved as is. Defaults that do not satisfy a range # constraints are clamped and take on a standard form. - val = _str_val(val_expr) + val = val_expr.value if _is_base_n(val, base): val_num = int(val, base) @@ -2044,7 +2128,7 @@ class Symbol(object): for val_expr, cond_expr in self.defaults: if eval_expr(cond_expr) != "n": self._write_to_conf = True - val = _str_val(val_expr) + val = val_expr.value break self._cached_val = val @@ -2187,7 +2271,7 @@ class Symbol(object): if self is self.config.modules: fields.append("is the modules symbol") - fields.append("direct deps " + eval_expr(self.direct_deps)) + fields.append("direct deps " + eval_expr(self.direct_dep)) fields.append("{} menu node{}" .format(len(self.nodes), @@ -2207,24 +2291,24 @@ class Symbol(object): # These attributes are always set on the instance from outside and # don't need defaults: + # _already_written # config + # direct_dep + # is_constant # name - # _already_written + # rev_dep + # weak_rev_dep self._type = UNKNOWN self.defaults = [] self.selects = [] self.implies = [] self.ranges = [] - self.rev_dep = "n" - self.weak_rev_dep = "n" self.nodes = [] self.user_value = None - self.direct_deps = "n" - # Populated in Config._build_dep() after parsing. Links the symbol to # the symbols that immediately depend on it (in a caching/invalidation # sense). The total set of dependent symbols for the symbol is @@ -2245,9 +2329,8 @@ class Symbol(object): # Flags - self.env_var = None - self.choice = None + self.env_var = None self.is_allnoconfig_y = False # Should the symbol get an entry in .config? Calculated along with the @@ -2488,7 +2571,7 @@ class Choice(object): defaults: List of (symbol, cond) tuples for the choices 'defaults's. For example, 'default A if B && C' is represented as (A, (AND, B, C)). If there is no - condition, 'cond' is None. + condition, 'cond' is self.config.y. Note that 'depends on' and parent dependencies are propagated to 'default' conditions. @@ -2789,7 +2872,7 @@ class MenuNode(object): visibility: The 'visible if' dependencies for the menu node (which must represent a - menu). None if there are no 'visible if' dependencies. 'visible if' + menu). config.y if there are no 'visible if' dependencies. 'visible if' dependencies are recursively propagated to the prompts of symbols and choices within the menu. @@ -2826,18 +2909,23 @@ class MenuNode(object): if isinstance(self.item, Symbol): fields.append("menu node for symbol " + self.item.name) + elif isinstance(self.item, Choice): s = "menu node for choice" if self.item.name is not None: s += " " + self.item.name fields.append(s) + elif self.item == MENU: fields.append("menu node for menu") + elif self.item == COMMENT: fields.append("menu node for comment") + elif self.item is None: fields.append("menu node for if (should not appear in the final " " tree)") + else: raise InternalError("unable to determine type in " "MenuNode.__repr__()") @@ -2923,9 +3011,6 @@ def eval_expr(expr): # regardless of their value return expr.value if expr._type in (BOOL, TRISTATE) else "n" - if isinstance(expr, str): - return expr if expr in ("m", "y") else "n" - if expr[0] == AND: ev1 = eval_expr(expr[1]) @@ -2955,25 +3040,20 @@ def eval_expr(expr): # pythonic way to structure this. oper, op1, op2 = expr - op1_type, op1_str = _type_and_val(op1) - op2_type, op2_str = _type_and_val(op2) # If both operands are strings... - if op1_type == STRING and op2_type == STRING: + if op1._type == STRING and op2._type == STRING: # ...then compare them lexicographically - comp = _strcmp(op1_str, op2_str) + comp = _strcmp(op1.value, op2.value) else: - # Otherwise, try to compare them as numbers + # Otherwise, try to compare them as numbers... try: - comp = int(op1_str, _TYPE_TO_BASE[op1_type]) - \ - int(op2_str, _TYPE_TO_BASE[op2_type]) + comp = int(op1.value, _TYPE_TO_BASE[op1._type]) - \ + int(op2.value, _TYPE_TO_BASE[op2._type]) except ValueError: - # They're not both valid numbers. If the comparison is - # anything but = or !=, return 'n'. Otherwise, reuse - # _strcmp() to check for (in)equality. - if oper not in (EQUAL, UNEQUAL): - return "n" - comp = _strcmp(op1_str, op2_str) + # Fall back on a lexicographic comparison if the operands don't + # parse as numbers + comp = _strcmp(op1.value, op2.value) if oper == EQUAL: res = comp == 0 elif oper == UNEQUAL: res = comp != 0 @@ -2987,6 +3067,26 @@ def eval_expr(expr): _internal_error("Internal error while evaluating expression: " "unknown operation {}.".format(expr[0])) +def expr_str(expr): + if isinstance(expr, Symbol): + return expr.name if not expr.is_constant else '"{}"'.format(expr.name) + + if expr[0] == NOT: + if isinstance(expr[1], Symbol): + return "!" + expr_str(expr[1]) + return "!({})".format(expr_str(expr[1])) + + if expr[0] == AND: + return "{} && {}".format(_format_and_op(expr[1]), + _format_and_op(expr[2])) + + if expr[0] == OR: + return "{} || {}".format(expr_str(expr[1]), expr_str(expr[2])) + + # Relation + return "{} {} {}".format(expr_str(expr[1]), + _RELATION_TO_STR[expr[0]], + expr_str(expr[2])) # # Internal classes @@ -3109,36 +3209,6 @@ def _get_visibility(sc): return vis -def _make_and(e1, e2): - """ - Constructs an AND (&&) expression. Performs trivial simplification. - """ - if e1 == "y": - return e2 - - if e2 == "y": - return e1 - - if e1 == "n" or e2 == "n": - return "n" - - return (AND, e1, e2) - -def _make_or(e1, e2): - """ - Constructs an OR (||) expression. Performs trivial simplification. - """ - if e1 == "n": - return e2 - - if e2 == "n": - return e1 - - if e1 == "y" or e2 == "y": - return "y" - - return (OR, e1, e2) - def _tri_min(v1, v2): """ Returns the smallest tristate value among v1 and v2. @@ -3151,88 +3221,40 @@ def _tri_max(v1, v2): """ return v1 if _TRI_TO_INT[v1] >= _TRI_TO_INT[v2] else v2 -def _expr_syms_rec(expr, res): +def _make_depend_on(sym, expr): """ - _expr_syms() helper. Recurses through expressions. + Adds 'sym' as a dependency to all symbols in 'expr'. Constant symbols in + 'expr' are skipped as they can never change value anyway. """ if isinstance(expr, Symbol): - res.append(expr) - elif isinstance(expr, str): - return + if not expr.is_constant: + expr._direct_dependents.add(sym) + elif expr[0] in (AND, OR): - _expr_syms_rec(expr[1], res) - _expr_syms_rec(expr[2], res) + _make_depend_on(sym, expr[1]) + _make_depend_on(sym, expr[2]) + elif expr[0] == NOT: - _expr_syms_rec(expr[1], res) + _make_depend_on(sym, expr[1]) + elif expr[0] in _RELATIONS: - if isinstance(expr[1], Symbol): - res.append(expr[1]) - if isinstance(expr[2], Symbol): - res.append(expr[2]) + if not expr[1].is_constant: + expr[1]._direct_dependents.add(sym) + if not expr[2].is_constant: + expr[2]._direct_dependents.add(sym) + else: _internal_error("Internal error while fetching symbols from an " "expression with token stream {}.".format(expr)) -def _expr_syms(expr, res): - """ - append()s the symbols in 'expr' to 'res'. Does not remove duplicates. - """ - if expr is not None: - _expr_syms_rec(expr, res) - -def _str_val(obj): - """ - Returns the value of obj as a string. If obj is not a string (constant - symbol), it must be a Symbol. - """ - return obj if isinstance(obj, str) else obj.value - def _format_and_op(expr): """ - _expr_to_str() helper. Returns the string representation of 'expr', which - is assumed to be an operand to AND, with parentheses added if needed. + expr_str() helper. Returns the string representation of 'expr', which is + assumed to be an operand to AND, with parentheses added if needed. """ if isinstance(expr, tuple) and expr[0] == OR: - return "({})".format(_expr_to_str(expr)) - return _expr_to_str(expr) - -def _expr_to_str(expr): - if isinstance(expr, str): - if expr in ("n", "m", "y"): - # Don't print spammy quotes for these - return expr - return '"{}"'.format(expr) - - if isinstance(expr, Symbol): - return expr.name - - if expr[0] == NOT: - if isinstance(expr[1], (str, Symbol)): - return "!" + _expr_to_str(expr[1]) - return "!({})".format(_expr_to_str(expr[1])) - - if expr[0] == AND: - return "{} && {}".format(_format_and_op(expr[1]), - _format_and_op(expr[2])) - - if expr[0] == OR: - return "{} || {}".format(_expr_to_str(expr[1]), - _expr_to_str(expr[2])) - - # Relation - return "{} {} {}".format(_expr_to_str(expr[1]), - _RELATION_TO_STR[expr[0]], - _expr_to_str(expr[2])) - -def _type_and_val(obj): - """ - Helper to hack around the fact that we don't represent plain strings as - Symbols. Takes either a plain string or a Symbol and returns a (, - ) tuple. - """ - return (obj._type, obj.value) \ - if not isinstance(obj, str) \ - else (STRING, obj) + return "({})".format(expr_str(expr)) + return expr_str(expr) def _indentation(line): """ @@ -3335,8 +3357,8 @@ def _sym_choice_str(sc): if node.prompt is not None: prompt_str = 'prompt "{}"'.format(node.prompt[0]) - if node.prompt[1] != "y": - prompt_str += " if " + _expr_to_str(node.prompt[1]) + if node.prompt[1] is not sc.config.y: + prompt_str += " if " + expr_str(node.prompt[1]) indent_add(prompt_str) if node is sc.nodes[0]: @@ -3353,16 +3375,16 @@ def _sym_choice_str(sc): if isinstance(sc, Symbol): for range_ in sc.ranges: range_string = "range {} {}" \ - .format(_expr_to_str(range_[0]), - _expr_to_str(range_[1])) - if range_[2] != "y": - range_string += " if " + _expr_to_str(range_[2]) + .format(expr_str(range_[0]), + expr_str(range_[1])) + if range_[2] is not sc.config.y: + range_string += " if " + expr_str(range_[2]) indent_add(range_string) for default in sc.defaults: - default_string = "default " + _expr_to_str(default[0]) - if default[1] != "y": - default_string += " if " + _expr_to_str(default[1]) + default_string = "default " + expr_str(default[0]) + if default[1] is not sc.config.y: + default_string += " if " + expr_str(default[1]) indent_add(default_string) if isinstance(sc, Choice) and sc.is_optional: @@ -3371,14 +3393,14 @@ def _sym_choice_str(sc): if isinstance(sc, Symbol): for select in sc.selects: select_string = "select " + select[0].name - if select[1] != "y": - select_string += " if " + _expr_to_str(select[1]) + if select[1] is not sc.config.y: + select_string += " if " + expr_str(select[1]) indent_add(select_string) for imply in sc.implies: imply_string = "imply " + imply[0].name - if imply[1] != "y": - imply_string += " if " + _expr_to_str(imply[1]) + if imply[1] is not sc.config.y: + imply_string += " if " + expr_str(imply[1]) indent_add(imply_string) if node.help is not None: @@ -3394,45 +3416,36 @@ def _sym_choice_str(sc): # Menu manipulation -def _eq_to_sym(eq): - """ - _expr_depends_on() helper. For (in)equalities of the form sym = y/m or - sym != n, returns sym. For other (in)equalities, returns None. - """ - relation, left, right = eq - - # Make sure the symbol (if any) appears to the left - if not isinstance(left, Symbol): - left, right = right, left - if not isinstance(left, Symbol): - return None - if (relation == EQUAL and right in ("m", "y")) or \ - (relation == UNEQUAL and right == "n"): - return left - return None - def _expr_depends_on(expr, sym): """ Reimplementation of expr_depends_symbol() from mconf.c. Used to determine if a submenu should be implicitly created, which influences what items inside choice statements are considered choice items. """ - if expr is None: - return False + if isinstance(expr, Symbol): + return expr is sym - def rec(expr): - if isinstance(expr, str): + if expr[0] in (EQUAL, UNEQUAL): + # Check for one of the following: + # sym = m/y, m/y = sym, sym != n, n != sym + + left, right = expr[1:] + + if right is sym: + left, right = right, left + + if left is not sym: return False - if isinstance(expr, Symbol): - return expr is sym - if expr[0] in (EQUAL, UNEQUAL): - return _eq_to_sym(expr) is sym - if expr[0] == AND: - return rec(expr[1]) or rec(expr[2]) - return False + return (expr[0] == EQUAL and right is sym.config.m or \ + right is sym.config.y) or \ + (expr[0] == UNEQUAL and right is sym.config.n) - return rec(expr) + if expr[0] == AND: + return _expr_depends_on(expr[1], sym) or \ + _expr_depends_on(expr[2], sym) + + return False def _has_auto_menu_dep(node1, node2): """ @@ -3440,7 +3453,6 @@ def _has_auto_menu_dep(node1, node2): has a prompt, we check its condition. Otherwise, we look directly at node2.dep. """ - if node2.prompt: return _expr_depends_on(node2.prompt[1], node1.item) @@ -3776,8 +3788,8 @@ _DEFAULT_VALUE = { _NO_CACHED_SELECTION = object() # Used in comparisons. 0 means the base is inferred from the format of the -# string. The entries for BOOL and TRISTATE are a convenience - they should -# never convert to valid numbers. +# string. The entries for BOOL and TRISTATE are an implementation convenience: +# They should never convert to valid numbers. _TYPE_TO_BASE = { BOOL: 0, HEX: 16, diff --git a/testsuite.py b/testsuite.py index 4eba62a..7859369 100644 --- a/testsuite.py +++ b/testsuite.py @@ -242,22 +242,22 @@ def run_selftests(): True, True, True) - print("Testing string literal (constant symbol) lexing") + print("Testing string literal lexing") # Dummy empty configuration just to get a Config object c = kconfiglib.Config("Kconfiglib/tests/empty") def verify_string_lex(s, res): """ - Verifies that the string (constant symbol) token 'res' is produced from + Verifies that a constant symbol with the name 'res' is produced from lexing 's'. Strips the first and last characters from 's' so that readable raw strings can be used as input """ s = s[1:-1] - token = c._tokenize(s, for_eval = True).next() - verify(token == res, - "expected {} to produced the string token {}, produced {}" - .format(s, token, res)) + token = c._tokenize(s, True, None, None).next() + verify(token.name == res, + 'expected {} to produced the constant symbol {}, produced {}' + .format(s, token.name, res)) verify_string_lex(r""" "" """, "") verify_string_lex(r""" '' """, "") @@ -295,7 +295,7 @@ def run_selftests(): """ s = s[1:-1] try: - c._tokenize(s, for_eval = True) + c._tokenize(s, True, None, None) except kconfiglib.KconfigSyntaxError: pass else: @@ -475,15 +475,16 @@ def run_selftests(): verify_eval("'ab' < 'aa'", "n") verify_eval("'ab' > 'aa'", "y") - # If one operand is numeric and the other not a valid number, we get 'n' - verify_eval("INT_37 < oops ", "n") - verify_eval("INT_37 <= oops ", "n") - verify_eval("INT_37 > oops ", "n") - verify_eval("INT_37 >= oops ", "n") - verify_eval("oops < INT_37", "n") - verify_eval("oops <= INT_37", "n") - verify_eval("oops > INT_37", "n") - verify_eval("oops >= INT_37", "n") + # Comparisons where one of the operands doesn't parse as a number also give + # a lexicographic comparison + verify_eval("INT_37 < '37a' ", "y") + verify_eval("'37a' > INT_37", "y") + verify_eval("INT_37 <= '37a' ", "y") + verify_eval("'37a' >= INT_37", "y") + verify_eval("INT_37 >= '37a' ", "n") + verify_eval("INT_37 > '37a' ", "n") + verify_eval("'37a' < INT_37", "n") + verify_eval("'37a' <= INT_37", "n") def verify_eval_bad(expr): try: @@ -1230,7 +1231,7 @@ g os.environ["ENV_VAR"] = "foo" # Contains reference to undefined environment variable, so disable warnings - c = kconfiglib.Config("Kconfiglib/tests/Kmisc", warn = False) + c = kconfiglib.Config("Kconfiglib/tests/Kmisc", warn=False) print("Testing is_optional...") @@ -1371,12 +1372,12 @@ g verify_value("STRING", "") # Assign BOOL - c.load_config("Kconfiglib/tests/config_set_bool", replace = False) + 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) + c.load_config("Kconfiglib/tests/config_set_string", replace=False) verify_value("BOOL", "y") verify_value("STRING", "foo bar") @@ -1399,8 +1400,8 @@ g print("Testing .config...") - c1 = kconfiglib.Config("Kconfiglib/tests/Kmisc", warn = False) - c2 = kconfiglib.Config("Kconfiglib/tests/Kmisc", warn = False) + c1 = kconfiglib.Config("Kconfiglib/tests/Kmisc", warn=False) + c2 = kconfiglib.Config("Kconfiglib/tests/Kmisc", warn=False) c1_undef, c1_bool, c1_choice, c1_menu, c1_comment = c1.syms["BOOL"], \ c1.syms["NOT_DEFINED_1"], get_choices(c1)[0], get_menus(c1)[0], \ @@ -1911,14 +1912,28 @@ def test_call_all(conf, arch): s.visibility s.unset_value() + # TODO: verify that constant symbols do not: + # 1) have a non-empty dep + # 2) have nodes + + # TODO: infinite recursion action + #for _, s in conf.const_syms.items(): + # s.__str__() + # s.__repr__() + # s.assignable + # s.type + # s.value + # s.visibility + # s.unset_value() + # Cheat with internals for c in conf._choices: c.__str__() c.__repr__() c.value - c.assignable + c.assignable c.selection - c.default_selection + c.default_selection c.type c.visibility @@ -1948,20 +1963,24 @@ def test_defconfig(conf, arch): def add_configs_for_arch(arch_): arch_dir = os.path.join("arch", arch_) + # Some arches have a "defconfig" in the root of their arch// # directory root_defconfig = os.path.join(arch_dir, "defconfig") if os.path.exists(root_defconfig): defconfigs.append(root_defconfig) + # Assume all files in the arch//configs directory (if it # exists) are configurations defconfigs_dir = os.path.join(arch_dir, "configs") if not os.path.exists(defconfigs_dir): return + if not os.path.isdir(defconfigs_dir): print("Warning: '{}' is not a directory - skipping" .format(defconfigs_dir)) return + for dirpath, _, filenames in os.walk(defconfigs_dir): for filename in filenames: defconfigs.append(os.path.join(dirpath, filename)) -- cgit v1.2.3 From a1e78132c1a34605653195d71c9112604fca3eda Mon Sep 17 00:00:00 2001 From: Ulf Magnusson Date: Fri, 27 Oct 2017 00:29:15 +0200 Subject: Backup --- kconfiglib.py | 695 +++++++++++++++++++++++++--------------------------------- testsuite.py | 17 +- 2 files changed, 313 insertions(+), 399 deletions(-) (limited to 'testsuite.py') diff --git a/kconfiglib.py b/kconfiglib.py index 0d92b48..2dd9b46 100644 --- a/kconfiglib.py +++ b/kconfiglib.py @@ -113,6 +113,8 @@ A || (B && C && D) (OR, A, (AND, B, (AND, C, D))) y config.y "y" config.y +TODO: show example for other constant symbols + As seen in the final two examples, n/m/y are always represented as the constant symbols config.n/m/y, regardless of whether they're written with or without quotes. 'config' represents the Config instance of the configuration the @@ -122,17 +124,13 @@ A missing expression (e.g. if the 'if ' part is removed from 'default A if ') is represented as config.y. The standard __str__() functions avoid printing 'if y' conditions to give cleaner output. -Implementation note -------------------- - -TODO: blah blah constant symbols - Send bug reports, suggestions, and questions to ulfalizer a.t Google's email service (or open a ticket on the GitHub page). """ # TODO: document n/m/y +# TODO: consistent docstring format import errno import os @@ -144,7 +142,6 @@ import sys # # Public classes # Public functions -# Internal classes # Internal functions # Public global constants # Internal global constants @@ -253,8 +250,8 @@ class Config(object): "_choices", "_print_undef_assign", "_print_warnings", - "_set_re", - "_unset_re", + "_set_re_match", + "_unset_re_match", "config_prefix", "const_syms", "defconfig_list", @@ -267,6 +264,18 @@ class Config(object): "syms", "top_node", "y", + + # These are used during parsing + "_line", + "_lines", + "_filename", + "_linenr", + "_file_len", + "_filestack", + "_tokens", + "_tokens_i", + "_tokens_len", + "_has_tokens", ) # @@ -305,11 +314,13 @@ class Config(object): if self.config_prefix is None: self.config_prefix = "CONFIG_" - # Regular expressions for parsing .config files - self._set_re = re.compile(r"{}(\w+)=(.*)" - .format(self.config_prefix)) - self._unset_re = re.compile(r"# {}(\w+) is not set" - .format(self.config_prefix)) + # Regular expressions for parsing .config files, with the get() method + # assigned directly as a small optimization (microscopic in this case, + # but it's consistent with the other regexes) + self._set_re_match = re.compile(r"{}(\w+)=(.*)" + .format(self.config_prefix)).match + self._unset_re_match = re.compile(r"# {}(\w+) is not set" + .format(self.config_prefix)).match self._print_warnings = warn self._print_undef_assign = False @@ -366,11 +377,16 @@ class Config(object): self.top_node.linenr = 1 # Parse the Kconfig files - self._parse_block(_FileFeed(self._open(filename), filename), - None, # end_token + + self._has_tokens = False + self._tokens_i = 0 + + self._filestack = [] + + self._enter_file_empty_stack(filename) + self._parse_block(None, # end_token self.top_node, # parent self.y, # visible_if_deps - None, # prev_line self.top_node) # prev_node self.top_node.list = self.top_node.next @@ -432,8 +448,8 @@ class Config(object): self._invalidate_all() # Small optimizations - set_re_match = self._set_re.match - unset_re_match = self._unset_re.match + set_re_match = self._set_re_match + unset_re_match = self._unset_re_match syms = self.syms for linenr, line in enumerate(f, 1): @@ -452,8 +468,8 @@ class Config(object): if sym._type == STRING and val.startswith('"'): if len(val) < 2 or val[-1] != '"': - self._warn("malformed string literal", filename, - linenr) + self._warn("malformed string literal", + filename, linenr) continue # Strip quotes and remove escapings. The unescaping # procedure should be safe since " can only appear as @@ -529,17 +545,11 @@ class Config(object): conditional ('if ...') expressions in the configuration (as well as in the C tools). m is rewritten to 'm && MODULES'. """ - return eval_expr( - self._parse_expr( - self._tokenize( - s, - True, # for_eval_string - None, # filename - None), # linenr - s, - None, # filename - None, # linenr - True)) # transform_m + # TODO: explain + self._line = s + self._filename = None + self._tokenize(True) + return eval_expr(self._parse_expr(True)) def unset_values(self): """ @@ -645,10 +655,10 @@ class Config(object): # Kconfig parsing # - def _tokenize(self, s, for_eval_string, filename, linenr): + def _tokenize(self, for_eval_string): """ - Returns a _Feed instance containing tokens derived from the string 's'. - Registers any new symbols encountered (via _lookup_sym()). + Parses Config._line, putting the tokens in Config._tokens. Registers + any new symbols encountered (via _lookup_sym()). Tries to be reasonably speedy by processing chunks of text via regexes and string operations where possible. This is a hotspot during parsing. @@ -659,13 +669,14 @@ class Config(object): register new symbols. """ + s = self._line + # Tricky implementation detail: While parsing a token, 'token' refers # to the previous token. See _NOT_REF for why this is needed. if for_eval_string: + self._tokens = [] token = None - tokens = [] - # The current index in the string being tokenized i = 0 @@ -673,19 +684,24 @@ class Config(object): # See comment at _initial_token_re_match definition initial_token_match = _initial_token_re_match(s) if not initial_token_match: - return None + self._tokens_len = 0 + return keyword = _get_keyword(initial_token_match.group(1)) + if keyword == _T_HELP: # Avoid junk after "help", e.g. "---", being registered as a # symbol - return _Feed((_T_HELP,)) + self._tokens = (_T_HELP,) + self._tokens_len = 1 # TODO: why is this needed? + self._tokens_i = 0 + return + if keyword is None: - # We expect a keyword as the first token - _tokenization_error(s, filename, linenr) + self._parse_error("expected keyword as first token") token = keyword - tokens = [keyword] + self._tokens = [keyword] # The current index in the string being tokenized i = initial_token_match.end() @@ -755,7 +771,8 @@ class Config(object): # can just find the matching quote. end = s.find(c, i) if end == -1: - _tokenization_error(s, filename, linenr) + self._parse_error("unterminated string") + val = s[i:end] i = end + 1 else: @@ -763,20 +780,25 @@ class Config(object): # very unusual case anyway. quote = c val = "" + while 1: if i >= len(s): - _tokenization_error(s, filename, linenr) + self._parse_error("unterminated string") + c = s[i] if c == quote: break + if c == "\\": if i + 1 >= len(s): - _tokenization_error(s, filename, linenr) + self._parse_error("unterminated string") + val += s[i + 1] i += 2 else: val += c i += 1 + i += 1 # This is the only place where we don't survive with a @@ -787,18 +809,22 @@ class Config(object): val \ if token in _STRING_LEX or \ (not for_eval_string and \ - tokens[0] == _T_OPTION) else \ + self._tokens[0] == _T_OPTION) else \ self._lookup_const_sym(val, for_eval_string) elif c == "&": # Invalid characters are ignored - if i >= len(s) or s[i] != "&": continue + if i >= len(s) or s[i] != "&": + continue + token = _T_AND i += 1 elif c == "|": # Invalid characters are ignored - if i >= len(s) or s[i] != "|": continue + if i >= len(s) or s[i] != "|": + continue + token = _T_OR i += 1 @@ -818,7 +844,8 @@ class Config(object): elif c == ")": token = _T_CLOSE_PAREN - elif c == "#": break # Comment + elif c == "#": + break # Very rare elif c == "<": @@ -844,20 +871,86 @@ class Config(object): while i < len(s) and s[i].isspace(): i += 1 - tokens.append(token) + self._tokens.append(token) + + self._tokens_i = 0 + self._tokens_len = len(self._tokens) - return _Feed(tokens) + def _next_token(self): + if self._tokens_i >= self._tokens_len: + return None + token = self._tokens[self._tokens_i] + self._tokens_i += 1 + return token + + def _peek_token(self): + return None if self._tokens_i >= self._tokens_len \ + else self._tokens[self._tokens_i] + + def _check_token(self, token): + if self._tokens_i < self._tokens_len and \ + self._tokens[self._tokens_i] == token: + self._tokens_i += 1 + return True + return False - def _parse_block(self, line_feeder, end_token, parent, visible_if_deps, - prev_line, prev_node): + def _enter_file_empty_stack(self, filename): + try: + # TODO: comment + f = self._open(filename) + except IOError as e: + # Extend the error message a bit in this case + raise IOError( + "{}:{}: {} Also note that e.g. $FOO in a 'source' " + "statement does not refer to the environment " + "variable FOO, but rather to the Kconfig Symbol FOO " + "(which would commonly have 'option env=\"FOO\"' in " + "its definition)." + .format(self._filename, self._linenr, e.message)) + + self._filename = filename + self._linenr = 0 + with f: + self._lines = f.readlines() + self._file_len = len(self._lines) + + def _enter_file(self, filename): + self._filestack.append((self._filename, self._linenr, self._lines, + self._file_len)) + self._enter_file_empty_stack(filename) + + def _leave_file(self): + self._filename, self._linenr, self._lines, self._file_len = \ + self._filestack.pop() + + def _next_line(self): + if self._linenr >= self._file_len: + return False + + line = self._lines[self._linenr] + self._linenr += 1 + + while line.endswith("\\\n"): + line = line[:-2] + self._lines[self._linenr] + self._linenr += 1 + + self._line = line + return True + + def _next_line_no_join(self): + if self._linenr >= self._file_len: + return None + + self._line = self._lines[self._linenr] + self._linenr += 1 + + return self._line + + def _parse_block(self, end_token, parent, visible_if_deps, prev_node): """ Parses a block, which is the contents of either a file or an if, menu, or choice statement. - line_feeder: - A _FileFeed instance feeding lines from a file. The Kconfig language - is line-based in practice. - end_token: The token that ends the block, e.g. _T_ENDIF ("endif") for ifs. None for files. @@ -890,35 +983,31 @@ class Config(object): """ while 1: - if prev_line is not None: - line, tokens = prev_line - else: - line = line_feeder.next() - if line is None: + if not self._has_tokens: + # Also advances to the next line + if not self._next_line(): if end_token is not None: raise KconfigSyntaxError("Unexpected end of file " + - line_feeder.filename) + self._filename) # We have reached the end of the file. Terminate the final # node and return it. prev_node.next = None return prev_node - tokens = self._tokenize(line, False, line_feeder.filename, - line_feeder.linenr) - if tokens is None: - continue + self._tokenize(False) - t0 = tokens.next() + self._has_tokens = False - # Cases are ordered roughly by frequency, which speeds things up a - # bit + t0 = self._next_token() + if t0 is None: + continue if t0 in (_T_CONFIG, _T_MENUCONFIG): # The tokenizer will automatically allocate a new Symbol object # for any new names it encounters, so we don't need to worry # about that here. - sym = tokens.next() + sym = self._next_token() node = MenuNode() node.config = self @@ -926,12 +1015,11 @@ class Config(object): node.help = None node.list = None node.parent = parent - node.filename = line_feeder.filename - node.linenr = line_feeder.linenr + node.filename = self._filename + node.linenr = self._linenr node.is_menuconfig = (t0 == _T_MENUCONFIG) - prev_line = self._parse_properties(line_feeder, node, - visible_if_deps) + self._parse_properties(node, visible_if_deps) sym.nodes.append(node) self.defined_syms.append(sym) @@ -941,29 +1029,15 @@ class Config(object): prev_node.next = prev_node = node elif t0 == _T_SOURCE: - kconfig_file = tokens.next() + kconfig_file = self._next_token() exp_kconfig_file = self._expand_sym_refs(kconfig_file) - try: - f = self._open(exp_kconfig_file) - except IOError as e: - # Extend the error message a bit in this case - raise IOError( - "{}:{}: {} Also note that e.g. $FOO in a 'source' " - "statement does not refer to the environment " - "variable FOO, but rather to the Kconfig Symbol FOO " - "(which would commonly have 'option env=\"FOO\"' in " - "its definition)." - .format(line_feeder.filename, line_feeder.linenr, - e.message)) - - prev_node = self._parse_block(_FileFeed(f, exp_kconfig_file), - None, # end_token + self._enter_file(exp_kconfig_file) + prev_node = self._parse_block(None, # end_token parent, visible_if_deps, - None, # prev_line prev_node) - prev_line = None + self._leave_file() elif t0 == end_token: # We have reached the end of the block. Terminate the final @@ -976,24 +1050,17 @@ class Config(object): node.item = None node.prompt = None node.parent = parent - node.filename = line_feeder.filename - node.linenr = line_feeder.linenr + node.filename = self._filename + node.linenr = self._linenr node.dep = \ - self._make_and(parent.dep, - self._parse_expr(tokens, line, - line_feeder.filename, - line_feeder.linenr, True)) + self._make_and(parent.dep, self._parse_expr(True)) - self._parse_block(line_feeder, - _T_ENDIF, + self._parse_block(_T_ENDIF, node, # parent visible_if_deps, - None, # prev_line node) # prev_node node.list = node.next - prev_line = None - prev_node.next = prev_node = node elif t0 == _T_MENU: @@ -1002,24 +1069,20 @@ class Config(object): node.item = MENU node.visibility = self.y node.parent = parent - node.filename = line_feeder.filename - node.linenr = line_feeder.linenr + node.filename = self._filename + node.linenr = self._linenr - prev_line = self._parse_properties(line_feeder, node, - visible_if_deps) - node.prompt = (tokens.next(), node.dep) + prompt = self._next_token() + self._parse_properties(node, visible_if_deps) + node.prompt = (prompt, node.dep) - self._parse_block(line_feeder, - _T_ENDMENU, + self._parse_block(_T_ENDMENU, node, # parent self._make_and(visible_if_deps, node.visibility), - prev_line, node) # prev_node node.list = node.next - prev_line = None - prev_node.next = prev_node = node elif t0 == _T_COMMENT: @@ -1028,17 +1091,17 @@ class Config(object): node.item = COMMENT node.list = None node.parent = parent - node.filename = line_feeder.filename - node.linenr = line_feeder.linenr + node.filename = self._filename + node.linenr = self._linenr - prev_line = self._parse_properties(line_feeder, node, - visible_if_deps) - node.prompt = (tokens.next(), node.dep) + prompt = self._next_token() + self._parse_properties(node, visible_if_deps) + node.prompt = (prompt, node.dep) prev_node.next = prev_node = node elif t0 == _T_CHOICE: - name = tokens.next() + name = self._next_token() if name is None: choice = Choice() self._choices.append(choice) @@ -1058,61 +1121,41 @@ class Config(object): node.item = choice node.help = None node.parent = parent - node.filename = line_feeder.filename - node.linenr = line_feeder.linenr + node.filename = self._filename + node.linenr = self._linenr - prev_line = self._parse_properties(line_feeder, node, - visible_if_deps) - self._parse_block(line_feeder, - _T_ENDCHOICE, + self._parse_properties(node, visible_if_deps) + self._parse_block(_T_ENDCHOICE, node, # parent visible_if_deps, - prev_line, node) # prev_node node.list = node.next - prev_line = None - choice.nodes.append(node) prev_node.next = prev_node = node elif t0 == _T_MAINMENU: - self.top_node.prompt = (tokens.next(), self.y) - self.top_node.filename = line_feeder.filename - self.top_node.linenr = line_feeder.linenr + self.top_node.prompt = (self._next_token(), self.y) + self.top_node.filename = self._filename + self.top_node.linenr = self._linenr else: - _parse_error(line, "unrecognized construct", - line_feeder.filename, line_feeder.linenr) + self._parse_error("unrecognized construct") - def _parse_cond(self, tokens, line, filename, linenr): + def _parse_cond(self): """ Parses an optional 'if ' construct and returns the parsed , or self.y if the next token is not _T_IF """ - return self._parse_expr(tokens, line, filename, linenr, True) \ - if tokens.check(_T_IF) else self.y - - def _parse_val_and_cond(self, tokens, line, filename, linenr): - """ - Parses ' if ' constructs, where the 'if' part is - optional. Returns a tuple containing the parsed expressions, with - config.y as the second element if the 'if' part is missing. - """ - return (self._parse_expr(tokens, line, filename, linenr, False), - self._parse_cond(tokens, line, filename, linenr)) + return self._parse_expr(True) if self._check_token(_T_IF) else self.y - def _parse_properties(self, line_feeder, node, visible_if_deps): + def _parse_properties(self, node, visible_if_deps): """ Parses properties for symbols, menus, choices, and comments. Also takes care of propagating dependencies from the menu node to the properties of the item (this mirrors the inner working of the C tools). - line_feeder: - A _FileFeed instance feeding lines from a file. The Kconfig language - is line-based in practice. - node: The menu node we're parsing properties on. Some properties (prompts, help texts, 'depends on') apply to the Menu node, while the others @@ -1139,39 +1182,29 @@ class Config(object): # properties above. node.dep = self.y - # The cached (line, tokens) tuple that we return - last_line = None - while 1: - line = line_feeder.next() - if line is None: + # Also advances to the next line + if not self._next_line(): break - filename = line_feeder.filename - linenr = line_feeder.linenr + self._tokenize(False) - tokens = self._tokenize(line, False, filename, linenr) - if tokens is None: + t0 = self._next_token() + if t0 is None: continue - t0 = tokens.next() - if t0 == _T_DEPENDS: - if not tokens.check(_T_ON): - _parse_error(line, 'expected "on" after "depends"', - filename, linenr) + if not self._check_token(_T_ON): + self._parse_error('expected "on" after "depends"') - node.dep = \ - self._make_and(node.dep, - self._parse_expr(tokens, line, filename, - linenr, True)) + node.dep = self._make_and(node.dep, self._parse_expr(True)) elif t0 == _T_HELP: # Find first non-blank (not all-space) line and get its # indentation while 1: - line = line_feeder.next_no_join() + line = self._next_line_no_join() if line is None or not line.isspace(): break @@ -1184,7 +1217,7 @@ class Config(object): # If the first non-empty lines has zero indent, there is no # help text node.help = "" - line_feeder.linenr -= 1 + self._linenr -= 1 # Unget the line break # The help text goes on till the first non-empty line with less @@ -1192,7 +1225,7 @@ class Config(object): help_lines = [_deindent(line, indent).rstrip()] while 1: - line = line_feeder.next_no_join() + line = self._next_line_no_join() if line is None or \ (not line.isspace() and _indentation(line) < indent): node.help = "\n".join(help_lines).rstrip() + "\n" @@ -1202,58 +1235,46 @@ class Config(object): if line is None: break - line_feeder.linenr -= 1 + self._linenr -= 1 # Unget the line elif t0 == _T_SELECT: if not isinstance(node.item, Symbol): - _parse_error(line, "only symbols can select", filename, - linenr) + self._parse_error("only symbols can select") - selects.append( - (tokens.next(), - self._parse_cond(tokens, line, filename, linenr))) + selects.append((self._next_token(), self._parse_cond())) elif t0 == _T_IMPLY: if not isinstance(node.item, Symbol): - _parse_error(line, "only symbols can imply", filename, - linenr) + self._parse_error("only symbols can imply") - implies.append( - (tokens.next(), - self._parse_cond(tokens, line, filename, linenr))) + implies.append((self._next_token(), self._parse_cond())) elif t0 in (_T_BOOL, _T_TRISTATE, _T_INT, _T_HEX, _T_STRING): node.item._type = _TOKEN_TO_TYPE[t0] - if tokens.peek() is not None: - prompt = (tokens.next(), - self._parse_cond(tokens, line, filename, linenr)) + if self._peek_token() is not None: + prompt = (self._next_token(), self._parse_cond()) elif t0 == _T_DEFAULT: - defaults.append( - self._parse_val_and_cond(tokens, line, filename, linenr)) + defaults.append((self._parse_expr(False), self._parse_cond())) elif t0 in (_T_DEF_BOOL, _T_DEF_TRISTATE): node.item._type = _TOKEN_TO_TYPE[t0] - defaults.append( - self._parse_val_and_cond(tokens, line, filename, - linenr)) + defaults.append((self._parse_expr(False), self._parse_cond())) elif t0 == _T_PROMPT: # 'prompt' properties override each other within a single # definition of a symbol, but additional prompts can be added # by defining the symbol multiple times - prompt = (tokens.next(), - self._parse_cond(tokens, line, filename, linenr)) + prompt = (self._next_token(), self._parse_cond()) elif t0 == _T_RANGE: - ranges.append( - (tokens.next(), - tokens.next(), - self._parse_cond(tokens, line, filename, linenr))) + ranges.append((self._next_token(), + self._next_token(), + self._parse_cond())) elif t0 == _T_OPTION: - if tokens.check(_T_ENV) and tokens.check(_T_EQUAL): - env_var = tokens.next() + if self._check_token(_T_ENV) and self._check_token(_T_EQUAL): + env_var = self._next_token() node.item.env_var = env_var @@ -1268,24 +1289,24 @@ class Config(object): "message, that might be an error, and you " "should email ulfalizer a.t Google's email " "service.".format(node.item.name, env_var), - filename, linenr) + self._filename, self._linenr) else: defaults.append( (self._lookup_const_sym(os.environ[env_var], False), self.y)) - elif tokens.check(_T_DEFCONFIG_LIST): + elif self._check_token(_T_DEFCONFIG_LIST): if self.defconfig_list is None: self.defconfig_list = node.item else: self._warn("'option defconfig_list' set on multiple " "symbols ({0} and {1}). Only {0} will be " - "used." - .format(self.defconfig_list.name, - node.item.name)) + "used.".format(self.defconfig_list.name, + node.item.name), + self._filename, self._linenr) - elif tokens.check(_T_MODULES): + elif self._check_token(_T_MODULES): # To reduce warning spam, only warn if 'option modules' is # set on some symbol that isn't MODULES, which should be # safe. I haven't run into any projects that make use @@ -1300,42 +1321,34 @@ class Config(object): "MODULES, like older versions of the C " "implementation did when 'option modules' " "wasn't used.)", - filename, linenr) + self._filename, self._linenr) - elif tokens.check(_T_ALLNOCONFIG_Y): + elif self._check_token(_T_ALLNOCONFIG_Y): if not isinstance(node.item, Symbol): - _parse_error(line, - "the 'allnoconfig_y' option is only " - "valid for symbols", - filename, linenr) + self._parse_error("the 'allnoconfig_y' option is only " + "valid for symbols") node.item.is_allnoconfig_y = True else: - _parse_error(line, "unrecognized option", filename, linenr) + self._parse_error("unrecognized option") elif t0 == _T_VISIBLE: - if not tokens.check(_T_IF): - _parse_error(line, 'expected "if" after "visible"', - filename, linenr) + if not self._check_token(_T_IF): + self._parse_error('expected "if" after "visible"') node.visibility = \ - self._make_and(node.visibility, - self._parse_expr(tokens, line, filename, - linenr, True)) + self._make_and(node.visibility, self._parse_expr(True)) elif t0 == _T_OPTIONAL: if not isinstance(node.item, Choice): - _parse_error(line, - '"optional" is only valid for choices', - filename, - linenr) + self._parse_error('"optional" is only valid for choices') node.item.is_optional = True else: - tokens.i = 0 - last_line = (line, tokens) + self._tokens_i = 0 + self._has_tokens = True break # Done parsing properties. Now add the new @@ -1395,13 +1408,10 @@ class Config(object): self._make_and(cond_expr, node.dep))) - # Return cached non-property line - return last_line - - def _parse_expr(self, feed, line, filename, linenr, transform_m): + def _parse_expr(self, transform_m): """ - Parses an expression from the tokens in 'feed' using a simple top-down - approach. The result has the form + Parses an expression from the tokens in Config._tokens using a simple + top-down approach. The result has the form '( )' where is e.g. kconfiglib.AND. If there is only one operand (i.e., no && or ||), then the operand is returned directly. This also goes for subexpressions. @@ -1410,20 +1420,6 @@ class Config(object): structure (AND, A, (AND, B, (OR, (NOT, C), (EQUAL, D, 3)))), with the Symbol objects stored directly in the expression. - feed: - _Feed instance containing the tokens for the expression. - - line: - The line containing the expression being parsed. - - filename: - The file containing the expression. None when using - Config.eval_string(). - - linenr: - The line number containing the expression. None when using - Config.eval_string(). - transform_m: True if 'm' should be rewritten to 'm && MODULES'. See the Config.eval_string() documentation. @@ -1453,35 +1449,32 @@ class Config(object): # we end up allocating a ton of lists instead of reusing expressions, # which is bad. - and_expr = self._parse_and_expr(feed, line, filename, linenr, - transform_m) + and_expr = self._parse_and_expr(transform_m) # Return 'and_expr' directly if we have a "single-operand" OR. # Otherwise, parse the expression on the right and make an OR node. # This turns A || B || C || D into (OR, A, (OR, B, (OR, C, D))). return and_expr \ - if not feed.check(_T_OR) else \ - (OR, and_expr, self._parse_expr(feed, line, filename, linenr, - transform_m)) + if not self._check_token(_T_OR) else \ + (OR, and_expr, self._parse_expr(transform_m)) - def _parse_and_expr(self, feed, line, filename, linenr, transform_m): - factor = self._parse_factor(feed, line, filename, linenr, transform_m) + def _parse_and_expr(self, transform_m): + factor = self._parse_factor(transform_m) # Return 'factor' directly if we have a "single-operand" AND. # Otherwise, parse the right operand and make an AND node. This turns # A && B && C && D into (AND, A, (AND, B, (AND, C, D))). return factor \ - if not feed.check(_T_AND) else \ - (AND, factor, self._parse_and_expr(feed, line, filename, - linenr, transform_m)) + if not self._check_token(_T_AND) else \ + (AND, factor, self._parse_and_expr(transform_m)) - def _parse_factor(self, feed, line, filename, linenr, transform_m): - token = feed.next() + def _parse_factor(self, transform_m): + token = self._next_token() if isinstance(token, Symbol): # Plain symbol or relation - next_token = feed.peek() + next_token = self._peek_token() if next_token not in _TOKEN_TO_REL: # Plain symbol @@ -1494,20 +1487,20 @@ class Config(object): return token # Relation - return (_TOKEN_TO_REL[feed.next()], token, feed.next()) + return (_TOKEN_TO_REL[self._next_token()], token, + self._next_token()) if token == _T_NOT: - return (NOT, self._parse_factor(feed, line, filename, linenr, - transform_m)) + return (NOT, self._parse_factor(transform_m)) if token == _T_OPEN_PAREN: - expr_parse = self._parse_expr(feed, line, filename, - linenr, transform_m) - if not feed.check(_T_CLOSE_PAREN): - _parse_error(line, "missing end parenthesis", filename, linenr) + expr_parse = self._parse_expr(transform_m) + if not self._check_token(_T_CLOSE_PAREN): + self._parse_error("missing end parenthesis") + return expr_parse - _parse_error(line, "malformed expression", filename, linenr) + self._parse_error("malformed expression") # # Symbol lookup @@ -1707,29 +1700,9 @@ class Config(object): s[sym_ref_match.end():] # - # Warnings + # Expression construction # - def _warn(self, msg, filename=None, linenr=None): - """For printing general warnings.""" - if self._print_warnings: - _stderr_msg("warning: " + msg, filename, linenr) - - def _warn_undef_assign(self, msg, filename=None, linenr=None): - """ - See the class documentation. - """ - if self._print_undef_assign: - _stderr_msg("warning: " + msg, filename, linenr) - - def _warn_undef_assign_load(self, name, val, filename, linenr): - """ - Special version for load_config(). - """ - self._warn_undef_assign( - 'attempt to assign the value "{}" to the undefined symbol {}' \ - .format(val, name), filename, linenr) - def _make_and(self, e1, e2): """ Constructs an AND (&&) expression. Performs trivial simplification. @@ -1760,6 +1733,39 @@ class Config(object): return (OR, e1, e2) + # + # Errors and warnings + # + + def _parse_error(self, msg): + if self._filename is None: + loc = "" + else: + loc = "{}:{}: ".format(self._filename, self._linenr) + + raise KconfigSyntaxError( + "{}Couldn't parse '{}': {}".format(loc, self._line.rstrip(), msg)) + + def _warn(self, msg, filename=None, linenr=None): + """For printing general warnings.""" + if self._print_warnings: + _stderr_msg("warning: " + msg, filename, linenr) + + def _warn_undef_assign(self, msg, filename=None, linenr=None): + """ + See the class documentation. + """ + if self._print_undef_assign: + _stderr_msg("warning: " + msg, filename, linenr) + + def _warn_undef_assign_load(self, name, val, filename, linenr): + """ + Special version for load_config(). + """ + self._warn_undef_assign( + 'attempt to assign the value "{}" to the undefined symbol {}' \ + .format(val, name), filename, linenr) + class Symbol(object): """ Represents a configuration symbol: @@ -2735,7 +2741,8 @@ class Choice(object): "choice" if self.name is None else "choice " + self.name, _TYPENAME[self.type], "mode " + self.value, - "visibility " + self.visibility] + "visibility " + self.visibility + ] if self.is_optional: fields.append("optional") @@ -3088,87 +3095,6 @@ def expr_str(expr): _RELATION_TO_STR[expr[0]], expr_str(expr[2])) -# -# Internal classes -# - -class _Feed(object): - """ - Class for working with sequences in a stream-like fashion; handy for - tokens. - """ - - __slots__ = ( - 'i', - 'length', - 'items', - ) - - def __init__(self, items): - self.items = items - self.length = len(self.items) - self.i = 0 - - def next(self): - if self.i >= self.length: - return None - item = self.items[self.i] - self.i += 1 - return item - - def peek(self): - return None if self.i >= self.length else self.items[self.i] - - def check(self, token): - """ - Checks if the next token is 'token'. If so, removes it from the token - feed and return True. Otherwise, leaves it in and return False. - """ - if self.i < self.length and self.items[self.i] == token: - self.i += 1 - return True - return False - -class _FileFeed(object): - """ - Feeds lines from a file. Keeps track of the filename and current line - number. Joins any line ending in \\ with the following line. We need to be - careful to get the line number right in the presence of continuation lines. - """ - - __slots__ = ( - 'filename', - 'lines', - 'length', - 'linenr' - ) - - def __init__(self, file_, filename): - self.filename = filename - with file_: - # No interleaving of I/O and processing yet. Don't know if it would - # help. - self.lines = file_.readlines() - self.length = len(self.lines) - self.linenr = 0 - - def next(self): - if self.linenr >= self.length: - return None - line = self.lines[self.linenr] - self.linenr += 1 - while line.endswith("\\\n"): - line = line[:-2] + self.lines[self.linenr] - self.linenr += 1 - return line - - def next_no_join(self): - if self.linenr >= self.length: - return None - line = self.lines[self.linenr] - self.linenr += 1 - return line - # # Internal functions # @@ -3286,28 +3212,13 @@ def _strcmp(s1, s2): """ return (s1 > s2) - (s1 < s2) -def _lines(*args): - """ - Returns a string consisting of all arguments, with newlines inserted - between them. - """ - return "\n".join(args) - def _stderr_msg(msg, filename, linenr): - if filename is not None: - sys.stderr.write("{}:{}: ".format(filename, linenr)) - sys.stderr.write(msg + "\n") - -def _tokenization_error(s, filename, linenr): - loc = "" if filename is None else "{}:{}: ".format(filename, linenr) - raise KconfigSyntaxError("{}Couldn't tokenize '{}'" - .format(loc, s.strip())) + if filename is None: + s = msg + else: + s = "{}:{}: ".format(filename, linenr) -def _parse_error(s, msg, filename, linenr): - loc = "" if filename is None else "{}:{}: ".format(filename, linenr) - raise KconfigSyntaxError("{}Couldn't parse '{}'{}" - .format(loc, s.strip(), - "." if msg is None else ": " + msg)) + sys.stderr.write(msg + "\n") def _internal_error(msg): raise InternalError( @@ -3419,7 +3330,7 @@ def _sym_choice_str(sc): def _expr_depends_on(expr, sym): """ Reimplementation of expr_depends_symbol() from mconf.c. Used to - determine if a submenu should be implicitly created, which influences + determine if a submenu should be implicitly created. This also influences what items inside choice statements are considered choice items. """ if isinstance(expr, Symbol): @@ -3504,7 +3415,7 @@ def _flatten(node): node = node.next -def _remove_if(node): +def _remove_ifs(node): """ Removes 'if' nodes (which can be recognized by MenuNode.item being None), which are assumed to already have been flattened. The C implementation @@ -3580,7 +3491,7 @@ def _finalize_tree(node): # We have a node with finalized children. Do final steps to finalize # this node. _flatten(node.list) - _remove_if(node) + _remove_ifs(node) # Empty choices (node.list None) are possible, so this needs to go outside if isinstance(node.item, Choice): @@ -3671,8 +3582,8 @@ def _finalize_tree(node): _T_VISIBLE, ) = range(44) -# Keyword to token map. Note that the get() method is assigned directly as a -# small optimization. +# Keyword to token map, with the get() method assigned directly as a small +# optimization _get_keyword = { "allnoconfig_y": _T_ALLNOCONFIG_Y, "bool": _T_BOOL, diff --git a/testsuite.py b/testsuite.py index 7859369..e81ba1d 100644 --- a/testsuite.py +++ b/testsuite.py @@ -253,11 +253,11 @@ def run_selftests(): lexing 's'. Strips the first and last characters from 's' so that readable raw strings can be used as input """ - s = s[1:-1] - token = c._tokenize(s, True, None, None).next() - verify(token.name == res, + c._line = s[1:-1] + c._tokenize(True) + verify(c._tokens[0].name == res, 'expected {} to produced the constant symbol {}, produced {}' - .format(s, token.name, res)) + .format(s, c._tokens[0].name, res)) verify_string_lex(r""" "" """, "") verify_string_lex(r""" '' """, "") @@ -293,9 +293,9 @@ def run_selftests(): first and last characters from 's' so we can use readable raw strings as input. """ - s = s[1:-1] + c._line = s[1:-1] try: - c._tokenize(s, True, None, None) + c._tokenize(True) except kconfiglib.KconfigSyntaxError: pass else: @@ -812,7 +812,8 @@ g verify_locations(c.syms["MULTI_DEF"].nodes, "tests/Klocation:6", "tests/Klocation:16", - "tests/Klocation_included:3") + "tests/Klocation_included:3", + "tests/Klocation:32") verify_locations(c.named_choices["CHOICE"].nodes, "tests/Klocation_included:5") @@ -1916,6 +1917,8 @@ def test_call_all(conf, arch): # 1) have a non-empty dep # 2) have nodes + # TODO: Look for weird stuff in the dictionaries + # TODO: infinite recursion action #for _, s in conf.const_syms.items(): # s.__str__() -- cgit v1.2.3 From 481bfd60d0b283f30b906d2edf8228aeb82e8492 Mon Sep 17 00:00:00 2001 From: Ulf Magnusson Date: Sat, 28 Oct 2017 00:46:00 +0200 Subject: Nearly finalize API Probably just some usability tweaks left. Having to do STR_TO_TRI[] for comparisons against 'assignable' values is kinda ugly and confusing. --- examples/allnoconfig.py | 4 +- examples/allyesconfig.py | 10 +- kconfiglib.py | 617 +++++++++++++++++++++++------------------------ testsuite.py | 561 +++++++++++++++++++++--------------------- 4 files changed, 582 insertions(+), 610 deletions(-) (limited to 'testsuite.py') diff --git a/examples/allnoconfig.py b/examples/allnoconfig.py index 8d41912..2348da4 100644 --- a/examples/allnoconfig.py +++ b/examples/allnoconfig.py @@ -8,7 +8,7 @@ # # $ make [ARCH=] scriptconfig SCRIPT=Kconfiglib/examples/allnoconfig.py -from kconfiglib import Config, Symbol, tri_less +from kconfiglib import Config, Symbol, STR_TO_TRI import sys def do_allnoconfig(node): @@ -27,7 +27,7 @@ def do_allnoconfig(node): if (sym.choice is None and not sym.is_allnoconfig_y and sym.assignable and - tri_less(sym.assignable[0], sym.value)): + STR_TO_TRI[sym.assignable[0]] < sym.tri_value): # Yup, lower it sym.set_value(sym.assignable[0]) diff --git a/examples/allyesconfig.py b/examples/allyesconfig.py index 32b302c..f91b6d7 100644 --- a/examples/allyesconfig.py +++ b/examples/allyesconfig.py @@ -18,7 +18,7 @@ # # $ make [ARCH=] scriptconfig SCRIPT=Kconfiglib/examples/allyesconfig.py -from kconfiglib import Config, Choice, tri_less +from kconfiglib import Config, Choice, STR_TO_TRI import sys conf = Config(sys.argv[1]) @@ -70,7 +70,7 @@ while 1: for sym in non_choice_syms: # See allnoconfig example. [-1] gives the last (highest) assignable # value. - if sym.assignable and tri_less(sym.value, sym.assignable[-1]): + if sym.assignable and sym.tri_value < STR_TO_TRI[sym.assignable[-1]]: sym.set_value(sym.assignable[-1]) no_changes = False @@ -79,7 +79,7 @@ while 1: for choice in choices: # Handle a choice whose visibility allows it to be in "y" mode - if choice.visibility == "y": + if choice.visibility == 2: selection = choice.default_selection # Does the choice have a default selection that we haven't already @@ -95,12 +95,12 @@ while 1: # This might happen if a choice depends on a symbol that can only be # "m", for example. - elif choice.visibility == "m": + elif choice.visibility == 1: for sym in choice.symbols: # Does the choice have a symbol that can be "m" that we haven't # already set to "m"? - if sym.user_value != "m" and "m" in sym.assignable: + if sym.user_tri_value != 1 and "m" in sym.assignable: # Yup, set it sym.set_value("m") diff --git a/kconfiglib.py b/kconfiglib.py index d0c46c6..25bb380 100644 --- a/kconfiglib.py +++ b/kconfiglib.py @@ -338,7 +338,8 @@ class Config(object): sym.name = nmy sym.is_constant = True sym._type = TRISTATE - sym._cached_val = nmy + sym._cached_tri_val = STR_TO_TRI[nmy] + sym._cached_str_val = nmy self.const_syms[nmy] = sym @@ -416,9 +417,10 @@ class Config(object): """ if self.defconfig_list is None: return None + for filename, cond_expr in self.defconfig_list.defaults: - if eval_expr(cond_expr) != "n": - filename = self._expand_sym_refs(filename.value) + if eval_expr(cond_expr): + filename = self._expand_sym_refs(filename.str_value) try: with self._open(filename) as f: return f.name @@ -474,6 +476,7 @@ class Config(object): self._warn("malformed string literal", filename, linenr) continue + # Strip quotes and remove escapings. The unescaping # procedure should be safe since " can only appear as # \" inside the string. @@ -481,7 +484,7 @@ class Config(object): .replace("\\\\", "\\") if sym.choice is not None: - mode = sym.choice.user_value + mode = sym.choice.user_str_value if mode is not None and mode != val: self._warn("assignment to {} changes mode of " 'containing choice from "{}" to "{}".' @@ -504,10 +507,10 @@ class Config(object): # Done parsing the assignment. Set the value. - if sym.user_value is not None: + if sym.user_str_value is not None: self._warn('{} set more than once. Old value: "{}", new ' 'value: "{}".' - .format(name, sym.user_value, val), + .format(name, sym.user_str_value, val), filename, linenr) sym._set_value_no_invalidate(val, True) @@ -542,7 +545,7 @@ class Config(object): returns "y". This function always yields a tristate value. To get the value of - non-bool, non-tristate symbols, use Symbol.value. + non-bool, non-tristate symbols, use Symbol.str_value. The result of this function is consistent with how evaluation works for conditional ('if ...') expressions in the configuration (as well as in @@ -571,11 +574,12 @@ class Config(object): for sym in self.defined_syms: # We're iterating over all symbols, so no need for symbols to # invalidate their dependent symbols - sym.user_value = None + sym.user_str_value = sym.user_tri_value = None sym._invalidate() for choice in self._choices: - choice.user_value = choice.user_selection = None + choice.user_str_value = choice.user_tri_value = \ + choice.user_selection = None choice._invalidate() def enable_warnings(self): @@ -616,7 +620,7 @@ class Config(object): 'config symbol prefix "{}"'.format(self.config_prefix), "warnings " + ("enabled" if self._print_warnings else "disabled"), "undef. symbol assignment warnings " + - ("enabled" if self._print_undef_assign else "disabled") + ("enabled" if self._print_undef_assign else "disabled"), ) return "<{}>".format(", ".join(fields)) @@ -1007,10 +1011,7 @@ class Config(object): prev_node.next = prev_node = node elif t0 == _T_SOURCE: - kconfig_file = self._next_token() - exp_kconfig_file = self._expand_sym_refs(kconfig_file) - - self._enter_file(exp_kconfig_file) + self._enter_file(self._expand_sym_refs(self._next_token())) prev_node = self._parse_block(None, # end_token parent, visible_if_deps, @@ -1030,8 +1031,7 @@ class Config(object): node.parent = parent node.filename = self._filename node.linenr = self._linenr - node.dep = \ - self._make_and(parent.dep, self._parse_expr(True)) + node.dep = self._make_and(parent.dep, self._parse_expr(True)) self._parse_block(_T_ENDIF, node, # parent @@ -1259,16 +1259,10 @@ class Config(object): node.item.env_var = env_var if env_var not in os.environ: - self._warn("the symbol {0} references the " - "non-existent environment variable {1} " - "(meaning the 'option env=\"{1}\"' will " - "have no effect). If you're using " - "Kconfiglib via 'make (i)scriptconfig', it " - "should have set up the environment " - "correctly for you. If you still got this " - "message, that might be an error, and you " - "should email ulfalizer a.t Google's email " - "service.".format(node.item.name, env_var), + self._warn("'option env=\"{0}\"' on symbol {1} will " + "have no effect, because the environment " + "variable {0} is not set" + .format(node.item.name, env_var), self._filename, self._linenr) else: defaults.append( @@ -1565,9 +1559,9 @@ class Config(object): add_fn(config_string) sym._already_written = True - elif (node.item == MENU and eval_expr(node.dep) != "n" and - eval_expr(node.visibility) != "n") or \ - (node.item == COMMENT and eval_expr(node.dep) != "n"): + elif eval_expr(node.dep) and \ + ((node.item == MENU and eval_expr(node.visibility)) or + node.item == COMMENT): add_fn("\n#\n# {}\n#\n".format(node.prompt[0])) @@ -1669,13 +1663,13 @@ class Config(object): while 1: sym_ref_match = _sym_ref_re_search(s) - if sym_ref_match is None: + if not sym_ref_match: return s sym = self.syms.get(sym_ref_match.group(1)) s = s[:sym_ref_match.start()] + \ - (sym.value if sym is not None else "") + \ + (sym.str_value if sym is not None else "") + \ s[sym_ref_match.end():] # @@ -1772,10 +1766,14 @@ class Symbol(object): menuconfig-like functionality. (Check the implementation of the property if you need to get the original type.) - value: + str_value: + TODO The current value of the symbol. Automatically recalculated as dependencies change. + tri_value: + TODO + assignable: A string containing the tristate values that can be assigned to the symbol, ordered from lowest (n) to highest (y). This corresponds to the @@ -1819,13 +1817,23 @@ class Symbol(object): dependencies) are propagated to the prompt dependencies. Additional dependencies can be specified with e.g. 'bool "foo" if ". - user_value: - The value assigned with Symbol.set_value(), or None if no value has been - assigned. This won't necessarily match 'value' even if set, as - dependencies and prompt visibility take precedence. + user_str_value: + The string value assigned with Symbol.set_value(), or None if no value + has been assigned. This won't necessarily match 'str_value' even if set, + as dependencies and prompt visibility take precedence. + + Note that you should use Symbol.set_value() to change this value (which + will also change user_tri_value). Changing the value directly will break + things, as Kconfiglib might need to invalidate other symbols. Properties + are always read-only. + + The string value is only used in comparisons (e.g. + 'depends on SYMBOL = "foo"'). See user_tri_value. - Note that you should use Symbol.set_value() to change this value. - Properties are always read-only. + user_tri_value: + The tristate value corresponding to user_str_value. The rule is that "n", + "m", and "y" correspond to 0, 1, and 2 for BOOL and TRISTATE symbols. + Other symbol types always evaluate to 0 (n) in a tristate sense. config_string: The .config assignment string that would get written out for the symbol @@ -1917,7 +1925,8 @@ class Symbol(object): "_already_written", "_cached_assignable", "_cached_deps", - "_cached_val", + "_cached_str_val", + "_cached_tri_val", "_cached_vis", "_direct_dependents", "_type", @@ -1935,7 +1944,8 @@ class Symbol(object): "ranges", "rev_dep", "selects", - "user_value", + "user_str_value", + "user_tri_value", "weak_rev_dep", ) @@ -1950,138 +1960,79 @@ class Symbol(object): """ if self._type == TRISTATE and \ - ((self.choice is not None and self.choice.value == "y") or - self.config.modules.value == "n"): + ((self.choice is not None and self.choice.tri_value == 2) or + not self.config.modules.tri_value): return BOOL return self._type @property - def value(self): + def str_value(self): """ See the class documentation. """ - if self._cached_val is not None: - return self._cached_val + if self._cached_str_val is not None: + return self._cached_str_val + + if self._type in (BOOL, TRISTATE): + self._cached_str_val = TRI_TO_STR[self.tri_value] + return self._cached_str_val # As a quirk of Kconfig, undefined symbols get their name as their - # value. This is why things like "FOO = bar" work for seeing if FOO has - # the value "bar". + # string value. This is why things like "FOO = bar" work for seeing if + # FOO has the value "bar". if self._type == UNKNOWN: - self._cached_val = self.name + self._cached_str_val = self.name return self.name - # This will hold the value at the end of the function - val = _DEFAULT_VALUE[self._type] - + val = "" vis = self.visibility - if self._type in (BOOL, TRISTATE): - if self.choice is None: - self._write_to_conf = (vis != "n") + self._write_to_conf = (vis != 0) - if vis != "n" and self.user_value is not None: - # If the symbol is visible and has a user value, we use - # that - val = _tri_min(self.user_value, vis) - - else: - # Otherwise, we look at defaults and weak reverse - # dependencies (implies) - - for default, cond in self.defaults: - cond_val = eval_expr(cond) - if cond_val != "n": - self._write_to_conf = True - val = _tri_min(eval_expr(default), cond_val) - break - - # Weak reverse dependencies are only considered if our - # direct dependencies are met - if eval_expr(self.direct_dep) != "n": - weak_rev_dep_val = \ - eval_expr(self.weak_rev_dep) - if weak_rev_dep_val != "n": - self._write_to_conf = True - val = _tri_max(val, weak_rev_dep_val) - - # Reverse (select-related) dependencies take precedence - rev_dep_val = eval_expr(self.rev_dep) - if rev_dep_val != "n": - self._write_to_conf = True - val = _tri_max(val, rev_dep_val) - - else: - # (bool/tristate) symbol in choice. See _get_visibility() for - # more choice-related logic. - - # Initially - self._write_to_conf = False - - if vis != "n": - mode = self.choice.value - - if mode != "n": - self._write_to_conf = True - - if mode == "y": - val = "y" if self.choice.selection is self else "n" - elif self.user_value in ("m", "y"): - # mode == "m" and self.user_value is not None or - # "n" - val = "m" - - # "m" is promoted to "y" in two circumstances: - # 1) If our type is boolean - # 2) If our weak_rev_dep (from IMPLY) is "y" - if val == "m" and \ - (self.type == BOOL or eval_expr(self.weak_rev_dep) == "y"): - val = "y" - - elif self._type in (INT, HEX): + if self._type in (INT, HEX): base = _TYPE_TO_BASE[self._type] # Check if a range is in effect for low_expr, high_expr, cond_expr in self.ranges: - if eval_expr(cond_expr) != "n": + if eval_expr(cond_expr): has_active_range = True - low = int(low_expr.value, base) if \ - _is_base_n(low_expr.value, base) else 0 - high = int(high_expr.value, base) if \ - _is_base_n(high_expr.value, base) else 0 + low = int(low_expr.str_value, base) if \ + _is_base_n(low_expr.str_value, base) else 0 + high = int(high_expr.str_value, base) if \ + _is_base_n(high_expr.str_value, base) else 0 break else: has_active_range = False - self._write_to_conf = (vis != "n") - - if vis != "n" and self.user_value is not None and \ - _is_base_n(self.user_value, base) and \ + if vis and self.user_str_value is not None and \ + _is_base_n(self.user_str_value, base) and \ (not has_active_range or - low <= int(self.user_value, base) <= high): + low <= int(self.user_str_value, base) <= high): # If the user value is well-formed and satisfies range # contraints, it is stored in exactly the same form as # specified in the assignment (with or without "0x", etc.) - val = self.user_value + val = self.user_str_value else: # No user value or invalid user value. Look at defaults. for val_expr, cond_expr in self.defaults: - if eval_expr(cond_expr) != "n": + if eval_expr(cond_expr): self._write_to_conf = True # Similarly to above, well-formed defaults are # preserved as is. Defaults that do not satisfy a range # constraints are clamped and take on a standard form. - val = val_expr.value + val = val_expr.str_value if _is_base_n(val, base): val_num = int(val, base) + # TODO: move outside? if has_active_range: clamped_val = None @@ -2105,18 +2056,93 @@ class Symbol(object): val = (hex(low) if self._type == HEX else str(low)) elif self._type == STRING: - self._write_to_conf = (vis != "n") - - if vis != "n" and self.user_value is not None: - val = self.user_value + if vis and self.user_str_value is not None: + val = self.user_str_value else: for val_expr, cond_expr in self.defaults: - if eval_expr(cond_expr) != "n": + if eval_expr(cond_expr): self._write_to_conf = True - val = val_expr.value + val = val_expr.str_value break - self._cached_val = val + self._cached_str_val = val + return val + + @property + def tri_value(self): + """ + See the class documentation. + """ + + if self._cached_tri_val is not None: + return self._cached_tri_val + + if self._type not in (BOOL, TRISTATE): + self._cached_tri_val = 0 + return self._cached_tri_val + + val = 0 + vis = self.visibility + + if self.choice is None: + self._write_to_conf = (vis != 0) + + if vis and self.user_tri_value is not None: + # If the symbol is visible and has a user value, we use that + val = min(self.user_tri_value, vis) + + else: + # Otherwise, we look at defaults and weak reverse dependencies + # (implies) + + for default, cond in self.defaults: + cond_val = eval_expr(cond) + if cond_val: + val = min(cond_val, eval_expr(default)) + self._write_to_conf = True + break + + # Weak reverse dependencies are only considered if our + # direct dependencies are met + if eval_expr(self.direct_dep): + weak_rev_dep_val = eval_expr(self.weak_rev_dep) + if weak_rev_dep_val: + val = max(weak_rev_dep_val, val) + self._write_to_conf = True + + # Reverse (select-related) dependencies take precedence + rev_dep_val = eval_expr(self.rev_dep) + if rev_dep_val: + val = max(rev_dep_val, val) + self._write_to_conf = True + + else: + # (bool/tristate) symbol in choice. See _get_visibility() for + # more choice-related logic. + + # Initially + self._write_to_conf = False + + if vis: + mode = self.choice.tri_value + + if mode: + self._write_to_conf = True + + if mode == 2: + val = 2 if self.choice.selection is self else 0 + elif self.user_tri_value: + # mode == 1, user value available and not 0 + val = 1 + + # m is promoted to y in two circumstances: + # 1) If our type is boolean + # 2) If our weak_rev_dep (from IMPLY) is y + if val == 1 and \ + (self.type == BOOL or eval_expr(self.weak_rev_dep) == 2): + val = 2 + + self._cached_tri_val = val return val @property @@ -2152,8 +2178,9 @@ class Symbol(object): # corresponds to the SYMBOL_AUTO flag in the C implementation. return None - # Note: _write_to_conf is determined when the value is calculated - val = self.value + # Note: _write_to_conf is determined when the value is calculated. This + # is a hidden function call due to property magic. + val = self.str_value if not self._write_to_conf: return None @@ -2184,10 +2211,11 @@ class Symbol(object): Equal in effect to assigning the value to the symbol within a .config file. Use the 'assignable' attribute to check which values can currently be assigned. Setting values outside 'assignable' will cause - Symbol.user_value to differ from Symbol.value (be truncated down or - up). Values that are invalid for the type (such as "foo" or "m" for a - BOOL) are ignored (and won't be stored in Symbol.user_value). A warning - is printed for attempts to assign invalid values. + Symbol.user_str/tri_value to differ from Symbol.str/tri_value (be + truncated down or up). Values that are invalid for the type (such as + "foo" or "m" for a BOOL) are ignored (and won't be stored in + Symbol.user_str/tri_value). A warning is printed for attempts to assign + invalid values. The values of other symbols that depend on this symbol are automatically recalculated to reflect the new value. @@ -2208,7 +2236,7 @@ class Symbol(object): Resets the user value of the symbol, as if the symbol had never gotten a user value via Config.load_config() or Symbol.set_value(). """ - self.user_value = None + self.user_str_value = self.user_tri_value = None self._rec_invalidate() def __str__(self): @@ -2229,17 +2257,17 @@ class Symbol(object): def __repr__(self): """ Prints some information about the symbol (including its name, value, - and visibility) when it is evaluated. + visibility, and location(s)) when it is evaluated. """ fields = [ "symbol " + self.name, _TYPENAME[self.type], - 'value "{}"'.format(self.value), - "visibility {}".format(self.visibility) + 'value "{}"'.format(self.str_value), + "visibility " + TRI_TO_STR[self.visibility], ] - if self.user_value is not None: - fields.append('user value "{}"'.format(self.user_value)) + if self.user_str_value is not None: + fields.append('user value "{}"'.format(self.user_str_value)) if self.choice is not None: fields.append("choice symbol") @@ -2256,11 +2284,13 @@ class Symbol(object): if self is self.config.modules: fields.append("is the modules symbol") - fields.append("direct deps " + eval_expr(self.direct_dep)) + fields.append("direct deps " + TRI_TO_STR[eval_expr(self.direct_dep)]) - fields.append("{} menu node{}" - .format(len(self.nodes), - "" if len(self.nodes) == 1 else "s")) + if self.nodes: + for node in self.nodes: + fields.append("{}:{}".format(node.filename, node.linenr)) + else: + fields.append("undefined") return "<{}>".format(", ".join(fields)) @@ -2292,7 +2322,7 @@ class Symbol(object): self.nodes = [] - self.user_value = None + self.user_str_value = self.user_tri_value = None # Populated in Config._build_dep() after parsing. Links the symbol to # the symbols that immediately depend on it (in a caching/invalidation @@ -2302,15 +2332,8 @@ class Symbol(object): # Cached values - # Caches the calculated value - self._cached_val = None - # Caches the visibility - self._cached_vis = None - # Caches the total list of dependent symbols. Calculated in - # _get_dependent(). - self._cached_deps = None - # Caches the 'assignable' attribute - self._cached_assignable = None + self._cached_str_val = self._cached_tri_val = self._cached_vis = \ + self._cached_deps = self._cached_assignable = None # Flags @@ -2332,35 +2355,35 @@ class Symbol(object): vis = self.visibility - if vis == "n": + if not vis: return "" rev_dep_val = eval_expr(self.rev_dep) - if vis == "y": - if rev_dep_val == "n": - if self.type == BOOL or eval_expr(self.weak_rev_dep) == "y": + if vis == 2: + if not rev_dep_val: + if self.type == BOOL or eval_expr(self.weak_rev_dep) == 2: return "ny" return "nmy" - if rev_dep_val == "y": + if rev_dep_val == 2: return "y" - # rev_dep_val == "m" + # rev_dep_val == 1 - if self.type == BOOL or eval_expr(self.weak_rev_dep) == "y": + if self.type == BOOL or eval_expr(self.weak_rev_dep) == 2: return "y" return "my" - # vis == "m" + # vis == 1 - if rev_dep_val == "n": - return "m" if eval_expr(self.weak_rev_dep) != "y" else "y" + if not rev_dep_val: + return "m" if eval_expr(self.weak_rev_dep) != 2 else "y" - if rev_dep_val == "y": + if rev_dep_val == 2: return "y" - # vis == "m", rev_dep == "m" (rare) + # vis == rev_dep_val == 1 return "m" @@ -2399,20 +2422,29 @@ class Symbol(object): "promptless symbol {} will have no effect" .format(value, self.name)) - self.user_value = value + self.user_str_value = value + self.user_tri_value = \ + STR_TO_TRI[value] \ + if self._type in (BOOL, TRISTATE) else \ + 0 + + # TODO: assigning automatically changes choice yada yada - if self.choice is not None and self._type in (BOOL, TRISTATE): - if value == "y": - self.choice.user_value = "y" + if self.choice is not None: + if self.user_tri_value == 2: + self.choice.user_str_value = "y" + self.choice.user_tri_value = 2 self.choice.user_selection = self - elif value == "m": - self.choice.user_value = "m" + elif self.user_tri_value == 1: + self.choice.user_str_value = "m" + self.choice.user_tri_value = 1 def _invalidate(self): """ Marks the symbol as needing to be recalculated. """ - self._cached_val = self._cached_vis = self._cached_assignable = None + self._cached_str_val = self._cached_tri_val = self._cached_vis = \ + self._cached_assignable = None def _rec_invalidate(self): """ @@ -2531,10 +2563,13 @@ class Choice(object): The symbol that would be selected by default, had the user not selected any symbol. Can be None for the same reasons as 'selected'. - user_value: + user_str_value: TODO The value (mode) selected by the user (by assigning some choice symbol or calling Choice.set_value()). This does not necessarily match Choice.value - for the same reasons that Symbol.user_value might not match Symbol.value. + for the same reasons that Symbol.user_str_value might not match + Symbol.value. + + user_tri_value: TODO user_selection: The symbol selected by the user (by setting it to "y"). Ignored if the @@ -2585,7 +2620,8 @@ class Choice(object): "nodes", "syms", "user_selection", - "user_value", + "user_str_value", + "user_tri_value", ) # @@ -2595,25 +2631,32 @@ class Choice(object): @property def type(self): """Returns the type of the choice. See Symbol.type.""" - if self._type == TRISTATE and self.config.modules.value == "n": + if self._type == TRISTATE and not self.config.modules.tri_value: return BOOL return self._type @property - def value(self): + def str_value(self): """ See the class documentation. """ - if self.user_value is not None: - val = _tri_min(self.user_value, self.visibility) + return TRI_TO_STR[self.tri_value] + + @property + def tri_value(self): + """ + See the class documentation. + """ + if self.user_tri_value is not None: + val = min(self.user_tri_value, self.visibility) else: - val = "n" + val = 0 - if val == "n" and not self.is_optional: - val = "m" + if not val and not self.is_optional: + val = 1 # Promote "m" to "y" for boolean choices - return "y" if val == "m" and self.type == BOOL else val + return 2 if val == 1 and self.type == BOOL else val @property def assignable(self): @@ -2645,13 +2688,13 @@ class Choice(object): if self._cached_selection is not _NO_CACHED_SELECTION: return self._cached_selection - if self.value != "y": + if self.tri_value != 2: self._cached_selection = None return None # User choice available? if self.user_selection is not None and \ - self.user_selection.visibility == "y": + self.user_selection.visibility == 2: self._cached_selection = self.user_selection return self.user_selection @@ -2666,14 +2709,12 @@ class Choice(object): See the class documentation. """ for sym, cond_expr in self.defaults: - if (eval_expr(cond_expr) != "n" and - # Must be visible too - sym.visibility != "n"): + if eval_expr(cond_expr) and sym.visibility: return sym # Otherwise, pick the first visible symbol, if any for sym in self.syms: - if sym.visibility != "n": + if sym.visibility: return sym # Couldn't find a default @@ -2692,7 +2733,8 @@ class Choice(object): "which has type {}. Assignment ignored" .format(value, _TYPENAME[self._type])) - self.user_value = value + self.user_str_value = value + self.user_tri_value = STR_TO_TRI[value] if self.syms: # Hackish way to invalidate the choice and all the choice symbols @@ -2703,7 +2745,7 @@ class Choice(object): Resets the user value (mode) and user selection of the Choice, as if the user had never touched the mode or any of the choice symbols. """ - self.user_value = self.user_selection = None + self.user_str_value = self.user_tri_value = self.user_selection = None if self.syms: # Hackish way to invalidate the choice and all the choice symbols self.syms[0]._rec_invalidate() @@ -2716,11 +2758,14 @@ class Choice(object): return _sym_choice_str(self) def __repr__(self): + """ + TODO + """ fields = [ "choice" if self.name is None else "choice " + self.name, _TYPENAME[self.type], - "mode " + self.value, - "visibility " + self.visibility + "mode " + self.str_value, + "visibility " + TRI_TO_STR[self.visibility], ] if self.is_optional: @@ -2729,13 +2774,11 @@ class Choice(object): if self.selection is not None: fields.append("{} selected".format(self.selection.name)) - fields.append("{} menu node{}" - .format(len(self.nodes), - "" if len(self.nodes) == 1 else "s")) + for node in self.nodes: + fields.append("{}:{}".format(node.filename, node.linenr)) return "<{}>".format(", ".join(fields)) - # # Private methods # @@ -2757,17 +2800,15 @@ class Choice(object): self.nodes = [] - self.user_value = None - self.user_selection = None + self.user_str_value = self.user_tri_value = self.user_selection = None # The prompts and default values without any dependencies from # enclosing menus and ifs propagated self.defaults = [] # Cached values + self._cached_vis = self._cached_assignable = None self._cached_selection = _NO_CACHED_SELECTION - self._cached_vis = None - self._cached_assignable = None self.is_optional = False @@ -2778,21 +2819,21 @@ class Choice(object): vis = self.visibility - if vis == "n": + if not vis: return "" - if vis == "y": + if vis == 2: if not self.is_optional: return "y" if self.type == BOOL else "my" return "y" - # vis == "m" + # vis == 1 return "nm" if self.is_optional else "m" def _invalidate(self): - self._cached_selection = _NO_CACHED_SELECTION self._cached_vis = self._cached_assignable = None + self._cached_selection = _NO_CACHED_SELECTION class MenuNode(object): """ @@ -2920,15 +2961,17 @@ class MenuNode(object): if self.prompt is not None: fields.append('prompt "{}" (visibility {})' - .format(self.prompt[0], eval_expr(self.prompt[1]))) + .format(self.prompt[0], + TRI_TO_STR[eval_expr(self.prompt[1])])) if isinstance(self.item, Symbol) and self.is_menuconfig: fields.append("is menuconfig") - fields.append("deps " + eval_expr(self.dep)) + fields.append("deps " + TRI_TO_STR[eval_expr(self.dep)]) if self.item == MENU: - fields.append("'visible if' deps " + eval_expr(self.visibility)) + fields.append("'visible if' deps " + \ + TRI_TO_STR[eval_expr(self.visibility)]) if isinstance(self.item, (Symbol, Choice)) and self.help is not None: fields.append("has help") @@ -2957,65 +3000,26 @@ class InternalError(Exception): # Public functions # -def tri_less(v1, v2): - """ - Returns True if the tristate v1 is less than the tristate v2, where "n", - "m" and "y" are ordered from lowest to highest. - """ - return _TRI_TO_INT[v1] < _TRI_TO_INT[v2] - -def tri_less_eq(v1, v2): - """ - Returns True if the tristate v1 is less than or equal to the tristate v2, - where "n", "m" and "y" are ordered from lowest to highest. - """ - return _TRI_TO_INT[v1] <= _TRI_TO_INT[v2] - -def tri_greater(v1, v2): - """ - Returns True if the tristate v1 is greater than the tristate v2, where "n", - "m" and "y" are ordered from lowest to highest. - """ - return _TRI_TO_INT[v1] > _TRI_TO_INT[v2] - -def tri_greater_eq(v1, v2): - """ - Returns True if the tristate v1 is greater than or equal to the tristate - v2, where "n", "m" and "y" are ordered from lowest to highest. - """ - return _TRI_TO_INT[v1] >= _TRI_TO_INT[v2] - -# Expression evaluation - def eval_expr(expr): """ - Evaluates an expression to "n", "m", or "y". + TODO """ - if isinstance(expr, Symbol): - # Non-bool/tristate symbols are always "n" in a tristate sense, - # regardless of their value - return expr.value if expr._type in (BOOL, TRISTATE) else "n" + return expr.tri_value if expr[0] == AND: - ev1 = eval_expr(expr[1]) - - # Short-circuit the ev1 == "n" case - return "n" if ev1 == "n" else \ - _tri_min(ev1, eval_expr(expr[2])) + v1 = eval_expr(expr[1]) + # Short-circuit the n case as an optimization (~5% faster + # allnoconfig.py and allyesconfig.py, as of writing) + return 0 if not v1 else min(v1, eval_expr(expr[2])) if expr[0] == OR: - ev1 = eval_expr(expr[1]) - - # Short-circuit the ev1 == "y" case - return "y" if ev1 == "y" else \ - _tri_max(ev1, eval_expr(expr[2])) + v1 = eval_expr(expr[1]) + # Short-circuit the y case as an optimization + return 2 if v1 == 2 else max(v1, eval_expr(expr[2])) if expr[0] == NOT: - ev = eval_expr(expr[1]) - return "n" if ev == "y" else \ - "y" if ev == "n" else \ - "m" + return 2 - eval_expr(expr[1]) if expr[0] in _RELATIONS: # Implements <, <=, >, >= comparisons as well. These were added to @@ -3030,16 +3034,16 @@ def eval_expr(expr): # If both operands are strings... if op1._type == STRING and op2._type == STRING: # ...then compare them lexicographically - comp = _strcmp(op1.value, op2.value) + comp = _strcmp(op1.str_value, op2.str_value) else: # Otherwise, try to compare them as numbers... try: - comp = int(op1.value, _TYPE_TO_BASE[op1._type]) - \ - int(op2.value, _TYPE_TO_BASE[op2._type]) + comp = int(op1.str_value, _TYPE_TO_BASE[op1._type]) - \ + int(op2.str_value, _TYPE_TO_BASE[op2._type]) except ValueError: # Fall back on a lexicographic comparison if the operands don't # parse as numbers - comp = _strcmp(op1.value, op2.value) + comp = _strcmp(op1.str_value, op2.str_value) if oper == EQUAL: res = comp == 0 elif oper == UNEQUAL: res = comp != 0 @@ -3048,12 +3052,15 @@ def eval_expr(expr): elif oper == GREATER: res = comp > 0 elif oper == GREATER_EQUAL: res = comp >= 0 - return "y" if res else "n" + return 2*res _internal_error("Internal error while evaluating expression: " "unknown operation {}.".format(expr[0])) def expr_str(expr): + """ + TODO + """ if isinstance(expr, Symbol): return expr.name if not expr.is_constant else '"{}"'.format(expr.name) @@ -3085,47 +3092,35 @@ def _get_visibility(sc): 'make menuconfig'. This function calculates the visibility for the Symbol or Choice 'sc' -- the logic is nearly identical. """ - vis = "n" + vis = 0 for node in sc.nodes: if node.prompt: - vis = _tri_max(vis, eval_expr(node.prompt[1])) + vis = max(vis, eval_expr(node.prompt[1])) if isinstance(sc, Symbol) and sc.choice is not None: if sc.choice._type == TRISTATE and sc._type != TRISTATE and \ - sc.choice.value != "y": + sc.choice.tri_value != 2: # Non-tristate choice symbols in tristate choices depend on the # choice being in mode "y" - return "n" + return 0 - if sc._type == TRISTATE and vis == "m" and sc.choice.value == "y": + if sc._type == TRISTATE and vis == 1 and sc.choice.tri_value == 2: # Choice symbols with visibility "m" are not visible if the # choice has mode "y" - return "n" + return 0 - vis = _tri_min(vis, sc.choice.visibility) + vis = min(vis, sc.choice.visibility) - # Promote "m" to "y" if we're dealing with a non-tristate. This might lead - # to infinite recursion if something really weird is done with MODULES, but + # Promote m to y if we're dealing with a non-tristate. This might lead to + # infinite recursion if something really weird is done with MODULES, but # it's not a problem in practice. - if vis == "m" and \ - (sc._type != TRISTATE or sc.config.modules.value == "n"): - return "y" + if vis == 1 and \ + (sc._type != TRISTATE or not sc.config.modules.tri_value): + return 2 return vis -def _tri_min(v1, v2): - """ - Returns the smallest tristate value among v1 and v2. - """ - return v1 if _TRI_TO_INT[v1] <= _TRI_TO_INT[v2] else v2 - -def _tri_max(v1, v2): - """ - Returns the largest tristate value among v1 and v2. - """ - return v1 if _TRI_TO_INT[v1] >= _TRI_TO_INT[v2] else v2 - def _make_depend_on(sym, expr): """ Adds 'sym' as a dependency to all symbols in 'expr'. Constant symbols in @@ -3192,10 +3187,8 @@ def _strcmp(s1, s2): return (s1 > s2) - (s1 < s2) def _stderr_msg(msg, filename, linenr): - if filename is None: - s = msg - else: - s = "{}:{}: ".format(filename, linenr) + if filename is not None: + msg = "{}:{}: {}".format(filename, linenr, msg) sys.stderr.write(msg + "\n") @@ -3509,6 +3502,18 @@ def _finalize_tree(node): COMMENT, ) = range(2) +TRI_TO_STR = { + 0: "n", + 1: "m", + 2: "y", +} + +STR_TO_TRI = { + "n": 0, + "m": 1, + "y": 2, +} + # # Internal global constants # @@ -3643,12 +3648,12 @@ _sym_ref_re_search = re.compile(r"\$([A-Za-z0-9_]+)").search # Strings to use for types _TYPENAME = { - UNKNOWN: "unknown", - BOOL: "bool", + UNKNOWN: "unknown", + BOOL: "bool", TRISTATE: "tristate", - STRING: "string", - HEX: "hex", - INT: "int", + STRING: "string", + HEX: "hex", + INT: "int", } # Token to type mapping @@ -3662,16 +3667,6 @@ _TOKEN_TO_TYPE = { _T_TRISTATE: TRISTATE, } -# Default values for symbols of different types (the value the symbol gets if -# it is not assigned a user value and none of its 'default' clauses kick in) -_DEFAULT_VALUE = { - BOOL: "n", - TRISTATE: "n", - HEX: "", - INT: "", - STRING: "", -} - # Constant representing that there's no cached choice selection. This is # distinct from a cached None (no selection). We create a unique object (any # will do) for it so we can test with 'is'. diff --git a/testsuite.py b/testsuite.py index e81ba1d..711a0fb 100644 --- a/testsuite.py +++ b/testsuite.py @@ -168,9 +168,9 @@ def run_selftests(): Verifies that a symbol has a particular value. """ sym = c.syms[sym_name] - verify(sym.value == val, + verify(sym.str_value == val, 'expected {} to have the value "{}", had the value "{}"' - .format(sym_name, val, sym.value)) + .format(sym_name, val, sym.str_value)) def assign_and_verify_value(sym_name, val, new_val): """ @@ -178,13 +178,13 @@ def run_selftests(): 'new_val'. """ sym = c.syms[sym_name] - old_val = sym.value + old_val = sym.str_value sym.set_value(val) - verify(sym.value == new_val, + 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.value, old_val)) + .format(sym_name, new_val, val, sym.str_value, old_val)) def assign_and_verify(sym_name, user_val): """ @@ -197,51 +197,19 @@ def run_selftests(): """Assigns a user value to the symbol and verifies the new user value.""" sym = c.syms[sym_name] - sym_old_user_val = sym.user_value + sym_old_user_val = sym.user_str_value sym.set_value(val) - verify(sym.user_value == user_val, + verify(sym.user_str_value == user_val, "{} should have the user value '{}' after being assigned " "the user value '{}'. Instead, the new user value was '{}'. " "The old user value was '{}'." - .format(sym_name, user_val, user_val, sym.user_value, + .format(sym_name, user_val, user_val, sym.user_str_value, sym_old_user_val)) # # Selftests # - print("Testing tristate comparisons") - - def verify_truth_table(comp_fn, *table): - for (x, y), expected in zip((("n", "n"), ("n", "m"), ("n", "y"), - ("m", "n"), ("m", "m"), ("m", "y"), - ("y", "n"), ("y", "m"), ("y", "y")), - table): - verify(comp_fn(x, y) == expected, - "expected {} on ('{}', '{}') to be '{}'". - format(comp_fn, x, y, expected)) - - verify_truth_table(kconfiglib.tri_less, - False, True, True, - False, False, True, - False, False, False) - - verify_truth_table(kconfiglib.tri_less_eq, - True, True, True, - False, True, True, - False, False, True) - - verify_truth_table(kconfiglib.tri_greater, - False, False, False, - True, False, False, - True, True, False) - - verify_truth_table(kconfiglib.tri_greater_eq, - True, False, False, - True, True, False, - True, True, True) - - print("Testing string literal lexing") # Dummy empty configuration just to get a Config object @@ -250,14 +218,12 @@ def run_selftests(): def verify_string_lex(s, res): """ Verifies that a constant symbol with the name 'res' is produced from - lexing 's'. Strips the first and last characters from 's' so that - readable raw strings can be used as input + lexing 's' """ - c._line = s[1:-1] - c._tokenize(True) + c.eval_string(s) verify(c._tokens[0].name == res, - 'expected {} to produced the constant symbol {}, produced {}' - .format(s, c._tokens[0].name, res)) + "expected <{}> to produced the constant symbol <{}>, " + 'produced <{}>'.format(s[1:-1], c._tokens[0].name, res)) verify_string_lex(r""" "" """, "") verify_string_lex(r""" '' """, "") @@ -293,13 +259,12 @@ def run_selftests(): first and last characters from 's' so we can use readable raw strings as input. """ - c._line = s[1:-1] try: - c._tokenize(True) + c.eval_string(s) except kconfiglib.KconfigSyntaxError: pass else: - fail("expected tokenization of {} to fail, didn't".format(s)) + fail("expected tokenization of {} to fail, didn't".format(s[1:-1])) verify_string_bad(r""" " """) verify_string_bad(r""" ' """) @@ -323,168 +288,168 @@ def run_selftests(): "'{}' evaluated to {}, expected {}".format(expr, res, val)) # No modules - verify_eval("n", "n") - verify_eval("m", "n") - verify_eval("y", "y") - verify_eval("'n'", "n") - verify_eval("'m'", "n") - verify_eval("'y'", "y") - verify_eval("M", "y") + 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.syms["MODULES"].set_value("y") - verify_eval("n", "n") - verify_eval("m", "m") - verify_eval("y", "y") - verify_eval("'n'", "n") - verify_eval("'m'", "m") - verify_eval("'y'", "y") - verify_eval("M", "m") - verify_eval("(Y || N) && (m && y)", "m") + c.modules.set_value("y") + 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", "n") - verify_eval("Y_STRING || m", "m") + verify_eval("Y_STRING", 0) + verify_eval("Y_STRING || m", 1) # As are all constants besides "y" and "m" - verify_eval('"foo"', "n") - verify_eval('"foo" || "bar"', "n") - verify_eval('"foo" || m', "m") + verify_eval('"foo"', 0) + verify_eval('"foo" || "bar"', 0) + verify_eval('"foo" || m', 1) # Test equality for symbols - verify_eval("N = N", "y") - verify_eval("N = n", "y") - verify_eval("N = 'n'", "y") - verify_eval("N != N", "n") - verify_eval("N != n", "n") - verify_eval("N != 'n'", "n") - - verify_eval("M = M", "y") - verify_eval("M = m", "y") - verify_eval("M = 'm'", "y") - verify_eval("M != M", "n") - verify_eval("M != m", "n") - verify_eval("M != 'm'", "n") - - verify_eval("Y = Y", "y") - verify_eval("Y = y", "y") - verify_eval("Y = 'y'", "y") - verify_eval("Y != Y", "n") - verify_eval("Y != y", "n") - verify_eval("Y != 'y'", "n") - - verify_eval("N != M", "y") - verify_eval("N != Y", "y") - verify_eval("M != Y", "y") - - verify_eval("Y_STRING = y", "y") - verify_eval("Y_STRING = 'y'", "y") - verify_eval('FOO_BAR_STRING = "foo bar"', "y") - verify_eval('FOO_BAR_STRING != "foo bar baz"', "y") - verify_eval('INT_37 = 37', "y") - verify_eval("INT_37 = '37'", "y") - verify_eval('HEX_0X37 = 0x37', "y") - verify_eval("HEX_0X37 = '0x37'", "y") + 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'", "y") - verify_eval("HEX_0X37 = '0x0037'", "y") + verify_eval("HEX_0X37 = '0x037'", 2) + verify_eval("HEX_0X37 = '0x0037'", 2) # Constant symbol comparisons - verify_eval('"foo" != "bar"', "y") - verify_eval('"foo" = "bar"', "n") - verify_eval('"foo" = "foo"', "y") + 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", "y") - verify_eval("not_defined_2 = not_defined_2", "y") - verify_eval("not_defined_1 != not_defined_2", "y") + 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", "y") - verify_eval("38 < INT_37", "n") - verify_eval("INT_37 < '38'", "y") - verify_eval("'38' < INT_37", "n") - verify_eval("INT_37 < 138", "y") - verify_eval("138 < INT_37", "n") - verify_eval("INT_37 < '138'", "y") - verify_eval("'138' < INT_37", "n") - verify_eval("INT_37 < -138", "n") - verify_eval("-138 < INT_37", "y") - verify_eval("INT_37 < '-138'", "n") - verify_eval("'-138' < INT_37", "y") - verify_eval("INT_37 < 37", "n") - verify_eval("37 < INT_37", "n") - verify_eval("INT_37 < 36", "n") - verify_eval("36 < INT_37", "y") + 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", "y") # 38 - verify_eval("INT_37 < 0x25", "n") # 37 - verify_eval("INT_37 < 0x24", "n") # 36 - verify_eval("HEX_0X37 < 56", "y") # 0x38 - verify_eval("HEX_0X37 < 55", "n") # 0x37 - verify_eval("HEX_0X37 < 54", "n") # 0x36 + 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", "y") - verify_eval("INT_37 <= 37", "y") - verify_eval("INT_37 <= 36", "n") - verify_eval("INT_37 > 38", "n") - verify_eval("INT_37 > 37", "n") - verify_eval("INT_37 > 36", "y") - verify_eval("INT_37 >= 38", "n") - verify_eval("INT_37 >= 37", "y") - verify_eval("INT_37 >= 36", "y") + 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", "y") - verify_eval("HEX_0X37 <= 0x37", "y") - verify_eval("HEX_0X37 <= 0x36", "n") - verify_eval("HEX_0X37 > 0x38", "n") - verify_eval("HEX_0X37 > 0x37", "n") - verify_eval("HEX_0X37 > 0x36", "y") - verify_eval("HEX_0X37 >= 0x38", "n") - verify_eval("HEX_0X37 >= 0x37", "y") - verify_eval("HEX_0X37 >= 0x36", "y") + 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", "y") - verify_eval("HEX_37 < 0x37", "n") - verify_eval("HEX_37 < 0x36", "n") + 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", "y") - verify_eval("INT_37 > HEX_0X37", "n") - verify_eval("HEX_0X37 < INT_37 ", "n") - verify_eval("HEX_0X37 > INT_37 ", "y") - verify_eval("INT_37 < INT_37 ", "n") - verify_eval("INT_37 <= INT_37 ", "y") - verify_eval("INT_37 > INT_37 ", "n") - verify_eval("INT_37 <= INT_37 ", "y") + 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) # Strings compare lexicographically - verify_eval("'aa' < 'ab'", "y") - verify_eval("'aa' > 'ab'", "n") - verify_eval("'ab' < 'aa'", "n") - verify_eval("'ab' > 'aa'", "y") + 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' ", "y") - verify_eval("'37a' > INT_37", "y") - verify_eval("INT_37 <= '37a' ", "y") - verify_eval("'37a' >= INT_37", "y") - verify_eval("INT_37 >= '37a' ", "n") - verify_eval("INT_37 > '37a' ", "n") - verify_eval("'37a' < INT_37", "n") - verify_eval("'37a' <= INT_37", "n") + 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: @@ -519,6 +484,8 @@ def run_selftests(): c = kconfiglib.Config("Kconfiglib/tests/Kstr", warn=False) + c.modules.set_value("y") + verify_str(c.syms["UNDEFINED"], """ """) @@ -582,8 +549,10 @@ config INT # We still hardcode the modules symbol. Otherwise OPTIONS would have made # more sense as a name here. - verify_str(c.syms["MODULES"], """ + verify_str(c.modules, """ config MODULES + bool + prompt "MODULES" option modules """) @@ -619,59 +588,58 @@ choice c = kconfiglib.Config("Kconfiglib/tests/Krepr", warn=False) verify_repr(c.syms["UNDEFINED"], """ - + """) verify_repr(c.syms["BASIC"], """ - + """) 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.syms["MODULES"], """ - + verify_repr(c.modules, """ + """) - print("Testing Choice.__repr__()") verify_repr(c.named_choices["CHOICE"], """ - + """) c.named_choices["CHOICE"].set_value("y") verify_repr(c.named_choices["CHOICE"], """ - + """) c.syms["CHOICE_2"].set_value("y") verify_repr(c.named_choices["CHOICE"], """ - + """) verify_repr(c.syms["CHOICE_HOOK"].nodes[0].next.item, """ - + """) @@ -830,13 +798,13 @@ g c = kconfiglib.Config("Kconfiglib/tests/Kvisibility") def verify_visibility(item, no_module_vis, module_vis): - c.syms["MODULES"].set_value("n") + c.modules.set_value("n") verify(item.visibility == no_module_vis, "expected {} to have visibility {} without modules, had " "visibility {}". format(repr(item), no_module_vis, item.visibility)) - c.syms["MODULES"].set_value("y") + c.modules.set_value("y") verify(item.visibility == module_vis, "expected {} to have visibility {} with modules, had " "visibility {}". @@ -844,72 +812,70 @@ g # Symbol visibility - verify_visibility(c.syms["NO_PROMPT"], "n", "n") - verify_visibility(c.syms["BOOL_N"], "n", "n") - verify_visibility(c.syms["BOOL_M"], "n", "y") - verify_visibility(c.syms["BOOL_MOD"], "y", "y") - verify_visibility(c.syms["BOOL_Y"], "y", "y") - verify_visibility(c.syms["TRISTATE_M"], "n", "m") - verify_visibility(c.syms["TRISTATE_MOD"], "y", "m") - verify_visibility(c.syms["TRISTATE_Y"], "y", "y") - verify_visibility(c.syms["BOOL_IF_N"], "n", "n") - verify_visibility(c.syms["BOOL_IF_M"], "n", "y") - verify_visibility(c.syms["BOOL_IF_Y"], "y", "y") - verify_visibility(c.syms["BOOL_MENU_N"], "n", "n") - verify_visibility(c.syms["BOOL_MENU_M"], "n", "y") - verify_visibility(c.syms["BOOL_MENU_Y"], "y", "y") - verify_visibility(c.syms["BOOL_CHOICE_N"], "n", "n") + 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 - verify_visibility(c.syms["BOOL_CHOICE_M"], "n", "n") + # is in 2 mode + verify_visibility(c.syms["BOOL_CHOICE_M"], 0, 0) - # Tristate choices start out in "m" mode. When running without modules, - # their type gets adjusted to bool. - verify_visibility(c.syms["BOOL_CHOICE_Y"], "y", "n") + # Tristate choices start out in m mode. When running without modules, their + # type gets adjusted to bool. + verify_visibility(c.syms["BOOL_CHOICE_Y"], 2, 0) c.syms["TRISTATE_CHOICE_M"].set_value("y") c.syms["TRISTATE_CHOICE_Y"].set_value("y") # Still limited by the visibility of the choice - verify_visibility(c.syms["BOOL_CHOICE_M"], "n", "n") + verify_visibility(c.syms["BOOL_CHOICE_M"], 0, 0) # This one should become visible now - verify_visibility(c.syms["BOOL_CHOICE_Y"], "y", "y") - - verify_visibility(c.syms["TRISTATE_IF_N"], "n", "n") - verify_visibility(c.syms["TRISTATE_IF_M"], "n", "m") - verify_visibility(c.syms["TRISTATE_IF_Y"], "y", "y") - verify_visibility(c.syms["TRISTATE_MENU_N"], "n", "n") - verify_visibility(c.syms["TRISTATE_MENU_M"], "n", "m") - verify_visibility(c.syms["TRISTATE_MENU_Y"], "y", "y") - verify_visibility(c.syms["TRISTATE_CHOICE_N"], "n", "n") - verify_visibility(c.syms["TRISTATE_CHOICE_M"], "n", "m") - verify_visibility(c.syms["TRISTATE_CHOICE_Y"], "y", "y") - - verify_visibility(c.named_choices["BOOL_CHOICE_N"], "n", "n") - verify_visibility(c.named_choices["BOOL_CHOICE_M"], "n", "y") - verify_visibility(c.named_choices["BOOL_CHOICE_Y"], "y", "y") - verify_visibility(c.named_choices["TRISTATE_CHOICE_N"], "n", "n") - verify_visibility(c.named_choices["TRISTATE_CHOICE_M"], "n", "m") - verify_visibility(c.named_choices["TRISTATE_CHOICE_Y"], "y", "y") - - verify_visibility(c.named_choices["TRISTATE_CHOICE_IF_M_AND_Y"], - "n", "m") - verify_visibility(c.named_choices["TRISTATE_CHOICE_MENU_N_AND_Y"], - "n", "n") + 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) # Menu visibility def verify_menu_visibility(menu, no_module_vis, module_vis): - c["MODULES"].set_value("n") + c.modules.set_value("n") menu_vis = kconfiglib.eval_expr(menu.node.dep) verify(menu_vis == no_module_vis, "menu \"{}\" should have visibility '{}' without modules, " "has visibility '{}'" .format(menu.title, no_module_vis, menu_vis)) - c["MODULES"].set_value("y") + c.modules.set_value("y") menu_vis = kconfiglib.eval_expr(menu.node.dep) verify(menu_vis == module_vis, "menu \"{}\" should have visibility '{}' with modules, " @@ -935,14 +901,14 @@ g menu_visible_if_m_2 = get_menus(c)[13:] def verify_visible_if_visibility(menu, no_module_vis, module_vis): - c["MODULES"].set_value("n") + c.modules.set_value("n") menu_vis = menu.get_visible_if_visibility() verify(menu_vis == no_module_vis, "menu \"{}\" should have 'visible if' visibility '{}' " "without modules, has 'visible if' visibility '{}'". format(menu.title, no_module_vis, menu_vis)) - c["MODULES"].set_value("y") + c.modules.set_value("y") menu_vis = menu.get_visible_if_visibility() verify(menu_vis == module_vis, "menu \"{}\" should have 'visible if' visibility '{}' " @@ -963,15 +929,15 @@ g #verify_visible_if_visibility(menu_visible_if_m_2, "n", "m") # Verify that 'visible if' visibility gets propagated to prompts - verify_visibility(c.syms["VISIBLE_IF_N"], "n", "n") - verify_visibility(c.syms["VISIBLE_IF_M"], "n", "m") - verify_visibility(c.syms["VISIBLE_IF_Y"], "y", "y") - verify_visibility(c.syms["VISIBLE_IF_M_2"], "n", "m") + 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) # Comment visibility def verify_comment_visibility(comment, no_module_vis, module_vis): - c["MODULES"].set_value("n") + c.modules.set_value("n") # TODO: uninternalize comment_vis = kconfiglib.eval_expr(comment.node.dep) verify(comment_vis == no_module_vis, @@ -979,7 +945,7 @@ g "modules, has visibility '{}'". format(comment.text, no_module_vis, comment_vis)) - c["MODULES"].set_value("y") + c.modules.set_value("y") comment_vis = kconfiglib.eval_expr(comment.node.dep) verify(comment_vis == module_vis, "comment \"{}\" should have visibility '{}' with " @@ -1251,7 +1217,7 @@ g ("BOOL", "TRISTATE", "STRING", "INT", "HEX")] for sym in syms: - verify(sym.user_value is None, + verify(sym.user_str_value is None and sym.user_tri_value is None, "{} should not have a user value to begin with") # Assign valid values for the types @@ -1278,7 +1244,7 @@ g for s in syms: s.unset_value() - verify(s.user_value is None, + verify(s.user_str_value is None and s.user_tri_value is None, "{} should not have a user value after being reset". format(s.name)) @@ -1399,7 +1365,7 @@ g # .config # - print("Testing .config...") + print("Testing Config separation...") c1 = kconfiglib.Config("Kconfiglib/tests/Kmisc", warn=False) c2 = kconfiglib.Config("Kconfiglib/tests/Kmisc", warn=False) @@ -1547,9 +1513,8 @@ g def select_and_verify(sym): choice = get_parent(sym) sym.set_value("y") - verify(choice.value == "y", - 'The mode of the choice should be "y" after selecting a ' - "symbol") + verify(choice.str_value == "y", + "The mode of the choice should be y after selecting a symbol") verify(sym.choice.selection is sym, "{} should be the selected choice symbol" .format(sym.name)) @@ -1568,25 +1533,24 @@ g select_and_verify(choice.syms[i]) def verify_mode(choice, no_modules_mode, modules_mode): - c.syms["MODULES"].set_value("n") - choice_mode = choice.value + c.modules.set_value("n") + choice_mode = choice.tri_value verify(choice_mode == no_modules_mode, - 'Wrong mode for choice {} with no modules. Expected "{}", ' - 'got "{}".'.format(choice.name, no_modules_mode, choice_mode)) + 'Wrong mode for choice {} with no modules. Expected {}, got {}.' + .format(choice.name, no_modules_mode, choice_mode)) - c.syms["MODULES"].set_value("y") - choice_mode = choice.value + c.modules.set_value("y") + choice_mode = choice.tri_value verify(choice_mode == modules_mode, - 'Wrong mode for choice {} with modules. Expected "{}", ' - 'got "{}".'.format(choice.name, modules_mode, - choice_mode)) + 'Wrong mode for choice {} with modules. Expected {}, got {}.' + .format(choice.name, modules_mode, choice_mode)) - verify_mode(choice_bool, "y", "y") - verify_mode(choice_bool_opt, "n", "n") - verify_mode(choice_tristate, "y", "m") - verify_mode(choice_tristate_opt, "n", "n") - verify_mode(choice_bool_m, "y", "y") - verify_mode(choice_tristate_m, "y", "m") + verify_mode(choice_bool, 2, 2) + verify_mode(choice_bool_opt, 0, 0) + verify_mode(choice_tristate, 2, 1) + verify_mode(choice_tristate_opt, 0, 0) + verify_mode(choice_bool_m, 2, 2) + verify_mode(choice_tristate_m, 2, 1) # Test defaults @@ -1606,7 +1570,7 @@ g # Test "y" mode selection - c.syms["MODULES"].set_value("y") + c.modules.set_value("y") select_and_verify_all(choice_bool) select_and_verify_all(choice_bool_opt) @@ -1621,12 +1585,12 @@ g for sym_name in ("T_1", "T_2"): assign_and_verify_value(sym_name, "m", "m") - verify(choice_tristate.value == "m", + verify(choice_tristate.tri_value == 1, 'Selecting {} to "m" should have changed the mode of the ' 'choice to "m"'.format(sym_name)) assign_and_verify_value(sym_name, "y", "y") - verify(choice_tristate.value == "y" and + verify(choice_tristate.tri_value == 2 and choice_tristate.selection is c.syms[sym_name], 'Selecting {} to "y" should have changed the mode of the ' 'choice to "y" and made it the selection'.format(sym_name)) @@ -1638,7 +1602,7 @@ g assign_and_verify_value(sym_name, "n", "n") # "y" should be truncated assign_and_verify_value(sym_name, "y", "m") - verify(choice_tristate_m.value == "m", + verify(choice_tristate_m.tri_value == 1, 'A choice that can only be in "m" mode was not') # Verify that choices with no explicitly specified type get the type of the @@ -1746,8 +1710,10 @@ g "\nSome selftests failed\n") def run_compatibility_tests(): - """Runs tests on configurations from the kernel. Tests compability with the - C implementation by comparing outputs.""" + """ + Runs tests on configurations from the kernel. Tests compability with the + C implementation by comparing outputs. + """ os.environ.pop("ARCH", None) os.environ.pop("SRCARCH", None) @@ -1798,9 +1764,9 @@ def run_compatibility_tests(): if compare_configs: if equal_confs(): - print(" {:14}OK".format(arch)) + print("{:14}OK".format(arch)) else: - print(" {:14}FAIL".format(arch)) + print("{:14}FAIL".format(arch)) fail() if all_passed: @@ -1810,8 +1776,9 @@ def run_compatibility_tests(): print("Some tests failed") def get_arch_srcarch_list(): - """Returns a list of (ARCH, SRCARCH) tuples to test.""" - + """ + Returns a list of (ARCH, SRCARCH) tuples to test. + """ res = [] def add_arch(arch): @@ -1837,16 +1804,17 @@ def get_arch_srcarch_list(): return res def test_load(conf, arch): - """Load all arch Kconfigs to make sure we don't throw any errors""" - print(" {:14}OK".format(arch)) + """ + Load all arch Kconfigs to make sure we don't throw any errors + """ + print("{:14}OK".format(arch)) -# The weird docstring formatting is to get the format right when we print the -# docstring ourselves def test_all_no(conf, arch): """ Verify that our examples/allnoconfig.py script generates the same .config as 'make allnoconfig', for each architecture. Runs the script via - 'make scriptconfig', so kinda slow even in speedy mode.""" + 'make scriptconfig', so kinda slow even in speedy mode. + """ # TODO: Support speedy mode for running the script shell("make scriptconfig SCRIPT=Kconfiglib/examples/allnoconfig.py " @@ -1861,7 +1829,8 @@ def test_all_no_simpler(conf, arch): """ Verify that our examples/allnoconfig_simpler.py script generates the same .config as 'make allnoconfig', for each architecture. Runs the script via - 'make scriptconfig', so kinda slow even in speedy mode.""" + 'make scriptconfig', so kinda slow even in speedy mode. + """ # TODO: Support speedy mode for running the script shell("make scriptconfig SCRIPT=Kconfiglib/examples/allnoconfig_simpler.py " @@ -1876,7 +1845,8 @@ def test_all_yes(conf, arch): """ Verify that our examples/allyesconfig.py script generates the same .config as 'make allyesconfig', for each architecture. Runs the script via - 'make scriptconfig', so kinda slow even in speedy mode.""" + 'make scriptconfig', so kinda slow even in speedy mode. + """ # TODO: Support speedy mode for running the script shell("make scriptconfig SCRIPT=Kconfiglib/examples/allyesconfig.py " @@ -1889,11 +1859,12 @@ def test_all_yes(conf, arch): def test_call_all(conf, arch): """ - Call all public methods on all symbols, menus, choices, and comments for + Call all public methods on all symbols, choices, and TODO menu nodes for all architectures to make sure we never crash or hang. (Nearly all public methods: some are hard to test like this, but are exercised by other - tests.)""" - print(" For {}...".format(arch)) + tests.) + """ + print("For {}...".format(arch)) conf.defconfig_filename conf.mainmenu_text @@ -1909,7 +1880,8 @@ def test_call_all(conf, arch): s.__repr__() s.assignable s.type - s.value + s.str_value + s.tri_value s.visibility s.unset_value() @@ -1933,7 +1905,8 @@ def test_call_all(conf, arch): for c in conf._choices: c.__str__() c.__repr__() - c.value + c.str_value + c.tri_value c.assignable c.selection c.default_selection @@ -1943,7 +1916,8 @@ def test_call_all(conf, arch): def test_config_absent(conf, arch): """ Verify that Kconfiglib generates the same .config as 'make alldefconfig', - for each architecture""" + for each architecture + """ conf.write_config("._config") if speedy: shell("scripts/kconfig/conf --alldefconfig Kconfig") @@ -1959,7 +1933,8 @@ def test_defconfig(conf, arch): run. With logging enabled, this test appends any failures to a file - test_defconfig_fails in the root.""" + test_defconfig_fails in the root. + """ global nconfigs defconfigs = [] @@ -2037,8 +2012,10 @@ def test_defconfig(conf, arch): # def rm_configs(): - """Delete any old ".config" (generated by the C implementation) and - "._config" (generated by us), if present.""" + """ + 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) -- cgit v1.2.3 From 840d65fe069297fb1d088b85e3164465ace4d467 Mon Sep 17 00:00:00 2001 From: Ulf Magnusson Date: Sat, 28 Oct 2017 05:18:37 +0200 Subject: Test suite work, cleanup, const sym invalidation fix --- kconfiglib.py | 203 ++++++++++++++++++++++++++++++++++------------------------ testsuite.py | 186 +++++++++++++++++++++++++++++++++++++---------------- 2 files changed, 250 insertions(+), 139 deletions(-) (limited to 'testsuite.py') diff --git a/kconfiglib.py b/kconfiglib.py index 25bb380..3262b43 100644 --- a/kconfiglib.py +++ b/kconfiglib.py @@ -360,13 +360,13 @@ class Config(object): # Predefined symbol. DEFCONFIG_LIST uses this. uname_sym = self._lookup_const_sym("UNAME_RELEASE") uname_sym._type = STRING - uname_sym.defaults.append( - (self._lookup_const_sym(platform.uname()[2]), - self.y)) # env_var doubles as the SYMBOL_AUTO flag from the C implementation, so # just set it to something. The naming breaks a bit here, but it's # pretty obscure. uname_sym.env_var = "" + uname_sym.defaults.append( + (self._lookup_const_sym(platform.uname()[2]), + self.y)) self.syms["UNAME_RELEASE"] = uname_sym self.top_node = MenuNode() @@ -418,8 +418,8 @@ class Config(object): if self.defconfig_list is None: return None - for filename, cond_expr in self.defconfig_list.defaults: - if eval_expr(cond_expr): + for filename, cond in self.defconfig_list.defaults: + if expr_value(cond): filename = self._expand_sym_refs(filename.str_value) try: with self._open(filename) as f: @@ -559,7 +559,7 @@ class Config(object): self._line = s del self._tokens[0] - return eval_expr(self._parse_expr(True)) + return expr_value(self._parse_expr(True)) def unset_values(self): """ @@ -921,7 +921,6 @@ class Config(object): self._reuse_line = False while self._line.endswith("\\\n"): - # TODO: Can hang if the file ends with a backslash self._line = self._line[:-2] + self._file.readline() self._linenr += 1 @@ -1346,39 +1345,39 @@ class Config(object): node.prompt = None # Add the new defaults, with dependencies propagated - for val_expr, cond_expr in defaults: + for val_expr, cond in defaults: node.item.defaults.append( - (val_expr, self._make_and(cond_expr, node.dep))) + (val_expr, self._make_and(cond, node.dep))) # Add the new ranges, with dependencies propagated - for low, high, cond_expr in ranges: + for low, high, cond in ranges: node.item.ranges.append( - (low, high, self._make_and(cond_expr, node.dep))) + (low, high, self._make_and(cond, node.dep))) # Handle selects - for target, cond_expr in selects: + for target, cond in selects: # Only stored for convenience. Not used during evaluation. node.item.selects.append( - (target, self._make_and(cond_expr, node.dep))) + (target, self._make_and(cond, node.dep))) # Modify the dependencies of the selected symbol target.rev_dep = \ self._make_or(target.rev_dep, self._make_and(node.item, - self._make_and(cond_expr, + self._make_and(cond, node.dep))) # Handle implies - for target, cond_expr in implies: + for target, cond in implies: # Only stored for convenience. Not used during evaluation. node.item.implies.append( - (target, self._make_and(cond_expr, node.dep))) + (target, self._make_and(cond, node.dep))) # Modify the dependencies of the implied symbol target.weak_rev_dep = \ self._make_or(target.weak_rev_dep, self._make_and(node.item, - self._make_and(cond_expr, + self._make_and(cond, node.dep))) def _parse_expr(self, transform_m): @@ -1559,8 +1558,8 @@ class Config(object): add_fn(config_string) sym._already_written = True - elif eval_expr(node.dep) and \ - ((node.item == MENU and eval_expr(node.visibility)) or + elif expr_value(node.dep) and \ + ((node.item == MENU and expr_value(node.visibility)) or node.item == COMMENT): add_fn("\n#\n# {}\n#\n".format(node.prompt[0])) @@ -1994,8 +1993,8 @@ class Symbol(object): base = _TYPE_TO_BASE[self._type] # Check if a range is in effect - for low_expr, high_expr, cond_expr in self.ranges: - if eval_expr(cond_expr): + for low_expr, high_expr, cond in self.ranges: + if expr_value(cond): has_active_range = True low = int(low_expr.str_value, base) if \ @@ -2020,8 +2019,8 @@ class Symbol(object): else: # No user value or invalid user value. Look at defaults. - for val_expr, cond_expr in self.defaults: - if eval_expr(cond_expr): + for val_expr, cond in self.defaults: + if expr_value(cond): self._write_to_conf = True # Similarly to above, well-formed defaults are @@ -2059,8 +2058,8 @@ class Symbol(object): if vis and self.user_str_value is not None: val = self.user_str_value else: - for val_expr, cond_expr in self.defaults: - if eval_expr(cond_expr): + for val_expr, cond in self.defaults: + if expr_value(cond): self._write_to_conf = True val = val_expr.str_value break @@ -2096,22 +2095,22 @@ class Symbol(object): # (implies) for default, cond in self.defaults: - cond_val = eval_expr(cond) + cond_val = expr_value(cond) if cond_val: - val = min(cond_val, eval_expr(default)) + val = min(cond_val, expr_value(default)) self._write_to_conf = True break # Weak reverse dependencies are only considered if our # direct dependencies are met - if eval_expr(self.direct_dep): - weak_rev_dep_val = eval_expr(self.weak_rev_dep) + if expr_value(self.direct_dep): + weak_rev_dep_val = expr_value(self.weak_rev_dep) if weak_rev_dep_val: val = max(weak_rev_dep_val, val) self._write_to_conf = True # Reverse (select-related) dependencies take precedence - rev_dep_val = eval_expr(self.rev_dep) + rev_dep_val = expr_value(self.rev_dep) if rev_dep_val: val = max(rev_dep_val, val) self._write_to_conf = True @@ -2139,7 +2138,7 @@ class Symbol(object): # 1) If our type is boolean # 2) If our weak_rev_dep (from IMPLY) is y if val == 1 and \ - (self.type == BOOL or eval_expr(self.weak_rev_dep) == 2): + (self.type == BOOL or expr_value(self.weak_rev_dep) == 2): val = 2 self._cached_tri_val = val @@ -2259,38 +2258,51 @@ class Symbol(object): Prints some information about the symbol (including its name, value, visibility, and location(s)) when it is evaluated. """ - fields = [ - "symbol " + self.name, - _TYPENAME[self.type], - 'value "{}"'.format(self.str_value), - "visibility " + TRI_TO_STR[self.visibility], - ] + fields = [] - if self.user_str_value is not None: - fields.append('user value "{}"'.format(self.user_str_value)) + fields.append("symbol " + self.name) + fields.append(_TYPENAME[self.type]) - if self.choice is not None: - fields.append("choice symbol") + for node in self.nodes: + if node.prompt is not None: + fields.append('"{}"'.format(node.prompt[0])) - if self.is_allnoconfig_y: - fields.append("allnoconfig_y") + fields.append('value "{}"'.format(self.str_value)) - if self is self.config.defconfig_list: - fields.append("is the defconfig_list symbol") + if not self.is_constant: + # These aren't helpful to show for constant symbols - if self.env_var is not None: - fields.append("from environment variable " + self.env_var) + if self.user_str_value is not None: + fields.append('user value "{}"'.format(self.user_str_value)) - if self is self.config.modules: - fields.append("is the modules symbol") + fields.append("visibility " + TRI_TO_STR[self.visibility]) - fields.append("direct deps " + TRI_TO_STR[eval_expr(self.direct_dep)]) + if self.choice is not None: + fields.append("choice symbol") + + if self.is_allnoconfig_y: + fields.append("allnoconfig_y") + + if self is self.config.defconfig_list: + fields.append("is the defconfig_list symbol") + + if self.env_var is not None: + fields.append("from environment variable " + self.env_var) + + if self is self.config.modules: + fields.append("is the modules symbol") + + fields.append("direct deps " + + TRI_TO_STR[expr_value(self.direct_dep)]) if self.nodes: for node in self.nodes: fields.append("{}:{}".format(node.filename, node.linenr)) else: - fields.append("undefined") + if self.is_constant: + fields.append("constant") + else: + fields.append("undefined") return "<{}>".format(", ".join(fields)) @@ -2358,11 +2370,11 @@ class Symbol(object): if not vis: return "" - rev_dep_val = eval_expr(self.rev_dep) + rev_dep_val = expr_value(self.rev_dep) if vis == 2: if not rev_dep_val: - if self.type == BOOL or eval_expr(self.weak_rev_dep) == 2: + if self.type == BOOL or expr_value(self.weak_rev_dep) == 2: return "ny" return "nmy" @@ -2371,14 +2383,14 @@ class Symbol(object): # rev_dep_val == 1 - if self.type == BOOL or eval_expr(self.weak_rev_dep) == 2: + if self.type == BOOL or expr_value(self.weak_rev_dep) == 2: return "y" return "my" # vis == 1 if not rev_dep_val: - return "m" if eval_expr(self.weak_rev_dep) != 2 else "y" + return "m" if expr_value(self.weak_rev_dep) != 2 else "y" if rev_dep_val == 2: return "y" @@ -2451,10 +2463,14 @@ class Symbol(object): Invalidates the symbol and all symbols and choices that (possibly indirectly) depend on it """ - self._invalidate() + # Constant symbols must never be invalidated, because they lose their + # value. They never appear as dependencies, but can still be manually + # assigned a user value (and that's OK, though pointless). + if not self.is_constant: + self._invalidate() - for item in self._get_dependent(): - item._invalidate() + for item in self._get_dependent(): + item._invalidate() def _get_dependent(self): """ @@ -2708,8 +2724,8 @@ class Choice(object): """ See the class documentation. """ - for sym, cond_expr in self.defaults: - if eval_expr(cond_expr) and sym.visibility: + for sym, cond in self.defaults: + if expr_value(cond) and sym.visibility: return sym # Otherwise, pick the first visible symbol, if any @@ -2761,19 +2777,38 @@ class Choice(object): """ TODO """ - fields = [ - "choice" if self.name is None else "choice " + self.name, - _TYPENAME[self.type], - "mode " + self.str_value, - "visibility " + TRI_TO_STR[self.visibility], - ] + fields = [] - if self.is_optional: - fields.append("optional") + fields.append("choice" if self.name is None else \ + "choice " + self.name) + fields.append(_TYPENAME[self.type]) + + for node in self.nodes: + if node.prompt is not None: + fields.append('"{}"'.format(node.prompt[0])) + + fields.append("mode " + self.str_value) + + if self.user_str_value is not None: + fields.append('user mode {}'.format(self.user_str_value)) if self.selection is not None: fields.append("{} selected".format(self.selection.name)) + if self.user_selection is not None: + user_sel_str = "{} selected by user" \ + .format(self.user_selection.name) + + if self.selection is not self.user_selection: + user_sel_str += " (overriden)" + + fields.append(user_sel_str) + + fields.append("visibility " + TRI_TO_STR[self.visibility]) + + if self.is_optional: + fields.append("optional") + for node in self.nodes: fields.append("{}:{}".format(node.filename, node.linenr)) @@ -2932,46 +2967,42 @@ class MenuNode(object): ) def __repr__(self): + """ + TODO + """ fields = [] if isinstance(self.item, Symbol): fields.append("menu node for symbol " + self.item.name) - elif isinstance(self.item, Choice): s = "menu node for choice" if self.item.name is not None: s += " " + self.item.name fields.append(s) - elif self.item == MENU: fields.append("menu node for menu") - elif self.item == COMMENT: fields.append("menu node for comment") - elif self.item is None: fields.append("menu node for if (should not appear in the final " " tree)") - else: raise InternalError("unable to determine type in " "MenuNode.__repr__()") - fields.append("{}:{}".format(self.filename, self.linenr)) - if self.prompt is not None: fields.append('prompt "{}" (visibility {})' .format(self.prompt[0], - TRI_TO_STR[eval_expr(self.prompt[1])])) + TRI_TO_STR[expr_value(self.prompt[1])])) if isinstance(self.item, Symbol) and self.is_menuconfig: fields.append("is menuconfig") - fields.append("deps " + TRI_TO_STR[eval_expr(self.dep)]) + fields.append("deps " + TRI_TO_STR[expr_value(self.dep)]) if self.item == MENU: fields.append("'visible if' deps " + \ - TRI_TO_STR[eval_expr(self.visibility)]) + TRI_TO_STR[expr_value(self.visibility)]) if isinstance(self.item, (Symbol, Choice)) and self.help is not None: fields.append("has help") @@ -2982,6 +3013,8 @@ class MenuNode(object): if self.next is not None: fields.append("has next") + fields.append("{}:{}".format(self.filename, self.linenr)) + return "<{}>".format(", ".join(fields)) class KconfigSyntaxError(Exception): @@ -3000,7 +3033,7 @@ class InternalError(Exception): # Public functions # -def eval_expr(expr): +def expr_value(expr): """ TODO """ @@ -3008,18 +3041,18 @@ def eval_expr(expr): return expr.tri_value if expr[0] == AND: - v1 = eval_expr(expr[1]) + v1 = expr_value(expr[1]) # Short-circuit the n case as an optimization (~5% faster # allnoconfig.py and allyesconfig.py, as of writing) - return 0 if not v1 else min(v1, eval_expr(expr[2])) + return 0 if not v1 else min(v1, expr_value(expr[2])) if expr[0] == OR: - v1 = eval_expr(expr[1]) + v1 = expr_value(expr[1]) # Short-circuit the y case as an optimization - return 2 if v1 == 2 else max(v1, eval_expr(expr[2])) + return 2 if v1 == 2 else max(v1, expr_value(expr[2])) if expr[0] == NOT: - return 2 - eval_expr(expr[1]) + return 2 - expr_value(expr[1]) if expr[0] in _RELATIONS: # Implements <, <=, >, >= comparisons as well. These were added to @@ -3096,7 +3129,7 @@ def _get_visibility(sc): for node in sc.nodes: if node.prompt: - vis = max(vis, eval_expr(node.prompt[1])) + vis = max(vis, expr_value(node.prompt[1])) if isinstance(sc, Symbol) and sc.choice is not None: if sc.choice._type == TRISTATE and sc._type != TRISTATE and \ diff --git a/testsuite.py b/testsuite.py index 711a0fb..4af7c19 100644 --- a/testsuite.py +++ b/testsuite.py @@ -56,7 +56,7 @@ def fail(msg=None): global all_passed all_passed = False if msg is not None: - print("Fail: " + msg) + print("fail: " + msg) def verify(cond, msg): if not cond: @@ -587,6 +587,18 @@ choice c = kconfiglib.Config("Kconfiglib/tests/Krepr", warn=False) + verify_repr(c.n, """ + +""") + + verify_repr(c.m, """ + +""") + + verify_repr(c.y, """ + +""") + verify_repr(c.syms["UNDEFINED"], """ """) @@ -596,7 +608,13 @@ choice """) verify_repr(c.syms["VISIBLE"], """ - + +""") + + c.syms["VISIBLE"].set_value("y") + + verify_repr(c.syms["VISIBLE"], """ + """) verify_repr(c.syms["DIR_DEP_N"], """ @@ -612,7 +630,7 @@ choice """) verify_repr(c.syms["CHOICE_1"], """ - + """) verify_repr(c.modules, """ @@ -623,66 +641,72 @@ choice print("Testing Choice.__repr__()") verify_repr(c.named_choices["CHOICE"], """ - + """) c.named_choices["CHOICE"].set_value("y") verify_repr(c.named_choices["CHOICE"], """ - + """) c.syms["CHOICE_2"].set_value("y") verify_repr(c.named_choices["CHOICE"], """ - + +""") + + c.syms["CHOICE_1"].set_value("m") + + 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, """ - + """) @@ -869,14 +893,14 @@ g def verify_menu_visibility(menu, no_module_vis, module_vis): c.modules.set_value("n") - menu_vis = kconfiglib.eval_expr(menu.node.dep) + menu_vis = kconfiglib.expr_value(menu.node.dep) verify(menu_vis == no_module_vis, "menu \"{}\" should have visibility '{}' without modules, " "has visibility '{}'" .format(menu.title, no_module_vis, menu_vis)) c.modules.set_value("y") - menu_vis = kconfiglib.eval_expr(menu.node.dep) + menu_vis = kconfiglib.expr_value(menu.node.dep) verify(menu_vis == module_vis, "menu \"{}\" should have visibility '{}' with modules, " "has visibility '{}'". @@ -939,14 +963,14 @@ g def verify_comment_visibility(comment, no_module_vis, module_vis): c.modules.set_value("n") # TODO: uninternalize - comment_vis = kconfiglib.eval_expr(comment.node.dep) + comment_vis = kconfiglib.expr_value(comment.node.dep) verify(comment_vis == no_module_vis, "comment \"{}\" should have visibility '{}' without " "modules, has visibility '{}'". format(comment.text, no_module_vis, comment_vis)) c.modules.set_value("y") - comment_vis = kconfiglib.eval_expr(comment.node.dep) + comment_vis = kconfiglib.expr_value(comment.node.dep) verify(comment_vis == module_vis, "comment \"{}\" should have visibility '{}' with " "modules, has visibility '{}'". @@ -1731,11 +1755,11 @@ def run_compatibility_tests(): # (generated by the C implementation) should be compared to ._config # (generated by us) after each invocation. all_arch_tests = [(test_load, False), - (test_config_absent, True), - (test_call_all, False), + (test_alldefconfig, True), + (test_sanity, False), (test_all_no, True), - (test_all_yes, True), (test_all_no_simpler, True), + (test_all_yes, True), # Needs to report success/failure for each arch/defconfig # combo, hence False. (test_defconfig, False)] @@ -1857,49 +1881,78 @@ def test_all_yes(conf, arch): else: shell("make allyesconfig") -def test_call_all(conf, arch): +def test_sanity(conf, arch): """ - Call all public methods on all symbols, choices, and TODO menu nodes for - all architectures to make sure we never crash or hang. (Nearly all public - methods: some are hard to test like this, but are exercised by other - tests.) + 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)) + conf.modules + conf.defconfig_list conf.defconfig_filename - conf.mainmenu_text conf.enable_undef_warnings() conf.disable_undef_warnings() - conf.disable_warnings() conf.enable_warnings() + conf.disable_warnings() + conf.mainmenu_text conf.unset_values() # Python 2/3 compatible - for _, s in conf.syms.items(): - s.__str__() - s.__repr__() - s.assignable - s.type - s.str_value - s.tri_value - s.visibility - s.unset_value() + for _, sym in conf.syms.items(): + if sym.name != "UNAME_RELEASE": + verify(not sym.is_constant, sym.name + " in 'syms' and constant") + + verify(sym not in conf.const_syms, + sym.name + " in both 'syms' and 'const_syms'") + + for dep in sym._direct_dependents: + verify(not dep.is_constant, + "the constant symbol {} depends on {}" + .format(dep.name, sym.name)) + + sym.__repr__() + sym.__str__() + sym.assignable + conf.disable_warnings() + sym.set_value("y") + conf.enable_warnings() + sym.str_value + sym.tri_value + sym.type + sym.unset_value() + sym.user_str_value + sym.user_tri_value + sym.visibility + + for sym in conf.defined_syms: + verify(sym.nodes, sym.name + " is defined but lacks menu nodes") + + for _, sym in conf.const_syms.items(): + verify(sym.is_constant, + '"{}" is in const_syms but not marked constant' + .format(sym.name)) - # TODO: verify that constant symbols do not: - # 1) have a non-empty dep - # 2) have nodes + verify(not sym.nodes, + '"{}" is constant but has menu nodes' + .format(sym.name)) - # TODO: Look for weird stuff in the dictionaries + verify(not sym._direct_dependents, + '"{}" is constant but is a dependency of some symbol' + .format(sym.name)) - # TODO: infinite recursion action - #for _, s in conf.const_syms.items(): - # s.__str__() - # s.__repr__() - # s.assignable - # s.type - # s.value - # s.visibility - # s.unset_value() + sym.__repr__() + sym.__str__() + sym.assignable + conf.disable_warnings() + sym.set_value("y") + conf.enable_warnings() + sym.str_value + sym.tri_value + sym.type + sym.unset_value() + sym.visibility # Cheat with internals for c in conf._choices: @@ -1907,13 +1960,38 @@ def test_call_all(conf, arch): c.__repr__() c.str_value c.tri_value + c.user_str_value + c.user_tri_value c.assignable c.selection c.default_selection c.type c.visibility -def test_config_absent(conf, arch): + # Menu nodes + + node = conf.top_node + + while 1: + # Everything else should be well exercised elsewhere + node.__repr__() + + 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(conf, arch): """ Verify that Kconfiglib generates the same .config as 'make alldefconfig', for each architecture -- cgit v1.2.3 From 989e9f77cfe8caabc7ac241572e9b52682901135 Mon Sep 17 00:00:00 2001 From: Ulf Magnusson Date: Mon, 30 Oct 2017 00:50:09 +0100 Subject: Consistently use 0/1/2 for tristate values Easier to work with, allowing e.g. direct comparisons with < and >. Make set_value() take 0, 1, 2 for bool and tristate symbols, and fix other APIs to match. Also: - Add introductions to various concepts in the module docstring. Document some more attributes. Still TODOs. - Rename the Config class to Kconfig. - Escape " and \ in the name of constant symbols when printing them. Also make the (un)escaping 100% consistent with how the C tools do it (\ before non-magic character should be unescaped too). - Clean up the escaping/unescaping code and provide two public escape()/unescape() functions. - Export the original MODULES-independent type in orig_type. It's needed for printing symbols in the reparsable __str__() Kconfig format with just public APIs. - Lots of other minor reorganizing and nits all over. --- examples/allnoconfig.py | 8 +- examples/allnoconfig_simpler.py | 8 +- examples/allyesconfig.py | 22 +- examples/defconfig.py | 2 +- examples/defconfig_oldconfig.py | 10 +- examples/eval_expr.py | 4 +- examples/help_grep.py | 4 +- examples/print_sym_info.py | 63 +- examples/print_tree.py | 4 +- kconfiglib.py | 1566 +++++++++++++++++++++++---------------- testsuite.py | 350 +++++---- 11 files changed, 1188 insertions(+), 853 deletions(-) (limited to 'testsuite.py') diff --git a/examples/allnoconfig.py b/examples/allnoconfig.py index 2348da4..be36b47 100644 --- a/examples/allnoconfig.py +++ b/examples/allnoconfig.py @@ -8,7 +8,7 @@ # # $ make [ARCH=] scriptconfig SCRIPT=Kconfiglib/examples/allnoconfig.py -from kconfiglib import Config, Symbol, STR_TO_TRI +from kconfiglib import Kconfig, Symbol, STR_TO_TRI import sys def do_allnoconfig(node): @@ -27,7 +27,7 @@ def do_allnoconfig(node): if (sym.choice is None and not sym.is_allnoconfig_y and sym.assignable and - STR_TO_TRI[sym.assignable[0]] < sym.tri_value): + sym.assignable[0] < sym.tri_value): # Yup, lower it sym.set_value(sym.assignable[0]) @@ -39,12 +39,12 @@ def do_allnoconfig(node): node = node.next -conf = Config(sys.argv[1]) +conf = Kconfig(sys.argv[1]) # Do an initial pass to set 'option allnoconfig_y' symbols to 'y' for sym in conf.defined_syms: if sym.is_allnoconfig_y: - sym.set_value("y") + sym.set_value(2) while 1: # Changing later symbols in the configuration can sometimes allow earlier diff --git a/examples/allnoconfig_simpler.py b/examples/allnoconfig_simpler.py index e732669..59a1bd4 100644 --- a/examples/allnoconfig_simpler.py +++ b/examples/allnoconfig_simpler.py @@ -12,17 +12,17 @@ # Kconfiglib immediately invalidates (flags for recalculation) all (possibly) # dependent symbols when a value is assigned to a symbol, which slows this down # a bit (due to tons of redundant invalidation), but makes any assignment -# pattern safe ("just works"). Config.load_config() instead invalidates all +# pattern safe ("just works"). Kconfig.load_config() instead invalidates all # symbols up front, making it much faster. If you really need to eke out # performance, look at how load_config() does things (which involves internal # APIs that don't invalidate symbols). This has been fast enough for all cases # I've seen so far though (around 3 seconds for this particular script on my # Core i7 2600K, including the initial Kconfig parsing). -from kconfiglib import Config, BOOL, TRISTATE +from kconfiglib import Kconfig, BOOL, TRISTATE import sys -conf = Config(sys.argv[1]) +conf = Kconfig(sys.argv[1]) # Avoid warnings printed by Kconfiglib when assigning a value to a symbol that # has no prompt. Such assignments never have an effect. @@ -30,6 +30,6 @@ conf.disable_warnings() for sym in conf.defined_syms: if sym.type in (BOOL, TRISTATE): - sym.set_value("y" if sym.is_allnoconfig_y else "n") + sym.set_value(2 if sym.is_allnoconfig_y else 0) conf.write_config(".config") diff --git a/examples/allyesconfig.py b/examples/allyesconfig.py index f91b6d7..6e2e065 100644 --- a/examples/allyesconfig.py +++ b/examples/allyesconfig.py @@ -8,20 +8,20 @@ # allyesconfig is a bit more involved than allnoconfig as we need to handle # choices in two different modes: # -# y: One symbol is "y", the rest are "n" -# m: Any number of symbols are "m", the rest are "n" +# y: One symbol is y, the rest are n +# m: Any number of symbols are m, the rest are n # -# Only tristate choices can be in "m" mode. No "m" mode choices seem to appear -# for allyesconfig on the kernel Kconfigs as of 4.14, but we still handle it. +# Only tristate choices can be in m mode. No m mode choices seem to appear for +# allyesconfig on the kernel Kconfigs as of 4.14, but we still handle it. # # Usage: # # $ make [ARCH=] scriptconfig SCRIPT=Kconfiglib/examples/allyesconfig.py -from kconfiglib import Config, Choice, STR_TO_TRI +from kconfiglib import Kconfig, Choice, STR_TO_TRI import sys -conf = Config(sys.argv[1]) +conf = Kconfig(sys.argv[1]) # Collect all the choices in the configuration. Demonstrates how the menu node # tree can be walked iteratively by using the parent pointers. @@ -70,14 +70,14 @@ while 1: for sym in non_choice_syms: # See allnoconfig example. [-1] gives the last (highest) assignable # value. - if sym.assignable and sym.tri_value < STR_TO_TRI[sym.assignable[-1]]: + if sym.assignable and sym.tri_value < sym.assignable[-1]: sym.set_value(sym.assignable[-1]) no_changes = False # Handle choices for choice in choices: - # Handle a choice whose visibility allows it to be in "y" mode + # Handle a choice whose visibility allows it to be in y mode if choice.visibility == 2: selection = choice.default_selection @@ -88,7 +88,7 @@ while 1: selection is not choice.user_selection: # Yup, select it - selection.set_value("y") + selection.set_value(2) no_changes = False # Handle a choice whose visibility only allows it to be in "m" mode. @@ -100,10 +100,10 @@ while 1: # Does the choice have a symbol that can be "m" that we haven't # already set to "m"? - if sym.user_tri_value != 1 and "m" in sym.assignable: + if sym.user_tri_value != 1 and 1 in sym.assignable: # Yup, set it - sym.set_value("m") + sym.set_value(1) no_changes = False if no_changes: diff --git a/examples/defconfig.py b/examples/defconfig.py index ce2bf6e..236db8d 100644 --- a/examples/defconfig.py +++ b/examples/defconfig.py @@ -8,7 +8,7 @@ import kconfiglib import os import sys -conf = kconfiglib.Config(sys.argv[1]) +conf = kconfiglib.Kconfig(sys.argv[1]) if os.path.exists(".config"): print("using existing .config") diff --git a/examples/defconfig_oldconfig.py b/examples/defconfig_oldconfig.py index 98173e7..8e72c8a 100644 --- a/examples/defconfig_oldconfig.py +++ b/examples/defconfig_oldconfig.py @@ -15,7 +15,7 @@ import kconfiglib import sys -conf = kconfiglib.Config(sys.argv[1]) +conf = kconfiglib.Kconfig(sys.argv[1]) # Mirrors defconfig conf.load_config("arch/x86/configs/x86_64_defconfig") @@ -23,15 +23,15 @@ conf.write_config(".config") # Mirrors the first oldconfig conf.load_config(".config") -conf.syms["ETHERNET"].set_value('n') +conf.syms["ETHERNET"].set_value(0) conf.write_config(".config") # Mirrors the second oldconfig conf.load_config(".config") -conf.syms["ETHERNET"].set_value('y') +conf.syms["ETHERNET"].set_value(2) for s in conf: - if s.user_value is None and 'n' in s.assignable: - s.set_value('n') + if s.user_value is None and 0 in s.assignable: + s.set_value(0) # Write the final configuration conf.write_config(".config") diff --git a/examples/eval_expr.py b/examples/eval_expr.py index a907f35..be02c77 100644 --- a/examples/eval_expr.py +++ b/examples/eval_expr.py @@ -13,10 +13,10 @@ if len(sys.argv) < 3: print('Pass symbol name (without "CONFIG_" prefix) with SCRIPT_ARG=NAME') sys.exit(1) -conf = kconfiglib.Config(sys.argv[1]) +conf = kconfiglib.Kconfig(sys.argv[1]) # Enable modules so that 'm' doesn't get demoted to 'n' -conf.syms["MODULES"].set_value("y") +conf.syms["MODULES"].set_value(2) print("the expression '{}' evaluates to {}" .format(sys.argv[2], conf.eval_string(sys.argv[2]))) diff --git a/examples/help_grep.py b/examples/help_grep.py index 6fcb08f..6f54583 100644 --- a/examples/help_grep.py +++ b/examples/help_grep.py @@ -33,7 +33,7 @@ # ... -from kconfiglib import Config, Symbol, Choice, MENU, COMMENT +from kconfiglib import Kconfig, Symbol, Choice, MENU, COMMENT import re import sys @@ -68,5 +68,5 @@ def search_tree(node): node = node.next -conf = Config(sys.argv[1]) +conf = Kconfig(sys.argv[1]) search_tree(conf.top_node) diff --git a/examples/print_sym_info.py b/examples/print_sym_info.py index 2c1b0f0..cc9f50a 100644 --- a/examples/print_sym_info.py +++ b/examples/print_sym_info.py @@ -5,48 +5,49 @@ # $ make [ARCH=] scriptconfig SCRIPT=Kconfiglib/examples/print_sym_info.py SCRIPT_ARG= # # Example output for SCRIPT_ARG=modules: -# -# config MODULES -# bool -# prompt "Enable loadable module support" -# option modules -# help -# Kernel modules are small pieces of compiled code which can -# be inserted in the running kernel, rather than being -# permanently built into the kernel. You use the "modprobe" -# tool to add (and sometimes remove) them. If you say Y here, -# many parts of the kernel can be built as modules (by -# answering M instead of Y where indicated): this is most -# useful for infrequently used options which are not required -# for booting. For more information, see the man pages for -# modprobe, lsmod, modinfo, insmod and rmmod. -# -# If you say Y here, you will need to run "make -# modules_install" to put the modules under /lib/modules/ -# where modprobe can find them (you may need to be root to do -# this). -# -# If unsure, say Y. # -# value = n -# visibility = y -# currently assignable values: n, y -# defined at init/Kconfig:1678 +# menuconfig MODULES +# bool +# prompt "Enable loadable module support" +# option modules +# help +# Kernel modules are small pieces of compiled code which can +# be inserted in the running kernel, rather than being +# permanently built into the kernel. You use the "modprobe" +# tool to add (and sometimes remove) them. If you say Y here, +# many parts of the kernel can be built as modules (by +# answering M instead of Y where indicated): this is most +# useful for infrequently used options which are not required +# for booting. For more information, see the man pages for +# modprobe, lsmod, modinfo, insmod and rmmod. +# +# If you say Y here, you will need to run "make +# modules_install" to put the modules under /lib/modules/ +# where modprobe can find them (you may need to be root to do +# this). +# +# If unsure, say Y. +# +# value = n +# visibility = y +# currently assignable values: n, y +# defined at init/Kconfig:1674 -import kconfiglib +from kconfiglib import Kconfig, TRI_TO_STR import sys if len(sys.argv) < 3: print('Pass symbol name (without "CONFIG_" prefix) with SCRIPT_ARG=') sys.exit(1) -conf = kconfiglib.Config(sys.argv[1]) +conf = Kconfig(sys.argv[1]) sym = conf.syms[sys.argv[2]] print(sym) -print("value = " + sym.value) -print("visibility = " + sym.visibility) -print("currently assignable values: " + ", ".join(sym.assignable)) +print("value = " + sym.str_value) +print("visibility = " + TRI_TO_STR[sym.visibility]) +print("currently assignable values: " + + ", ".join([TRI_TO_STR[v] for v in sym.assignable])) for node in sym.nodes: print("defined at {}:{}".format(node.filename, node.linenr)) diff --git a/examples/print_tree.py b/examples/print_tree.py index 8d18ce9..9d9eac2 100644 --- a/examples/print_tree.py +++ b/examples/print_tree.py @@ -38,7 +38,7 @@ # config GENERIC_IRQ_PROBE # ... -from kconfiglib import Config, Symbol, Choice, MENU, COMMENT +from kconfiglib import Kconfig, Symbol, Choice, MENU, COMMENT import sys def indent_print(s, indent): @@ -63,5 +63,5 @@ def print_items(node, indent): node = node.next -conf = Config(sys.argv[1]) +conf = Kconfig(sys.argv[1]) print_items(conf.top_node, 0) diff --git a/kconfiglib.py b/kconfiglib.py index 1bf61a6..96a421c 100644 --- a/kconfiglib.py +++ b/kconfiglib.py @@ -9,127 +9,290 @@ from Kconfig-based configuration systems. Features include the following: - Reading/writing of .config files - - Inspection of symbol properties: print()ing a symbol (which calls __str__()) - produces output which could be fed back into a Kconfig parser to redefine - the symbol, and __str__() is implemented with only public APIs. + - Inspection of symbol properties and expressions: printing a symbol (calling + Symbol.__str__()) gives output which could be fed back into a Kconfig parser + to redefine the symbol, and __str__() is implemented with only public APIs. - A helpful __repr__() is implemented on all objects as well. + A helpful __repr__() is implemented on all objects as well, also implemented + with public APIs. - - Expression inspection and evaluation: All expressions are exposed and use a - simple tuple-based format that can be processed manually if needed. + - Expressions use a simple tuple-based format and can be processed manually if + needed. - Menu tree inspection: The underlying menu tree is exposed, including submenus created implicitly from symbols depending on preceding symbols. This can be used e.g. to implement menuconfig-like functionality. - Runs under both Python 2 and 3. The code mostly uses basic Python features - (the most advanced things used are probably @property and __slots__). + and has no third-party dependencies (the most advanced things used are + @property and __slots__). - Robust and highly compatible with the standard Kconfig C tools: The test - suite automatically compares the output from Kconfiglib with the output from - the C tools on the real kernel Kconfig and defconfig files for all ARCHes. - The comparison is done by diffing the generated .config files to make sure - they're identical. All tests are expected to pass. + suite automatically compares output from Kconfiglib and the C tools (by + diffing generated .config files) on the real kernel Kconfig and defconfig + files, for all ARCHes. All tests are expected to pass. - A suite of self tests is also included. + A set of selftests is also included. - - Internals that (mostly) mirror the C implementation. A lot can indirectly be - learned about how it works by reading the Kconfiglib documentation and code. + - Not horribly slow despite being a pure Python implementation: Parses the x86 + Kconfigs in about a second on a Core i7 2600K (with a warm file cache). For + long-running jobs, PyPy gives a big performance boost. - - Pretty speedy by pure Python standards: Parses the x86 Kconfigs in about a - second on a Core i7 2600K (with a warm file cache). For long-running jobs, - PyPy gives a large performance boost. + - Internals that (mostly) mirror the C implementation while being simpler to + understand. -Using Kconfiglib on the Linux kernel -==================================== +Using Kconfiglib on the Linux kernel with the Makefile targets +============================================================== For the Linux kernel, a handy interface is provided by the -scripts/kconfig/Makefile patch. +scripts/kconfig/Makefile patch, which adds the following targets: -Use the 'iscriptconfig' target for experimentation. It gives an interactive -Python prompt where the configuration for ARCH has been preloaded. - $ make [ARCH=] [PYTHONCMD=] iscriptconfig +make iscriptconfig +------------------ -To run a script, use the 'scriptconfig' target. +This target gives an interactive Python prompt where the configuration for ARCH +has been preloaded and is available in 'kconf'. - $ make [ARCH=] [PYTHONCMD=] scriptconfig SCRIPT= [SCRIPT_ARG=] +To get a feel for the API, try evaluating and printing the symbols in +kconf.defined_syms, and explore the menu tree starting at kconf.top_node by +following 'next' and 'list' pointers. -PYTHONCMD is the Python interpreter to use. It defaults to "python". +The item contained in the menu node is found in MenuNode.item, and all symbols +and choices have a 'nodes' attribute which gives their menu nodes (usually only +one). + +If you want to look up a symbol by name, use the kconf.syms dictionary. + + +As usual, ARCH= can be passed to 'make' to select the arch. +PYTHONCMD= selects the Python executable to use (default: +"python"). Tip: IronPython (PYTHONCMD=ipython) autocompletion is handy when figuring out -the API. +the API, as it provides autocompletion for attributes. + +make scriptconfig SCRIPT=