dte test coverage


Directory: ./
Coverage: low: ≥ 0% medium: ≥ 50.0% high: ≥ 85.0%
Coverage Exec / Excl / Total
Lines: 100.0% 439 / 0 / 439
Functions: 100.0% 15 / 0 / 15
Branches: 100.0% 6 / 0 / 6

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