Merge branch 'feature/check_kconfig' into 'master'

Check the style of Kconfig files

See merge request idf/esp-idf!3971
This commit is contained in:
Ivan Grokhotkov 2019-01-11 21:14:27 +08:00
commit 72b3c6d6b8
6 changed files with 1040 additions and 327 deletions

View file

@ -731,6 +731,23 @@ check_python_style:
# run it only under Python 3 (it is very slow under Python 2) # run it only under Python 3 (it is very slow under Python 2)
- ${IDF_PATH}/tools/ci/multirun_with_pyenv.sh -p 3.4.8 python -m flake8 --config=$IDF_PATH/.flake8 --output-file=flake8_output.txt --tee --benchmark $IDF_PATH - ${IDF_PATH}/tools/ci/multirun_with_pyenv.sh -p 3.4.8 python -m flake8 --config=$IDF_PATH/.flake8 --output-file=flake8_output.txt --tee --benchmark $IDF_PATH
check_kconfigs:
<<: *check_job_template
before_script: *do_nothing_before
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:
- ${IDF_PATH}/tools/ci/multirun_with_pyenv.sh ${IDF_PATH}/tools/test_check_kconfigs.py
- ${IDF_PATH}/tools/check_kconfigs.py
check_ut_cmake_make: check_ut_cmake_make:
stage: check stage: check
image: $CI_DOCKER_REGISTRY/esp32-ci-env$BOT_DOCKER_IMAGE_TAG image: $CI_DOCKER_REGISTRY/esp32-ci-env$BOT_DOCKER_IMAGE_TAG

95
Kconfig
View file

@ -4,35 +4,35 @@
# #
mainmenu "Espressif IoT Development Framework Configuration" mainmenu "Espressif IoT Development Framework Configuration"
config IDF_CMAKE config IDF_CMAKE
bool bool
option env="IDF_CMAKE" option env="IDF_CMAKE"
# A proxy to get environment variable $IDF_TARGET config IDF_TARGET_ENV
config IDF_TARGET_ENV # A proxy to get environment variable $IDF_TARGET
string string
option env="IDF_TARGET" option env="IDF_TARGET"
# This option records the IDF target when sdkconfig is generated the first time. config IDF_TARGET
# It is not updated if environment variable $IDF_TARGET changes later, and # This option records the IDF target when sdkconfig is generated the first time.
# the build system is responsible for detecting the mismatch between # It is not updated if environment variable $IDF_TARGET changes later, and
# CONFIG_IDF_TARGET and $IDF_TARGET. # the build system is responsible for detecting the mismatch between
config IDF_TARGET # CONFIG_IDF_TARGET and $IDF_TARGET.
string string
default "IDF_TARGET_NOT_SET" if IDF_TARGET_ENV="" default "IDF_TARGET_NOT_SET" if IDF_TARGET_ENV=""
default IDF_TARGET_ENV default IDF_TARGET_ENV
menu "SDK tool configuration" menu "SDK tool configuration"
config TOOLPREFIX config TOOLPREFIX
string "Compiler toolchain path/prefix" string "Compiler toolchain path/prefix"
default "xtensa-esp32-elf-" default "xtensa-esp32-elf-"
help help
The prefix/path that is used to call the toolchain. The default setting assumes The prefix/path that is used to call the toolchain. The default setting assumes
a crosstool-ng gcc setup that is in your PATH. a crosstool-ng gcc setup that is in your PATH.
config PYTHON config PYTHON
string "Python 2 interpreter" string "Python 2 interpreter"
depends on !IDF_CMAKE depends on !IDF_CMAKE
default "python" default "python"
@ -43,7 +43,7 @@ config PYTHON
(Note: This option is used with the GNU Make build system only, not idf.py (Note: This option is used with the GNU Make build system only, not idf.py
or CMake-based builds.) or CMake-based builds.)
config MAKE_WARN_UNDEFINED_VARIABLES config MAKE_WARN_UNDEFINED_VARIABLES
bool "'make' warns on undefined variables" bool "'make' warns on undefined variables"
default "y" default "y"
help help
@ -54,13 +54,13 @@ config MAKE_WARN_UNDEFINED_VARIABLES
or otherwise missing, but it can be unwanted if you have Makefiles which or otherwise missing, but it can be unwanted if you have Makefiles which
depend on undefined variables expanding to an empty string. depend on undefined variables expanding to an empty string.
endmenu # SDK tool configuration endmenu # SDK tool configuration
source "$COMPONENT_KCONFIGS_PROJBUILD" source "$COMPONENT_KCONFIGS_PROJBUILD"
menu "Compiler options" menu "Compiler options"
choice OPTIMIZATION_COMPILER choice OPTIMIZATION_COMPILER
prompt "Optimization Level" prompt "Optimization Level"
default OPTIMIZATION_LEVEL_DEBUG default OPTIMIZATION_LEVEL_DEBUG
help help
@ -76,13 +76,13 @@ choice OPTIMIZATION_COMPILER
in project makefile, before including $(IDF_PATH)/make/project.mk. Note that in project makefile, before including $(IDF_PATH)/make/project.mk. Note that
custom optimization levels may be unsupported. custom optimization levels may be unsupported.
config OPTIMIZATION_LEVEL_DEBUG config OPTIMIZATION_LEVEL_DEBUG
bool "Debug (-Og)" bool "Debug (-Og)"
config OPTIMIZATION_LEVEL_RELEASE config OPTIMIZATION_LEVEL_RELEASE
bool "Release (-Os)" bool "Release (-Os)"
endchoice endchoice
choice OPTIMIZATION_ASSERTION_LEVEL choice OPTIMIZATION_ASSERTION_LEVEL
prompt "Assertion level" prompt "Assertion level"
default OPTIMIZATION_ASSERTIONS_ENABLED default OPTIMIZATION_ASSERTIONS_ENABLED
help help
@ -96,40 +96,40 @@ choice OPTIMIZATION_ASSERTION_LEVEL
- Disabled entirely (not recommended for most configurations.) -DNDEBUG is added - Disabled entirely (not recommended for most configurations.) -DNDEBUG is added
to CPPFLAGS in this case. to CPPFLAGS in this case.
config OPTIMIZATION_ASSERTIONS_ENABLED config OPTIMIZATION_ASSERTIONS_ENABLED
prompt "Enabled" prompt "Enabled"
bool bool
help help
Enable assertions. Assertion content and line number will be printed on failure. Enable assertions. Assertion content and line number will be printed on failure.
config OPTIMIZATION_ASSERTIONS_SILENT config OPTIMIZATION_ASSERTIONS_SILENT
prompt "Silent (saves code size)" prompt "Silent (saves code size)"
bool bool
help help
Enable silent assertions. Failed assertions will abort(), user needs to Enable silent assertions. Failed assertions will abort(), user needs to
use the aborting address to find the line number with the failed assertion. use the aborting address to find the line number with the failed assertion.
config OPTIMIZATION_ASSERTIONS_DISABLED config OPTIMIZATION_ASSERTIONS_DISABLED
prompt "Disabled (sets -DNDEBUG)" prompt "Disabled (sets -DNDEBUG)"
bool bool
help help
If assertions are disabled, -DNDEBUG is added to CPPFLAGS. If assertions are disabled, -DNDEBUG is added to CPPFLAGS.
endchoice # assertions endchoice # assertions
menuconfig CXX_EXCEPTIONS menuconfig CXX_EXCEPTIONS
bool "Enable C++ exceptions" bool "Enable C++ exceptions"
default n default n
help help
Enabling this option compiles all IDF C++ files with exception support enabled. Enabling this option compiles all IDF C++ files with exception support enabled.
Disabling this option disables C++ exception support in all compiled files, and any libstdc++ code which throws Disabling this option disables C++ exception support in all compiled files, and any libstdc++ code
an exception will abort instead. which throws an exception will abort instead.
Enabling this option currently adds an additional ~500 bytes of heap overhead Enabling this option currently adds an additional ~500 bytes of heap overhead
when an exception is thrown in user code for the first time. when an exception is thrown in user code for the first time.
config CXX_EXCEPTIONS_EMG_POOL_SIZE config CXX_EXCEPTIONS_EMG_POOL_SIZE
int "Emergency Pool Size" int "Emergency Pool Size"
default 0 default 0
depends on CXX_EXCEPTIONS depends on CXX_EXCEPTIONS
@ -137,7 +137,7 @@ config CXX_EXCEPTIONS_EMG_POOL_SIZE
Size (in bytes) of the emergency memory pool for C++ exceptions. This pool will be used to allocate Size (in bytes) of the emergency memory pool for C++ exceptions. This pool will be used to allocate
memory for thrown exceptions when there is not enough memory on the heap. memory for thrown exceptions when there is not enough memory on the heap.
choice STACK_CHECK_MODE choice STACK_CHECK_MODE
prompt "Stack smashing protection mode" prompt "Stack smashing protection mode"
default STACK_CHECK_NONE default STACK_CHECK_NONE
help help
@ -146,12 +146,12 @@ choice STACK_CHECK_MODE
The guards are initialized when a function is entered and then checked when the function exits. The guards are initialized when a function is entered and then checked when the function exits.
If a guard check fails, program is halted. Protection has the following modes: If a guard check fails, program is halted. Protection has the following modes:
- In NORMAL mode (GCC flag: -fstack-protector) only functions that call alloca, - In NORMAL mode (GCC flag: -fstack-protector) only functions that call alloca, and functions with
and functions with buffers larger than 8 bytes are protected. buffers larger than 8 bytes are protected.
- STRONG mode (GCC flag: -fstack-protector-strong) is like NORMAL, but includes - STRONG mode (GCC flag: -fstack-protector-strong) is like NORMAL, but includes additional functions
additional functions to be protected -- those that have local array definitions, to be protected -- those that have local array definitions, or have references to local frame
or have references to local frame addresses. addresses.
- In OVERALL mode (GCC flag: -fstack-protector-all) all functions are protected. - In OVERALL mode (GCC flag: -fstack-protector-all) all functions are protected.
@ -162,23 +162,23 @@ choice STACK_CHECK_MODE
- coverage: NORMAL < STRONG < OVERALL - coverage: NORMAL < STRONG < OVERALL
config STACK_CHECK_NONE config STACK_CHECK_NONE
bool "None" bool "None"
config STACK_CHECK_NORM config STACK_CHECK_NORM
bool "Normal" bool "Normal"
config STACK_CHECK_STRONG config STACK_CHECK_STRONG
bool "Strong" bool "Strong"
config STACK_CHECK_ALL config STACK_CHECK_ALL
bool "Overall" bool "Overall"
endchoice endchoice
config STACK_CHECK config STACK_CHECK
bool bool
default !STACK_CHECK_NONE default !STACK_CHECK_NONE
help help
Stack smashing protection. Stack smashing protection.
config WARN_WRITE_STRINGS config WARN_WRITE_STRINGS
bool "Enable -Wwrite-strings warning flag" bool "Enable -Wwrite-strings warning flag"
default "n" default "n"
help help
@ -192,15 +192,16 @@ config WARN_WRITE_STRINGS
For C++, this warns about the deprecated conversion from string For C++, this warns about the deprecated conversion from string
literals to ``char *``. literals to ``char *``.
config DISABLE_GCC8_WARNINGS config DISABLE_GCC8_WARNINGS
bool "Disable new warnings introduced in GCC 6 - 8" bool "Disable new warnings introduced in GCC 6 - 8"
default "n" default "n"
help help
Enable this option if using GCC 6 or newer, and wanting to disable warnings which don't appear with GCC 5. Enable this option if using GCC 6 or newer, and wanting to disable warnings which don't appear with
GCC 5.
endmenu # Compiler Options endmenu # Compiler Options
menu "Component config" menu "Component config"
source "$COMPONENT_KCONFIGS" source "$COMPONENT_KCONFIGS"
endmenu endmenu

View file

@ -1,19 +1,19 @@
menu "Application Level Tracing" menu "Application Level Tracing"
choice ESP32_APPTRACE_DESTINATION choice ESP32_APPTRACE_DESTINATION
prompt "Data Destination" prompt "Data Destination"
default ESP32_APPTRACE_DEST_NONE default ESP32_APPTRACE_DEST_NONE
help help
Select destination for application trace: trace memory or none (to disable). Select destination for application trace: trace memory or none (to disable).
config ESP32_APPTRACE_DEST_TRAX config ESP32_APPTRACE_DEST_TRAX
bool "Trace memory" bool "Trace memory"
select ESP32_APPTRACE_ENABLE select ESP32_APPTRACE_ENABLE
config ESP32_APPTRACE_DEST_NONE config ESP32_APPTRACE_DEST_NONE
bool "None" bool "None"
endchoice endchoice
config ESP32_APPTRACE_ENABLE config ESP32_APPTRACE_ENABLE
bool bool
depends on !ESP32_TRAX depends on !ESP32_TRAX
select MEMMAP_TRACEMEM select MEMMAP_TRACEMEM
@ -22,13 +22,13 @@ config ESP32_APPTRACE_ENABLE
help help
Enables/disable application tracing module. Enables/disable application tracing module.
config ESP32_APPTRACE_LOCK_ENABLE config ESP32_APPTRACE_LOCK_ENABLE
bool bool
default !SYSVIEW_ENABLE default !SYSVIEW_ENABLE
help help
Enables/disable application tracing module internal sync lock. Enables/disable application tracing module internal sync lock.
config ESP32_APPTRACE_ONPANIC_HOST_FLUSH_TMO config ESP32_APPTRACE_ONPANIC_HOST_FLUSH_TMO
int "Timeout for flushing last trace data to host on panic" int "Timeout for flushing last trace data to host on panic"
depends on ESP32_APPTRACE_ENABLE depends on ESP32_APPTRACE_ENABLE
range -1 5000 range -1 5000
@ -37,7 +37,7 @@ config ESP32_APPTRACE_ONPANIC_HOST_FLUSH_TMO
Timeout for flushing last trace data to host in case of panic. In ms. Timeout for flushing last trace data to host in case of panic. In ms.
Use -1 to disable timeout and wait forever. Use -1 to disable timeout and wait forever.
config ESP32_APPTRACE_POSTMORTEM_FLUSH_TRAX_THRESH config ESP32_APPTRACE_POSTMORTEM_FLUSH_TRAX_THRESH
int "Threshold for flushing last trace data to host on panic" int "Threshold for flushing last trace data to host on panic"
depends on ESP32_APPTRACE_DEST_TRAX depends on ESP32_APPTRACE_DEST_TRAX
range 0 16384 range 0 16384
@ -46,7 +46,7 @@ config ESP32_APPTRACE_POSTMORTEM_FLUSH_TRAX_THRESH
Threshold for flushing last trace data to host on panic in post-mortem mode. Threshold for flushing last trace data to host on panic in post-mortem mode.
This is minimal amount of data needed to perform flush. In bytes. This is minimal amount of data needed to perform flush. In bytes.
config ESP32_APPTRACE_PENDING_DATA_SIZE_MAX config ESP32_APPTRACE_PENDING_DATA_SIZE_MAX
int "Size of the pending data buffer" int "Size of the pending data buffer"
depends on ESP32_APPTRACE_DEST_TRAX depends on ESP32_APPTRACE_DEST_TRAX
default 0 default 0
@ -55,16 +55,16 @@ config ESP32_APPTRACE_PENDING_DATA_SIZE_MAX
the time critical code (scheduler, ISRs etc). If this parameter is 0 then the time critical code (scheduler, ISRs etc). If this parameter is 0 then
events will be discarded when main HW buffer is full. events will be discarded when main HW buffer is full.
menu "FreeRTOS SystemView Tracing" menu "FreeRTOS SystemView Tracing"
depends on ESP32_APPTRACE_ENABLE depends on ESP32_APPTRACE_ENABLE
config SYSVIEW_ENABLE config SYSVIEW_ENABLE
bool "SystemView Tracing Enable" bool "SystemView Tracing Enable"
depends on ESP32_APPTRACE_ENABLE depends on ESP32_APPTRACE_ENABLE
default n default n
help help
Enables supporrt for SEGGER SystemView tracing functionality. Enables supporrt for SEGGER SystemView tracing functionality.
choice SYSVIEW_TS_SOURCE choice SYSVIEW_TS_SOURCE
prompt "Timer to use as timestamp source" prompt "Timer to use as timestamp source"
depends on SYSVIEW_ENABLE depends on SYSVIEW_ENABLE
default SYSVIEW_TS_SOURCE_CCOUNT if FREERTOS_UNICORE && !PM_ENABLE default SYSVIEW_TS_SOURCE_CCOUNT if FREERTOS_UNICORE && !PM_ENABLE
@ -74,125 +74,125 @@ choice SYSVIEW_TS_SOURCE
SystemView needs to use a hardware timer as the source of timestamps SystemView needs to use a hardware timer as the source of timestamps
when tracing. This option selects the timer for it. when tracing. This option selects the timer for it.
config SYSVIEW_TS_SOURCE_CCOUNT config SYSVIEW_TS_SOURCE_CCOUNT
bool "CPU cycle counter (CCOUNT)" bool "CPU cycle counter (CCOUNT)"
depends on FREERTOS_UNICORE && !PM_ENABLE depends on FREERTOS_UNICORE && !PM_ENABLE
config SYSVIEW_TS_SOURCE_TIMER_00 config SYSVIEW_TS_SOURCE_TIMER_00
bool "Timer 0, Group 0" bool "Timer 0, Group 0"
depends on !PM_ENABLE depends on !PM_ENABLE
config SYSVIEW_TS_SOURCE_TIMER_01 config SYSVIEW_TS_SOURCE_TIMER_01
bool "Timer 1, Group 0" bool "Timer 1, Group 0"
depends on !PM_ENABLE depends on !PM_ENABLE
config SYSVIEW_TS_SOURCE_TIMER_10 config SYSVIEW_TS_SOURCE_TIMER_10
bool "Timer 0, Group 1" bool "Timer 0, Group 1"
depends on !PM_ENABLE depends on !PM_ENABLE
config SYSVIEW_TS_SOURCE_TIMER_11 config SYSVIEW_TS_SOURCE_TIMER_11
bool "Timer 1, Group 1" bool "Timer 1, Group 1"
depends on !PM_ENABLE depends on !PM_ENABLE
config SYSVIEW_TS_SOURCE_ESP_TIMER config SYSVIEW_TS_SOURCE_ESP_TIMER
bool "esp_timer high resolution timer" bool "esp_timer high resolution timer"
endchoice endchoice
config SYSVIEW_EVT_OVERFLOW_ENABLE config SYSVIEW_EVT_OVERFLOW_ENABLE
bool "Trace Buffer Overflow Event" bool "Trace Buffer Overflow Event"
depends on SYSVIEW_ENABLE depends on SYSVIEW_ENABLE
default y default y
help help
Enables "Trace Buffer Overflow" event. Enables "Trace Buffer Overflow" event.
config SYSVIEW_EVT_ISR_ENTER_ENABLE config SYSVIEW_EVT_ISR_ENTER_ENABLE
bool "ISR Enter Event" bool "ISR Enter Event"
depends on SYSVIEW_ENABLE depends on SYSVIEW_ENABLE
default y default y
help help
Enables "ISR Enter" event. Enables "ISR Enter" event.
config SYSVIEW_EVT_ISR_EXIT_ENABLE config SYSVIEW_EVT_ISR_EXIT_ENABLE
bool "ISR Exit Event" bool "ISR Exit Event"
depends on SYSVIEW_ENABLE depends on SYSVIEW_ENABLE
default y default y
help help
Enables "ISR Exit" event. Enables "ISR Exit" event.
config SYSVIEW_EVT_ISR_TO_SCHEDULER_ENABLE config SYSVIEW_EVT_ISR_TO_SCHEDULER_ENABLE
bool "ISR Exit to Scheduler Event" bool "ISR Exit to Scheduler Event"
depends on SYSVIEW_ENABLE depends on SYSVIEW_ENABLE
default y default y
help help
Enables "ISR to Scheduler" event. Enables "ISR to Scheduler" event.
config SYSVIEW_EVT_TASK_START_EXEC_ENABLE config SYSVIEW_EVT_TASK_START_EXEC_ENABLE
bool "Task Start Execution Event" bool "Task Start Execution Event"
depends on SYSVIEW_ENABLE depends on SYSVIEW_ENABLE
default y default y
help help
Enables "Task Start Execution" event. Enables "Task Start Execution" event.
config SYSVIEW_EVT_TASK_STOP_EXEC_ENABLE config SYSVIEW_EVT_TASK_STOP_EXEC_ENABLE
bool "Task Stop Execution Event" bool "Task Stop Execution Event"
depends on SYSVIEW_ENABLE depends on SYSVIEW_ENABLE
default y default y
help help
Enables "Task Stop Execution" event. Enables "Task Stop Execution" event.
config SYSVIEW_EVT_TASK_START_READY_ENABLE config SYSVIEW_EVT_TASK_START_READY_ENABLE
bool "Task Start Ready State Event" bool "Task Start Ready State Event"
depends on SYSVIEW_ENABLE depends on SYSVIEW_ENABLE
default y default y
help help
Enables "Task Start Ready State" event. Enables "Task Start Ready State" event.
config SYSVIEW_EVT_TASK_STOP_READY_ENABLE config SYSVIEW_EVT_TASK_STOP_READY_ENABLE
bool "Task Stop Ready State Event" bool "Task Stop Ready State Event"
depends on SYSVIEW_ENABLE depends on SYSVIEW_ENABLE
default y default y
help help
Enables "Task Stop Ready State" event. Enables "Task Stop Ready State" event.
config SYSVIEW_EVT_TASK_CREATE_ENABLE config SYSVIEW_EVT_TASK_CREATE_ENABLE
bool "Task Create Event" bool "Task Create Event"
depends on SYSVIEW_ENABLE depends on SYSVIEW_ENABLE
default y default y
help help
Enables "Task Create" event. Enables "Task Create" event.
config SYSVIEW_EVT_TASK_TERMINATE_ENABLE config SYSVIEW_EVT_TASK_TERMINATE_ENABLE
bool "Task Terminate Event" bool "Task Terminate Event"
depends on SYSVIEW_ENABLE depends on SYSVIEW_ENABLE
default y default y
help help
Enables "Task Terminate" event. Enables "Task Terminate" event.
config SYSVIEW_EVT_IDLE_ENABLE config SYSVIEW_EVT_IDLE_ENABLE
bool "System Idle Event" bool "System Idle Event"
depends on SYSVIEW_ENABLE depends on SYSVIEW_ENABLE
default y default y
help help
Enables "System Idle" event. Enables "System Idle" event.
config SYSVIEW_EVT_TIMER_ENTER_ENABLE config SYSVIEW_EVT_TIMER_ENTER_ENABLE
bool "Timer Enter Event" bool "Timer Enter Event"
depends on SYSVIEW_ENABLE depends on SYSVIEW_ENABLE
default y default y
help help
Enables "Timer Enter" event. Enables "Timer Enter" event.
config SYSVIEW_EVT_TIMER_EXIT_ENABLE config SYSVIEW_EVT_TIMER_EXIT_ENABLE
bool "Timer Exit Event" bool "Timer Exit Event"
depends on SYSVIEW_ENABLE depends on SYSVIEW_ENABLE
default y default y
help help
Enables "Timer Exit" event. Enables "Timer Exit" event.
endmenu endmenu
config ESP32_GCOV_ENABLE config ESP32_GCOV_ENABLE
bool "GCOV to Host Enable" bool "GCOV to Host Enable"
depends on ESP32_DEBUG_STUBS_ENABLE && ESP32_APPTRACE_ENABLE && !SYSVIEW_ENABLE depends on ESP32_DEBUG_STUBS_ENABLE && ESP32_APPTRACE_ENABLE && !SYSVIEW_ENABLE
default y default y

484
tools/check_kconfigs.py Executable file
View file

@ -0,0 +1,484 @@
#!/usr/bin/env python
#
# Copyright 2018 Espressif Systems (Shanghai) PTE LTD
#
# Licensed under the Apache License, Version 2.0 (the "License");
# you may not use this file except in compliance with the License.
# You may obtain a copy of the License at
#
# http://www.apache.org/licenses/LICENSE-2.0
#
# Unless required by applicable law or agreed to in writing, software
# distributed under the License is distributed on an "AS IS" BASIS,
# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
# See the License for the specific language governing permissions and
# limitations under the License.
from __future__ import print_function
from __future__ import unicode_literals
import os
import sys
import re
import argparse
from io import open
# regular expression search object for matching Kconfig files
RE_KCONFIG = re.compile(r'^Kconfig(?:\.projbuild)?$')
# 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
# accepts tuples but no lists.
IGNORE_DIRS = (
# Kconfigs from submodules need to be ignored:
os.path.join('components', 'mqtt', 'esp-mqtt'),
# Temporary (incompatibility) list:
os.path.join('components', 'app_update'),
os.path.join('components', 'aws_iot'),
os.path.join('components', 'bootloader'),
os.path.join('components', 'bt'),
os.path.join('components', 'driver'),
os.path.join('components', 'esp32'),
os.path.join('components', 'esp_adc_cal'),
os.path.join('components', 'esp_event'),
os.path.join('components', 'esp_http_client'),
os.path.join('components', 'esp_http_server'),
os.path.join('components', 'esptool_py'),
os.path.join('components', 'ethernet'),
os.path.join('components', 'fatfs'),
os.path.join('components', 'freemodbus'),
os.path.join('components', 'freertos'),
os.path.join('components', 'heap'),
os.path.join('components', 'libsodium'),
os.path.join('components', 'log'),
os.path.join('components', 'lwip'),
os.path.join('components', 'mbedtls'),
os.path.join('components', 'mdns'),
os.path.join('components', 'mqtt'),
os.path.join('components', 'nvs_flash'),
os.path.join('components', 'openssl'),
os.path.join('components', 'partition_table'),
os.path.join('components', 'pthread'),
os.path.join('components', 'spi_flash'),
os.path.join('components', 'spiffs'),
os.path.join('components', 'tcpip_adapter'),
os.path.join('components', 'unity'),
os.path.join('components', 'vfs'),
os.path.join('components', 'wear_levelling'),
os.path.join('examples', 'bluetooth', 'a2dp_gatts_coex', 'main'),
os.path.join('examples', 'bluetooth', 'a2dp_sink', 'main'),
os.path.join('examples', 'bluetooth', 'ble_ibeacon', 'main'),
os.path.join('examples', 'bluetooth', 'ble_throughput', 'throughput_client', 'main'),
os.path.join('examples', 'bluetooth', 'ble_throughput', 'throughput_server', 'main'),
os.path.join('examples', 'bluetooth', 'gatt_server', 'main'),
os.path.join('examples', 'ethernet', 'ethernet', 'main'),
os.path.join('examples', 'ethernet', 'iperf', 'main'),
os.path.join('examples', 'get-started', 'blink', 'main'),
os.path.join('examples', 'mesh', 'internal_communication', 'main'),
os.path.join('examples', 'mesh', 'manual_networking', 'main'),
os.path.join('examples', 'peripherals', 'adc2', 'main'),
os.path.join('examples', 'peripherals', 'i2c', 'i2c_self_test', 'main'),
os.path.join('examples', 'peripherals', 'i2c', 'i2c_tools', 'main'),
os.path.join('examples', 'peripherals', 'sdio', 'host', 'main'),
os.path.join('examples', 'peripherals', 'sdio', 'slave', 'main'),
os.path.join('examples', 'peripherals', 'spi_master', 'main'),
os.path.join('examples', 'peripherals', 'uart', 'nmea0183_parser', 'main'),
os.path.join('examples', 'protocols', 'asio', 'chat_client', 'main'),
os.path.join('examples', 'protocols', 'asio', 'chat_server', 'main'),
os.path.join('examples', 'protocols', 'asio', 'tcp_echo_server', 'main'),
os.path.join('examples', 'protocols', 'asio', 'udp_echo_server', 'main'),
os.path.join('examples', 'protocols', 'aws_iot', 'subscribe_publish', 'main'),
os.path.join('examples', 'protocols', 'aws_iot', 'thing_shadow', 'main'),
os.path.join('examples', 'protocols', 'coap_client', 'main'),
os.path.join('examples', 'protocols', 'coap_server', 'main'),
os.path.join('examples', 'protocols', 'esp_http_client', 'main'),
os.path.join('examples', 'protocols', 'http2_request', 'main'),
os.path.join('examples', 'protocols', 'http_request', 'main'),
os.path.join('examples', 'protocols', 'http_server', 'advanced_tests', 'main'),
os.path.join('examples', 'protocols', 'http_server', 'persistent_sockets', 'main'),
os.path.join('examples', 'protocols', 'http_server', 'simple', 'main'),
os.path.join('examples', 'protocols', 'https_mbedtls', 'main'),
os.path.join('examples', 'protocols', 'https_request', 'main'),
os.path.join('examples', 'protocols', 'https_server', 'main'),
os.path.join('examples', 'protocols', 'mdns', 'main'),
os.path.join('examples', 'protocols', 'modbus_slave', 'main'),
os.path.join('examples', 'protocols', 'mqtt', 'ssl', 'main'),
os.path.join('examples', 'protocols', 'mqtt', 'ssl_mutual_auth', 'main'),
os.path.join('examples', 'protocols', 'mqtt', 'tcp', 'main'),
os.path.join('examples', 'protocols', 'mqtt', 'ws', 'main'),
os.path.join('examples', 'protocols', 'mqtt', 'wss', 'main'),
os.path.join('examples', 'protocols', 'openssl_client', 'main'),
os.path.join('examples', 'protocols', 'openssl_server', 'main'),
os.path.join('examples', 'protocols', 'pppos_client', 'main'),
os.path.join('examples', 'protocols', 'sntp', 'main'),
os.path.join('examples', 'protocols', 'sockets', 'tcp_client', 'main'),
os.path.join('examples', 'protocols', 'sockets', 'tcp_server', 'main'),
os.path.join('examples', 'protocols', 'sockets', 'udp_client', 'main'),
os.path.join('examples', 'protocols', 'sockets', 'udp_multicast', 'main'),
os.path.join('examples', 'protocols', 'sockets', 'udp_server', 'main'),
os.path.join('examples', 'provisioning', 'ble_prov', 'main'),
os.path.join('examples', 'provisioning', 'console_prov', 'main'),
os.path.join('examples', 'provisioning', 'custom_config', 'main'),
os.path.join('examples', 'provisioning', 'softap_prov', 'main'),
os.path.join('examples', 'system', 'app_trace_to_host', 'main'),
os.path.join('examples', 'system', 'base_mac_address', 'main'),
os.path.join('examples', 'system', 'console', 'main'),
os.path.join('examples', 'system', 'deep_sleep', 'main'),
os.path.join('examples', 'system', 'gcov', 'main'),
os.path.join('examples', 'system', 'ota', 'native_ota_example', 'main'),
os.path.join('examples', 'system', 'ota', 'simple_ota_example', 'main'),
os.path.join('examples', 'system', 'sysview_tracing', 'main'),
os.path.join('examples', 'wifi', 'espnow', 'main'),
os.path.join('examples', 'wifi', 'getting_started', 'softAP', 'main'),
os.path.join('examples', 'wifi', 'getting_started', 'station', 'main'),
os.path.join('examples', 'wifi', 'power_save', 'main'),
os.path.join('examples', 'wifi', 'scan', 'main'),
os.path.join('examples', 'wifi', 'simple_sniffer', 'main'),
os.path.join('examples', 'wifi', 'wpa2_enterprise', 'main'),
os.path.join('examples', 'wifi', 'wps', 'main'),
os.path.join('tools', 'kconfig'),
os.path.join('tools', 'kconfig_new', 'test'),
os.path.join('tools', 'ldgen', 'test', 'data'),
os.path.join('tools', 'unit-test-app', 'components', 'test_utils'),
)
SPACES_PER_INDENT = 4
CONFIG_NAME_MAX_LENGTH = 50
# TODO increase prefix length (after the names have been refactored)
CONFIG_NAME_MIN_PREFIX_LENGTH = 0
# 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),
]
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 LineRuleChecker(BaseChecker):
"""
checks LINE_ERROR_RULES for each line
"""
def process_line(self, line, line_number):
errors = []
for rule in LINE_ERROR_RULES:
m = rule[0].search(line)
if m:
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 = []
# 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)
)
''', 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']:
# 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
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) # no suggested correction for this
if 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,
'Common prefix "{}" length is {} and should be at least {} characters long'
''.format(common_prefix, common_prefix_len, 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:
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'
return
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 in inside if/endif belong to the
# same prefix level
self.check_common_prefix(line, line_number)
expected_indent = current_level * SPACES_PER_INDENT
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):
if not os.path.isdir(path):
raise argparse.ArgumentTypeError("{} is not a valid directory!".format(path))
return path
def main():
default_path = os.getenv('IDF_PATH', None)
parser = argparse.ArgumentParser(description='Kconfig style checker')
parser.add_argument('--verbose', '-v', help='Print more information (useful for debugging)',
action='store_true', default=False)
parser.add_argument('--directory', '-d', help='Path to directory where Kconfigs should be recursively checked '
'(for example $IDF_PATH)',
type=valid_directory,
required=default_path is None,
default=default_path)
args = parser.parse_args()
success_couter = 0
ignore_counter = 0
failure = False
# IGNORE_DIRS makes sense when the required directory is IDF_PATH
check_ignore_dirs = default_path is not None and os.path.abspath(args.directory) == os.path.abspath(default_path)
for root, dirnames, filenames in os.walk(args.directory):
for filename in [f for f in filenames if RE_KCONFIG.search(f)]:
full_path = os.path.join(root, filename)
path_in_idf = os.path.relpath(full_path, args.directory)
if check_ignore_dirs and path_in_idf.startswith(IGNORE_DIRS):
print('{}: Ignored'.format(path_in_idf))
ignore_counter += 1
continue
suggestions_full_path = full_path + OUTPUT_SUFFIX
with open(full_path, 'r', encoding='utf-8') as f, \
open(suggestions_full_path, 'w', encoding='utf-8', newline='\n') as f_o, \
LineRuleChecker(path_in_idf) as line_checker, \
IndentAndNameChecker(path_in_idf, debug=args.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]:
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)
failure = True
f_o.write(e.suggested_line)
except UnicodeDecodeError:
raise ValueError("The encoding of {} is not Unicode.".format(path_in_idf))
if failure:
print('{} has been saved with suggestions for resolving the issues. Please note that the suggestions '
'can be wrong and you might need to re-run the checker several times for solving all issues.'
''.format(path_in_idf + OUTPUT_SUFFIX))
print('Please fix the errors and run {} for checking the correctness of '
'Kconfigs.'.format(os.path.relpath(os.path.abspath(__file__), args.directory)))
sys.exit(1)
else:
success_couter += 1
print('{}: OK'.format(path_in_idf))
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))
if ignore_counter > 0:
print('{} files have been ignored.'.format(ignore_counter))
if success_couter > 0:
print('{} files have been successfully checked.'.format(success_couter))
if __name__ == "__main__":
main()

View file

@ -71,3 +71,5 @@ tools/ldgen/test/test_generation.py
examples/build_system/cmake/idf_as_lib/build.sh examples/build_system/cmake/idf_as_lib/build.sh
examples/storage/parttool/parttool_example.py examples/storage/parttool/parttool_example.py
examples/system/ota/otatool/otatool_example.py examples/system/ota/otatool/otatool_example.py
tools/check_kconfigs.py
tools/test_check_kconfigs.py

209
tools/test_check_kconfigs.py Executable file
View file

@ -0,0 +1,209 @@
#!/usr/bin/env python
#
# Copyright 2018 Espressif Systems (Shanghai) PTE LTD
#
# Licensed under the Apache License, Version 2.0 (the "License");
# you may not use this file except in compliance with the License.
# You may obtain a copy of the License at
#
# http://www.apache.org/licenses/LICENSE-2.0
#
# Unless required by applicable law or agreed to in writing, software
# distributed under the License is distributed on an "AS IS" BASIS,
# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
# See the License for the specific language governing permissions and
# limitations under the License.
import unittest
from check_kconfigs import LineRuleChecker
from check_kconfigs import InputError
from check_kconfigs import IndentAndNameChecker
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 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(' 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')
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):
self.expt_success('menu "test"')
self.expt_success(' config ABC')
self.expt_success(' config ' + ('X' * 50))
self.expect_error(' config ' + ('X' * 51), expect=None)
self.expt_success(' menuconfig ' + ('X' * 50))
self.expect_error(' menuconfig ' + ('X' * 51), expect=None)
self.expt_success(' choice ' + ('X' * 50))
self.expect_error(' choice ' + ('X' * 51), 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()