vtgl

terminal emulator implemented in OpenGL
git clone anongit@rnpnr.xyz:vtgl.git
Log | Files | Refs | Feed | LICENSE

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 }