summaryrefslogtreecommitdiffstats
path: root/scripts/kconfig/tests
diff options
context:
space:
mode:
Diffstat (limited to 'scripts/kconfig/tests')
-rw-r--r--scripts/kconfig/tests/auto_submenu/Kconfig52
-rw-r--r--scripts/kconfig/tests/auto_submenu/__init__.py13
-rw-r--r--scripts/kconfig/tests/auto_submenu/expected_stdout10
-rw-r--r--scripts/kconfig/tests/choice/Kconfig56
-rw-r--r--scripts/kconfig/tests/choice/__init__.py41
-rw-r--r--scripts/kconfig/tests/choice/alldef_expected_config5
-rw-r--r--scripts/kconfig/tests/choice/allmod_expected_config9
-rw-r--r--scripts/kconfig/tests/choice/allno_expected_config5
-rw-r--r--scripts/kconfig/tests/choice/allyes_expected_config9
-rw-r--r--scripts/kconfig/tests/choice/oldask0_expected_stdout10
-rw-r--r--scripts/kconfig/tests/choice/oldask1_config2
-rw-r--r--scripts/kconfig/tests/choice/oldask1_expected_stdout15
-rw-r--r--scripts/kconfig/tests/choice_value_with_m_dep/Kconfig21
-rw-r--r--scripts/kconfig/tests/choice_value_with_m_dep/__init__.py16
-rw-r--r--scripts/kconfig/tests/choice_value_with_m_dep/config2
-rw-r--r--scripts/kconfig/tests/choice_value_with_m_dep/expected_config3
-rw-r--r--scripts/kconfig/tests/choice_value_with_m_dep/expected_stdout4
-rw-r--r--scripts/kconfig/tests/conftest.py291
-rw-r--r--scripts/kconfig/tests/err_recursive_dep/Kconfig63
-rw-r--r--scripts/kconfig/tests/err_recursive_dep/__init__.py10
-rw-r--r--scripts/kconfig/tests/err_recursive_dep/expected_stderr38
-rw-r--r--scripts/kconfig/tests/err_recursive_inc/Kconfig2
-rw-r--r--scripts/kconfig/tests/err_recursive_inc/Kconfig.inc15
-rw-r--r--scripts/kconfig/tests/err_recursive_inc/Kconfig.inc24
-rw-r--r--scripts/kconfig/tests/err_recursive_inc/Kconfig.inc32
-rw-r--r--scripts/kconfig/tests/err_recursive_inc/__init__.py11
-rw-r--r--scripts/kconfig/tests/err_recursive_inc/expected_stderr6
-rw-r--r--scripts/kconfig/tests/inter_choice/Kconfig25
-rw-r--r--scripts/kconfig/tests/inter_choice/__init__.py15
-rw-r--r--scripts/kconfig/tests/inter_choice/defconfig1
-rw-r--r--scripts/kconfig/tests/inter_choice/expected_config4
-rw-r--r--scripts/kconfig/tests/new_choice_with_dep/Kconfig39
-rw-r--r--scripts/kconfig/tests/new_choice_with_dep/__init__.py15
-rw-r--r--scripts/kconfig/tests/new_choice_with_dep/config3
-rw-r--r--scripts/kconfig/tests/new_choice_with_dep/expected_stdout10
-rw-r--r--scripts/kconfig/tests/no_write_if_dep_unmet/Kconfig16
-rw-r--r--scripts/kconfig/tests/no_write_if_dep_unmet/__init__.py20
-rw-r--r--scripts/kconfig/tests/no_write_if_dep_unmet/config1
-rw-r--r--scripts/kconfig/tests/no_write_if_dep_unmet/expected_config5
-rw-r--r--scripts/kconfig/tests/preprocess/builtin_func/Kconfig27
-rw-r--r--scripts/kconfig/tests/preprocess/builtin_func/__init__.py9
-rw-r--r--scripts/kconfig/tests/preprocess/builtin_func/expected_stderr5
-rw-r--r--scripts/kconfig/tests/preprocess/builtin_func/expected_stdout1
-rw-r--r--scripts/kconfig/tests/preprocess/circular_expansion/Kconfig5
-rw-r--r--scripts/kconfig/tests/preprocess/circular_expansion/__init__.py11
-rw-r--r--scripts/kconfig/tests/preprocess/circular_expansion/expected_stderr1
-rw-r--r--scripts/kconfig/tests/preprocess/escape/Kconfig44
-rw-r--r--scripts/kconfig/tests/preprocess/escape/__init__.py8
-rw-r--r--scripts/kconfig/tests/preprocess/escape/expected_stderr10
-rw-r--r--scripts/kconfig/tests/preprocess/variable/Kconfig53
-rw-r--r--scripts/kconfig/tests/preprocess/variable/__init__.py8
-rw-r--r--scripts/kconfig/tests/preprocess/variable/expected_stderr9
-rw-r--r--scripts/kconfig/tests/pytest.ini7
53 files changed, 1057 insertions, 0 deletions
diff --git a/scripts/kconfig/tests/auto_submenu/Kconfig b/scripts/kconfig/tests/auto_submenu/Kconfig
new file mode 100644
index 000000000..b20761edc
--- /dev/null
+++ b/scripts/kconfig/tests/auto_submenu/Kconfig
@@ -0,0 +1,52 @@
+# SPDX-License-Identifier: GPL-2.0
+
+config A
+ bool "A"
+ default y
+
+config A0
+ bool "A0"
+ depends on A
+ default y
+ help
+ This depends on A, so should be a submenu of A.
+
+config A0_0
+ bool "A1_0"
+ depends on A0
+ help
+ Submenus are created recursively.
+ This should be a submenu of A0.
+
+config A1
+ bool "A1"
+ depends on A
+ default y
+ help
+ This should line up with A0.
+
+choice
+ prompt "choice"
+ depends on A1
+ help
+ Choice should become a submenu as well.
+
+config A1_0
+ bool "A1_0"
+
+config A1_1
+ bool "A1_1"
+
+endchoice
+
+config B
+ bool "B"
+ help
+ This is independent of A.
+
+config C
+ bool "C"
+ depends on A
+ help
+ This depends on A, but not a consecutive item, so can/should not
+ be a submenu.
diff --git a/scripts/kconfig/tests/auto_submenu/__init__.py b/scripts/kconfig/tests/auto_submenu/__init__.py
new file mode 100644
index 000000000..25abd927f
--- /dev/null
+++ b/scripts/kconfig/tests/auto_submenu/__init__.py
@@ -0,0 +1,13 @@
+# SPDX-License-Identifier: GPL-2.0
+"""
+Create submenu for symbols that depend on the preceding one.
+
+If a symbols has dependency on the preceding symbol, the menu entry
+should become the submenu of the preceding one, and displayed with
+deeper indentation.
+"""
+
+
+def test(conf):
+ assert conf.oldaskconfig() == 0
+ assert conf.stdout_contains('expected_stdout')
diff --git a/scripts/kconfig/tests/auto_submenu/expected_stdout b/scripts/kconfig/tests/auto_submenu/expected_stdout
new file mode 100644
index 000000000..bf5236f39
--- /dev/null
+++ b/scripts/kconfig/tests/auto_submenu/expected_stdout
@@ -0,0 +1,10 @@
+A (A) [Y/n/?] (NEW)
+ A0 (A0) [Y/n/?] (NEW)
+ A1_0 (A0_0) [N/y/?] (NEW)
+ A1 (A1) [Y/n/?] (NEW)
+ choice
+ > 1. A1_0 (A1_0) (NEW)
+ 2. A1_1 (A1_1) (NEW)
+ choice[1-2?]:
+B (B) [N/y/?] (NEW)
+C (C) [N/y/?] (NEW)
diff --git a/scripts/kconfig/tests/choice/Kconfig b/scripts/kconfig/tests/choice/Kconfig
new file mode 100644
index 000000000..a412205b1
--- /dev/null
+++ b/scripts/kconfig/tests/choice/Kconfig
@@ -0,0 +1,56 @@
+# SPDX-License-Identifier: GPL-2.0
+
+config MODULES
+ bool "Enable loadable module support"
+ option modules
+ default y
+
+choice
+ prompt "boolean choice"
+ default BOOL_CHOICE1
+
+config BOOL_CHOICE0
+ bool "choice 0"
+
+config BOOL_CHOICE1
+ bool "choice 1"
+
+endchoice
+
+choice
+ prompt "optional boolean choice"
+ optional
+ default OPT_BOOL_CHOICE1
+
+config OPT_BOOL_CHOICE0
+ bool "choice 0"
+
+config OPT_BOOL_CHOICE1
+ bool "choice 1"
+
+endchoice
+
+choice
+ prompt "tristate choice"
+ default TRI_CHOICE1
+
+config TRI_CHOICE0
+ tristate "choice 0"
+
+config TRI_CHOICE1
+ tristate "choice 1"
+
+endchoice
+
+choice
+ prompt "optional tristate choice"
+ optional
+ default OPT_TRI_CHOICE1
+
+config OPT_TRI_CHOICE0
+ tristate "choice 0"
+
+config OPT_TRI_CHOICE1
+ tristate "choice 1"
+
+endchoice
diff --git a/scripts/kconfig/tests/choice/__init__.py b/scripts/kconfig/tests/choice/__init__.py
new file mode 100644
index 000000000..4318fce05
--- /dev/null
+++ b/scripts/kconfig/tests/choice/__init__.py
@@ -0,0 +1,41 @@
+# SPDX-License-Identifier: GPL-2.0
+"""
+Basic choice tests.
+
+The handling of 'choice' is a bit complicated part in Kconfig.
+
+The behavior of 'y' choice is intuitive. If choice values are tristate,
+the choice can be 'm' where each value can be enabled independently.
+Also, if a choice is marked as 'optional', the whole choice can be
+invisible.
+"""
+
+
+def test_oldask0(conf):
+ assert conf.oldaskconfig() == 0
+ assert conf.stdout_contains('oldask0_expected_stdout')
+
+
+def test_oldask1(conf):
+ assert conf.oldaskconfig('oldask1_config') == 0
+ assert conf.stdout_contains('oldask1_expected_stdout')
+
+
+def test_allyes(conf):
+ assert conf.allyesconfig() == 0
+ assert conf.config_contains('allyes_expected_config')
+
+
+def test_allmod(conf):
+ assert conf.allmodconfig() == 0
+ assert conf.config_contains('allmod_expected_config')
+
+
+def test_allno(conf):
+ assert conf.allnoconfig() == 0
+ assert conf.config_contains('allno_expected_config')
+
+
+def test_alldef(conf):
+ assert conf.alldefconfig() == 0
+ assert conf.config_contains('alldef_expected_config')
diff --git a/scripts/kconfig/tests/choice/alldef_expected_config b/scripts/kconfig/tests/choice/alldef_expected_config
new file mode 100644
index 000000000..7a754bf4b
--- /dev/null
+++ b/scripts/kconfig/tests/choice/alldef_expected_config
@@ -0,0 +1,5 @@
+CONFIG_MODULES=y
+# CONFIG_BOOL_CHOICE0 is not set
+CONFIG_BOOL_CHOICE1=y
+# CONFIG_TRI_CHOICE0 is not set
+# CONFIG_TRI_CHOICE1 is not set
diff --git a/scripts/kconfig/tests/choice/allmod_expected_config b/scripts/kconfig/tests/choice/allmod_expected_config
new file mode 100644
index 000000000..f1f5dcdb7
--- /dev/null
+++ b/scripts/kconfig/tests/choice/allmod_expected_config
@@ -0,0 +1,9 @@
+CONFIG_MODULES=y
+# CONFIG_BOOL_CHOICE0 is not set
+CONFIG_BOOL_CHOICE1=y
+# CONFIG_OPT_BOOL_CHOICE0 is not set
+CONFIG_OPT_BOOL_CHOICE1=y
+CONFIG_TRI_CHOICE0=m
+CONFIG_TRI_CHOICE1=m
+CONFIG_OPT_TRI_CHOICE0=m
+CONFIG_OPT_TRI_CHOICE1=m
diff --git a/scripts/kconfig/tests/choice/allno_expected_config b/scripts/kconfig/tests/choice/allno_expected_config
new file mode 100644
index 000000000..b88ee7a43
--- /dev/null
+++ b/scripts/kconfig/tests/choice/allno_expected_config
@@ -0,0 +1,5 @@
+# CONFIG_MODULES is not set
+# CONFIG_BOOL_CHOICE0 is not set
+CONFIG_BOOL_CHOICE1=y
+# CONFIG_TRI_CHOICE0 is not set
+CONFIG_TRI_CHOICE1=y
diff --git a/scripts/kconfig/tests/choice/allyes_expected_config b/scripts/kconfig/tests/choice/allyes_expected_config
new file mode 100644
index 000000000..e5a062a11
--- /dev/null
+++ b/scripts/kconfig/tests/choice/allyes_expected_config
@@ -0,0 +1,9 @@
+CONFIG_MODULES=y
+# CONFIG_BOOL_CHOICE0 is not set
+CONFIG_BOOL_CHOICE1=y
+# CONFIG_OPT_BOOL_CHOICE0 is not set
+CONFIG_OPT_BOOL_CHOICE1=y
+# CONFIG_TRI_CHOICE0 is not set
+CONFIG_TRI_CHOICE1=y
+# CONFIG_OPT_TRI_CHOICE0 is not set
+CONFIG_OPT_TRI_CHOICE1=y
diff --git a/scripts/kconfig/tests/choice/oldask0_expected_stdout b/scripts/kconfig/tests/choice/oldask0_expected_stdout
new file mode 100644
index 000000000..b251bba96
--- /dev/null
+++ b/scripts/kconfig/tests/choice/oldask0_expected_stdout
@@ -0,0 +1,10 @@
+Enable loadable module support (MODULES) [Y/n/?] (NEW)
+boolean choice
+ 1. choice 0 (BOOL_CHOICE0) (NEW)
+> 2. choice 1 (BOOL_CHOICE1) (NEW)
+choice[1-2?]:
+optional boolean choice [N/y/?] (NEW)
+tristate choice [M/y/?] (NEW)
+ choice 0 (TRI_CHOICE0) [N/m/?] (NEW)
+ choice 1 (TRI_CHOICE1) [N/m/?] (NEW)
+optional tristate choice [N/m/y/?] (NEW)
diff --git a/scripts/kconfig/tests/choice/oldask1_config b/scripts/kconfig/tests/choice/oldask1_config
new file mode 100644
index 000000000..b67bfe3c6
--- /dev/null
+++ b/scripts/kconfig/tests/choice/oldask1_config
@@ -0,0 +1,2 @@
+# CONFIG_MODULES is not set
+CONFIG_OPT_BOOL_CHOICE0=y
diff --git a/scripts/kconfig/tests/choice/oldask1_expected_stdout b/scripts/kconfig/tests/choice/oldask1_expected_stdout
new file mode 100644
index 000000000..c2125e9bf
--- /dev/null
+++ b/scripts/kconfig/tests/choice/oldask1_expected_stdout
@@ -0,0 +1,15 @@
+Enable loadable module support (MODULES) [N/y/?]
+boolean choice
+ 1. choice 0 (BOOL_CHOICE0) (NEW)
+> 2. choice 1 (BOOL_CHOICE1) (NEW)
+choice[1-2?]:
+optional boolean choice [Y/n/?] (NEW)
+optional boolean choice
+> 1. choice 0 (OPT_BOOL_CHOICE0)
+ 2. choice 1 (OPT_BOOL_CHOICE1) (NEW)
+choice[1-2?]:
+tristate choice
+ 1. choice 0 (TRI_CHOICE0) (NEW)
+> 2. choice 1 (TRI_CHOICE1) (NEW)
+choice[1-2?]:
+optional tristate choice [N/y/?]
diff --git a/scripts/kconfig/tests/choice_value_with_m_dep/Kconfig b/scripts/kconfig/tests/choice_value_with_m_dep/Kconfig
new file mode 100644
index 000000000..7106c26bb
--- /dev/null
+++ b/scripts/kconfig/tests/choice_value_with_m_dep/Kconfig
@@ -0,0 +1,21 @@
+# SPDX-License-Identifier: GPL-2.0
+
+config MODULES
+ def_bool y
+ option modules
+
+config DEP
+ tristate
+ default m
+
+choice
+ prompt "Tristate Choice"
+
+config CHOICE0
+ tristate "Choice 0"
+
+config CHOICE1
+ tristate "Choice 1"
+ depends on DEP
+
+endchoice
diff --git a/scripts/kconfig/tests/choice_value_with_m_dep/__init__.py b/scripts/kconfig/tests/choice_value_with_m_dep/__init__.py
new file mode 100644
index 000000000..075b4e086
--- /dev/null
+++ b/scripts/kconfig/tests/choice_value_with_m_dep/__init__.py
@@ -0,0 +1,16 @@
+# SPDX-License-Identifier: GPL-2.0
+"""
+Hide tristate choice values with mod dependency in y choice.
+
+If tristate choice values depend on symbols set to 'm', they should be
+hidden when the choice containing them is changed from 'm' to 'y'
+(i.e. exclusive choice).
+
+Related Linux commit: fa64e5f6a35efd5e77d639125d973077ca506074
+"""
+
+
+def test(conf):
+ assert conf.oldaskconfig('config', 'y') == 0
+ assert conf.config_contains('expected_config')
+ assert conf.stdout_contains('expected_stdout')
diff --git a/scripts/kconfig/tests/choice_value_with_m_dep/config b/scripts/kconfig/tests/choice_value_with_m_dep/config
new file mode 100644
index 000000000..3a126b7a2
--- /dev/null
+++ b/scripts/kconfig/tests/choice_value_with_m_dep/config
@@ -0,0 +1,2 @@
+CONFIG_CHOICE0=m
+CONFIG_CHOICE1=m
diff --git a/scripts/kconfig/tests/choice_value_with_m_dep/expected_config b/scripts/kconfig/tests/choice_value_with_m_dep/expected_config
new file mode 100644
index 000000000..4d07b4495
--- /dev/null
+++ b/scripts/kconfig/tests/choice_value_with_m_dep/expected_config
@@ -0,0 +1,3 @@
+CONFIG_MODULES=y
+CONFIG_DEP=m
+CONFIG_CHOICE0=y
diff --git a/scripts/kconfig/tests/choice_value_with_m_dep/expected_stdout b/scripts/kconfig/tests/choice_value_with_m_dep/expected_stdout
new file mode 100644
index 000000000..2b50ab65c
--- /dev/null
+++ b/scripts/kconfig/tests/choice_value_with_m_dep/expected_stdout
@@ -0,0 +1,4 @@
+Tristate Choice [M/y/?] y
+Tristate Choice
+> 1. Choice 0 (CHOICE0)
+choice[1]: 1
diff --git a/scripts/kconfig/tests/conftest.py b/scripts/kconfig/tests/conftest.py
new file mode 100644
index 000000000..0345ef6e3
--- /dev/null
+++ b/scripts/kconfig/tests/conftest.py
@@ -0,0 +1,291 @@
+# SPDX-License-Identifier: GPL-2.0
+#
+# Copyright (C) 2018 Masahiro Yamada <yamada.masahiro@socionext.com>
+#
+
+"""
+Kconfig unit testing framework.
+
+This provides fixture functions commonly used from test files.
+"""
+
+import os
+import pytest
+import shutil
+import subprocess
+import tempfile
+
+CONF_PATH = os.path.abspath(os.path.join('scripts', 'kconfig', 'conf'))
+
+
+class Conf:
+ """Kconfig runner and result checker.
+
+ This class provides methods to run text-based interface of Kconfig
+ (scripts/kconfig/conf) and retrieve the resulted configuration,
+ stdout, and stderr. It also provides methods to compare those
+ results with expectations.
+ """
+
+ def __init__(self, request):
+ """Create a new Conf instance.
+
+ request: object to introspect the requesting test module
+ """
+ # the directory of the test being run
+ self._test_dir = os.path.dirname(str(request.fspath))
+
+ # runners
+ def _run_conf(self, mode, dot_config=None, out_file='.config',
+ interactive=False, in_keys=None, extra_env={}):
+ """Run text-based Kconfig executable and save the result.
+
+ mode: input mode option (--oldaskconfig, --defconfig=<file> etc.)
+ dot_config: .config file to use for configuration base
+ out_file: file name to contain the output config data
+ interactive: flag to specify the interactive mode
+ in_keys: key inputs for interactive modes
+ extra_env: additional environments
+ returncode: exit status of the Kconfig executable
+ """
+ command = [CONF_PATH, mode, 'Kconfig']
+
+ # Override 'srctree' environment to make the test as the top directory
+ extra_env['srctree'] = self._test_dir
+
+ # Run Kconfig in a temporary directory.
+ # This directory is automatically removed when done.
+ with tempfile.TemporaryDirectory() as temp_dir:
+
+ # if .config is given, copy it to the working directory
+ if dot_config:
+ shutil.copyfile(os.path.join(self._test_dir, dot_config),
+ os.path.join(temp_dir, '.config'))
+
+ ps = subprocess.Popen(command,
+ stdin=subprocess.PIPE,
+ stdout=subprocess.PIPE,
+ stderr=subprocess.PIPE,
+ cwd=temp_dir,
+ env=dict(os.environ, **extra_env))
+
+ # If input key sequence is given, feed it to stdin.
+ if in_keys:
+ ps.stdin.write(in_keys.encode('utf-8'))
+
+ while ps.poll() is None:
+ # For interactive modes such as oldaskconfig, oldconfig,
+ # send 'Enter' key until the program finishes.
+ if interactive:
+ ps.stdin.write(b'\n')
+
+ self.retcode = ps.returncode
+ self.stdout = ps.stdout.read().decode()
+ self.stderr = ps.stderr.read().decode()
+
+ # Retrieve the resulted config data only when .config is supposed
+ # to exist. If the command fails, the .config does not exist.
+ # 'listnewconfig' does not produce .config in the first place.
+ if self.retcode == 0 and out_file:
+ with open(os.path.join(temp_dir, out_file)) as f:
+ self.config = f.read()
+ else:
+ self.config = None
+
+ # Logging:
+ # Pytest captures the following information by default. In failure
+ # of tests, the captured log will be displayed. This will be useful to
+ # figure out what has happened.
+
+ print("[command]\n{}\n".format(' '.join(command)))
+
+ print("[retcode]\n{}\n".format(self.retcode))
+
+ print("[stdout]")
+ print(self.stdout)
+
+ print("[stderr]")
+ print(self.stderr)
+
+ if self.config is not None:
+ print("[output for '{}']".format(out_file))
+ print(self.config)
+
+ return self.retcode
+
+ def oldaskconfig(self, dot_config=None, in_keys=None):
+ """Run oldaskconfig.
+
+ dot_config: .config file to use for configuration base (optional)
+ in_key: key inputs (optional)
+ returncode: exit status of the Kconfig executable
+ """
+ return self._run_conf('--oldaskconfig', dot_config=dot_config,
+ interactive=True, in_keys=in_keys)
+
+ def oldconfig(self, dot_config=None, in_keys=None):
+ """Run oldconfig.
+
+ dot_config: .config file to use for configuration base (optional)
+ in_key: key inputs (optional)
+ returncode: exit status of the Kconfig executable
+ """
+ return self._run_conf('--oldconfig', dot_config=dot_config,
+ interactive=True, in_keys=in_keys)
+
+ def olddefconfig(self, dot_config=None):
+ """Run olddefconfig.
+
+ dot_config: .config file to use for configuration base (optional)
+ returncode: exit status of the Kconfig executable
+ """
+ return self._run_conf('--olddefconfig', dot_config=dot_config)
+
+ def defconfig(self, defconfig):
+ """Run defconfig.
+
+ defconfig: defconfig file for input
+ returncode: exit status of the Kconfig executable
+ """
+ defconfig_path = os.path.join(self._test_dir, defconfig)
+ return self._run_conf('--defconfig={}'.format(defconfig_path))
+
+ def _allconfig(self, mode, all_config):
+ if all_config:
+ all_config_path = os.path.join(self._test_dir, all_config)
+ extra_env = {'KCONFIG_ALLCONFIG': all_config_path}
+ else:
+ extra_env = {}
+
+ return self._run_conf('--{}config'.format(mode), extra_env=extra_env)
+
+ def allyesconfig(self, all_config=None):
+ """Run allyesconfig.
+
+ all_config: fragment config file for KCONFIG_ALLCONFIG (optional)
+ returncode: exit status of the Kconfig executable
+ """
+ return self._allconfig('allyes', all_config)
+
+ def allmodconfig(self, all_config=None):
+ """Run allmodconfig.
+
+ all_config: fragment config file for KCONFIG_ALLCONFIG (optional)
+ returncode: exit status of the Kconfig executable
+ """
+ return self._allconfig('allmod', all_config)
+
+ def allnoconfig(self, all_config=None):
+ """Run allnoconfig.
+
+ all_config: fragment config file for KCONFIG_ALLCONFIG (optional)
+ returncode: exit status of the Kconfig executable
+ """
+ return self._allconfig('allno', all_config)
+
+ def alldefconfig(self, all_config=None):
+ """Run alldefconfig.
+
+ all_config: fragment config file for KCONFIG_ALLCONFIG (optional)
+ returncode: exit status of the Kconfig executable
+ """
+ return self._allconfig('alldef', all_config)
+
+ def randconfig(self, all_config=None):
+ """Run randconfig.
+
+ all_config: fragment config file for KCONFIG_ALLCONFIG (optional)
+ returncode: exit status of the Kconfig executable
+ """
+ return self._allconfig('rand', all_config)
+
+ def savedefconfig(self, dot_config):
+ """Run savedefconfig.
+
+ dot_config: .config file for input
+ returncode: exit status of the Kconfig executable
+ """
+ return self._run_conf('--savedefconfig', out_file='defconfig')
+
+ def listnewconfig(self, dot_config=None):
+ """Run listnewconfig.
+
+ dot_config: .config file to use for configuration base (optional)
+ returncode: exit status of the Kconfig executable
+ """
+ return self._run_conf('--listnewconfig', dot_config=dot_config,
+ out_file=None)
+
+ # checkers
+ def _read_and_compare(self, compare, expected):
+ """Compare the result with expectation.
+
+ compare: function to compare the result with expectation
+ expected: file that contains the expected data
+ """
+ with open(os.path.join(self._test_dir, expected)) as f:
+ expected_data = f.read()
+ return compare(self, expected_data)
+
+ def _contains(self, attr, expected):
+ return self._read_and_compare(
+ lambda s, e: getattr(s, attr).find(e) >= 0,
+ expected)
+
+ def _matches(self, attr, expected):
+ return self._read_and_compare(lambda s, e: getattr(s, attr) == e,
+ expected)
+
+ def config_contains(self, expected):
+ """Check if resulted configuration contains expected data.
+
+ expected: file that contains the expected data
+ returncode: True if result contains the expected data, False otherwise
+ """
+ return self._contains('config', expected)
+
+ def config_matches(self, expected):
+ """Check if resulted configuration exactly matches expected data.
+
+ expected: file that contains the expected data
+ returncode: True if result matches the expected data, False otherwise
+ """
+ return self._matches('config', expected)
+
+ def stdout_contains(self, expected):
+ """Check if resulted stdout contains expected data.
+
+ expected: file that contains the expected data
+ returncode: True if result contains the expected data, False otherwise
+ """
+ return self._contains('stdout', expected)
+
+ def stdout_matches(self, expected):
+ """Check if resulted stdout exactly matches expected data.
+
+ expected: file that contains the expected data
+ returncode: True if result matches the expected data, False otherwise
+ """
+ return self._matches('stdout', expected)
+
+ def stderr_contains(self, expected):
+ """Check if resulted stderr contains expected data.
+
+ expected: file that contains the expected data
+ returncode: True if result contains the expected data, False otherwise
+ """
+ return self._contains('stderr', expected)
+
+ def stderr_matches(self, expected):
+ """Check if resulted stderr exactly matches expected data.
+
+ expected: file that contains the expected data
+ returncode: True if result matches the expected data, False otherwise
+ """
+ return self._matches('stderr', expected)
+
+
+@pytest.fixture(scope="module")
+def conf(request):
+ """Create a Conf instance and provide it to test functions."""
+ return Conf(request)
diff --git a/scripts/kconfig/tests/err_recursive_dep/Kconfig b/scripts/kconfig/tests/err_recursive_dep/Kconfig
new file mode 100644
index 000000000..ebdb3ffd8
--- /dev/null
+++ b/scripts/kconfig/tests/err_recursive_dep/Kconfig
@@ -0,0 +1,63 @@
+# SPDX-License-Identifier: GPL-2.0
+
+# depends on itself
+
+config A
+ bool "A"
+ depends on A
+
+# select itself
+
+config B
+ bool
+ select B
+
+# depends on each other
+
+config C1
+ bool "C1"
+ depends on C2
+
+config C2
+ bool "C2"
+ depends on C1
+
+# depends on and select
+
+config D1
+ bool "D1"
+ depends on D2
+ select D2
+
+config D2
+ bool
+
+# depends on and imply
+
+config E1
+ bool "E1"
+ depends on E2
+ imply E2
+
+config E2
+ bool "E2"
+
+# property
+
+config F1
+ bool "F1"
+ default F2
+
+config F2
+ bool "F2"
+ depends on F1
+
+# menu
+
+menu "menu depending on its content"
+ depends on G
+
+config G
+ bool "G"
+
+endmenu
diff --git a/scripts/kconfig/tests/err_recursive_dep/__init__.py b/scripts/kconfig/tests/err_recursive_dep/__init__.py
new file mode 100644
index 000000000..5f3821b43
--- /dev/null
+++ b/scripts/kconfig/tests/err_recursive_dep/__init__.py
@@ -0,0 +1,10 @@
+# SPDX-License-Identifier: GPL-2.0
+"""
+Detect recursive dependency error.
+
+Recursive dependency should be treated as an error.
+"""
+
+def test(conf):
+ assert conf.oldaskconfig() == 1
+ assert conf.stderr_contains('expected_stderr')
diff --git a/scripts/kconfig/tests/err_recursive_dep/expected_stderr b/scripts/kconfig/tests/err_recursive_dep/expected_stderr
new file mode 100644
index 000000000..c9f4abf9a
--- /dev/null
+++ b/scripts/kconfig/tests/err_recursive_dep/expected_stderr
@@ -0,0 +1,38 @@
+Kconfig:11:error: recursive dependency detected!
+Kconfig:11: symbol B is selected by B
+For a resolution refer to Documentation/kbuild/kconfig-language.rst
+subsection "Kconfig recursive dependency limitations"
+
+Kconfig:5:error: recursive dependency detected!
+Kconfig:5: symbol A depends on A
+For a resolution refer to Documentation/kbuild/kconfig-language.rst
+subsection "Kconfig recursive dependency limitations"
+
+Kconfig:17:error: recursive dependency detected!
+Kconfig:17: symbol C1 depends on C2
+Kconfig:21: symbol C2 depends on C1
+For a resolution refer to Documentation/kbuild/kconfig-language.rst
+subsection "Kconfig recursive dependency limitations"
+
+Kconfig:32:error: recursive dependency detected!
+Kconfig:32: symbol D2 is selected by D1
+Kconfig:27: symbol D1 depends on D2
+For a resolution refer to Documentation/kbuild/kconfig-language.rst
+subsection "Kconfig recursive dependency limitations"
+
+Kconfig:37:error: recursive dependency detected!
+Kconfig:37: symbol E1 depends on E2
+Kconfig:42: symbol E2 is implied by E1
+For a resolution refer to Documentation/kbuild/kconfig-language.rst
+subsection "Kconfig recursive dependency limitations"
+
+Kconfig:60:error: recursive dependency detected!
+Kconfig:60: symbol G depends on G
+For a resolution refer to Documentation/kbuild/kconfig-language.rst
+subsection "Kconfig recursive dependency limitations"
+
+Kconfig:51:error: recursive dependency detected!
+Kconfig:51: symbol F2 depends on F1
+Kconfig:49: symbol F1 default value contains F2
+For a resolution refer to Documentation/kbuild/kconfig-language.rst
+subsection "Kconfig recursive dependency limitations"
diff --git a/scripts/kconfig/tests/err_recursive_inc/Kconfig b/scripts/kconfig/tests/err_recursive_inc/Kconfig
new file mode 100644
index 000000000..c6f4adec7
--- /dev/null
+++ b/scripts/kconfig/tests/err_recursive_inc/Kconfig
@@ -0,0 +1,2 @@
+# SPDX-License-Identifier: GPL-2.0-only
+source "Kconfig.inc1"
diff --git a/scripts/kconfig/tests/err_recursive_inc/Kconfig.inc1 b/scripts/kconfig/tests/err_recursive_inc/Kconfig.inc1
new file mode 100644
index 000000000..01cbf0d69
--- /dev/null
+++ b/scripts/kconfig/tests/err_recursive_inc/Kconfig.inc1
@@ -0,0 +1,5 @@
+# SPDX-License-Identifier: GPL-2.0-only
+
+
+
+source "Kconfig.inc2"
diff --git a/scripts/kconfig/tests/err_recursive_inc/Kconfig.inc2 b/scripts/kconfig/tests/err_recursive_inc/Kconfig.inc2
new file mode 100644
index 000000000..82351075a
--- /dev/null
+++ b/scripts/kconfig/tests/err_recursive_inc/Kconfig.inc2
@@ -0,0 +1,4 @@
+# SPDX-License-Identifier: GPL-2.0-only
+
+
+source "Kconfig.inc3"
diff --git a/scripts/kconfig/tests/err_recursive_inc/Kconfig.inc3 b/scripts/kconfig/tests/err_recursive_inc/Kconfig.inc3
new file mode 100644
index 000000000..c6f4adec7
--- /dev/null
+++ b/scripts/kconfig/tests/err_recursive_inc/Kconfig.inc3
@@ -0,0 +1,2 @@
+# SPDX-License-Identifier: GPL-2.0-only
+source "Kconfig.inc1"
diff --git a/scripts/kconfig/tests/err_recursive_inc/__init__.py b/scripts/kconfig/tests/err_recursive_inc/__init__.py
new file mode 100644
index 000000000..27aa1895a
--- /dev/null
+++ b/scripts/kconfig/tests/err_recursive_inc/__init__.py
@@ -0,0 +1,11 @@
+# SPDX-License-Identifier: GPL-2.0
+"""
+Detect recursive inclusion error.
+
+If recursive inclusion is detected, it should fail with error messages.
+"""
+
+
+def test(conf):
+ assert conf.oldaskconfig() != 0
+ assert conf.stderr_contains('expected_stderr')
diff --git a/scripts/kconfig/tests/err_recursive_inc/expected_stderr b/scripts/kconfig/tests/err_recursive_inc/expected_stderr
new file mode 100644
index 000000000..b070a31fd
--- /dev/null
+++ b/scripts/kconfig/tests/err_recursive_inc/expected_stderr
@@ -0,0 +1,6 @@
+Recursive inclusion detected.
+Inclusion path:
+ current file : Kconfig.inc1
+ included from: Kconfig.inc3:2
+ included from: Kconfig.inc2:4
+ included from: Kconfig.inc1:5
diff --git a/scripts/kconfig/tests/inter_choice/Kconfig b/scripts/kconfig/tests/inter_choice/Kconfig
new file mode 100644
index 000000000..5698a4018
--- /dev/null
+++ b/scripts/kconfig/tests/inter_choice/Kconfig
@@ -0,0 +1,25 @@
+# SPDX-License-Identifier: GPL-2.0
+
+config MODULES
+ def_bool y
+ option modules
+
+choice
+ prompt "Choice"
+
+config CHOICE_VAL0
+ tristate "Choice 0"
+
+config CHOIVE_VAL1
+ tristate "Choice 1"
+
+endchoice
+
+choice
+ prompt "Another choice"
+ depends on CHOICE_VAL0
+
+config DUMMY
+ bool "dummy"
+
+endchoice
diff --git a/scripts/kconfig/tests/inter_choice/__init__.py b/scripts/kconfig/tests/inter_choice/__init__.py
new file mode 100644
index 000000000..ffea6b114
--- /dev/null
+++ b/scripts/kconfig/tests/inter_choice/__init__.py
@@ -0,0 +1,15 @@
+# SPDX-License-Identifier: GPL-2.0
+"""
+Do not affect user-assigned choice value by another choice.
+
+Handling of state flags for choices is complecated. In old days,
+the defconfig result of a choice could be affected by another choice
+if those choices interact by 'depends on', 'select', etc.
+
+Related Linux commit: fbe98bb9ed3dae23e320c6b113e35f129538d14a
+"""
+
+
+def test(conf):
+ assert conf.defconfig('defconfig') == 0
+ assert conf.config_contains('expected_config')
diff --git a/scripts/kconfig/tests/inter_choice/defconfig b/scripts/kconfig/tests/inter_choice/defconfig
new file mode 100644
index 000000000..162c4148e
--- /dev/null
+++ b/scripts/kconfig/tests/inter_choice/defconfig
@@ -0,0 +1 @@
+CONFIG_CHOICE_VAL0=y
diff --git a/scripts/kconfig/tests/inter_choice/expected_config b/scripts/kconfig/tests/inter_choice/expected_config
new file mode 100644
index 000000000..5dceefb05
--- /dev/null
+++ b/scripts/kconfig/tests/inter_choice/expected_config
@@ -0,0 +1,4 @@
+CONFIG_MODULES=y
+CONFIG_CHOICE_VAL0=y
+# CONFIG_CHOIVE_VAL1 is not set
+CONFIG_DUMMY=y
diff --git a/scripts/kconfig/tests/new_choice_with_dep/Kconfig b/scripts/kconfig/tests/new_choice_with_dep/Kconfig
new file mode 100644
index 000000000..127731c04
--- /dev/null
+++ b/scripts/kconfig/tests/new_choice_with_dep/Kconfig
@@ -0,0 +1,39 @@
+# SPDX-License-Identifier: GPL-2.0
+
+config A
+ bool "A"
+ help
+ This is a new symbol.
+
+choice
+ prompt "Choice ?"
+ depends on A
+ help
+ "depends on A" has been newly added.
+
+config CHOICE_B
+ bool "Choice B"
+
+config CHOICE_C
+ bool "Choice C"
+ help
+ This is a new symbol, so should be asked.
+
+endchoice
+
+choice
+ prompt "Choice2 ?"
+
+config CHOICE_D
+ bool "Choice D"
+
+config CHOICE_E
+ bool "Choice E"
+
+config CHOICE_F
+ bool "Choice F"
+ depends on A
+ help
+ This is a new symbol, so should be asked.
+
+endchoice
diff --git a/scripts/kconfig/tests/new_choice_with_dep/__init__.py b/scripts/kconfig/tests/new_choice_with_dep/__init__.py
new file mode 100644
index 000000000..fe9d32253
--- /dev/null
+++ b/scripts/kconfig/tests/new_choice_with_dep/__init__.py
@@ -0,0 +1,15 @@
+# SPDX-License-Identifier: GPL-2.0
+"""
+Ask new choice values when they become visible.
+
+If new choice values are added with new dependency, and they become
+visible during user configuration, oldconfig should recognize them
+as (NEW), and ask the user for choice.
+
+Related Linux commit: 5d09598d488f081e3be23f885ed65cbbe2d073b5
+"""
+
+
+def test(conf):
+ assert conf.oldconfig('config', 'y') == 0
+ assert conf.stdout_contains('expected_stdout')
diff --git a/scripts/kconfig/tests/new_choice_with_dep/config b/scripts/kconfig/tests/new_choice_with_dep/config
new file mode 100644
index 000000000..47ef95d56
--- /dev/null
+++ b/scripts/kconfig/tests/new_choice_with_dep/config
@@ -0,0 +1,3 @@
+CONFIG_CHOICE_B=y
+# CONFIG_CHOICE_D is not set
+CONFIG_CHOICE_E=y
diff --git a/scripts/kconfig/tests/new_choice_with_dep/expected_stdout b/scripts/kconfig/tests/new_choice_with_dep/expected_stdout
new file mode 100644
index 000000000..74dc0bcb2
--- /dev/null
+++ b/scripts/kconfig/tests/new_choice_with_dep/expected_stdout
@@ -0,0 +1,10 @@
+A (A) [N/y/?] (NEW) y
+ Choice ?
+ > 1. Choice B (CHOICE_B)
+ 2. Choice C (CHOICE_C) (NEW)
+ choice[1-2?]:
+Choice2 ?
+ 1. Choice D (CHOICE_D)
+> 2. Choice E (CHOICE_E)
+ 3. Choice F (CHOICE_F) (NEW)
+choice[1-3?]:
diff --git a/scripts/kconfig/tests/no_write_if_dep_unmet/Kconfig b/scripts/kconfig/tests/no_write_if_dep_unmet/Kconfig
new file mode 100644
index 000000000..4767aab6d
--- /dev/null
+++ b/scripts/kconfig/tests/no_write_if_dep_unmet/Kconfig
@@ -0,0 +1,16 @@
+# SPDX-License-Identifier: GPL-2.0
+
+config A
+ bool "A"
+
+choice
+ prompt "Choice ?"
+ depends on A
+
+config CHOICE_B
+ bool "Choice B"
+
+config CHOICE_C
+ bool "Choice C"
+
+endchoice
diff --git a/scripts/kconfig/tests/no_write_if_dep_unmet/__init__.py b/scripts/kconfig/tests/no_write_if_dep_unmet/__init__.py
new file mode 100644
index 000000000..ffd469d1f
--- /dev/null
+++ b/scripts/kconfig/tests/no_write_if_dep_unmet/__init__.py
@@ -0,0 +1,20 @@
+# SPDX-License-Identifier: GPL-2.0
+"""
+Do not write choice values to .config if the dependency is unmet.
+
+"# CONFIG_... is not set" should not be written into the .config file
+for symbols with unmet dependency.
+
+This was not working correctly for choice values because choice needs
+a bit different symbol computation.
+
+This checks that no unneeded "# COFIG_... is not set" is contained in
+the .config file.
+
+Related Linux commit: cb67ab2cd2b8abd9650292c986c79901e3073a59
+"""
+
+
+def test(conf):
+ assert conf.oldaskconfig('config', 'n') == 0
+ assert conf.config_matches('expected_config')
diff --git a/scripts/kconfig/tests/no_write_if_dep_unmet/config b/scripts/kconfig/tests/no_write_if_dep_unmet/config
new file mode 100644
index 000000000..abd280e2f
--- /dev/null
+++ b/scripts/kconfig/tests/no_write_if_dep_unmet/config
@@ -0,0 +1 @@
+CONFIG_A=y
diff --git a/scripts/kconfig/tests/no_write_if_dep_unmet/expected_config b/scripts/kconfig/tests/no_write_if_dep_unmet/expected_config
new file mode 100644
index 000000000..473228810
--- /dev/null
+++ b/scripts/kconfig/tests/no_write_if_dep_unmet/expected_config
@@ -0,0 +1,5 @@
+#
+# Automatically generated file; DO NOT EDIT.
+# Main menu
+#
+# CONFIG_A is not set
diff --git a/scripts/kconfig/tests/preprocess/builtin_func/Kconfig b/scripts/kconfig/tests/preprocess/builtin_func/Kconfig
new file mode 100644
index 000000000..baa328827
--- /dev/null
+++ b/scripts/kconfig/tests/preprocess/builtin_func/Kconfig
@@ -0,0 +1,27 @@
+# SPDX-License-Identifier: GPL-2.0
+
+# 'info' prints the argument to stdout.
+$(info,hello world 0)
+
+# 'warning-if', if the first argument is y, sends the second argument to stderr,
+# and the message is prefixed with the current file name and line number.
+$(warning-if,y,hello world 1)
+
+# 'error-if' is similar, but it terminates the parsing immediately.
+# The following is just no-op since the first argument is not y.
+$(error-if,n,this should not be printed)
+
+# Shorthand
+warning = $(warning-if,y,$(1))
+
+# 'shell' executes a command, and returns its stdout.
+$(warning,$(shell,echo hello world 3))
+
+# Every newline in the output is replaced with a space,
+# but any trailing newlines are deleted.
+$(warning,$(shell,printf 'hello\nworld\n\n4\n\n\n'))
+
+# 'filename' is expanded to the currently parsed file name,
+# 'lineno' to the line number.
+$(warning,filename=$(filename))
+$(warning,lineno=$(lineno))
diff --git a/scripts/kconfig/tests/preprocess/builtin_func/__init__.py b/scripts/kconfig/tests/preprocess/builtin_func/__init__.py
new file mode 100644
index 000000000..2e53ba08f
--- /dev/null
+++ b/scripts/kconfig/tests/preprocess/builtin_func/__init__.py
@@ -0,0 +1,9 @@
+# SPDX-License-Identifier: GPL-2.0
+"""
+Built-in function tests.
+"""
+
+def test(conf):
+ assert conf.oldaskconfig() == 0
+ assert conf.stdout_contains('expected_stdout')
+ assert conf.stderr_matches('expected_stderr')
diff --git a/scripts/kconfig/tests/preprocess/builtin_func/expected_stderr b/scripts/kconfig/tests/preprocess/builtin_func/expected_stderr
new file mode 100644
index 000000000..33ea9ca38
--- /dev/null
+++ b/scripts/kconfig/tests/preprocess/builtin_func/expected_stderr
@@ -0,0 +1,5 @@
+Kconfig:8: hello world 1
+Kconfig:18: hello world 3
+Kconfig:22: hello world 4
+Kconfig:26: filename=Kconfig
+Kconfig:27: lineno=27
diff --git a/scripts/kconfig/tests/preprocess/builtin_func/expected_stdout b/scripts/kconfig/tests/preprocess/builtin_func/expected_stdout
new file mode 100644
index 000000000..82de3a7e9
--- /dev/null
+++ b/scripts/kconfig/tests/preprocess/builtin_func/expected_stdout
@@ -0,0 +1 @@
+hello world 0
diff --git a/scripts/kconfig/tests/preprocess/circular_expansion/Kconfig b/scripts/kconfig/tests/preprocess/circular_expansion/Kconfig
new file mode 100644
index 000000000..6838997c2
--- /dev/null
+++ b/scripts/kconfig/tests/preprocess/circular_expansion/Kconfig
@@ -0,0 +1,5 @@
+# SPDX-License-Identifier: GPL-2.0
+
+X = $(Y)
+Y = $(X)
+$(info $(X))
diff --git a/scripts/kconfig/tests/preprocess/circular_expansion/__init__.py b/scripts/kconfig/tests/preprocess/circular_expansion/__init__.py
new file mode 100644
index 000000000..419bda3e0
--- /dev/null
+++ b/scripts/kconfig/tests/preprocess/circular_expansion/__init__.py
@@ -0,0 +1,11 @@
+# SPDX-License-Identifier: GPL-2.0
+"""
+Detect circular variable expansion.
+
+If a recursively expanded variable references itself (eventually),
+it should fail with an error message.
+"""
+
+def test(conf):
+ assert conf.oldaskconfig() != 0
+ assert conf.stderr_matches('expected_stderr')
diff --git a/scripts/kconfig/tests/preprocess/circular_expansion/expected_stderr b/scripts/kconfig/tests/preprocess/circular_expansion/expected_stderr
new file mode 100644
index 000000000..cde68fa98
--- /dev/null
+++ b/scripts/kconfig/tests/preprocess/circular_expansion/expected_stderr
@@ -0,0 +1 @@
+Kconfig:5: Recursive variable 'X' references itself (eventually)
diff --git a/scripts/kconfig/tests/preprocess/escape/Kconfig b/scripts/kconfig/tests/preprocess/escape/Kconfig
new file mode 100644
index 000000000..4e3f44445
--- /dev/null
+++ b/scripts/kconfig/tests/preprocess/escape/Kconfig
@@ -0,0 +1,44 @@
+# SPDX-License-Identifier: GPL-2.0
+
+# Shorthand
+warning = $(warning-if,y,$(1))
+
+# You can not pass commas directly to a function since they are treated as
+# delimiters. You can use the following trick to do so.
+comma := ,
+$(warning,hello$(comma) world)
+
+# Like Make, single quotes, double quotes, spaces are treated verbatim.
+# The following prints the text as-is.
+$(warning, ' " '" ' ''' "'")
+
+# Unlike Make, '$' has special meaning only when it is followed by '('.
+# No need to escape '$' itself.
+$(warning,$)
+$(warning,$$)
+$ := 1
+$(warning,$($))
+
+# You need a trick to escape '$' followed by '('
+# The following should print "$(X)". It should not be expanded further.
+dollar := $
+$(warning,$(dollar)(X))
+
+# You need a trick to treat unbalanced parentheses.
+# The following should print "(".
+left_paren := (
+$(warning,$(left_paren))
+
+# A simple expanded should not be expanded multiple times.
+# The following should print "$(X)". It should not be expanded further.
+Y := $(dollar)(X)
+$(warning,$(Y))
+
+# The following should print "$(X)" as well.
+Y = $(dollar)(X)
+$(warning,$(Y))
+
+# The following should print "$(".
+# It should not be emit "unterminated reference" error.
+unterminated := $(dollar)(
+$(warning,$(unterminated))
diff --git a/scripts/kconfig/tests/preprocess/escape/__init__.py b/scripts/kconfig/tests/preprocess/escape/__init__.py
new file mode 100644
index 000000000..7ee8e747f
--- /dev/null
+++ b/scripts/kconfig/tests/preprocess/escape/__init__.py
@@ -0,0 +1,8 @@
+# SPDX-License-Identifier: GPL-2.0
+"""
+Escape sequence tests.
+"""
+
+def test(conf):
+ assert conf.oldaskconfig() == 0
+ assert conf.stderr_matches('expected_stderr')
diff --git a/scripts/kconfig/tests/preprocess/escape/expected_stderr b/scripts/kconfig/tests/preprocess/escape/expected_stderr
new file mode 100644
index 000000000..1c00957dd
--- /dev/null
+++ b/scripts/kconfig/tests/preprocess/escape/expected_stderr
@@ -0,0 +1,10 @@
+Kconfig:9: hello, world
+Kconfig:13: ' " '" ' ''' "'"
+Kconfig:17: $
+Kconfig:18: $$
+Kconfig:20: 1
+Kconfig:25: $(X)
+Kconfig:30: (
+Kconfig:35: $(X)
+Kconfig:39: $(X)
+Kconfig:44: $(
diff --git a/scripts/kconfig/tests/preprocess/variable/Kconfig b/scripts/kconfig/tests/preprocess/variable/Kconfig
new file mode 100644
index 000000000..9ce2f95cb
--- /dev/null
+++ b/scripts/kconfig/tests/preprocess/variable/Kconfig
@@ -0,0 +1,53 @@
+# SPDX-License-Identifier: GPL-2.0
+
+# Shorthand
+warning = $(warning-if,y,$(1))
+
+# Simply expanded variable.
+X := 1
+SIMPLE := $(X)
+X := 2
+$(warning,SIMPLE = $(SIMPLE))
+
+# Recursively expanded variable.
+X := 1
+RECURSIVE = $(X)
+X := 2
+$(warning,RECURSIVE = $(RECURSIVE))
+
+# Append something to a simply expanded variable.
+Y := 3
+SIMPLE += $(Y)
+Y := 4
+$(warning,SIMPLE = $(SIMPLE))
+
+# Append something to a recursively expanded variable.
+Y := 3
+RECURSIVE += $(Y)
+Y := 4
+$(warning,RECURSIVE = $(RECURSIVE))
+
+# Use += operator to an undefined variable.
+# This works as a recursively expanded variable.
+Y := 3
+UNDEFINED_VARIABLE += $(Y)
+Y := 4
+$(warning,UNDEFINED_VARIABLE = $(UNDEFINED_VARIABLE))
+
+# You can use variable references for the lefthand side of assignment statement.
+X := A
+Y := B
+$(X)$(Y) := 5
+$(warning,AB = $(AB))
+
+# User-defined function.
+greeting = $(1), my name is $(2).
+$(warning,$(greeting,Hello,John))
+
+# The number of arguments is not checked for user-defined functions.
+# If some arguments are optional, it is useful to pass fewer parameters.
+# $(2) will be blank in this case.
+$(warning,$(greeting,Hello))
+
+# Unreferenced parameters are just ignored.
+$(warning,$(greeting,Hello,John,ignored,ignored))
diff --git a/scripts/kconfig/tests/preprocess/variable/__init__.py b/scripts/kconfig/tests/preprocess/variable/__init__.py
new file mode 100644
index 000000000..e88b1708d
--- /dev/null
+++ b/scripts/kconfig/tests/preprocess/variable/__init__.py
@@ -0,0 +1,8 @@
+# SPDX-License-Identifier: GPL-2.0
+"""
+Variable and user-defined function tests.
+"""
+
+def test(conf):
+ assert conf.oldaskconfig() == 0
+ assert conf.stderr_matches('expected_stderr')
diff --git a/scripts/kconfig/tests/preprocess/variable/expected_stderr b/scripts/kconfig/tests/preprocess/variable/expected_stderr
new file mode 100644
index 000000000..a4841c3fd
--- /dev/null
+++ b/scripts/kconfig/tests/preprocess/variable/expected_stderr
@@ -0,0 +1,9 @@
+Kconfig:10: SIMPLE = 1
+Kconfig:16: RECURSIVE = 2
+Kconfig:22: SIMPLE = 1 3
+Kconfig:28: RECURSIVE = 2 4
+Kconfig:35: UNDEFINED_VARIABLE = 4
+Kconfig:41: AB = 5
+Kconfig:45: Hello, my name is John.
+Kconfig:50: Hello, my name is .
+Kconfig:53: Hello, my name is John.
diff --git a/scripts/kconfig/tests/pytest.ini b/scripts/kconfig/tests/pytest.ini
new file mode 100644
index 000000000..85d7ce8e4
--- /dev/null
+++ b/scripts/kconfig/tests/pytest.ini
@@ -0,0 +1,7 @@
+[pytest]
+addopts = --verbose
+
+# Pytest requires that test files have unique names, because pytest imports
+# them as top-level modules. It is silly to prefix or suffix a test file with
+# the directory name that contains it. Use __init__.py for all test files.
+python_files = __init__.py