dte test coverage


Directory: ./
File: test/command.c
Date: 2025-07-19 20:13:10
Exec Total Coverage
Lines: 416 416 100.0%
Functions: 14 14 100.0%
Branches: 6 6 100.0%

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