Merge branch 'fix/kconfigs_check_move' into 'master'

fix(tools/kconfig): Moved check_konfigs.py to esp-idf-kconfig package

Closes IDFGH-9516

See merge request espressif/esp-idf!26033
This commit is contained in:
Roland Dobai 2023-10-10 17:58:43 +08:00
commit 81804be35f
9 changed files with 51 additions and 687 deletions

View File

@ -71,21 +71,6 @@ check_api_usage:
- tools/ci/check_api_violation.sh
- tools/ci/check_examples_extra_component_dirs.sh
test_check_kconfigs:
extends: .pre_check_template
artifacts:
when: on_failure
paths:
- components/*/Kconfig*.new
- examples/*/*/*/Kconfig*.new
- examples/*/*/*/*/Kconfig*.new
- tools/*/Kconfig*.new
- tools/*/*/Kconfig*.new
- tools/*/*/*/Kconfig*.new
expire_in: 1 week
script:
- python ${IDF_PATH}/tools/ci/test_check_kconfigs.py
check_blobs:
extends:
- .pre_check_template

View File

@ -154,7 +154,6 @@
- "components/**/*"
- "tools/ci/test_autocomplete.py"
- "tools/ci/test_check_kconfigs.py"
- "tools/mass_mfg/**/*"

View File

@ -43,7 +43,11 @@ Format rules for Kconfig files are as follows:
Format Checker
--------------
``tools/ci/check_kconfigs.py`` is provided for checking Kconfig files against the above format rules. The checker checks all Kconfig and ``Kconfig.projbuild`` files in the ESP-IDF directory, and generates a new file with suffix ``.new`` with some suggestions about how to fix issues (if there are any). Please note that the checker cannot correct all format issues and the responsibility of the developer is to final check and make corrections in order to pass the tests. For example, indentations will be corrected if there is not any misleading formatting, but it cannot come up with a common prefix for options inside a menu.
``kconfcheck`` tool in esp-idf-kconfig_ package is provided for checking Kconfig files against the above format rules. The checker checks all Kconfig and ``Kconfig.projbuild`` files given as arguments, and generates a new file with suffix ``.new`` with some suggestions about how to fix issues (if there are any). Please note that the checker cannot correct all format issues and the responsibility of the developer is to final check and make corrections in order to pass the tests. For example, indentations will be corrected if there is not any misleading formatting, but it cannot come up with a common prefix for options inside a menu.
The ``esp-idf-kconfig`` package is available in ESP-IDF environments, where the checker tool can be invoked by running command: ``python -m kconfcheck <path_to_kconfig_file>``
For more information, see `esp-idf-kconfig package documentation <https://github.com/espressif/esp-idf-kconfig/blob/master/docs/DOCUMENTATION.md>`_
.. _configuration-options-compatibility:

View File

@ -1,6 +1,6 @@
#!/usr/bin/env python
#
# SPDX-FileCopyrightText: 2019-2022 Espressif Systems (Shanghai) CO LTD
# SPDX-FileCopyrightText: 2019-2023 Espressif Systems (Shanghai) CO LTD
# SPDX-License-Identifier: Apache-2.0
from __future__ import print_function, unicode_literals
@ -11,7 +11,6 @@ import sys
from io import open
from typing import Set, Tuple
from check_kconfigs import valid_directory
from idf_ci_utils import get_submodule_dirs
# FILES_TO_CHECK used as "startswith" pattern to match sdkconfig.defaults variants
@ -34,7 +33,7 @@ def _parse_path(path: 'os.PathLike[str]', sep: str=None) -> Set:
return ret
def _valid_directory(path: 'os.PathLike[str]') -> 'os.PathLike[str]':
def valid_directory(path: str) -> str:
if not os.path.isdir(path):
raise argparse.ArgumentTypeError('{} is not a valid directory!'.format(path))
return path

View File

@ -1,6 +1,6 @@
#!/usr/bin/env python
#
# SPDX-FileCopyrightText: 2018-2021 Espressif Systems (Shanghai) CO LTD
# SPDX-FileCopyrightText: 2018-2023 Espressif Systems (Shanghai) CO LTD
# SPDX-License-Identifier: Apache-2.0
from __future__ import print_function, unicode_literals
@ -8,21 +8,18 @@ from __future__ import print_function, unicode_literals
import argparse
import os
import re
import subprocess
import sys
from io import open
from idf_ci_utils import IDF_PATH, get_submodule_dirs
# regular expression for matching Kconfig files
RE_KCONFIG = r'^Kconfig(\.projbuild)?(\.in)?$'
# ouput file with suggestions will get this suffix
OUTPUT_SUFFIX = '.new'
# ignored directories (makes sense only when run on IDF_PATH)
# Note: IGNORE_DIRS is a tuple in order to be able to use it directly with the startswith() built-in function which
# Note: ignore_dirs is a tuple in order to be able to use it directly with the startswith() built-in function which
# accepts tuples but no lists.
IGNORE_DIRS = (
IGNORE_DIRS: tuple = (
# Kconfigs from submodules need to be ignored:
os.path.join(IDF_PATH, 'components', 'mqtt', 'esp-mqtt'),
# Test Kconfigs are also ignored
@ -30,438 +27,62 @@ IGNORE_DIRS = (
os.path.join(IDF_PATH, 'tools', 'kconfig_new', 'test'),
)
SPACES_PER_INDENT = 4
CONFIG_NAME_MAX_LENGTH = 40
CONFIG_NAME_MIN_PREFIX_LENGTH = 3
# The checker will not fail if it encounters this string (it can be used for temporarily resolve conflicts)
RE_NOERROR = re.compile(r'\s+#\s+NOERROR\s+$')
# list or rules for lines
LINE_ERROR_RULES = [
# (regular expression for finding, error message, correction)
(re.compile(r'\t'), 'tabulators should be replaced by spaces', r' ' * SPACES_PER_INDENT),
(re.compile(r'\s+\n'), 'trailing whitespaces should be removed', r'\n'),
(re.compile(r'.{120}'), 'line should be shorter than 120 characters', None),
]
ignore_dirs: tuple = IGNORE_DIRS
class InputError(RuntimeError):
"""
Represents and error on the input
"""
def __init__(self, path, line_number, error_msg, suggested_line):
super(InputError, self).__init__('{}:{}: {}'.format(path, line_number, error_msg))
self.suggested_line = suggested_line
class BaseChecker(object):
"""
Base class for all checker objects
"""
def __init__(self, path_in_idf):
self.path_in_idf = path_in_idf
def __enter__(self):
return self
def __exit__(self, type, value, traceback):
pass
class SourceChecker(BaseChecker):
# allow to source only files which will be also checked by the script
# Note: The rules are complex and the LineRuleChecker cannot be used
def process_line(self, line, line_number):
m = re.search(r'^\s*[ro]{0,2}source(\s*)"([^"]+)"', line)
if m:
if len(m.group(1)) == 0:
raise InputError(self.path_in_idf, line_number, '"source" has to been followed by space',
line.replace('source', 'source '))
path = m.group(2)
filename = os.path.basename(path)
if path in ['$COMPONENT_KCONFIGS_SOURCE_FILE', '$COMPONENT_KCONFIGS_PROJBUILD_SOURCE_FILE']:
pass
elif not filename.startswith('Kconfig.'):
raise InputError(self.path_in_idf, line_number, 'only filenames starting with Kconfig.* can be sourced',
line.replace(path, os.path.join(os.path.dirname(path), 'Kconfig.' + filename)))
class LineRuleChecker(BaseChecker):
"""
checks LINE_ERROR_RULES for each line
"""
def process_line(self, line, line_number):
suppress_errors = RE_NOERROR.search(line) is not None
errors = []
for rule in LINE_ERROR_RULES:
m = rule[0].search(line)
if m:
if suppress_errors:
# just print but no failure
e = InputError(self.path_in_idf, line_number, rule[1], line)
print(e)
else:
errors.append(rule[1])
if rule[2]:
line = rule[0].sub(rule[2], line)
if len(errors) > 0:
raise InputError(self.path_in_idf, line_number, '; '.join(errors), line)
class IndentAndNameChecker(BaseChecker):
"""
checks the indentation of each line and configuration names
"""
def __init__(self, path_in_idf, debug=False):
super(IndentAndNameChecker, self).__init__(path_in_idf)
self.debug = debug
self.min_prefix_length = CONFIG_NAME_MIN_PREFIX_LENGTH
# stack of the nested menuconfig items, e.g. ['mainmenu', 'menu', 'config']
self.level_stack = []
# stack common prefixes of configs
self.prefix_stack = []
# if the line ends with '\' then we force the indent of the next line
self.force_next_indent = 0
# menu items which increase the indentation of the next line
self.re_increase_level = re.compile(r'''^\s*
(
(menu(?!config))
|(mainmenu)
|(choice)
|(config)
|(menuconfig)
|(help)
|(if)
|(source)
|(osource)
|(rsource)
|(orsource)
)
''', re.X)
# closing menu items which decrease the indentation
self.re_decrease_level = re.compile(r'''^\s*
(
(endmenu)
|(endchoice)
|(endif)
)
''', re.X)
# matching beginning of the closing menuitems
self.pair_dic = {'endmenu': 'menu',
'endchoice': 'choice',
'endif': 'if',
}
# regex for config names
self.re_name = re.compile(r'''^
(
(?:config)
|(?:menuconfig)
|(?:choice)
)\s+
(\w+)
''', re.X)
# regex for new prefix stack
self.re_new_stack = re.compile(r'''^
(
(?:menu(?!config))
|(?:mainmenu)
|(?:choice)
)
''', re.X)
def __exit__(self, type, value, traceback):
super(IndentAndNameChecker, self).__exit__(type, value, traceback)
if len(self.prefix_stack) > 0:
self.check_common_prefix('', 'EOF')
if len(self.prefix_stack) != 0:
if self.debug:
print(self.prefix_stack)
raise RuntimeError("Prefix stack should be empty. Perhaps a menu/choice hasn't been closed")
def del_from_level_stack(self, count):
""" delete count items from the end of the level_stack """
if count > 0:
# del self.level_stack[-0:] would delete everything and we expect not to delete anything for count=0
del self.level_stack[-count:]
def update_level_for_inc_pattern(self, new_item):
if self.debug:
print('level+', new_item, ': ', self.level_stack, end=' -> ')
# "config" and "menuconfig" don't have a closing pair. So if new_item is an item which need to be indented
# outside the last "config" or "menuconfig" then we need to find to a parent where it belongs
if new_item in ['config', 'menuconfig', 'menu', 'choice', 'if', 'source', 'rsource', 'osource', 'orsource']:
# item is not belonging to a previous "config" or "menuconfig" so need to indent to parent
for i, item in enumerate(reversed(self.level_stack)):
if item in ['menu', 'mainmenu', 'choice', 'if']:
# delete items ("config", "menuconfig", "help") until the appropriate parent
self.del_from_level_stack(i)
break
else:
# delete everything when configs are at top level without a parent menu, mainmenu...
self.del_from_level_stack(len(self.level_stack))
self.level_stack.append(new_item)
if self.debug:
print(self.level_stack)
# The new indent is for the next line. Use the old one for the current line:
return len(self.level_stack) - 1
def update_level_for_dec_pattern(self, new_item):
if self.debug:
print('level-', new_item, ': ', self.level_stack, end=' -> ')
target = self.pair_dic[new_item]
for i, item in enumerate(reversed(self.level_stack)):
# find the matching beginning for the closing item in reverse-order search
# Note: "menuconfig", "config" and "help" don't have closing pairs and they are also on the stack. Now they
# will be deleted together with the "menu" or "choice" we are closing.
if item == target:
i += 1 # delete also the matching beginning
if self.debug:
print('delete ', i, end=' -> ')
self.del_from_level_stack(i)
break
if self.debug:
print(self.level_stack)
return len(self.level_stack)
def check_name_and_update_prefix(self, line, line_number):
m = self.re_name.search(line)
if m:
name = m.group(2)
name_length = len(name)
if name_length > CONFIG_NAME_MAX_LENGTH:
raise InputError(self.path_in_idf, line_number,
'{} is {} characters long and it should be {} at most'
''.format(name, name_length, CONFIG_NAME_MAX_LENGTH),
line + '\n') # no suggested correction for this
if len(self.prefix_stack) == 0:
self.prefix_stack.append(name)
elif self.prefix_stack[-1] is None:
self.prefix_stack[-1] = name
else:
# this has nothing common with paths but the algorithm can be used for this also
self.prefix_stack[-1] = os.path.commonprefix([self.prefix_stack[-1], name])
if self.debug:
print('prefix+', self.prefix_stack)
m = self.re_new_stack.search(line)
if m:
self.prefix_stack.append(None)
if self.debug:
print('prefix+', self.prefix_stack)
def check_common_prefix(self, line, line_number):
common_prefix = self.prefix_stack.pop()
if self.debug:
print('prefix-', self.prefix_stack)
if common_prefix is None:
return
common_prefix_len = len(common_prefix)
if common_prefix_len < self.min_prefix_length:
raise InputError(self.path_in_idf, line_number,
'The common prefix for the config names of the menu ending at this line is "{}".\n'
'\tAll config names in this menu should start with the same prefix of {} characters '
'or more.'.format(common_prefix, self.min_prefix_length),
line) # no suggested correction for this
if len(self.prefix_stack) > 0:
parent_prefix = self.prefix_stack[-1]
if parent_prefix is None:
# propagate to parent level where it will influence the prefix checking with the rest which might
# follow later on that level
self.prefix_stack[-1] = common_prefix
else:
if len(self.level_stack) > 0 and self.level_stack[-1] in ['mainmenu', 'menu']:
# the prefix from menu is not required to propagate to the children
return
if not common_prefix.startswith(parent_prefix):
raise InputError(self.path_in_idf, line_number,
'Common prefix "{}" should start with {}'
''.format(common_prefix, parent_prefix),
line) # no suggested correction for this
def process_line(self, line, line_number):
stripped_line = line.strip()
if len(stripped_line) == 0:
self.force_next_indent = 0
return
current_level = len(self.level_stack)
m = re.search(r'\S', line) # indent found as the first non-space character
if m:
current_indent = m.start()
else:
current_indent = 0
if current_level > 0 and self.level_stack[-1] == 'help':
if current_indent >= current_level * SPACES_PER_INDENT:
# this line belongs to 'help'
self.force_next_indent = 0
return
if self.force_next_indent > 0:
if current_indent != self.force_next_indent:
raise InputError(self.path_in_idf, line_number,
'Indentation consists of {} spaces instead of {}'.format(current_indent,
self.force_next_indent),
(' ' * self.force_next_indent) + line.lstrip())
else:
if not stripped_line.endswith('\\'):
self.force_next_indent = 0
return
elif stripped_line.endswith('\\') and stripped_line.startswith(('config', 'menuconfig', 'choice')):
raise InputError(self.path_in_idf, line_number,
'Line-wrap with backslash is not supported here',
line) # no suggestion for this
self.check_name_and_update_prefix(stripped_line, line_number)
m = self.re_increase_level.search(line)
if m:
current_level = self.update_level_for_inc_pattern(m.group(1))
else:
m = self.re_decrease_level.search(line)
if m:
new_item = m.group(1)
current_level = self.update_level_for_dec_pattern(new_item)
if new_item not in ['endif']:
# endif doesn't require to check the prefix because the items inside if/endif belong to the
# same prefix level
self.check_common_prefix(line, line_number)
expected_indent = current_level * SPACES_PER_INDENT
if stripped_line.endswith('\\'):
self.force_next_indent = expected_indent + SPACES_PER_INDENT
else:
self.force_next_indent = 0
if current_indent != expected_indent:
raise InputError(self.path_in_idf, line_number,
'Indentation consists of {} spaces instead of {}'.format(current_indent, expected_indent),
(' ' * expected_indent) + line.lstrip())
def valid_directory(path):
def valid_directory(path:str) -> str:
if not os.path.isdir(path):
raise argparse.ArgumentTypeError('{} is not a valid directory!'.format(path))
return path
return str(path)
def validate_kconfig_file(kconfig_full_path, verbose=False): # type: (str, bool) -> bool
suggestions_full_path = kconfig_full_path + OUTPUT_SUFFIX
fail = False
with open(kconfig_full_path, 'r', encoding='utf-8') as f, \
open(suggestions_full_path, 'w', encoding='utf-8', newline='\n') as f_o, \
LineRuleChecker(kconfig_full_path) as line_checker, \
SourceChecker(kconfig_full_path) as source_checker, \
IndentAndNameChecker(kconfig_full_path, debug=verbose) as indent_and_name_checker:
try:
for line_number, line in enumerate(f, start=1):
try:
for checker in [line_checker, indent_and_name_checker, source_checker]:
checker.process_line(line, line_number)
# The line is correct therefore we echo it to the output file
f_o.write(line)
except InputError as e:
print(e)
fail = True
f_o.write(e.suggested_line)
except UnicodeDecodeError:
raise ValueError('The encoding of {} is not Unicode.'.format(kconfig_full_path))
if fail:
print('\t{} has been saved with suggestions for resolving the issues.\n'
'\tPlease note that the suggestions can be wrong and '
'you might need to re-run the checker several times '
'for solving all issues'.format(suggestions_full_path))
print('\tPlease fix the errors and run {} for checking the correctness of '
'Kconfig files.'.format(os.path.abspath(__file__)))
return False
else:
print('{}: OK'.format(kconfig_full_path))
try:
os.remove(suggestions_full_path)
except Exception:
# not a serious error is when the file cannot be deleted
print('{} cannot be deleted!'.format(suggestions_full_path))
finally:
return True
def main():
parser = argparse.ArgumentParser(description='Kconfig style checker')
parser.add_argument('files', nargs='*',
help='Kconfig files')
parser.add_argument('--verbose', '-v', action='store_true',
help='Print more information (useful for debugging)')
parser.add_argument('--includes', '-d', nargs='*',
parser = argparse.ArgumentParser(description='Kconfig style checker')
parser.add_argument(
'files',
nargs='*',
help='Kconfig files to check (full paths separated by space)',
)
parser.add_argument(
'--exclude-submodules',
action='store_true',
help='Exclude submodules',
)
parser.add_argument(
'--includes',
'-d',
nargs='*',
help='Extra paths for recursively searching Kconfig files. (for example $IDF_PATH)',
type=valid_directory)
parser.add_argument('--exclude-submodules', action='store_true',
help='Exclude submodules')
args = parser.parse_args()
type=valid_directory
)
success_counter = 0
failure_counter = 0
ignore_counter = 0
args, unknown_args = parser.parse_known_args()
ignore_dirs = IGNORE_DIRS
if args.exclude_submodules:
# if the deprecated argument '--exclude-submodules' is used
if args.exclude_submodules:
ignore_dirs = ignore_dirs + tuple(get_submodule_dirs(full_path=True))
files = [os.path.abspath(file_path) for file_path in args.files]
files_to_check: list = []
if args.includes:
# if the deprecated argument '--includes' is used all valid paths are checked for KConfigs
# except IGNORE_DIRS and submodules (if exclude is given)
# paths to KConfig files are passed to esp-idf-kconfig kconfcheck tool
if args.includes:
for directory in args.includes:
for root, dirnames, filenames in os.walk(directory):
for filename in filenames:
full_path = os.path.join(root, filename)
if full_path.startswith(ignore_dirs):
continue
if re.search(RE_KCONFIG, filename):
files.append(full_path)
files_to_check.append(f'{full_path}')
elif re.search(RE_KCONFIG, filename, re.IGNORECASE):
# On Windows Kconfig files are working with different cases!
print('{}: Incorrect filename. The case should be "Kconfig"!'.format(full_path))
failure_counter += 1
for full_path in files:
if full_path.startswith(ignore_dirs):
print('{}: Ignored'.format(full_path))
ignore_counter += 1
continue
is_valid = validate_kconfig_file(full_path, args.verbose)
if is_valid:
success_counter += 1
else:
failure_counter += 1
if ignore_counter > 0:
print('{} files have been ignored.'.format(ignore_counter))
if success_counter > 0:
print('{} files have been successfully checked.'.format(success_counter))
if failure_counter > 0:
print('{} files have errors. Please take a look at the log.'.format(failure_counter))
return 1
if not files:
print('WARNING: no files specified. Please specify files or use '
'"--includes" to search Kconfig files recursively')
return 0
print(
'{}: Incorrect filename. The case should be "Kconfig"!'.format(
full_path
)
)
if __name__ == '__main__':
sys.exit(main())
sys.exit(subprocess.run([sys.executable, '-m', 'kconfcheck'] + files_to_check + unknown_args).returncode)

View File

@ -45,7 +45,7 @@ def get_mr_changed_files(source_branch: str) -> t.List[str]:
return []
git_output = subprocess.check_output(
['git', 'diff', '--name-only', f'origin/{mr.target_branch}...origin/{source_branch}']
['git', 'diff', '--name-only', '--diff-filter=d', f'origin/{mr.target_branch}...origin/{source_branch}']
).decode('utf8')
return [line.strip() for line in git_output.splitlines() if line.strip()]

View File

@ -79,7 +79,6 @@ tools/ci/mirror-submodule-update.sh
tools/ci/multirun_with_pyenv.sh
tools/ci/push_to_github.sh
tools/ci/test_autocomplete.py
tools/ci/test_check_kconfigs.py
tools/ci/test_configure_ci_environment.sh
tools/ci/test_reproducible_build.sh
tools/docker/entrypoint.sh

View File

@ -28,7 +28,6 @@ examples/system/ota/otatool/otatool_example.py
tools/ble/lib_ble_client.py
tools/ble/lib_gap.py
tools/ble/lib_gatt.py
tools/ci/check_kconfigs.py
tools/ci/python_packages/idf_http_server_test/adder.py
tools/ci/python_packages/idf_http_server_test/client.py
tools/ci/python_packages/idf_http_server_test/test.py
@ -56,7 +55,6 @@ tools/ci/python_packages/ttfw_idf/__init__.py
tools/ci/python_packages/ttfw_idf/unity_test_parser.py
tools/ci/python_packages/wifi_tools.py
tools/ci/test_autocomplete.py
tools/ci/test_check_kconfigs.py
tools/esp_app_trace/espytrace/apptrace.py
tools/esp_app_trace/espytrace/sysview.py
tools/esp_app_trace/logtrace_proc.py

View File

@ -1,241 +0,0 @@
#!/usr/bin/env python
#
# SPDX-FileCopyrightText: 2018-2021 Espressif Systems (Shanghai) CO LTD
# SPDX-License-Identifier: Apache-2.0
import unittest
from check_kconfigs import CONFIG_NAME_MAX_LENGTH, IndentAndNameChecker, InputError, LineRuleChecker, SourceChecker
class ApplyLine(object):
def apply_line(self, string):
self.checker.process_line(string + '\n', 0)
def expect_error(self, string, expect, cleanup=None):
try:
with self.assertRaises(InputError) as cm:
self.apply_line(string)
if expect:
self.assertEqual(cm.exception.suggested_line, expect + '\n')
finally:
if cleanup:
# cleanup of the previous failure
self.apply_line(cleanup)
def expt_success(self, string):
self.apply_line(string)
class TestLineRuleChecker(unittest.TestCase, ApplyLine):
def setUp(self):
self.checker = LineRuleChecker('Kconfig')
def tearDown(self):
pass
def test_tabulators(self):
self.expect_error('\ttest', expect=' test')
self.expect_error('\t test', expect=' test')
self.expect_error(' \ttest', expect=' test')
self.expect_error(' \t test', expect=' test')
self.expt_success(' test')
self.expt_success('test')
def test_trailing_whitespaces(self):
self.expect_error(' ', expect='')
self.expect_error(' ', expect='')
self.expect_error('test ', expect='test')
self.expt_success('test')
self.expt_success('')
def test_line_length(self):
self.expect_error('x' * 120, expect=None)
self.expt_success('x' * 119)
self.expt_success('')
class TestSourceChecker(unittest.TestCase, ApplyLine):
def setUp(self):
self.checker = SourceChecker('Kconfig')
def tearDown(self):
pass
def test_source_file_name(self):
self.expect_error('source "notKconfig.test"', expect='source "Kconfig.notKconfig.test"')
self.expect_error('source "Kconfig"', expect='source "Kconfig.Kconfig"')
self.expt_success('source "Kconfig.in"')
self.expt_success('source "/tmp/Kconfig.test"')
self.expt_success('source "/tmp/Kconfig.in"')
self.expect_error('source"Kconfig.in"', expect='source "Kconfig.in"')
self.expt_success('source "/tmp/Kconfig.in" # comment')
class TestIndentAndNameChecker(unittest.TestCase, ApplyLine):
def setUp(self):
self.checker = IndentAndNameChecker('Kconfig')
self.checker.min_prefix_length = 4
def tearDown(self):
self.checker.__exit__('Kconfig', None, None)
class TestIndent(TestIndentAndNameChecker):
def setUp(self):
super(TestIndent, self).setUp()
self.checker.min_prefix_length = 0 # prefixes are ignored in this test case
def test_indent_characters(self):
self.expt_success('menu "test"')
self.expect_error(' test', expect=' test')
self.expect_error(' test', expect=' test')
self.expect_error(' test', expect=' test')
self.expect_error(' test', expect=' test')
self.expt_success(' test')
self.expt_success(' test2')
self.expt_success(' config')
self.expect_error(' default', expect=' default')
self.expt_success(' help')
self.expect_error(' text', expect=' text')
self.expt_success(' help text')
self.expt_success(' menu')
self.expt_success(' endmenu')
self.expect_error(' choice', expect=' choice', cleanup=' endchoice')
self.expect_error(' choice', expect=' choice', cleanup=' endchoice')
self.expt_success(' choice')
self.expt_success(' endchoice')
self.expt_success(' config')
self.expt_success('endmenu')
def test_help_content(self):
self.expt_success('menu "test"')
self.expt_success(' config')
self.expt_success(' help')
self.expt_success(' description')
self.expt_success(' config keyword in the help')
self.expt_success(' menu keyword in the help')
self.expt_success(' menuconfig keyword in the help')
self.expt_success(' endmenu keyword in the help')
self.expt_success(' endmenu keyword in the help')
self.expt_success('') # newline in help
self.expt_success(' endmenu keyword in the help')
self.expect_error(' menu "real menu with wrong indent"',
expect=' menu "real menu with wrong indent"', cleanup=' endmenu')
self.expt_success('endmenu')
def test_mainmenu(self):
self.expt_success('mainmenu "test"')
self.expect_error('test', expect=' test')
self.expt_success(' not_a_keyword')
self.expt_success(' config')
self.expt_success(' menuconfig')
self.expect_error('test', expect=' test')
self.expect_error(' test', expect=' test')
self.expt_success(' menu')
self.expt_success(' endmenu')
def test_ifendif(self):
self.expt_success('menu "test"')
self.expt_success(' config')
self.expt_success(' help')
self.expect_error(' if', expect=' if', cleanup=' endif')
self.expt_success(' if')
self.expect_error(' config', expect=' config')
self.expt_success(' config')
self.expt_success(' help')
self.expt_success(' endif')
self.expt_success(' config')
self.expt_success('endmenu')
def test_config_without_menu(self):
self.expt_success('menuconfig')
self.expt_success(' help')
self.expt_success(' text')
self.expt_success('')
self.expt_success(' text')
self.expt_success('config')
self.expt_success(' help')
def test_source_after_config(self):
self.expt_success('menuconfig')
self.expt_success(' help')
self.expt_success(' text')
self.expect_error(' source', expect='source')
self.expt_success('source "Kconfig.in"')
def test_comment_after_config(self):
self.expt_success('menuconfig')
self.expt_success(' # comment')
self.expt_success(' help')
self.expt_success(' text')
self.expect_error('# comment', expect=' # comment')
self.expt_success(' # second not realcomment"')
class TestName(TestIndentAndNameChecker):
def setUp(self):
super(TestName, self).setUp()
self.checker.min_prefix_length = 0 # prefixes are ignored in this test case
def test_name_length(self):
max_length = CONFIG_NAME_MAX_LENGTH
too_long = max_length + 1
self.expt_success('menu "test"')
self.expt_success(' config ABC')
self.expt_success(' config ' + ('X' * max_length))
self.expect_error(' config ' + ('X' * too_long), expect=None)
self.expt_success(' menuconfig ' + ('X' * max_length))
self.expect_error(' menuconfig ' + ('X' * too_long), expect=None)
self.expt_success(' choice ' + ('X' * max_length))
self.expect_error(' choice ' + ('X' * too_long), expect=None)
self.expt_success('endmenu')
class TestPrefix(TestIndentAndNameChecker):
def test_prefix_len(self):
self.expt_success('menu "test"')
self.expt_success(' config ABC_1')
self.expt_success(' config ABC_2')
self.expt_success(' config ABC_DEBUG')
self.expt_success(' config ABC_ANOTHER')
self.expt_success('endmenu')
self.expt_success('menu "test2"')
self.expt_success(' config A')
self.expt_success(' config B')
self.expect_error('endmenu', expect=None)
def test_choices(self):
self.expt_success('menu "test"')
self.expt_success(' choice ASSERTION_LEVEL')
self.expt_success(' config ASSERTION_DEBUG')
self.expt_success(' config ASSERTION_RELEASE')
self.expt_success(' menuconfig ASSERTION_XY')
self.expt_success(' endchoice')
self.expt_success(' choice DEBUG')
self.expt_success(' config DE_1')
self.expt_success(' config DE_2')
self.expect_error(' endchoice', expect=None)
self.expect_error('endmenu', expect=None)
def test_nested_menu(self):
self.expt_success('menu "test"')
self.expt_success(' config DOESNT_MATTER')
self.expt_success(' menu "inner menu"')
self.expt_success(' config MENUOP_1')
self.expt_success(' config MENUOP_2')
self.expt_success(' config MENUOP_3')
self.expt_success(' endmenu')
self.expt_success('endmenu')
def test_nested_ifendif(self):
self.expt_success('menu "test"')
self.expt_success(' config MENUOP_1')
self.expt_success(' if MENUOP_1')
self.expt_success(' config MENUOP_2')
self.expt_success(' endif')
self.expt_success('endmenu')
if __name__ == '__main__':
unittest.main()