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