dte test coverage


Directory: ./
File: test/command.c
Date: 2024-12-21 16:03:22
Exec Total Coverage
Lines: 423 423 100.0%
Functions: 13 13 100.0%
Branches: 10 10 100.0%

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