Ver Fonte

Merge tag 'kconfig-v4.17' of git://git.kernel.org/pub/scm/linux/kernel/git/masahiroy/linux-kbuild

Pull Kconfig updates from Masahiro Yamada:

 - improve checkpatch for more precise Kconfig code checking

 - clarify effective selects by grouping reverse dependencies in help

 - do not write out '# CONFIG_FOO is not set' from invisible symbols

 - make oldconfig as silent as it should be

 - rename 'silentoldconfig' to 'syncconfig'

 - add unit-test framework and several test cases

 - warn unmet dependency of tristate symbols

 - make unmet dependency warnings readable, removing false positives

 - improve recursive include detection

 - use yylineno to simplify the line number tracking

 - misc cleanups

* tag 'kconfig-v4.17' of git://git.kernel.org/pub/scm/linux/kernel/git/masahiroy/linux-kbuild: (30 commits)
  kconfig: use yylineno option instead of manual lineno increments
  kconfig: detect recursive inclusion earlier
  kconfig: remove duplicated file name and lineno of recursive inclusion
  kconfig: do not include both curses.h and ncurses.h for nconfig
  kconfig: make unmet dependency warnings readable
  kconfig: warn unmet direct dependency of tristate symbols selected by y
  kconfig: tests: test if recursive inclusion is detected
  kconfig: tests: test if recursive dependencies are detected
  kconfig: tests: test randconfig for choice in choice
  kconfig: tests: test defconfig when two choices interact
  kconfig: tests: check visibility of tristate choice values in y choice
  kconfig: tests: check unneeded "is not set" with unmet dependency
  kconfig: tests: test if new symbols in choice are asked
  kconfig: tests: test automatic submenu creation
  kconfig: tests: add basic choice tests
  kconfig: tests: add framework for Kconfig unit testing
  kbuild: add PYTHON2 and PYTHON3 variables
  kconfig: remove redundant streamline_config.pl prerequisite
  kconfig: rename silentoldconfig to syncconfig
  kconfig: invoke oldconfig instead of silentoldconfig from local*config
  ...
Linus Torvalds há 7 anos atrás
pai
commit
147a89bc71
58 ficheiros alterados com 1043 adições e 139 exclusões
  1. 1 1
      Documentation/kbuild/kconfig.txt
  2. 1 1
      Documentation/networking/i40e.txt
  3. 4 2
      Makefile
  4. 17 4
      scripts/checkpatch.pl
  5. 22 7
      scripts/kconfig/Makefile
  6. 21 20
      scripts/kconfig/conf.c
  7. 28 58
      scripts/kconfig/expr.c
  8. 2 2
      scripts/kconfig/expr.h
  9. 1 0
      scripts/kconfig/lkc.h
  10. 6 6
      scripts/kconfig/menu.c
  11. 1 3
      scripts/kconfig/nconf.h
  12. 26 14
      scripts/kconfig/symbol.c
  13. 50 0
      scripts/kconfig/tests/auto_submenu/Kconfig
  14. 12 0
      scripts/kconfig/tests/auto_submenu/__init__.py
  15. 10 0
      scripts/kconfig/tests/auto_submenu/expected_stdout
  16. 54 0
      scripts/kconfig/tests/choice/Kconfig
  17. 40 0
      scripts/kconfig/tests/choice/__init__.py
  18. 5 0
      scripts/kconfig/tests/choice/alldef_expected_config
  19. 9 0
      scripts/kconfig/tests/choice/allmod_expected_config
  20. 5 0
      scripts/kconfig/tests/choice/allno_expected_config
  21. 9 0
      scripts/kconfig/tests/choice/allyes_expected_config
  22. 10 0
      scripts/kconfig/tests/choice/oldask0_expected_stdout
  23. 2 0
      scripts/kconfig/tests/choice/oldask1_config
  24. 15 0
      scripts/kconfig/tests/choice/oldask1_expected_stdout
  25. 19 0
      scripts/kconfig/tests/choice_value_with_m_dep/Kconfig
  26. 15 0
      scripts/kconfig/tests/choice_value_with_m_dep/__init__.py
  27. 2 0
      scripts/kconfig/tests/choice_value_with_m_dep/config
  28. 3 0
      scripts/kconfig/tests/choice_value_with_m_dep/expected_config
  29. 4 0
      scripts/kconfig/tests/choice_value_with_m_dep/expected_stdout
  30. 291 0
      scripts/kconfig/tests/conftest.py
  31. 1 0
      scripts/kconfig/tests/err_recursive_inc/Kconfig
  32. 4 0
      scripts/kconfig/tests/err_recursive_inc/Kconfig.inc1
  33. 3 0
      scripts/kconfig/tests/err_recursive_inc/Kconfig.inc2
  34. 1 0
      scripts/kconfig/tests/err_recursive_inc/Kconfig.inc3
  35. 10 0
      scripts/kconfig/tests/err_recursive_inc/__init__.py
  36. 6 0
      scripts/kconfig/tests/err_recursive_inc/expected_stderr
  37. 23 0
      scripts/kconfig/tests/inter_choice/Kconfig
  38. 14 0
      scripts/kconfig/tests/inter_choice/__init__.py
  39. 1 0
      scripts/kconfig/tests/inter_choice/defconfig
  40. 4 0
      scripts/kconfig/tests/inter_choice/expected_config
  41. 37 0
      scripts/kconfig/tests/new_choice_with_dep/Kconfig
  42. 14 0
      scripts/kconfig/tests/new_choice_with_dep/__init__.py
  43. 3 0
      scripts/kconfig/tests/new_choice_with_dep/config
  44. 10 0
      scripts/kconfig/tests/new_choice_with_dep/expected_stdout
  45. 14 0
      scripts/kconfig/tests/no_write_if_dep_unmet/Kconfig
  46. 19 0
      scripts/kconfig/tests/no_write_if_dep_unmet/__init__.py
  47. 1 0
      scripts/kconfig/tests/no_write_if_dep_unmet/config
  48. 5 0
      scripts/kconfig/tests/no_write_if_dep_unmet/expected_config
  49. 7 0
      scripts/kconfig/tests/pytest.ini
  50. 33 0
      scripts/kconfig/tests/rand_nested_choice/Kconfig
  51. 16 0
      scripts/kconfig/tests/rand_nested_choice/__init__.py
  52. 2 0
      scripts/kconfig/tests/rand_nested_choice/expected_stdout0
  53. 4 0
      scripts/kconfig/tests/rand_nested_choice/expected_stdout1
  54. 5 0
      scripts/kconfig/tests/rand_nested_choice/expected_stdout2
  55. 62 0
      scripts/kconfig/tests/warn_recursive_dep/Kconfig
  56. 9 0
      scripts/kconfig/tests/warn_recursive_dep/__init__.py
  57. 30 0
      scripts/kconfig/tests/warn_recursive_dep/expected_stderr
  58. 20 21
      scripts/kconfig/zconf.l

+ 1 - 1
Documentation/kbuild/kconfig.txt

@@ -119,7 +119,7 @@ Examples:
 		15% of tristates will be set to 'y', 15% to 'm', 70% to 'n'
 
 ______________________________________________________________________
-Environment variables for 'silentoldconfig'
+Environment variables for 'syncconfig'
 
 KCONFIG_NOSILENTUPDATE
 --------------------------------------------------

+ 1 - 1
Documentation/networking/i40e.txt

@@ -32,7 +32,7 @@ Enabling the driver
 The driver is enabled via the standard kernel configuration system,
 using the make command:
 
-     Make oldconfig/silentoldconfig/menuconfig/etc.
+     make config/oldconfig/menuconfig/etc.
 
 The driver is located in the menu structure at:
 

+ 4 - 2
Makefile

@@ -386,6 +386,8 @@ INSTALLKERNEL  := installkernel
 DEPMOD		= /sbin/depmod
 PERL		= perl
 PYTHON		= python
+PYTHON2		= python2
+PYTHON3		= python3
 CHECK		= sparse
 
 CHECKFLAGS     := -D__linux__ -Dlinux -D__STDC__ -Dunix -D__unix__ \
@@ -432,7 +434,7 @@ GCC_PLUGINS_CFLAGS :=
 
 export ARCH SRCARCH CONFIG_SHELL HOSTCC HOSTCFLAGS CROSS_COMPILE AS LD CC
 export CPP AR NM STRIP OBJCOPY OBJDUMP HOSTLDFLAGS HOST_LOADLIBES
-export MAKE LEX YACC AWK GENKSYMS INSTALLKERNEL PERL PYTHON UTS_MACHINE
+export MAKE LEX YACC AWK GENKSYMS INSTALLKERNEL PERL PYTHON PYTHON2 PYTHON3 UTS_MACHINE
 export HOSTCXX HOSTCXXFLAGS LDFLAGS_MODULE CHECK CHECKFLAGS
 
 export KBUILD_CPPFLAGS NOSTDINC_FLAGS LINUXINCLUDE OBJCOPYFLAGS LDFLAGS
@@ -591,7 +593,7 @@ $(KCONFIG_CONFIG) include/config/auto.conf.cmd: ;
 # include/generated/ and include/config/. Update them if .config is newer than
 # include/config/auto.conf (which mirrors .config).
 include/config/%.conf: $(KCONFIG_CONFIG) include/config/auto.conf.cmd
-	$(Q)$(MAKE) -f $(srctree)/Makefile silentoldconfig
+	$(Q)$(MAKE) -f $(srctree)/Makefile syncconfig
 else
 # external modules needs include/generated/autoconf.h and include/config/auto.conf
 # but do not care if they are up-to-date. Use auto.conf to trigger the test

+ 17 - 4
scripts/checkpatch.pl

@@ -2797,7 +2797,10 @@ sub process {
 # Only applies when adding the entry originally, after that we do not have
 # sufficient context to determine whether it is indeed long enough.
 		if ($realfile =~ /Kconfig/ &&
-		    $line =~ /^\+\s*config\s+/) {
+		    # 'choice' is usually the last thing on the line (though
+		    # Kconfig supports named choices), so use a word boundary
+		    # (\b) rather than a whitespace character (\s)
+		    $line =~ /^\+\s*(?:config|menuconfig|choice)\b/) {
 			my $length = 0;
 			my $cnt = $realcnt;
 			my $ln = $linenr + 1;
@@ -2812,9 +2815,13 @@ sub process {
 				next if ($f =~ /^-/);
 				last if (!$file && $f =~ /^\@\@/);
 
-				if ($lines[$ln - 1] =~ /^\+\s*(?:bool|tristate)\s*\"/) {
+				if ($lines[$ln - 1] =~ /^\+\s*(?:bool|tristate|prompt)\s*["']/) {
 					$is_start = 1;
-				} elsif ($lines[$ln - 1] =~ /^\+\s*(?:---)?help(?:---)?$/) {
+				} elsif ($lines[$ln - 1] =~ /^\+\s*(?:help|---help---)\s*$/) {
+					if ($lines[$ln - 1] =~ "---help---") {
+						WARN("CONFIG_DESCRIPTION",
+						     "prefer 'help' over '---help---' for new help texts\n" . $herecurr);
+					}
 					$length = -1;
 				}
 
@@ -2822,7 +2829,13 @@ sub process {
 				$f =~ s/#.*//;
 				$f =~ s/^\s+//;
 				next if ($f =~ /^$/);
-				if ($f =~ /^\s*config\s/) {
+
+				# This only checks context lines in the patch
+				# and so hopefully shouldn't trigger false
+				# positives, even though some of these are
+				# common words in help texts
+				if ($f =~ /^\s*(?:config|menuconfig|choice|endchoice|
+						  if|endif|menu|endmenu|source)\b/x) {
 					$is_end = 1;
 					last;
 				}

+ 22 - 7
scripts/kconfig/Makefile

@@ -3,7 +3,7 @@
 # Kernel configuration targets
 # These targets are used from top-level makefile
 
-PHONY += xconfig gconfig menuconfig config silentoldconfig update-po-config \
+PHONY += xconfig gconfig menuconfig config syncconfig update-po-config \
 	localmodconfig localyesconfig
 
 ifdef KBUILD_KCONFIG
@@ -36,22 +36,22 @@ nconfig: $(obj)/nconf
 
 # This has become an internal implementation detail and is now deprecated
 # for external use.
-silentoldconfig: $(obj)/conf
+syncconfig: $(obj)/conf
 	$(Q)mkdir -p include/config include/generated
 	$< $(silent) --$@ $(Kconfig)
 
-localyesconfig localmodconfig: $(obj)/streamline_config.pl $(obj)/conf
+localyesconfig localmodconfig: $(obj)/conf
 	$(Q)mkdir -p include/config include/generated
-	$(Q)perl $< --$@ $(srctree) $(Kconfig) > .tmp.config
+	$(Q)perl $(srctree)/$(src)/streamline_config.pl --$@ $(srctree) $(Kconfig) > .tmp.config
 	$(Q)if [ -f .config ]; then 					\
 			cmp -s .tmp.config .config ||			\
 			(mv -f .config .config.old.1;			\
 			 mv -f .tmp.config .config;			\
-			 $(obj)/conf $(silent) --silentoldconfig $(Kconfig); \
+			 $< $(silent) --oldconfig $(Kconfig);		\
 			 mv -f .config.old.1 .config.old)		\
 	else								\
 			mv -f .tmp.config .config;			\
-			$(obj)/conf $(silent) --silentoldconfig $(Kconfig); \
+			$< $(silent) --oldconfig $(Kconfig);		\
 	fi
 	$(Q)rm -f .tmp.config
 
@@ -86,7 +86,7 @@ PHONY += $(simple-targets)
 $(simple-targets): $(obj)/conf
 	$< $(silent) --$@ $(Kconfig)
 
-PHONY += oldnoconfig savedefconfig defconfig
+PHONY += oldnoconfig silentoldconfig savedefconfig defconfig
 
 # oldnoconfig is an alias of olddefconfig, because people already are dependent
 # on its behavior (sets new symbols to their default value but not 'n') with the
@@ -95,6 +95,13 @@ oldnoconfig: olddefconfig
 	@echo "  WARNING: \"oldnoconfig\" target will be removed after Linux 4.19"
 	@echo "            Please use \"olddefconfig\" instead, which is an alias."
 
+# We do not expect manual invokcation of "silentoldcofig" (or "syncconfig").
+silentoldconfig: syncconfig
+	@echo "  WARNING: \"silentoldconfig\" has been renamed to \"syncconfig\""
+	@echo "            and is now an internal implementation detail."
+	@echo "            What you want is probably \"oldconfig\"."
+	@echo "            \"silentoldconfig\" will be removed after Linux 4.19"
+
 savedefconfig: $(obj)/conf
 	$< $(silent) --$@=defconfig $(Kconfig)
 
@@ -133,6 +140,14 @@ PHONY += tinyconfig
 tinyconfig:
 	$(Q)$(MAKE) -f $(srctree)/Makefile allnoconfig tiny.config
 
+# CHECK: -o cache_dir=<path> working?
+PHONY += testconfig
+testconfig: $(obj)/conf
+	$(PYTHON3) -B -m pytest $(srctree)/$(src)/tests \
+	-o cache_dir=$(abspath $(obj)/tests/.cache) \
+	$(if $(findstring 1,$(KBUILD_VERBOSE)),--capture=no)
+clean-dirs += tests/.cache
+
 # Help text used by make help
 help:
 	@echo  '  config	  - Update current config utilising a line-oriented program'

+ 21 - 20
scripts/kconfig/conf.c

@@ -23,7 +23,7 @@ static void check_conf(struct menu *menu);
 
 enum input_mode {
 	oldaskconfig,
-	silentoldconfig,
+	syncconfig,
 	oldconfig,
 	allnoconfig,
 	allyesconfig,
@@ -100,7 +100,7 @@ static int conf_askvalue(struct symbol *sym, const char *def)
 
 	switch (input_mode) {
 	case oldconfig:
-	case silentoldconfig:
+	case syncconfig:
 		if (sym_has_value(sym)) {
 			printf("%s\n", def);
 			return 0;
@@ -293,7 +293,7 @@ static int conf_choice(struct menu *menu)
 		printf("[1-%d?]: ", cnt);
 		switch (input_mode) {
 		case oldconfig:
-		case silentoldconfig:
+		case syncconfig:
 			if (!is_new) {
 				cnt = def;
 				printf("%d\n", cnt);
@@ -358,10 +358,11 @@ static void conf(struct menu *menu)
 
 		switch (prop->type) {
 		case P_MENU:
-			if ((input_mode == silentoldconfig ||
-			     input_mode == listnewconfig ||
-			     input_mode == olddefconfig) &&
-			    rootEntry != menu) {
+			/*
+			 * Except in oldaskconfig mode, we show only menus that
+			 * contain new symbols.
+			 */
+			if (input_mode != oldaskconfig && rootEntry != menu) {
 				check_conf(menu);
 				return;
 			}
@@ -424,7 +425,7 @@ static void check_conf(struct menu *menu)
 				if (sym->name && !sym_is_choice_value(sym)) {
 					printf("%s%s\n", CONFIG_, sym->name);
 				}
-			} else if (input_mode != olddefconfig) {
+			} else {
 				if (!conf_cnt++)
 					printf(_("*\n* Restart config...\n*\n"));
 				rootEntry = menu_get_parent_menu(menu);
@@ -440,7 +441,7 @@ static void check_conf(struct menu *menu)
 static struct option long_opts[] = {
 	{"oldaskconfig",    no_argument,       NULL, oldaskconfig},
 	{"oldconfig",       no_argument,       NULL, oldconfig},
-	{"silentoldconfig", no_argument,       NULL, silentoldconfig},
+	{"syncconfig",      no_argument,       NULL, syncconfig},
 	{"defconfig",       optional_argument, NULL, defconfig},
 	{"savedefconfig",   required_argument, NULL, savedefconfig},
 	{"allnoconfig",     no_argument,       NULL, allnoconfig},
@@ -467,8 +468,8 @@ static void conf_usage(const char *progname)
 	printf("  --listnewconfig         List new options\n");
 	printf("  --oldaskconfig          Start a new configuration using a line-oriented program\n");
 	printf("  --oldconfig             Update a configuration using a provided .config as base\n");
-	printf("  --silentoldconfig       Similar to oldconfig but generates configuration in\n"
-	       "                          include/{generated/,config/} (oldconfig used to be more verbose)\n");
+	printf("  --syncconfig            Similar to oldconfig but generates configuration in\n"
+	       "                          include/{generated/,config/}\n");
 	printf("  --olddefconfig          Same as oldconfig but sets new symbols to their default value\n");
 	printf("  --oldnoconfig           An alias of olddefconfig\n");
 	printf("  --defconfig <file>      New config with default defined in <file>\n");
@@ -500,7 +501,7 @@ int main(int ac, char **av)
 		}
 		input_mode = (enum input_mode)opt;
 		switch (opt) {
-		case silentoldconfig:
+		case syncconfig:
 			sync_kconfig = 1;
 			break;
 		case defconfig:
@@ -582,7 +583,7 @@ int main(int ac, char **av)
 		}
 		break;
 	case savedefconfig:
-	case silentoldconfig:
+	case syncconfig:
 	case oldaskconfig:
 	case oldconfig:
 	case listnewconfig:
@@ -662,24 +663,24 @@ int main(int ac, char **av)
 	case oldaskconfig:
 		rootEntry = &rootmenu;
 		conf(&rootmenu);
-		input_mode = silentoldconfig;
+		input_mode = oldconfig;
 		/* fall through */
 	case oldconfig:
 	case listnewconfig:
-	case olddefconfig:
-	case silentoldconfig:
+	case syncconfig:
 		/* Update until a loop caused no more changes */
 		do {
 			conf_cnt = 0;
 			check_conf(&rootmenu);
-		} while (conf_cnt &&
-			 (input_mode != listnewconfig &&
-			  input_mode != olddefconfig));
+		} while (conf_cnt);
+		break;
+	case olddefconfig:
+	default:
 		break;
 	}
 
 	if (sync_kconfig) {
-		/* silentoldconfig is used during the build so we shall update autoconf.
+		/* syncconfig is used during the build so we shall update autoconf.
 		 * All other commands are only used to generate a config.
 		 */
 		if (conf_get_changed() && conf_write(NULL)) {

+ 28 - 58
scripts/kconfig/expr.c

@@ -1137,49 +1137,9 @@ static int expr_compare_type(enum expr_type t1, enum expr_type t2)
 	return 0;
 }
 
-static inline struct expr *
-expr_get_leftmost_symbol(const struct expr *e)
-{
-
-	if (e == NULL)
-		return NULL;
-
-	while (e->type != E_SYMBOL)
-		e = e->left.expr;
-
-	return expr_copy(e);
-}
-
-/*
- * Given expression `e1' and `e2', returns the leaf of the longest
- * sub-expression of `e1' not containing 'e2.
- */
-struct expr *expr_simplify_unmet_dep(struct expr *e1, struct expr *e2)
-{
-	struct expr *ret;
-
-	switch (e1->type) {
-	case E_OR:
-		return expr_alloc_and(
-		    expr_simplify_unmet_dep(e1->left.expr, e2),
-		    expr_simplify_unmet_dep(e1->right.expr, e2));
-	case E_AND: {
-		struct expr *e;
-		e = expr_alloc_and(expr_copy(e1), expr_copy(e2));
-		e = expr_eliminate_dups(e);
-		ret = (!expr_eq(e, e1)) ? e1 : NULL;
-		expr_free(e);
-		break;
-		}
-	default:
-		ret = e1;
-		break;
-	}
-
-	return expr_get_leftmost_symbol(ret);
-}
-
-static void __expr_print(struct expr *e, void (*fn)(void *, struct symbol *, const char *), void *data, int prevtoken, bool revdep)
+void expr_print(struct expr *e,
+		void (*fn)(void *, struct symbol *, const char *),
+		void *data, int prevtoken)
 {
 	if (!e) {
 		fn(data, NULL, "y");
@@ -1234,14 +1194,9 @@ static void __expr_print(struct expr *e, void (*fn)(void *, struct symbol *, con
 		fn(data, e->right.sym, e->right.sym->name);
 		break;
 	case E_OR:
-		if (revdep && e->left.expr->type != E_OR)
-			fn(data, NULL, "\n  - ");
-		__expr_print(e->left.expr, fn, data, E_OR, revdep);
-		if (revdep)
-			fn(data, NULL, "\n  - ");
-		else
-			fn(data, NULL, " || ");
-		__expr_print(e->right.expr, fn, data, E_OR, revdep);
+		expr_print(e->left.expr, fn, data, E_OR);
+		fn(data, NULL, " || ");
+		expr_print(e->right.expr, fn, data, E_OR);
 		break;
 	case E_AND:
 		expr_print(e->left.expr, fn, data, E_AND);
@@ -1274,11 +1229,6 @@ static void __expr_print(struct expr *e, void (*fn)(void *, struct symbol *, con
 		fn(data, NULL, ")");
 }
 
-void expr_print(struct expr *e, void (*fn)(void *, struct symbol *, const char *), void *data, int prevtoken)
-{
-	__expr_print(e, fn, data, prevtoken, false);
-}
-
 static void expr_print_file_helper(void *data, struct symbol *sym, const char *str)
 {
 	xfwrite(str, strlen(str), 1, data);
@@ -1329,7 +1279,27 @@ void expr_gstr_print(struct expr *e, struct gstr *gs)
  * line with a minus. This makes expressions much easier to read.
  * Suitable for reverse dependency expressions.
  */
-void expr_gstr_print_revdep(struct expr *e, struct gstr *gs)
+static void expr_print_revdep(struct expr *e,
+			      void (*fn)(void *, struct symbol *, const char *),
+			      void *data, tristate pr_type, const char **title)
+{
+	if (e->type == E_OR) {
+		expr_print_revdep(e->left.expr, fn, data, pr_type, title);
+		expr_print_revdep(e->right.expr, fn, data, pr_type, title);
+	} else if (expr_calc_value(e) == pr_type) {
+		if (*title) {
+			fn(data, NULL, *title);
+			*title = NULL;
+		}
+
+		fn(data, NULL, "  - ");
+		expr_print(e, fn, data, E_NONE);
+		fn(data, NULL, "\n");
+	}
+}
+
+void expr_gstr_print_revdep(struct expr *e, struct gstr *gs,
+			    tristate pr_type, const char *title)
 {
-	__expr_print(e, expr_print_gstr_helper, gs, E_NONE, true);
+	expr_print_revdep(e, expr_print_gstr_helper, gs, pr_type, &title);
 }

+ 2 - 2
scripts/kconfig/expr.h

@@ -305,12 +305,12 @@ struct expr *expr_transform(struct expr *e);
 int expr_contains_symbol(struct expr *dep, struct symbol *sym);
 bool expr_depends_symbol(struct expr *dep, struct symbol *sym);
 struct expr *expr_trans_compare(struct expr *e, enum expr_type type, struct symbol *sym);
-struct expr *expr_simplify_unmet_dep(struct expr *e1, struct expr *e2);
 
 void expr_fprint(struct expr *e, FILE *out);
 struct gstr; /* forward */
 void expr_gstr_print(struct expr *e, struct gstr *gs);
-void expr_gstr_print_revdep(struct expr *e, struct gstr *gs);
+void expr_gstr_print_revdep(struct expr *e, struct gstr *gs,
+			    tristate pr_type, const char *title);
 
 static inline int expr_is_yes(struct expr *e)
 {

+ 1 - 0
scripts/kconfig/lkc.h

@@ -68,6 +68,7 @@ struct kconf_id {
 	enum symbol_type stype;
 };
 
+extern int yylineno;
 void zconfdump(FILE *out);
 void zconf_starthelp(void);
 FILE *zconf_fopen(const char *name);

+ 6 - 6
scripts/kconfig/menu.c

@@ -828,16 +828,16 @@ static void get_symbol_str(struct gstr *r, struct symbol *sym,
 
 	get_symbol_props_str(r, sym, P_SELECT, _("  Selects: "));
 	if (sym->rev_dep.expr) {
-		str_append(r, _("  Selected by: "));
-		expr_gstr_print_revdep(sym->rev_dep.expr, r);
-		str_append(r, "\n");
+		expr_gstr_print_revdep(sym->rev_dep.expr, r, yes, "  Selected by [y]:\n");
+		expr_gstr_print_revdep(sym->rev_dep.expr, r, mod, "  Selected by [m]:\n");
+		expr_gstr_print_revdep(sym->rev_dep.expr, r, no, "  Selected by [n]:\n");
 	}
 
 	get_symbol_props_str(r, sym, P_IMPLY, _("  Implies: "));
 	if (sym->implied.expr) {
-		str_append(r, _("  Implied by: "));
-		expr_gstr_print_revdep(sym->implied.expr, r);
-		str_append(r, "\n");
+		expr_gstr_print_revdep(sym->implied.expr, r, yes, "  Implied by [y]:\n");
+		expr_gstr_print_revdep(sym->implied.expr, r, mod, "  Implied by [m]:\n");
+		expr_gstr_print_revdep(sym->implied.expr, r, no, "  Implied by [n]:\n");
 	}
 
 	str_append(r, "\n\n");

+ 1 - 3
scripts/kconfig/nconf.h

@@ -15,7 +15,7 @@
 #include <string.h>
 #include <unistd.h>
 #include <locale.h>
-#include <curses.h>
+#include <ncurses.h>
 #include <menu.h>
 #include <panel.h>
 #include <form.h>
@@ -24,8 +24,6 @@
 #include <time.h>
 #include <sys/time.h>
 
-#include "ncurses.h"
-
 #define max(a, b) ({\
 		typeof(a) _a = a;\
 		typeof(b) _b = b;\

+ 26 - 14
scripts/kconfig/symbol.c

@@ -243,7 +243,7 @@ static void sym_calc_visibility(struct symbol *sym)
 	tri = yes;
 	if (sym->dir_dep.expr)
 		tri = expr_calc_value(sym->dir_dep.expr);
-	if (tri == mod)
+	if (tri == mod && sym_get_type(sym) == S_BOOLEAN)
 		tri = yes;
 	if (sym->dir_dep.tri != tri) {
 		sym->dir_dep.tri = tri;
@@ -333,6 +333,27 @@ static struct symbol *sym_calc_choice(struct symbol *sym)
 	return def_sym;
 }
 
+static void sym_warn_unmet_dep(struct symbol *sym)
+{
+	struct gstr gs = str_new();
+
+	str_printf(&gs,
+		   "\nWARNING: unmet direct dependencies detected for %s\n",
+		   sym->name);
+	str_printf(&gs,
+		   "  Depends on [%c]: ",
+		   sym->dir_dep.tri == mod ? 'm' : 'n');
+	expr_gstr_print(sym->dir_dep.expr, &gs);
+	str_printf(&gs, "\n");
+
+	expr_gstr_print_revdep(sym->rev_dep.expr, &gs, yes,
+			       "  Selected by [y]:\n");
+	expr_gstr_print_revdep(sym->rev_dep.expr, &gs, mod,
+			       "  Selected by [m]:\n");
+
+	fputs(str_get(&gs), stderr);
+}
+
 void sym_calc_value(struct symbol *sym)
 {
 	struct symbol_value newval, oldval;
@@ -403,9 +424,10 @@ void sym_calc_value(struct symbol *sym)
 			if (!sym_is_choice(sym)) {
 				prop = sym_get_default_prop(sym);
 				if (prop) {
-					sym->flags |= SYMBOL_WRITE;
 					newval.tri = EXPR_AND(expr_calc_value(prop->expr),
 							      prop->visible.tri);
+					if (newval.tri != no)
+						sym->flags |= SYMBOL_WRITE;
 				}
 				if (sym->implied.tri != no) {
 					sym->flags |= SYMBOL_WRITE;
@@ -413,18 +435,8 @@ void sym_calc_value(struct symbol *sym)
 				}
 			}
 		calc_newval:
-			if (sym->dir_dep.tri == no && sym->rev_dep.tri != no) {
-				struct expr *e;
-				e = expr_simplify_unmet_dep(sym->rev_dep.expr,
-				    sym->dir_dep.expr);
-				fprintf(stderr, "warning: (");
-				expr_fprint(e, stderr);
-				fprintf(stderr, ") selects %s which has unmet direct dependencies (",
-					sym->name);
-				expr_fprint(sym->dir_dep.expr, stderr);
-				fprintf(stderr, ")\n");
-				expr_free(e);
-			}
+			if (sym->dir_dep.tri < sym->rev_dep.tri)
+				sym_warn_unmet_dep(sym);
 			newval.tri = EXPR_OR(newval.tri, sym->rev_dep.tri);
 		}
 		if (newval.tri == mod &&

+ 50 - 0
scripts/kconfig/tests/auto_submenu/Kconfig

@@ -0,0 +1,50 @@
+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.

+ 12 - 0
scripts/kconfig/tests/auto_submenu/__init__.py

@@ -0,0 +1,12 @@
+"""
+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')

+ 10 - 0
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) 

+ 54 - 0
scripts/kconfig/tests/choice/Kconfig

@@ -0,0 +1,54 @@
+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

+ 40 - 0
scripts/kconfig/tests/choice/__init__.py

@@ -0,0 +1,40 @@
+"""
+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')

+ 5 - 0
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

+ 9 - 0
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

+ 5 - 0
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

+ 9 - 0
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

+ 10 - 0
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) 

+ 2 - 0
scripts/kconfig/tests/choice/oldask1_config

@@ -0,0 +1,2 @@
+# CONFIG_MODULES is not set
+CONFIG_OPT_BOOL_CHOICE0=y

+ 15 - 0
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/?] 

+ 19 - 0
scripts/kconfig/tests/choice_value_with_m_dep/Kconfig

@@ -0,0 +1,19 @@
+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

+ 15 - 0
scripts/kconfig/tests/choice_value_with_m_dep/__init__.py

@@ -0,0 +1,15 @@
+"""
+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')

+ 2 - 0
scripts/kconfig/tests/choice_value_with_m_dep/config

@@ -0,0 +1,2 @@
+CONFIG_CHOICE0=m
+CONFIG_CHOICE1=m

+ 3 - 0
scripts/kconfig/tests/choice_value_with_m_dep/expected_config

@@ -0,0 +1,3 @@
+CONFIG_MODULES=y
+CONFIG_DEP=m
+CONFIG_CHOICE0=y

+ 4 - 0
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

+ 291 - 0
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)

+ 1 - 0
scripts/kconfig/tests/err_recursive_inc/Kconfig

@@ -0,0 +1 @@
+source "Kconfig.inc1"

+ 4 - 0
scripts/kconfig/tests/err_recursive_inc/Kconfig.inc1

@@ -0,0 +1,4 @@
+
+
+
+source "Kconfig.inc2"

+ 3 - 0
scripts/kconfig/tests/err_recursive_inc/Kconfig.inc2

@@ -0,0 +1,3 @@
+
+
+source "Kconfig.inc3"

+ 1 - 0
scripts/kconfig/tests/err_recursive_inc/Kconfig.inc3

@@ -0,0 +1 @@
+source "Kconfig.inc1"

+ 10 - 0
scripts/kconfig/tests/err_recursive_inc/__init__.py

@@ -0,0 +1,10 @@
+"""
+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')

+ 6 - 0
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:1
+  included from: Kconfig.inc2:3
+  included from: Kconfig.inc1:4

+ 23 - 0
scripts/kconfig/tests/inter_choice/Kconfig

@@ -0,0 +1,23 @@
+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

+ 14 - 0
scripts/kconfig/tests/inter_choice/__init__.py

@@ -0,0 +1,14 @@
+"""
+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')

+ 1 - 0
scripts/kconfig/tests/inter_choice/defconfig

@@ -0,0 +1 @@
+CONFIG_CHOICE_VAL0=y

+ 4 - 0
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

+ 37 - 0
scripts/kconfig/tests/new_choice_with_dep/Kconfig

@@ -0,0 +1,37 @@
+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

+ 14 - 0
scripts/kconfig/tests/new_choice_with_dep/__init__.py

@@ -0,0 +1,14 @@
+"""
+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')

+ 3 - 0
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

+ 10 - 0
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?]: 

+ 14 - 0
scripts/kconfig/tests/no_write_if_dep_unmet/Kconfig

@@ -0,0 +1,14 @@
+config A
+	bool "A"
+
+choice
+	prompt "Choice ?"
+	depends on A
+
+config CHOICE_B
+	bool "Choice B"
+
+config CHOICE_C
+	bool "Choice C"
+
+endchoice

+ 19 - 0
scripts/kconfig/tests/no_write_if_dep_unmet/__init__.py

@@ -0,0 +1,19 @@
+"""
+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')

+ 1 - 0
scripts/kconfig/tests/no_write_if_dep_unmet/config

@@ -0,0 +1 @@
+CONFIG_A=y

+ 5 - 0
scripts/kconfig/tests/no_write_if_dep_unmet/expected_config

@@ -0,0 +1,5 @@
+#
+# Automatically generated file; DO NOT EDIT.
+# Linux Kernel Configuration
+#
+# CONFIG_A is not set

+ 7 - 0
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

+ 33 - 0
scripts/kconfig/tests/rand_nested_choice/Kconfig

@@ -0,0 +1,33 @@
+choice
+	prompt "choice"
+
+config A
+	bool "A"
+
+config B
+	bool "B"
+
+if B
+choice
+	prompt "sub choice"
+
+config C
+	bool "C"
+
+config D
+	bool "D"
+
+if D
+choice
+	prompt "subsub choice"
+
+config E
+	bool "E"
+
+endchoice
+endif # D
+
+endchoice
+endif # B
+
+endchoice

+ 16 - 0
scripts/kconfig/tests/rand_nested_choice/__init__.py

@@ -0,0 +1,16 @@
+"""
+Set random values recursively in nested choices.
+
+Kconfig can create a choice-in-choice structure by using 'if' statement.
+randconfig should correctly set random choice values.
+
+Related Linux commit: 3b9a19e08960e5cdad5253998637653e592a3c29
+"""
+
+
+def test(conf):
+    for i in range(20):
+        assert conf.randconfig() == 0
+        assert (conf.config_contains('expected_stdout0') or
+                conf.config_contains('expected_stdout1') or
+                conf.config_contains('expected_stdout2'))

+ 2 - 0
scripts/kconfig/tests/rand_nested_choice/expected_stdout0

@@ -0,0 +1,2 @@
+CONFIG_A=y
+# CONFIG_B is not set

+ 4 - 0
scripts/kconfig/tests/rand_nested_choice/expected_stdout1

@@ -0,0 +1,4 @@
+# CONFIG_A is not set
+CONFIG_B=y
+CONFIG_C=y
+# CONFIG_D is not set

+ 5 - 0
scripts/kconfig/tests/rand_nested_choice/expected_stdout2

@@ -0,0 +1,5 @@
+# CONFIG_A is not set
+CONFIG_B=y
+# CONFIG_C is not set
+CONFIG_D=y
+CONFIG_E=y

+ 62 - 0
scripts/kconfig/tests/warn_recursive_dep/Kconfig

@@ -0,0 +1,62 @@
+# 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
+# This is not recursive dependency
+
+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

+ 9 - 0
scripts/kconfig/tests/warn_recursive_dep/__init__.py

@@ -0,0 +1,9 @@
+"""
+Warn recursive inclusion.
+
+Recursive dependency should be warned.
+"""
+
+def test(conf):
+    assert conf.oldaskconfig() == 0
+    assert conf.stderr_contains('expected_stderr')

+ 30 - 0
scripts/kconfig/tests/warn_recursive_dep/expected_stderr

@@ -0,0 +1,30 @@
+Kconfig:9:error: recursive dependency detected!
+Kconfig:9:	symbol B is selected by B
+For a resolution refer to Documentation/kbuild/kconfig-language.txt
+subsection "Kconfig recursive dependency limitations"
+
+Kconfig:3:error: recursive dependency detected!
+Kconfig:3:	symbol A depends on A
+For a resolution refer to Documentation/kbuild/kconfig-language.txt
+subsection "Kconfig recursive dependency limitations"
+
+Kconfig:15:error: recursive dependency detected!
+Kconfig:15:	symbol C1 depends on C2
+Kconfig:19:	symbol C2 depends on C1
+For a resolution refer to Documentation/kbuild/kconfig-language.txt
+subsection "Kconfig recursive dependency limitations"
+
+Kconfig:30:error: recursive dependency detected!
+Kconfig:30:	symbol D2 is selected by D1
+Kconfig:25:	symbol D1 depends on D2
+For a resolution refer to Documentation/kbuild/kconfig-language.txt
+subsection "Kconfig recursive dependency limitations"
+
+Kconfig:59:error: recursive dependency detected!
+Kconfig:59:	symbol G depends on G
+For a resolution refer to Documentation/kbuild/kconfig-language.txt
+subsection "Kconfig recursive dependency limitations"
+
+Kconfig:50:error: recursive dependency detected!
+Kconfig:50:	symbol F2 depends on F1
+Kconfig:48:	symbol F1 default value contains F2

+ 20 - 21
scripts/kconfig/zconf.l

@@ -1,5 +1,5 @@
 %option nostdinit noyywrap never-interactive full ecs
-%option 8bit nodefault perf-report perf-report
+%option 8bit nodefault yylineno
 %option noinput
 %x COMMAND HELP STRING PARAM
 %{
@@ -83,7 +83,6 @@ n	[A-Za-z0-9_-]
 
 [ \t]*#.*\n	|
 [ \t]*\n	{
-	current_file->lineno++;
 	return T_EOL;
 }
 [ \t]*#.*
@@ -104,7 +103,7 @@ n	[A-Za-z0-9_-]
 		const struct kconf_id *id = kconf_id_lookup(yytext, yyleng);
 		BEGIN(PARAM);
 		current_pos.file = current_file;
-		current_pos.lineno = current_file->lineno;
+		current_pos.lineno = yylineno;
 		if (id && id->flags & TF_COMMAND) {
 			yylval.id = id;
 			return id->token;
@@ -116,7 +115,6 @@ n	[A-Za-z0-9_-]
 	.	warn_ignored_character(*yytext);
 	\n	{
 		BEGIN(INITIAL);
-		current_file->lineno++;
 		return T_EOL;
 	}
 }
@@ -138,7 +136,7 @@ n	[A-Za-z0-9_-]
 		new_string();
 		BEGIN(STRING);
 	}
-	\n	BEGIN(INITIAL); current_file->lineno++; return T_EOL;
+	\n	BEGIN(INITIAL); return T_EOL;
 	({n}|[/.])+	{
 		const struct kconf_id *id = kconf_id_lookup(yytext, yyleng);
 		if (id && id->flags & TF_PARAM) {
@@ -150,7 +148,7 @@ n	[A-Za-z0-9_-]
 		return T_WORD;
 	}
 	#.*	/* comment */
-	\\\n	current_file->lineno++;
+	\\\n	;
 	[[:blank:]]+
 	.	warn_ignored_character(*yytext);
 	<<EOF>> {
@@ -187,7 +185,6 @@ n	[A-Za-z0-9_-]
 		fprintf(stderr,
 			"%s:%d:warning: multi-line strings not supported\n",
 			zconf_curname(), zconf_lineno());
-		current_file->lineno++;
 		BEGIN(INITIAL);
 		return T_EOL;
 	}
@@ -220,12 +217,10 @@ n	[A-Za-z0-9_-]
 		}
 	}
 	[ \t]*\n/[^ \t\n] {
-		current_file->lineno++;
 		zconf_endhelp();
 		return T_HELPTEXT;
 	}
 	[ \t]*\n	{
-		current_file->lineno++;
 		append_string("\n", 1);
 	}
 	[^ \t\n].* {
@@ -304,7 +299,7 @@ void zconf_initscan(const char *name)
 	memset(current_buf, 0, sizeof(*current_buf));
 
 	current_file = file_lookup(name);
-	current_file->lineno = 1;
+	yylineno = 1;
 }
 
 void zconf_nextfile(const char *name)
@@ -325,24 +320,26 @@ void zconf_nextfile(const char *name)
 	buf->parent = current_buf;
 	current_buf = buf;
 
-	for (iter = current_file->parent; iter; iter = iter->parent ) {
-		if (!strcmp(current_file->name,iter->name) ) {
+	current_file->lineno = yylineno;
+	file->parent = current_file;
+
+	for (iter = current_file; iter; iter = iter->parent) {
+		if (!strcmp(iter->name, file->name)) {
 			fprintf(stderr,
-				"%s:%d: recursive inclusion detected. "
-				"Inclusion path:\n  current file : '%s'\n",
-				zconf_curname(), zconf_lineno(),
-				zconf_curname());
-			iter = current_file;
+				"Recursive inclusion detected.\n"
+				"Inclusion path:\n"
+				"  current file : %s\n", file->name);
+			iter = file;
 			do {
 				iter = iter->parent;
-				fprintf(stderr, "  included from: '%s:%d'\n",
+				fprintf(stderr, "  included from: %s:%d\n",
 					iter->name, iter->lineno - 1);
-			} while (strcmp(iter->name, current_file->name));
+			} while (strcmp(iter->name, file->name));
 			exit(1);
 		}
 	}
-	file->lineno = 1;
-	file->parent = current_file;
+
+	yylineno = 1;
 	current_file = file;
 }
 
@@ -351,6 +348,8 @@ static void zconf_endfile(void)
 	struct buffer *parent;
 
 	current_file = current_file->parent;
+	if (current_file)
+		yylineno = current_file->lineno;
 
 	parent = current_buf->parent;
 	if (parent) {