Line | Branch | Exec | Source |
---|---|---|---|
1 | #include "test.h" | ||
2 | #include "command/error.h" | ||
3 | #include "commands.h" | ||
4 | #include "editor.h" | ||
5 | #include "util/ascii.h" | ||
6 | #include "util/debug.h" | ||
7 | #include "window.h" | ||
8 | |||
9 | 1 | static const char *dummy_lookup_alias(const EditorState *e, const char *name) | |
10 | { | ||
11 | 1 | BUG_ON(!e); | |
12 | 1 | BUG_ON(!name); | |
13 | 1 | return "insert \"..."; | |
14 | } | ||
15 | |||
16 | 1 | static void test_normal_command_errors(TestContext *ctx) | |
17 | { | ||
18 | 1 | static const struct { | |
19 | const char *cmd_str; | ||
20 | const char *expected_error_substr; | ||
21 | } tests[] = { | ||
22 | // Note: do not include error substrings produced by strerror(3) | ||
23 | {"non__existent__command", "no such command or alias"}, | ||
24 | {"alias '' up", "empty alias name"}, | ||
25 | {"alias -- -name up", "alias name cannot begin with '-'"}, | ||
26 | {"alias 'x y' up", "invalid byte in alias name"}, | ||
27 | {"alias open close", "can't replace existing command"}, | ||
28 | {"alias a b; alias b a; a; alias a; alias b", "alias recursion limit reached"}, | ||
29 | {"bind C-C-C", "invalid key string"}, | ||
30 | {"bind -T invalid-mode C-k eol", "can't bind key in unknown mode"}, | ||
31 | {"cd", "too few arguments"}, | ||
32 | {"cd ''", "directory argument cannot be empty"}, | ||
33 | {"cd /non-existent/dir/_x_29_", "changing directory failed"}, | ||
34 | {"compile dfa34dazzdf2 echo", "no such error parser"}, | ||
35 | {"cursor xyz bar #f40", "invalid mode argument"}, | ||
36 | {"cursor insert _ #f40", "invalid cursor type"}, | ||
37 | {"cursor insert bar blue", "invalid cursor color"}, | ||
38 | {"def-mode normal", "mode 'normal' already exists"}, | ||
39 | {"def-mode command", "mode 'command' already exists"}, | ||
40 | {"def-mode search", "mode 'search' already exists"}, | ||
41 | {"def-mode -- -", "mode name can't be empty or start with '-'"}, | ||
42 | {"def-mode ''", "mode name can't be empty or start with '-'"}, | ||
43 | {"def-mode xz qrst9", "unknown fallthrough mode 'qrst9'"}, | ||
44 | {"def-mode xz search", "unable to use 'search' as fall-through mode"}, | ||
45 | {"def-mode _m; def-mode _m", "mode '_m' already exists"}, | ||
46 | {"errorfmt x (re) zz", "unknown substring name"}, | ||
47 | {"errorfmt x (re) file line", "invalid substring count"}, | ||
48 | {"exec -s sh -c 'kill -s USR1 $$'", "child received signal"}, | ||
49 | {"exec -s sh -c 'exit 44'", "child returned 44"}, | ||
50 | {"exec -s _z9kjdf_2dmm_92:j-a3d_xzkw::", "unable to exec"}, | ||
51 | {"exec -s -e errmsg sh -c 'echo X >&2; exit 9'", "child returned 9: \"X\""}, | ||
52 | {"exec -s -i invalid true", "invalid action for -i: 'invalid'"}, | ||
53 | {"exec -s -o invalid true", "invalid action for -o: 'invalid'"}, | ||
54 | {"exec -s -e invalid true", "invalid action for -e: 'invalid'"}, | ||
55 | {"ft -- -name ext", "invalid filetype name"}, | ||
56 | {"hi xyz red green blue", "too many colors"}, | ||
57 | {"hi xyz _invalid_", "invalid color or attribute"}, | ||
58 | {"include -b nonexistent", "no built-in config with name 'nonexistent'"}, | ||
59 | {"include /.n-o-n-e-x-i-s-t-e-n-t/_/az_..3", "error reading"}, | ||
60 | {"line 0", "invalid line number"}, | ||
61 | {"line _", "invalid line number"}, | ||
62 | {"line 1:x", "invalid line number"}, | ||
63 | {"macro xyz", "unknown action"}, | ||
64 | {"match-bracket", "no character under cursor"}, | ||
65 | {"mode inValiD", "unknown mode 'inValiD'"}, | ||
66 | {"move-tab 0", "invalid tab position"}, | ||
67 | {"msg -np", "mutually exclusive"}, | ||
68 | {"msg -p 12", "mutually exclusive"}, | ||
69 | {"open -t filename", "can't be used with filename arguments"}, | ||
70 | {"open /non/exi/ste/nt/file_Name", "directory does not exist"}, | ||
71 | {"open /dev/null", "not a regular file"}, | ||
72 | {"open ''", "empty filename not allowed"}, | ||
73 | {"option c auto-indent true syntax", "missing option value"}, | ||
74 | {"option c filetype c", "filetype cannot be set via option command"}, | ||
75 | {"option c invalid 1", "no such option"}, | ||
76 | {"option c tab-bar true", "is not local"}, | ||
77 | {"option c detect-indent 9", "invalid flag"}, | ||
78 | {"option c indent-width 9", "must be in 1-8 range"}, | ||
79 | {"option c auto-indent xyz", "invalid value"}, | ||
80 | {"option , auto-indent true", "invalid filetype"}, | ||
81 | {"option '' auto-indent true", "first argument cannot be empty"}, | ||
82 | {"option -r '' auto-indent true", "first argument cannot be empty"}, | ||
83 | {"quit xyz", "not a valid integer"}, | ||
84 | {"quit 9000", "exit code should be between"}, | ||
85 | {"redo 8", "nothing to redo"}, | ||
86 | {"redo A", "invalid change id"}, | ||
87 | {"repeat x up", "not a valid repeat count"}, | ||
88 | {"repeat 2 invalid-cmd", "no such command: invalid-cmd"}, | ||
89 | {"replace '' x", "must contain at least 1 character"}, | ||
90 | {"replace e5fwHgHuCFVZd x", "not found"}, | ||
91 | {"replace -c a b", "-c flag unavailable in headless mode"}, | ||
92 | {"save ''", "empty filename"}, | ||
93 | {"save", "no filename"}, | ||
94 | {"save /dev/", "will not overwrite directory"}, | ||
95 | {"search -nw", "mutually exclusive"}, | ||
96 | {"search -p str", "mutually exclusive"}, | ||
97 | {"search e5fwHgHuCFVZd", "not found"}, | ||
98 | {"set filetype", "not boolean"}, | ||
99 | {"set filetype ' '", "invalid filetype name"}, | ||
100 | {"set tab-width s", "integer value for tab-width expected"}, | ||
101 | {"set indent-width 9", "must be in 1-8 range"}, | ||
102 | {"set emulate-tab x", "invalid value"}, | ||
103 | {"set case-sensitive-search z", "invalid value"}, | ||
104 | {"set ws-error A", "invalid flag"}, | ||
105 | {"set non-existent 1", "no such option"}, | ||
106 | {"set -g filetype c", "not global"}, | ||
107 | {"set -l statusline-right _", "not local"}, | ||
108 | {"set 1 2 3", "one or even number of arguments expected"}, | ||
109 | {"set statusline-left %", "format character expected"}, | ||
110 | {"set statusline-left %!", "invalid format character"}, | ||
111 | {"setenv DTE_VERSION 0.1", "cannot be changed"}, | ||
112 | {"shift x", "invalid number"}, | ||
113 | {"shift 0", "must be non-zero"}, | ||
114 | {"show -c alias", "requires 2 arguments"}, | ||
115 | {"show zzz", "invalid argument"}, | ||
116 | {"show bind M-M-M---", "invalid key string"}, | ||
117 | {"show buffer x", "doesn't take extra arguments"}, | ||
118 | {"show builtin a78sd8623d7k", "no built-in config"}, | ||
119 | {"show cursor xyz", "no cursor entry"}, | ||
120 | {"show option 31ldx92kjsd8", "invalid option name"}, | ||
121 | {"show wsplit m2wz9u263t8a", "invalid window"}, | ||
122 | {"suspend", "unavailable in headless mode"}, | ||
123 | {"toggle ws-error", "requires arguments"}, | ||
124 | {"view z", "invalid view index"}, | ||
125 | {"view 0", "invalid view index"}, | ||
126 | {"wrap-paragraph _", "invalid paragraph width"}, | ||
127 | {"wrap-paragraph 90000", "width must be between"}, | ||
128 | {"wsplit -t file", "flags -n and -t can't be used with filename"}, | ||
129 | {"wsplit -t; wresize nonnum; wclose", "invalid resize value"}, | ||
130 | |||
131 | // Error strings produced by command_parse_error_to_string(): | ||
132 | {"x '", "command syntax error: unclosed '"}, | ||
133 | {"x \"", "command syntax error: unclosed \""}, | ||
134 | {"x \"\\", "command syntax error: unexpected EOF"}, | ||
135 | |||
136 | // Error strings produced by arg_parse_error_msg(): | ||
137 | {"bind -nnnnnnnnnn C-k eol", "too many options given"}, | ||
138 | {"bind -Z C-k eol", "invalid option -Z"}, | ||
139 | {"bind -Tsearch C-k eol", "option -T must be given separately"}, | ||
140 | {"bind -T ", "option -T requires an argument"}, | ||
141 | {"bind 1 2 3", "too many arguments"}, | ||
142 | {"bind", "too few arguments"}, | ||
143 | |||
144 | // These are tested last, since some of the commands succeed and | ||
145 | // affect the state of the Buffer (i.e. the undo history) and some | ||
146 | // are dependant on it (and cannot be reordered) | ||
147 | {"insert 1; undo; redo 900", "there is only 1 possible change to redo"}, | ||
148 | {"insert 2; undo; redo 900", "there are only 2 possible changes to redo"}, | ||
149 | {"insert x; close; undo", "close without saving"}, | ||
150 | {"insert x; wclose; undo", "close window without saving"}, | ||
151 | {"insert x; quit; undo", "quit without saving"}, | ||
152 | {"insert x; close -p; undo", "-p flag unavailable in headless mode"}, | ||
153 | {"insert x; wclose -p; undo", "-p flag unavailable in headless mode"}, | ||
154 | {"insert x; quit -p; undo", "-p flag unavailable in headless mode"}, | ||
155 | {"insert x; match-bracket; undo", "character under cursor not matchable"}, | ||
156 | {"insert {{}; match-bracket; undo", "no matching bracket found"}, | ||
157 | {"repeat 4100 insert x; search -w; undo", "word under cursor too long"}, | ||
158 | }; | ||
159 | |||
160 | 1 | EditorState *e = ctx->userdata; | |
161 | 1 | ErrorBuffer *ebuf = &e->err; | |
162 | 1 | ASSERT_NONNULL(window_open_empty_buffer(e->window)); | |
163 | |||
164 |
2/2✓ Branch 0 (21→5) taken 127 times.
✓ Branch 1 (21→22) taken 1 times.
|
128 | FOR_EACH_I(i, tests) { |
165 | 127 | const char *cmd = tests[i].cmd_str; | |
166 | 127 | const char *substr = tests[i].expected_error_substr; | |
167 | 127 | ASSERT_NONNULL(cmd); | |
168 | 127 | ASSERT_NONNULL(substr); | |
169 | 127 | ASSERT_TRUE(substr[0] != '\0'); | |
170 | 127 | ASSERT_TRUE(substr[1] != '\0'); | |
171 | 127 | ASSERT_TRUE(!ascii_isupper(substr[0])); | |
172 | |||
173 | 127 | clear_error(ebuf); | |
174 | 127 | EXPECT_FALSE(handle_normal_command(e, cmd, false)); | |
175 | 127 | EXPECT_FALSE(ebuf->buf[0] == '\0'); | |
176 | 127 | EXPECT_TRUE(ebuf->is_error); | |
177 | |||
178 | // Check for substring in error message (ignoring capitalization) | ||
179 | 127 | const char *found = strstr(ebuf->buf, substr + 1); | |
180 |
2/4✓ Branch 0 (15→16) taken 127 times.
✗ Branch 1 (15→19) not taken.
✓ Branch 2 (16→17) taken 127 times.
✗ Branch 3 (16→19) not taken.
|
127 | if (likely(found && found != ebuf->buf)) { |
181 | 127 | test_pass(ctx); | |
182 | 127 | EXPECT_EQ(ascii_tolower(found[-1]), substr[0]); | |
183 | } else { | ||
184 | − | TEST_FAIL ( | |
185 | "Test #%zu: substring \"%s\" not found in message \"%s\"", | ||
186 | i + 1, substr, ebuf->buf | ||
187 | ); | ||
188 | } | ||
189 | } | ||
190 | |||
191 | 1 | window_close_current_view(e->window); | |
192 | 1 | set_view(e->window->view); | |
193 | |||
194 | // Special case errors produced by run_command(): | ||
195 | // ---------------------------------------------- | ||
196 | |||
197 | 1 | CommandRunner runner = normal_mode_cmdrunner(e); | |
198 | 1 | runner.lookup_alias = NULL; | |
199 | 1 | clear_error(ebuf); | |
200 | 1 | EXPECT_FALSE(handle_command(&runner, "_xyz")); | |
201 | 1 | EXPECT_STREQ(ebuf->buf, "No such command: _xyz"); | |
202 | 1 | EXPECT_TRUE(ebuf->is_error); | |
203 | |||
204 | 1 | runner.lookup_alias = dummy_lookup_alias; | |
205 | 1 | clear_error(ebuf); | |
206 | 1 | EXPECT_FALSE(handle_command(&runner, "_abc")); | |
207 | 1 | EXPECT_TRUE(str_has_prefix(ebuf->buf, "Parsing alias _abc:")); | |
208 | 1 | EXPECT_TRUE(ebuf->is_error); | |
209 | |||
210 | 1 | EXPECT_NULL(ebuf->config_filename); | |
211 | 1 | ebuf->config_filename = "example"; | |
212 | 1 | ebuf->config_line = 1; | |
213 | 1 | runner.lookup_alias = NULL; | |
214 | 1 | clear_error(ebuf); | |
215 | 1 | EXPECT_FALSE(handle_command(&runner, "quit")); | |
216 | 1 | EXPECT_STREQ(ebuf->buf, "example:1: Command quit not allowed in config file"); | |
217 | 1 | EXPECT_TRUE(ebuf->is_error); | |
218 | 1 | ebuf->config_filename = NULL; | |
219 | |||
220 | // Special case errors produced by exec_config(): | ||
221 | // ---------------------------------------------- | ||
222 | |||
223 | // This is a special case because handle_command() currently returns true, | ||
224 | // since cmd_include() only indicates failure for I/O errors and not for | ||
225 | // failing commands in the loaded file | ||
226 | 1 | clear_error(ebuf); | |
227 | 1 | EXPECT_TRUE(handle_command(&runner, "include test/data/recursive.dterc")); | |
228 | 1 | EXPECT_STREQ(ebuf->buf, "test/data/recursive.dterc:1: config recursion limit reached"); | |
229 | 1 | EXPECT_TRUE(ebuf->is_error); | |
230 | 1 | } | |
231 | |||
232 | static const TestEntry tests[] = { | ||
233 | TEST(test_normal_command_errors), | ||
234 | }; | ||
235 | |||
236 | const TestGroup error_tests = TEST_GROUP(tests); | ||
237 |