Commit 147a89bc authored by Linus Torvalds's avatar Linus Torvalds

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
  ...
parents 3b24b837 18492685
......@@ -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
--------------------------------------------------
......
......@@ -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:
......
......@@ -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
......
......@@ -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;
}
......
......@@ -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'
......
......@@ -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)) {
......
......@@ -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);
}
......@@ -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)
{
......
......@@ -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);
......
......@@ -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");
......
......@@ -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;\
......
......@@ -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 &&
......
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.
"""
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')
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)
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
"""
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')
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
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
# 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
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
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)
# CONFIG_MODULES is not set
CONFIG_OPT_BOOL_CHOICE0=y
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/?]
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
"""
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')
CONFIG_MODULES=y
CONFIG_DEP=m
CONFIG_CHOICE0=y
Tristate Choice [M/y/?] y
Tristate Choice
> 1. Choice 0 (CHOICE0)
choice[1]: 1
# 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)
"""
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')
Recursive inclusion detected.
Inclusion path:
current file : Kconfig.inc1
included from: Kconfig.inc3:1
included from: Kconfig.inc2:3
included from: Kconfig.inc1:4
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
"""
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')
CONFIG_MODULES=y
CONFIG_CHOICE_VAL0=y
# CONFIG_CHOIVE_VAL1 is not set
CONFIG_DUMMY=y
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
"""
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')
CONFIG_CHOICE_B=y
# CONFIG_CHOICE_D is not set
CONFIG_CHOICE_E=y
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?]:
config A
bool "A"
choice
prompt "Choice ?"
depends on A
config CHOICE_B
bool "Choice B"
config CHOICE_C
bool "Choice C"
endchoice
"""
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')
#
# Automatically generated file; DO NOT EDIT.
# Linux Kernel Configuration
#
# CONFIG_A is not set
[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
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
"""
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'))
# CONFIG_A is not set
CONFIG_B=y
CONFIG_C=y
# CONFIG_D is not set
# CONFIG_A is not set
CONFIG_B=y
# CONFIG_C is not set
CONFIG_D=y
CONFIG_E=y
# 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
"""
Warn recursive inclusion.
Recursive dependency should be warned.
"""
def test(conf):
assert conf.oldaskconfig() == 0
assert conf.stderr_contains('expected_stderr')
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
%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) {
......
Markdown is supported
0%
or
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment