#define _GNU_SOURCE #include "term_api.h" #include #include #include #include #include #include #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 }