summaryrefslogtreecommitdiff
path: root/tests/resize_failure_test.c
diff options
context:
space:
mode:
authorBerke Güzel <wenekar1@gmail.com>2026-10-12 17:49:34 +0300
committerBerke Güzel <wenekar1@gmail.com>2026-10-12 17:49:34 +0300
commit4384435941b8c7658856b381d071aee2e0035e89 (patch)
tree5ece99c3dd1decb8ec3fd404c0ece2b5865a552a /tests/resize_failure_test.c
term: move the term core to subprojects/term
The core now builds, tests, and benchmarks alone, as a Meson subproject with no dependency on the main tree. - include/term/ holds term.h and the header-only unicode/. src/ holds the core and its private headers. The main tree gets only include/term through term_dep, so it cannot reach a private header. - The term tests, the term benchmarks, the Kitty parser suite, and tools/amalgamate.py and tools/generate_unicode.py move with it. The tests run as term:<name>. - The main build passes its -march (arch_flag) and b_ndebug to the subproject. A directory configured before this commit needs meson configure -Dterm:arch_flag=-march=... once. - The term tests compile with -UNDEBUG. They do work inside assert. - The subproject exports term_test_dep with term_api.h, for the main tree's render, thumbnail, color, and selection tests. - The Kitty bridge used struct vector2i and has not compiled since the public API redesign. It builds again. 2 of its 14 tests fail: REP after a tab, and a cursor report after a resize. - dirgraph.py drops the term and unicode layers. Its self-test uses pty. - The symbols test ignores the __odr_asan symbols that ASan adds. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Diffstat (limited to 'tests/resize_failure_test.c')
-rw-r--r--tests/resize_failure_test.c152
1 files changed, 152 insertions, 0 deletions
diff --git a/tests/resize_failure_test.c b/tests/resize_failure_test.c
new file mode 100644
index 0000000..5d7e63c
--- /dev/null
+++ b/tests/resize_failure_test.c
@@ -0,0 +1,152 @@
+#define _GNU_SOURCE
+#include "term_api.h"
+
+#include <assert.h>
+#include <dlfcn.h>
+#include <stdbool.h>
+#include <stddef.h>
+#include <stdio.h>
+#include <stdlib.h>
+
+#ifndef __has_feature
+#define __has_feature(x) 0
+#endif
+
+#if defined(__SANITIZE_ADDRESS__) || defined(__SANITIZE_THREAD__)
+#define SANITIZED 1
+#elif __has_feature(address_sanitizer) || __has_feature(memory_sanitizer) || \
+ __has_feature(thread_sanitizer)
+#define SANITIZED 1
+#endif
+
+#ifndef SANITIZED
+static bool fail_next_calloc;
+static bool fail_reallocs;
+
+void *calloc(size_t n, size_t size)
+{
+ static void *(*real_calloc)(size_t, size_t);
+ if (!real_calloc)
+ real_calloc = (void *(*)(size_t, size_t))dlsym(RTLD_NEXT, "calloc");
+ if (fail_next_calloc) {
+ fail_next_calloc = false;
+ return NULL;
+ }
+ return real_calloc(n, size);
+}
+
+void *realloc(void *p, size_t size)
+{
+ static void *(*real_realloc)(void *, size_t);
+ if (!real_realloc)
+ real_realloc = (void *(*)(void *, size_t))dlsym(RTLD_NEXT, "realloc");
+ if (fail_reallocs)
+ return NULL;
+ return real_realloc(p, size);
+}
+#endif
+
+int main(void)
+{
+#ifdef SANITIZED
+ puts("resize failure test skipped under sanitizer");
+ return 0;
+#else
+ struct term *term = tt_new(10, 2);
+ assert(term);
+ tt_write(term, "abc", 3);
+
+ fail_next_calloc = true;
+ bool resized = tt_resize(term, 20, 2);
+ /* The resize must reach the failing allocation. */
+ assert(!fail_next_calloc);
+
+ /* A failed resize must leave the old terminal geometry usable as a unit. */
+ assert(!resized);
+ assert(tt_cols(term) == 10);
+ assert(tt_rows(term) == 2);
+ assert(tt_cell(term, 0, 0)->text == 'a');
+ assert(tt_cell(term, 2, 0)->text == 'c');
+
+ tt_write(term, "\r12345678901", 12);
+ assert(tt_cursor_x(term) == 1);
+ assert(tt_cursor_y(term) == 1);
+ assert(tt_cell(term, 0, 1)->text == '1');
+
+ /* The failed lazy allocation must not prevent a later resize retry. */
+ resized = tt_resize(term, 20, 2);
+ assert(resized);
+ assert(tt_cols(term) == 20);
+ assert(tt_rows(term) == 2);
+ assert(tt_cell(term, 0, 0)->text == '1');
+ term_free(term);
+
+ term = tt_new(10, 3);
+ assert(term);
+ const char margins[] = "\033[?69h\033[1;5s\033[H";
+ tt_write(term, margins, sizeof(margins) - 1);
+ fail_next_calloc = true;
+ resized = tt_resize(term, 20, 3);
+ assert(!fail_next_calloc);
+
+ /* A failed resize keeps the margins that fit the old width. */
+ assert(!resized);
+ assert(tt_cols(term) == 10);
+ tt_write(term, "abcdefg", 7);
+ assert(tt_cell(term, 4, 0)->text == 'e');
+ assert(tt_cell(term, 5, 0)->text != 'f');
+ assert(tt_cell(term, 0, 1)->text == 'f');
+ term_free(term);
+
+ term = tt_new(10, 2);
+ assert(term);
+ tt_write(term, "normal", 6);
+ fail_next_calloc = true;
+ tt_write(term, "\033[?1049h", 8);
+ assert(!fail_next_calloc);
+
+ /* A failed alternate-screen allocation keeps the normal screen active. */
+ assert(!term_modes(term).alternate);
+ assert(tt_cell(term, 0, 0)->text == 'n');
+ assert(tt_cell(term, 5, 0)->text == 'l');
+ tt_write(term, "\033[?1049l", 8);
+ assert(tt_cell(term, 0, 0)->text == 'n');
+ term_free(term);
+
+ /* A reflow that cannot grow its row buffers fails before it changes the
+ * history, so the old geometry and text survive the failed resize. The
+ * first resize sizes the buffers, so only the reflow's growth fails. */
+ term = tt_new(20, 40);
+ assert(term);
+ for (int i = 0; i < 40; i++) {
+ char line[32];
+ int n = snprintf(line, sizeof line, "row %02d 0123456789\r\n", i);
+ tt_write(term, line, (size_t)n);
+ }
+ resized = tt_resize(term, 21, 40);
+ assert(resized);
+ int history = tt_history(term);
+ char *before = tt_history_text(term, 1000);
+ assert(before);
+
+ fail_reallocs = true;
+ resized = tt_resize(term, 10, 40);
+ fail_reallocs = false;
+ assert(!resized);
+ assert(tt_cols(term) == 21);
+ assert(tt_rows(term) == 40);
+ assert(tt_history(term) == history);
+ char *after = tt_history_text(term, 1000);
+ assert(after && !strcmp(before, after));
+ free(before);
+ free(after);
+
+ resized = tt_resize(term, 10, 40);
+ assert(resized);
+ assert(tt_cols(term) == 10);
+ term_free(term);
+
+ puts("lazy screen allocation failures preserve observable terminal state");
+ return 0;
+#endif
+}