dte test coverage


Directory: ./
Coverage: low: ≥ 0% medium: ≥ 50.0% high: ≥ 85.0%
Coverage Exec / Excl / Total
Lines: 93.0% 185 / 3 / 202
Functions: 100.0% 8 / 0 / 8
Branches: 65.6% 21 / 0 / 32

test/config.c
Line Branch Exec Source
1 #include "build-defs.h"
2 #include <stdlib.h>
3 #include <unistd.h>
4 #include "test.h"
5 #include "config.h"
6 #include "command/macro.h"
7 #include "commands.h"
8 #include "convert.h"
9 #include "editor.h"
10 #include "frame.h"
11 #include "insert.h"
12 #include "mode.h"
13 #include "syntax/state.h"
14 #include "syntax/syntax.h"
15 #include "util/log.h"
16 #include "util/path.h"
17 #include "util/readfile.h"
18 #include "util/str-util.h"
19 #include "util/string-view.h"
20 #include "util/utf8.h"
21 #include "util/xsnprintf.h"
22 #include "window.h"
23
24 #if HAVE_EMBED
25 #include "test-data-embed.h"
26 #else
27 #include "test-data.h"
28 #endif
29
30 1 static void test_builtin_configs(TestContext *ctx)
31 {
32 1 EditorState *e = ctx->userdata;
33 1 HashMap *syntaxes = &e->syntaxes;
34 1 size_t n;
35 1 const BuiltinConfig *editor_builtin_configs = get_builtin_configs_array(&n);
36 1 e->err.print_to_stderr = true;
37
38
2/2
✓ Branch 26 → 4 taken 80 times.
✓ Branch 26 → 27 taken 1 time.
81 for (size_t i = 0; i < n; i++) {
39 80 const BuiltinConfig cfg = editor_builtin_configs[i];
40 80 char path[4096];
41 80 xsnprintf(path, sizeof path, "config/%s", cfg.name);
42
43
2/2
✓ Branch 5 → 6 taken 19 times.
✓ Branch 5 → 9 taken 61 times.
80 if (!str_has_prefix(cfg.name, "syntax/")) {
44 // Check that built-in configs and scripts are identical to
45 // their source files
46 19 char *src;
47 19 ssize_t size = read_file(path, &src, 8u << 20);
48 19 EXPECT_MEMEQ(src, size, cfg.text.data, cfg.text.length);
49 19 free(src);
50 19 continue;
51 }
52
53
2/2
✓ Branch 9 → 10 taken 4 times.
✓ Branch 9 → 11 taken 57 times.
61 if (str_has_prefix(cfg.name, "syntax/inc/")) {
54 4 continue;
55 }
56
57 // Check that the files corresponding to built-in syntax files load
58 // without errors.
59 //
60 // Note that we load from disk instead of from the builtins already
61 // in memory, because it makes it much simpler to get source locations
62 // in error messages correct. For example, load_syntax(e, cfg.text, …)
63 // would produce "syntax/c:…" and would require manual adjustments if
64 // wanting e.g. `make check` (inside dte) to jump to the real source
65 // location ("config/syntax/c").
66
67 57 unsigned int saved_nr_errs = e->err.nr_errors;
68 57 const char *basename = path_basename(cfg.name);
69 57 EXPECT_NULL(find_syntax(syntaxes, basename));
70
71 57 SyntaxLoadFlags flags = SYN_MUST_EXIST | SYN_LINT | SYN_WARN_ON_UNUSED_SUBSYN;
72 57 Syntax *syn = load_syntax_file(e, path, flags);
73 57 EXPECT_NONNULL(syn);
74
1/2
✗ Branch 15 → 16 not taken.
✓ Branch 15 → 17 taken 57 times.
57 if (!syn) {
75 continue;
76 }
77
78 57 EXPECT_EQ(e->err.nr_errors, saved_nr_errs);
79 57 EXPECT_STREQ(syn->name, basename);
80 57 EXPECT_NONNULL(syn->start_state);
81 57 EXPECT_TRUE(syn->states.count >= 1);
82 57 EXPECT_NONNULL(find_syntax(syntaxes, basename));
83 }
84
85 1 update_all_syntax_styles(&e->syntaxes, &e->styles);
86 1 e->err.print_to_stderr = false;
87 1 }
88
89 21 static void expect_files_equal(TestContext *ctx, const char *path1, const char *path2)
90 {
91 21 size_t filesize_limit = 1u << 20; // 1MiB
92 21 char *buf1;
93 21 ssize_t size1 = read_file(path1, &buf1, filesize_limit);
94
1/2
✗ Branch 3 → 4 not taken.
✓ Branch 3 → 6 taken 21 times.
21 if (size1 < 0) {
95 TEST_FAIL("Error reading '%s': %s", path1, strerror(errno));
96 21 return;
97 }
98 21 test_pass(ctx);
99
100 21 char *buf2;
101 21 ssize_t size2 = read_file(path2, &buf2, filesize_limit);
102
1/2
✗ Branch 8 → 9 not taken.
✓ Branch 8 → 12 taken 21 times.
21 if (size2 < 0) {
103 free(buf1);
104 TEST_FAIL("Error reading '%s': %s", path2, strerror(errno));
105 return;
106 }
107 21 test_pass(ctx);
108
109 // The code below is similar to expect_memeq(), but with the error
110 // messages made more useful in the context of comparing files
111 // (instead of just arbitrary memory)
112
113 21 const StringView sv1 = string_view(buf1, size1);
114 21 const StringView sv2 = string_view(buf2, size2);
115
1/2
✓ Branch 14 → 15 taken 21 times.
✗ Branch 14 → 17 not taken.
21 if (likely(strview_equal(sv1, sv2))) {
116 21 free(buf1);
117 21 free(buf2);
118 21 test_pass(ctx);
119 21 return;
120 }
121
122 char buf[4096];
123 size_t m1 = u_make_printable(sv1, buf, sizeof(buf) - 64, 0);
124 size_t m2 = u_make_printable(sv2, buf + m1, sizeof(buf) - m1, 0);
125 const char *color1 = ctx->cyan;
126 const char *color2 = ctx->yellow;
127 const char *sgr0 = ctx->sgr0;
128
129 // Add space padding after the shorter filename, so that both contents
130 // strings are aligned and minor differences are easier to spot
131 int path_len_diff = (int)strlen(path1) - (int)strlen(path2);
132 int pad1 = (path_len_diff < 0) ? -path_len_diff : 0;
133 int pad2 = (path_len_diff > 0) ? path_len_diff : 0;
134
135 TEST_FAIL (
136 "Files %s%s%s and %s%s%s differ:\n"
137 "%s:1: %*s%s%.*s%s\n"
138 "%s:1: %*s%s%.*s%s",
139 color1, path1, sgr0,
140 color2, path2, sgr0,
141 path1, pad1, "", color1, (int)m1, buf, sgr0,
142 path2, pad2, "", color2, (int)m2, buf + m1, sgr0
143 );
144
145 free(buf1);
146 free(buf2);
147 }
148
149 1 static void test_exec_config(TestContext *ctx)
150 {
151 1 static const char *const outfiles[] = {
152 "env.txt",
153 "crlf.txt",
154 "insert.txt",
155 "join.txt",
156 "change.txt",
157 "thai-utf8.txt",
158 "pipe-from.txt",
159 "pipe-to.txt",
160 "redo1.txt",
161 "redo2.txt",
162 "replace.txt",
163 "indent.txt",
164 "repeat.txt",
165 "wrap.txt",
166 "exec.txt",
167 "move.txt",
168 "delete.txt",
169 "new-line.txt",
170 "tag.txt",
171 };
172
173 // Delete output files left over from previous runs
174 1 int r = unlink("build/test/thai-tis620.txt");
175
2/4
✓ Branch 3 → 4 taken 1 time.
✗ Branch 3 → 6 not taken.
✗ Branch 4 → 5 not taken.
✓ Branch 4 → 6 taken 1 time.
1 LOG_ERRNO_ON(r && errno != ENOENT, "unlink")
176
2/2
✓ Branch 13 → 7 taken 19 times.
✓ Branch 13 → 14 taken 1 time.
20 FOR_EACH_I(i, outfiles) {
177 19 char out[64];
178 19 xsnprintf(out, sizeof out, "build/test/%s", outfiles[i]);
179 19 r = unlink(out);
180
2/4
✓ Branch 9 → 10 taken 19 times.
✗ Branch 9 → 12 not taken.
✗ Branch 10 → 11 not taken.
✓ Branch 10 → 12 taken 19 times.
19 LOG_ERRNO_ON(r && errno != ENOENT, "unlink")
181 }
182
183 // Execute *.dterc files
184 1 EditorState *e = ctx->userdata;
185
2/2
✓ Branch 17 → 15 taken 19 times.
✓ Branch 17 → 22 taken 1 time.
20 FOR_EACH_I(i, builtin_configs) {
186 19 const BuiltinConfig config = builtin_configs[i];
187 19 exec_normal_config(e, config.text);
188 }
189
190 // Check that output files have expected contents
191
2/2
✓ Branch 22 → 18 taken 19 times.
✓ Branch 22 → 23 taken 1 time.
20 FOR_EACH_I(i, outfiles) {
192 19 char out[64], ref[64];
193 19 xsnprintf(out, sizeof out, "build/test/%s", outfiles[i]);
194 19 xsnprintf(ref, sizeof ref, "test/data/%s", outfiles[i]);
195 19 expect_files_equal(ctx, out, ref);
196 }
197
198
1/2
✓ Branch 24 → 25 taken 1 time.
✗ Branch 24 → 26 not taken.
1 if (conversion_supported_by_iconv("UTF-8", "TIS-620")) {
199 1 expect_files_equal(ctx, "build/test/thai-tis620.txt", "test/data/thai-tis620.txt");
200 }
201
202 1 const StringView sv = strview("toggle utf8-bom \\");
203 1 EXPECT_FALSE(e->options.utf8_bom);
204 1 exec_normal_config(e, sv);
205 1 EXPECT_TRUE(e->options.utf8_bom);
206 1 exec_normal_config(e, sv);
207 1 EXPECT_FALSE(e->options.utf8_bom);
208 1 }
209
210 1 static void test_detect_indent(TestContext *ctx)
211 {
212 1 EditorState *e = ctx->userdata;
213 1 EXPECT_FALSE(e->options.detect_indent);
214 1 EXPECT_FALSE(e->options.expand_tab);
215 1 EXPECT_EQ(e->options.indent_width, 8);
216
217 1 static const char cmds[] =
218 "option -r '/test/data/detect-indent\\.ini$' detect-indent 2,4,8;"
219 "open test/data/detect-indent.ini;";
220
221 1 EXPECT_TRUE(handle_normal_command(e, cmds, false));
222 1 EXPECT_EQ(e->buffer->options.detect_indent, 1 << 1 | 1 << 3 | 1 << 7);
223 1 EXPECT_TRUE(e->buffer->options.expand_tab);
224 1 EXPECT_EQ(e->buffer->options.indent_width, 2);
225
226 1 EXPECT_TRUE(handle_normal_command(e, "close", false));
227 1 }
228
229 1 static void test_editor_state(TestContext *ctx)
230 {
231 1 const EditorState *e = ctx->userdata;
232 1 const Buffer *buffer = e->buffer;
233 1 const View *view = e->view;
234 1 const Window *window = e->window;
235 1 const Frame *root_frame = e->root_frame;
236 1 ASSERT_NONNULL(window);
237 1 ASSERT_NONNULL(root_frame);
238 1 ASSERT_NONNULL(buffer);
239 1 ASSERT_NONNULL(view);
240 1 ASSERT_PTREQ(window->view, view);
241 1 ASSERT_PTREQ(window->frame, root_frame);
242 1 ASSERT_PTREQ(window->editor, e);
243 1 ASSERT_PTREQ(view->buffer, buffer);
244 1 ASSERT_PTREQ(view->window, window);
245 1 ASSERT_PTREQ(root_frame->window, window);
246 1 ASSERT_PTREQ(root_frame->parent, NULL);
247
248 1 ASSERT_EQ(window->views.count, 1);
249 1 ASSERT_EQ(buffer->views.count, 1);
250 1 ASSERT_EQ(e->buffers.count, 1);
251 1 ASSERT_PTREQ(window->views.ptrs[0], view);
252 1 ASSERT_PTREQ(buffer->views.ptrs[0], view);
253 1 ASSERT_PTREQ(window_get_first_view(window), view);
254 1 ASSERT_PTREQ(buffer_get_first_view(buffer), view);
255 1 ASSERT_PTREQ(e->buffers.ptrs[0], buffer);
256
257 1 ASSERT_NONNULL(buffer->encoding);
258 1 ASSERT_NONNULL(buffer->blocks.next);
259 1 ASSERT_PTREQ(&buffer->blocks, view->cursor.head);
260 1 ASSERT_PTREQ(buffer->blocks.next, view->cursor.blk);
261 1 ASSERT_PTREQ(buffer->cur_change, &buffer->change_head);
262 1 ASSERT_PTREQ(buffer->saved_change, buffer->cur_change);
263 1 EXPECT_NULL(buffer->display_filename);
264 1 EXPECT_STREQ(buffer->options.filetype, "none");
265 1 EXPECT_TRUE(buffer_filetype_is_none(buffer));
266
267 // Note: this isn't necessarily equal to 1 because some UNITTEST
268 // blocks may have already called window_open_empty_buffer()
269 // before init_headless_mode() was entered
270 1 EXPECT_TRUE(buffer->id > 0);
271 1 }
272
273 1 static void test_handle_normal_command(TestContext *ctx)
274 {
275 1 EditorState *e = ctx->userdata;
276 1 EXPECT_TRUE(handle_normal_command(e, "right; left", false));
277 1 EXPECT_TRUE(handle_normal_command(e, ";left;right;;left;right;;;", false));
278 1 EXPECT_FALSE(handle_normal_command(e, "alias 'err", false));
279 1 EXPECT_FALSE(handle_normal_command(e, "refresh; alias 'x", false));
280 1 }
281
282 1 static void test_macro_record(TestContext *ctx)
283 {
284 1 EditorState *e = ctx->userdata;
285 1 MacroRecorder *m = &e->macro;
286 1 EXPECT_PTREQ(e->mode->cmds, &normal_commands);
287
288 1 EXPECT_EQ(m->macro.count, 0);
289 1 EXPECT_EQ(m->prev_macro.count, 0);
290 1 EXPECT_FALSE(macro_is_recording(m));
291 1 EXPECT_TRUE(macro_record(m));
292 1 EXPECT_TRUE(macro_is_recording(m));
293
294 1 EXPECT_TRUE(handle_normal_command(e, "open", false));
295 1 EXPECT_TRUE(handle_input(e, 'x'));
296 1 EXPECT_TRUE(handle_input(e, 'y'));
297 1 EXPECT_TRUE(handle_normal_command(e, "bol", true));
298 1 EXPECT_TRUE(handle_input(e, '-'));
299 1 EXPECT_TRUE(handle_input(e, 'z'));
300 1 EXPECT_TRUE(handle_normal_command(e, "eol; right; insert -m .; new-line", true));
301
302 1 const StringView t1 = strview("test 1\n");
303 1 insert_text(e->view, t1, true);
304 1 macro_insert_text_hook(m, t1);
305
306 1 const StringView t2 = strview("-- test 2");
307 1 insert_text(e->view, t2, true);
308 1 macro_insert_text_hook(m, t2);
309
310 1 macro_search_hook(m, "[12]", true, false);
311
312 1 EXPECT_TRUE(macro_is_recording(m));
313 1 EXPECT_TRUE(macro_stop(m));
314 1 EXPECT_FALSE(macro_is_recording(m));
315 1 EXPECT_EQ(m->macro.count, 10);
316 1 EXPECT_EQ(m->prev_macro.count, 0);
317
318 1 static const char expected_macro[] =
319 "insert -k xy\n"
320 "bol\n"
321 "insert -k -- -z\n"
322 "eol\n"
323 "right\n"
324 "insert -m .\n"
325 "new-line\n"
326 "insert -m \"test 1\\n\"\n"
327 "insert -m -- '-- test 2'\n"
328 "search -r -H [12]\n"
329 ;
330
331 1 String s = dump_macro(m);
332 1 EXPECT_MEMEQ(s.buffer, s.len, expected_macro, sizeof(expected_macro) - 1);
333 1 string_free(&s);
334
335 // Check that the replayed macro produces the same buffer as the commands above
336 1 static const char cmds[] =
337 "save -f build/test/macro-rec.txt;"
338 "close -f;"
339 "open;"
340 "macro play;"
341 "save -f build/test/macro-out.txt;"
342 "close -f;"
343 "show macro;"
344 "close -f;";
345
346 1 EXPECT_TRUE(handle_normal_command(e, cmds, false));
347 1 expect_files_equal(ctx, "build/test/macro-rec.txt", "build/test/macro-out.txt");
348
349 // Ensure macro_cancel() keeps the previously recorded macro
350 1 EXPECT_FALSE(macro_is_recording(m));
351 1 EXPECT_TRUE(macro_record(m));
352 1 EXPECT_TRUE(macro_is_recording(m));
353 1 EXPECT_TRUE(handle_input(e, 'x'));
354 1 EXPECT_TRUE(macro_cancel(m));
355 1 EXPECT_FALSE(macro_is_recording(m));
356 1 EXPECT_EQ(m->macro.count, 10);
357 1 EXPECT_EQ(m->prev_macro.count, 0);
358 1 }
359
360 static const TestEntry tests[] = {
361 TEST(test_editor_state),
362 TEST(test_handle_normal_command),
363 TEST(test_builtin_configs),
364 TEST(test_exec_config),
365 TEST(test_detect_indent),
366 TEST(test_macro_record),
367 };
368
369 const TestGroup config_tests = TEST_GROUP(tests);
370
371 1 void init_headless_mode(TestContext *ctx)
372 {
373 1 EditorState *e = ctx->userdata;
374 1 ASSERT_NONNULL(e);
375 1 exec_rc_files(e, NULL, false, false);
376 1 e->window = new_window(e);
377 1 e->root_frame = new_root_frame(e->window);
378 1 set_view(window_open_empty_buffer(e->window));
379 1 }
380