dte test coverage


Directory: ./
Coverage: low: ≥ 0% medium: ≥ 50.0% high: ≥ 85.0%
Coverage Exec / Excl / Total
Lines: 93.2% 177 / 3 / 193
Functions: 100.0% 8 / 0 / 8
Branches: 66.7% 20 / 0 / 30

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