dte test coverage


Directory: ./
File: test/syntax.c
Date: 2025-09-07 23:01:39
Exec Total Coverage
Lines: 231 234 98.7%
Functions: 3 3 100.0%
Branches: 10 12 83.3%

Line Branch Exec Source
1 #include "test.h"
2 #include "block-iter.h"
3 #include "config.h"
4 #include "editor.h"
5 #include "syntax/bitset.h"
6 #include "syntax/highlight.h"
7 #include "util/log.h"
8 #include "util/utf8.h"
9 #include "window.h"
10
11 1 static void test_bitset(TestContext *ctx)
12 {
13 1 BitSetWord set[BITSET_NR_WORDS(256)];
14 1 ASSERT_TRUE(sizeof(set) >= 32);
15
16 1 memset(set, 0, sizeof(set));
17 1 EXPECT_FALSE(bitset_contains(set, '0'));
18 1 EXPECT_FALSE(bitset_contains(set, 'a'));
19 1 EXPECT_FALSE(bitset_contains(set, 'z'));
20 1 EXPECT_FALSE(bitset_contains(set, '!'));
21 1 EXPECT_FALSE(bitset_contains(set, '\0'));
22
23 1 bitset_add_char_range(set, "0-9a-fxy");
24 1 EXPECT_TRUE(bitset_contains(set, '0'));
25 1 EXPECT_TRUE(bitset_contains(set, '8'));
26 1 EXPECT_TRUE(bitset_contains(set, '9'));
27 1 EXPECT_TRUE(bitset_contains(set, 'a'));
28 1 EXPECT_TRUE(bitset_contains(set, 'b'));
29 1 EXPECT_TRUE(bitset_contains(set, 'f'));
30 1 EXPECT_TRUE(bitset_contains(set, 'x'));
31 1 EXPECT_TRUE(bitset_contains(set, 'y'));
32 1 EXPECT_FALSE(bitset_contains(set, 'g'));
33 1 EXPECT_FALSE(bitset_contains(set, 'z'));
34 1 EXPECT_FALSE(bitset_contains(set, 'A'));
35 1 EXPECT_FALSE(bitset_contains(set, 'F'));
36 1 EXPECT_FALSE(bitset_contains(set, 'X'));
37 1 EXPECT_FALSE(bitset_contains(set, 'Z'));
38 1 EXPECT_FALSE(bitset_contains(set, '{'));
39 1 EXPECT_FALSE(bitset_contains(set, '`'));
40 1 EXPECT_FALSE(bitset_contains(set, '/'));
41 1 EXPECT_FALSE(bitset_contains(set, ':'));
42 1 EXPECT_FALSE(bitset_contains(set, '\0'));
43
44 1 BITSET_INVERT(set);
45 1 EXPECT_FALSE(bitset_contains(set, '0'));
46 1 EXPECT_FALSE(bitset_contains(set, '8'));
47 1 EXPECT_FALSE(bitset_contains(set, '9'));
48 1 EXPECT_FALSE(bitset_contains(set, 'a'));
49 1 EXPECT_FALSE(bitset_contains(set, 'b'));
50 1 EXPECT_FALSE(bitset_contains(set, 'f'));
51 1 EXPECT_FALSE(bitset_contains(set, 'x'));
52 1 EXPECT_FALSE(bitset_contains(set, 'y'));
53 1 EXPECT_TRUE(bitset_contains(set, 'g'));
54 1 EXPECT_TRUE(bitset_contains(set, 'z'));
55 1 EXPECT_TRUE(bitset_contains(set, 'A'));
56 1 EXPECT_TRUE(bitset_contains(set, 'F'));
57 1 EXPECT_TRUE(bitset_contains(set, 'X'));
58 1 EXPECT_TRUE(bitset_contains(set, 'Z'));
59 1 EXPECT_TRUE(bitset_contains(set, '{'));
60 1 EXPECT_TRUE(bitset_contains(set, '`'));
61 1 EXPECT_TRUE(bitset_contains(set, '/'));
62 1 EXPECT_TRUE(bitset_contains(set, ':'));
63 1 EXPECT_TRUE(bitset_contains(set, '\0'));
64
65 1 memset(set, 0, sizeof(set));
66 1 bitset_add_char_range(set, "\1-?r-r^-b");
67 1 EXPECT_TRUE(bitset_contains(set, '\1'));
68 1 EXPECT_TRUE(bitset_contains(set, '\2'));
69 1 EXPECT_TRUE(bitset_contains(set, ' '));
70 1 EXPECT_TRUE(bitset_contains(set, '!'));
71 1 EXPECT_TRUE(bitset_contains(set, '>'));
72 1 EXPECT_TRUE(bitset_contains(set, '?'));
73 1 EXPECT_TRUE(bitset_contains(set, 'r'));
74 1 EXPECT_TRUE(bitset_contains(set, '^'));
75 1 EXPECT_TRUE(bitset_contains(set, '_'));
76 1 EXPECT_TRUE(bitset_contains(set, '`'));
77 1 EXPECT_TRUE(bitset_contains(set, 'a'));
78 1 EXPECT_TRUE(bitset_contains(set, 'b'));
79 1 EXPECT_FALSE(bitset_contains(set, '\0'));
80 1 EXPECT_FALSE(bitset_contains(set, '@'));
81 1 EXPECT_FALSE(bitset_contains(set, 'c'));
82 1 EXPECT_FALSE(bitset_contains(set, 'q'));
83 1 EXPECT_FALSE(bitset_contains(set, 's'));
84 1 EXPECT_FALSE(bitset_contains(set, 'A'));
85
86 1 memset(set, 0, sizeof(set));
87 1 bitset_add_char_range(set, "\x03-\xFC");
88 1 EXPECT_TRUE(bitset_contains(set, '\x03'));
89 1 EXPECT_TRUE(bitset_contains(set, '\x40'));
90 1 EXPECT_TRUE(bitset_contains(set, '\x7F'));
91 1 EXPECT_TRUE(bitset_contains(set, '\x80'));
92 1 EXPECT_TRUE(bitset_contains(set, '\xFC'));
93 1 EXPECT_FALSE(bitset_contains(set, '\x00'));
94 1 EXPECT_FALSE(bitset_contains(set, '\x01'));
95 1 EXPECT_FALSE(bitset_contains(set, '\x02'));
96 1 EXPECT_FALSE(bitset_contains(set, '\xFE'));
97 1 EXPECT_FALSE(bitset_contains(set, '\xFF'));
98
2/2
✓ Branch 0 (81→79) taken 250 times.
✓ Branch 1 (81→82) taken 1 times.
251 for (unsigned int i = 3; i <= 0xFC; i++) {
99 250 IEXPECT_TRUE(bitset_contains(set, i));
100 }
101
102 1 memset(set, 0, sizeof(set));
103 1 bitset_add_char_range(set, "?-@");
104 1 EXPECT_TRUE(bitset_contains(set, '?'));
105 1 EXPECT_TRUE(bitset_contains(set, '@'));
106 1 EXPECT_FALSE(bitset_contains(set, '>'));
107 1 EXPECT_FALSE(bitset_contains(set, 'A'));
108
109 1 memset(set, 0, sizeof(set));
110 1 bitset_add_char_range(set, "z-a");
111
2/2
✓ Branch 0 (91→89) taken 4 times.
✓ Branch 1 (91→92) taken 1 times.
5 FOR_EACH_I(i, set) {
112 4 EXPECT_UINT_EQ(set[i], 0);
113 }
114
115 1 BITSET_INVERT(set);
116
2/2
✓ Branch 0 (96→94) taken 4 times.
✓ Branch 1 (96→97) taken 1 times.
5 FOR_EACH_I(i, set) {
117 4 EXPECT_UINT_EQ(set[i], bitset_word_max());
118 }
119 1 }
120
121 1 static void test_load_syntax_errors(TestContext *ctx)
122 {
123 1 EditorState *e = ctx->userdata;
124 1 ErrorBuffer *ebuf = &e->err;
125 1 SyntaxLoadFlags flags = SYN_LINT;
126
127 1 clear_error(ebuf);
128 1 StringView text = strview("syntax dup; state a; eat this; syntax dup; state b; eat this");
129 1 EXPECT_NULL(load_syntax(e, text, "dup", flags));
130 1 EXPECT_STREQ(ebuf->buf, "dup:2: Syntax 'dup' already exists");
131
132 1 clear_error(ebuf);
133 1 text = strview("syntax empty");
134 1 EXPECT_NULL(load_syntax(e, text, "empty", flags));
135 1 EXPECT_STREQ(ebuf->buf, "empty:2: Empty syntax");
136
137 1 clear_error(ebuf);
138 1 text = strview("syntax hde; state a; heredocend b; eat this; state b; eat this");
139 1 EXPECT_NULL(load_syntax(e, text, "hde", flags));
140 1 EXPECT_STREQ(ebuf->buf, "hde:2: heredocend can be used only in subsyntaxes");
141
142 1 clear_error(ebuf);
143 1 text = strview("syntax loop; state ident; noeat this");
144 1 EXPECT_NULL(load_syntax(e, text, "loop", flags));
145 1 EXPECT_STREQ(ebuf->buf, "loop:1: noeat: using noeat to jump to same state causes infinite loop");
146
147 1 clear_error(ebuf);
148 1 text = strview("syntax ml; state a; inlist X this; eat this");
149 1 EXPECT_NULL(load_syntax(e, text, "ml", flags));
150 1 EXPECT_STREQ(ebuf->buf, "ml:2: No such list 'X'");
151
152 1 clear_error(ebuf);
153 1 text = strview("syntax mst; state a; noeat X");
154 1 EXPECT_NULL(load_syntax(e, text, "mst", flags));
155 1 EXPECT_STREQ(ebuf->buf, "mst:2: No such state 'X'");
156
157 1 clear_error(ebuf);
158 1 text = strview("syntax nda; state a; char a this");
159 1 EXPECT_NULL(load_syntax(e, text, "nda", flags));
160 1 EXPECT_STREQ(ebuf->buf, "nda:2: No default action in state 'a'");
161
162 1 clear_error(ebuf);
163 1 text = strview("syntax not-known; state a; eat this");
164 1 EXPECT_NULL(load_syntax(e, text, "known", flags));
165 1 EXPECT_STREQ(ebuf->buf, "known: no main syntax found (i.e. with name 'known')");
166
167 // Non-fatal errors:
168
169 // Unreachable state
170 1 clear_error(ebuf);
171 1 text = strview("syntax ust; state a; eat this; state U; eat a");
172 1 const Syntax *syntax = load_syntax(e, text, "ust", flags);
173 1 ASSERT_NONNULL(syntax);
174 1 EXPECT_STREQ(syntax->name, "ust");
175 1 EXPECT_EQ(syntax->states.count, 2);
176 1 EXPECT_STREQ(syntax->start_state->name, "a");
177 1 EXPECT_STREQ(ebuf->buf, "ust:2: State 'U' is unreachable");
178
179 // Unused list
180 1 clear_error(ebuf);
181 1 text = strview("syntax ul; state a; eat this; list unused a");
182 1 syntax = load_syntax(e, text, "ul", flags);
183 1 ASSERT_NONNULL(syntax);
184 1 EXPECT_STREQ(syntax->name, "ul");
185 1 EXPECT_EQ(syntax->states.count, 1);
186 1 EXPECT_STREQ(syntax->start_state->name, "a");
187 1 EXPECT_STREQ(ebuf->buf, "ul:2: List 'unused' never used");
188
189 // Unused sub-syntax
190 1 clear_error(ebuf);
191 1 text = strview("syntax .uss-x; state a; eat this; syntax uss; state a; eat this");
192 1 syntax = load_syntax(e, text, "uss", flags);
193 1 ASSERT_NONNULL(syntax);
194 1 EXPECT_STREQ(syntax->name, "uss");
195 1 EXPECT_EQ(syntax->states.count, 1);
196 1 EXPECT_STREQ(syntax->start_state->name, "a");
197 1 EXPECT_STREQ(ebuf->buf, "uss:2: Subsyntax '.uss-x' is unused");
198
199 // Use of named destination, instead of "this"
200 1 clear_error(ebuf);
201 1 text = strview("syntax td; state a; eat a");
202 1 syntax = load_syntax(e, text, "td", flags);
203 1 ASSERT_NONNULL(syntax);
204 1 EXPECT_STREQ(syntax->name, "td");
205 1 EXPECT_EQ(syntax->states.count, 1);
206 1 EXPECT_STREQ(syntax->start_state->name, "a");
207 1 EXPECT_STREQ(ebuf->buf, "td:1: eat: destination 'a' can be optimized to 'this' in 'td' syntax");
208
209 // Redundant emit-name
210 1 clear_error(ebuf);
211 1 text = strview("syntax ren; state a; eat this a");
212 1 syntax = load_syntax(e, text, "ren", flags);
213 1 ASSERT_NONNULL(syntax);
214 1 EXPECT_STREQ(syntax->name, "ren");
215 1 EXPECT_EQ(syntax->states.count, 1);
216 1 EXPECT_STREQ(syntax->start_state->name, "a");
217 1 EXPECT_STREQ(ebuf->buf, "ren:1: eat: emit-name 'a' not needed (destination state uses same emit-name)");
218 1 }
219
220 1 static void test_hl_line(TestContext *ctx)
221 {
222
1/2
✗ Branch 0 (2→3) not taken.
✓ Branch 1 (2→5) taken 1 times.
1 if (!get_builtin_config("syntax/c")) {
223 LOG_INFO("syntax/c not available; skipping %s()", __func__);
224 return;
225 }
226
227 1 EditorState *e = ctx->userdata;
228 1 Window *window = e->window;
229 1 ASSERT_NONNULL(window);
230 1 View *view = window_open_file(window, "test/data/test.c", NULL);
231 1 ASSERT_NONNULL(view);
232 1 Buffer *buffer = view->buffer;
233 1 ASSERT_NONNULL(buffer);
234 1 const size_t line_nr = 5;
235 1 ASSERT_TRUE(buffer->nl >= line_nr);
236
237 1 Syntax *syn = buffer->syntax;
238 1 ASSERT_NONNULL(syn);
239 1 ASSERT_NONNULL(syn->start_state);
240 1 EXPECT_STREQ(syn->name, "c");
241 1 EXPECT_FALSE(syn->heredoc);
242
243 1 const StyleMap *styles = &e->styles;
244 1 PointerArray *lss = &buffer->line_start_states;
245 1 BlockIter tmp = block_iter(buffer);
246 1 hl_fill_start_states(syn, lss, styles, &tmp, buffer->nl);
247 1 block_iter_goto_line(&view->cursor, line_nr - 1);
248 1 view_update(view);
249 1 ASSERT_EQ(view->cx, 0);
250 1 ASSERT_EQ(view->cy, line_nr - 1);
251
252 1 StringView line = get_current_line(&view->cursor);
253 1 ASSERT_EQ(line.length, 65);
254
255 1 bool next_changed;
256 1 const TermStyle **hl = hl_line(syn, lss, styles, line, line_nr, &next_changed);
257 1 ASSERT_NONNULL(hl);
258 1 EXPECT_TRUE(next_changed);
259
260 1 const TermStyle *t = find_style(styles, "text");
261 1 const TermStyle *c = find_style(styles, "constant");
262 1 const TermStyle *s = find_style(styles, "string");
263 1 const TermStyle *x = find_style(styles, "special");
264 1 const TermStyle *n = find_style(styles, "numeric");
265 1 const TermStyle *y = find_style(styles, "type");
266 1 ASSERT_NONNULL(t);
267 1 ASSERT_NONNULL(c);
268 1 ASSERT_NONNULL(s);
269 1 ASSERT_NONNULL(x);
270 1 ASSERT_NONNULL(n);
271 1 ASSERT_NONNULL(y);
272 1 EXPECT_EQ(t->fg, COLOR_DEFAULT);
273 1 EXPECT_EQ(c->fg, COLOR_CYAN);
274 1 EXPECT_EQ(s->fg, COLOR_YELLOW);
275 1 EXPECT_EQ(x->fg, COLOR_MAGENTA);
276 1 EXPECT_EQ(n->fg, COLOR_BLUE);
277 1 EXPECT_EQ(y->fg, COLOR_GREEN);
278
279 1 const TermStyle *const expected_styles[] = {
280 t, t, t, t, t, t, t, t, t, t, t, t, c, c, c, c,
281 c, c, t, t, s, s, s, s, s, s, s, s, s, s, s, s,
282 s, s, s, s, x, x, s, t, t, s, s, s, s, s, t, t,
283 x, x, x, t, t, t, y, y, y, y, y, y, t, n, n, t,
284 t
285 };
286
287 1 size_t i = 0;
288
2/2
✓ Branch 0 (49→43) taken 65 times.
✓ Branch 1 (49→50) taken 1 times.
66 for (size_t pos = 0; pos < line.length; i++) {
289 65 CodePoint u = u_get_char(line.data, line.length, &pos);
290 65 IEXPECT_EQ(u, line.data[i]);
291
1/2
✗ Branch 0 (45→46) not taken.
✓ Branch 1 (45→47) taken 65 times.
65 if (i >= ARRAYLEN(expected_styles)) {
292 continue;
293 }
294 65 IEXPECT_TRUE(same_style(hl[i], expected_styles[i]));
295 }
296
297 1 EXPECT_EQ(i, ARRAYLEN(expected_styles));
298 1 window_close(window);
299 }
300
301 static const TestEntry tests[] = {
302 TEST(test_bitset),
303 TEST(test_load_syntax_errors),
304 TEST(test_hl_line),
305 };
306
307 const TestGroup syntax_tests = TEST_GROUP(tests);
308