dte test coverage


Directory: ./
File: test/error.c
Date: 2025-06-04 06:50:24
Exec Total Coverage
Lines: 52 52 100.0%
Functions: 2 2 100.0%
Branches: 4 6 66.7%

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 -p 12", "flags [-n|-p|-N|-P] can't be used with [number] argument"},
68 {"open -t filename", "can't be used with filename arguments"},
69 {"open /non/exi/ste/nt/file_Name", "directory does not exist"},
70 {"open /dev/null", "not a regular file"},
71 {"open ''", "empty filename not allowed"},
72 {"option c auto-indent true syntax", "missing option value"},
73 {"option c filetype c", "filetype cannot be set via option command"},
74 {"option c invalid 1", "no such option"},
75 {"option c tab-bar true", "is not local"},
76 {"option c detect-indent 1,0", "invalid flag"},
77 {"option c detect-indent 9", "invalid flag"},
78 {"option c indent-width 0", "must be in 1-8 range"},
79 {"option c indent-width 9", "must be in 1-8 range"},
80 {"option c auto-indent xyz", "invalid value"},
81 {"option , auto-indent true", "invalid filetype"},
82 {"option '' auto-indent true", "first argument cannot be empty"},
83 {"option -r '' auto-indent true", "first argument cannot be empty"},
84 {"quit xyz", "not a valid integer"},
85 {"quit 9000", "exit code should be between"},
86 {"redo 8", "nothing to redo"},
87 {"redo A", "invalid change id"},
88 {"repeat x up", "not a valid repeat count"},
89 {"repeat 2 invalid-cmd", "no such command: invalid-cmd"},
90 {"replace '' x", "must contain at least 1 character"},
91 {"replace e5fwHgHuCFVZd x", "not found"},
92 {"replace -c a b", "-c flag unavailable in headless mode"},
93 {"save ''", "empty filename"},
94 {"save", "no filename"},
95 {"save /dev/", "will not overwrite directory"},
96 {"search -nw", "mutually exclusive"},
97 {"search -p str", "mutually exclusive"},
98 {"search e5fwHgHuCFVZd", "not found"},
99 {"set filetype", "not boolean"},
100 {"set filetype ' '", "invalid filetype name"},
101 {"set tab-width s", "integer value for tab-width expected"},
102 {"set indent-width 9", "must be in 1-8 range"},
103 {"set emulate-tab x", "invalid value"},
104 {"set msg-compile xyz", "invalid value for msg-compile; expected: A, B, C"},
105 {"set msg-tag xyz", "invalid value for msg-tag; expected: A, B, C"},
106 {"set newline xyz", "invalid value for newline; expected: unix, dos"},
107 {
108 "set case-sensitive-search z",
109 "invalid value for case-sensitive-search; expected: false, true, auto"
110 },
111 {
112 "set save-unmodified xyz",
113 "invalid value for save-unmodified; expected: none, touch, full"
114 },
115 {
116 "set window-separator xyz",
117 "invalid value for window-separator; expected: blank, bar"
118 },
119 {
120 "set ws-error A",
121 "invalid flag 'A' for ws-error; expected: space-indent,"
122 " space-align, tab-indent, tab-after-indent, special,"
123 " auto-indent, trailing, all-trailing"
124 },
125 {"set non-existent 1", "no such option"},
126 {"set -g filetype c", "not global"},
127 {"set -l statusline-right _", "not local"},
128 {"set 1 2 3", "one or even number of arguments expected"},
129 {"set statusline-left %", "format character expected"},
130 {"set statusline-left %!", "invalid format character"},
131 {"set filesize-limit 1M", "invalid filesize"},
132 {"set filesize-limit 1Mi", "invalid filesize"},
133 {"set filesize-limit 1MB", "invalid filesize"},
134 {"set filesize-limit 1MIB", "invalid filesize"},
135 {"set filesize-limit 1Mib", "invalid filesize"},
136 {"setenv DTE_VERSION 0.1", "cannot be changed"},
137 {"shift x", "invalid number"},
138 {"shift 0", "must be non-zero"},
139 {"show -c alias", "requires 2 arguments"},
140 {"show zzz", "invalid argument"},
141 {"show bind M-M-M---", "invalid key string"},
142 {"show buffer x", "doesn't take extra arguments"},
143 {"show builtin a78sd8623d7k", "no built-in config"},
144 {"show cursor xyz", "no cursor entry"},
145 {"show option 31ldx92kjsd8", "invalid option name"},
146 {"show wsplit m2wz9u263t8a", "invalid window"},
147 {"suspend", "unavailable in headless mode"},
148 {"toggle ws-error", "requires arguments"},
149 {"view z", "invalid view index"},
150 {"view 0", "invalid view index"},
151 {"wrap-paragraph _", "invalid paragraph width"},
152 {"wrap-paragraph 90000", "width must be between"},
153 {"wsplit -t file", "flags [-n|-t] can't be used with [filename] arguments"},
154 {"wsplit -t; wresize nonnum; wclose", "invalid resize value"},
155
156 // Error strings produced by command_parse_error_to_string():
157 {"x '", "command syntax error: unclosed '"},
158 {"x \"", "command syntax error: unclosed \""},
159 {"x \"\\", "command syntax error: unexpected EOF"},
160
161 // Error strings produced by arg_parse_error_msg():
162 {"bind -nnnnnnnnnnnnnn C-k eol", "too many options given"},
163 {"bind -Z C-k eol", "invalid option -Z"},
164 {"bind -Tsearch C-k eol", "option -T must be given separately"},
165 {"bind -T ", "option -T requires an argument"},
166 {"bind 1 2 3", "too many arguments"},
167 {"bind", "too few arguments"},
168
169 // These are tested last, since some of the commands succeed and
170 // affect the state of the Buffer (i.e. the undo history) and some
171 // are dependant on it (and cannot be reordered)
172 {"insert 1; undo; redo 900", "there is only 1 possible change to redo"},
173 {"insert 2; undo; redo 900", "there are only 2 possible changes to redo"},
174 {"insert x; close; undo", "close without saving"},
175 {"insert x; wclose; undo", "close window without saving"},
176 {"insert x; quit; undo", "quit without saving"},
177 {"insert x; close -p; undo", "-p flag unavailable in headless mode"},
178 {"insert x; wclose -p; undo", "-p flag unavailable in headless mode"},
179 {"insert x; quit -p; undo", "-p flag unavailable in headless mode"},
180 {"insert x; match-bracket; undo", "character under cursor not matchable"},
181 {"insert {{}; match-bracket; undo", "no matching bracket found"},
182 {"repeat 4100 insert x; search -w; undo", "word under cursor too long"},
183 };
184
185 1 EditorState *e = ctx->userdata;
186 1 ErrorBuffer *ebuf = &e->err;
187 1 ASSERT_NONNULL(window_open_empty_buffer(e->window));
188
189
2/2
✓ Branch 0 (21→5) taken 138 times.
✓ Branch 1 (21→22) taken 1 times.
139 FOR_EACH_I(i, tests) {
190 138 const char *cmd = tests[i].cmd_str;
191 138 const char *substr = tests[i].expected_error_substr;
192 138 ASSERT_NONNULL(cmd);
193 138 ASSERT_NONNULL(substr);
194 138 ASSERT_NE(substr[0], '\0');
195 138 ASSERT_NE(substr[1], '\0');
196 138 ASSERT_TRUE(!ascii_isupper(substr[0]));
197
198 138 clear_error(ebuf);
199 138 EXPECT_FALSE(handle_normal_command(e, cmd, false));
200 138 EXPECT_NE(ebuf->buf[0], '\0');
201 138 EXPECT_TRUE(ebuf->is_error);
202
203 // Check for substring in error message (ignoring capitalization)
204 138 const char *found = strstr(ebuf->buf, substr + 1);
205
2/4
✓ Branch 0 (15→16) taken 138 times.
✗ Branch 1 (15→19) not taken.
✓ Branch 2 (16→17) taken 138 times.
✗ Branch 3 (16→19) not taken.
138 if (likely(found && found != ebuf->buf)) {
206 138 test_pass(ctx);
207 138 EXPECT_EQ(ascii_tolower(found[-1]), substr[0]);
208 } else {
209 TEST_FAIL (
210 "Test #%zu: substring \"%s\" not found in message \"%s\"",
211 i + 1, substr, ebuf->buf
212 );
213 }
214 }
215
216 1 window_close_current_view(e->window);
217 1 set_view(e->window->view);
218
219 // Special case errors produced by run_command():
220 // ----------------------------------------------
221
222 1 CommandRunner runner = normal_mode_cmdrunner(e);
223 1 runner.lookup_alias = NULL;
224 1 clear_error(ebuf);
225 1 EXPECT_FALSE(handle_command(&runner, "_xyz"));
226 1 EXPECT_STREQ(ebuf->buf, "No such command: _xyz");
227 1 EXPECT_TRUE(ebuf->is_error);
228
229 1 runner.lookup_alias = dummy_lookup_alias;
230 1 clear_error(ebuf);
231 1 EXPECT_FALSE(handle_command(&runner, "_abc"));
232 1 EXPECT_TRUE(str_has_prefix(ebuf->buf, "Parsing alias _abc:"));
233 1 EXPECT_TRUE(ebuf->is_error);
234
235 1 EXPECT_NULL(ebuf->config_filename);
236 1 ebuf->config_filename = "example";
237 1 ebuf->config_line = 1;
238 1 runner.lookup_alias = NULL;
239 1 clear_error(ebuf);
240 1 EXPECT_FALSE(handle_command(&runner, "quit"));
241 1 EXPECT_STREQ(ebuf->buf, "example:1: Command quit not allowed in config file");
242 1 EXPECT_TRUE(ebuf->is_error);
243 1 ebuf->config_filename = NULL;
244
245 // Special case errors produced by exec_config():
246 // ----------------------------------------------
247
248 // This is a special case because handle_command() currently returns true,
249 // since cmd_include() only indicates failure for I/O errors and not for
250 // failing commands in the loaded file
251 1 clear_error(ebuf);
252 1 EXPECT_TRUE(handle_command(&runner, "include test/data/recursive.dterc"));
253 1 EXPECT_STREQ(ebuf->buf, "test/data/recursive.dterc:1: config recursion limit reached");
254 1 EXPECT_TRUE(ebuf->is_error);
255 1 }
256
257 static const TestEntry tests[] = {
258 TEST(test_normal_command_errors),
259 };
260
261 const TestGroup error_tests = TEST_GROUP(tests);
262