diff --git a/.murdock b/.murdock index 65f51db3b7da7..afdf7e9e185fa 100755 --- a/.murdock +++ b/.murdock @@ -335,7 +335,7 @@ get_supported_boards() { for board in $boards do echo $board - done | $(_greplist $BOARDS) + done | $(_greplist $BOARDS) | $(_greplist esp8266-esp-12x) } get_supported_toolchains() { diff --git a/tests/shell_ztimer/Makefile b/tests/shell_ztimer/Makefile new file mode 100644 index 0000000000000..5006708be45b8 --- /dev/null +++ b/tests/shell_ztimer/Makefile @@ -0,0 +1,24 @@ +DEVELHELP=0 +include ../Makefile.tests_common + +USEMODULE += app_metadata +USEMODULE += shell +USEMODULE += shell_commands +USEMODULE += ps + +USEMODULE += ztimer_msec + +# for z1, socat doesn't work (unknown reason) +ifeq (z1, $(BOARD)) + RIOT_TERMINAL ?= pyterm +endif + +# Use a terminal that does not introduce extra characters into the stream. +RIOT_TERMINAL ?= socat + +APP_SHELL_FMT ?= NONE + +include $(RIOTBASE)/Makefile.include + +# the test script skips tests if socat is not used +$(call target-export-variables,$(RIOT_TERMINAL),RIOT_TERMINAL) diff --git a/tests/shell_ztimer/Makefile.ci b/tests/shell_ztimer/Makefile.ci new file mode 100644 index 0000000000000..1152ca53bcbb8 --- /dev/null +++ b/tests/shell_ztimer/Makefile.ci @@ -0,0 +1,8 @@ +BOARD_INSUFFICIENT_MEMORY := \ + arduino-duemilanove \ + arduino-leonardo \ + arduino-nano \ + arduino-uno \ + atmega328p \ + atmega328p-xplained-mini \ + # diff --git a/tests/shell_ztimer/ReadMe.txt b/tests/shell_ztimer/ReadMe.txt new file mode 100644 index 0000000000000..16bf30e2416f5 --- /dev/null +++ b/tests/shell_ztimer/ReadMe.txt @@ -0,0 +1,14 @@ +This application shows how to use own or the system shell commands. In order to use +the system shell commands: + +1. Additionally to the module: shell, shell_commands, + the module for the corresponding system command is to include, e.g. + module ps for the ps command (cf. the Makefile in the application root + directory). +2. Start the shell like this: + 2.1 reserve buffer: + char line_buf[SHELL_DEFAULT_BUFSIZE]; + 2.1a run shell only with system commands: + shell_run(NULL, line_buf, SHELL_DEFAULT_BUFSIZE); + 2.1b run shell with provided commands in addition to system commands: + shell_run(shell_commands, line_buf, SHELL_DEFAULT_BUFSIZE); diff --git a/tests/shell_ztimer/app.config.test b/tests/shell_ztimer/app.config.test new file mode 100644 index 0000000000000..6fa9b1aac7934 --- /dev/null +++ b/tests/shell_ztimer/app.config.test @@ -0,0 +1,6 @@ +CONFIG_MODULE_APP_METADATA=y +CONFIG_MODULE_SHELL=y +CONFIG_MODULE_SHELL_COMMANDS=y +CONFIG_MODULE_PS=y + +CONFIG_MODULE_ZTIMER_MSEC=y diff --git a/tests/shell_ztimer/main.c b/tests/shell_ztimer/main.c new file mode 100644 index 0000000000000..4556873f2af52 --- /dev/null +++ b/tests/shell_ztimer/main.c @@ -0,0 +1,150 @@ +/* + * Copyright (C) 2013 Kaspar Schleiser + * Copyright (C) 2013 Freie Universität Berlin + * + * This file is subject to the terms and conditions of the GNU Lesser + * General Public License v2.1. See the file LICENSE in the top level + * directory for more details. + */ + +/** + * @file + * @brief shows how to set up own and use the system shell commands. + * By typing help in the serial console, all the supported commands + * are listed. + * + * @author Kaspar Schleiser + * @author Zakaria Kasmi + * + */ + +#include +#include + +#include "shell_commands.h" +#include "shell.h" + +#if MODULE_STDIO_RTT +#include "xtimer.h" +#endif + +#if MODULE_SHELL_HOOKS +void shell_post_readline_hook(void) +{ + puts("shell_post_readline_hook"); +} + +void shell_pre_command_hook(int argc, char **argv) +{ + (void)argc; + (void)argv; + puts("shell_pre_command_hook"); +} + +void shell_post_command_hook(int ret, int argc, char **argv) +{ + (void)ret; + (void)argc; + (void)argv; + puts("shell_post_command_hook"); +} +#endif + +static int print_teststart(int argc, char **argv) +{ + (void)argc; + (void)argv; + printf("[TEST_START]\n"); + + return 0; +} + +static int print_testend(int argc, char **argv) +{ + (void)argc; + (void)argv; + printf("[TEST_END]\n"); + + return 0; +} + +static int print_echo(int argc, char **argv) +{ + for (int i = 0; i < argc; ++i) { + printf("\"%s\"", argv[i]); + } + puts(""); + + return 0; +} + +static int print_shell_bufsize(int argc, char **argv) +{ + (void)argc; + (void)argv; + printf("%d\n", SHELL_DEFAULT_BUFSIZE); + + return 0; +} + +static int print_empty(int argc, char **argv) +{ + (void)argc; + (void)argv; + return 0; +} + +static const shell_command_t shell_commands[] = { + { "bufsize", "Get the shell's buffer size", print_shell_bufsize }, + { "start_test", "starts a test", print_teststart }, + { "end_test", "ends a test", print_testend }, + { "echo", "prints the input command", print_echo }, + { "empty", "print nothing on command", print_empty }, + { NULL, NULL, NULL } +}; + +static int _xfa_test1(int argc, char **argv) +{ + (void)argc; + (void)argv; + printf("[XFA TEST 1 OK]\n"); + + return 0; +} + +static int _xfa_test2(int argc, char **argv) +{ + (void)argc; + (void)argv; + printf("[XFA TEST 2 OK]\n"); + + return 0; +} + +/* Add above commands to the shell commands XFA using helper macro. + * Intentionally reversed order to test linker script based alphanumeric + * ordering. */ +SHELL_COMMAND(xfa_test2, "xfa test command 2", _xfa_test2); +SHELL_COMMAND(xfa_test1, "xfa test command 1", _xfa_test1); + +int main(void) +{ + printf("test_shell.\n"); + + /* define buffer to be used by the shell */ + char line_buf[SHELL_DEFAULT_BUFSIZE]; + + /* define own shell commands */ + shell_run_once(shell_commands, line_buf, SHELL_DEFAULT_BUFSIZE); + + puts("shell exited"); + + /* Restart the shell after the previous one exits, so that we can test + * Ctrl-D exit */ + shell_run(shell_commands, line_buf, SHELL_DEFAULT_BUFSIZE); + + /* or use only system shell commands */ + /* shell_run(NULL, line_buf, SHELL_DEFAULT_BUFSIZE); */ + + return 0; +} diff --git a/tests/shell_ztimer/tests/01-run.py b/tests/shell_ztimer/tests/01-run.py new file mode 100755 index 0000000000000..a4b423ad8db14 --- /dev/null +++ b/tests/shell_ztimer/tests/01-run.py @@ -0,0 +1,233 @@ +#!/usr/bin/env python3 + +# Copyright (C) 2017 Alexandre Abadie +# +# This file is subject to the terms and conditions of the GNU Lesser +# General Public License v2.1. See the file LICENSE in the top level +# directory for more details. + +import sys +import os +from testrunner import run + + +EXPECTED_HELP = ( + 'Command Description', + '---------------------------------------', + 'start_test starts a test', + 'end_test ends a test', + 'echo prints the input command', + 'reboot Reboot the node', + 'ps Prints information about running threads.', + 'app_metadata Returns application metadata', + 'xfa_test1 xfa test command 1', + 'xfa_test2 xfa test command 2' +) + +EXPECTED_PS = ( + '\tpid | state Q | pri', + r'\t \d | running Q | 7' +) + +RIOT_TERMINAL = os.environ.get('RIOT_TERMINAL') +CLEANTERMS = {"socat"} + +# In native we are directly executing the binary (no terminal program). We must +# therefore use Ctrl-V (DLE or "data link escape") before Ctrl-C to send a +# literal ETX instead of SIGINT. +# When using a board it is also a problem because we are using a "user friendly" +# terminal that interprets Ctrl-C. So until we have rawterm we must also use +# ctrl-v in boards. + +DLE = '\x16' + +CONTROL_C = DLE+'\x03' +CONTROL_D = DLE+'\x04' + +PROMPT = '> ' + +CMDS = ( + ('start_test', '[TEST_START]'), + + # test empty line input + ('\n', PROMPT), + + # test simple word separation + ('echo a string', '"echo""a""string"'), + ('echo multiple spaces between argv', '"echo""multiple""spaces""between""argv"'), + ('echo \t tabs\t\t processed \t\tlike\t \t\tspaces', '"echo""tabs""processed""like""spaces"'), + + # test unknown commands + ('unknown_command', 'shell: command not found: unknown_command'), + + # test leading/trailing BLANK + (' echo leading spaces', '"echo""leading""spaces"'), + ('\t\t\t\t\techo leading tabs', '"echo""leading""tabs"'), + ('echo trailing spaces ', '"echo""trailing""spaces"'), + ('echo trailing tabs\t\t\t\t\t', '"echo""trailing""tabs"'), + + # test backspace + ('hello-willy\b\b\b\borld', 'shell: command not found: hello-world'), + ('\b\b\b\becho', '"echo"'), + + # test escaping + ('echo \\\'', '"echo""\'"'), + ('echo \\"', '"echo""""'), + ('echo escaped\\ space', '"echo""escaped space"'), + ('echo escape within \'\\s\\i\\n\\g\\l\\e\\q\\u\\o\\t\\e\'', '"echo""escape""within""singlequote"'), + ('echo escape within "\\d\\o\\u\\b\\l\\e\\q\\u\\o\\t\\e"', '"echo""escape""within""doublequote"'), + ("""echo "t\e st" "\\"" '\\'' a\ b""", '"echo""te st"""""\'""a b"'), # noqa: W605 + + # test correct quoting + ('echo "hello"world', '"echo""helloworld"'), + ('echo hel"lowo"rld', '"echo""helloworld"'), + ('echo hello"world"', '"echo""helloworld"'), + ('echo quoted space " "', '"echo""quoted""space"" "'), + ('echo abc"def\'ghijk"lmn', '"echo""abcdef\'ghijklmn"'), + ('echo abc\'def"ghijk\'lmn', '"echo""abcdef"ghijklmn"'), + ('echo "\'" \'"\'', '"echo""\'""""'), + + # test incorrect quoting + ('echo a\\', 'shell: incorrect quoting'), + ('echo "', 'shell: incorrect quoting'), + ('echo \'', 'shell: incorrect quoting'), + ('echo abcdef"ghijklmn', 'shell: incorrect quoting'), + ('echo abcdef\'ghijklmn', 'shell: incorrect quoting'), + + # test default commands + ('ps', EXPECTED_PS), + ('help', EXPECTED_HELP), + + # test commands added to shell_commands_xfa + ('xfa_test1', '[XFA TEST 1 OK]'), + ('xfa_test2', '[XFA TEST 2 OK]'), + + # test reboot + ('reboot', 'test_shell.'), + + ('end_test', '[TEST_END]'), +) + +CMDS_CLEANTERM = { + (CONTROL_C, PROMPT), +} + +CMDS_REGEX = {'ps'} + +BOARD = os.environ['BOARD'] + +# there's an issue with some boards' serial that causes lost characters. +LINE_EXCEEDED_BLACKLIST = { + # There is an issue with nrf52dk when the Virtual COM port is connected + # and sending more than 64 bytes over UART. If no terminal is connected + # to the Virtual COM and interfacing directly to the nrf52832 UART pins + # the issue is not present. See issue #10639 on GitHub. + 'nrf52dk', + 'z1', +} + + +def print_error(message): + FAIL = '\033[91m' + ENDC = '\033[0m' + print(FAIL + message + ENDC) + + +def check_cmd(child, cmd, expected): + regex = cmd in CMDS_REGEX + child.expect(PROMPT) + child.sendline(cmd) + for line in expected: + if regex: + child.expect(line) + else: + child.expect_exact(line) + + +def check_startup(child): + child.sendline(CONTROL_C) + child.expect_exact(PROMPT) + + +def check_and_get_bufsize(child): + child.sendline('bufsize') + child.expect('([0-9]+)\r\n') + bufsize = int(child.match.group(1)) + + return bufsize + + +def check_line_exceeded(child, longline): + + if BOARD in LINE_EXCEEDED_BLACKLIST: + print_error('test case "check_line_exceeded" blacklisted, SKIP') + return + + child.sendline(longline) + child.expect('shell: maximum line length exceeded') + + +def check_line_canceling(child): + child.expect(PROMPT) + child.sendline('garbage1234' + CONTROL_C) + garbage_expected = 'garbage1234\r\r\n' + garbage_received = child.read(len(garbage_expected)) + + assert garbage_expected == garbage_received + + +def check_erase_long_line(child, longline): + # FIXME: this only works on native, due to #10634 combined with socat + # insisting in line-buffering the terminal. + + if BOARD == 'native': + longline_erased = longline + "\b"*len(longline) + "echo" + child.sendline(longline_erased) + child.expect_exact('"echo"') + + +def check_control_d(child): + # The current shell instance was initiated by shell_run_once(). The shell will exit. + child.sendline(CONTROL_D) + child.expect_exact('shell exited') + + # The current shell instance was initiated by shell_run(). The shell will respawn + # automatically except on native. On native, RIOT is shut down completely, + # therefore exclude this part. + if BOARD != 'native': + child.sendline(CONTROL_D) + child.expect_exact(PROMPT) + + +def testfunc(child): + # avoid sending an extra empty line on native. + if BOARD == 'native': + child.crlf = '\n' + + bufsize = check_and_get_bufsize(child) + longline = "_"*bufsize + "verylong" + + check_line_exceeded(child, longline) + + if RIOT_TERMINAL in CLEANTERMS: + check_line_canceling(child) + else: + print("skipping check_line_canceling()") + + check_erase_long_line(child, longline) + + check_control_d(child) + + # loop other defined commands and expected output + for cmd, expected in CMDS: + check_cmd(child, cmd, expected) + + if RIOT_TERMINAL in CLEANTERMS: + for cmd, expected in CMDS_CLEANTERM: + check_cmd(child, cmd, expected) + else: + print("skipping cleanterm tests") + + +if __name__ == "__main__": + sys.exit(run(testfunc)) diff --git a/tests/shell_ztimer_esp_wifi/Kconfig b/tests/shell_ztimer_esp_wifi/Kconfig new file mode 100644 index 0000000000000..a803df4c6c8ab --- /dev/null +++ b/tests/shell_ztimer_esp_wifi/Kconfig @@ -0,0 +1,3 @@ +config MODULE_ESP_WIFI_ANY + bool + default y diff --git a/tests/shell_ztimer_esp_wifi/Makefile b/tests/shell_ztimer_esp_wifi/Makefile new file mode 100644 index 0000000000000..94a2a5d516823 --- /dev/null +++ b/tests/shell_ztimer_esp_wifi/Makefile @@ -0,0 +1,25 @@ +DEVELHELP=0 +include ../Makefile.tests_common + +USEMODULE += app_metadata +USEMODULE += shell +USEMODULE += shell_commands +USEMODULE += ps + +USEMODULE += ztimer_msec +USEMODULE += esp_wifi_any + +# for z1, socat doesn't work (unknown reason) +ifeq (z1, $(BOARD)) + RIOT_TERMINAL ?= pyterm +endif + +# Use a terminal that does not introduce extra characters into the stream. +RIOT_TERMINAL ?= socat + +APP_SHELL_FMT ?= NONE + +include $(RIOTBASE)/Makefile.include + +# the test script skips tests if socat is not used +$(call target-export-variables,$(RIOT_TERMINAL),RIOT_TERMINAL) diff --git a/tests/shell_ztimer_esp_wifi/Makefile.ci b/tests/shell_ztimer_esp_wifi/Makefile.ci new file mode 100644 index 0000000000000..1152ca53bcbb8 --- /dev/null +++ b/tests/shell_ztimer_esp_wifi/Makefile.ci @@ -0,0 +1,8 @@ +BOARD_INSUFFICIENT_MEMORY := \ + arduino-duemilanove \ + arduino-leonardo \ + arduino-nano \ + arduino-uno \ + atmega328p \ + atmega328p-xplained-mini \ + # diff --git a/tests/shell_ztimer_esp_wifi/ReadMe.txt b/tests/shell_ztimer_esp_wifi/ReadMe.txt new file mode 100644 index 0000000000000..16bf30e2416f5 --- /dev/null +++ b/tests/shell_ztimer_esp_wifi/ReadMe.txt @@ -0,0 +1,14 @@ +This application shows how to use own or the system shell commands. In order to use +the system shell commands: + +1. Additionally to the module: shell, shell_commands, + the module for the corresponding system command is to include, e.g. + module ps for the ps command (cf. the Makefile in the application root + directory). +2. Start the shell like this: + 2.1 reserve buffer: + char line_buf[SHELL_DEFAULT_BUFSIZE]; + 2.1a run shell only with system commands: + shell_run(NULL, line_buf, SHELL_DEFAULT_BUFSIZE); + 2.1b run shell with provided commands in addition to system commands: + shell_run(shell_commands, line_buf, SHELL_DEFAULT_BUFSIZE); diff --git a/tests/shell_ztimer_esp_wifi/app.config.test b/tests/shell_ztimer_esp_wifi/app.config.test new file mode 100644 index 0000000000000..65b1c3e178bdc --- /dev/null +++ b/tests/shell_ztimer_esp_wifi/app.config.test @@ -0,0 +1,7 @@ +CONFIG_MODULE_APP_METADATA=y +CONFIG_MODULE_SHELL=y +CONFIG_MODULE_SHELL_COMMANDS=y +CONFIG_MODULE_PS=y + +CONFIG_MODULE_ZTIMER_MSEC=y +CONFIG_MODULE_ESP_WIFI_ANY=y diff --git a/tests/shell_ztimer_esp_wifi/main.c b/tests/shell_ztimer_esp_wifi/main.c new file mode 100644 index 0000000000000..4556873f2af52 --- /dev/null +++ b/tests/shell_ztimer_esp_wifi/main.c @@ -0,0 +1,150 @@ +/* + * Copyright (C) 2013 Kaspar Schleiser + * Copyright (C) 2013 Freie Universität Berlin + * + * This file is subject to the terms and conditions of the GNU Lesser + * General Public License v2.1. See the file LICENSE in the top level + * directory for more details. + */ + +/** + * @file + * @brief shows how to set up own and use the system shell commands. + * By typing help in the serial console, all the supported commands + * are listed. + * + * @author Kaspar Schleiser + * @author Zakaria Kasmi + * + */ + +#include +#include + +#include "shell_commands.h" +#include "shell.h" + +#if MODULE_STDIO_RTT +#include "xtimer.h" +#endif + +#if MODULE_SHELL_HOOKS +void shell_post_readline_hook(void) +{ + puts("shell_post_readline_hook"); +} + +void shell_pre_command_hook(int argc, char **argv) +{ + (void)argc; + (void)argv; + puts("shell_pre_command_hook"); +} + +void shell_post_command_hook(int ret, int argc, char **argv) +{ + (void)ret; + (void)argc; + (void)argv; + puts("shell_post_command_hook"); +} +#endif + +static int print_teststart(int argc, char **argv) +{ + (void)argc; + (void)argv; + printf("[TEST_START]\n"); + + return 0; +} + +static int print_testend(int argc, char **argv) +{ + (void)argc; + (void)argv; + printf("[TEST_END]\n"); + + return 0; +} + +static int print_echo(int argc, char **argv) +{ + for (int i = 0; i < argc; ++i) { + printf("\"%s\"", argv[i]); + } + puts(""); + + return 0; +} + +static int print_shell_bufsize(int argc, char **argv) +{ + (void)argc; + (void)argv; + printf("%d\n", SHELL_DEFAULT_BUFSIZE); + + return 0; +} + +static int print_empty(int argc, char **argv) +{ + (void)argc; + (void)argv; + return 0; +} + +static const shell_command_t shell_commands[] = { + { "bufsize", "Get the shell's buffer size", print_shell_bufsize }, + { "start_test", "starts a test", print_teststart }, + { "end_test", "ends a test", print_testend }, + { "echo", "prints the input command", print_echo }, + { "empty", "print nothing on command", print_empty }, + { NULL, NULL, NULL } +}; + +static int _xfa_test1(int argc, char **argv) +{ + (void)argc; + (void)argv; + printf("[XFA TEST 1 OK]\n"); + + return 0; +} + +static int _xfa_test2(int argc, char **argv) +{ + (void)argc; + (void)argv; + printf("[XFA TEST 2 OK]\n"); + + return 0; +} + +/* Add above commands to the shell commands XFA using helper macro. + * Intentionally reversed order to test linker script based alphanumeric + * ordering. */ +SHELL_COMMAND(xfa_test2, "xfa test command 2", _xfa_test2); +SHELL_COMMAND(xfa_test1, "xfa test command 1", _xfa_test1); + +int main(void) +{ + printf("test_shell.\n"); + + /* define buffer to be used by the shell */ + char line_buf[SHELL_DEFAULT_BUFSIZE]; + + /* define own shell commands */ + shell_run_once(shell_commands, line_buf, SHELL_DEFAULT_BUFSIZE); + + puts("shell exited"); + + /* Restart the shell after the previous one exits, so that we can test + * Ctrl-D exit */ + shell_run(shell_commands, line_buf, SHELL_DEFAULT_BUFSIZE); + + /* or use only system shell commands */ + /* shell_run(NULL, line_buf, SHELL_DEFAULT_BUFSIZE); */ + + return 0; +} diff --git a/tests/shell_ztimer_esp_wifi/tests/01-run.py b/tests/shell_ztimer_esp_wifi/tests/01-run.py new file mode 100755 index 0000000000000..a4b423ad8db14 --- /dev/null +++ b/tests/shell_ztimer_esp_wifi/tests/01-run.py @@ -0,0 +1,233 @@ +#!/usr/bin/env python3 + +# Copyright (C) 2017 Alexandre Abadie +# +# This file is subject to the terms and conditions of the GNU Lesser +# General Public License v2.1. See the file LICENSE in the top level +# directory for more details. + +import sys +import os +from testrunner import run + + +EXPECTED_HELP = ( + 'Command Description', + '---------------------------------------', + 'start_test starts a test', + 'end_test ends a test', + 'echo prints the input command', + 'reboot Reboot the node', + 'ps Prints information about running threads.', + 'app_metadata Returns application metadata', + 'xfa_test1 xfa test command 1', + 'xfa_test2 xfa test command 2' +) + +EXPECTED_PS = ( + '\tpid | state Q | pri', + r'\t \d | running Q | 7' +) + +RIOT_TERMINAL = os.environ.get('RIOT_TERMINAL') +CLEANTERMS = {"socat"} + +# In native we are directly executing the binary (no terminal program). We must +# therefore use Ctrl-V (DLE or "data link escape") before Ctrl-C to send a +# literal ETX instead of SIGINT. +# When using a board it is also a problem because we are using a "user friendly" +# terminal that interprets Ctrl-C. So until we have rawterm we must also use +# ctrl-v in boards. + +DLE = '\x16' + +CONTROL_C = DLE+'\x03' +CONTROL_D = DLE+'\x04' + +PROMPT = '> ' + +CMDS = ( + ('start_test', '[TEST_START]'), + + # test empty line input + ('\n', PROMPT), + + # test simple word separation + ('echo a string', '"echo""a""string"'), + ('echo multiple spaces between argv', '"echo""multiple""spaces""between""argv"'), + ('echo \t tabs\t\t processed \t\tlike\t \t\tspaces', '"echo""tabs""processed""like""spaces"'), + + # test unknown commands + ('unknown_command', 'shell: command not found: unknown_command'), + + # test leading/trailing BLANK + (' echo leading spaces', '"echo""leading""spaces"'), + ('\t\t\t\t\techo leading tabs', '"echo""leading""tabs"'), + ('echo trailing spaces ', '"echo""trailing""spaces"'), + ('echo trailing tabs\t\t\t\t\t', '"echo""trailing""tabs"'), + + # test backspace + ('hello-willy\b\b\b\borld', 'shell: command not found: hello-world'), + ('\b\b\b\becho', '"echo"'), + + # test escaping + ('echo \\\'', '"echo""\'"'), + ('echo \\"', '"echo""""'), + ('echo escaped\\ space', '"echo""escaped space"'), + ('echo escape within \'\\s\\i\\n\\g\\l\\e\\q\\u\\o\\t\\e\'', '"echo""escape""within""singlequote"'), + ('echo escape within "\\d\\o\\u\\b\\l\\e\\q\\u\\o\\t\\e"', '"echo""escape""within""doublequote"'), + ("""echo "t\e st" "\\"" '\\'' a\ b""", '"echo""te st"""""\'""a b"'), # noqa: W605 + + # test correct quoting + ('echo "hello"world', '"echo""helloworld"'), + ('echo hel"lowo"rld', '"echo""helloworld"'), + ('echo hello"world"', '"echo""helloworld"'), + ('echo quoted space " "', '"echo""quoted""space"" "'), + ('echo abc"def\'ghijk"lmn', '"echo""abcdef\'ghijklmn"'), + ('echo abc\'def"ghijk\'lmn', '"echo""abcdef"ghijklmn"'), + ('echo "\'" \'"\'', '"echo""\'""""'), + + # test incorrect quoting + ('echo a\\', 'shell: incorrect quoting'), + ('echo "', 'shell: incorrect quoting'), + ('echo \'', 'shell: incorrect quoting'), + ('echo abcdef"ghijklmn', 'shell: incorrect quoting'), + ('echo abcdef\'ghijklmn', 'shell: incorrect quoting'), + + # test default commands + ('ps', EXPECTED_PS), + ('help', EXPECTED_HELP), + + # test commands added to shell_commands_xfa + ('xfa_test1', '[XFA TEST 1 OK]'), + ('xfa_test2', '[XFA TEST 2 OK]'), + + # test reboot + ('reboot', 'test_shell.'), + + ('end_test', '[TEST_END]'), +) + +CMDS_CLEANTERM = { + (CONTROL_C, PROMPT), +} + +CMDS_REGEX = {'ps'} + +BOARD = os.environ['BOARD'] + +# there's an issue with some boards' serial that causes lost characters. +LINE_EXCEEDED_BLACKLIST = { + # There is an issue with nrf52dk when the Virtual COM port is connected + # and sending more than 64 bytes over UART. If no terminal is connected + # to the Virtual COM and interfacing directly to the nrf52832 UART pins + # the issue is not present. See issue #10639 on GitHub. + 'nrf52dk', + 'z1', +} + + +def print_error(message): + FAIL = '\033[91m' + ENDC = '\033[0m' + print(FAIL + message + ENDC) + + +def check_cmd(child, cmd, expected): + regex = cmd in CMDS_REGEX + child.expect(PROMPT) + child.sendline(cmd) + for line in expected: + if regex: + child.expect(line) + else: + child.expect_exact(line) + + +def check_startup(child): + child.sendline(CONTROL_C) + child.expect_exact(PROMPT) + + +def check_and_get_bufsize(child): + child.sendline('bufsize') + child.expect('([0-9]+)\r\n') + bufsize = int(child.match.group(1)) + + return bufsize + + +def check_line_exceeded(child, longline): + + if BOARD in LINE_EXCEEDED_BLACKLIST: + print_error('test case "check_line_exceeded" blacklisted, SKIP') + return + + child.sendline(longline) + child.expect('shell: maximum line length exceeded') + + +def check_line_canceling(child): + child.expect(PROMPT) + child.sendline('garbage1234' + CONTROL_C) + garbage_expected = 'garbage1234\r\r\n' + garbage_received = child.read(len(garbage_expected)) + + assert garbage_expected == garbage_received + + +def check_erase_long_line(child, longline): + # FIXME: this only works on native, due to #10634 combined with socat + # insisting in line-buffering the terminal. + + if BOARD == 'native': + longline_erased = longline + "\b"*len(longline) + "echo" + child.sendline(longline_erased) + child.expect_exact('"echo"') + + +def check_control_d(child): + # The current shell instance was initiated by shell_run_once(). The shell will exit. + child.sendline(CONTROL_D) + child.expect_exact('shell exited') + + # The current shell instance was initiated by shell_run(). The shell will respawn + # automatically except on native. On native, RIOT is shut down completely, + # therefore exclude this part. + if BOARD != 'native': + child.sendline(CONTROL_D) + child.expect_exact(PROMPT) + + +def testfunc(child): + # avoid sending an extra empty line on native. + if BOARD == 'native': + child.crlf = '\n' + + bufsize = check_and_get_bufsize(child) + longline = "_"*bufsize + "verylong" + + check_line_exceeded(child, longline) + + if RIOT_TERMINAL in CLEANTERMS: + check_line_canceling(child) + else: + print("skipping check_line_canceling()") + + check_erase_long_line(child, longline) + + check_control_d(child) + + # loop other defined commands and expected output + for cmd, expected in CMDS: + check_cmd(child, cmd, expected) + + if RIOT_TERMINAL in CLEANTERMS: + for cmd, expected in CMDS_CLEANTERM: + check_cmd(child, cmd, expected) + else: + print("skipping cleanterm tests") + + +if __name__ == "__main__": + sys.exit(run(testfunc))