From dd2ad603e8457835454a6d7a7c932d909b7dc623 Mon Sep 17 00:00:00 2001 From: Hendrik van Essen Date: Sat, 8 Feb 2020 14:47:55 +0100 Subject: [PATCH 1/6] tests/shell: test quoting and escaping Check that single and double quotes work, along with backslash escaping and that malformed strings are rejected. Right now the test is failing. The next commit will replace the tokenizer with one that works correctly. Co-authored-by: Juan Carrano --- tests/shell/tests/01-run.py | 5 +++++ 1 file changed, 5 insertions(+) diff --git a/tests/shell/tests/01-run.py b/tests/shell/tests/01-run.py index c292893c5f..c01fd10069 100755 --- a/tests/shell/tests/01-run.py +++ b/tests/shell/tests/01-run.py @@ -54,6 +54,11 @@ CMDS = ( ('\b\b\b\becho', '\"echo\"'), ('help', EXPECTED_HELP), ('echo a string', '\"echo\"\"a\"\"string\"'), + ("""echo "t\e st" "\\"" '\\'' a\ b""", '"echo""te st"""""\'""a b"'), + ('echo a\\', 'shell: incorrect quoting'), + ('echo "', 'shell: incorrect quoting'), + ("echo '", 'shell: incorrect quoting'), + ('echo "\'" \'"\'', '"echo""\'""""'), ('ps', EXPECTED_PS), ('help', EXPECTED_HELP), ('reboot', 'test_shell.'), From 86f60357cfdb3278f0c3386e28e7a238a5a17323 Mon Sep 17 00:00:00 2001 From: Hendrik van Essen Date: Sat, 8 Feb 2020 15:00:27 +0100 Subject: [PATCH 2/6] sys/shell: refactor tokenizer code The tokenizer (the code that breaks up the line given to the shell into strings to create argv) was quite a messy piece of code. This commit refactors it into a more traditional state-machine based parser. This fixes the issues with quote handling exposed by the recently introduced test. Co-authored-by: Juan Carrano --- sys/shell/shell.c | 216 +++++++++++++++++++++++++++------------------- 1 file changed, 129 insertions(+), 87 deletions(-) diff --git a/sys/shell/shell.c b/sys/shell/shell.c index 4c851286b5..667d2620a7 100644 --- a/sys/shell/shell.c +++ b/sys/shell/shell.c @@ -66,6 +66,27 @@ #define PROMPT_ON 0 #endif /* SHELL_NO_PROMPT */ +#define SQUOTE '\'' +#define DQUOTE '"' +#define ESCAPECHAR '\\' +#define BLANK ' ' + +enum PARSE_STATE { + PARSE_SPACE, + PARSE_UNQUOTED, + PARSE_SINGLEQUOTE, + PARSE_DOUBLEQUOTE, + PARSE_ESCAPE_MASK, + PARSE_UNQUOTED_ESC, + PARSE_SINGLEQUOTE_ESC, + PARSE_DOUBLEQUOTE_ESC, +}; + +static enum PARSE_STATE escape_toggle(enum PARSE_STATE s) +{ + return s ^ PARSE_ESCAPE_MASK; +} + static shell_command_handler_t find_handler(const shell_command_t *command_list, char *command) { const shell_command_t *command_lists[] = { @@ -119,107 +140,128 @@ static void print_help(const shell_command_t *command_list) } } +/** + * Break input line into words, create argv and call the command handler. + * + * Words are broken up at spaces. A backslash escaped the character that comes + * after (meaning if it is taken literally and if it is a space it does not break + * the word). Spaces can also be protected by quoting with double or single + * quotes. + * + State diagram for the tokenizer: +``` + ┌───[\]────┐ ┌─────["]────┐ ┌───[']─────┐ ┌───[\]────┐ + ↓ │ ↓ │ │ ↓ │ ↓ + ┏━━━━━━━━━━┓ ┏━┷━━━━━┓ ┏━┷━━━┷━┓ ┏━━━━┷━━┓ ┏━━━━━━━━━━┓ + ┃DQUOTE ESC┃ ┃DQUOTE ┠───["]─>┃SPACE ┃<─[']──┨SQUOTE ┃ ┃SQUOTE ESC┃ + ┗━━━━━━━━┯━┛ ┗━━━━━━┯┛ ┗┯━━━━┯━┛ ┗━┯━━━━━┛ ┗━━━┯━━━━━━┛ + │ ↑ │ │ │ │ ↑(store) │ + │ (store)│ │ ┌─[\]──┘ └──[*]────┐ │ │ │ + └──[*]──▶┴◀[*]┘ │ │ └[*]▶┴◀──[*]──┘ + ↓ ┏━━━━━━━┓ ↓ + ├◀[\]┨NOQUOTE┃◀─────┼◀─┐ + │ ┗━━━━━┯━┛(store)↑ │ + │ │ │ │ + │ └─[*]─────┘ │ + │ ┏━━━━━━━━━━━┓ │ + └───▶┃NOQUOTE ESC┠──[*]──┘ + ┗━━━━━━━━━━━┛ +``` + */ static void handle_input_line(const shell_command_t *command_list, char *line) { static const char *INCORRECT_QUOTING = "shell: incorrect quoting"; /* first we need to calculate the number of arguments */ - unsigned argc = 0; - char *pos = line; - int contains_esc_seq = 0; - while (1) { - if ((unsigned char) *pos > ' ') { - /* found an argument */ - if (*pos == '"' || *pos == '\'') { - /* it's a quoted argument */ - const char quote_char = *pos; - do { - ++pos; - if (!*pos) { - puts(INCORRECT_QUOTING); - return; - } - else if (*pos == '\\') { - /* skip over the next character */ - ++contains_esc_seq; - ++pos; - if (!*pos) { - puts(INCORRECT_QUOTING); - return; - } - continue; - } - } while (*pos != quote_char); - if ((unsigned char) pos[1] > ' ') { - puts(INCORRECT_QUOTING); - return; + int argc = 0; + char *readpos = line; + char *writepos = readpos; + enum PARSE_STATE pstate = PARSE_SPACE; + + while (*readpos != '\0') { + switch (pstate) { + case PARSE_SPACE: + if (*readpos != BLANK) { + argc++; } - } - else { - /* it's an unquoted argument */ - do { - if (*pos == '\\') { - /* skip over the next character */ - ++contains_esc_seq; - ++pos; - if (!*pos) { - puts(INCORRECT_QUOTING); - return; - } - } - ++pos; - if (*pos == '"') { - puts(INCORRECT_QUOTING); - return; - } - } while ((unsigned char) *pos > ' '); - } + if (*readpos == SQUOTE) { + pstate = PARSE_SINGLEQUOTE; + } + else if (*readpos == DQUOTE) { + pstate = PARSE_DOUBLEQUOTE; + } + else if (*readpos == ESCAPECHAR) { + pstate = PARSE_UNQUOTED_ESC; + } + else if (*readpos != BLANK) { + pstate = PARSE_UNQUOTED; + break; + } + goto parse_end; + + case PARSE_UNQUOTED: + if (*readpos == BLANK) { + pstate = PARSE_SPACE; + *writepos++ = '\0'; + goto parse_end; + } + else if (*readpos == ESCAPECHAR) { + pstate = escape_toggle(pstate); + goto parse_end; + } + break; - /* count the number of arguments we got */ - ++argc; - } + case PARSE_SINGLEQUOTE: + if (*readpos == SQUOTE) { + pstate = PARSE_SPACE; + *writepos++ = '\0'; + goto parse_end; + } + else if (*readpos == ESCAPECHAR) { + pstate = escape_toggle(pstate); + goto parse_end; + } + break; - /* zero out current position (space or quotation mark) and advance */ - if (*pos > 0) { - *pos = 0; - ++pos; - } - else { - break; + case PARSE_DOUBLEQUOTE: + if (*readpos == DQUOTE) { + pstate = PARSE_SPACE; + *writepos++ = '\0'; + goto parse_end; + } + else if (*readpos == ESCAPECHAR) { + pstate = escape_toggle(pstate); + goto parse_end; + } + break; + + default: /* QUOTED state */ + pstate = escape_toggle(pstate); + break; } + *writepos++ = *readpos; + parse_end: + readpos++; } - if (!argc) { + *writepos = '\0'; + + if (pstate != PARSE_SPACE && pstate != PARSE_UNQUOTED) { + puts(INCORRECT_QUOTING); + return; + } + + if (argc == 0) { return; } /* then we fill the argv array */ - char *argv[argc + 1]; - argv[argc] = NULL; - pos = line; - for (unsigned i = 0; i < argc; ++i) { - while (!*pos) { - ++pos; - } - if (*pos == '"' || *pos == '\'') { - ++pos; - } - argv[i] = pos; - while (*pos) { - ++pos; - } - } - for (char **arg = argv; contains_esc_seq && *arg; ++arg) { - for (char *c = *arg; *c; ++c) { - if (*c != '\\') { - continue; - } - for (char *d = c; *d; ++d) { - *d = d[1]; - } - if (--contains_esc_seq == 0) { - break; - } - } + int collected; + char *argv[argc]; + + readpos = line; + for (collected = 0; collected < argc; collected++) { + argv[collected] = readpos; + readpos += strlen(readpos) + 1; } /* then we call the appropriate handler */ From 37cff932543c4b3791e099ff3299ff56c8865d49 Mon Sep 17 00:00:00 2001 From: Hendrik van Essen Date: Sat, 8 Feb 2020 15:07:16 +0100 Subject: [PATCH 3/6] sys/shell: further refactor tokenizer (part 1/2) Factor out common code for quoted and unquoted tokens. This makes the code slighly less clear, but it also eliminates repetition (which may improve clarity). Co-authored-by: Juan Carrano --- sys/shell/shell.c | 68 +++++++++++++++++++++-------------------------- 1 file changed, 31 insertions(+), 37 deletions(-) diff --git a/sys/shell/shell.c b/sys/shell/shell.c index 667d2620a7..e545e0e262 100644 --- a/sys/shell/shell.c +++ b/sys/shell/shell.c @@ -170,20 +170,24 @@ static void print_help(const shell_command_t *command_list) */ static void handle_input_line(const shell_command_t *command_list, char *line) { - static const char *INCORRECT_QUOTING = "shell: incorrect quoting"; - /* first we need to calculate the number of arguments */ int argc = 0; char *readpos = line; char *writepos = readpos; enum PARSE_STATE pstate = PARSE_SPACE; - while (*readpos != '\0') { + for (; *readpos != '\0'; readpos++) { + + char wordbreak = BLANK; + bool is_wordbreak = false; + switch (pstate) { + case PARSE_SPACE: if (*readpos != BLANK) { argc++; } + if (*readpos == SQUOTE) { pstate = PARSE_SINGLEQUOTE; } @@ -195,58 +199,48 @@ static void handle_input_line(const shell_command_t *command_list, char *line) } else if (*readpos != BLANK) { pstate = PARSE_UNQUOTED; - break; + *writepos++ = *readpos; } - goto parse_end; - + break; + case PARSE_UNQUOTED: - if (*readpos == BLANK) { - pstate = PARSE_SPACE; - *writepos++ = '\0'; - goto parse_end; - } - else if (*readpos == ESCAPECHAR) { - pstate = escape_toggle(pstate); - goto parse_end; - } + wordbreak = BLANK; + is_wordbreak = true; break; case PARSE_SINGLEQUOTE: - if (*readpos == SQUOTE) { - pstate = PARSE_SPACE; - *writepos++ = '\0'; - goto parse_end; - } - else if (*readpos == ESCAPECHAR) { - pstate = escape_toggle(pstate); - goto parse_end; - } + wordbreak = SQUOTE; + is_wordbreak = true; break; case PARSE_DOUBLEQUOTE: - if (*readpos == DQUOTE) { - pstate = PARSE_SPACE; - *writepos++ = '\0'; - goto parse_end; - } - else if (*readpos == ESCAPECHAR) { - pstate = escape_toggle(pstate); - goto parse_end; - } + wordbreak = DQUOTE; + is_wordbreak = true; break; default: /* QUOTED state */ pstate = escape_toggle(pstate); + *writepos++ = *readpos; break; } - *writepos++ = *readpos; - parse_end: - readpos++; + + if (is_wordbreak) { + if (*readpos == wordbreak) { + pstate = PARSE_SPACE; + *writepos++ = '\0'; + } + else if (*readpos == ESCAPECHAR) { + pstate = escape_toggle(pstate); + } + else { + *writepos++ = *readpos; + } + } } *writepos = '\0'; if (pstate != PARSE_SPACE && pstate != PARSE_UNQUOTED) { - puts(INCORRECT_QUOTING); + puts("shell: incorrect quoting"); return; } From 0782b493ed0edb38b47c39d7c13fc623e96bd795 Mon Sep 17 00:00:00 2001 From: Hendrik van Essen Date: Sat, 8 Feb 2020 15:15:49 +0100 Subject: [PATCH 4/6] sys/shell: simplify array traversal code The code for traversing arrays of shell commands (used to print help messages and to search for commmand handlers) was needlessly complex. Co-authored-by: Juan Carrano --- sys/shell/shell.c | 87 ++++++++++++++++++++++------------------------- 1 file changed, 40 insertions(+), 47 deletions(-) diff --git a/sys/shell/shell.c b/sys/shell/shell.c index e545e0e262..72808a2337 100644 --- a/sys/shell/shell.c +++ b/sys/shell/shell.c @@ -42,12 +42,6 @@ #define BS '\x08' /** ASCII "Backspace" */ #define DEL '\x7f' /** ASCII "Delete" */ -#ifdef MODULE_SHELL_COMMANDS - #define MORE_COMMANDS _shell_command_list -#else - #define MORE_COMMANDS -#endif /* MODULE_SHELL_COMMANDS */ - #ifdef MODULE_NEWLIB #define flush_if_needed() fflush(stdout) #else @@ -66,6 +60,12 @@ #define PROMPT_ON 0 #endif /* SHELL_NO_PROMPT */ +#ifdef MODULE_SHELL_COMMANDS + #define _builtin_cmds _shell_command_list +#else + #define _builtin_cmds NULL +#endif + #define SQUOTE '\'' #define DQUOTE '"' #define ESCAPECHAR '\\' @@ -87,56 +87,49 @@ static enum PARSE_STATE escape_toggle(enum PARSE_STATE s) return s ^ PARSE_ESCAPE_MASK; } -static shell_command_handler_t find_handler(const shell_command_t *command_list, char *command) +static shell_command_handler_t search_commands(const shell_command_t *entry, + char *command) { - const shell_command_t *command_lists[] = { - command_list, - MORE_COMMANDS - }; - - /* iterating over command_lists */ - for (unsigned int i = 0; i < ARRAY_SIZE(command_lists); i++) { - - const shell_command_t *entry; - - if ((entry = command_lists[i])) { - /* iterating over commands in command_lists entry */ - while (entry->name != NULL) { - if (strcmp(entry->name, command) == 0) { - return entry->handler; - } - else { - entry++; - } - } + for (; entry->name != NULL; entry++) { + if (strcmp(entry->name, command) == 0) { + return entry->handler; } } - return NULL; } +static shell_command_handler_t find_handler( + const shell_command_t *command_list, char *command) +{ + shell_command_handler_t handler = NULL; + if (command_list != NULL) { + handler = search_commands(command_list, command); + } + + if (handler == NULL && _builtin_cmds != NULL) { + handler = search_commands(_builtin_cmds, command); + } + + return handler; +} + +static void print_commands(const shell_command_t *entry) +{ + for (; entry->name != NULL; entry++) { + printf("%-20s %s\n", entry->name, entry->desc); + } +} + static void print_help(const shell_command_t *command_list) { - printf("%-20s %s\n", "Command", "Description"); - puts("---------------------------------------"); + puts("Command Description" + "\n---------------------------------------"); + if (command_list != NULL) { + print_commands(command_list); + } - const shell_command_t *command_lists[] = { - command_list, - MORE_COMMANDS - }; - - /* iterating over command_lists */ - for (unsigned int i = 0; i < ARRAY_SIZE(command_lists); i++) { - - const shell_command_t *entry; - - if ((entry = command_lists[i])) { - /* iterating over commands in command_lists entry */ - while (entry->name != NULL) { - printf("%-20s %s\n", entry->name, entry->desc); - entry++; - } - } + if (_builtin_cmds != NULL) { + print_commands(_builtin_cmds); } } From cc759ebccabc16e60644586b1423cfec8b21b096 Mon Sep 17 00:00:00 2001 From: Hendrik van Essen Date: Wed, 17 Jun 2020 14:48:14 +0200 Subject: [PATCH 5/6] sys/shell: further refactor tokenizer (part 2/2) Code now correctly handles quotes within PARSE_UNQUOTED and tabs are now considered a BLANK just like a space. --- sys/shell/shell.c | 128 +++++++++++++++++++++++++++++++--------------- 1 file changed, 87 insertions(+), 41 deletions(-) diff --git a/sys/shell/shell.c b/sys/shell/shell.c index 72808a2337..abcb437cfa 100644 --- a/sys/shell/shell.c +++ b/sys/shell/shell.c @@ -69,20 +69,24 @@ #define SQUOTE '\'' #define DQUOTE '"' #define ESCAPECHAR '\\' -#define BLANK ' ' +#define SPACE ' ' +#define TAB '\t' -enum PARSE_STATE { - PARSE_SPACE, - PARSE_UNQUOTED, - PARSE_SINGLEQUOTE, - PARSE_DOUBLEQUOTE, - PARSE_ESCAPE_MASK, - PARSE_UNQUOTED_ESC, - PARSE_SINGLEQUOTE_ESC, - PARSE_DOUBLEQUOTE_ESC, +#define PARSE_ESCAPE_MASK 0x4; + +enum parse_state { + PARSE_BLANK = 0x0, + + PARSE_UNQUOTED = 0x1, + PARSE_SINGLEQUOTE = 0x2, + PARSE_DOUBLEQUOTE = 0x3, + + PARSE_UNQUOTED_ESC = 0x5, + PARSE_SINGLEQUOTE_ESC = 0x6, + PARSE_DOUBLEQUOTE_ESC = 0x7, }; -static enum PARSE_STATE escape_toggle(enum PARSE_STATE s) +static enum parse_state escape_toggle(enum parse_state s) { return s ^ PARSE_ESCAPE_MASK; } @@ -136,30 +140,56 @@ static void print_help(const shell_command_t *command_list) /** * Break input line into words, create argv and call the command handler. * - * Words are broken up at spaces. A backslash escaped the character that comes + * Words are broken up at spaces. A backslash escapes the character that comes * after (meaning if it is taken literally and if it is a space it does not break * the word). Spaces can also be protected by quoting with double or single * quotes. * - State diagram for the tokenizer: -``` - ┌───[\]────┐ ┌─────["]────┐ ┌───[']─────┐ ┌───[\]────┐ - ↓ │ ↓ │ │ ↓ │ ↓ - ┏━━━━━━━━━━┓ ┏━┷━━━━━┓ ┏━┷━━━┷━┓ ┏━━━━┷━━┓ ┏━━━━━━━━━━┓ - ┃DQUOTE ESC┃ ┃DQUOTE ┠───["]─>┃SPACE ┃<─[']──┨SQUOTE ┃ ┃SQUOTE ESC┃ - ┗━━━━━━━━┯━┛ ┗━━━━━━┯┛ ┗┯━━━━┯━┛ ┗━┯━━━━━┛ ┗━━━┯━━━━━━┛ - │ ↑ │ │ │ │ ↑(store) │ - │ (store)│ │ ┌─[\]──┘ └──[*]────┐ │ │ │ - └──[*]──▶┴◀[*]┘ │ │ └[*]▶┴◀──[*]──┘ - ↓ ┏━━━━━━━┓ ↓ - ├◀[\]┨NOQUOTE┃◀─────┼◀─┐ - │ ┗━━━━━┯━┛(store)↑ │ - │ │ │ │ - │ └─[*]─────┘ │ - │ ┏━━━━━━━━━━━┓ │ - └───▶┃NOQUOTE ESC┠──[*]──┘ - ┗━━━━━━━━━━━┛ -``` + * There are two unquoted states (PARSE_BLANK and PARSE_UNQUOTED) and two quoted + * states (PARSE_SINGLEQUOTE and PARSE_DOUBLEQUOTE). In addition, every state + * (except PARSE_BLANK) has an escaped pair state (e.g PARSE_SINGLEQUOTE and + * PARSE_SINGLEQUOTE_ESC). + * + * For the following let's define some things + * - Function transit(character, state) to change to 'state' after + * 'character' was read. The order of a list of transit-functions matters. + * - A BLANK is either SPACE or TAB + * - '*' means any character + * + * PARSE_BLANK + * transit(SQUOTE, PARSE_SINGLEQUOTE) + * transit(DQUOTE, PARSE_DOUBLEQUOTE) + * transit(ESCAPECHAR, PARSE_UNQUOTED_ESC) + * transit(BLANK, PARSE_BLANK) + * transit(*, PARSE_UNQUOTED) -> store character + * + * PARSE_UNQUOTED + * transit(SQUOTE, PARSE_SINGLEQUOTE) + * transit(DQUOTE, PARSE_DOUBLEQUOTE) + * transit(BLANK, PARSE_BLANK) + * transit(ESCAPECHAR, PARSE_UNQUOTED_ESC) + * transit(*, PARSE_UNQUOTED) -> store character + * + * PARSE_UNQUOTED_ESC + * transit(*, PARSE_UNQUOTED) -> store character + * + * PARSE_SINGLEQUOTE + * transit(SQUOTE, PARSE_UNQUOTED) + * transit(ESCAPECHAR, PARSE_SINGLEQUOTE_ESC) + * transit(*, PARSE_SINGLEQUOTE) -> store character + * + * PARSE_SINGLEQUOTE_ESC + * transit(*, PARSE_SINGLEQUOTE) -> store character + * + * PARSE_DOUBLEQUOTE + * transit(DQUOTE, PARSE_UNQUOTED) + * transit(ESCAPECHAR, PARSE_DOUBLEQUOTE_ESC) + * transit(*, PARSE_DOUBLEQUOTE) -> store character + * + * PARSE_DOUBLEQUOTE_ESC + * transit(*, PARSE_DOUBLEQUOTE) -> store character + * + * */ static void handle_input_line(const shell_command_t *command_list, char *line) { @@ -167,17 +197,18 @@ static void handle_input_line(const shell_command_t *command_list, char *line) int argc = 0; char *readpos = line; char *writepos = readpos; - enum PARSE_STATE pstate = PARSE_SPACE; + + uint8_t pstate = PARSE_BLANK; for (; *readpos != '\0'; readpos++) { - char wordbreak = BLANK; + char wordbreak = SPACE; bool is_wordbreak = false; switch (pstate) { - case PARSE_SPACE: - if (*readpos != BLANK) { + case PARSE_BLANK: + if (*readpos != SPACE && *readpos != TAB) { argc++; } @@ -190,15 +221,29 @@ static void handle_input_line(const shell_command_t *command_list, char *line) else if (*readpos == ESCAPECHAR) { pstate = PARSE_UNQUOTED_ESC; } - else if (*readpos != BLANK) { + else if (*readpos != SPACE && *readpos != TAB) { pstate = PARSE_UNQUOTED; *writepos++ = *readpos; } break; case PARSE_UNQUOTED: - wordbreak = BLANK; - is_wordbreak = true; + if (*readpos == SQUOTE) { + pstate = PARSE_SINGLEQUOTE; + } + else if (*readpos == DQUOTE) { + pstate = PARSE_DOUBLEQUOTE; + } + else if (*readpos == ESCAPECHAR) { + pstate = escape_toggle(pstate); + } + else if (*readpos == SPACE || *readpos == TAB) { + pstate = PARSE_BLANK; + *writepos++ = '\0'; + } + else { + *writepos++ = *readpos; + } break; case PARSE_SINGLEQUOTE: @@ -219,8 +264,9 @@ static void handle_input_line(const shell_command_t *command_list, char *line) if (is_wordbreak) { if (*readpos == wordbreak) { - pstate = PARSE_SPACE; - *writepos++ = '\0'; + if (wordbreak == SQUOTE || wordbreak == DQUOTE) { + pstate = PARSE_UNQUOTED; + } } else if (*readpos == ESCAPECHAR) { pstate = escape_toggle(pstate); @@ -232,7 +278,7 @@ static void handle_input_line(const shell_command_t *command_list, char *line) } *writepos = '\0'; - if (pstate != PARSE_SPACE && pstate != PARSE_UNQUOTED) { + if (pstate != PARSE_BLANK && pstate != PARSE_UNQUOTED) { puts("shell: incorrect quoting"); return; } From cbf5cc53273e7ba8022c122db974eb8cd9f7b313 Mon Sep 17 00:00:00 2001 From: Hendrik van Essen Date: Wed, 17 Jun 2020 15:23:31 +0200 Subject: [PATCH 6/6] tests/shell: refactor test file and add several new test cases Divide test cases in to groups and add test cases for: - multiple spaces between arguments - tabs between arguments - leading/trailing spaces - more simple variations for escaping - multiple tests for correct quoting A second occurence of the test case "('help', EXPECTED_HELP)," was removed. --- tests/shell/tests/01-run.py | 47 +++++++++++++++++++++++++++++++++---- 1 file changed, 42 insertions(+), 5 deletions(-) diff --git a/tests/shell/tests/01-run.py b/tests/shell/tests/01-run.py index c01fd10069..ecd4f43b92 100755 --- a/tests/shell/tests/01-run.py +++ b/tests/shell/tests/01-run.py @@ -43,24 +43,61 @@ CONTROL_D = DLE+'\x04' PROMPT = '> ' CMDS = ( + # test start ('start_test', '[TEST_START]'), (CONTROL_C, PROMPT), ('\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 long line ('123456789012345678901234567890123456789012345678901234567890', 'shell: command not found: ' '123456789012345678901234567890123456789012345678901234567890'), ('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\"'), - ('help', EXPECTED_HELP), - ('echo a string', '\"echo\"\"a\"\"string\"'), + ('\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"'), + + # 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 "\'" \'"\'', '"echo""\'""""'), + ('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 end ('reboot', 'test_shell.'), ('end_test', '[TEST_END]'), )