| 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 81 → 79 taken 250 times.
✓ Branch 81 → 82 taken 1 time.
|
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 91 → 89 taken 4 times.
✓ Branch 91 → 92 taken 1 time.
|
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 96 → 94 taken 4 times.
✓ Branch 96 → 97 taken 1 time.
|
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 2 → 3 not taken.
✓ Branch 2 → 5 taken 1 time.
|
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 49 → 43 taken 65 times.
✓ Branch 49 → 50 taken 1 time.
|
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 45 → 46 not taken.
✓ Branch 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 |