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 |