Line | Branch | Exec | Source |
---|---|---|---|
1 | #include <limits.h> | ||
2 | #include "test.h" | ||
3 | #include "command/alias.h" | ||
4 | #include "command/args.h" | ||
5 | #include "command/cache.h" | ||
6 | #include "command/parse.h" | ||
7 | #include "command/run.h" | ||
8 | #include "command/serialize.h" | ||
9 | #include "commands.h" | ||
10 | #include "editor.h" | ||
11 | #include "replace.h" | ||
12 | #include "search.h" | ||
13 | #include "util/ascii.h" | ||
14 | #include "util/debug.h" | ||
15 | #include "util/path.h" | ||
16 | #include "util/str-util.h" | ||
17 | |||
18 | 1 | static void test_parse_command_arg(TestContext *ctx) | |
19 | { | ||
20 | 1 | EditorState *e = ctx->userdata; | |
21 | 1 | CommandRunner runner = normal_mode_cmdrunner(e); | |
22 | 1 | runner.expand_tilde_slash = false; | |
23 | 1 | ASSERT_PTREQ(runner.e, e); | |
24 | 1 | ASSERT_PTREQ(runner.cmds, &normal_commands); | |
25 | 1 | EXPECT_PTREQ(runner.lookup_alias, find_normal_alias); | |
26 | 1 | EXPECT_PTREQ(runner.home_dir, &e->home_dir); | |
27 | 1 | EXPECT_FALSE(runner.allow_recording); | |
28 | 1 | EXPECT_EQ(runner.recursion_count, 0); | |
29 | |||
30 | // Single, unquoted argument | ||
31 | 1 | char *arg = parse_command_arg(&runner, STRN("arg")); | |
32 | 1 | EXPECT_STREQ(arg, "arg"); | |
33 | 1 | free(arg); | |
34 | |||
35 | // Two unquoted, space-separated arguments | ||
36 | 1 | arg = parse_command_arg(&runner, STRN("hello world")); | |
37 | 1 | EXPECT_STREQ(arg, "hello"); | |
38 | 1 | free(arg); | |
39 | |||
40 | // Two unquoted, tab-separated arguments | ||
41 | 1 | arg = parse_command_arg(&runner, STRN("hello\tworld")); | |
42 | 1 | EXPECT_STREQ(arg, "hello"); | |
43 | 1 | free(arg); | |
44 | |||
45 | // Unquoted argument preceded by whitespace | ||
46 | 1 | arg = parse_command_arg(&runner, STRN(" x")); | |
47 | 1 | EXPECT_STREQ(arg, ""); | |
48 | 1 | free(arg); | |
49 | |||
50 | // Single-quoted argument, including whitespace | ||
51 | 1 | arg = parse_command_arg(&runner, STRN("' foo ' ..")); | |
52 | 1 | EXPECT_STREQ(arg, " foo "); | |
53 | 1 | free(arg); | |
54 | |||
55 | // Several adjacent, quoted strings forming a single argument | ||
56 | 1 | arg = parse_command_arg(&runner, STRN("\"foo\"'bar'' baz '\"etc\".")); | |
57 | 1 | EXPECT_STREQ(arg, "foobar baz etc."); | |
58 | 1 | free(arg); | |
59 | |||
60 | // Control character escapes in a double-quoted string | ||
61 | 1 | arg = parse_command_arg(&runner, STRN("\"\\a\\b\\t\\n\\v\\f\\r\"")); | |
62 | 1 | EXPECT_STREQ(arg, "\a\b\t\n\v\f\r"); | |
63 | 1 | free(arg); | |
64 | |||
65 | // Backslash escape sequence in a double-quoted string | ||
66 | 1 | arg = parse_command_arg(&runner, STRN("\"\\\\\"")); | |
67 | 1 | EXPECT_STREQ(arg, "\\"); | |
68 | 1 | free(arg); | |
69 | |||
70 | // Double-quote escape sequence in a double-quoted string | ||
71 | 1 | arg = parse_command_arg(&runner, STRN("\"\\\"\"")); | |
72 | 1 | EXPECT_STREQ(arg, "\""); | |
73 | 1 | free(arg); | |
74 | |||
75 | // Escape character escape sequence in a double-quoted string | ||
76 | 1 | arg = parse_command_arg(&runner, STRN("\"\\e[0m\"")); | |
77 | 1 | EXPECT_STREQ(arg, "\033[0m"); | |
78 | 1 | free(arg); | |
79 | |||
80 | // Unrecognized escape sequence in a double-quoted string | ||
81 | 1 | arg = parse_command_arg(&runner, STRN("\"\\z\"")); | |
82 | 1 | EXPECT_STREQ(arg, "\\z"); | |
83 | 1 | free(arg); | |
84 | |||
85 | // Hexadecimal escape sequences in a double-quoted string | ||
86 | 1 | arg = parse_command_arg(&runner, STRN("\"\\x1B[31m\\x7E\\x2f\\x1b[0m\"")); | |
87 | 1 | EXPECT_STREQ(arg, "\x1B[31m~/\x1B[0m"); | |
88 | 1 | free(arg); | |
89 | |||
90 | // Invalid hexadecimal escape sequences | ||
91 | 1 | arg = parse_command_arg(&runner, STRN("\"\\x\\x1\\xFG\\xz1\"")); | |
92 | 1 | EXPECT_STREQ(arg, "Gz1"); | |
93 | 1 | free(arg); | |
94 | |||
95 | // Incomplete hexadecimal escape sequence | ||
96 | 1 | arg = parse_command_arg(&runner, STRN("\"\\x")); | |
97 | 1 | EXPECT_STREQ(arg, ""); | |
98 | 1 | free(arg); | |
99 | |||
100 | // 4-digit Unicode escape sequence | ||
101 | 1 | arg = parse_command_arg(&runner, STRN("\"\\u148A\"")); | |
102 | 1 | EXPECT_STREQ(arg, "\xE1\x92\x8A"); | |
103 | 1 | free(arg); | |
104 | |||
105 | // 8-digit Unicode escape sequence | ||
106 | 1 | arg = parse_command_arg(&runner, STRN("\"\\U0001F4A4\"")); | |
107 | 1 | EXPECT_STREQ(arg, "\xF0\x9F\x92\xA4"); | |
108 | 1 | free(arg); | |
109 | |||
110 | // "\U" escape sequence terminated by non-hexadecimal character | ||
111 | 1 | arg = parse_command_arg(&runner, STRN("\"\\U1F4A4...\"")); | |
112 | 1 | EXPECT_STREQ(arg, "\xF0\x9F\x92\xA4..."); | |
113 | 1 | free(arg); | |
114 | |||
115 | // Incomplete Unicode escape sequence | ||
116 | 1 | arg = parse_command_arg(&runner, STRN("\"\\u")); | |
117 | 1 | EXPECT_STREQ(arg, ""); | |
118 | 1 | free(arg); | |
119 | |||
120 | // Invalid Unicode escape sequence | ||
121 | 1 | arg = parse_command_arg(&runner, STRN("\"\\ugef\"")); | |
122 | 1 | EXPECT_STREQ(arg, "gef"); | |
123 | 1 | free(arg); | |
124 | |||
125 | // Unsupported, escape-like sequences in a single-quoted string | ||
126 | 1 | arg = parse_command_arg(&runner, STRN("'\\t\\n'")); | |
127 | 1 | EXPECT_STREQ(arg, "\\t\\n"); | |
128 | 1 | free(arg); | |
129 | |||
130 | // Trailing backslash | ||
131 | // Note: `s` is unterminated, to allow ASan to catch OOB reads | ||
132 | 1 | static const NONSTRING char s[4] = "123\\"; | |
133 | 1 | arg = parse_command_arg(&runner, s, sizeof s); | |
134 | 1 | EXPECT_STREQ(arg, "123"); | |
135 | 1 | free(arg); | |
136 | |||
137 | // Single-quoted, empty string | ||
138 | 1 | arg = parse_command_arg(&runner, STRN("''")); | |
139 | 1 | EXPECT_STREQ(arg, ""); | |
140 | 1 | free(arg); | |
141 | |||
142 | // Double-quoted, empty string | ||
143 | 1 | arg = parse_command_arg(&runner, STRN("\"\"")); | |
144 | 1 | EXPECT_STREQ(arg, ""); | |
145 | 1 | free(arg); | |
146 | |||
147 | // NULL input with zero length | ||
148 | 1 | arg = parse_command_arg(&runner, NULL, 0); | |
149 | 1 | EXPECT_STREQ(arg, ""); | |
150 | 1 | free(arg); | |
151 | |||
152 | // Empty input | ||
153 | 1 | arg = parse_command_arg(&runner, "", 1); | |
154 | 1 | EXPECT_STREQ(arg, ""); | |
155 | 1 | free(arg); | |
156 | |||
157 | // Built-in vars (expand to nothing; buffer isn't initialized yet) | ||
158 | 1 | static const char vars[] = | |
159 | "' '$FILE" | ||
160 | "' '$FILEDIR" | ||
161 | "' '$RFILE" | ||
162 | "' '$RFILEDIR" | ||
163 | "' '$FILETYPE" | ||
164 | "' '$LINENO" | ||
165 | "' '$COLNO" | ||
166 | "' '$WORD" | ||
167 | ; | ||
168 | 1 | arg = parse_command_arg(&runner, vars, sizeof(vars) - 1); | |
169 | 1 | EXPECT_STREQ(arg, " "); | |
170 | 1 | free(arg); | |
171 | |||
172 | // Built-in $DTE_HOME var (expands to user config dir) | ||
173 | 1 | arg = parse_command_arg(&runner, STRN("$DTE_HOME")); | |
174 | 1 | EXPECT_TRUE(path_is_absolute(arg)); | |
175 | 1 | EXPECT_TRUE(str_has_suffix(arg, "/test/DTE_HOME")); | |
176 | 1 | free(arg); | |
177 | |||
178 | // Built-in $MSGPOS var (expands to "1" by default) | ||
179 | 1 | arg = parse_command_arg(&runner, STRN("$MSGPOS")); | |
180 | 1 | EXPECT_STREQ(arg, "1"); | |
181 | 1 | free(arg); | |
182 | |||
183 | // Environment var (via getenv(3)) | ||
184 | 1 | arg = parse_command_arg(&runner, STRN("$DTE_VERSION")); | |
185 | 1 | EXPECT_STREQ(arg, e->version); | |
186 | 1 | free(arg); | |
187 | |||
188 | // Tilde expansion | ||
189 | 1 | runner.expand_tilde_slash = true; | |
190 | 1 | arg = parse_command_arg(&runner, STRN("~/filename")); | |
191 | 1 | EXPECT_TRUE(str_has_suffix(arg, "/test/HOME/filename")); | |
192 | 1 | free(arg); | |
193 | |||
194 | 1 | runner.expand_tilde_slash = false; | |
195 | 1 | arg = parse_command_arg(&runner, STRN("'xyz")); | |
196 | 1 | EXPECT_STREQ(arg, "xyz"); | |
197 | 1 | free(arg); | |
198 | |||
199 | 1 | arg = parse_command_arg(&runner, STRN("\"\\u148A\"xyz'foo'\"\\x5A\"\\;\t.")); | |
200 | 1 | EXPECT_STREQ(arg, "\xE1\x92\x8AxyzfooZ;"); | |
201 | 1 | free(arg); | |
202 | 1 | } | |
203 | |||
204 | 1 | static void test_parse_commands(TestContext *ctx) | |
205 | { | ||
206 | 1 | EditorState *e = ctx->userdata; | |
207 | 1 | const CommandRunner runner = normal_mode_cmdrunner(e); | |
208 | 1 | PointerArray array = PTR_ARRAY_INIT; | |
209 | 1 | EXPECT_EQ(parse_commands(&runner, &array, " left -c;;"), CMDERR_NONE); | |
210 | 1 | ASSERT_EQ(array.count, 5); | |
211 | 1 | EXPECT_STREQ(array.ptrs[0], "left"); | |
212 | 1 | EXPECT_STREQ(array.ptrs[1], "-c"); | |
213 | 1 | EXPECT_NULL(array.ptrs[2]); | |
214 | 1 | EXPECT_NULL(array.ptrs[3]); | |
215 | 1 | EXPECT_NULL(array.ptrs[4]); | |
216 | 1 | ptr_array_free(&array); | |
217 | |||
218 | 1 | EXPECT_EQ(parse_commands(&runner, &array, "save -e UTF-8 file.c; close -q"), CMDERR_NONE); | |
219 | 1 | ASSERT_EQ(array.count, 8); | |
220 | 1 | EXPECT_STREQ(array.ptrs[0], "save"); | |
221 | 1 | EXPECT_STREQ(array.ptrs[1], "-e"); | |
222 | 1 | EXPECT_STREQ(array.ptrs[2], "UTF-8"); | |
223 | 1 | EXPECT_STREQ(array.ptrs[3], "file.c"); | |
224 | 1 | EXPECT_NULL(array.ptrs[4]); | |
225 | 1 | EXPECT_STREQ(array.ptrs[5], "close"); | |
226 | 1 | EXPECT_STREQ(array.ptrs[6], "-q"); | |
227 | 1 | EXPECT_NULL(array.ptrs[7]); | |
228 | 1 | ptr_array_free(&array); | |
229 | |||
230 | 1 | EXPECT_EQ(parse_commands(&runner, &array, "\n ; ; \t\n "), CMDERR_NONE); | |
231 | 1 | ASSERT_EQ(array.count, 3); | |
232 | 1 | EXPECT_NULL(array.ptrs[0]); | |
233 | 1 | EXPECT_NULL(array.ptrs[1]); | |
234 | 1 | EXPECT_NULL(array.ptrs[2]); | |
235 | 1 | ptr_array_free(&array); | |
236 | |||
237 | 1 | EXPECT_EQ(parse_commands(&runner, &array, ""), CMDERR_NONE); | |
238 | 1 | ASSERT_EQ(array.count, 1); | |
239 | 1 | EXPECT_NULL(array.ptrs[0]); | |
240 | 1 | ptr_array_free(&array); | |
241 | |||
242 | 1 | EXPECT_EQ(parse_commands(&runner, &array, "insert '... "), CMDERR_UNCLOSED_SQUOTE); | |
243 | 1 | ptr_array_free(&array); | |
244 | |||
245 | 1 | EXPECT_EQ(parse_commands(&runner, &array, "insert \" "), CMDERR_UNCLOSED_DQUOTE); | |
246 | 1 | ptr_array_free(&array); | |
247 | |||
248 | 1 | EXPECT_EQ(parse_commands(&runner, &array, "insert \"\\\" "), CMDERR_UNCLOSED_DQUOTE); | |
249 | 1 | ptr_array_free(&array); | |
250 | |||
251 | 1 | EXPECT_EQ(parse_commands(&runner, &array, "insert \\"), CMDERR_UNEXPECTED_EOF); | |
252 | 1 | ptr_array_free(&array); | |
253 | |||
254 | 1 | EXPECT_EQ(parse_commands(&runner, &array, "insert \"\\"), CMDERR_UNEXPECTED_EOF); | |
255 | 1 | ptr_array_free(&array); | |
256 | 1 | } | |
257 | |||
258 | 1 | static void test_command_parse_error_to_string(TestContext *ctx) | |
259 | { | ||
260 | 1 | const char *str = command_parse_error_to_string(CMDERR_UNCLOSED_SQUOTE); | |
261 | 1 | EXPECT_STREQ(str, "unclosed '"); | |
262 | 1 | str = command_parse_error_to_string(CMDERR_UNCLOSED_DQUOTE); | |
263 | 1 | EXPECT_STREQ(str, "unclosed \""); | |
264 | 1 | str = command_parse_error_to_string(CMDERR_UNEXPECTED_EOF); | |
265 | 1 | EXPECT_STREQ(str, "unexpected EOF"); | |
266 | 1 | } | |
267 | |||
268 | 1 | static void test_find_normal_command(TestContext *ctx) | |
269 | { | ||
270 | 1 | const Command *cmd = find_normal_command("alias"); | |
271 | 1 | ASSERT_NONNULL(cmd); | |
272 | 1 | EXPECT_STREQ(cmd->name, "alias"); | |
273 | |||
274 | 1 | cmd = find_normal_command("bind"); | |
275 | 1 | ASSERT_NONNULL(cmd); | |
276 | 1 | EXPECT_STREQ(cmd->name, "bind"); | |
277 | |||
278 | 1 | cmd = find_normal_command("wswap"); | |
279 | 1 | ASSERT_NONNULL(cmd); | |
280 | 1 | EXPECT_STREQ(cmd->name, "wswap"); | |
281 | |||
282 | 1 | EXPECT_NULL(find_normal_command("alia")); | |
283 | 1 | EXPECT_NULL(find_normal_command("aliass")); | |
284 | 1 | EXPECT_NULL(find_normal_command("Alias")); | |
285 | 1 | EXPECT_NULL(find_normal_command("bin ")); | |
286 | 1 | EXPECT_NULL(find_normal_command("bind ")); | |
287 | 1 | EXPECT_NULL(find_normal_command(" bind")); | |
288 | 1 | EXPECT_NULL(find_normal_command("bind!")); | |
289 | 1 | EXPECT_NULL(find_normal_command("bind\n")); | |
290 | 1 | } | |
291 | |||
292 | 1 | static void test_parse_args(TestContext *ctx) | |
293 | { | ||
294 | 1 | EditorState *e = ctx->userdata; | |
295 | 1 | const CommandRunner runner = normal_mode_cmdrunner(e); | |
296 | 1 | const CommandSet *cmds = runner.cmds; | |
297 | 1 | const char *cmd_str = "open -g file.c file.h *.mk -e UTF-8"; | |
298 | 1 | PointerArray array = PTR_ARRAY_INIT; | |
299 | 1 | ASSERT_NONNULL(cmds); | |
300 | 1 | ASSERT_EQ(parse_commands(&runner, &array, cmd_str), CMDERR_NONE); | |
301 | 1 | ASSERT_EQ(array.count, 8); | |
302 | |||
303 | 1 | const Command *cmd = cmds->lookup(array.ptrs[0]); | |
304 | 1 | ASSERT_NONNULL(cmd); | |
305 | 1 | EXPECT_STREQ(cmd->name, "open"); | |
306 | |||
307 | 1 | CommandArgs a = cmdargs_new((char**)array.ptrs + 1); | |
308 | 1 | ASSERT_EQ(do_parse_args(cmd, &a), ARGERR_NONE); | |
309 | 1 | EXPECT_EQ(a.nr_flags, 2); | |
310 | 1 | EXPECT_EQ(a.flags[0], 'g'); | |
311 | 1 | EXPECT_EQ(a.flags[1], 'e'); | |
312 | 1 | EXPECT_EQ(a.flags[2], '\0'); | |
313 | 1 | EXPECT_TRUE(cmdargs_has_flag(&a, 'g')); | |
314 | 1 | EXPECT_TRUE(cmdargs_has_flag(&a, 'e')); | |
315 | 1 | EXPECT_FALSE(cmdargs_has_flag(&a, 'f')); | |
316 | 1 | EXPECT_FALSE(cmdargs_has_flag(&a, 'E')); | |
317 | 1 | EXPECT_FALSE(cmdargs_has_flag(&a, '0')); | |
318 | 1 | EXPECT_EQ(a.nr_flag_args, 1); | |
319 | 1 | EXPECT_STREQ(a.args[0], "UTF-8"); | |
320 | 1 | EXPECT_EQ(a.nr_args, 3); | |
321 | 1 | EXPECT_STREQ(a.args[1], "file.c"); | |
322 | 1 | EXPECT_STREQ(a.args[2], "file.h"); | |
323 | 1 | EXPECT_STREQ(a.args[3], "*.mk"); | |
324 | 1 | EXPECT_NULL(a.args[4]); | |
325 | |||
326 | 1 | ptr_array_free(&array); | |
327 | 1 | EXPECT_NULL(array.ptrs); | |
328 | 1 | EXPECT_EQ(array.alloc, 0); | |
329 | 1 | EXPECT_EQ(array.count, 0); | |
330 | |||
331 | 1 | cmd_str = "bind 1 2 3 4 5 -6"; | |
332 | 1 | ASSERT_EQ(parse_commands(&runner, &array, cmd_str), CMDERR_NONE); | |
333 | 1 | ASSERT_EQ(array.count, 8); | |
334 | 1 | EXPECT_STREQ(array.ptrs[0], "bind"); | |
335 | 1 | EXPECT_STREQ(array.ptrs[1], "1"); | |
336 | 1 | EXPECT_STREQ(array.ptrs[6], "-6"); | |
337 | 1 | EXPECT_NULL(array.ptrs[7]); | |
338 | |||
339 | 1 | cmd = cmds->lookup(array.ptrs[0]); | |
340 | 1 | ASSERT_NONNULL(cmd); | |
341 | 1 | EXPECT_STREQ(cmd->name, "bind"); | |
342 | 1 | EXPECT_EQ(cmd->max_args, 2); | |
343 | 1 | EXPECT_EQ(cmd->flags[0], 'c'); | |
344 | |||
345 | 1 | a = cmdargs_new((char**)array.ptrs + 1); | |
346 | 1 | a.flags[0] = 'X'; | |
347 | 1 | ASSERT_EQ(do_parse_args(cmd, &a), ARGERR_TOO_MANY_ARGUMENTS); | |
348 | 1 | EXPECT_EQ(a.nr_args, 6); | |
349 | 1 | EXPECT_EQ(a.nr_flags, 0); | |
350 | 1 | EXPECT_EQ(a.nr_flag_args, 0); | |
351 | 1 | EXPECT_EQ(a.flags[0], '\0'); | |
352 | 1 | EXPECT_EQ(a.flag_set, 0); | |
353 | 1 | ptr_array_free(&array); | |
354 | |||
355 | 1 | cmd_str = "open \"-\\xff\""; | |
356 | 1 | ASSERT_EQ(parse_commands(&runner, &array, cmd_str), CMDERR_NONE); | |
357 | 1 | ASSERT_EQ(array.count, 3); | |
358 | 1 | EXPECT_STREQ(array.ptrs[0], "open"); | |
359 | 1 | EXPECT_STREQ(array.ptrs[1], "-\xff"); | |
360 | 1 | EXPECT_NULL(array.ptrs[2]); | |
361 | 1 | cmd = cmds->lookup(array.ptrs[0]); | |
362 | 1 | ASSERT_NONNULL(cmd); | |
363 | 1 | a = cmdargs_new((char**)array.ptrs + 1); | |
364 | 1 | EXPECT_EQ(do_parse_args(cmd, &a), ARGERR_INVALID_OPTION); | |
365 | 1 | EXPECT_EQ(a.flags[0], '\xff'); | |
366 | 1 | ptr_array_free(&array); | |
367 | |||
368 | 1 | ASSERT_EQ(parse_commands(&runner, &array, "save -e"), CMDERR_NONE); | |
369 | 1 | ASSERT_EQ(array.count, 3); | |
370 | 1 | cmd = cmds->lookup(array.ptrs[0]); | |
371 | 1 | ASSERT_NONNULL(cmd); | |
372 | 1 | a = cmdargs_new((char**)array.ptrs + 1); | |
373 | 1 | EXPECT_EQ(do_parse_args(cmd, &a), ARGERR_OPTION_ARGUMENT_MISSING); | |
374 | 1 | EXPECT_EQ(a.flags[0], 'e'); | |
375 | 1 | ptr_array_free(&array); | |
376 | |||
377 | 1 | ASSERT_EQ(parse_commands(&runner, &array, "save -eUTF-8"), CMDERR_NONE); | |
378 | 1 | ASSERT_EQ(array.count, 3); | |
379 | 1 | cmd = cmds->lookup(array.ptrs[0]); | |
380 | 1 | ASSERT_NONNULL(cmd); | |
381 | 1 | a = cmdargs_new((char**)array.ptrs + 1); | |
382 | 1 | EXPECT_EQ(do_parse_args(cmd, &a), ARGERR_OPTION_ARGUMENT_NOT_SEPARATE); | |
383 | 1 | EXPECT_EQ(a.flags[0], 'e'); | |
384 | 1 | ptr_array_free(&array); | |
385 | |||
386 | 1 | static const struct { | |
387 | const char *cmd_str; | ||
388 | char expected_flag0; | ||
389 | } invalid[] = { | ||
390 | {"open -7", '7'}, // ARGERR_INVALID_OPTION | ||
391 | {"exec -oopen ls", 'o'}, // ARGERR_OPTION_ARGUMENT_NOT_SEPARATE | ||
392 | {"exec -e", 'e'}, // ARGERR_OPTION_ARGUMENT_MISSING | ||
393 | {"quit -ffffffffffffffff", '\0'}, // ARGERR_TOO_MANY_OPTIONS | ||
394 | {"exec", '\0'}, // ARGERR_TOO_FEW_ARGUMENTS | ||
395 | {"up 1 2 3", '\0'}, // ARGERR_TOO_MANY_ARGUMENTS | ||
396 | }; | ||
397 | |||
398 |
2/2✓ Branch 0 taken 6 times.
✓ Branch 1 taken 1 times.
|
7 | FOR_EACH_I(i, invalid) { |
399 | 6 | cmd_str = invalid[i].cmd_str; | |
400 | 6 | ASSERT_EQ(parse_commands(&runner, &array, cmd_str), CMDERR_NONE); | |
401 | 6 | ASSERT_NONNULL(array.ptrs); | |
402 | 6 | ASSERT_NONNULL(array.ptrs[0]); | |
403 | 6 | cmd = cmds->lookup(array.ptrs[0]); | |
404 | 6 | ASSERT_NONNULL(cmd); | |
405 | 6 | a = cmdargs_new((char**)array.ptrs + 1); | |
406 | 6 | EXPECT_FALSE(parse_args(cmd, &a)); | |
407 | 6 | char f0 = invalid[i].expected_flag0; | |
408 |
2/2✓ Branch 0 taken 3 times.
✓ Branch 1 taken 3 times.
|
6 | if (f0 != '\0') { |
409 | 3 | IEXPECT_EQ(a.flags[0], f0); | |
410 | } | ||
411 | 6 | ptr_array_free(&array); | |
412 | } | ||
413 | |||
414 | // ARGERR_NONE | ||
415 | 1 | ASSERT_EQ(parse_commands(&runner, &array, "close -f"), CMDERR_NONE); | |
416 | 1 | ASSERT_NONNULL(array.ptrs); | |
417 | 1 | ASSERT_NONNULL(array.ptrs[0]); | |
418 | 1 | cmd = cmds->lookup(array.ptrs[0]); | |
419 | 1 | ASSERT_NONNULL(cmd); | |
420 | 1 | a = cmdargs_new((char**)array.ptrs + 1); | |
421 | 1 | EXPECT_TRUE(parse_args(cmd, &a)); | |
422 | 1 | EXPECT_EQ(a.nr_flags, 1); | |
423 | 1 | EXPECT_TRUE(cmdargs_has_flag(&a, 'f')); | |
424 | 1 | ptr_array_free(&array); | |
425 | 1 | } | |
426 | |||
427 | 1 | static void test_cached_command_new(TestContext *ctx) | |
428 | { | ||
429 | 1 | EditorState *e = ctx->userdata; | |
430 | 1 | const CommandRunner runner = normal_mode_cmdrunner(e); | |
431 | 1 | const CommandSet *cmds = runner.cmds; | |
432 | 1 | const char cmd_str[] = "open -t -e UTF-8 file.c inc.h"; | |
433 | 1 | CachedCommand *cc = cached_command_new(&runner, cmd_str); | |
434 | 1 | ASSERT_NONNULL(cmds); | |
435 | 1 | ASSERT_NONNULL(cc); | |
436 | 1 | ASSERT_NONNULL(cc->cmd); | |
437 | 1 | EXPECT_PTREQ(cc->cmd, cmds->lookup("open")); | |
438 | 1 | EXPECT_STREQ(cc->cmd_str, cmd_str); | |
439 | 1 | ASSERT_EQ(cc->a.nr_args, 2); | |
440 | 1 | ASSERT_EQ(cc->a.nr_flag_args, 1); | |
441 | 1 | ASSERT_EQ(cc->a.nr_flags, 2); | |
442 | 1 | EXPECT_STREQ(cc->a.args[0], "UTF-8"); | |
443 | 1 | EXPECT_STREQ(cc->a.args[1], "file.c"); | |
444 | 1 | EXPECT_STREQ(cc->a.args[2], "inc.h"); | |
445 | 1 | EXPECT_EQ(cc->a.flags[0], 't'); | |
446 | 1 | EXPECT_EQ(cc->a.flags[1], 'e'); | |
447 | 1 | EXPECT_TRUE(cmdargs_has_flag(&cc->a, 't')); | |
448 | 1 | EXPECT_TRUE(cmdargs_has_flag(&cc->a, 'e')); | |
449 | 1 | cached_command_free(cc); | |
450 | 1 | cached_command_free(NULL); | |
451 | |||
452 | 1 | static const char *const uncacheable[] = { | |
453 | "", // No command | ||
454 | "zxcvbnm", // Invalid command | ||
455 | "left; right", // Multiple commands | ||
456 | "insert $DTE_HOME", // Variable expansion | ||
457 | "insert -xyz321", // Invalid flags | ||
458 | "alias 1 2 3", // Too many arguments | ||
459 | "alias", // Too few arguments | ||
460 | "insert 'xyz.", // CMDERR_UNCLOSED_SQUOTE | ||
461 | "insert \"xyz", // CMDERR_UNCLOSED_DQUOTE | ||
462 | "insert xyz\\", // CMDERR_UNEXPECTED_EOF | ||
463 | }; | ||
464 | |||
465 |
2/2✓ Branch 0 taken 10 times.
✓ Branch 1 taken 1 times.
|
11 | for (size_t i = 0; i < ARRAYLEN(uncacheable); i++) { |
466 | 10 | cc = cached_command_new(&runner, uncacheable[i]); | |
467 | 10 | ASSERT_NONNULL(cc); | |
468 | 10 | EXPECT_NULL(cc->cmd); | |
469 | 10 | cached_command_free(cc); | |
470 | } | ||
471 | 1 | } | |
472 | |||
473 | 19 | static const char *escape_command_arg(String *buf, const char *arg, bool escape_tilde) | |
474 | { | ||
475 | 19 | string_clear(buf); | |
476 | 19 | string_append_escaped_arg(buf, arg, escape_tilde); | |
477 | 19 | return string_borrow_cstring(buf); | |
478 | } | ||
479 | |||
480 | 1 | static void test_string_append_escaped_arg(TestContext *ctx) | |
481 | { | ||
482 | 1 | String buf = string_new(64); | |
483 | 1 | const char *str = escape_command_arg(&buf, "arg", false); | |
484 | 1 | EXPECT_STREQ(str, "arg"); | |
485 | |||
486 | 1 | str = escape_command_arg(&buf, "arg-x.y:z", false); | |
487 | 1 | EXPECT_STREQ(str, "arg-x.y:z"); | |
488 | |||
489 | 1 | str = escape_command_arg(&buf, "", false); | |
490 | 1 | EXPECT_STREQ(str, "''"); | |
491 | |||
492 | 1 | str = escape_command_arg(&buf, " ", false); | |
493 | 1 | EXPECT_STREQ(str, "' '"); | |
494 | |||
495 | 1 | str = escape_command_arg(&buf, "hello world", false); | |
496 | 1 | EXPECT_STREQ(str, "'hello world'"); | |
497 | |||
498 | 1 | str = escape_command_arg(&buf, "line1\nline2\n", false); | |
499 | 1 | EXPECT_STREQ(str, "\"line1\\nline2\\n\""); | |
500 | |||
501 | 1 | str = escape_command_arg(&buf, " \t\r\n\x1F\x7F", false); | |
502 | 1 | EXPECT_STREQ(str, "\" \\t\\r\\n\\x1F\\x7F\""); | |
503 | |||
504 | 1 | str = escape_command_arg(&buf, "x ' y", false); | |
505 | 1 | EXPECT_STREQ(str, "\"x ' y\""); | |
506 | |||
507 | 1 | str = escape_command_arg(&buf, "\033[P", false); | |
508 | 1 | EXPECT_STREQ(str, "\"\\e[P\""); | |
509 | |||
510 | 1 | str = escape_command_arg(&buf, "\"''\"", false); | |
511 | 1 | EXPECT_STREQ(str, "\"\\\"''\\\"\""); | |
512 | |||
513 | 1 | str = escape_command_arg(&buf, "\t\\", false); | |
514 | 1 | EXPECT_STREQ(str, "\"\\t\\\\\""); | |
515 | |||
516 | 1 | str = escape_command_arg(&buf, "~/file with spaces", false); | |
517 | 1 | EXPECT_STREQ(str, "~/'file with spaces'"); | |
518 | 1 | str = escape_command_arg(&buf, "~/file with spaces", true); | |
519 | 1 | EXPECT_STREQ(str, "'~/file with spaces'"); | |
520 | |||
521 | 1 | str = escape_command_arg(&buf, "~/need \t\ndquotes", false); | |
522 | 1 | EXPECT_STREQ(str, "~/\"need \\t\\ndquotes\""); | |
523 | 1 | str = escape_command_arg(&buf, "~/need \t\ndquotes", true); | |
524 | 1 | EXPECT_STREQ(str, "\"~/need \\t\\ndquotes\""); | |
525 | |||
526 | 1 | str = escape_command_arg(&buf, "~/file-with-no-spaces", false); | |
527 | 1 | EXPECT_STREQ(str, "~/file-with-no-spaces"); | |
528 | 1 | str = escape_command_arg(&buf, "~/file-with-no-spaces", true); | |
529 | 1 | EXPECT_STREQ(str, "\\~/file-with-no-spaces"); | |
530 | |||
531 | 1 | str = escape_command_arg(&buf, "~/", false); | |
532 | 1 | EXPECT_STREQ(str, "~/"); | |
533 | 1 | str = escape_command_arg(&buf, "~/", true); | |
534 | 1 | EXPECT_STREQ(str, "\\~/"); | |
535 | |||
536 | 1 | string_free(&buf); | |
537 | 1 | } | |
538 | |||
539 | 1 | static void test_command_struct_layout(TestContext *ctx) | |
540 | { | ||
541 | 1 | const Command *cmd = find_normal_command("open"); | |
542 | 1 | EXPECT_STREQ(cmd->name, "open"); | |
543 | 1 | EXPECT_STREQ(cmd->flags, "e=gt"); | |
544 | 1 | EXPECT_FALSE(cmd->cmdopts & CMDOPT_ALLOW_IN_RC); | |
545 | 1 | EXPECT_UINT_EQ(cmd->min_args, 0); | |
546 | 1 | EXPECT_UINT_EQ(cmd->max_args, 0xFF); | |
547 | |||
548 | IGNORE_WARNING("-Wpedantic") | ||
549 | 1 | EXPECT_NONNULL(cmd->cmd); | |
550 | UNIGNORE_WARNINGS | ||
551 | 1 | } | |
552 | |||
553 | 1 | static void test_cmdargs_flagset_idx(TestContext *ctx) | |
554 | { | ||
555 | 1 | EXPECT_EQ(cmdargs_flagset_idx('A'), 1); | |
556 | 1 | EXPECT_EQ(cmdargs_flagset_idx('Z'), 26); | |
557 | 1 | EXPECT_EQ(cmdargs_flagset_idx('a'), 27); | |
558 | 1 | EXPECT_EQ(cmdargs_flagset_idx('z'), 52); | |
559 | 1 | EXPECT_EQ(cmdargs_flagset_idx('0'), 53); | |
560 | 1 | EXPECT_EQ(cmdargs_flagset_idx('1'), 54); | |
561 | 1 | EXPECT_EQ(cmdargs_flagset_idx('9'), 62); | |
562 | |||
563 |
2/2✓ Branch 0 taken 75 times.
✓ Branch 1 taken 1 times.
|
76 | for (unsigned char c = '0', z = 'z'; c <= z; c++) { |
564 |
2/2✓ Branch 0 taken 62 times.
✓ Branch 1 taken 13 times.
|
75 | if (ascii_isalnum(c)) { |
565 | 62 | const unsigned int idx = cmdargs_flagset_idx(c); | |
566 | 62 | EXPECT_TRUE(idx < 63); | |
567 | 62 | EXPECT_EQ(idx, base64_decode(c) + 1); | |
568 | } | ||
569 | } | ||
570 | 1 | } | |
571 | |||
572 | 1 | static void test_cmdargs_convert_flags_1(TestContext *ctx) | |
573 | { | ||
574 | 1 | static const FlagMapping map[] = { | |
575 | {'b', REPLACE_BASIC}, | ||
576 | {'c', REPLACE_CONFIRM}, | ||
577 | {'g', REPLACE_GLOBAL}, | ||
578 | {'i', REPLACE_IGNORE_CASE}, | ||
579 | }; | ||
580 | |||
581 | 2 | const CommandArgs a = { | |
582 | 1 | .flag_set = cmdargs_flagset_value('c') | cmdargs_flagset_value('g') | |
583 | }; | ||
584 | |||
585 | 1 | ReplaceFlags flags = cmdargs_convert_flags(&a, map, ARRAYLEN(map)); | |
586 | 1 | EXPECT_EQ(flags, REPLACE_CONFIRM | REPLACE_GLOBAL); | |
587 | 1 | } | |
588 | |||
589 | 1 | static void test_cmdargs_convert_flags_2(TestContext *ctx) | |
590 | { | ||
591 | 1 | static const FlagMapping map[] = { | |
592 | {'C', EFLAG_SAVE_CMD_HIST}, | ||
593 | {'S', EFLAG_SAVE_SEARCH_HIST}, | ||
594 | {'F', EFLAG_SAVE_FILE_HIST}, | ||
595 | {'H', EFLAG_SAVE_ALL_HIST}, | ||
596 | }; | ||
597 | |||
598 | 1 | CommandArgs a = {.flag_set = cmdargs_flagset_value('C')}; | |
599 | 1 | EditorFlags flags = cmdargs_convert_flags(&a, map, ARRAYLEN(map)); | |
600 | 1 | EXPECT_EQ(flags, EFLAG_SAVE_CMD_HIST); | |
601 | |||
602 | 1 | a.flag_set |= cmdargs_flagset_value('F'); | |
603 | 1 | flags = cmdargs_convert_flags(&a, map, ARRAYLEN(map)); | |
604 | 1 | EXPECT_EQ(flags, EFLAG_SAVE_CMD_HIST | EFLAG_SAVE_FILE_HIST); | |
605 | |||
606 | 1 | a.flag_set |= cmdargs_flagset_value('S'); | |
607 | 1 | flags = cmdargs_convert_flags(&a, map, ARRAYLEN(map)); | |
608 | 1 | EXPECT_EQ(flags, EFLAG_SAVE_ALL_HIST); | |
609 | |||
610 | 1 | a.flag_set = cmdargs_flagset_value('H'); | |
611 | 1 | EXPECT_EQ(flags, cmdargs_convert_flags(&a, map, ARRAYLEN(map))); | |
612 | 1 | } | |
613 | |||
614 | 1 | static void test_add_alias(TestContext *ctx) | |
615 | { | ||
616 | 1 | const char name[] = "insert-foo"; | |
617 | 1 | const char cmd[] = "insert -m foo"; | |
618 | 1 | HashMap m = HASHMAP_INIT; | |
619 | 1 | add_alias(&m, name, cmd); | |
620 | 1 | EXPECT_STREQ(find_alias(&m, name), cmd); | |
621 | 1 | EXPECT_EQ(m.count, 1); | |
622 | |||
623 | 1 | remove_alias(&m, "insert-foo"); | |
624 | 1 | EXPECT_NULL(find_alias(&m, name)); | |
625 | 1 | EXPECT_EQ(m.count, 0); | |
626 | 1 | hashmap_free(&m, free); | |
627 | 1 | } | |
628 | |||
629 | static const TestEntry tests[] = { | ||
630 | TEST(test_parse_command_arg), | ||
631 | TEST(test_parse_commands), | ||
632 | TEST(test_command_parse_error_to_string), | ||
633 | TEST(test_find_normal_command), | ||
634 | TEST(test_parse_args), | ||
635 | TEST(test_cached_command_new), | ||
636 | TEST(test_string_append_escaped_arg), | ||
637 | TEST(test_command_struct_layout), | ||
638 | TEST(test_cmdargs_flagset_idx), | ||
639 | TEST(test_cmdargs_convert_flags_1), | ||
640 | TEST(test_cmdargs_convert_flags_2), | ||
641 | TEST(test_add_alias), | ||
642 | }; | ||
643 | |||
644 | const TestGroup command_tests = TEST_GROUP(tests); | ||
645 |