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