test.c (8508B)
1 #include "vtgl.c" 2 3 /* NOTE: stubs for stuff we aren't testing */ 4 #undef glfwSetWindowTitle 5 #undef glfwGetWindowTitle 6 7 void glfwSetWindowTitle(GLFWwindow *w, const char *s) {}; 8 const char *glfwGetWindowTitle(GLFWwindow *w) { return "test"; }; 9 10 #include <string.h> /* memcmp */ 11 12 #define ESC(a) s8("\x1B"#a) 13 #define CSI(a) ESC([a) 14 15 struct test_result { b32 status; const char *info; }; 16 #define TEST_FN(name) struct test_result name(Term *term, Arena arena) 17 typedef TEST_FN(Test_Fn); 18 19 #define TESTS \ 20 X(csi_embedded_control) \ 21 X(colour_setting) \ 22 X(cursor_at_line_boundary) \ 23 X(cursor_movement) \ 24 X(cursor_tabs) \ 25 X(working_ringbuffer) 26 27 #define X(fn) static TEST_FN(fn); 28 TESTS 29 #undef X 30 31 #define X(fn) fn, 32 static Test_Fn *tests[] = { 33 TESTS 34 }; 35 36 static void 37 buffer_reset(Term *t) 38 { 39 t->views[0].log.widx = 0; 40 t->views[0].log.filled = 0; 41 t->views[1].log.widx = 0; 42 t->views[1].log.filled = 0; 43 t->unprocessed_bytes = 0; 44 t->views[0].lines.widx = 0; 45 t->views[0].lines.filled = 0; 46 t->views[1].lines.widx = 0; 47 t->views[1].lines.filled = 0; 48 } 49 50 static size 51 copy_into_ringbuf(RingBuf *rb, s8 raw) 52 { 53 ASSERT(raw.len < rb->cap); 54 for (size i = 0; i < raw.len; i++) 55 rb->buf[rb->widx + i] = raw.data[i]; 56 57 rb->widx += raw.len; 58 rb->filled += raw.len; 59 60 CLAMP(rb->filled, 0, rb->cap); 61 if (rb->widx >= rb->cap) 62 rb->widx -= rb->cap; 63 64 ASSERT(rb->filled >= 0); 65 ASSERT(rb->widx >= 0 && rb->widx < rb->cap); 66 return raw.len; 67 } 68 69 static b32 70 check_cells_equal(Cell *a, Cell *b) 71 { 72 b32 result = a->cp == b->cp && 73 a->style.bg.rgba == b->style.bg.rgba && 74 a->style.fg.rgba == b->style.fg.rgba && 75 a->style.attr == b->style.attr; 76 return result; 77 } 78 79 static s8 80 launder_static_string(Term *term, s8 static_str) 81 { 82 RingBuf *rb = &term->views[term->view_idx].log; 83 term->unprocessed_bytes += copy_into_ringbuf(rb, static_str); 84 s8 raw = { 85 .len = term->unprocessed_bytes, 86 .data = rb->buf + (rb->widx - term->unprocessed_bytes) 87 }; 88 return raw; 89 } 90 91 static TEST_FN(csi_embedded_control) 92 { 93 struct test_result result = {.info = __FUNCTION__}; 94 95 /* NOTE: print a '1' with default style then start changing the colour, 96 * but backspace within the escape sequence so the cursor is now over the 97 * '1', but then put a newline inside the sequence so that cursor is on 98 * the next line, finally print a ' 2' with the completed colour sequence. */ 99 launder_static_string(term, s8("1")); 100 launder_static_string(term, CSI(48;2;7\b5;63;4\n2m)); 101 s8 raw = launder_static_string(term, s8(" 2")); 102 size parsed_lines = split_raw_input_to_lines(term, raw); 103 blit_lines(term, arena, parsed_lines); 104 105 #if 0 106 dump_csi(&term->csi); 107 #endif 108 109 Cell c1 = { .cp = '1', .style = { 110 .bg = g_colours.data[g_colours.bgidx], 111 .fg = g_colours.data[g_colours.fgidx], 112 .attr = (ATTR_NULL), 113 }}; 114 Cell c2 = { .cp = '2', .style = { 115 .bg = (Colour){.r = 75, .g = 63, .b = 42, .a = 0xFF}, 116 .fg = g_colours.data[3], 117 .attr = (ATTR_INVISIBLE|ATTR_STRUCK), 118 }}; 119 result.status = term->cursor.pos.y == 1 && term->cursor.pos.x == 3; 120 result.status |= check_cells_equal(&c1, &term->views[term->view_idx].fb.rows[0][0]); 121 result.status |= check_cells_equal(&c2, &term->views[term->view_idx].fb.rows[1][1]); 122 123 return result; 124 } 125 126 static TEST_FN(colour_setting) 127 { 128 struct test_result result = {.info = __FUNCTION__}; 129 130 launder_static_string(term, CSI(8m)); 131 launder_static_string(term, CSI(4m)); 132 launder_static_string(term, CSI(9m)); 133 launder_static_string(term, CSI(24m)); 134 launder_static_string(term, CSI(33m)); 135 launder_static_string(term, CSI(48;2;75;63;42m)); 136 s8 raw = launder_static_string(term, s8("A")); 137 138 size parsed_lines = split_raw_input_to_lines(term, raw); 139 blit_lines(term, arena, parsed_lines); 140 141 Cell c = { .cp = 'A', .style = { 142 .bg = (Colour){.r = 75, .g = 63, .b = 42, .a = 0xFF}, 143 .fg = g_colours.data[3], 144 .attr = (ATTR_INVISIBLE|ATTR_STRUCK), 145 }}; 146 result.status = check_cells_equal(&c, term->views[term->view_idx].fb.rows[0]); 147 148 return result; 149 } 150 151 static TEST_FN(cursor_movement) 152 { 153 struct test_result result = {.info = __FUNCTION__}; 154 155 s8 raw = launder_static_string(term, CSI(17;2H)); 156 size parsed_lines = split_raw_input_to_lines(term, raw); 157 blit_lines(term, arena, parsed_lines); 158 159 raw = launder_static_string(term, CSI(7G)); 160 parsed_lines = split_raw_input_to_lines(term, raw); 161 blit_lines(term, arena, parsed_lines); 162 163 result.status = term->cursor.pos.y == 16 && term->cursor.pos.x == 6; 164 165 return result; 166 } 167 168 static TEST_FN(cursor_tabs) 169 { 170 struct test_result result = {.info = __FUNCTION__}; 171 172 /* NOTE: first test advancing to a tabstop */ 173 s8 raw = launder_static_string(term, s8("123\t")); 174 size parsed_lines = split_raw_input_to_lines(term, raw); 175 blit_lines(term, arena, parsed_lines); 176 177 result.status = term->cursor.pos.x == (g_tabstop); 178 179 /* NOTE: now test negative tabstop movement */ 180 launder_static_string(term, s8("\t1234\t")); 181 raw = launder_static_string(term, CSI(2Z)); 182 parsed_lines = split_raw_input_to_lines(term, raw); 183 blit_lines(term, arena, parsed_lines); 184 185 result.status &= term->cursor.pos.x == (g_tabstop); 186 187 return result; 188 } 189 190 static TEST_FN(cursor_at_line_boundary) 191 { 192 /* NOTE: two tests here 193 * 1. split_raw_input_to_lines should modify cursor style properties 194 * 2. split_raw_input_to_lines should not split utf-8 characters into separate lines 195 */ 196 struct test_result result = {.info = __FUNCTION__}; 197 198 s8 long_line = s8alloc(&arena, 8192); 199 mem_clear(long_line.data, ' ', long_line.len); 200 201 /* NOTE: change the cursor state in the middle of the line */ 202 long_line.data[128 + 0] = 0x1B; 203 long_line.data[128 + 1] = '['; 204 long_line.data[128 + 2] = '5'; 205 long_line.data[128 + 3] = 'm'; 206 207 /* NOTE: place a non-ASCII character on the long line edge */ 208 s8 red_dragon = utf8_encode(0x1F004); 209 long_line.data[SPLIT_LONG - 1] = red_dragon.data[0]; 210 long_line.data[SPLIT_LONG + 0] = red_dragon.data[1]; 211 long_line.data[SPLIT_LONG + 1] = red_dragon.data[2]; 212 long_line.data[SPLIT_LONG + 2] = red_dragon.data[3]; 213 214 /* NOTE: shove a newline at the end so that finish parsing the second line */ 215 long_line.data[long_line.len - 1] = '\n'; 216 217 #if 0 218 long_line.data[SPLIT_LONG + 3] = 0; 219 printf("encoded char: %s\n", (char *)(long_line.data + SPLIT_LONG - 1)); 220 long_line.data[SPLIT_LONG + 3] = ' '; 221 #endif 222 223 s8 raw = launder_static_string(term, long_line); 224 225 LineBuf *lb = &term->views[term->view_idx].lines; 226 /* NOTE: check if line actually split and that it was split after red dragon */ 227 size parsed_lines = split_raw_input_to_lines(term, raw); 228 result.status = parsed_lines == 2; 229 result.status &= line_length(lb->buf) > SPLIT_LONG; 230 231 /* NOTE: check that cursor state was transferred */ 232 Cursor line_end = simulate_line(term, lb->buf); 233 result.status &= !memcmp(&line_end.style, 234 &lb->buf[1].cursor_state, 235 sizeof(lb->buf[0].cursor_state)); 236 237 return result; 238 } 239 240 static TEST_FN(working_ringbuffer) 241 { 242 struct test_result result = {.info = __FUNCTION__}; 243 RingBuf *rb = &term->views[term->view_idx].log; 244 rb->buf[0] = 0xFE; 245 result.status = (rb->buf[0] == rb->buf[ rb->cap]) && 246 (rb->buf[0] == rb->buf[-rb->cap]); 247 return result; 248 } 249 250 static u32 failure_count; 251 252 int 253 main(void) 254 { 255 Arena memory = os_new_arena(16 * MEGABYTE); 256 Term term = {0}; 257 258 for (u32 i = 0; i < ARRAY_COUNT(term.saved_cursors); i++) { 259 cursor_reset(&term); 260 cursor_move_to(&term, 0, 0); 261 cursor_alt(&term, 1); 262 } 263 264 os_alloc_ring_buffer(&term.views[0].log, BACKLOG_SIZE); 265 line_buf_alloc(&term.views[0].lines, &memory, term.views[0].log.buf, term.cursor.style, 266 BACKLOG_LINES); 267 os_alloc_ring_buffer(&term.views[1].log, ALT_BACKLOG_SIZE); 268 line_buf_alloc(&term.views[1].lines, &memory, term.views[1].log.buf, term.cursor.style, 269 ALT_BACKLOG_LINES); 270 271 /* TODO: should probably be some odd size */ 272 term.size = (uv2){.w = 80, .h = 24}; 273 os_alloc_framebuffer(&term.views[0].fb, term.size.h, term.size.w); 274 os_alloc_framebuffer(&term.views[1].fb, term.size.h, term.size.w); 275 276 for (u32 i = 0; i < ARRAY_COUNT(tests); i++) { 277 buffer_reset(&term); 278 term_reset(&term); 279 struct test_result result = tests[i](&term, memory); 280 if (result.status == 0) { 281 failure_count++; 282 printf("TEST FAILED: [%u/%lu] %s\n", i, ARRAY_COUNT(tests), result.info); 283 } 284 } 285 printf("FINISHED: [%lu/%lu] Succeeded\n", ARRAY_COUNT(tests) - failure_count, 286 ARRAY_COUNT(tests)); 287 return 0; 288 }