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/xsnprintf.h" | ||
21 | #include "window.h" | ||
22 | |||
23 | #if HAVE_EMBED | ||
24 | #include "test-data-embed.h" | ||
25 | #else | ||
26 | #include "test-data.h" | ||
27 | #endif | ||
28 | |||
29 | 1 | static void test_builtin_configs(TestContext *ctx) | |
30 | { | ||
31 | 1 | EditorState *e = ctx->userdata; | |
32 | 1 | HashMap *syntaxes = &e->syntaxes; | |
33 | 1 | size_t n; | |
34 | 1 | const BuiltinConfig *editor_builtin_configs = get_builtin_configs_array(&n); | |
35 | 1 | e->err.print_to_stderr = true; | |
36 | |||
37 |
2/2✓ Branch 0 (20→4) taken 77 times.
✓ Branch 1 (20→21) taken 1 times.
|
78 | for (size_t i = 0; i < n; i++) { |
38 | 77 | const BuiltinConfig cfg = editor_builtin_configs[i]; | |
39 |
2/2✓ Branch 0 (4→5) taken 59 times.
✓ Branch 1 (4→14) taken 18 times.
|
77 | if (str_has_prefix(cfg.name, "syntax/")) { |
40 |
2/2✓ Branch 0 (5→6) taken 4 times.
✓ Branch 1 (5→7) taken 55 times.
|
59 | if (str_has_prefix(cfg.name, "syntax/inc/")) { |
41 | 4 | continue; | |
42 | } | ||
43 | // Check that built-in syntax files load without errors | ||
44 | 55 | EXPECT_NULL(find_syntax(syntaxes, path_basename(cfg.name))); | |
45 | 55 | unsigned int saved_nr_errs = e->err.nr_errors; | |
46 | 55 | EXPECT_NONNULL(load_syntax(e, cfg.text, cfg.name, 0)); | |
47 | 55 | EXPECT_EQ(e->err.nr_errors, saved_nr_errs); | |
48 | 55 | EXPECT_NONNULL(find_syntax(syntaxes, path_basename(cfg.name))); | |
49 | } else { | ||
50 | // Check that built-in configs and scripts are identical to | ||
51 | // their source files | ||
52 | 18 | char path[4096]; | |
53 | 18 | xsnprintf(path, sizeof path, "config/%s", cfg.name); | |
54 | 18 | char *src; | |
55 | 18 | ssize_t size = read_file(path, &src, 8u << 20); | |
56 | 18 | EXPECT_MEMEQ(src, size, cfg.text.data, cfg.text.length); | |
57 | 18 | free(src); | |
58 | } | ||
59 | } | ||
60 | |||
61 | 1 | update_all_syntax_styles(&e->syntaxes, &e->styles); | |
62 | 1 | e->err.print_to_stderr = false; | |
63 | 1 | } | |
64 | |||
65 | 20 | static void expect_files_equal(TestContext *ctx, const char *path1, const char *path2) | |
66 | { | ||
67 | 20 | size_t filesize_limit = 1u << 20; // 1MiB | |
68 | 20 | char *buf1; | |
69 | 20 | ssize_t size1 = read_file(path1, &buf1, filesize_limit); | |
70 |
1/2✗ Branch 0 (3→4) not taken.
✓ Branch 1 (3→6) taken 20 times.
|
20 | if (size1 < 0) { |
71 | − | TEST_FAIL("Error reading '%s': %s", path1, strerror(errno)); | |
72 | ✗ | return; | |
73 | } | ||
74 | 20 | test_pass(ctx); | |
75 | |||
76 | 20 | char *buf2; | |
77 | 20 | ssize_t size2 = read_file(path2, &buf2, filesize_limit); | |
78 |
1/2✗ Branch 0 (8→9) not taken.
✓ Branch 1 (8→12) taken 20 times.
|
20 | if (size2 < 0) { |
79 | ✗ | free(buf1); | |
80 | − | TEST_FAIL("Error reading '%s': %s", path2, strerror(errno)); | |
81 | ✗ | return; | |
82 | } | ||
83 | 20 | test_pass(ctx); | |
84 | |||
85 |
2/4✓ Branch 0 (13→14) taken 20 times.
✗ Branch 1 (13→16) not taken.
✗ Branch 2 (15→16) not taken.
✓ Branch 3 (15→17) taken 20 times.
|
20 | if (size1 != size2 || !mem_equal(buf1, buf2, size1)) { |
86 | − | TEST_FAIL("Files differ: '%s', '%s'", path1, path2); | |
87 | } else { | ||
88 | 20 | test_pass(ctx); | |
89 | } | ||
90 | |||
91 | 20 | free(buf1); | |
92 | 20 | free(buf2); | |
93 | } | ||
94 | |||
95 | 1 | static void test_exec_config(TestContext *ctx) | |
96 | { | ||
97 | 1 | static const char *const outfiles[] = { | |
98 | "env.txt", | ||
99 | "crlf.txt", | ||
100 | "insert.txt", | ||
101 | "join.txt", | ||
102 | "change.txt", | ||
103 | "thai-utf8.txt", | ||
104 | "pipe-from.txt", | ||
105 | "pipe-to.txt", | ||
106 | "redo1.txt", | ||
107 | "redo2.txt", | ||
108 | "replace.txt", | ||
109 | "shift.txt", | ||
110 | "repeat.txt", | ||
111 | "wrap.txt", | ||
112 | "exec.txt", | ||
113 | "move.txt", | ||
114 | "delete.txt", | ||
115 | "tag.txt", | ||
116 | }; | ||
117 | |||
118 | // Delete output files left over from previous runs | ||
119 | 1 | int r = unlink("build/test/thai-tis620.txt"); | |
120 |
2/4✓ Branch 0 (3→4) taken 1 times.
✗ Branch 1 (3→12) not taken.
✗ Branch 2 (4→5) not taken.
✓ Branch 3 (4→12) taken 1 times.
|
1 | LOG_ERRNO_ON(r && errno != ENOENT, "unlink") |
121 |
2/2✓ Branch 0 (13→6) taken 18 times.
✓ Branch 1 (13→14) taken 1 times.
|
19 | FOR_EACH_I(i, outfiles) { |
122 | 18 | char out[64]; | |
123 | 18 | xsnprintf(out, sizeof out, "build/test/%s", outfiles[i]); | |
124 | 18 | r = unlink(out); | |
125 |
2/4✓ Branch 0 (8→9) taken 18 times.
✗ Branch 1 (8→11) not taken.
✗ Branch 2 (9→10) not taken.
✓ Branch 3 (9→11) taken 18 times.
|
18 | LOG_ERRNO_ON(r && errno != ENOENT, "unlink") |
126 | } | ||
127 | |||
128 | // Execute *.dterc files | ||
129 | 1 | EditorState *e = ctx->userdata; | |
130 |
2/2✓ Branch 0 (17→15) taken 18 times.
✓ Branch 1 (17→22) taken 1 times.
|
19 | FOR_EACH_I(i, builtin_configs) { |
131 | 18 | const BuiltinConfig config = builtin_configs[i]; | |
132 | 18 | exec_normal_config(e, config.text); | |
133 | } | ||
134 | |||
135 | // Check that output files have expected contents | ||
136 |
2/2✓ Branch 0 (22→18) taken 18 times.
✓ Branch 1 (22→23) taken 1 times.
|
19 | FOR_EACH_I(i, outfiles) { |
137 | 18 | char out[64], ref[64]; | |
138 | 18 | xsnprintf(out, sizeof out, "build/test/%s", outfiles[i]); | |
139 | 18 | xsnprintf(ref, sizeof ref, "test/data/%s", outfiles[i]); | |
140 | 18 | expect_files_equal(ctx, out, ref); | |
141 | } | ||
142 | |||
143 |
1/2✓ Branch 0 (24→25) taken 1 times.
✗ Branch 1 (24→26) not taken.
|
1 | if (conversion_supported_by_iconv("UTF-8", "TIS-620")) { |
144 | 1 | expect_files_equal(ctx, "build/test/thai-tis620.txt", "test/data/thai-tis620.txt"); | |
145 | } | ||
146 | |||
147 | 1 | const StringView sv = STRING_VIEW("toggle utf8-bom \\"); | |
148 | 1 | EXPECT_FALSE(e->options.utf8_bom); | |
149 | 1 | exec_normal_config(e, sv); | |
150 | 1 | EXPECT_TRUE(e->options.utf8_bom); | |
151 | 1 | exec_normal_config(e, sv); | |
152 | 1 | EXPECT_FALSE(e->options.utf8_bom); | |
153 | 1 | } | |
154 | |||
155 | 1 | static void test_detect_indent(TestContext *ctx) | |
156 | { | ||
157 | 1 | EditorState *e = ctx->userdata; | |
158 | 1 | EXPECT_FALSE(e->options.detect_indent); | |
159 | 1 | EXPECT_FALSE(e->options.expand_tab); | |
160 | 1 | EXPECT_EQ(e->options.indent_width, 8); | |
161 | |||
162 | 1 | static const char cmds[] = | |
163 | "option -r '/test/data/detect-indent\\.ini$' detect-indent 2,4,8;" | ||
164 | "open test/data/detect-indent.ini;"; | ||
165 | |||
166 | 1 | EXPECT_TRUE(handle_normal_command(e, cmds, false)); | |
167 | 1 | EXPECT_EQ(e->buffer->options.detect_indent, 1 << 1 | 1 << 3 | 1 << 7); | |
168 | 1 | EXPECT_TRUE(e->buffer->options.expand_tab); | |
169 | 1 | EXPECT_EQ(e->buffer->options.indent_width, 2); | |
170 | |||
171 | 1 | EXPECT_TRUE(handle_normal_command(e, "close", false)); | |
172 | 1 | } | |
173 | |||
174 | 1 | static void test_editor_state(TestContext *ctx) | |
175 | { | ||
176 | 1 | const EditorState *e = ctx->userdata; | |
177 | 1 | const Buffer *buffer = e->buffer; | |
178 | 1 | const View *view = e->view; | |
179 | 1 | const Window *window = e->window; | |
180 | 1 | const Frame *root_frame = e->root_frame; | |
181 | 1 | ASSERT_NONNULL(window); | |
182 | 1 | ASSERT_NONNULL(root_frame); | |
183 | 1 | ASSERT_NONNULL(buffer); | |
184 | 1 | ASSERT_NONNULL(view); | |
185 | 1 | ASSERT_PTREQ(window->view, view); | |
186 | 1 | ASSERT_PTREQ(window->frame, root_frame); | |
187 | 1 | ASSERT_PTREQ(window->editor, e); | |
188 | 1 | ASSERT_PTREQ(view->buffer, buffer); | |
189 | 1 | ASSERT_PTREQ(view->window, window); | |
190 | 1 | ASSERT_PTREQ(root_frame->window, window); | |
191 | 1 | ASSERT_PTREQ(root_frame->parent, NULL); | |
192 | |||
193 | 1 | ASSERT_EQ(window->views.count, 1); | |
194 | 1 | ASSERT_EQ(buffer->views.count, 1); | |
195 | 1 | ASSERT_EQ(e->buffers.count, 1); | |
196 | 1 | ASSERT_PTREQ(window->views.ptrs[0], view); | |
197 | 1 | ASSERT_PTREQ(buffer->views.ptrs[0], view); | |
198 | 1 | ASSERT_PTREQ(window_get_first_view(window), view); | |
199 | 1 | ASSERT_PTREQ(buffer_get_first_view(buffer), view); | |
200 | 1 | ASSERT_PTREQ(e->buffers.ptrs[0], buffer); | |
201 | |||
202 | 1 | ASSERT_NONNULL(buffer->encoding); | |
203 | 1 | ASSERT_NONNULL(buffer->blocks.next); | |
204 | 1 | ASSERT_PTREQ(&buffer->blocks, view->cursor.head); | |
205 | 1 | ASSERT_PTREQ(buffer->blocks.next, view->cursor.blk); | |
206 | 1 | ASSERT_PTREQ(buffer->cur_change, &buffer->change_head); | |
207 | 1 | ASSERT_PTREQ(buffer->saved_change, buffer->cur_change); | |
208 | 1 | EXPECT_NULL(buffer->display_filename); | |
209 | |||
210 | // Note: this isn't necessarily equal to 1 because some UNITTEST | ||
211 | // blocks may have already called window_open_empty_buffer() | ||
212 | // before init_headless_mode() was entered | ||
213 | 1 | EXPECT_TRUE(buffer->id > 0); | |
214 | 1 | } | |
215 | |||
216 | 1 | static void test_handle_normal_command(TestContext *ctx) | |
217 | { | ||
218 | 1 | EditorState *e = ctx->userdata; | |
219 | 1 | EXPECT_TRUE(handle_normal_command(e, "right; left", false)); | |
220 | 1 | EXPECT_TRUE(handle_normal_command(e, ";left;right;;left;right;;;", false)); | |
221 | 1 | EXPECT_FALSE(handle_normal_command(e, "alias 'err", false)); | |
222 | 1 | EXPECT_FALSE(handle_normal_command(e, "refresh; alias 'x", false)); | |
223 | 1 | } | |
224 | |||
225 | 1 | static void test_macro_record(TestContext *ctx) | |
226 | { | ||
227 | 1 | EditorState *e = ctx->userdata; | |
228 | 1 | MacroRecorder *m = &e->macro; | |
229 | 1 | EXPECT_PTREQ(e->mode->cmds, &normal_commands); | |
230 | |||
231 | 1 | EXPECT_EQ(m->macro.count, 0); | |
232 | 1 | EXPECT_EQ(m->prev_macro.count, 0); | |
233 | 1 | EXPECT_FALSE(macro_is_recording(m)); | |
234 | 1 | EXPECT_TRUE(macro_record(m)); | |
235 | 1 | EXPECT_TRUE(macro_is_recording(m)); | |
236 | |||
237 | 1 | EXPECT_TRUE(handle_normal_command(e, "open", false)); | |
238 | 1 | EXPECT_TRUE(handle_input(e, 'x')); | |
239 | 1 | EXPECT_TRUE(handle_input(e, 'y')); | |
240 | 1 | EXPECT_TRUE(handle_normal_command(e, "bol", true)); | |
241 | 1 | EXPECT_TRUE(handle_input(e, '-')); | |
242 | 1 | EXPECT_TRUE(handle_input(e, 'z')); | |
243 | 1 | EXPECT_TRUE(handle_normal_command(e, "eol; right; insert -m .; new-line", true)); | |
244 | |||
245 | 1 | const StringView t1 = STRING_VIEW("test 1\n"); | |
246 | 1 | insert_text(e->view, t1.data, t1.length, true); | |
247 | 1 | macro_insert_text_hook(m, t1.data, t1.length); | |
248 | |||
249 | 1 | const StringView t2 = STRING_VIEW("-- test 2"); | |
250 | 1 | insert_text(e->view, t2.data, t2.length, true); | |
251 | 1 | macro_insert_text_hook(m, t2.data, t2.length); | |
252 | |||
253 | 1 | EXPECT_TRUE(macro_is_recording(m)); | |
254 | 1 | EXPECT_TRUE(macro_stop(m)); | |
255 | 1 | EXPECT_FALSE(macro_is_recording(m)); | |
256 | 1 | EXPECT_EQ(m->macro.count, 9); | |
257 | 1 | EXPECT_EQ(m->prev_macro.count, 0); | |
258 | |||
259 | 1 | static const char cmds[] = | |
260 | "save -f build/test/macro-rec.txt;" | ||
261 | "close -f;" | ||
262 | "open;" | ||
263 | "macro play;" | ||
264 | "save -f build/test/macro-out.txt;" | ||
265 | "close -f;" | ||
266 | "show macro;" | ||
267 | "close -f;"; | ||
268 | |||
269 | 1 | EXPECT_TRUE(handle_normal_command(e, cmds, false)); | |
270 | 1 | expect_files_equal(ctx, "build/test/macro-rec.txt", "build/test/macro-out.txt"); | |
271 | |||
272 | 1 | EXPECT_FALSE(macro_is_recording(m)); | |
273 | 1 | EXPECT_TRUE(macro_record(m)); | |
274 | 1 | EXPECT_TRUE(macro_is_recording(m)); | |
275 | 1 | EXPECT_TRUE(handle_input(e, 'x')); | |
276 | 1 | EXPECT_TRUE(macro_cancel(m)); | |
277 | 1 | EXPECT_FALSE(macro_is_recording(m)); | |
278 | 1 | EXPECT_EQ(m->macro.count, 9); | |
279 | 1 | EXPECT_EQ(m->prev_macro.count, 0); | |
280 | 1 | } | |
281 | |||
282 | static const TestEntry tests[] = { | ||
283 | TEST(test_editor_state), | ||
284 | TEST(test_handle_normal_command), | ||
285 | TEST(test_builtin_configs), | ||
286 | TEST(test_exec_config), | ||
287 | TEST(test_detect_indent), | ||
288 | TEST(test_macro_record), | ||
289 | }; | ||
290 | |||
291 | const TestGroup config_tests = TEST_GROUP(tests); | ||
292 | |||
293 | DISABLE_WARNING("-Wmissing-prototypes") | ||
294 | |||
295 | 1 | void init_headless_mode(TestContext *ctx) | |
296 | { | ||
297 | 1 | EditorState *e = ctx->userdata; | |
298 | 1 | ASSERT_NONNULL(e); | |
299 | 1 | exec_rc_files(e, NULL, false); | |
300 | 1 | e->window = new_window(e); | |
301 | 1 | e->root_frame = new_root_frame(e->window); | |
302 | 1 | set_view(window_open_empty_buffer(e->window)); | |
303 | 1 | } | |
304 |