vtgl

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

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 }