dte test coverage


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