diff options
Diffstat (limited to 'tests')
| -rw-r--r-- | tests/correctness_probe.c | 13 | ||||
| -rw-r--r-- | tests/meson.build | 12 | ||||
| -rw-r--r-- | tests/resize_failure_test.c | 51 | ||||
| -rw-r--r-- | tests/snapshot_failure_test.c | 91 | ||||
| -rw-r--r-- | tests/snapshot_scroll_arena_test.c | 29 |
5 files changed, 193 insertions, 3 deletions
diff --git a/tests/correctness_probe.c b/tests/correctness_probe.c index 0b55211..a7c15df 100644 --- a/tests/correctness_probe.c +++ b/tests/correctness_probe.c @@ -26,6 +26,7 @@ void *__wrap_realloc(void *ptr, size_t size) int main(void) { + bool failed = false; struct term_options options = { .size = { 8, 3 }, .scrollback = 5 }; struct term *t = term_new(&options); struct term_snapshot *s = term_snapshot_new(); @@ -35,11 +36,14 @@ int main(void) fail_realloc = 1; bool ok = term_snapshot_update(s, t); uint32_t cps[16]; - int n = term_snapshot_cluster(s, term_snapshot_row(s, 0)[0].text, cps, 16); - printf("snapshot arena allocation failure: update=%d cluster_length=%d (expected false, or length 2)\n", ok, n); + const struct term_cell *row = term_snapshot_row(s, 0); + int n = row ? term_snapshot_cluster(s, row[0].text, cps, 16) : 0; + printf("snapshot arena allocation failure: update=%d cluster_length=%d (expected false and empty)\n", ok, n); + failed |= ok || term_snapshot_view(s)->size.x != 0 || term_snapshot_view(s)->size.y != 0; assert(term_snapshot_update(s, t)); n = term_snapshot_cluster(s, term_snapshot_row(s, 0)[0].text, cps, 16); printf("retry after allocation recovers: cluster_length=%d (expected 2)\n", n); + failed |= n != 2 || cps[0] != 'e' || cps[1] != 0x301; term_snapshot_free(s); term_free(t); @@ -55,6 +59,7 @@ int main(void) printf("after %d lines: arena_words=%u dead_words=%u history_rows=%d\n", i, s->arena_len, s->arena_dead, term_history_rows(t)); } + failed |= s->arena_len >= 8192 || term_history_rows(t) != 5; term_snapshot_free(s); term_free(t); @@ -74,7 +79,9 @@ int main(void) printf("after failed height shrink: resize=%d height=%d top=%c cursor_y=%d history=%d\n", ok, term_size(t).y, (char)term_snapshot_row(s, 0)[0].text, term_cursor(t).pos.y, term_history_rows(t)); + failed |= ok || term_size(t).y != 4 || term_snapshot_row(s, 0)[0].text != 'A' || + term_cursor(t).pos.y != 3 || term_history_rows(t) != 0; term_snapshot_free(s); term_free(t); - return 0; + return failed ? 1 : 0; } diff --git a/tests/meson.build b/tests/meson.build index 4cf559c..69f3c49 100644 --- a/tests/meson.build +++ b/tests/meson.build @@ -24,6 +24,7 @@ term_tests = [ ['gstore-test', files('gstore_test.c'), [], []], ['hist-test', files('hist_test.c'), [], []], ['history-text-test', files('history_text_test.c'), [thread_dep], []], + ['snapshot-scroll-arena-test', files('snapshot_scroll_arena_test.c'), [], []], ['view-test', files('view_test.c'), [], []], ['resize-property-test', files('resize_property_test.c'), [], []], ['term-resize-failure-test', files('resize_failure_test.c'), [dl_dep], ['extlib']], @@ -105,6 +106,17 @@ test('term-header-test', bash, args: [files('header_test.sh'), meson.project_source_root()], suite: 'unit', ) +if cc.has_link_argument('-Wl,--wrap=realloc') and cc.has_link_argument('-Wl,--wrap=malloc') + snapshot_failure_test = executable('snapshot-failure-test', 'snapshot_failure_test.c', + c_args: test_c_args, + link_args: ['-Wl,--wrap=realloc', '-Wl,--wrap=malloc'], + dependencies: [term_internal_dep], + ) + test('snapshot-failure-test', snapshot_failure_test, + env: test_env, + suite: 'unit', + ) +endif test('term-cross-test', bash, args: [files('cross_test.sh'), meson.project_source_root()], suite: 'portability', diff --git a/tests/resize_failure_test.c b/tests/resize_failure_test.c index 5d7e63c..fbfa38d 100644 --- a/tests/resize_failure_test.c +++ b/tests/resize_failure_test.c @@ -146,6 +146,57 @@ int main(void) assert(tt_cols(term) == 10); term_free(term); + /* A failed same-width height shrink must not scroll rows into history or + * move the cursor. The same resize must succeed when allocation recovers. */ + term = tt_new(8, 4); + assert(term); + const char four_rows[] = "A\r\nB\r\nC\r\nD"; + tt_write(term, four_rows, sizeof four_rows - 1); + assert(tt_cursor_y(term) == 3); + assert(tt_history(term) == 0); + + fail_next_calloc = true; + resized = tt_resize(term, 8, 2); + assert(!fail_next_calloc); + assert(!resized); + assert(tt_cols(term) == 8 && tt_rows(term) == 4); + assert(tt_cell(term, 0, 0)->text == 'A'); + assert(tt_cell(term, 0, 1)->text == 'B'); + assert(tt_cell(term, 0, 2)->text == 'C'); + assert(tt_cell(term, 0, 3)->text == 'D'); + assert(tt_cursor_y(term) == 3); + assert(tt_history(term) == 0); + + resized = tt_resize(term, 8, 2); + assert(resized); + assert(tt_cols(term) == 8 && tt_rows(term) == 2); + assert(tt_cell(term, 0, 0)->text == 'C'); + assert(tt_cell(term, 0, 1)->text == 'D'); + assert(tt_cursor_y(term) == 1); + assert(tt_history(term) == 2); + term_free(term); + + /* The alternate screen uses the same allocation-before-scroll path. */ + term = tt_new(8, 4); + assert(term); + tt_write(term, "\033[?1049h", 8); + tt_write(term, four_rows, sizeof four_rows - 1); + assert(term_modes(term).alternate); + assert(tt_cursor_y(term) == 3); + fail_next_calloc = true; + resized = tt_resize(term, 8, 2); + assert(!fail_next_calloc); + assert(!resized); + assert(tt_rows(term) == 4 && tt_cursor_y(term) == 3); + assert(tt_cell(term, 0, 0)->text == 'A'); + assert(tt_cell(term, 0, 3)->text == 'D'); + resized = tt_resize(term, 8, 2); + assert(resized); + assert(tt_rows(term) == 2 && tt_cursor_y(term) == 1); + assert(tt_cell(term, 0, 0)->text == 'C'); + assert(tt_cell(term, 0, 1)->text == 'D'); + term_free(term); + puts("lazy screen allocation failures preserve observable terminal state"); return 0; #endif diff --git a/tests/snapshot_failure_test.c b/tests/snapshot_failure_test.c new file mode 100644 index 0000000..4133bfd --- /dev/null +++ b/tests/snapshot_failure_test.c @@ -0,0 +1,91 @@ +#include "term/term.h" + +#include <assert.h> +#include <stdbool.h> +#include <stddef.h> +#include <stdlib.h> + +static bool fail_next_realloc; +static int malloc_failure_countdown = -1; +static bool malloc_was_failed; +void *__real_malloc(size_t); +void *__wrap_malloc(size_t size) +{ + if (malloc_failure_countdown >= 0) { + if (malloc_failure_countdown-- == 0) { + malloc_failure_countdown = -1; + malloc_was_failed = true; + return NULL; + } + } + return __real_malloc(size); +} + +void *__real_realloc(void *, size_t); +void *__wrap_realloc(void *p, size_t size) +{ + if (fail_next_realloc) { + fail_next_realloc = false; + return NULL; + } + return __real_realloc(p, size); +} + +int main(void) +{ + struct term_options options = { .size = { 8, 3 }, .scrollback = 5 }; + struct term *term = term_new(&options); + struct term_snapshot *snapshot = term_snapshot_new(); + assert(term && snapshot && term_snapshot_update(snapshot, term)); + + const char accented[] = "e\xcc\x81"; + term_feed(term, accented, sizeof(accented) - 1, 0); + fail_next_realloc = true; + assert(!term_snapshot_update(snapshot, term)); + assert(!fail_next_realloc); + assert(term_snapshot_view(snapshot)->size.x == 0); + assert(term_snapshot_view(snapshot)->size.y == 0); + assert(term_snapshot_row(snapshot, 0) == NULL); + + assert(term_snapshot_update(snapshot, term)); + const struct term_cell *row = term_snapshot_row(snapshot, 0); + assert(row); + uint32_t cps[4]; + assert(term_snapshot_cluster(snapshot, row[0].text, cps, 4) == 2); + assert(cps[0] == 'e' && cps[1] == 0x301); + + term_snapshot_free(snapshot); + term_free(term); + + /* More than 256 rows moves both temporary arrays off the stack. */ + options.size.y = 300; + term = term_new(&options); + snapshot = term_snapshot_new(); + assert(term && snapshot && term_snapshot_update(snapshot, term)); + + /* Fail new_keys allocation, then make sure retry fills all rows. */ + malloc_was_failed = false; + malloc_failure_countdown = 0; + assert(!term_snapshot_update(snapshot, term)); + assert(malloc_was_failed); + assert(term_snapshot_view(snapshot)->size.y == 0); + assert(term_snapshot_row(snapshot, 0) == NULL); + assert(term_snapshot_update(snapshot, term)); + assert(term_snapshot_view(snapshot)->size.y == 300); + assert(term_snapshot_row(snapshot, 299)); + + /* The first malloc is new_keys; fail the following selection-span array. */ + malloc_was_failed = false; + malloc_failure_countdown = 1; + assert(!term_snapshot_update(snapshot, term)); + assert(malloc_was_failed); + assert(term_snapshot_view(snapshot)->size.y == 0); + assert(term_snapshot_row(snapshot, 0) == NULL); + assert(term_snapshot_update(snapshot, term)); + assert(term_snapshot_view(snapshot)->size.y == 300); + assert(term_snapshot_row(snapshot, 299)); + + term_snapshot_free(snapshot); + term_free(term); + return 0; +} diff --git a/tests/snapshot_scroll_arena_test.c b/tests/snapshot_scroll_arena_test.c new file mode 100644 index 0000000..7da6507 --- /dev/null +++ b/tests/snapshot_scroll_arena_test.c @@ -0,0 +1,29 @@ +/* Include snapshot.c to inspect its private arena accounting. The static + * library supplies the terminal implementation; this translation unit's + * snapshot symbols satisfy the test's references before the archive is read. + */ +#include <assert.h> +#include "../src/snapshot.c" + +int main(void) +{ + struct term_options options = { .size = { 8, 24 }, .scrollback = 5 }; + struct term *t = term_new(&options); + struct term_snapshot *s = term_snapshot_new(); + assert(t && s && term_snapshot_update(s, t)); + + const char line[] = "e\xcc\x81\r\n"; + for (int i = 0; i < 10000; i++) { + term_feed(t, line, sizeof(line) - 1, 0); + assert(term_snapshot_update(s, t)); + } + + /* A fixed viewport holds fewer than 24 * 8 cluster records at once. + * Dead records are compacted before they can dominate this arena. */ + assert(term_history_rows(t) == 5); + assert(s->arena_len < 8192); + + term_snapshot_free(s); + term_free(t); + return 0; +} |
