vtgl

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

test.c (11285B)


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