diff options
Diffstat (limited to 'tests/term_test.c')
| -rw-r--r-- | tests/term_test.c | 195 |
1 files changed, 183 insertions, 12 deletions
diff --git a/tests/term_test.c b/tests/term_test.c index 9dbe902..235e9df 100644 --- a/tests/term_test.c +++ b/tests/term_test.c @@ -4,6 +4,7 @@ #include <assert.h> #include <limits.h> #include <pthread.h> +#include <sched.h> #include <stdatomic.h> #include <stdbool.h> #include <stdio.h> @@ -1440,6 +1441,55 @@ static void test_snapshot_follows_new_terminal(void) term_snapshot_free(s); } +/* The getters take a NULL snapshot and an out-of-range row like a NULL + * terminal: they return the empty value. The view keeps its address. */ +static void test_snapshot_null_and_range(void) +{ + uint32_t cps[4] = { 0xAAAA, 0xAAAA, 0xAAAA, 0xAAAA }; + assert(term_snapshot_view(NULL) == NULL); + assert(term_snapshot_row(NULL, 0) == NULL); + assert(term_snapshot_row_key(NULL, 0) == 0); + struct term_span none = term_snapshot_row_selection(NULL, 0); + assert(none.begin == 0 && none.end == 0); + assert(term_snapshot_cluster(NULL, 'a', cps, 4) == 0); + assert(term_snapshot_cluster(NULL, TERM_TEXT_CLUSTER, cps, 4) == 0); + assert(cps[0] == 0xAAAA); + + struct term *t = tt_new(4, 2); + tt_write(t, "e\xcc\x81", 3); + struct term_snapshot *s = term_snapshot_new(); + assert(term_snapshot_update(s, t)); + + const struct term_view *view = term_snapshot_view(s); + const uint32_t *palette = view->palette; + assert(term_snapshot_update(s, t)); + assert(term_snapshot_view(s) == view && view->palette == palette); + + assert(term_snapshot_row(s, -1) == NULL); + assert(term_snapshot_row(s, 2) == NULL); + assert(term_snapshot_row_key(s, -1) == 0); + assert(term_snapshot_row_key(s, 2) == 0); + struct term_span off = term_snapshot_row_selection(s, 2); + assert(off.begin == 0 && off.end == 0); + + /* A cluster cell gives its count with no buffer, and with cap 0 it + * writes nothing. */ + uint32_t text = term_snapshot_row(s, 0)[0].text; + assert(text & TERM_TEXT_CLUSTER); + assert(term_snapshot_cluster(s, text, NULL, 0) == 2); + assert(term_snapshot_cluster(s, text, cps, 0) == 2); + assert(cps[0] == 0xAAAA); + assert(term_snapshot_cluster(s, text, NULL, 4) == 2); + /* A plain code point is one code point, and cap 0 writes nothing. */ + assert(term_snapshot_cluster(s, 'a', NULL, 0) == 1); + assert(term_snapshot_cluster(s, 'a', cps, 0) == 1); + assert(cps[0] == 0xAAAA); + assert(term_snapshot_cluster(s, 'a', cps, 4) == 1 && cps[0] == 'a'); + + term_snapshot_free(s); + term_free(t); +} + static void test_string_payloads(void) { static const char *prefixes[] = {"\033]0;", "\033]7;file://host/", "\033P"}; @@ -1485,6 +1535,32 @@ static void test_string_payloads(void) } } +/* term_new() refuses a name that a DCS reply could echo unsafely. */ +static void test_host_name_rejected(void) +{ + static const char *const bad[] = { + "\033x", "a\tb", "\177", "\300", "a\nb", + }; + char longest[65]; + memset(longest, 'a', 64); + longest[64] = 0; + char fits[64]; + memset(fits, 'b', 63); + fits[63] = 0; + + for (size_t i = 0; i < sizeof(bad) / sizeof(bad[0]); i++) { + struct term_options opt = { .size = { .x = 10, .y = 3 }, .name = bad[i] }; + assert(!term_new(&opt)); + } + struct term_options opt = { .size = { .x = 10, .y = 3 }, .name = longest }; + assert(!term_new(&opt)); + + opt.name = fits; + struct term *t = term_new(&opt); + assert(t); + term_free(t); +} + static void test_newline_policy_survives_reset(void) { static const char *const resets[] = { "\033c", "\033[!p", "\033[20l" }; @@ -1762,21 +1838,22 @@ struct concurrent_worker_args { _Atomic bool done; }; +static const char concurrent_stream[] = + "The quick brown fox jumps over the lazy dog.\r\n" + "\033[31mRed text\033[0m and \033[32mgreen text\033[0m.\r\n" + "Some unicode: 漢字, ひらがな, and emojis: 😀🚀🎉\r\n" + "\033[2J\033[HCleared and reset\r\n" + "Line 1\r\nLine 2\r\nLine 3\r\nLine 4\r\nLine 5\r\n" + "\033]7;file:///tmp/dir\007\033]22;pointer\007" + "\033]8;;https://example.com\007link\033]8;;\007\r\n"; + static void *concurrent_feed_worker(void *arg) { struct concurrent_worker_args *w = arg; - static const char stream[] = - "The quick brown fox jumps over the lazy dog.\r\n" - "\033[31mRed text\033[0m and \033[32mgreen text\033[0m.\r\n" - "Some unicode: 漢字, ひらがな, and emojis: 😀🚀🎉\r\n" - "\033[2J\033[HCleared and reset\r\n" - "Line 1\r\nLine 2\r\nLine 3\r\nLine 4\r\nLine 5\r\n" - "\033]7;file:///tmp/dir\007\033]22;pointer\007" - "\033]8;;https://example.com\007link\033]8;;\007\r\n"; - size_t len = strlen(stream); + size_t len = strlen(concurrent_stream); for (int i = 0; i < 2000; i++) { - term_feed(w->t, stream, len, 0); + term_feed(w->t, concurrent_stream, len, 0); } atomic_store(&w->done, true); return NULL; @@ -1784,10 +1861,11 @@ static void *concurrent_feed_worker(void *arg) static void test_concurrent_feed_and_snapshot(void) { - struct term *t = term_new(&(struct term_options){ + struct term_options opt = { .size = { .x = 80, .y = 24 }, .scrollback = 1000, - }); + }; + struct term *t = term_new(&opt); assert(t != NULL); struct concurrent_worker_args w = { .t = t, .done = false }; @@ -1828,13 +1906,104 @@ static void test_concurrent_feed_and_snapshot(void) bool ok = term_snapshot_update(snap, t); assert(ok); + /* The reader changed only the view and the selection. The screen and + * the history must match a terminal that got the same bytes on one + * thread. */ + struct term *ref = term_new(&opt); + assert(ref != NULL); + for (int i = 0; i < 2000; i++) + tt_write(ref, concurrent_stream, strlen(concurrent_stream)); + assert(tt_cols(t) == tt_cols(ref) && tt_rows(t) == tt_rows(ref)); + assert(tt_history(t) == tt_history(ref)); + assert(tt_cursor_x(t) == tt_cursor_x(ref) && tt_cursor_y(t) == tt_cursor_y(ref)); + char *got = malloc((size_t)tt_cols(t) + 1); + char *want = malloc((size_t)tt_cols(ref) + 1); + assert(got && want); + for (int y = 0; y < tt_rows(t); y++) { + row_text(t, y, got); + row_text(ref, y, want); + assert(!strcmp(got, want)); + } + free(got); + free(want); + char *text = term_history_text(t, 1000); + char *text_ref = term_history_text(ref, 1000); + assert(text && text_ref && !strcmp(text, text_ref)); + term_text_free(text); + term_text_free(text_ref); + + term_free(ref); term_snapshot_free(snap); term_free(t); } +static void test_reserved_options_must_be_zero(void) +{ + struct term_options opt = { .size = { .x = 10, .y = 2 } }; + struct term *t = term_new(&opt); + assert(t != NULL); + term_free(t); + + size_t words = sizeof(opt.reserved) / sizeof(opt.reserved[0]); + for (size_t i = 0; i < words; i++) { + opt.reserved[i] = 1; + assert(term_new(&opt) == NULL); + opt.reserved[i] = 0; + } + opt.reserved[words - 1] = 0x80000000u; + assert(term_new(&opt) == NULL); + opt.reserved[words - 1] = 0; + t = term_new(&opt); + assert(t != NULL); + term_free(t); +} + +struct lock_probe { + struct term *t; + _Atomic bool entered; +}; + +static void *lock_probe_worker(void *arg) +{ + struct lock_probe *p = arg; + term_lock(p->t); + atomic_store(&p->entered, true); + term_unlock(p->t); + return NULL; +} + +/* term_lock() nests on the owning thread. Another thread waits until the + * last term_unlock(). */ +static void test_lock_reentrant_across_threads(void) +{ + struct term *t = term_new(&(struct term_options){ + .size = { .x = 80, .y = 24 }, + }); + assert(t != NULL); + + term_lock(t); + term_lock(t); + assert(tt_cols(t) == 80); + term_unlock(t); + + struct lock_probe p = { .t = t, .entered = false }; + pthread_t th; + assert(pthread_create(&th, NULL, lock_probe_worker, &p) == 0); + for (int i = 0; i < 1000; i++) + sched_yield(); + assert(!atomic_load(&p.entered)); + term_unlock(t); + + assert(pthread_join(th, NULL) == 0); + assert(atomic_load(&p.entered)); + term_free(t); +} + int main(void) { + test_reserved_options_must_be_zero(); test_concurrent_feed_and_snapshot(); + test_lock_reentrant_across_threads(); test_grow_past_reservation(); test_skipped_cells_read_blank(); test_csi_text_chunks(); @@ -1859,7 +2028,9 @@ int main(void) test_alt_shrink_then_flood(); test_alt_erase_scrollback(); test_snapshot_follows_new_terminal(); + test_snapshot_null_and_range(); test_bulk_rows_survive_edits(); + test_host_name_rejected(); assert(!strcmp(TERM_UNICODE_DATA_VERSION, "18.0.0")); assert(!strcmp(term_unicode_version(), TERM_UNICODE_DATA_VERSION)); |
