| Line | Branch | Exec | Source |
|---|---|---|---|
| 1 | #include "test.h" | ||
| 2 | #include "command/error.h" | ||
| 3 | #include "commands.h" | ||
| 4 | #include "editor.h" | ||
| 5 | #include "util/ascii.h" | ||
| 6 | #include "util/debug.h" | ||
| 7 | #include "window.h" | ||
| 8 | |||
| 9 | 1 | static const char *dummy_lookup_alias(const EditorState *e, const char *name) | |
| 10 | { | ||
| 11 | 1 | BUG_ON(!e); | |
| 12 | 1 | BUG_ON(!name); | |
| 13 | 1 | return "insert \"..."; | |
| 14 | } | ||
| 15 | |||
| 16 | 1 | static void test_normal_command_errors(TestContext *ctx) | |
| 17 | { | ||
| 18 | 1 | static const struct { | |
| 19 | const char *cmd_str; | ||
| 20 | const char *expected_error_substr; | ||
| 21 | } tests[] = { | ||
| 22 | // Note: do not include error substrings produced by strerror(3) | ||
| 23 | {"non__existent__command", "no such command or alias"}, | ||
| 24 | {"alias '' up", "empty alias name"}, | ||
| 25 | {"alias -- -name up", "alias name cannot begin with '-'"}, | ||
| 26 | {"alias 'x y' up", "invalid byte in alias name"}, | ||
| 27 | {"alias open close", "can't replace existing command"}, | ||
| 28 | {"alias a b; alias b a; a; alias a; alias b", "alias recursion limit reached"}, | ||
| 29 | {"bind C-C-C", "invalid key string"}, | ||
| 30 | {"bind -T invalid-mode C-k eol", "can't bind key in unknown mode"}, | ||
| 31 | {"cd", "too few arguments"}, | ||
| 32 | {"cd ''", "directory argument cannot be empty"}, | ||
| 33 | {"cd /non-existent/dir/_x_29_", "changing directory failed"}, | ||
| 34 | {"compile dfa34dazzdf2 echo", "no such error parser"}, | ||
| 35 | {"cursor xyz bar #f40", "invalid mode argument"}, | ||
| 36 | {"cursor insert _ #f40", "invalid cursor type"}, | ||
| 37 | {"cursor insert bar blue", "invalid cursor color"}, | ||
| 38 | {"def-mode normal", "mode 'normal' already exists"}, | ||
| 39 | {"def-mode command", "mode 'command' already exists"}, | ||
| 40 | {"def-mode search", "mode 'search' already exists"}, | ||
| 41 | {"def-mode -- -", "mode name can't be empty or start with '-'"}, | ||
| 42 | {"def-mode ''", "mode name can't be empty or start with '-'"}, | ||
| 43 | {"def-mode xz qrst9", "unknown fallthrough mode 'qrst9'"}, | ||
| 44 | {"def-mode xz search", "unable to use 'search' as fall-through mode"}, | ||
| 45 | {"def-mode _m; def-mode _m", "mode '_m' already exists"}, | ||
| 46 | {"errorfmt x (re) zz", "unknown substring name"}, | ||
| 47 | {"errorfmt x (re) file line", "expected 2 subexpressions in regex, but found 1: '(re)'"}, | ||
| 48 | {"exec -s sh -c 'kill -s USR1 $$'", "child received signal"}, | ||
| 49 | {"exec -s sh -c 'exit 44'", "child returned 44"}, | ||
| 50 | {"exec -s _z9kjdf_2dmm_92:j-a3d_xzkw::", "unable to exec"}, | ||
| 51 | {"exec -s -e errmsg sh -c 'echo X >&2; exit 9'", "child returned 9: \"X\""}, | ||
| 52 | {"exec -s -i invalid true", "invalid action for -i: 'invalid'"}, | ||
| 53 | {"exec -s -o invalid true", "invalid action for -o: 'invalid'"}, | ||
| 54 | {"exec -s -e invalid true", "invalid action for -e: 'invalid'"}, | ||
| 55 | {"ft -- -name ext", "invalid filetype name"}, | ||
| 56 | {"hi xyz red green blue", "too many colors"}, | ||
| 57 | {"hi xyz _invalid_", "invalid color or attribute"}, | ||
| 58 | {"include -b nonexistent", "no built-in config with name 'nonexistent'"}, | ||
| 59 | {"include /.n-o-n-e-x-i-s-t-e-n-t/_/az_..3", "error reading"}, | ||
| 60 | {"line 0", "invalid line number"}, | ||
| 61 | {"line _", "invalid line number"}, | ||
| 62 | {"line 1:x", "invalid line number"}, | ||
| 63 | {"macro xyz", "unknown action"}, | ||
| 64 | {"match-bracket", "no character under cursor"}, | ||
| 65 | {"mode inValiD", "unknown mode 'inValiD'"}, | ||
| 66 | {"move-tab 0", "invalid tab position"}, | ||
| 67 | {"msg -p 12", "flags [-n|-p] can't be used with [number] argument"}, | ||
| 68 | {"open -t filename", "can't be used with filename arguments"}, | ||
| 69 | {"open /non/exi/ste/nt/file_Name", "directory does not exist"}, | ||
| 70 | {"open /dev/null", "not a regular file"}, | ||
| 71 | {"open ''", "empty filename not allowed"}, | ||
| 72 | {"option c auto-indent true syntax", "missing option value"}, | ||
| 73 | {"option c filetype c", "filetype cannot be set via option command"}, | ||
| 74 | {"option c invalid 1", "no such option"}, | ||
| 75 | {"option c tab-bar true", "is not local"}, | ||
| 76 | {"option c detect-indent 1,0", "invalid flag"}, | ||
| 77 | {"option c detect-indent 9", "invalid flag"}, | ||
| 78 | {"option c indent-width 0", "must be in 1-8 range"}, | ||
| 79 | {"option c indent-width 9", "must be in 1-8 range"}, | ||
| 80 | {"option c auto-indent xyz", "invalid value"}, | ||
| 81 | {"option , auto-indent true", "invalid filetype"}, | ||
| 82 | {"option '' auto-indent true", "first argument cannot be empty"}, | ||
| 83 | {"option -r '' auto-indent true", "first argument cannot be empty"}, | ||
| 84 | {"quit xyz", "not a valid integer"}, | ||
| 85 | {"quit 9000", "exit code should be between"}, | ||
| 86 | {"redo 8", "nothing to redo"}, | ||
| 87 | {"redo A", "invalid change id"}, | ||
| 88 | {"repeat x up", "not a valid repeat count"}, | ||
| 89 | {"repeat 2 invalid-cmd", "no such command: invalid-cmd"}, | ||
| 90 | {"replace '' x", "must contain at least 1 character"}, | ||
| 91 | {"replace e5fwHgHuCFVZd x", "not found"}, | ||
| 92 | {"replace -c a b", "-c flag unavailable in headless mode"}, | ||
| 93 | {"save ''", "empty filename"}, | ||
| 94 | {"save", "no filename"}, | ||
| 95 | {"save /dev/", "will not overwrite directory"}, | ||
| 96 | {"search -nw", "mutually exclusive"}, | ||
| 97 | {"search -p str", "mutually exclusive"}, | ||
| 98 | {"search e5fwHgHuCFVZd", "not found"}, | ||
| 99 | {"set filetype", "not boolean"}, | ||
| 100 | {"set filetype ' '", "invalid filetype name"}, | ||
| 101 | {"set tab-width s", "integer value for tab-width expected"}, | ||
| 102 | {"set indent-width 9", "must be in 1-8 range"}, | ||
| 103 | {"set emulate-tab x", "invalid value"}, | ||
| 104 | {"set msg-compile xyz", "invalid value for msg-compile; expected: A, B, C"}, | ||
| 105 | {"set msg-tag xyz", "invalid value for msg-tag; expected: A, B, C"}, | ||
| 106 | {"set newline xyz", "invalid value for newline; expected: unix, dos"}, | ||
| 107 | { | ||
| 108 | "set case-sensitive-search z", | ||
| 109 | "invalid value for case-sensitive-search; expected: false, true, auto" | ||
| 110 | }, | ||
| 111 | { | ||
| 112 | "set save-unmodified xyz", | ||
| 113 | "invalid value for save-unmodified; expected: none, touch, full" | ||
| 114 | }, | ||
| 115 | { | ||
| 116 | "set window-separator xyz", | ||
| 117 | "invalid value for window-separator; expected: blank, bar" | ||
| 118 | }, | ||
| 119 | { | ||
| 120 | "set ws-error A", | ||
| 121 | "invalid flag 'A' for ws-error; expected: space-indent," | ||
| 122 | " space-align, tab-indent, tab-after-indent, special," | ||
| 123 | " auto-indent, trailing, all-trailing" | ||
| 124 | }, | ||
| 125 | {"set non-existent 1", "no such option"}, | ||
| 126 | {"set -g filetype c", "not global"}, | ||
| 127 | {"set -l statusline-right _", "not local"}, | ||
| 128 | {"set 1 2 3", "one or even number of arguments expected"}, | ||
| 129 | {"set statusline-left %", "format character expected"}, | ||
| 130 | {"set statusline-left %!", "invalid format character"}, | ||
| 131 | {"set filesize-limit 1M", "invalid filesize"}, | ||
| 132 | {"set filesize-limit 1Mi", "invalid filesize"}, | ||
| 133 | {"set filesize-limit 1MB", "invalid filesize"}, | ||
| 134 | {"set filesize-limit 1MIB", "invalid filesize"}, | ||
| 135 | {"set filesize-limit 1Mib", "invalid filesize"}, | ||
| 136 | {"setenv DTE_VERSION 0.1", "cannot be changed"}, | ||
| 137 | {"shift x", "invalid number"}, | ||
| 138 | {"shift 0", "must be non-zero"}, | ||
| 139 | {"show -c alias", "requires 2 arguments"}, | ||
| 140 | {"show zzz", "invalid argument"}, | ||
| 141 | {"show bind M-M-M---", "invalid key string"}, | ||
| 142 | {"show buffer x", "doesn't take extra arguments"}, | ||
| 143 | {"show builtin a78sd8623d7k", "no built-in config"}, | ||
| 144 | {"show cursor xyz", "no cursor entry"}, | ||
| 145 | {"show option 31ldx92kjsd8", "invalid option name"}, | ||
| 146 | {"show wsplit m2wz9u263t8a", "invalid window"}, | ||
| 147 | {"suspend", "unavailable in headless mode"}, | ||
| 148 | {"toggle ws-error", "requires arguments"}, | ||
| 149 | {"view z", "invalid view index"}, | ||
| 150 | {"view 0", "invalid view index"}, | ||
| 151 | {"wrap-paragraph _", "invalid paragraph width"}, | ||
| 152 | {"wrap-paragraph 90000", "width must be between"}, | ||
| 153 | {"wsplit -t file", "flags [-n|-t] can't be used with [filename] arguments"}, | ||
| 154 | {"wsplit -t; wresize nonnum; wclose", "invalid resize value"}, | ||
| 155 | |||
| 156 | // Error strings produced by command_parse_error_to_string(): | ||
| 157 | {"x '", "command syntax error: unclosed '"}, | ||
| 158 | {"x \"", "command syntax error: unclosed \""}, | ||
| 159 | {"x \"\\", "command syntax error: unexpected EOF"}, | ||
| 160 | |||
| 161 | // Error strings produced by arg_parse_error_msg(): | ||
| 162 | {"bind -nnnnnnnnnnnnnn C-k eol", "too many options given"}, | ||
| 163 | {"bind -Z C-k eol", "invalid option -Z"}, | ||
| 164 | {"bind -Tsearch C-k eol", "option -T must be given separately"}, | ||
| 165 | {"bind -T ", "option -T requires an argument"}, | ||
| 166 | {"bind 1 2 3", "too many arguments"}, | ||
| 167 | {"bind", "too few arguments"}, | ||
| 168 | |||
| 169 | // These are tested last, since some of the commands succeed and | ||
| 170 | // affect the state of the Buffer (i.e. the undo history) and some | ||
| 171 | // are dependant on it (and cannot be reordered) | ||
| 172 | {"insert 1; undo; redo 900", "there is only 1 possible change to redo"}, | ||
| 173 | {"insert 2; undo; redo 900", "there are only 2 possible changes to redo"}, | ||
| 174 | {"insert x; close; undo", "close without saving"}, | ||
| 175 | {"insert x; wclose; undo", "close window without saving"}, | ||
| 176 | {"insert x; quit; undo", "quit without saving"}, | ||
| 177 | {"insert x; close -p; undo", "-p flag unavailable in headless mode"}, | ||
| 178 | {"insert x; wclose -p; undo", "-p flag unavailable in headless mode"}, | ||
| 179 | {"insert x; quit -p; undo", "-p flag unavailable in headless mode"}, | ||
| 180 | {"insert x; match-bracket; undo", "character under cursor not matchable"}, | ||
| 181 | {"insert {{}; match-bracket; undo", "no matching bracket found"}, | ||
| 182 | {"repeat 4100 insert x; search -w; undo", "word under cursor too long"}, | ||
| 183 | }; | ||
| 184 | |||
| 185 | 1 | EditorState *e = ctx->userdata; | |
| 186 | 1 | ErrorBuffer *ebuf = &e->err; | |
| 187 | 1 | ASSERT_NONNULL(window_open_empty_buffer(e->window)); | |
| 188 | |||
| 189 |
2/2✓ Branch 21 → 5 taken 138 times.
✓ Branch 21 → 22 taken 1 time.
|
139 | FOR_EACH_I(i, tests) { |
| 190 | 138 | const char *cmd = tests[i].cmd_str; | |
| 191 | 138 | const char *substr = tests[i].expected_error_substr; | |
| 192 | 138 | ASSERT_NONNULL(cmd); | |
| 193 | 138 | ASSERT_NONNULL(substr); | |
| 194 | 138 | ASSERT_NE(substr[0], '\0'); | |
| 195 | 138 | ASSERT_NE(substr[1], '\0'); | |
| 196 | 138 | ASSERT_TRUE(!ascii_isupper(substr[0])); | |
| 197 | |||
| 198 | 138 | clear_error(ebuf); | |
| 199 | 138 | EXPECT_FALSE(handle_normal_command(e, cmd, false)); | |
| 200 | 138 | EXPECT_NE(ebuf->buf[0], '\0'); | |
| 201 | 138 | EXPECT_TRUE(ebuf->is_error); | |
| 202 | |||
| 203 | // Check for substring in error message (ignoring capitalization) | ||
| 204 | 138 | const char *found = strstr(ebuf->buf, substr + 1); | |
| 205 |
2/4✓ Branch 15 → 16 taken 138 times.
✗ Branch 15 → 19 not taken.
✓ Branch 16 → 17 taken 138 times.
✗ Branch 16 → 19 not taken.
|
138 | if (likely(found && found != ebuf->buf)) { |
| 206 | 138 | test_pass(ctx); | |
| 207 | 138 | EXPECT_EQ(ascii_tolower(found[-1]), substr[0]); | |
| 208 | } else { | ||
| 209 | − | TEST_FAIL ( | |
| 210 | "Test #%zu: substring \"%s\" not found in message \"%s\"", | ||
| 211 | i + 1, substr, ebuf->buf | ||
| 212 | ); | ||
| 213 | } | ||
| 214 | } | ||
| 215 | |||
| 216 | 1 | window_close_current_view(e->window); | |
| 217 | 1 | set_view(e->window->view); | |
| 218 | |||
| 219 | // Special case errors produced by run_command(): | ||
| 220 | // ---------------------------------------------- | ||
| 221 | |||
| 222 | 1 | CommandRunner runner = normal_mode_cmdrunner(e); | |
| 223 | 1 | runner.lookup_alias = NULL; | |
| 224 | 1 | clear_error(ebuf); | |
| 225 | 1 | EXPECT_FALSE(handle_command(&runner, "_xyz")); | |
| 226 | 1 | EXPECT_STREQ(ebuf->buf, "No such command: _xyz"); | |
| 227 | 1 | EXPECT_TRUE(ebuf->is_error); | |
| 228 | |||
| 229 | 1 | runner.lookup_alias = dummy_lookup_alias; | |
| 230 | 1 | clear_error(ebuf); | |
| 231 | 1 | EXPECT_FALSE(handle_command(&runner, "_abc")); | |
| 232 | 1 | EXPECT_TRUE(str_has_prefix(ebuf->buf, "Parsing alias _abc:")); | |
| 233 | 1 | EXPECT_TRUE(ebuf->is_error); | |
| 234 | |||
| 235 | 1 | EXPECT_NULL(ebuf->config_filename); | |
| 236 | 1 | ebuf->config_filename = "example"; | |
| 237 | 1 | ebuf->config_line = 1; | |
| 238 | 1 | runner.lookup_alias = NULL; | |
| 239 | 1 | clear_error(ebuf); | |
| 240 | 1 | EXPECT_FALSE(handle_command(&runner, "quit")); | |
| 241 | 1 | EXPECT_STREQ(ebuf->buf, "example:1: Command quit not allowed in config file"); | |
| 242 | 1 | EXPECT_TRUE(ebuf->is_error); | |
| 243 | 1 | ebuf->config_filename = NULL; | |
| 244 | |||
| 245 | // Special case errors produced by exec_config(): | ||
| 246 | // ---------------------------------------------- | ||
| 247 | |||
| 248 | // This is a special case because handle_command() currently returns true, | ||
| 249 | // since cmd_include() only indicates failure for I/O errors and not for | ||
| 250 | // failing commands in the loaded file | ||
| 251 | 1 | clear_error(ebuf); | |
| 252 | 1 | EXPECT_TRUE(handle_command(&runner, "include test/data/recursive.dterc")); | |
| 253 | 1 | EXPECT_STREQ(ebuf->buf, "test/data/recursive.dterc:1: config recursion limit reached"); | |
| 254 | 1 | EXPECT_TRUE(ebuf->is_error); | |
| 255 | 1 | } | |
| 256 | |||
| 257 | static const TestEntry tests[] = { | ||
| 258 | TEST(test_normal_command_errors), | ||
| 259 | }; | ||
| 260 | |||
| 261 | const TestGroup error_tests = TEST_GROUP(tests); | ||
| 262 |