Merge tag 'kconfig-v4.17' of git://git.kernel.org/pub/scm/linux/kernel/git/masahiroy...
authorLinus Torvalds <torvalds@linux-foundation.org>
Tue, 3 Apr 2018 23:28:01 +0000 (16:28 -0700)
committerLinus Torvalds <torvalds@linux-foundation.org>
Tue, 3 Apr 2018 23:28:01 +0000 (16:28 -0700)
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
  ...

58 files changed:
Documentation/kbuild/kconfig.txt
Documentation/networking/i40e.txt
Makefile
scripts/checkpatch.pl
scripts/kconfig/Makefile
scripts/kconfig/conf.c
scripts/kconfig/expr.c
scripts/kconfig/expr.h
scripts/kconfig/lkc.h
scripts/kconfig/menu.c
scripts/kconfig/nconf.h
scripts/kconfig/symbol.c
scripts/kconfig/tests/auto_submenu/Kconfig [new file with mode: 0644]
scripts/kconfig/tests/auto_submenu/__init__.py [new file with mode: 0644]
scripts/kconfig/tests/auto_submenu/expected_stdout [new file with mode: 0644]
scripts/kconfig/tests/choice/Kconfig [new file with mode: 0644]
scripts/kconfig/tests/choice/__init__.py [new file with mode: 0644]
scripts/kconfig/tests/choice/alldef_expected_config [new file with mode: 0644]
scripts/kconfig/tests/choice/allmod_expected_config [new file with mode: 0644]
scripts/kconfig/tests/choice/allno_expected_config [new file with mode: 0644]
scripts/kconfig/tests/choice/allyes_expected_config [new file with mode: 0644]
scripts/kconfig/tests/choice/oldask0_expected_stdout [new file with mode: 0644]
scripts/kconfig/tests/choice/oldask1_config [new file with mode: 0644]
scripts/kconfig/tests/choice/oldask1_expected_stdout [new file with mode: 0644]
scripts/kconfig/tests/choice_value_with_m_dep/Kconfig [new file with mode: 0644]
scripts/kconfig/tests/choice_value_with_m_dep/__init__.py [new file with mode: 0644]
scripts/kconfig/tests/choice_value_with_m_dep/config [new file with mode: 0644]
scripts/kconfig/tests/choice_value_with_m_dep/expected_config [new file with mode: 0644]
scripts/kconfig/tests/choice_value_with_m_dep/expected_stdout [new file with mode: 0644]
scripts/kconfig/tests/conftest.py [new file with mode: 0644]
scripts/kconfig/tests/err_recursive_inc/Kconfig [new file with mode: 0644]
scripts/kconfig/tests/err_recursive_inc/Kconfig.inc1 [new file with mode: 0644]
scripts/kconfig/tests/err_recursive_inc/Kconfig.inc2 [new file with mode: 0644]
scripts/kconfig/tests/err_recursive_inc/Kconfig.inc3 [new file with mode: 0644]
scripts/kconfig/tests/err_recursive_inc/__init__.py [new file with mode: 0644]
scripts/kconfig/tests/err_recursive_inc/expected_stderr [new file with mode: 0644]
scripts/kconfig/tests/inter_choice/Kconfig [new file with mode: 0644]
scripts/kconfig/tests/inter_choice/__init__.py [new file with mode: 0644]
scripts/kconfig/tests/inter_choice/defconfig [new file with mode: 0644]
scripts/kconfig/tests/inter_choice/expected_config [new file with mode: 0644]
scripts/kconfig/tests/new_choice_with_dep/Kconfig [new file with mode: 0644]
scripts/kconfig/tests/new_choice_with_dep/__init__.py [new file with mode: 0644]
scripts/kconfig/tests/new_choice_with_dep/config [new file with mode: 0644]
scripts/kconfig/tests/new_choice_with_dep/expected_stdout [new file with mode: 0644]
scripts/kconfig/tests/no_write_if_dep_unmet/Kconfig [new file with mode: 0644]
scripts/kconfig/tests/no_write_if_dep_unmet/__init__.py [new file with mode: 0644]
scripts/kconfig/tests/no_write_if_dep_unmet/config [new file with mode: 0644]
scripts/kconfig/tests/no_write_if_dep_unmet/expected_config [new file with mode: 0644]
scripts/kconfig/tests/pytest.ini [new file with mode: 0644]
scripts/kconfig/tests/rand_nested_choice/Kconfig [new file with mode: 0644]
scripts/kconfig/tests/rand_nested_choice/__init__.py [new file with mode: 0644]
scripts/kconfig/tests/rand_nested_choice/expected_stdout0 [new file with mode: 0644]
scripts/kconfig/tests/rand_nested_choice/expected_stdout1 [new file with mode: 0644]
scripts/kconfig/tests/rand_nested_choice/expected_stdout2 [new file with mode: 0644]
scripts/kconfig/tests/warn_recursive_dep/Kconfig [new file with mode: 0644]
scripts/kconfig/tests/warn_recursive_dep/__init__.py [new file with mode: 0644]
scripts/kconfig/tests/warn_recursive_dep/expected_stderr [new file with mode: 0644]
scripts/kconfig/zconf.l

index bbc99c0..7233118 100644 (file)
@@ -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
 --------------------------------------------------
index 57e616e..c2d6e18 100644 (file)
@@ -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:
 
index 65b6159..c1a608a 100644 (file)
--- a/Makefile
+++ b/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
index 949842e..764ffd1 100755 (executable)
@@ -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;
                                }
index eb139a1..f9bdd02 100644 (file)
@@ -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'
index 822dc51..4e08121 100644 (file)
@@ -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)) {
index d453819..e1a39e9 100644 (file)
@@ -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);
 }
index c16e82e..94a383b 100644 (file)
@@ -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)
 {
index 2d5ec2d..f4394af 100644 (file)
@@ -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);
index 36cd3e1..5c5c137 100644 (file)
@@ -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");
index 0d52617..9f6f21d 100644 (file)
@@ -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;\
index 2220bc4..f0b2e3b 100644 (file)
@@ -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 &&
diff --git a/scripts/kconfig/tests/auto_submenu/Kconfig b/scripts/kconfig/tests/auto_submenu/Kconfig
new file mode 100644 (file)
index 0000000..c17bf2c
--- /dev/null
@@ -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.
diff --git a/scripts/kconfig/tests/auto_submenu/__init__.py b/scripts/kconfig/tests/auto_submenu/__init__.py
new file mode 100644 (file)
index 0000000..32e79b8
--- /dev/null
@@ -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')
diff --git a/scripts/kconfig/tests/auto_submenu/expected_stdout b/scripts/kconfig/tests/auto_submenu/expected_stdout
new file mode 100644 (file)
index 0000000..bf5236f
--- /dev/null
@@ -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 (file)
index 0000000..cc60e9c
--- /dev/null
@@ -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
diff --git a/scripts/kconfig/tests/choice/__init__.py b/scripts/kconfig/tests/choice/__init__.py
new file mode 100644 (file)
index 0000000..9edcc52
--- /dev/null
@@ -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')
diff --git a/scripts/kconfig/tests/choice/alldef_expected_config b/scripts/kconfig/tests/choice/alldef_expected_config
new file mode 100644 (file)
index 0000000..7a754bf
--- /dev/null
@@ -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 (file)
index 0000000..f1f5dcd
--- /dev/null
@@ -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 (file)
index 0000000..b88ee7a
--- /dev/null
@@ -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 (file)
index 0000000..e5a062a
--- /dev/null
@@ -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 (file)
index 0000000..b251bba
--- /dev/null
@@ -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 (file)
index 0000000..b67bfe3
--- /dev/null
@@ -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 (file)
index 0000000..c2125e9
--- /dev/null
@@ -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 (file)
index 0000000..11ac25c
--- /dev/null
@@ -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
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 (file)
index 0000000..f8d728c
--- /dev/null
@@ -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')
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 (file)
index 0000000..3a126b7
--- /dev/null
@@ -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 (file)
index 0000000..4d07b44
--- /dev/null
@@ -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 (file)
index 0000000..2b50ab6
--- /dev/null
@@ -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 (file)
index 0000000..0345ef6
--- /dev/null
@@ -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_inc/Kconfig b/scripts/kconfig/tests/err_recursive_inc/Kconfig
new file mode 100644 (file)
index 0000000..0e4c875
--- /dev/null
@@ -0,0 +1 @@
+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 (file)
index 0000000..00e408d
--- /dev/null
@@ -0,0 +1,4 @@
+
+
+
+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 (file)
index 0000000..349ea2d
--- /dev/null
@@ -0,0 +1,3 @@
+
+
+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 (file)
index 0000000..0e4c875
--- /dev/null
@@ -0,0 +1 @@
+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 (file)
index 0000000..0e4c839
--- /dev/null
@@ -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')
diff --git a/scripts/kconfig/tests/err_recursive_inc/expected_stderr b/scripts/kconfig/tests/err_recursive_inc/expected_stderr
new file mode 100644 (file)
index 0000000..6b582ee
--- /dev/null
@@ -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
diff --git a/scripts/kconfig/tests/inter_choice/Kconfig b/scripts/kconfig/tests/inter_choice/Kconfig
new file mode 100644 (file)
index 0000000..e44449f
--- /dev/null
@@ -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
diff --git a/scripts/kconfig/tests/inter_choice/__init__.py b/scripts/kconfig/tests/inter_choice/__init__.py
new file mode 100644 (file)
index 0000000..5c7fc36
--- /dev/null
@@ -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')
diff --git a/scripts/kconfig/tests/inter_choice/defconfig b/scripts/kconfig/tests/inter_choice/defconfig
new file mode 100644 (file)
index 0000000..162c414
--- /dev/null
@@ -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 (file)
index 0000000..5dceefb
--- /dev/null
@@ -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 (file)
index 0000000..53ef1b8
--- /dev/null
@@ -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
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 (file)
index 0000000..f0e0ead
--- /dev/null
@@ -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')
diff --git a/scripts/kconfig/tests/new_choice_with_dep/config b/scripts/kconfig/tests/new_choice_with_dep/config
new file mode 100644 (file)
index 0000000..47ef95d
--- /dev/null
@@ -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 (file)
index 0000000..74dc0bc
--- /dev/null
@@ -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 (file)
index 0000000..c00b8fe
--- /dev/null
@@ -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
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 (file)
index 0000000..207261b
--- /dev/null
@@ -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')
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 (file)
index 0000000..abd280e
--- /dev/null
@@ -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 (file)
index 0000000..0d15e41
--- /dev/null
@@ -0,0 +1,5 @@
+#
+# Automatically generated file; DO NOT EDIT.
+# Linux Kernel Configuration
+#
+# CONFIG_A is not set
diff --git a/scripts/kconfig/tests/pytest.ini b/scripts/kconfig/tests/pytest.ini
new file mode 100644 (file)
index 0000000..85d7ce8
--- /dev/null
@@ -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
diff --git a/scripts/kconfig/tests/rand_nested_choice/Kconfig b/scripts/kconfig/tests/rand_nested_choice/Kconfig
new file mode 100644 (file)
index 0000000..c591d51
--- /dev/null
@@ -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
diff --git a/scripts/kconfig/tests/rand_nested_choice/__init__.py b/scripts/kconfig/tests/rand_nested_choice/__init__.py
new file mode 100644 (file)
index 0000000..e729a4e
--- /dev/null
@@ -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'))
diff --git a/scripts/kconfig/tests/rand_nested_choice/expected_stdout0 b/scripts/kconfig/tests/rand_nested_choice/expected_stdout0
new file mode 100644 (file)
index 0000000..05450f3
--- /dev/null
@@ -0,0 +1,2 @@
+CONFIG_A=y
+# CONFIG_B is not set
diff --git a/scripts/kconfig/tests/rand_nested_choice/expected_stdout1 b/scripts/kconfig/tests/rand_nested_choice/expected_stdout1
new file mode 100644 (file)
index 0000000..37ab295
--- /dev/null
@@ -0,0 +1,4 @@
+# CONFIG_A is not set
+CONFIG_B=y
+CONFIG_C=y
+# CONFIG_D is not set
diff --git a/scripts/kconfig/tests/rand_nested_choice/expected_stdout2 b/scripts/kconfig/tests/rand_nested_choice/expected_stdout2
new file mode 100644 (file)
index 0000000..849ff47
--- /dev/null
@@ -0,0 +1,5 @@
+# CONFIG_A is not set
+CONFIG_B=y
+# CONFIG_C is not set
+CONFIG_D=y
+CONFIG_E=y
diff --git a/scripts/kconfig/tests/warn_recursive_dep/Kconfig b/scripts/kconfig/tests/warn_recursive_dep/Kconfig
new file mode 100644 (file)
index 0000000..a65bfcb
--- /dev/null
@@ -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
diff --git a/scripts/kconfig/tests/warn_recursive_dep/__init__.py b/scripts/kconfig/tests/warn_recursive_dep/__init__.py
new file mode 100644 (file)
index 0000000..adb2195
--- /dev/null
@@ -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')
diff --git a/scripts/kconfig/tests/warn_recursive_dep/expected_stderr b/scripts/kconfig/tests/warn_recursive_dep/expected_stderr
new file mode 100644 (file)
index 0000000..3de807d
--- /dev/null
@@ -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
index 88b650e..045093d 100644 (file)
@@ -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) {