# Copyright (c) 2011-2019, Ulf Magnusson
# SPDX-License-Identifier: ISC

# This is the Kconfiglib test suite. It runs selftests on Kconfigs provided by
# us and tests compatibility with the C Kconfig implementation by comparing the
# output of Kconfiglib with the output of the scripts/kconfig/*conf utilities
# for different targets and defconfigs. It should be run from the top-level
# kernel directory with
#
#   $ python Kconfiglib/testsuite.py
#
# Some additional options can be turned on by passing them as arguments. They
# default to off.
#
#  - obsessive:
#    By default, only valid arch/defconfig pairs are tested. In obsessive mode,
#    every arch will be tested with every defconfig. Increases the testing time
#    by an order of magnitude. Occasionally finds (usually obscure) bugs, and I
#    make sure everything passes with it.
#
#  - obsessive-min-config:
#    Like obsessive, for the minimal configuation (defconfig) tests.
#
#  - log:
#    Log timestamped defconfig test failures to the file test_defconfig_fails.
#    Handy in obsessive mode.
#
# For example, this commands runs the test suite in obsessive mode with logging
# enabled:
#
#   $ python(3) Kconfiglib/testsuite.py obsessive log
#
# pypy works too, and runs most tests much faster than CPython.
#
# All tests should pass. Report regressions to ulfalizer a.t Google's email
# service.

import difflib
import errno
import os
import re
import shutil
import subprocess
import sys
import tempfile
import textwrap

from kconfiglib import Kconfig, Symbol, Choice, COMMENT, MENU, MenuNode, \
                       BOOL, TRISTATE, HEX, \
                       TRI_TO_STR, \
                       escape, unescape, \
                       expr_str, expr_items, split_expr, \
                       _ordered_unique, \
                       OR, AND, \
                       KconfigError


def shell(cmd):
    with open(os.devnull, "w") as devnull:
        subprocess.call(cmd, shell=True, stdout=devnull, stderr=devnull)


all_passed = True


def fail(msg=None):
    global all_passed
    all_passed = False
    if msg is not None:
        print("fail: " + msg)


def verify(cond, msg):
    if not cond:
        fail(msg)


def verify_equal(x, y):
    if x != y:
        fail("'{}' does not equal '{}'".format(x, y))


# Prevent accidental loading of configuration files by removing
# KCONFIG_ALLCONFIG from the environment
os.environ.pop("KCONFIG_ALLCONFIG", None)

obsessive = False
obsessive_min_config = False
log = False


def run_tests():
    global obsessive, log
    for s in sys.argv[1:]:
        if s == "obsessive":
            obsessive = True
            print("Obsessive mode enabled")
        elif s == "obsessive-min-config":
            obsessive_min_config = True
            print("Obsessive minimal config mode enabled")
        elif s == "log":
            log = True
            print("Log mode enabled")
        else:
            print("Unrecognized option '{}'".format(s))
            return

    run_selftests()
    run_compatibility_tests()


def run_selftests():
    #
    # Common helper functions. These all expect 'c' to hold the current
    # configuration.
    #

    def verify_value(sym_name, val):
        # Verifies that a symbol has a particular value.

        if isinstance(val, int):
            val = TRI_TO_STR[val]

        sym = c.syms[sym_name]
        verify(sym.str_value == val,
               'expected {} to have the value "{}", had the value "{}"'
               .format(sym_name, val, sym.str_value))

    def assign_and_verify_value(sym_name, val, new_val):
        # Assigns 'val' to a symbol and verifies that its value becomes
        # 'new_val'. Assumes (and tests) that 'val' is valid for the
        # symbol type.

        if isinstance(new_val, int):
            new_val = TRI_TO_STR[new_val]

        sym = c.syms[sym_name]
        old_val = sym.str_value
        verify(sym.set_value(val),
               "assigning '{}' to {} unexpectedly failed"
               .format(val, sym_name))
        verify(sym.str_value == new_val,
               "expected {} to have the value '{}' after being assigned the "
               "value '{}'. Instead, the value is '{}'. The old value was "
               "'{}'."
               .format(sym_name, new_val, val, sym.str_value, old_val))

    def assign_and_verify(sym_name, user_val):
        # Like assign_and_verify_value(), with the expected value being the
        # value just set.

        assign_and_verify_value(sym_name, user_val, user_val)

    def assign_and_verify_user_value(sym_name, val, user_val, valid):
        # Assigns a user value to the symbol and verifies the new user value.
        # If valid is True, the user value is valid for the type, otherwise
        # not. This is used to test the set_value() return value.

        sym = c.syms[sym_name]
        sym_old_user_val = sym.user_value

        verify(sym.set_value(val) == valid,
               "expected the user value '{}' to be {} for {}, was not"
               .format(val, "valid" if valid else "invalid", sym_name))
        verify(sym.user_value == user_val,
               "the assigned user value '{}' wasn't reflected in user_value "
               "on the symbol {}. Instead, the new user_value was '{}'. The "
               "old user value was '{}'."
               .format(user_val, sym_name, sym.user_value, sym_old_user_val))

    #
    # Selftests
    #

    print("Testing string literal lexing")

    # Dummy empty configuration just to get a Kconfig object
    c = Kconfig("Kconfiglib/tests/empty")

    def verify_string_lex(s, expected):
        # Verifies that a constant symbol with the name 'res' is produced from
        # lexing 's'

        res = c._tokenize("if " + s)[1].name
        verify(res == expected,
               "expected <{}> to produced the constant symbol <{}>, "
               'produced <{}>'.format(s[1:-1], expected, res))

    verify_string_lex(r""" "" """, "")
    verify_string_lex(r""" '' """, "")

    verify_string_lex(r""" "a" """, "a")
    verify_string_lex(r""" 'a' """, "a")
    verify_string_lex(r""" "ab" """, "ab")
    verify_string_lex(r""" 'ab' """, "ab")
    verify_string_lex(r""" "abc" """, "abc")
    verify_string_lex(r""" 'abc' """, "abc")

    verify_string_lex(r""" "'" """, "'")
    verify_string_lex(r""" '"' """, '"')

    verify_string_lex(r""" "\"" """, '"')
    verify_string_lex(r""" '\'' """, "'")

    verify_string_lex(r""" "\"\"" """, '""')
    verify_string_lex(r""" '\'\'' """, "''")

    verify_string_lex(r""" "\'" """, "'")
    verify_string_lex(r""" '\"' """, '"')

    verify_string_lex(r""" "\\" """, "\\")
    verify_string_lex(r""" '\\' """, "\\")

    verify_string_lex(r""" "\a\\'\b\c\"'d" """, 'a\\\'bc"\'d')
    verify_string_lex(r""" '\a\\"\b\c\'"d' """, "a\\\"bc'\"d")

    def verify_string_bad(s):
        # Verifies that tokenizing 's' throws a KconfigError. Strips the first
        # and last characters from 's' so we can use readable raw strings as
        # input.

        try:
            c.eval_string(s)
        except KconfigError:
            pass
        else:
            fail("expected tokenization of {} to fail, didn't".format(s[1:-1]))

    verify_string_bad(r""" " """)
    verify_string_bad(r""" ' """)
    verify_string_bad(r""" "' """)
    verify_string_bad(r""" '" """)
    verify_string_bad(r""" "\" """)
    verify_string_bad(r""" '\' """)
    verify_string_bad(r""" "foo """)
    verify_string_bad(r""" 'foo """)


    print("Testing escape() and unescape()")

    def verify_escape_unescape(s, sesc):
        # Verify that 's' escapes to 'sesc' and that 'sesc' unescapes to 's'
        verify_equal(escape(s), sesc)
        verify_equal(unescape(sesc), s)

    verify_escape_unescape(r''          , r''              )
    verify_escape_unescape(r'foo'       , r'foo'           )
    verify_escape_unescape(r'"'         , r'\"'            )
    verify_escape_unescape(r'""'        , r'\"\"'          )
    verify_escape_unescape('\\'         , r'\\'            )
    verify_escape_unescape(r'\\'        , r'\\\\'          )
    verify_escape_unescape(r'\"'        , r'\\\"'          )
    verify_escape_unescape(r'"ab\cd"ef"', r'\"ab\\cd\"ef\"')

    # Backslashes before any character should be unescaped, not just before "
    # and \
    verify_equal(unescape(r"\afoo\b\c\\d\\\e\\\\f"), r"afoobc\d\e\\f")


    print("Testing _ordered_unique()")

    verify_equal(_ordered_unique([]), [])
    verify_equal(_ordered_unique([1]), [1])
    verify_equal(_ordered_unique([1, 2]), [1, 2])
    verify_equal(_ordered_unique([1, 1]), [1])
    verify_equal(_ordered_unique([1, 1, 2]), [1, 2])
    verify_equal(_ordered_unique([1, 2, 1]), [1, 2])
    verify_equal(_ordered_unique([1, 2, 2]), [1, 2])
    verify_equal(_ordered_unique([1, 2, 3, 2, 1, 2, 3, 4, 3, 2, 1, 0]),
                                 [1, 2, 3, 4, 0])


    print("Testing expression evaluation")

    c = Kconfig("Kconfiglib/tests/Keval", warn=False)

    def verify_eval(expr, val):
        res = c.eval_string(expr)
        verify(res == val,
               "'{}' evaluated to {}, expected {}".format(expr, res, val))

    # No modules
    verify_eval("n", 0)
    verify_eval("m", 0)
    verify_eval("y", 2)
    verify_eval("'n'", 0)
    verify_eval("'m'", 0)
    verify_eval("'y'", 2)
    verify_eval("M", 2)

    # Modules
    c.modules.set_value(2)
    verify_eval("n", 0)
    verify_eval("m", 1)
    verify_eval("y", 2)
    verify_eval("'n'", 0)
    verify_eval("'m'", 1)
    verify_eval("'y'", 2)
    verify_eval("M", 1)
    verify_eval("(Y || N) && (m && y)", 1)

    # Non-bool/non-tristate symbols are always n in a tristate sense
    verify_eval("Y_STRING", 0)
    verify_eval("Y_STRING || m", 1)

    # As are all constants besides y and m
    verify_eval('"foo"', 0)
    verify_eval('"foo" || "bar"', 0)
    verify_eval('"foo" || m', 1)

    # Test equality for symbols

    verify_eval("N = N", 2)
    verify_eval("N = n", 2)
    verify_eval("N = 'n'", 2)
    verify_eval("N != N", 0)
    verify_eval("N != n", 0)
    verify_eval("N != 'n'", 0)

    verify_eval("M = M", 2)
    verify_eval("M = m", 2)
    verify_eval("M = 'm'", 2)
    verify_eval("M != M", 0)
    verify_eval("M != m", 0)
    verify_eval("M != 'm'", 0)

    verify_eval("Y = Y", 2)
    verify_eval("Y = y", 2)
    verify_eval("Y = 'y'", 2)
    verify_eval("Y != Y", 0)
    verify_eval("Y != y", 0)
    verify_eval("Y != 'y'", 0)

    verify_eval("N != M", 2)
    verify_eval("N != Y", 2)
    verify_eval("M != Y", 2)

    verify_eval("Y_STRING = y", 2)
    verify_eval("Y_STRING = 'y'", 2)
    verify_eval('FOO_BAR_STRING = "foo bar"', 2)
    verify_eval('FOO_BAR_STRING != "foo bar baz"', 2)
    verify_eval('INT_37 = 37', 2)
    verify_eval("INT_37 = '37'", 2)
    verify_eval('HEX_0X37 = 0x37', 2)
    verify_eval("HEX_0X37 = '0x37'", 2)

    # These should also hold after 31847b67 (kconfig: allow use of relations
    # other than (in)equality)
    verify_eval("HEX_0X37 = '0x037'", 2)
    verify_eval("HEX_0X37 = '0x0037'", 2)

    # Constant symbol comparisons
    verify_eval('"foo" != "bar"', 2)
    verify_eval('"foo" = "bar"', 0)
    verify_eval('"foo" = "foo"', 2)

    # Undefined symbols get their name as their value
    c.warn = False
    verify_eval("'not_defined' = not_defined", 2)
    verify_eval("not_defined_2 = not_defined_2", 2)
    verify_eval("not_defined_1 != not_defined_2", 2)

    # Test less than/greater than

    # Basic evaluation
    verify_eval("INT_37 < 38", 2)
    verify_eval("38 < INT_37", 0)
    verify_eval("INT_37 < '38'", 2)
    verify_eval("'38' < INT_37", 0)
    verify_eval("INT_37 < 138", 2)
    verify_eval("138 < INT_37", 0)
    verify_eval("INT_37 < '138'", 2)
    verify_eval("'138' < INT_37", 0)
    verify_eval("INT_37 < -138", 0)
    verify_eval("-138 < INT_37", 2)
    verify_eval("INT_37 < '-138'", 0)
    verify_eval("'-138' < INT_37", 2)
    verify_eval("INT_37 < 37", 0)
    verify_eval("37 < INT_37", 0)
    verify_eval("INT_37 < 36", 0)
    verify_eval("36 < INT_37", 2)

    # Different formats in comparison
    verify_eval("INT_37 < 0x26", 2) # 38
    verify_eval("INT_37 < 0x25", 0) # 37
    verify_eval("INT_37 < 0x24", 0) # 36
    verify_eval("HEX_0X37 < 56", 2) # 0x38
    verify_eval("HEX_0X37 < 55", 0) # 0x37
    verify_eval("HEX_0X37 < 54", 0) # 0x36

    # Other int comparisons
    verify_eval("INT_37 <= 38", 2)
    verify_eval("INT_37 <= 37", 2)
    verify_eval("INT_37 <= 36", 0)
    verify_eval("INT_37 >  38", 0)
    verify_eval("INT_37 >  37", 0)
    verify_eval("INT_37 >  36", 2)
    verify_eval("INT_37 >= 38", 0)
    verify_eval("INT_37 >= 37", 2)
    verify_eval("INT_37 >= 36", 2)

    # Other hex comparisons
    verify_eval("HEX_0X37 <= 0x38", 2)
    verify_eval("HEX_0X37 <= 0x37", 2)
    verify_eval("HEX_0X37 <= 0x36", 0)
    verify_eval("HEX_0X37 >  0x38", 0)
    verify_eval("HEX_0X37 >  0x37", 0)
    verify_eval("HEX_0X37 >  0x36", 2)
    verify_eval("HEX_0X37 >= 0x38", 0)
    verify_eval("HEX_0X37 >= 0x37", 2)
    verify_eval("HEX_0X37 >= 0x36", 2)

    # A hex holding a value without a "0x" prefix should still be treated as
    # hexadecimal
    verify_eval("HEX_37 < 0x38", 2)
    verify_eval("HEX_37 < 0x37", 0)
    verify_eval("HEX_37 < 0x36", 0)

    # Symbol comparisons
    verify_eval("INT_37   <  HEX_0X37", 2)
    verify_eval("INT_37   >  HEX_0X37", 0)
    verify_eval("HEX_0X37 <  INT_37  ", 0)
    verify_eval("HEX_0X37 >  INT_37  ", 2)
    verify_eval("INT_37   <  INT_37  ", 0)
    verify_eval("INT_37   <= INT_37  ", 2)
    verify_eval("INT_37   >  INT_37  ", 0)
    verify_eval("INT_37   <= INT_37  ", 2)

    # Tristate value comparisons
    verify_eval("n < n", 0)
    verify_eval("n < m", 2)
    verify_eval("n < y", 2)
    verify_eval("n < N", 0)
    verify_eval("n < M", 2)
    verify_eval("n < Y", 2)
    verify_eval("0 > n", 0)
    verify_eval("1 > n", 2)
    verify_eval("2 > n", 2)
    verify_eval("m < n", 0)
    verify_eval("m < m", 0)
    verify_eval("m < y", 2)

    # Strings compare lexicographically
    verify_eval("'aa' < 'ab'", 2)
    verify_eval("'aa' > 'ab'", 0)
    verify_eval("'ab' < 'aa'", 0)
    verify_eval("'ab' > 'aa'", 2)

    # Comparisons where one of the operands doesn't parse as a number also give
    # a lexicographic comparison
    verify_eval("INT_37 <  '37a' ", 2)
    verify_eval("'37a'  >  INT_37", 2)
    verify_eval("INT_37 <= '37a' ", 2)
    verify_eval("'37a'  >= INT_37", 2)
    verify_eval("INT_37 >= '37a' ", 0)
    verify_eval("INT_37 >  '37a' ", 0)
    verify_eval("'37a'  <  INT_37", 0)
    verify_eval("'37a'  <= INT_37", 0)

    def verify_eval_bad(expr):
        try:
            c.eval_string(expr)
        except KconfigError:
            pass
        else:
            fail('expected eval_string("{}") to throw KconfigError, '
                 "didn't".format(expr))

    # Verify that some bad stuff throws KconfigError's
    verify_eval_bad("")
    verify_eval_bad("&")
    verify_eval_bad("|")
    verify_eval_bad("!")
    verify_eval_bad("(")
    verify_eval_bad(")")
    verify_eval_bad("=")
    verify_eval_bad("(X")
    verify_eval_bad("X)")
    verify_eval_bad("X X")
    verify_eval_bad("!X X")
    verify_eval_bad("X !X")
    verify_eval_bad("(X) X")
    verify_eval_bad("X &&")
    verify_eval_bad("&& X")
    verify_eval_bad("X && && X")
    verify_eval_bad("X && !&&")
    verify_eval_bad("X ||")
    verify_eval_bad("|| X")


    print("Testing Symbol.__str__()/custom_str() and def_{int,hex,string}")

    def verify_str(item, s):
        verify_equal(str(item), s[1:-1])

    def verify_custom_str(item, s):
        verify_equal(item.custom_str(lambda sc: "[{}]".format(sc.name)),
                     s[1:-1])

    c = Kconfig("Kconfiglib/tests/Kstr", warn=False)

    c.modules.set_value(2)

    verify_str(c.syms["UNDEFINED"], """
""")

    verify_str(c.syms["BASIC_NO_PROMPT"], """
config BASIC_NO_PROMPT
	bool
	help
	  blah blah
	  
	    blah blah blah
	  
	   blah
""")

    verify_str(c.syms["BASIC_PROMPT"], """
config BASIC_PROMPT
	bool "basic"
""")

    verify_str(c.syms["ADVANCED"], """
config ADVANCED
	tristate "prompt" if DEP
	default DEFAULT_1
	default DEFAULT_2 if DEP
	select SELECTED_1
	select SELECTED_2 if DEP
	imply IMPLIED_1
	imply IMPLIED_2 if DEP
	help
	  first help text

config ADVANCED
	tristate "prompt 2"

menuconfig ADVANCED
	tristate "prompt 3"

config ADVANCED
	tristate
	depends on (A || !B || (C && D) || !(E && F) || G = H || (I && !J && (K || L) && !(M || N) && O = P)) && DEP4 && DEP3
	help
	  second help text

config ADVANCED
	tristate "prompt 4" if VIS
	depends on DEP4 && DEP3
""")

    verify_custom_str(c.syms["ADVANCED"], """
config ADVANCED
	tristate "prompt" if [DEP]
	default [DEFAULT_1]
	default [DEFAULT_2] if [DEP]
	select [SELECTED_1]
	select [SELECTED_2] if [DEP]
	imply [IMPLIED_1]
	imply [IMPLIED_2] if [DEP]
	help
	  first help text

config ADVANCED
	tristate "prompt 2"

menuconfig ADVANCED
	tristate "prompt 3"

config ADVANCED
	tristate
	depends on ([A] || ![B] || ([C] && [D]) || !([E] && [F]) || [G] = [H] || ([I] && ![J] && ([K] || [L]) && !([M] || [N]) && [O] = [P])) && [DEP4] && [DEP3]
	help
	  second help text

config ADVANCED
	tristate "prompt 4" if [VIS]
	depends on [DEP4] && [DEP3]
""")


    verify_str(c.syms["ONLY_DIRECT_DEPS"], """
config ONLY_DIRECT_DEPS
	int
	depends on DEP1 && DEP2
""")

    verify_str(c.syms["STRING"], """
config STRING
	string
	default "foo"
	default "bar" if DEP
	default STRING2
	default STRING3 if DEP
""")

    verify_str(c.syms["INT"], """
config INT
	int
	range 1 2
	range FOO BAR
	range BAZ QAZ if DEP
	default 7 if DEP
""")

    verify_str(c.syms["HEX"], """
config HEX
	hex
	range 0x100 0x200
	range FOO BAR
	range BAZ QAZ if DEP
	default 0x123
""")

    verify_str(c.modules, """
config MODULES
	bool "MODULES"
	option modules
""")

    verify_str(c.syms["OPTIONS"], """
config OPTIONS
	option allnoconfig_y
	option defconfig_list
	option env="ENV"
""")

    verify_str(c.syms["CORRECT_PROP_LOCS_BOOL"], """
config CORRECT_PROP_LOCS_BOOL
	bool "prompt 1"
	default DEFAULT_1
	default DEFAULT_2
	select SELECT_1
	select SELECT_2
	imply IMPLY_1
	imply IMPLY_2
	depends on LOC_1
	help
	  help 1

menuconfig CORRECT_PROP_LOCS_BOOL
	bool "prompt 2"
	default DEFAULT_3
	default DEFAULT_4
	select SELECT_3
	select SELECT_4
	imply IMPLY_3
	imply IMPLY_4
	depends on LOC_2
	help
	  help 2

config CORRECT_PROP_LOCS_BOOL
	bool "prompt 3"
	default DEFAULT_5
	default DEFAULT_6
	select SELECT_5
	select SELECT_6
	imply IMPLY_5
	imply IMPLY_6
	depends on LOC_3
	help
	  help 2
""")

    verify_str(c.syms["CORRECT_PROP_LOCS_INT"], """
config CORRECT_PROP_LOCS_INT
	int
	range 1 2
	range 3 4
	depends on LOC_1

config CORRECT_PROP_LOCS_INT
	int
	range 5 6
	range 7 8
	depends on LOC_2
""")

    verify_str(c.syms["PROMPT_ONLY"], """
config PROMPT_ONLY
	prompt "prompt only"
""")

    verify_custom_str(c.syms["CORRECT_PROP_LOCS_INT"], """
config CORRECT_PROP_LOCS_INT
	int
	range [1] [2]
	range [3] [4]
	depends on [LOC_1]

config CORRECT_PROP_LOCS_INT
	int
	range [5] [6]
	range [7] [8]
	depends on [LOC_2]
""")



    print("Testing Choice.__str__()/custom_str()")

    verify_str(c.named_choices["CHOICE"], """
choice CHOICE
	tristate "foo"
	default CHOICE_1
	default CHOICE_2 if dep
""")

    verify_str(c.named_choices["CHOICE"].nodes[0].next.item, """
choice
	tristate "no name"
	optional
""")

    verify_str(c.named_choices["CORRECT_PROP_LOCS_CHOICE"], """
choice CORRECT_PROP_LOCS_CHOICE
	bool
	default CHOICE_3
	depends on LOC_1

choice CORRECT_PROP_LOCS_CHOICE
	bool
	default CHOICE_4
	depends on LOC_2

choice CORRECT_PROP_LOCS_CHOICE
	bool
	default CHOICE_5
	depends on LOC_3
""")

    verify_custom_str(c.named_choices["CORRECT_PROP_LOCS_CHOICE"], """
choice CORRECT_PROP_LOCS_CHOICE
	bool
	default [CHOICE_3]
	depends on [LOC_1]

choice CORRECT_PROP_LOCS_CHOICE
	bool
	default [CHOICE_4]
	depends on [LOC_2]

choice CORRECT_PROP_LOCS_CHOICE
	bool
	default [CHOICE_5]
	depends on [LOC_3]
""")


    print("Testing MenuNode.__str__()/custom_str() for menus and comments")

    verify_str(c.syms["SIMPLE_MENU_HOOK"].nodes[0].next, """
menu "simple menu"
""")

    verify_str(c.syms["ADVANCED_MENU_HOOK"].nodes[0].next, """
menu "advanced menu"
	depends on A
	visible if B && (C || D)
""")

    verify_custom_str(c.syms["ADVANCED_MENU_HOOK"].nodes[0].next, """
menu "advanced menu"
	depends on [A]
	visible if [B] && ([C] || [D])
""")

    verify_str(c.syms["SIMPLE_COMMENT_HOOK"].nodes[0].next, """
comment "simple comment"
""")

    verify_str(c.syms["ADVANCED_COMMENT_HOOK"].nodes[0].next, """
comment "advanced comment"
	depends on A && B
""")

    verify_custom_str(c.syms["ADVANCED_COMMENT_HOOK"].nodes[0].next, """
comment "advanced comment"
	depends on [A] && [B]
""")


    print("Testing {MenuNode,Symbol,Choice}.orig_*")

    # Just test some corner cases here re. MenuNode.orig_*. They are already
    # indirectly tested above. Use MenuNode.__str__() as a proxy.

    verify_str(c.syms["DEP_REM_CORNER_CASES"], """
config DEP_REM_CORNER_CASES
	bool
	default A
	depends on n

config DEP_REM_CORNER_CASES
	bool
	default B if n

config DEP_REM_CORNER_CASES
	bool
	default C
	depends on m && MODULES

config DEP_REM_CORNER_CASES
	bool
	default D if A

config DEP_REM_CORNER_CASES
	bool
	default E if !E1
	default F if F1 = F2
	default G if G1 || H1
	depends on !H

config DEP_REM_CORNER_CASES
	bool
	default H
	depends on "foo" = "bar"

config DEP_REM_CORNER_CASES
	bool "prompt" if FOO || BAR
	depends on BAZ && QAZ
""")

    # Test {Symbol,Choice}.orig_*

    def verify_deps(elms, dep_index, expected):
        verify_equal(" ".join(expr_str(elm[dep_index]) for elm in elms),
                     expected)

    verify_deps(c.syms["BOOL_SYM_ORIG"].orig_defaults,        1, "DEP y y")
    verify_deps(c.syms["BOOL_SYM_ORIG"].orig_selects,         1, "y DEP y")
    verify_deps(c.syms["BOOL_SYM_ORIG"].orig_implies,         1, "y y DEP")
    verify_deps(c.syms["INT_SYM_ORIG"].orig_ranges,           2, "DEP y DEP")
    verify_deps(c.named_choices["CHOICE_ORIG"].orig_defaults, 1, "y DEP DEP")


    print("Testing Symbol.__repr__()")

    def verify_repr(item, s):
        verify_equal(repr(item) + "\n", s[1:])

    c = Kconfig("Kconfiglib/tests/Krepr", warn=False)

    verify_repr(c.n, """
<symbol n, tristate, value n, constant>
""")

    verify_repr(c.m, """
<symbol m, tristate, value m, constant>
""")

    verify_repr(c.y, """
<symbol y, tristate, value y, constant>
""")

    verify_repr(c.syms["UNDEFINED"], """
<symbol UNDEFINED, unknown, value "UNDEFINED", visibility n, direct deps n, undefined>
""")

    verify_repr(c.syms["BASIC"], """
<symbol BASIC, bool, value y, visibility n, direct deps y, Kconfiglib/tests/Krepr:9>
""")

    verify_repr(c.syms["VISIBLE"], """
<symbol VISIBLE, bool, "visible", value n, visibility y, direct deps y, Kconfiglib/tests/Krepr:14>
""")

    c.syms["VISIBLE"].set_value(2)
    c.syms["STRING"].set_value("foo")

    verify_repr(c.syms["VISIBLE"], """
<symbol VISIBLE, bool, "visible", value y, user value y, visibility y, direct deps y, Kconfiglib/tests/Krepr:14>
""")

    verify_repr(c.syms["STRING"], """
<symbol STRING, string, "visible", value "foo", user value "foo", visibility y, direct deps y, Kconfiglib/tests/Krepr:17>
""")

    verify_repr(c.syms["DIR_DEP_N"], """
<symbol DIR_DEP_N, unknown, value "DIR_DEP_N", visibility n, direct deps n, Kconfiglib/tests/Krepr:20>
""")

    verify_repr(c.syms["OPTIONS"], """
<symbol OPTIONS, unknown, value "OPTIONS", visibility n, allnoconfig_y, is the defconfig_list symbol, from environment variable ENV, direct deps y, Kconfiglib/tests/Krepr:23>
""")

    verify_repr(c.syms["MULTI_DEF"], """
<symbol MULTI_DEF, unknown, value "MULTI_DEF", visibility n, direct deps y, Kconfiglib/tests/Krepr:28, Kconfiglib/tests/Krepr:29>
""")

    verify_repr(c.syms["CHOICE_1"], """
<symbol CHOICE_1, tristate, "choice sym", value n, visibility m, choice symbol, direct deps m, Kconfiglib/tests/Krepr:36>
""")

    verify_repr(c.modules, """
<symbol MODULES, bool, value y, visibility n, is the modules symbol, direct deps y, Kconfiglib/tests/Krepr:1>
""")


    print("Testing Choice.__repr__()")

    verify_repr(c.named_choices["CHOICE"], """
<choice CHOICE, tristate, "choice", mode m, visibility y, Kconfiglib/tests/Krepr:33>
""")

    c.named_choices["CHOICE"].set_value(2)

    verify_repr(c.named_choices["CHOICE"], """
<choice CHOICE, tristate, "choice", mode y, user mode y, CHOICE_1 selected, visibility y, Kconfiglib/tests/Krepr:33>
""")

    c.syms["CHOICE_2"].set_value(2)

    verify_repr(c.named_choices["CHOICE"], """
<choice CHOICE, tristate, "choice", mode y, user mode y, CHOICE_2 selected, CHOICE_2 selected by user, visibility y, Kconfiglib/tests/Krepr:33>
""")

    c.named_choices["CHOICE"].set_value(1)

    verify_repr(c.named_choices["CHOICE"], """
<choice CHOICE, tristate, "choice", mode m, user mode m, CHOICE_2 selected by user (overridden), visibility y, Kconfiglib/tests/Krepr:33>
""")

    verify_repr(c.syms["CHOICE_HOOK"].nodes[0].next.item, """
<choice, tristate, "optional choice", mode n, visibility n, optional, Kconfiglib/tests/Krepr:46>
""")


    print("Testing MenuNode.__repr__()")

    verify_repr(c.syms["BASIC"].nodes[0], """
<menu node for symbol BASIC, deps y, has help, has next, Kconfiglib/tests/Krepr:9>
""")

    verify_repr(c.syms["DIR_DEP_N"].nodes[0], """
<menu node for symbol DIR_DEP_N, deps n, has next, Kconfiglib/tests/Krepr:20>
""")

    verify_repr(c.syms["MULTI_DEF"].nodes[0], """
<menu node for symbol MULTI_DEF, deps y, has next, Kconfiglib/tests/Krepr:28>
""")

    verify_repr(c.syms["MULTI_DEF"].nodes[1], """
<menu node for symbol MULTI_DEF, deps y, has next, Kconfiglib/tests/Krepr:29>
""")

    verify_repr(c.syms["MENUCONFIG"].nodes[0], """
<menu node for symbol MENUCONFIG, is menuconfig, deps y, has next, Kconfiglib/tests/Krepr:31>
""")

    verify_repr(c.named_choices["CHOICE"].nodes[0], """
<menu node for choice CHOICE, prompt "choice" (visibility y), deps y, has child, has next, Kconfiglib/tests/Krepr:33>
""")

    verify_repr(c.syms["CHOICE_HOOK"].nodes[0].next, """
<menu node for choice, prompt "optional choice" (visibility n), deps y, has next, Kconfiglib/tests/Krepr:46>
""")

    verify_repr(c.syms["NO_VISIBLE_IF_HOOK"].nodes[0].next, """
<menu node for menu, prompt "no visible if" (visibility y), deps y, 'visible if' deps y, has next, Kconfiglib/tests/Krepr:53>
""")

    verify_repr(c.syms["VISIBLE_IF_HOOK"].nodes[0].next, """
<menu node for menu, prompt "visible if" (visibility y), deps y, 'visible if' deps m, has next, Kconfiglib/tests/Krepr:58>
""")

    verify_repr(c.syms["COMMENT_HOOK"].nodes[0].next, """
<menu node for comment, prompt "comment" (visibility y), deps y, Kconfiglib/tests/Krepr:64>
""")


    print("Testing Kconfig.__repr__()")

    verify_repr(c, """
<configuration with 15 symbols, main menu prompt "Main menu", srctree is current directory, config symbol prefix "CONFIG_", warnings disabled, printing of warnings to stderr enabled, undef. symbol assignment warnings disabled, overriding symbol assignment warnings enabled, redundant symbol assignment warnings enabled>
""")

    os.environ["srctree"] = "Kconfiglib"
    os.environ["CONFIG_"] = "CONFIG_ value"

    c = Kconfig("tests/Krepr", warn=False)
    c.warn = True
    c.warn_to_stderr = False
    c.warn_assign_override = False
    c.warn_assign_redun = False
    c.warn_assign_undef = True

    verify_repr(c, """
<configuration with 15 symbols, main menu prompt "Main menu", srctree "Kconfiglib", config symbol prefix "CONFIG_ value", warnings enabled, printing of warnings to stderr disabled, undef. symbol assignment warnings enabled, overriding symbol assignment warnings disabled, redundant symbol assignment warnings disabled>
""")

    os.environ.pop("srctree", None)
    os.environ.pop("CONFIG_", None)


    print("Testing tricky help strings")

    c = Kconfig("Kconfiglib/tests/Khelp")

    def verify_help(node, s):
        verify_equal(node.help, s[1:-1])

    verify_help(c.syms["TWO_HELP_STRINGS"].nodes[0], """
first help string
""")

    verify_help(c.syms["TWO_HELP_STRINGS"].nodes[1], """
second help string
""")

    verify_help(c.syms["NO_BLANK_AFTER_HELP"].nodes[0], """
help for
NO_BLANK_AFTER_HELP
""")

    verify_help(c.named_choices["CHOICE_HELP"].nodes[0], """
help for
CHOICE_HELP
""")

    verify_help(c.syms["HELP_TERMINATED_BY_COMMENT"].nodes[0], """
a
b
c
""")

    verify_help(c.syms["TRICKY_HELP"].nodes[0], """
a
 b
  c

 d
  e
   f


g
 h
  i
""")


    print("Testing locations, source/rsource/gsource/grsource, and "
          "Kconfig.kconfig_filenames")

    def verify_locations(nodes, *expected_locs):
        verify(len(nodes) == len(expected_locs),
               "Wrong number of locations for " + repr(nodes))

        for node, expected_loc in zip(nodes, expected_locs):
            node_loc = "{}:{}".format(node.filename, node.linenr)
            verify(node_loc == expected_loc,
                   "expected {} to have the location {}, had the location {}"
                   .format(repr(node), expected_loc, node_loc))

    # Expanded in the 'source' statement in Klocation

    os.environ["TESTS_DIR_FROM_ENV"] = "tests"
    os.environ["SUB_DIR_FROM_ENV"] = "sub"

    os.environ["_SOURCED"] = "_sourced"
    os.environ["_RSOURCED"] = "_rsourced"
    os.environ["_GSOURCED"] = "_gsourced"
    os.environ["_GRSOURCED"] = "_grsourced"

    # Test twice, with $srctree as a relative and an absolute path,
    # respectively
    for srctree in "Kconfiglib", os.path.abspath("Kconfiglib"):
        os.environ["srctree"] = srctree

        # Has symbol with empty help text, so disable warnings
        c = Kconfig("tests/Klocation", warn=False)

        verify_locations(c.syms["UNDEFINED"].nodes)
        verify_equal(c.syms["UNDEFINED"].name_and_loc, "UNDEFINED (undefined)")

        verify_locations(c.syms["ONE_DEF"].nodes, "tests/Klocation:4")
        verify_equal(c.syms["ONE_DEF"].name_and_loc,
                     "ONE_DEF (defined at tests/Klocation:4)")

        verify_locations(c.syms["TWO_DEF"].nodes,
                         "tests/Klocation:7",
                         "tests/Klocation:10")
        verify_equal(c.syms["TWO_DEF"].name_and_loc,
                     "TWO_DEF (defined at tests/Klocation:7, tests/Klocation:10)")

        verify_locations(c.syms["MANY_DEF"].nodes,
                         "tests/Klocation:13",
                         "tests/Klocation:43",
                         "tests/Klocation:45",
                         "tests/Klocation_sourced:3",
                         "tests/sub/Klocation_rsourced:2",
                         "tests/sub/Klocation_gsourced1:1",
                         "tests/sub/Klocation_gsourced2:1",
                         "tests/sub/Klocation_gsourced1:1",
                         "tests/sub/Klocation_gsourced2:1",
                         "tests/sub/Klocation_grsourced1:1",
                         "tests/sub/Klocation_grsourced2:1",
                         "tests/sub/Klocation_grsourced1:1",
                         "tests/sub/Klocation_grsourced2:1",
                         "tests/Klocation:78")

        verify_locations(c.named_choices["CHOICE_ONE_DEF"].nodes,
                         "tests/Klocation_sourced:5")
        verify_equal(c.named_choices["CHOICE_ONE_DEF"].name_and_loc,
                     "<choice CHOICE_ONE_DEF> (defined at tests/Klocation_sourced:5)")

        verify_locations(c.named_choices["CHOICE_TWO_DEF"].nodes,
                         "tests/Klocation_sourced:9",
                         "tests/Klocation_sourced:13")
        verify_equal(c.named_choices["CHOICE_TWO_DEF"].name_and_loc,
                     "<choice CHOICE_TWO_DEF> (defined at tests/Klocation_sourced:9, tests/Klocation_sourced:13)")

        verify_locations([c.syms["MENU_HOOK"].nodes[0].next],
                         "tests/Klocation_sourced:20")

        verify_locations([c.syms["COMMENT_HOOK"].nodes[0].next],
                         "tests/Klocation_sourced:26")

        # Test Kconfig.kconfig_filenames

        verify_equal(c.kconfig_filenames, [
            "tests/Klocation",
            "tests/Klocation_sourced",
            "tests/sub/Klocation_rsourced",
            "tests/sub/Klocation_gsourced1",
            "tests/sub/Klocation_gsourced2",
            "tests/sub/Klocation_gsourced1",
            "tests/sub/Klocation_gsourced2",
            "tests/sub/Klocation_grsourced1",
            "tests/sub/Klocation_grsourced2",
            "tests/sub/Klocation_grsourced1",
            "tests/sub/Klocation_grsourced2"
        ])

        # Test recursive 'source' detection

        try:
            Kconfig("tests/Krecursive1")
        except KconfigError as e:
            verify_equal(str(e), """
tests/Krecursive2:1: recursive 'source' of 'tests/Krecursive1' detected. Check that environment variables are set correctly.
Include path:
tests/Krecursive1:1
tests/Krecursive2:1
"""[:-1])
        except:
            fail("recursive 'source' raised wrong exception")
        else:
            fail("recursive 'source' did not raise exception")

        # Verify that source and rsource throw exceptions for missing files

        # TODO: Make an exception test helper

        try:
            Kconfig("tests/Kmissingsource")
        except KconfigError as e:
            if "not found" not in str(e):
                fail("'source' with missing file raised wrong KconfigError")
        except:
            fail("'source' with missing file raised wrong exception")
        else:
            fail("'source' with missing file did not raise exception")

        try:
            Kconfig("tests/Kmissingrsource")
        except KconfigError as e:
            if "not found" not in str(e):
                fail("'rsource' with missing file raised wrong KconfigError")
        except:
            fail("'rsource' with missing file raised wrong exception")
        else:
            fail("'rsource' with missing file did not raise exception")

    # Test a tricky case involving symlinks. $srctree is tests/symlink, which
    # points to tests/sub/sub, meaning tests/symlink/.. != tests/. Previously,
    # using 'rsource' from a file sourced with an absolute path triggered an
    # unsafe relpath() with tests/symlink/.. in it, crashing.

    os.environ["srctree"] = "Kconfiglib/tests/symlink"
    os.environ["KCONFIG_SYMLINK_2"] = os.path.abspath(
        "Kconfiglib/tests/sub/Kconfig_symlink_2")
    if not os.path.isabs(
        Kconfig("Kconfig_symlink_1").syms["FOUNDME"].nodes[0].filename):

        fail("Symlink + rsource issues")


    print("Testing Kconfig.node_iter()")

    # Reuse tests/Klocation. The node_iter(unique_syms=True) case already gets
    # plenty of testing from write_config() as well.

    os.environ["srctree"] = "Kconfiglib"
    c = Kconfig("tests/Klocation", warn=False)

    verify_equal(
        [node.item.name for node in c.node_iter()
         if isinstance(node.item, Symbol)],
        ["ONE_DEF", "TWO_DEF", "TWO_DEF", "MANY_DEF", "HELP_1", "HELP_2",
         "HELP_3", "MANY_DEF", "MANY_DEF", "MANY_DEF", "MENU_HOOK",
         "COMMENT_HOOK"] + 10*["MANY_DEF"])

    verify_equal(
        [node.item.name for node in c.node_iter(True)
         if isinstance(node.item, Symbol)],
        ["ONE_DEF", "TWO_DEF", "MANY_DEF", "HELP_1", "HELP_2", "HELP_3",
         "MENU_HOOK", "COMMENT_HOOK"])

    verify_equal(
        [node.prompt[0] for node in c.node_iter()
         if not isinstance(node.item, Symbol)],
        ["one-def choice", "two-def choice 1", "two-def choice 2",
         "menu", "comment"])

    verify_equal(
        [node.prompt[0] for node in c.node_iter(True)
         if not isinstance(node.item, Symbol)],
        ["one-def choice", "two-def choice 1", "two-def choice 2",
         "menu", "comment"])


    print("Testing MenuNode.include_path")

    os.environ["srctree"] = "Kconfiglib/tests"

    c = Kconfig("Kinclude_path")

    def verify_node_path(node, *expected):
        if node.include_path != expected:
            fail("Wrong include path for node {!r}. Got {}, expected {}."
                 .format(node, node.include_path, expected))

    def verify_sym_path(sym_name, node_i, *expected):
        verify_node_path(c.syms[sym_name].nodes[node_i], *expected)

    verify_sym_path("TOP", 0)
    verify_sym_path("TOP", 1)
    verify_sym_path("TOP", 2)

    verify_sym_path("ONE_DOWN", 0, ("Kinclude_path", 4))
    verify_sym_path("ONE_DOWN", 1, ("Kinclude_path", 4))
    verify_sym_path("ONE_DOWN", 2, ("Kinclude_path", 4))
    verify_sym_path("ONE_DOWN", 3, ("Kinclude_path", 9))
    verify_sym_path("ONE_DOWN", 4, ("Kinclude_path", 9))
    verify_sym_path("ONE_DOWN", 5, ("Kinclude_path", 9))

    verify_sym_path("TWO_DOWN", 0,
                    ("Kinclude_path", 4), ("Kinclude_path_sourced_1", 4))
    verify_sym_path("TWO_DOWN", 1,
                    ("Kinclude_path", 4), ("Kinclude_path_sourced_1", 9))
    verify_sym_path("TWO_DOWN", 2,
                    ("Kinclude_path", 9), ("Kinclude_path_sourced_1", 4))
    verify_sym_path("TWO_DOWN", 3,
                    ("Kinclude_path", 9), ("Kinclude_path_sourced_1", 9))

    verify_node_path(c.top_node)
    verify_node_path(c.menus[0], ("Kinclude_path", 4), ("Kinclude_path_sourced_1", 4))
    verify_node_path(c.comments[0], ("Kinclude_path", 4), ("Kinclude_path_sourced_1", 4))
    verify_node_path(c.choices[0].nodes[0], ("Kinclude_path", 4), ("Kinclude_path_sourced_1", 4))

    os.environ.pop("srctree", None)


    print("Testing Kconfig.choices/menus/comments")

    c = Kconfig("Kconfiglib/tests/Kitemlists")

    def verify_prompts(items, *expected_prompts):
        verify(len(items) == len(expected_prompts),
               "Wrong number of prompts for {}".format(items))

        for item, expected_prompt in zip(items, expected_prompts):
            if not isinstance(item, MenuNode):
                item = item.nodes[0]

            verify(item.prompt[0] == expected_prompt,
                   "Wrong prompt for {}, expected '{}'"
                   .format(repr(item), expected_prompt))

    verify_prompts(c.choices, "choice 1", "choice 2", "choice 3", "choice 2")
    verify_prompts(c.menus, "menu 1", "menu 2", "menu 3", "menu 4", "menu 5")
    verify_prompts(c.comments, "comment 1", "comment 2", "comment 3")


    print("Testing Symbol/Choice.direct_dep")

    c = Kconfig("Kconfiglib/tests/Kdirdep")

    verify_equal(expr_str(c.syms["NO_DEP_SYM"].direct_dep), 'y')
    verify_equal(expr_str(c.syms["DEP_SYM"].direct_dep), "A || (B && C) || !D")

    verify_equal(expr_str(c.named_choices["NO_DEP_CHOICE"].direct_dep), 'y')
    verify_equal(expr_str(c.named_choices["DEP_CHOICE"].direct_dep),
                 "A || B || C")


    print("Testing expr_items()")

    c = Kconfig("Kconfiglib/tests/Kexpr_items")

    def verify_expr_items(expr, *sym_names):
        verify_equal(tuple(sorted(item.name for item in expr_items(expr))),
                     sym_names)

    verify_expr_items(
        c.syms["TEST"].defaults[0][0],
        "A", "B", "C", "D", "E", "F", "G", "H"
    )

    verify_expr_items(
        c.syms["TEST_CHOICE"].nodes[0].prompt[1],
        "A", "CHOICE"
    )


    print("Testing MenuNode/Symbol/Choice.referenced")

    c = Kconfig("Kconfiglib/tests/Kreferenced", warn=False)

    def verify_deps(item, *dep_names):
        verify_equal(tuple(sorted(item.name for item in item.referenced)),
                     dep_names)

    verify_deps(c.top_node, "y")

    verify_deps(c.syms["NO_REFS"].nodes[0], "y")

    verify_deps(c.syms["JUST_DEPENDS_ON_REFS"].nodes[0], "A", "B")

    verify_deps(c.syms["LOTS_OF_REFS"].nodes[0],
                *(chr(n) for n in range(ord("A"), ord("Z") + 1)))

    verify_deps(c.syms["INT_REFS"].nodes[0],
                "A", "B", "C", "D", "E", "F", "G", "H", "y")

    verify_deps(c.syms["CHOICE_REF"].nodes[0], "CHOICE")

    verify_deps(c.menus[0], "A", "B", "C", "D")

    verify_deps(c.comments[0], "A", "B")

    verify_deps(c.syms["MULTI_DEF_SYM"], "A", "B", "C", "y")
    verify_deps(c.named_choices["MULTI_DEF_CHOICE"], "A", "B", "C")


    print("Testing split_expr()")

    c = Kconfig("Kconfiglib/tests/empty")
    c.warn = False

    def verify_split(to_split, op, operand_strs):
        # The same hackage as in Kconfig.eval_string()
        c._tokens = c._tokenize("if " + to_split)[1:]
        c._tokens_i = 0

        operands = split_expr(c._parse_expr(False), op)

        verify(len(operands) == len(operand_strs),
               "Wrong number of operands when {} was split by {}"
               .format(to_split, "OR" if op == OR else "AND"))

        for operand, operand_str in zip(operands, operand_strs):
            verify_equal(expr_str(operand), operand_str)

    verify_split("A",                    OR, ("A",                ))
    verify_split("!A",                   OR, ("!A",               ))
    verify_split("A = B",                OR, ("A = B",            ))
    verify_split("A && B",               OR, ("A && B",           ))
    verify_split("A || B",               OR, ("A", "B"            ))
    verify_split("(A || B) || C",        OR, ("A", "B", "C"       ))
    verify_split("A || (B || C)",        OR, ("A", "B", "C"       ))
    verify_split("A || !(B || C)",       OR, ("A", "!(B || C)"    ))
    verify_split("A || (B && (C || D))", OR, ("A", "B && (C || D)"))
    verify_split("(A && (B || C)) || D", OR, ("A && (B || C)", "D"))

    verify_split("A",                    AND, ("A",                ))
    verify_split("!A",                   AND, ("!A",               ))
    verify_split("A = B",                AND, ("A = B",            ))
    verify_split("A || B",               AND, ("A || B",           ))
    verify_split("A && B",               AND, ("A", "B"            ))
    verify_split("(A && B) && C",        AND, ("A", "B", "C"       ))
    verify_split("A && (B && C)",        AND, ("A", "B", "C"       ))
    verify_split("A && !(B && C)",       AND, ("A", "!(B && C)"    ))
    verify_split("A && (B || (C && D))", AND, ("A", "B || (C && D)"))
    verify_split("(A || (B && C)) && D", AND, ("A || (B && C)", "D"))


    print("Testing visibility")

    c = Kconfig("Kconfiglib/tests/Kvisibility")

    def verify_visibility(item, no_module_vis, module_vis):
        c.modules.set_value(0)
        verify(item.visibility == no_module_vis,
               "expected {} to have visibility {} without modules, had "
               "visibility {}".
               format(repr(item), no_module_vis, item.visibility))

        c.modules.set_value(2)
        verify(item.visibility == module_vis,
               "expected {} to have visibility {} with modules, had "
               "visibility {}".
               format(repr(item), module_vis, item.visibility))

    # Symbol visibility

    verify_visibility(c.syms["NO_PROMPT"],     0, 0)
    verify_visibility(c.syms["BOOL_N"],        0, 0)
    verify_visibility(c.syms["BOOL_M"],        0, 2)
    verify_visibility(c.syms["BOOL_MOD"],      2, 2)
    verify_visibility(c.syms["BOOL_Y"],        2, 2)
    verify_visibility(c.syms["TRISTATE_M"],    0, 1)
    verify_visibility(c.syms["TRISTATE_MOD"],  2, 1)
    verify_visibility(c.syms["TRISTATE_Y"],    2, 2)
    verify_visibility(c.syms["BOOL_IF_N"],     0, 0)
    verify_visibility(c.syms["BOOL_IF_M"],     0, 2)
    verify_visibility(c.syms["BOOL_IF_Y"],     2, 2)
    verify_visibility(c.syms["BOOL_MENU_N"],   0, 0)
    verify_visibility(c.syms["BOOL_MENU_M"],   0, 2)
    verify_visibility(c.syms["BOOL_MENU_Y"],   2, 2)
    verify_visibility(c.syms["BOOL_CHOICE_N"], 0, 0)

    # Non-tristate symbols in tristate choices are only visible if the choice
    # is in y mode

    # The choice can't be brought to y mode because of the 'if m'
    verify_visibility(c.syms["BOOL_CHOICE_M"], 0, 0)
    c.syms["BOOL_CHOICE_M"].choice.set_value(2)
    verify_visibility(c.syms["BOOL_CHOICE_M"], 0, 0)

    # The choice gets y mode only when running without modules, because it
    # defaults to m mode
    verify_visibility(c.syms["BOOL_CHOICE_Y"], 2, 0)
    c.syms["BOOL_CHOICE_Y"].choice.set_value(2)
    # When set to y mode, the choice symbol becomes visible both with and
    # without modules
    verify_visibility(c.syms["BOOL_CHOICE_Y"], 2, 2)

    verify_visibility(c.syms["TRISTATE_IF_N"],     0, 0)
    verify_visibility(c.syms["TRISTATE_IF_M"],     0, 1)
    verify_visibility(c.syms["TRISTATE_IF_Y"],     2, 2)
    verify_visibility(c.syms["TRISTATE_MENU_N"],   0, 0)
    verify_visibility(c.syms["TRISTATE_MENU_M"],   0, 1)
    verify_visibility(c.syms["TRISTATE_MENU_Y"],   2, 2)
    verify_visibility(c.syms["TRISTATE_CHOICE_N"], 0, 0)
    verify_visibility(c.syms["TRISTATE_CHOICE_M"], 0, 1)
    verify_visibility(c.syms["TRISTATE_CHOICE_Y"], 2, 2)

    verify_visibility(c.named_choices["BOOL_CHOICE_N"],     0, 0)
    verify_visibility(c.named_choices["BOOL_CHOICE_M"],     0, 2)
    verify_visibility(c.named_choices["BOOL_CHOICE_Y"],     2, 2)
    verify_visibility(c.named_choices["TRISTATE_CHOICE_N"], 0, 0)
    verify_visibility(c.named_choices["TRISTATE_CHOICE_M"], 0, 1)
    verify_visibility(c.named_choices["TRISTATE_CHOICE_Y"], 2, 2)

    verify_visibility(c.named_choices["TRISTATE_CHOICE_IF_M_AND_Y"],   0, 1)
    verify_visibility(c.named_choices["TRISTATE_CHOICE_MENU_N_AND_Y"], 0, 0)

    # Verify that 'visible if' visibility gets propagated to prompts

    verify_visibility(c.syms["VISIBLE_IF_N"], 0, 0)
    verify_visibility(c.syms["VISIBLE_IF_M"], 0, 1)
    verify_visibility(c.syms["VISIBLE_IF_Y"], 2, 2)
    verify_visibility(c.syms["VISIBLE_IF_M_2"], 0, 1)

    # Verify that string/int/hex symbols with m visibility accept a user value

    assign_and_verify("STRING_m", "foo bar")
    assign_and_verify("INT_m", "123")
    assign_and_verify("HEX_m", "0x123")


    print("Testing .assignable")

    c = Kconfig("Kconfiglib/tests/Kassignable")

    def verify_assignable_imp(item, assignable_no_modules, assignable_modules):
        # Verifies the assignable values for 'item', with and without modules.

        for modules_val, assignable in (0, assignable_no_modules), \
                                       (2, assignable_modules):

            c.modules.set_value(modules_val)
            module_msg = "without modules" if modules_val == 0 else \
                         "with modules"

            verify(item.assignable == assignable,
                   "Incorrect assignable values for {} {}. Should be {}, "
                   "was {}."
                   .format(item.name, module_msg, assignable, item.assignable))

            # Verify that the values can actually be assigned too

            for val in item.assignable:
                item.set_value(val)
                verify(item.tri_value == val,
                       "Unable to set {} to {} {}, even though it was in "
                       ".assignable".format(item.name, val, module_msg))

    def verify_assignable(sym_name, assignable_no_modules, assignable_modules):
        verify_assignable_imp(c.syms[sym_name],
                              assignable_no_modules,
                              assignable_modules)

    def verify_const_unassignable(sym_name):
        verify_assignable_imp(c.const_syms[sym_name], (), ())

    # Things that shouldn't be .assignable
    verify_const_unassignable("n")
    verify_const_unassignable("m")
    verify_const_unassignable("y")
    verify_const_unassignable("const")
    verify_assignable("UNDEFINED", (), ())
    verify_assignable("NO_PROMPT", (), ())
    verify_assignable("STRING", (), ())
    verify_assignable("INT", (), ())
    verify_assignable("HEX", (), ())

    # Non-selected symbols
    verify_assignable("Y_VIS_BOOL", (0, 2), (0,    2))
    verify_assignable("M_VIS_BOOL", (    ), (0,    2))  # Vis. promoted
    verify_assignable("N_VIS_BOOL", (    ), (       ))
    verify_assignable("Y_VIS_TRI",  (0, 2), (0, 1, 2))
    verify_assignable("M_VIS_TRI",  (    ), (0, 1   ))
    verify_assignable("N_VIS_TRI",  (    ), (       ))

    # Symbols selected to y
    verify_assignable("Y_SEL_Y_VIS_BOOL", (2,), (2,))
    verify_assignable("Y_SEL_M_VIS_BOOL", (  ), (2,))  # Vis. promoted
    verify_assignable("Y_SEL_N_VIS_BOOL", (  ), (  ))
    verify_assignable("Y_SEL_Y_VIS_TRI",  (2,), (2,))
    verify_assignable("Y_SEL_M_VIS_TRI",  (  ), (2,))
    verify_assignable("Y_SEL_N_VIS_TRI",  (  ), (  ))

    # Symbols selected to m
    verify_assignable("M_SEL_Y_VIS_BOOL", (2,), (   2,))  # Value promoted
    verify_assignable("M_SEL_M_VIS_BOOL", (  ), (   2,))  # Vis./value promoted
    verify_assignable("M_SEL_N_VIS_BOOL", (  ), (     ))
    verify_assignable("M_SEL_Y_VIS_TRI",  (2,), (1, 2 ))
    verify_assignable("M_SEL_M_VIS_TRI",  (  ), (1,   ))
    verify_assignable("M_SEL_N_VIS_TRI",  (  ), (     ))

    # Symbols implied to y
    verify_assignable("Y_IMP_Y_VIS_BOOL", (0, 2), (0, 2))
    verify_assignable("Y_IMP_M_VIS_BOOL", (    ), (0, 2))  # Vis. promoted
    verify_assignable("Y_IMP_N_VIS_BOOL", (    ), (    ))
    verify_assignable("Y_IMP_Y_VIS_TRI",  (0, 2), (0, 2))  # m removed by imply
    verify_assignable("Y_IMP_M_VIS_TRI",  (    ), (0, 2))  # m promoted to y by imply
    verify_assignable("Y_IMP_N_VIS_TRI",  (    ), (    ))

    # Symbols implied to m (never affects assignable values)
    verify_assignable("M_IMP_Y_VIS_BOOL", (0, 2), (0,    2))
    verify_assignable("M_IMP_M_VIS_BOOL", (    ), (0,    2))  # Vis. promoted
    verify_assignable("M_IMP_N_VIS_BOOL", (    ), (       ))
    verify_assignable("M_IMP_Y_VIS_TRI",  (0, 2), (0, 1, 2))
    verify_assignable("M_IMP_M_VIS_TRI",  (    ), (0, 1   ))
    verify_assignable("M_IMP_N_VIS_TRI",  (    ), (       ))

    # Symbols in y-mode choice
    verify_assignable("Y_CHOICE_BOOL",           (2,), (2,))
    verify_assignable("Y_CHOICE_TRISTATE",       (2,), (2,))
    verify_assignable("Y_CHOICE_N_VIS_TRISTATE", (  ), (  ))

    # Symbols in m/y-mode choice, starting out in m mode, or y mode when
    # running without modules
    verify_assignable("MY_CHOICE_BOOL",           (2,), (    ))
    verify_assignable("MY_CHOICE_TRISTATE",       (2,), (0, 1))
    verify_assignable("MY_CHOICE_N_VIS_TRISTATE", (  ), (    ))

    c.named_choices["MY_CHOICE"].set_value(2)

    # Symbols in m/y-mode choice, now in y mode
    verify_assignable("MY_CHOICE_BOOL",           (2,), (2,))
    verify_assignable("MY_CHOICE_TRISTATE",       (2,), (2,))
    verify_assignable("MY_CHOICE_N_VIS_TRISTATE", (  ), (  ))

    def verify_choice_assignable(choice_name, assignable_no_modules,
                                 assignable_modules):
        verify_assignable_imp(c.named_choices[choice_name],
                              assignable_no_modules,
                              assignable_modules)

    # Choices with various possible modes
    verify_choice_assignable("Y_CHOICE",   (2,  ), (      2,))
    verify_choice_assignable("MY_CHOICE",  (2,  ), (   1, 2 ))
    verify_choice_assignable("NMY_CHOICE", (0, 2), (0, 1, 2 ))
    verify_choice_assignable("NY_CHOICE",  (0, 2), (0,    2 ))
    verify_choice_assignable("NM_CHOICE",  (    ), (0, 1    ))
    verify_choice_assignable("M_CHOICE",   (    ), (   1,   ))
    verify_choice_assignable("N_CHOICE",   (    ), (        ))


    print("Testing object relations")

    c = Kconfig("Kconfiglib/tests/Krelation")

    verify(c.syms["A"].nodes[0].parent is c.top_node,
           "A's parent should be the top node")

    verify(c.syms["B"].nodes[0].parent.item is c.named_choices["CHOICE_1"],
           "B's parent should be the first choice")

    verify(c.syms["C"].nodes[0].parent.item is c.syms["B"],
           "C's parent should be B (due to auto menus)")

    verify(c.syms["E"].nodes[0].parent.item == MENU,
           "E's parent should be a menu")

    verify(c.syms["E"].nodes[0].parent.parent is c.top_node,
           "E's grandparent should be the top node")

    verify(c.syms["G"].nodes[0].parent.item is c.named_choices["CHOICE_2"],
           "G's parent should be the second choice")

    verify(c.syms["G"].nodes[0].parent.parent.item == MENU,
           "G's grandparent should be a menu")


    print("Testing hex/int ranges")

    c = Kconfig("Kconfiglib/tests/Krange", warn=False)

    for sym_name in "HEX_NO_RANGE", "INT_NO_RANGE", "HEX_40", "INT_40":
        sym = c.syms[sym_name]
        verify(not sym.ranges,
               "{} should not have ranges".format(sym_name))

    for sym_name in "HEX_ALL_RANGES_DISABLED", "INT_ALL_RANGES_DISABLED", \
                    "HEX_RANGE_10_20_LOW_DEFAULT", \
                    "INT_RANGE_10_20_LOW_DEFAULT":
        sym = c.syms[sym_name]
        verify(sym.ranges, "{} should have ranges".format(sym_name))

    # hex/int symbols without defaults should get no default value
    verify_value("HEX_NO_RANGE", "")
    verify_value("INT_NO_RANGE", "")
    # And neither if all ranges are disabled
    verify_value("HEX_ALL_RANGES_DISABLED", "")
    verify_value("INT_ALL_RANGES_DISABLED", "")
    # Make sure they are assignable though, and test that the form of the user
    # value is reflected in the value for hex symbols
    assign_and_verify("HEX_NO_RANGE", "0x123")
    assign_and_verify("HEX_NO_RANGE", "123")
    assign_and_verify("INT_NO_RANGE", "123")

    # Defaults outside of the valid range should be clamped
    verify_value("HEX_RANGE_10_20_LOW_DEFAULT", "0x10")
    verify_value("HEX_RANGE_10_20_HIGH_DEFAULT", "0x20")
    verify_value("INT_RANGE_10_20_LOW_DEFAULT", "10")
    verify_value("INT_RANGE_10_20_HIGH_DEFAULT", "20")
    # Defaults inside the valid range should be preserved. For hex symbols,
    # they should additionally use the same form as in the assignment.
    verify_value("HEX_RANGE_10_20_OK_DEFAULT", "0x15")
    verify_value("HEX_RANGE_10_20_OK_DEFAULT_ALTERNATE", "15")
    verify_value("INT_RANGE_10_20_OK_DEFAULT", "15")

    # hex/int symbols with no defaults but valid ranges should default to the
    # lower end of the range if it's > 0
    verify_value("HEX_RANGE_10_20", "0x10")
    verify_value("HEX_RANGE_0_10", "")
    verify_value("INT_RANGE_10_20", "10")
    verify_value("INT_RANGE_0_10", "")
    verify_value("INT_RANGE_NEG_10_10", "")

    # User values and dependent ranges

    # Avoid warnings for assigning values outside the active range
    c.warn = False

    def verify_range(sym_name, low, high, default):
        # Verifies that all values in the range 'low'-'high' can be assigned,
        # and that assigning values outside the range reverts the value back to
        # 'default' (None if it should revert back to "").

        is_hex = (c.syms[sym_name].type == HEX)

        for i in range(low, high + 1):
            assign_and_verify_user_value(sym_name, str(i), str(i), True)
            if is_hex:
                # The form of the user value should be preserved for hex
                # symbols
                assign_and_verify_user_value(sym_name, hex(i), hex(i), True)

        # Verify that assigning a user value just outside the range causes
        # defaults to be used

        if default is None:
            default_str = ""
        else:
            default_str = hex(default) if is_hex else str(default)

        if is_hex:
            too_low_str = hex(low - 1)
            too_high_str = hex(high + 1)
        else:
            too_low_str = str(low - 1)
            too_high_str = str(high + 1)

        assign_and_verify_value(sym_name, too_low_str, default_str)
        assign_and_verify_value(sym_name, too_high_str, default_str)

    verify_range("HEX_RANGE_10_20_LOW_DEFAULT",  0x10, 0x20,  0x10)
    verify_range("HEX_RANGE_10_20_HIGH_DEFAULT", 0x10, 0x20,  0x20)
    verify_range("HEX_RANGE_10_20_OK_DEFAULT",   0x10, 0x20,  0x15)

    verify_range("INT_RANGE_10_20_LOW_DEFAULT",  10,   20,    10)
    verify_range("INT_RANGE_10_20_HIGH_DEFAULT", 10,   20,    20)
    verify_range("INT_RANGE_10_20_OK_DEFAULT",   10,   20,    15)

    verify_range("HEX_RANGE_10_20",              0x10, 0x20,  0x10)

    verify_range("INT_RANGE_10_20",              10,  20,     10)
    verify_range("INT_RANGE_0_10",               0,   10,     None)
    verify_range("INT_RANGE_NEG_10_10",          -10, 10,     None)

    # Dependent ranges

    verify_value("HEX_40", "40")
    verify_value("INT_40", "40")

    c.syms["HEX_RANGE_10_20"].unset_value()
    c.syms["INT_RANGE_10_20"].unset_value()
    verify_value("HEX_RANGE_10_40_DEPENDENT", "0x10")
    verify_value("INT_RANGE_10_40_DEPENDENT", "10")
    c.syms["HEX_RANGE_10_20"].set_value("15")
    c.syms["INT_RANGE_10_20"].set_value("15")
    verify_value("HEX_RANGE_10_40_DEPENDENT", "0x15")
    verify_value("INT_RANGE_10_40_DEPENDENT", "15")
    c.unset_values()
    verify_range("HEX_RANGE_10_40_DEPENDENT", 0x10, 0x40,  0x10)
    verify_range("INT_RANGE_10_40_DEPENDENT", 10,   40,    10)

    # Ranges and symbols defined in multiple locations

    verify_value("INACTIVE_RANGE", "2")
    verify_value("ACTIVE_RANGE", "1")


    print("Testing defconfig_filename")

    c = Kconfig("Kconfiglib/tests/empty")
    verify(c.defconfig_filename is None,
           "defconfig_filename should be None with no defconfig_list symbol")

    c = Kconfig("Kconfiglib/tests/Kdefconfig_nonexistent")
    verify(c.defconfig_filename is None,
           "defconfig_filename should be None when none of the files in the "
           "defconfig_list symbol exist")

    # Referenced in Kdefconfig_existent(_but_n)
    os.environ["FOO"] = "defconfig_2"

    c = Kconfig("Kconfiglib/tests/Kdefconfig_existent_but_n")
    verify(c.defconfig_filename is None,
           "defconfig_filename should be None when the condition is n for all "
           "the defaults")

    c = Kconfig("Kconfiglib/tests/Kdefconfig_existent")
    verify(c.defconfig_filename == "Kconfiglib/tests/defconfig_2",
           "defconfig_filename should return the existing file "
           "Kconfiglib/tests/defconfig_2")

    # Should also look relative to $srctree if the specified defconfig is a
    # relative path and can't be opened

    c = Kconfig("Kconfiglib/tests/Kdefconfig_srctree")
    verify(c.defconfig_filename == "Kconfiglib/tests/defconfig_2",
           "defconfig_filename gave wrong file with $srctree unset")

    os.environ["srctree"] = "Kconfiglib/tests"
    c = Kconfig("Kdefconfig_srctree")
    verify(c.defconfig_filename == "Kconfiglib/tests/sub/defconfig_in_sub",
           "defconfig_filename gave wrong file with $srctree set")

    os.environ.pop("srctree", None)


    print("Testing mainmenu_text")

    c = Kconfig("Kconfiglib/tests/empty")
    verify(c.mainmenu_text == "Main menu",
           "An empty Kconfig should get a default main menu prompt")

    # Expanded in the mainmenu text
    os.environ["FOO"] = "bar baz"
    c = Kconfig("Kconfiglib/tests/Kmainmenu")
    verify(c.mainmenu_text == "---bar baz---",
           "Wrong mainmenu text")


    print("Testing user_value")

    # References undefined env. var. Disable warnings.
    c = Kconfig("Kconfiglib/tests/Kmisc", warn=False)

    # Avoid warnings from assigning invalid user values and assigning user
    # values to symbols without prompts
    c.warn = False

    syms = [c.syms[name] for name in
            ("BOOL", "TRISTATE", "STRING", "INT", "HEX")]

    for sym in syms:
        verify(sym.user_value is None,
               "{} should not have a user value to begin with")

    # Assign valid values for the types

    assign_and_verify_user_value("BOOL", 0, 0, True)
    assign_and_verify_user_value("BOOL", 2, 2, True)
    assign_and_verify_user_value("TRISTATE", 0, 0, True)
    assign_and_verify_user_value("TRISTATE", 1, 1, True)
    assign_and_verify_user_value("TRISTATE", 2, 2, True)
    assign_and_verify_user_value("STRING", "foo bar", "foo bar", True)
    assign_and_verify_user_value("INT", "123", "123", True)
    assign_and_verify_user_value("HEX", "0x123", "0x123", True)

    # Assign invalid values for the types. They should retain their old user
    # value.

    assign_and_verify_user_value("BOOL", 1, 2, False)
    assign_and_verify_user_value("BOOL", "foo", 2, False)
    assign_and_verify_user_value("BOOL", "1", 2, False)
    assign_and_verify_user_value("TRISTATE", "foo", 2, False)
    assign_and_verify_user_value("TRISTATE", "1", 2, False)
    assign_and_verify_user_value("STRING", 0, "foo bar", False)
    assign_and_verify_user_value("INT", "foo", "123", False)
    assign_and_verify_user_value("INT", 0, "123", False)
    assign_and_verify_user_value("HEX", "foo", "0x123", False)
    assign_and_verify_user_value("HEX", 0, "0x123", False)
    assign_and_verify_user_value("HEX", "-0x1", "0x123", False)

    for s in syms:
        s.unset_value()
        verify(s.user_value is None,
               "{} should not have a user value after being reset".
               format(s.name))


    print("Testing is_menuconfig")

    c = Kconfig("Kconfiglib/tests/Kmenuconfig")

    for not_menuconfig in c.syms["NOT_MENUCONFIG_1"].nodes[0], \
                          c.syms["NOT_MENUCONFIG_2"].nodes[0], \
                          c.syms["MENUCONFIG_MULTI_DEF"].nodes[0], \
                          c.syms["COMMENT_HOOK"].nodes[0].next:

        verify(not not_menuconfig.is_menuconfig,
               "'{}' should have is_menuconfig False".format(not_menuconfig))

    for menuconfig in c.top_node, \
                      c.syms["MENUCONFIG_1"].nodes[0], \
                      c.syms["MENUCONFIG_MULTI_DEF"].nodes[1], \
                      c.syms["MENU_HOOK"].nodes[0].next, \
                      c.syms["CHOICE_HOOK"].nodes[0].next:

        verify(menuconfig.is_menuconfig,
               "'{}' should have is_menuconfig True".format(menuconfig))


    print("Testing 'option env' semantics")

    os.environ["ENV_VAR"] = "ENV_VAR value"

    # References undefined env. var., so disable warnings
    c = Kconfig("Kconfiglib/tests/Kmisc", warn=False)

    # Verify that 'option env' is treated like a default
    verify_value("FROM_ENV", "ENV_VAR value")
    verify_value("FROM_ENV_MISSING", "missing")

    verify_value("FROM_ENV_WEIRD", "weird")


    print("Testing defined vs undefined symbols")

    for name in "A", "B", "C", "D", "BOOL", "TRISTATE", "STRING", "INT", "HEX":
        verify(c.syms[name].nodes,
               "{} should be defined".format(name))

    for name in "NOT_DEFINED_1", "NOT_DEFINED_2", "NOT_DEFINED_3", \
                "NOT_DEFINED_4":
        sym = c.syms[name]
        verify(not c.syms[name].nodes,
               "{} should not be defined".format(name))


    print("Testing Symbol.choice")

    for name in "A", "B", "C", "D":
        verify(c.syms[name].choice is not None,
               "{} should be a choice symbol".format(name))

    for name in "Q1", "Q2", "Q3", "BOOL", "TRISTATE", "STRING", "INT", "HEX", \
                "FROM_ENV", "FROM_ENV_MISSING", "NOT_DEFINED_1", \
                "NOT_DEFINED_2", "NOT_DEFINED_3", "NOT_DEFINED_4":
        verify(c.syms[name].choice is None,
               "{} should not be a choice symbol".format(name))


    print("Testing is_allnoconfig_y")

    verify(not c.syms["NOT_ALLNOCONFIG_Y"].is_allnoconfig_y,
           "NOT_ALLNOCONFIG_Y should not be allnoconfig_y")
    verify(c.syms["ALLNOCONFIG_Y"].is_allnoconfig_y,
           "ALLNOCONFIG_Y should be allnoconfig_y")


    print("Testing .config reading and writing")

    config_test_file = "Kconfiglib/tests/config_test"

    def verify_file_contents(fname, contents):
        with open(fname, "r") as f:
            file_contents = f.read()
            verify(file_contents == contents,
                   "{} contains '{}'. Expected '{}'."
                   .format(fname, file_contents, contents))

    # Writing/reading strings with characters that need to be escaped

    c = Kconfig("Kconfiglib/tests/Kescape")

    # Test the default value
    c.write_config(config_test_file + "_from_def")
    verify_file_contents(config_test_file + "_from_def",
                         r'''CONFIG_STRING="\"\\"''' "\n")
    # Write our own value
    c.syms["STRING"].set_value(r'''\"a'\\''')
    c.write_config(config_test_file + "_from_user")
    verify_file_contents(config_test_file + "_from_user",
                         r'''CONFIG_STRING="\\\"a'\\\\"''' "\n")

    # Read back the two configs and verify the respective values
    c.load_config(config_test_file + "_from_def")
    verify_value("STRING", '"\\')
    c.load_config(config_test_file + "_from_user")
    verify_value("STRING", r'''\"a'\\''')

    # Appending values from a .config

    c = Kconfig("Kconfiglib/tests/Kappend")

    # Values before assigning
    verify_value("BOOL", "n")
    verify_value("STRING", "")

    # Assign BOOL
    c.load_config("Kconfiglib/tests/config_set_bool", replace=False)
    verify_value("BOOL", "y")
    verify_value("STRING", "")

    # Assign STRING
    c.load_config("Kconfiglib/tests/config_set_string", replace=False)
    verify_value("BOOL", "y")
    verify_value("STRING", "foo bar")

    # Reset BOOL
    c.load_config("Kconfiglib/tests/config_set_string")
    verify_value("BOOL", "n")
    verify_value("STRING", "foo bar")

    # Loading a completely empty .config should reset values
    c.load_config("Kconfiglib/tests/empty")
    verify_value("STRING", "")

    # An indented assignment in a .config should be ignored
    c.load_config("Kconfiglib/tests/config_indented")
    verify_value("IGNOREME", "y")

    # Symbol order in headers and minimal configuration files should match
    # definition order, like in .config files

    c = Kconfig("Kconfiglib/tests/Korder")

    c.write_autoconf(config_test_file)
    verify_file_contents(config_test_file, """
#define CONFIG_O 0
#define CONFIG_R 1
#define CONFIG_D 2
#define CONFIG_E 3
#define CONFIG_R2 4
#define CONFIG_I 5
#define CONFIG_N 6
#define CONFIG_G 7
"""[1:])

    # Differs from defaults
    c.syms["O"].set_value("-1")
    c.syms["R"].set_value("-1")
    c.syms["E"].set_value("-1")
    c.syms["R2"].set_value("-1")
    c.syms["N"].set_value("-1")
    c.syms["G"].set_value("-1")
    c.write_min_config(config_test_file)
    verify_file_contents(config_test_file, """
CONFIG_O=-1
CONFIG_R=-1
CONFIG_E=-1
CONFIG_R2=-1
CONFIG_N=-1
CONFIG_G=-1
"""[1:])

    # Test header strings in configuration files and headers

    os.environ["KCONFIG_CONFIG_HEADER"] = "config header from env.\n"
    os.environ["KCONFIG_AUTOHEADER_HEADER"] = "header header from env.\n"

    c = Kconfig("Kconfiglib/tests/Kheader")
    c.write_config(config_test_file, header="config header from param\n")
    verify_file_contents(config_test_file, """\
config header from param
CONFIG_FOO=y
""")
    c.write_min_config(config_test_file, header="min. config header from param\n")
    verify_file_contents(config_test_file, """\
min. config header from param
""")
    c.write_config(config_test_file)
    verify_file_contents(config_test_file, """\
config header from env.
CONFIG_FOO=y
""")
    c.write_min_config(config_test_file)
    verify_file_contents(config_test_file, """\
config header from env.
""")
    c.write_autoconf(config_test_file, header="header header from param\n")
    verify_file_contents(config_test_file, """\
header header from param
#define CONFIG_FOO 1
""")
    c.write_autoconf(config_test_file)
    verify_file_contents(config_test_file, """\
header header from env.
#define CONFIG_FOO 1
""")

    del os.environ["KCONFIG_CONFIG_HEADER"]
    del os.environ["KCONFIG_AUTOHEADER_HEADER"]


    print("Testing Kconfig fetching and separation")

    for c in Kconfig("Kconfiglib/tests/Kmisc", warn=False), \
             Kconfig("Kconfiglib/tests/Kmisc", warn=False):
        for item in c.syms["BOOL"], \
                    c.syms["BOOL"].nodes[0], \
                    c.named_choices["OPTIONAL"], \
                    c.named_choices["OPTIONAL"].nodes[0], \
                    c.syms["MENU_HOOK"].nodes[0].next, \
                    c.syms["COMMENT_HOOK"].nodes[0].next:
            verify(item.kconfig is c,
                   ".kconfig not properly set for " + repr(item))


    print("Testing imply semantics")

    c = Kconfig("Kconfiglib/tests/Kimply")

    verify_value("IMPLY_DIRECT_DEPS", "y")
    verify_value("UNMET_DIRECT_1", "n")
    verify_value("UNMET_DIRECT_2", "n")
    verify_value("UNMET_DIRECT_3", "n")
    verify_value("MET_DIRECT_1", "y")
    verify_value("MET_DIRECT_2", "y")
    verify_value("MET_DIRECT_3", "y")
    verify_value("MET_DIRECT_4", "y")

    verify_value("IMPLY_COND", "y")
    verify_value("IMPLIED_N_COND", "n")
    verify_value("IMPLIED_M_COND", "m")
    verify_value("IMPLIED_Y_COND", "y")

    verify_value("IMPLY_N_1", "n")
    verify_value("IMPLY_N_2", "n")
    verify_value("IMPLIED_FROM_N_1", "n")
    verify_value("IMPLIED_FROM_N_2", "n")

    verify_value("IMPLY_M", "m")
    verify_value("IMPLIED_M", "m")
    verify_value("IMPLIED_M_BOOL", "y")

    verify_value("IMPLY_M_TO_Y", "y")
    verify_value("IMPLIED_M_TO_Y", "y")

    # Test user value semantics

    # Verify that IMPLIED_TRISTATE is invalidated if the direct
    # dependencies change

    assign_and_verify("IMPLY", 2)
    assign_and_verify("DIRECT_DEP", 2)
    verify_value("IMPLIED_TRISTATE", 2)
    assign_and_verify("DIRECT_DEP", 0)
    verify_value("IMPLIED_TRISTATE", 0)
    # Set back for later tests
    assign_and_verify("DIRECT_DEP", 2)

    # Verify that IMPLIED_TRISTATE can be set to anything when IMPLY has value
    # n, and that it gets the value n by default (for non-imply-related
    # reasons)

    assign_and_verify("IMPLY", 0)
    assign_and_verify("IMPLIED_TRISTATE", 0)
    assign_and_verify("IMPLIED_TRISTATE", 1)
    assign_and_verify("IMPLIED_TRISTATE", 2)
    c.syms["IMPLIED_TRISTATE"].unset_value()
    verify_value("IMPLIED_TRISTATE", "n")

    # Same as above for m. Anything still goes, but m by default now.

    assign_and_verify("IMPLY", 1)
    assign_and_verify("IMPLIED_TRISTATE", 0)
    assign_and_verify("IMPLIED_TRISTATE", 1)
    assign_and_verify("IMPLIED_TRISTATE", 2)
    c.syms["IMPLIED_TRISTATE"].unset_value()
    verify_value("IMPLIED_TRISTATE", 1)

    # Same as above for y. Only n and y should be accepted. m gets promoted to
    # y. Default should be y.

    assign_and_verify("IMPLY", 2)
    assign_and_verify("IMPLIED_TRISTATE", 0)
    assign_and_verify_value("IMPLIED_TRISTATE", 1, 2)
    assign_and_verify("IMPLIED_TRISTATE", 2)
    c.syms["IMPLIED_TRISTATE"].unset_value()
    verify_value("IMPLIED_TRISTATE", 2)

    # Being implied to either m or y should give a bool the value y

    c.syms["IMPLY"].unset_value()
    verify_value("IMPLIED_BOOL", 0)
    assign_and_verify("IMPLY", 0)
    verify_value("IMPLIED_BOOL", 0)
    assign_and_verify("IMPLY", 1)
    verify_value("IMPLIED_BOOL", 2)
    assign_and_verify("IMPLY", 2)
    verify_value("IMPLIED_BOOL", 2)

    # A bool implied to m or y can take the values n and y

    c.syms["IMPLY"].set_value(1)
    assign_and_verify("IMPLIED_BOOL", 0)
    assign_and_verify("IMPLIED_BOOL", 2)

    c.syms["IMPLY"].set_value(2)
    assign_and_verify("IMPLIED_BOOL", 0)
    assign_and_verify("IMPLIED_BOOL", 2)


    print("Testing choice semantics")

    # Would warn for choice value symbols defined without a type, even
    # though the type is automatically derived. This is probably more
    # helpful than ignoring those cases, as this feature isn't used
    # deliberately anywhere from what I've seen.
    c = Kconfig("Kconfiglib/tests/Kchoice", warn=False)

    for name in "BOOL", "BOOL_OPT", "BOOL_M", "DEFAULTS":
        verify(c.named_choices[name].orig_type == BOOL,
               "choice {} should have type bool".format(name))

    for name in "TRISTATE", "TRISTATE_OPT", "TRISTATE_M":
        verify(c.named_choices[name].orig_type == TRISTATE,
               "choice {} should have type tristate".format(name))

    def select_and_verify(sym):
        choice = sym.nodes[0].parent.item
        choice.set_value(2)

        sym.set_value(2)

        verify(sym.choice.selection is sym,
               sym.name + " should be the selected symbol")

        verify(choice.user_selection is sym,
               sym.name + " should be the user selection of the choice")

        verify(sym.tri_value == 2,
               sym.name + " should have value y when selected")

        verify(sym.user_value == 2,
               sym.name + " should have user value y when selected")

        for sibling in choice.syms:
            if sibling is not sym:
                verify(sibling.tri_value == 0,
                       sibling.name + " should be n when not selected")

    def select_and_verify_all(choice_name):
        choice = c.named_choices[choice_name]

        # Select in forward order
        for sym in choice.syms:
            select_and_verify(sym)

        # Select in reverse order
        for sym in reversed(choice.syms):
            select_and_verify(sym)

    def verify_mode(choice_name, no_modules_mode, modules_mode):
        choice = c.named_choices[choice_name]

        c.modules.set_value(0)
        verify(choice.tri_value == no_modules_mode,
               'Wrong mode for choice {} with no modules. Expected {}, got {}.'
               .format(choice.name, no_modules_mode, choice.tri_value))

        c.modules.set_value(2)
        verify(choice.tri_value == modules_mode,
               'Wrong mode for choice {} with modules. Expected {}, got {}.'
               .format(choice.name, modules_mode, choice.tri_value))

    verify_mode("BOOL",         2, 2)
    verify_mode("BOOL_OPT",     0, 0)
    verify_mode("TRISTATE",     2, 1)
    verify_mode("TRISTATE_OPT", 0, 0)
    verify_mode("BOOL_M",       0, 2)
    verify_mode("TRISTATE_M",   0, 1)

    # Test defaults

    choice = c.named_choices["DEFAULTS"]

    c.syms["TRISTATE_SYM"].set_value(0)
    verify(choice.selection is c.syms["OPT_4"],
           "Wrong choice default with TRISTATE_SYM = n")

    c.syms["TRISTATE_SYM"].set_value(2)
    verify(choice.selection is c.syms["OPT_2"],
           "Wrong choice default with TRISTATE_SYM = y")

    c.syms["OPT_1"].set_value(2)
    verify(choice.selection is c.syms["OPT_1"],
           "User selection should override defaults")

    verify(c.named_choices["DEFAULTS_NOT_VISIBLE"].selection
           is c.syms["OPT_8"],
           "Non-visible choice symbols should cause the next default to be "
           "considered")

    # Test y mode selection

    c.modules.set_value(2)

    select_and_verify_all("BOOL")
    select_and_verify_all("BOOL_OPT")
    select_and_verify_all("TRISTATE")
    select_and_verify_all("TRISTATE_OPT")
    # For BOOL_M, the mode should have been promoted
    select_and_verify_all("BOOL_M")

    # Test m mode selection

    c.named_choices["TRISTATE"].set_value(1)

    verify(c.named_choices["TRISTATE"].tri_value == 1,
           "TRISTATE choice should have mode m after explicit mode assignment")

    assign_and_verify_value("T_1", 0, 0)
    assign_and_verify_value("T_2", 0, 0)
    assign_and_verify_value("T_1", 1, 1)
    assign_and_verify_value("T_2", 1, 1)
    assign_and_verify_value("T_1", 2, 1)
    assign_and_verify_value("T_2", 2, 1)

    # Switching to y mode should cause T_2 to become selected
    c.named_choices["TRISTATE"].set_value(2)
    verify_value("T_1", 0)
    verify_value("T_2", 2)

    # Verify that choices with no explicitly specified type get the type of the
    # first contained symbol with a type

    verify(c.named_choices["NO_TYPE_BOOL"].orig_type == BOOL,
           "Expected first choice without explicit type to have type bool")

    verify(c.named_choices["NO_TYPE_TRISTATE"].orig_type == TRISTATE,
           "Expected second choice without explicit type to have type "
           "tristate")

    # Verify that symbols without a type in the choice get the type of the
    # choice

    for name in "MMT_1", "MMT_2", "MMT_4", "MMT_5":
        verify(c.syms[name].orig_type == BOOL,
               "Expected {} to get type bool".format(name))

    verify(c.syms["MMT_3"].orig_type == TRISTATE,
           "Expected MMT_3 to have type tristate")

    # Verify that the default selection can change depending on the
    # visibility of the choice symbols

    default_with_dep_choice = c.named_choices["DEFAULT_WITH_DEP"]

    verify(default_with_dep_choice.selection is c.syms["B"],
           "Wrong choice default with unsatisfied deps on default")

    c.syms["DEP"].set_value("y")

    verify(default_with_dep_choice.selection is c.syms["A"],
           "Wrong choice default with satisfied deps on default")

    c.syms["DEP"].set_value("n")

    verify(default_with_dep_choice.selection is c.syms["B"],
           "Wrong choice default with unsatisfied deps on default (round two)")

    # Verify that symbols in choices that depend on the preceding symbol aren't
    # considered choice symbols

    weird_choice = c.named_choices["WEIRD_SYMS"]

    def verify_is_normal_choice_symbol(name):
        sym = c.syms[name]
        verify(sym.choice is not None and
               sym in weird_choice.syms and
               sym.nodes[0].parent.item is weird_choice,
               "{} should be a normal choice symbol".format(sym.name))

    def verify_is_weird_choice_symbol(name):
        sym = c.syms[name]
        verify(sym.choice is None and
               sym not in weird_choice.syms,
               "{} should be a weird (non-)choice symbol"
               .format(sym.name))

    verify_is_normal_choice_symbol("WS1")
    verify_is_weird_choice_symbol("WS2")
    verify_is_weird_choice_symbol("WS3")
    verify_is_weird_choice_symbol("WS4")
    verify_is_weird_choice_symbol("WS5")
    verify_is_normal_choice_symbol("WS6")
    verify_is_weird_choice_symbol("WS7")
    verify_is_weird_choice_symbol("WS8")
    verify_is_normal_choice_symbol("WS9")


    print("Testing 'if' node removal")

    c = Kconfig("Kconfiglib/tests/Kifremoval", warn=False)

    nodes = tuple(c.node_iter())
    verify_equal(nodes[0].item.name, "A")
    verify_equal(nodes[1].item.name, "B")
    verify_equal(nodes[2].item.name, "C")
    verify_equal(nodes[3].item.name, "D")
    verify_equal(nodes[4].prompt[0], "E")
    verify_equal(nodes[5].prompt[0], "F")
    verify_equal(nodes[6].prompt[0], "G")
    verify_equal(nodes[7].item.name, "H")
    verify_equal(nodes[8].item.name, "I")
    verify_equal(nodes[9].item.name, "J")
    verify(len(nodes) == 10,
           "Wrong number of nodes after 'if' removal")


    print("Testing multi.def. property copying")

    c = Kconfig("Kconfiglib/tests/Kdepcopy", warn=False)

    def verify_props(desc, props, prop_names):
        actual = [prop[0].name for prop in props]
        expected = prop_names.split()

        verify(actual == expected,
               "Wrong {} properties, expected '{}', got '{}'"
               .format(desc, expected, actual))

    verify_props("default", c.syms["MULTIDEF"].defaults,
                 "A B C D E F G H I J K L M N O P Q R")

    verify_props("select", c.syms["MULTIDEF"].selects,
                 "AA BB CC DD EE FF GG HH II JJ")

    verify_props("imply", c.syms["MULTIDEF"].selects,
                 "AA BB CC DD EE FF GG HH II JJ")

    verify_props("select", c.syms["MULTIDEF_CHOICE"].selects,
                 "A B C")

    verify_props("range", c.syms["MULTIDEF_RANGE"].ranges,
                 "A B C D E F")

    verify_props("default", c.choices[1].defaults,
                 "A B C D E")


    print("Testing dependency loop detection")

    # These are all expected to raise dependency loop errors
    for i in range(11):
        filename = "Kconfiglib/tests/Kdeploop" + str(i)
        try:
            Kconfig(filename)
        except KconfigError as e:
            if "Dependency loop" not in str(e):
                fail("dependency loop in {} raised wrong KconfigError"
                     .format(filename))
        except:
            fail("dependency loop in {} raised wrong exception"
                 .format(filename))
        else:
            fail("dependency loop in {} not detected".format(filename))

    # Check the most complicated message completely
    try:
        Kconfig("Kconfiglib/tests/Kdeploop10")
    except KconfigError as e:
        verify_equal(str(e), """
Dependency loop
===============

A (defined at Kconfiglib/tests/Kdeploop10:1), with definition...

config A
	bool
	depends on B

...depends on B (defined at Kconfiglib/tests/Kdeploop10:5), with definition...

config B
	bool
	depends on C = 7

...depends on C (defined at Kconfiglib/tests/Kdeploop10:9), with definition...

config C
	int
	range D 8

...depends on D (defined at Kconfiglib/tests/Kdeploop10:13), with definition...

config D
	int
	default 3 if E
	default 8

...depends on E (defined at Kconfiglib/tests/Kdeploop10:18), with definition...

config E
	bool

(select-related dependencies: F && G)

...depends on G (defined at Kconfiglib/tests/Kdeploop10:25), with definition...

config G
	bool
	depends on H

...depends on the choice symbol H (defined at Kconfiglib/tests/Kdeploop10:32), with definition...

config H
	bool "H"
	depends on I && <choice>

...depends on the choice symbol I (defined at Kconfiglib/tests/Kdeploop10:41), with definition...

config I
	bool "I"
	depends on <choice>

...depends on <choice> (defined at Kconfiglib/tests/Kdeploop10:38), with definition...

choice
	bool "choice" if J

...depends on J (defined at Kconfiglib/tests/Kdeploop10:46), with definition...

config J
	bool
	depends on A

...depends again on A (defined at Kconfiglib/tests/Kdeploop10:1)
"""[:-1])
    except:
        fail("Loop detection message check raised wrong exception")
    else:
        fail("Loop detection message check did not raise exception")


    print("Testing preprocessor")

    os.environ["ENV_1"] = "env_1"
    os.environ["ENV_2"] = "env_2"
    os.environ["ENV_3"] = "env_3"
    os.environ["ENV_4"] = "env_4"
    os.environ["ENV_5"] = "n"
    os.environ["ENV_6"] = "Kconfiglib/tests/empty"
    os.environ["ENV_7"] = "env_7"
    # We verify warnings manually
    c = Kconfig("Kconfiglib/tests/Kpreprocess", warn_to_stderr=False)

    def verify_variable(name, unexp_value, exp_value, recursive, *args):
        var = c.variables[name]

        verify(var.value == unexp_value,
               "expected variable '{}' to have the unexpanded value '{}', had "
               "the value '{}'".format(name, unexp_value, var.value))

        if not args:
            verify(var.expanded_value == exp_value,
                   "expected expanded_value for {} to be '{}', was '{}'"
                   .format(name, exp_value, var.expanded_value))

        verify(var.expanded_value_w_args(*args) == exp_value,
               "expected expanded_value_w_args() for '{}' to be '{}', was '{}'"
               .format(name, exp_value, var.expanded_value_w_args(*args)))

        verify(var.is_recursive == recursive,
               "{} was {}, shouldn't be"
               .format(name, "recursive" if var.is_recursive else "simple"))

    verify_variable("simple-recursive", "foo", "foo", True)
    verify_variable("simple-immediate", "bar", "bar", False)
    verify_variable("simple-recursive-2", "baz", "baz", True)

    verify_variable("whitespaced", "foo", "foo", True)

    verify_variable("preserve-recursive", "foo bar", "foo bar", True)
    verify_variable("preserve-immediate", "foo bar", "foo bar", False)

    verify_variable("recursive",
                    "$(foo) $(bar) $($(b-char)a$(z-char)) $(indir)",
                    "abc def ghi jkl mno",
                    True)

    verify_variable("immediate", "foofoo", "foofoo", False)

    verify_variable("messy-fn-res",
                    "$($(fn-indir)-unused-arg, a  b (,) , c  d )",
                    'surround-rev-quote " c  d " " a  b (,) " surround-rev-quote ',
                    True)

    verify_variable("special-chars-fn-res",
                    "$(fn,$(comma)$(dollar)$(left-paren)foo$(right-paren))",
                    '",$(foo)"',
                    True)

    verify_variable("quote", '"$(1)" "$(2)"', '"" ""', True)
    verify_variable("quote", '"$(1)" "$(2)"', '"one" ""', True,
                    "one")
    verify_variable("quote", '"$(1)" "$(2)"', '"one" "two"', True,
                    "one", "two")
    verify_variable("quote", '"$(1)" "$(2)"', '"one" "two"', True,
                    "one", "two", "three")

    verify_str(c.syms["PRINT_ME"], r"""
config PRINT_ME
	string "env_1" if (FOO && BAR) || !BAZ || !QAZ
	default "\"foo\"" if "foo \"bar\" baz" = ""
""")

    verify_str(c.syms["PRINT_ME_TOO"], r"""
config PRINT_ME_TOO
	bool "foo"
	default FOOBARBAZQAZ if QAZ && QAZFOO && xxx
""")

    def verify_repr(name, s):
        verify_equal(repr(c.variables[name]), s)

    verify_repr(
        "simple-immediate",
        "<variable simple-immediate, immediate, value 'bar'>")

    verify_repr(
        "messy-fn-res",
        "<variable messy-fn-res, recursive, value '$($(fn-indir)-unused-arg, a  b (,) , c  d )'>")

    def verify_recursive(name):
        try:
            c.variables[name].expanded_value
        except KconfigError:
            pass
        else:
            fail("Expected '{}' expansion to flag recursive expansion, didn't"
                 .format(name))

    verify_recursive("rec-1")
    # Indirectly verifies that it's not recursive
    verify_variable("safe-fn-rec-res",
                    "$(safe-fn-rec,safe-fn-rec-2)",
                    "foo",
                    True)
    verify_recursive("unsafe-fn-rec")

    verify_variable("foo-bar-baz", "$(rhs)", "value", True)

    verify_variable("space-var-res", "$(foo bar)", "value", True)

    verify_variable("shell-res",
                    "$(shell,false && echo foo bar || echo baz qaz)",
                    "baz qaz",
                    True)

    verify_variable("shell-stderr-res", "", "", False)

    verify_variable("parens-res",
                    "pre-$(shell,echo '(a,$(b-char),(c,d),e)')-post",
                    "pre-(a,b,(c,d),e)-post",
                    True)

    verify_variable("location-res",
                    "Kconfiglib/tests/Kpreprocess:129",
                    "Kconfiglib/tests/Kpreprocess:129",
                    False)

    verify_variable("warning-res", "", "", False)
    verify_variable("error-n-res", "", "", False)

    try:
        c.variables["error-y-res"].expanded_value
    except KconfigError:
        pass
    else:
        fail("expanding error-y-res didn't raise an exception")

    # Check Kconfig.env_vars
    verify_equal(c.env_vars,
                 set(("ENV_1", "ENV_2", "ENV_3", "ENV_4", "ENV_5", "ENV_6")))

    # Check that the expected warnings were generated
    verify_equal(c.warnings, [
        "Kconfiglib/tests/Kpreprocess:122: warning: 'echo message on stderr >&2' wrote to stderr: message on stderr",
        "Kconfiglib/tests/Kpreprocess:134: warning: a warning"
    ])


    print("Testing user-defined preprocessor functions")

    # Make Kconfiglib/tests/kconfigfunctions.py importable
    sys.path.insert(0, "Kconfiglib/tests")

    c = Kconfig("Kconfiglib/tests/Kuserfunctions")

    verify_variable("add-zero",  "$(add)",          "0", True)
    verify_variable("add-one",   "$(add,1)",        "1", True)
    verify_variable("add-three", "$(add,1,-1,2,1)", "3", True)

    verify_variable("one-one", "$(one,foo bar)", "onefoo barfoo bar", True)

    verify_variable("one-or-more-one", "$(one-or-more,foo)", "foo + ", True)
    verify_variable("one-or-more-three", "$(one-or-more,foo,bar,baz)",
                    "foo + bar,baz", True)

    verify_variable("location-1", "Kconfiglib/tests/Kuserfunctions:13",
                    "Kconfiglib/tests/Kuserfunctions:13", False)
    verify_variable("location-2", "Kconfiglib/tests/Kuserfunctions:14",
                    "Kconfiglib/tests/Kuserfunctions:14", False)

    def verify_bad_argno(name):
        try:
            c.variables[name].expanded_value
        except KconfigError:
            pass
        else:
            fail("Expected '{}' expansion to flag wrong number of arguments, "
                 "didn't".format(name))

    verify_bad_argno("one-zero")
    verify_bad_argno("one-two")
    verify_bad_argno("one-or-more-zero")

    sys.path.pop(0)

    # This test can fail on older Python 3.x versions, because they don't
    # preserve dict insertion order during iteration. The output is still
    # correct, just different.
    if not (3, 0) <= sys.version_info <= (3, 5):
        print("Testing KCONFIG_WARN_UNDEF")

        os.environ["KCONFIG_WARN_UNDEF"] = "y"
        c = Kconfig("Kconfiglib/tests/Kundef", warn_to_stderr=False)

        verify_equal("\n".join(c.warnings), """
warning: the int symbol INT (defined at Kconfiglib/tests/Kundef:8) has a non-int range [UNDEF_2 (undefined), 8 (undefined)]
warning: undefined symbol UNDEF_1:

- Referenced at Kconfiglib/tests/Kundef:4:

config BOOL
	bool "foo" if DEF || !UNDEF_1
	default UNDEF_2

- Referenced at Kconfiglib/tests/Kundef:19:

menu "menu"
	depends on UNDEF_1
	visible if UNDEF_3
warning: undefined symbol UNDEF_2:

- Referenced at Kconfiglib/tests/Kundef:4:

config BOOL
	bool "foo" if DEF || !UNDEF_1
	default UNDEF_2

- Referenced at Kconfiglib/tests/Kundef:8:

config INT
	int
	range UNDEF_2 8
	range 5 15
	default 10
warning: undefined symbol UNDEF_3:

- Referenced at Kconfiglib/tests/Kundef:19:

menu "menu"
	depends on UNDEF_1
	visible if UNDEF_3
"""[1:-1])

        os.environ.pop("KCONFIG_WARN_UNDEF")


    print("\nAll selftests passed\n" if all_passed else
          "\nSome selftests failed\n")


def run_compatibility_tests():
    # Runs tests on configurations from the kernel. Tests compability with the
    # C implementation by comparing outputs.

    # Referenced inside the kernel Kconfig files.
    #
    # The str() makes the type of the value 'str' on both Python 2 and Python 3,
    # which is nice for some later dictionary key sanity checks.

    os.environ["KERNELVERSION"] = str(
        subprocess.check_output("make kernelversion", shell=True)
            .decode("utf-8").rstrip()
    )

    os.environ["CC_VERSION_TEXT"] = str(
        subprocess.check_output("gcc --version | head -n1", shell=True)
            .decode("utf-8").rstrip()
    )

    os.environ["srctree"] = "."
    os.environ["CC"] = "gcc"
    os.environ["LD"] = "ld"


    if not os.path.exists("scripts/kconfig/conf"):
        print("\nscripts/kconfig/conf does not exist -- running "
              "'make allnoconfig' to build it...")
        shell("make allnoconfig")


    print("Running compatibility tests...\n")

    test_fns = (test_defconfig,
                # Fails for a few defconfigs due to a bug in the C tools. Will
                # be enabled once patches get in.
                #test_min_config,
                test_alldefconfig,
                test_allnoconfig,
                test_allnoconfig_walk,
                test_allmodconfig,
                test_allyesconfig,
                test_sanity)

    for test_fn in test_fns:
        # The test description is taken from the docstring of the corresponding
        # function
        print(textwrap.dedent(test_fn.__doc__))

        for arch, srcarch in all_arch_srcarch():
            # Referenced inside the Kconfig files
            os.environ["ARCH"] = arch
            os.environ["SRCARCH"] = srcarch

            rm_configs()

            test_fn(arch, srcarch)

    if all_passed:
        print("All selftests and compatibility tests passed")
    else:
        sys.exit("Some tests failed")


def all_arch_srcarch():
    for srcarch in os.listdir("arch"):
        # arc and h8300 are currently broken with the C tools on linux-next as
        # well. Perhaps they require cross-compilers to be installed.
        #
        # User-mode Linux has an unorthodox Kconfig setup that would require a
        # different testing setup. Skip it too.
        if srcarch in ("arc", "h8300", "um"):
            continue

        if os.path.exists(os.path.join("arch", srcarch, "Kconfig")):
            yield (srcarch, srcarch)

    # Some arches define additional ARCH settings with ARCH != SRCARCH
    # (search for "Additional ARCH settings for" in the top-level Makefile)

    yield ("i386", "x86")
    yield ("x86_64", "x86")

    yield ("sparc32", "sparc")
    yield ("sparc64", "sparc")

    yield ("sh64", "sh")


def test_allnoconfig(arch, srcarch):
    """
    Verify that allnoconfig.py generates the same .config as
    'make allnoconfig', for each architecture. Runs the script via
    'make scriptconfig'.
    """
    shell("make scriptconfig SCRIPT=Kconfiglib/allnoconfig.py "
          "PYTHONCMD='{}'".format(sys.executable))
    shell("mv .config ._config")
    shell("scripts/kconfig/conf --allnoconfig Kconfig")

    compare_configs(arch)


def test_allnoconfig_walk(arch, srcarch):
    """
    Verify that examples/allnoconfig_walk.py generates the same .config as
    'make allnoconfig', for each architecture. Runs the script via
    'make scriptconfig'.
    """
    shell("make scriptconfig SCRIPT=Kconfiglib/examples/allnoconfig_walk.py "
          "PYTHONCMD='{}'".format(sys.executable))
    shell("mv .config ._config")
    shell("scripts/kconfig/conf --allnoconfig Kconfig")

    compare_configs(arch)


def test_allmodconfig(arch, srcarch):
    """
    Verify that allmodconfig.py generates the same .config as
    'make allmodconfig', for each architecture. Runs the script via
    'make scriptconfig'.
    """
    shell("make scriptconfig SCRIPT=Kconfiglib/allmodconfig.py "
          "PYTHONCMD='{}'".format(sys.executable))
    shell("mv .config ._config")
    shell("scripts/kconfig/conf --allmodconfig Kconfig")

    compare_configs(arch)


def test_allyesconfig(arch, srcarch):
    """
    Verify that allyesconfig.py generates the same .config as
    'make allyesconfig', for each architecture. Runs the script via
    'make scriptconfig'.
    """
    shell("make scriptconfig SCRIPT=Kconfiglib/allyesconfig.py "
          "PYTHONCMD='{}'".format(sys.executable))
    shell("mv .config ._config")
    shell("scripts/kconfig/conf --allyesconfig Kconfig")

    compare_configs(arch)


def test_sanity(arch, srcarch):
    """
    Do sanity checks on each configuration and call all public methods on all
    symbols, choices, and menu nodes for all architectures to make sure we
    never crash or hang.
    """
    print("For {}...".format(arch))

    kconf = Kconfig()

    for sym in kconf.defined_syms:
        verify(sym._visited == 2,
               "{} has broken dependency loop detection (_visited = {})"
               .format(sym.name, sym._visited))

    kconf.modules
    kconf.defconfig_list
    kconf.defconfig_filename

    # Legacy warning functions
    kconf.enable_redun_warnings()
    kconf.disable_redun_warnings()
    kconf.enable_undef_warnings()
    kconf.disable_undef_warnings()
    kconf.enable_warnings()
    kconf.disable_warnings()
    kconf.enable_stderr_warnings()
    kconf.disable_stderr_warnings()

    kconf.mainmenu_text
    kconf.unset_values()

    kconf.write_autoconf("/dev/null")

    # No tempfile.TemporaryDirectory in Python 2
    tmpdir = tempfile.mkdtemp()
    kconf.sync_deps(os.path.join(tmpdir, "deps"))  # Create
    kconf.sync_deps(os.path.join(tmpdir, "deps"))  # Update
    shutil.rmtree(tmpdir)

    # Python 2/3 compatible
    for key, sym in kconf.syms.items():
        verify(isinstance(key, str), "weird key '{}' in syms dict".format(key))

        verify(not sym.is_constant, sym.name + " in 'syms' and constant")

        verify(sym not in kconf.const_syms,
               sym.name + " in both 'syms' and 'const_syms'")

        for dep in sym._dependents:
            verify(not dep.is_constant,
                   "the constant symbol {} depends on {}"
                   .format(dep.name, sym.name))

        sym.__repr__()
        sym.__str__()
        sym.assignable
        kconf.disable_warnings()
        sym.set_value(2)
        sym.set_value("foo")
        sym.unset_value()
        kconf.enable_warnings()  # Legacy warning function
        sym.str_value
        sym.tri_value
        sym.type
        sym.user_value
        sym.visibility

    for sym in kconf.defined_syms:
        verify(sym.nodes, sym.name + " is defined but lacks menu nodes")

        verify(not (sym.orig_type not in (BOOL, TRISTATE) and sym.choice),
               sym.name + " is a choice symbol but not bool/tristate")

    for key, sym in kconf.const_syms.items():
        verify(isinstance(key, str),
               "weird key '{}' in const_syms dict".format(key))

        verify(sym.is_constant,
               '"{}" is in const_syms but not marked constant'
               .format(sym.name))

        verify(not sym.nodes,
               '"{}" is constant but has menu nodes'.format(sym.name))

        verify(not sym._dependents,
               '"{}" is constant but is a dependency of some symbol'
               .format(sym.name))

        verify(not sym.choice,
               '"{}" is constant and a choice symbol'.format(sym.name))

        sym.__repr__()
        sym.__str__()
        sym.assignable
        kconf.disable_warnings()
        sym.set_value(2)
        sym.set_value("foo")
        sym.unset_value()
        kconf.enable_warnings()  # Legacy warning function
        sym.str_value
        sym.tri_value
        sym.type
        sym.visibility

    for choice in kconf.choices:
        for sym in choice.syms:
            verify(sym.choice is choice,
                   "{0} is in choice.syms but 'sym.choice' is not the choice"
                   .format(sym.name))

            verify(sym.type in (BOOL, TRISTATE),
                   "{} is a choice symbol but is not a bool/tristate"
                   .format(sym.name))

        choice.__str__()
        choice.__repr__()
        choice.str_value
        choice.tri_value
        choice.user_value
        choice.assignable
        choice.selection
        choice.type
        choice.visibility

    # Menu nodes

    node = kconf.top_node

    while 1:
        # Everything else should be well exercised elsewhere
        node.__repr__()
        node.__str__()
        verify(isinstance(node.item, (Symbol, Choice)) or \
               node.item in (MENU, COMMENT),
               "'{}' appeared as a menu item".format(node.item))

        if node.list is not None:
            node = node.list

        elif node.next is not None:
            node = node.next

        else:
            while node.parent is not None:
                node = node.parent
                if node.next is not None:
                    node = node.next
                    break
            else:
                break


def test_alldefconfig(arch, srcarch):
    """
    Verify that alldefconfig.py generates the same .config as
    'make alldefconfig', for each architecture. Runs the script via
    'make scriptconfig'.
    """
    shell("make scriptconfig SCRIPT=Kconfiglib/alldefconfig.py "
          "PYTHONCMD='{}'".format(sys.executable))
    shell("mv .config ._config")
    shell("scripts/kconfig/conf --alldefconfig Kconfig")

    compare_configs(arch)


def test_defconfig(arch, srcarch):
    """
    Verify that Kconfiglib generates the same .config as scripts/kconfig/conf,
    for each architecture/defconfig pair. In obsessive mode, this test includes
    nonsensical groupings of arches with defconfigs from other arches (every
    arch/defconfig combination) and takes an order of magnitude longer time to
    run.

    With logging enabled, this test appends any failures to a file
    test_defconfig_fails in the root.
    """
    kconf = Kconfig()

    if obsessive:
        defconfigs = []

        # Collect all defconfigs. This could be done once instead, but it's
        # a speedy operation comparatively.
        for srcarch_ in os.listdir("arch"):
            defconfigs.extend(defconfig_files(srcarch_))
    else:
        defconfigs = defconfig_files(srcarch)

    # Test architecture for each defconfig

    for defconfig in defconfigs:
        rm_configs()

        kconf.load_config(defconfig)
        kconf.write_config("._config")
        shell("scripts/kconfig/conf --defconfig='{}' Kconfig".
              format(defconfig))

        arch_defconfig_str = "  {:14}with {:60} ".format(arch, defconfig)

        if equal_configs():
            print(arch_defconfig_str + "OK")
        else:
            print(arch_defconfig_str + "FAIL")
            fail()
            if log:
                with open("test_defconfig_fails", "a") as fail_log:
                    fail_log.write("{} with {} did not match\n"
                                   .format(arch, defconfig))


def test_min_config(arch, srcarch):
    """
    Verify that Kconfiglib generates the same .config as 'make savedefconfig'
    for each architecture/defconfig pair.
    """
    kconf = Kconfig()

    if obsessive_min_config:
        defconfigs = []
        for srcarch_ in os.listdir("arch"):
            defconfigs.extend(defconfig_files(srcarch_))
    else:
        defconfigs = defconfig_files(srcarch)

    for defconfig in defconfigs:
        rm_configs()

        kconf.load_config(defconfig)
        kconf.write_min_config("._config")

        shell("cp {} .config".format(defconfig))

        shell("scripts/kconfig/conf --savedefconfig=.config Kconfig")

        arch_defconfig_str = "  {:14}with {:60} ".format(arch, defconfig)

        if equal_configs():
            print(arch_defconfig_str + "OK")
        else:
            print(arch_defconfig_str + "FAIL")


#
# Helper functions
#


def defconfig_files(srcarch):
    # Yields a list of defconfig file filenames for a particular srcarch
    # subdirectory (arch/<srcarch>/)

    srcarch_dir = os.path.join("arch", srcarch)

    # Some arches have a defconfig in the root of their arch/<arch>/ directory
    root_defconfig = os.path.join(srcarch_dir, "defconfig")
    if os.path.exists(root_defconfig):
        yield root_defconfig

    # Assume all files in the arch/<arch>/configs/ directory (if it exists) are
    # configurations
    defconfigs_dir = os.path.join(srcarch_dir, "configs")

    if not os.path.isdir(defconfigs_dir):
        return

    for dirpath, _, filenames in os.walk(defconfigs_dir):
        for filename in filenames:
            yield os.path.join(dirpath, filename)


def rm_configs():
    # Delete any old ".config" (generated by the C implementation) and
    # "._config" (generated by us), if present.

    def rm_if_exists(f):
        if os.path.exists(f):
            os.remove(f)

    rm_if_exists(".config")
    rm_if_exists("._config")


def compare_configs(arch):
    if equal_configs():
        print("{:14}OK".format(arch))
    else:
        print("{:14}FAIL".format(arch))
        fail()


def equal_configs():
    with open(".config") as f:
        their = f.readlines()

    # Strip the header generated by 'conf'
    i = 0
    for line in their:
        if not line.startswith("#") or \
           re.match(r"# CONFIG_(\w+) is not set", line):
            break
        i += 1
    their = their[i:]

    try:
        f = open("._config")
    except EnvironmentError as e:
        if e.errno != errno.ENOENT:
            raise
        print("._config not found. Did you forget to apply the Makefile patch?")
        return False
    else:
        with f:
            our = f.readlines()

    if their == our:
        return True

    # Print a unified diff to help debugging
    print("Mismatched .config's! Unified diff:")
    sys.stdout.writelines(difflib.unified_diff(their, our, fromfile="their",
                                               tofile="our"))

    return False


if __name__ == "__main__":
    run_tests()