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", "invalid substring count"}, | ||
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|-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 0 (21→5) taken 138 times.
✓ Branch 1 (21→22) taken 1 times.
|
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 0 (15→16) taken 138 times.
✗ Branch 1 (15→19) not taken.
✓ Branch 2 (16→17) taken 138 times.
✗ Branch 3 (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 |