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