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