test.c (11275B)
1 /* See LICENSE for copyright details */ 2 #include "vtgl.h" 3 #include "config.h" 4 5 /* NOTE: stubs for stuff we aren't testing */ 6 static void get_gpu_glyph_index(Arena, void *, void *, u32, u32, u32, CachedGlyph **); 7 8 KEYBIND_FN(copy) { return 0; } 9 KEYBIND_FN(paste) { return 0; } 10 KEYBIND_FN(scroll) { return 0; } 11 KEYBIND_FN(zoom) { return 0; } 12 13 #include "font.c" 14 #include "terminal.c" 15 16 static void 17 get_gpu_glyph_index(Arena a, void *b, void *c, u32 cp, u32 d, u32 e, CachedGlyph **cg) 18 { 19 /* TODO: this is wrong but will have to do for these tests */ 20 static CachedGlyph scg; 21 if (BETWEEN(cp, ' ', '~')) scg.tile_count = 1; 22 else scg.tile_count = 2; 23 *cg = &scg; 24 } 25 26 static b32 27 mem_cmp(void *a_, void *b_, size len) 28 { 29 /* NOTE: small size assumption */ 30 ASSERT(len < 32); 31 b32 result = 0; 32 u8 *a = a_, *b = b_; 33 for (size i = 0; i < len; i++) 34 result |= a[i] != b[i]; 35 return result; 36 } 37 38 struct test_result { b32 status; const char *info; }; 39 #define TEST_FN(name) struct test_result name(Term *term, Arena arena) 40 typedef TEST_FN(Test_Fn); 41 42 #define TESTS \ 43 X(csi_embedded_control) \ 44 X(colour_setting) \ 45 X(cursor_at_line_boundary) \ 46 X(cursor_movement) \ 47 X(cursor_tabs) \ 48 X(cursor_tabs_across_boundary) \ 49 X(working_ringbuffer) 50 51 #define X(fn) static TEST_FN(fn); 52 TESTS 53 #undef X 54 55 #define X(fn) fn, 56 static Test_Fn *tests[] = { 57 TESTS 58 }; 59 #undef X 60 61 #define ESC(a) s8("\x1B"#a) 62 #define CSI(a) ESC([a) 63 64 static s8 failure_string = s8("\x1B[31mFAILURE\x1B[0m\n"); 65 static s8 success_string = s8("\x1B[32mSUCCESS\x1B[0m\n"); 66 67 static void 68 buffer_reset(Term *t) 69 { 70 t->views[0].log.widx = 0; 71 t->views[0].log.filled = 0; 72 t->views[1].log.widx = 0; 73 t->views[1].log.filled = 0; 74 t->unprocessed_bytes = 0; 75 t->views[0].lines.widx = 0; 76 t->views[0].lines.filled = 0; 77 t->views[1].lines.widx = 0; 78 t->views[1].lines.filled = 0; 79 } 80 81 static size 82 copy_into_ringbuf(RingBuf *rb, s8 raw) 83 { 84 ASSERT(raw.len < rb->cap); 85 for (size i = 0; i < raw.len; i++) 86 rb->buf[rb->widx + i] = raw.data[i]; 87 88 rb->widx += raw.len; 89 rb->filled += raw.len; 90 91 CLAMP(rb->filled, 0, rb->cap); 92 if (rb->widx >= rb->cap) 93 rb->widx -= rb->cap; 94 95 ASSERT(rb->filled >= 0); 96 ASSERT(rb->widx >= 0 && rb->widx < rb->cap); 97 return raw.len; 98 } 99 100 static b32 101 check_cells_equal(Cell *a, Cell *b) 102 { 103 b32 result = a->cp == b->cp && 104 a->bg == b->bg && 105 a->fg == b->fg; 106 return result; 107 } 108 109 static s8 110 launder_static_string(Term *term, s8 static_str) 111 { 112 RingBuf *rb = &term->views[term->view_idx].log; 113 term->unprocessed_bytes += copy_into_ringbuf(rb, static_str); 114 s8 raw = { 115 .len = term->unprocessed_bytes, 116 .data = rb->buf + (rb->widx - term->unprocessed_bytes) 117 }; 118 return raw; 119 } 120 121 static TEST_FN(csi_embedded_control) 122 { 123 struct test_result result = {.info = __FUNCTION__}; 124 125 /* NOTE: print a '1' with default style then start changing the colour, 126 * but backspace within the escape sequence so the cursor is now over the 127 * '1', but then put a newline inside the sequence so that cursor is on 128 * the next line, finally print a ' 2' with the completed colour sequence. */ 129 launder_static_string(term, s8("1")); 130 launder_static_string(term, CSI(48;2;7\b5;63;4\n2m)); 131 s8 raw = launder_static_string(term, s8(" 2")); 132 handle_input(term, arena, raw); 133 134 #if 0 135 dump_csi(&term->csi); 136 #endif 137 138 CellStyle final_style = { 139 .bg = (Colour){.r = 75, .g = 63, .b = 42, .a = 0xFF}, 140 .fg = g_colours.data[g_colours.fgidx], 141 .attr = ATTR_NULL, 142 }; 143 144 Cell c1 = {.cp = '1', 145 .bg = SHADER_PACK_BG(g_colours.data[g_colours.bgidx].rgba, ATTR_NULL), 146 .fg = SHADER_PACK_FG(g_colours.data[g_colours.fgidx].rgba, ATTR_NULL), 147 }; 148 Cell c2 = {.cp = '2', 149 .bg = SHADER_PACK_BG(final_style.bg.rgba, final_style.attr), 150 .fg = SHADER_PACK_FG(final_style.fg.rgba, final_style.attr), 151 }; 152 153 result.status = term->cursor.pos.y == 1 && term->cursor.pos.x == 2; 154 result.status &= check_cells_equal(&c1, &term->views[term->view_idx].fb.rows[0][0]); 155 result.status &= check_cells_equal(&c2, &term->views[term->view_idx].fb.rows[1][1]); 156 /* NOTE: we also want to ensure that we cannot split a line in the middle of a CSI */ 157 LineBuf *lb = &term->views[0].lines; 158 result.status &= lb->filled == 0 && *lb->buf[lb->widx].start != '2'; 159 160 return result; 161 } 162 163 static TEST_FN(colour_setting) 164 { 165 struct test_result result = {.info = __FUNCTION__}; 166 167 launder_static_string(term, CSI(8m)); 168 launder_static_string(term, CSI(4m)); 169 launder_static_string(term, CSI(9m)); 170 launder_static_string(term, CSI(24m)); 171 launder_static_string(term, CSI(33m)); 172 launder_static_string(term, CSI(48;2;75;63;42m)); 173 s8 raw = launder_static_string(term, s8("A")); 174 175 handle_input(term, arena, raw); 176 177 u32 attr = (ATTR_INVISIBLE|ATTR_STRUCK); 178 Cell c = {.cp = 'A', 179 .bg = SHADER_PACK_BG(((Colour){.r = 75, .g = 63, .b = 42, .a = 0xFF}.rgba), attr), 180 .fg = SHADER_PACK_FG(g_colours.data[3].rgba, attr), 181 }; 182 result.status = check_cells_equal(&c, term->views[term->view_idx].fb.rows[0]); 183 184 return result; 185 } 186 187 static TEST_FN(cursor_at_line_boundary) 188 { 189 /* NOTE: Test that lines are not split in the middle of utf-8 characters */ 190 struct test_result result = {.info = __FUNCTION__}; 191 192 s8 long_line = s8alloc(&arena, 8192); 193 mem_clear(long_line.data, ' ', long_line.len); 194 195 /* NOTE: change the cursor state in the middle of the line */ 196 long_line.data[128 + 0] = 0x1B; 197 long_line.data[128 + 1] = '['; 198 long_line.data[128 + 2] = '5'; 199 long_line.data[128 + 3] = 'm'; 200 201 /* NOTE: place a non-ASCII character on the long line edge */ 202 s8 red_dragon = utf8_encode(0x1F004); 203 long_line.data[SPLIT_LONG - 1] = red_dragon.data[0]; 204 long_line.data[SPLIT_LONG + 0] = red_dragon.data[1]; 205 206 s8 raw = launder_static_string(term, (s8){.len = SPLIT_LONG + 1, .data = long_line.data}); 207 long_line = consume(long_line, SPLIT_LONG + 1); 208 209 LineBuf *lb = &term->views[term->view_idx].lines; 210 size line_count = lb->filled; 211 handle_input(term, arena, raw); 212 213 /* NOTE: ensure line didn't split on red dragon */ 214 result.status = term->unprocessed_bytes != 0; 215 216 long_line.data[0] = red_dragon.data[2]; 217 long_line.data[1] = red_dragon.data[3]; 218 219 /* NOTE: shove a newline at the end so that the line completes */ 220 long_line.data[long_line.len - 1] = '\n'; 221 222 #if 0 223 long_line.data[SPLIT_LONG + 3] = 0; 224 printf("encoded char: %s\n", (char *)(long_line.data + SPLIT_LONG - 1)); 225 long_line.data[SPLIT_LONG + 3] = ' '; 226 #endif 227 228 raw = launder_static_string(term, long_line); 229 handle_input(term, arena, raw); 230 231 result.status &= line_length(lb->buf) > SPLIT_LONG; 232 233 return result; 234 } 235 236 237 static TEST_FN(cursor_movement) 238 { 239 struct test_result result = {.info = __FUNCTION__}; 240 241 s8 raw = launder_static_string(term, CSI(17;2H)); 242 handle_input(term, arena, raw); 243 244 raw = launder_static_string(term, CSI(7G)); 245 handle_input(term, arena, raw); 246 247 result.status = term->cursor.pos.y == 16 && term->cursor.pos.x == 6; 248 249 return result; 250 } 251 252 static TEST_FN(cursor_tabs) 253 { 254 struct test_result result = {.info = __FUNCTION__}; 255 256 /* NOTE: first test advancing to a tabstop */ 257 s8 raw = launder_static_string(term, s8("123\t")); 258 handle_input(term, arena, raw); 259 260 result.status = term->cursor.pos.x == (g_tabstop); 261 262 /* NOTE: now test negative tabstop movement and tabstop setting */ 263 launder_static_string(term, s8("12")); 264 launder_static_string(term, ESC(H)); 265 launder_static_string(term, s8("34\t")); 266 raw = launder_static_string(term, CSI(2Z)); 267 handle_input(term, arena, raw); 268 269 result.status &= term->cursor.pos.x == (g_tabstop); 270 271 return result; 272 } 273 274 static TEST_FN(cursor_tabs_across_boundary) 275 { 276 struct test_result result = {.info = __FUNCTION__}; 277 278 /* NOTE: clear tabstops then set one beyond multiple boundaries */ 279 launder_static_string(term, CSI(3g)); 280 launder_static_string(term, CSI(1;67H)); 281 launder_static_string(term, ESC(H)); 282 launder_static_string(term, CSI(1;1H)); 283 s8 raw = launder_static_string(term, s8("\t")); 284 handle_input(term, arena, raw); 285 286 result.status = term->cursor.pos.x == 66; 287 288 /* NOTE: now set one exactly on a boundary */ 289 launder_static_string(term, CSI(1;34H)); 290 launder_static_string(term, ESC(H)); 291 launder_static_string(term, CSI(1;1H)); 292 raw = launder_static_string(term, s8("\t")); 293 handle_input(term, arena, raw); 294 295 result.status &= term->cursor.pos.x == 33; 296 297 /* NOTE: now set one right before the previous */ 298 launder_static_string(term, CSI(1;33H)); 299 launder_static_string(term, ESC(H)); 300 launder_static_string(term, CSI(1;1H)); 301 302 raw = launder_static_string(term, s8("\t")); 303 handle_input(term, arena, raw); 304 result.status &= term->cursor.pos.x == 32; 305 306 raw = launder_static_string(term, s8("\t")); 307 handle_input(term, arena, raw); 308 result.status &= term->cursor.pos.x == 33; 309 310 /* NOTE: ensure backwards tab works */ 311 launder_static_string(term, CSI(1;34H)); 312 launder_static_string(term, ESC(0g)); 313 launder_static_string(term, CSI(1;66H)); 314 raw = launder_static_string(term, CSI(1Z)); 315 handle_input(term, arena, raw); 316 result.status &= term->cursor.pos.x == 33; 317 318 return result; 319 } 320 321 static TEST_FN(working_ringbuffer) 322 { 323 struct test_result result = {.info = __FUNCTION__}; 324 RingBuf *rb = &term->views[term->view_idx].log; 325 rb->buf[0] = 0xFE; 326 result.status = (rb->buf[0] == rb->buf[ rb->cap]) && 327 (rb->buf[0] == rb->buf[-rb->cap]); 328 return result; 329 } 330 331 int 332 main(void) 333 { 334 Arena memory = arena_from_memory_block(linux_block_alloc(MB(32))); 335 Term term = {0}; 336 u32 failure_count = 0; 337 338 Stream log = stream_alloc(&memory, MB(4)); 339 for (u32 i = 0; i < ARRAY_COUNT(term.saved_cursors); i++) { 340 cursor_reset(&term); 341 cursor_move_to(&term, 0, 0); 342 cursor_alt(&term, 1); 343 } 344 345 os_allocate_ring_buffer(&term.views[0].log, BACKLOG_SIZE); 346 line_buf_alloc(&term.views[0].lines, &memory, term.views[0].log.buf, term.cursor.style, 347 BACKLOG_LINES); 348 os_allocate_ring_buffer(&term.views[1].log, ALT_BACKLOG_SIZE); 349 line_buf_alloc(&term.views[1].lines, &memory, term.views[1].log.buf, term.cursor.style, 350 ALT_BACKLOG_LINES); 351 352 /* TODO: should probably be some odd size */ 353 term.size = (iv2){.w = 80, .h = 24}; 354 term.views[0].fb.backing_store = memory_block_from_arena(&memory, MB(2)); 355 term.views[1].fb.backing_store = memory_block_from_arena(&memory, MB(2)); 356 initialize_framebuffer(&term.views[0].fb, term.size); 357 initialize_framebuffer(&term.views[1].fb, term.size); 358 359 u32 max_name_len = 0; 360 #define X(name) if (sizeof(#name) - 1 > max_name_len) max_name_len = sizeof(#name) - 1; 361 TESTS 362 #undef X 363 max_name_len += 1; 364 365 for (u32 i = 0; i < ARRAY_COUNT(tests); i++) { 366 buffer_reset(&term); 367 term_reset(&term); 368 struct test_result result = tests[i](&term, memory); 369 s8 fn = c_str_to_s8((char *)result.info); 370 stream_push_s8(&log, fn); 371 stream_push_s8(&log, s8(":")); 372 size count = fn.len; 373 while (count < max_name_len) { stream_push_byte(&log, ' '); count++; } 374 if (result.status == 0) { 375 failure_count++; 376 stream_push_s8(&log, failure_string); 377 } else { 378 stream_push_s8(&log, success_string); 379 } 380 } 381 stream_push_s8(&log, s8("FINISHED: [")); 382 stream_push_u64(&log, ARRAY_COUNT(tests) - failure_count); 383 stream_push_byte(&log, '/'); 384 stream_push_u64(&log, ARRAY_COUNT(tests)); 385 stream_push_s8(&log, s8("] Succeeded\n")); 386 os_write_err_msg(stream_to_s8(&log)); 387 return 0; 388 }