diff options
| author | Berke Güzel <wenekar1@gmail.com> | 2026-10-12 17:49:34 +0300 |
|---|---|---|
| committer | Berke Güzel <wenekar1@gmail.com> | 2026-10-12 17:49:34 +0300 |
| commit | 4384435941b8c7658856b381d071aee2e0035e89 (patch) | |
| tree | 5ece99c3dd1decb8ec3fd404c0ece2b5865a552a /tests/dcs_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/dcs_test.c')
| -rw-r--r-- | tests/dcs_test.c | 475 |
1 files changed, 475 insertions, 0 deletions
diff --git a/tests/dcs_test.c b/tests/dcs_test.c new file mode 100644 index 0000000..0e67f78 --- /dev/null +++ b/tests/dcs_test.c @@ -0,0 +1,475 @@ +#include "term_api.h" +#include <assert.h> +#include <stdbool.h> +#include <stdio.h> +#include <string.h> + +static char response[16384]; +static size_t response_len; +static void respond(void *ud, const char *data, size_t len) +{ + (void)ud; + if (len >= sizeof(response)) len = sizeof(response) - 1; + memcpy(response, data, len); + response[len] = 0; + response_len = len; +} +static void write_seq(struct term *t, const char *s) +{ + for (; *s; s++) tt_write(t, s, 1); +} +static const char *row_text(struct term *t, int r, char *buf) +{ + int n = 0; + for (int c = 0; c < tt_cols(t); c++) + buf[n++] = (char)tt_view_cell(t, c, r)->text; + while (n > 0 && buf[n - 1] == ' ') n--; + buf[n] = 0; + return buf; +} +static bool row_is(struct term *t, int r, const char *want) +{ + char got[TERM_MAX_COLS + 1]; + if (strcmp(row_text(t, r, got), want)) { + fprintf(stderr, "row %d: got \"%s\" want \"%s\"\n", r, got, want); + return false; + } + return true; +} +static struct term *new_term(int cols, int rows) +{ + struct term *t = tt_new_opts(cols, rows, &(struct term_callbacks){ .reply = respond }); + assert(t); + response_len = 0; + response[0] = 0; + return t; +} +static void expect_response(const char *want) +{ + if (response_len != strlen(want) || memcmp(response, want, response_len)) { + fprintf(stderr, "response: got len %zu \"%s\" want \"%s\"\n", + response_len, response, want); + assert(!"response mismatch"); + } +} +static void expect_no_response(void) +{ + assert(response_len == 0); +} + +static void hex_encode(char *out, const char *s) +{ + for (; *s; s++) { + snprintf(out, 3, "%02x", (unsigned char)*s); + out += 2; + } + *out = 0; +} + +static void assert_rqss(struct term *t, const char *spec, const char *payload) +{ + char req[128], want[256]; + snprintf(req, sizeof(req), "\033P$q%s\033\\", spec); + snprintf(want, sizeof(want), "\033P1$r%s\033\\", payload); + response_len = 0; + write_seq(t, req); + expect_response(want); +} +static void assert_rqss_invalid(struct term *t, const char *spec) +{ + char req[128]; + snprintf(req, sizeof(req), "\033P$q%s\033\\", spec); + response_len = 0; + write_seq(t, req); + expect_response("\033P0$r\033\\"); +} + +static void test_decrqss_sgr(void) +{ + struct term *t = new_term(80, 24); + assert_rqss(t, "m", "m"); + write_seq(t, "\033[1;31m"); + assert_rqss(t, "m", "0;1;31m"); + term_free(t); + + t = new_term(80, 24); + write_seq(t, "\033[4:3m"); + assert_rqss(t, "m", "0;4:3m"); + term_free(t); + + t = new_term(80, 24); + write_seq(t, "\033[38;2;10;20;30m"); + assert_rqss(t, "m", "0;38:2:10:20:30m"); + term_free(t); +} + +static void test_decrqss_margins_and_page(void) +{ + struct term *t = new_term(80, 24); + assert_rqss(t, "r", "1;24r"); + assert_rqss(t, "t", "24t"); + assert_rqss(t, "$|", "80$|"); + assert_rqss(t, "*|", "24*|"); + write_seq(t, "\033[3;10r"); + assert_rqss(t, "r", "3;10r"); + term_free(t); + + t = new_term(80, 24); + assert_rqss(t, "s", "1;80s"); + write_seq(t, "\033[?69h\033[5;20s"); + assert_rqss(t, "s", "5;20s"); + term_free(t); +} + +static void test_decrqss_cursor_style(void) +{ + struct term *t = new_term(80, 24); + assert_rqss(t, " q", "1 q"); + write_seq(t, "\033[5 q"); + assert_rqss(t, " q", "5 q"); + write_seq(t, "\033[4 q"); + assert_rqss(t, " q", "4 q"); + term_free(t); +} + +static void test_decrqss_misc(void) +{ + struct term *t = new_term(80, 24); + assert_rqss(t, "\"p", "64;1\"p"); + assert_rqss(t, "\"q", "0\"q"); + assert_rqss(t, "$}", "0$}"); + assert_rqss(t, "$~", "0$~"); + assert_rqss(t, "*x", "0*x"); + write_seq(t, "\033[2*x"); + assert_rqss(t, "*x", "2*x"); + term_free(t); +} + +static void test_decrqss_invalid(void) +{ + struct term *t = new_term(80, 24); + static const char *bad[] = { + "){", ",|", ",}", ">0f", ">0m", ">0t", "z", "ABC", + }; + for (size_t i = 0; i < sizeof(bad) / sizeof(bad[0]); i++) + assert_rqss_invalid(t, bad[i]); + + response_len = 0; + write_seq(t, "\033P$x\033\\"); + expect_no_response(); + term_free(t); +} + +static void assert_tcap(struct term *t, const char *name, const char *value) +{ + char hn[128], hv[512], req[256], want[1024]; + hex_encode(hn, name); + hex_encode(hv, value); + snprintf(req, sizeof(req), "\033P+q%s\033\\", hn); + snprintf(want, sizeof(want), "\033P1+r%s=%s\033\\", hn, hv); + response_len = 0; + write_seq(t, req); + expect_response(want); +} + +static void test_xtgettcap(void) +{ + struct term *t = new_term(80, 24); + assert_tcap(t, "kind", "\033[1;2B"); + assert_tcap(t, "TN", "wsap_term"); + assert_tcap(t, "name", "wsap_term"); + assert_tcap(t, "Co", "16777216"); + assert_tcap(t, "colors", "16777216"); + assert_tcap(t, "RGB", "8/8/8"); + assert_tcap(t, "kbs", "\177"); + assert_tcap(t, "kcbt", "\033[Z"); + assert_tcap(t, "kcuu1", "\033OA"); + assert_tcap(t, "kcud1", "\033OB"); + assert_tcap(t, "kcuf1", "\033OC"); + assert_tcap(t, "kcub1", "\033OD"); + assert_tcap(t, "khome", "\033OH"); + assert_tcap(t, "kend", "\033OF"); + assert_tcap(t, "kent", "\033OM"); + assert_tcap(t, "kich1", "\033[2~"); + assert_tcap(t, "kdch1", "\033[3~"); + assert_tcap(t, "kpp", "\033[5~"); + assert_tcap(t, "knp", "\033[6~"); + assert_tcap(t, "kf1", "\033OP"); + assert_tcap(t, "kf2", "\033OQ"); + assert_tcap(t, "kf3", "\033OR"); + assert_tcap(t, "kf4", "\033OS"); + assert_tcap(t, "kf5", "\033[15~"); + assert_tcap(t, "kf6", "\033[17~"); + assert_tcap(t, "kf7", "\033[18~"); + assert_tcap(t, "kf8", "\033[19~"); + assert_tcap(t, "kf9", "\033[20~"); + assert_tcap(t, "kf10", "\033[21~"); + assert_tcap(t, "kf11", "\033[23~"); + assert_tcap(t, "kf12", "\033[24~"); + assert_tcap(t, "kf13", "\033[1;2P"); + assert_tcap(t, "kf24", "\033[24;2~"); + assert_tcap(t, "kUP", "\033[1;2A"); + assert_tcap(t, "kDN", "\033[1;2B"); + assert_tcap(t, "kLFT", "\033[1;2D"); + assert_tcap(t, "kRIT", "\033[1;2C"); + assert_tcap(t, "kmous", "\033[<"); + assert_tcap(t, "kri", "\033[1;2A"); + assert_tcap(t, "kxIN", "\033[I"); + assert_tcap(t, "kxOUT", "\033[O"); + term_free(t); +} + +static void test_xtgettcap_multi(void) +{ + struct term *t = new_term(80, 24); + char hk[16], htn[16], hb[16], hv[32], req[256], want[1024]; + hex_encode(hk, "kind"); + hex_encode(htn, "TN"); + hex_encode(hb, "BOGUS"); + hex_encode(hv, "\033[1;2B"); + + snprintf(req, sizeof(req), "\033P+q%s;%s\033\\", hk, htn); + snprintf(want, sizeof(want), "\033P1+r%s=%s;%s=%s\033\\", + hk, hv, htn, "777361705f7465726d"); + response_len = 0; + write_seq(t, req); + expect_response(want); + + snprintf(req, sizeof(req), "\033P+q%s;%s;%s\033\\", hk, hb, htn); + snprintf(want, sizeof(want), "\033P1+r%s=%s;%s\033\\", hk, hv, hb); + response_len = 0; + write_seq(t, req); + expect_response(want); + assert(!strstr(response, htn)); + + snprintf(req, sizeof(req), "\033P+q%s\033\\", hb); + snprintf(want, sizeof(want), "\033P0+r%s\033\\", hb); + response_len = 0; + write_seq(t, req); + expect_response(want); + term_free(t); +} + +static void test_xtgettcap_malformed(void) +{ + struct term *t = new_term(80, 24); + static const char *bad[] = {"6b696", "zz", "6g", "6b69 6e"}; + for (size_t i = 0; i < sizeof(bad) / sizeof(bad[0]); i++) { + char req[128]; + snprintf(req, sizeof(req), "\033P+q%s\033\\", bad[i]); + response_len = 0; + write_seq(t, req); + assert(response_len > 4); + assert(!memcmp(response, "\033P0+r", 5)); + assert(response[response_len - 2] == '\033'); + assert(response[response_len - 1] == '\\'); + } + term_free(t); +} + +static void test_xtsettcap(void) +{ + struct term *t = new_term(80, 24); + char hn[32]; + hex_encode(hn, "myterm"); + char req[128]; + snprintf(req, sizeof(req), "\033P+p%s\033\\", hn); + response_len = 0; + write_seq(t, req); + expect_no_response(); + assert_tcap(t, "TN", "myterm"); + term_free(t); +} + +static void test_xtgetxres(void) +{ + struct term *t = new_term(80, 24); + char hn[64], req[128], want[256]; + hex_encode(hn, "allowTcapOps"); + snprintf(req, sizeof(req), "\033P+Q%s\033\\", hn); + snprintf(want, sizeof(want), "\033P0+R%s\033\\", hn); + response_len = 0; + write_seq(t, req); + expect_response(want); + term_free(t); +} + +static void test_decrsps_cir(void) +{ + struct term *t = new_term(80, 24); + write_seq(t, "\033P1$t3;5;1;I;@;H;00;@;BBBB\033\\"); + assert(tt_cursor_y(t) == 2 && tt_cursor_x(t) == 4); + + write_seq(t, "Z"); + const struct term_cell *cell = tt_cell(t, 4, 2); + assert(cell->text == 'Z'); + assert(cell->attrs & TERM_ATTR_BOLD); + assert(cell->fg == TERM_COLOR_BG); + term_free(t); + + t = new_term(80, 24); + write_seq(t, "\033P1$t3;5;1;@;@;H;00;@;BBBB\033\\"); + write_seq(t, "\033[1;80Hab"); + assert(tt_cell(t, 79, 0)->text == 'a'); + assert(tt_cell(t, 0, 1)->text == 'b'); + term_free(t); + + t = new_term(80, 24); + write_seq(t, "\033[5;15r"); + write_seq(t, "\033P1$t3;5;1;@;@;I;00;@;BBBB\033\\"); + write_seq(t, "\033[1;1H"); + assert(tt_cursor_y(t) == 4 && tt_cursor_x(t) == 0); + term_free(t); +} + +static void test_decrsps_tabsr(void) +{ + struct term *t = new_term(80, 24); + write_seq(t, "\033P2$t4/10\033\\"); + write_seq(t, "\033[1;1H"); + write_seq(t, "\t"); + assert(tt_cursor_x(t) == 3); + write_seq(t, "\t"); + assert(tt_cursor_x(t) == 9); + write_seq(t, "\t"); + assert(tt_cursor_x(t) == 79); + term_free(t); +} + +static void test_synchronized_output(void) +{ + struct term *t = new_term(80, 24); + write_seq(t, "\033P=1s\033\\"); + assert(term_modes(t).synchronized); + write_seq(t, "\033P=2s\033\\"); + assert(!term_modes(t).synchronized); + + write_seq(t, "\033P=10s\033\\"); + assert(!term_modes(t).synchronized); + write_seq(t, "\033P=1;s\033\\"); + assert(term_modes(t).synchronized); + write_seq(t, "\033P=xs\033\\"); + assert(term_modes(t).synchronized); + write_seq(t, "\033P=2;s\033\\"); + assert(!term_modes(t).synchronized); + term_free(t); +} + +static void test_ignored_dcs(void) +{ + static const char *seqs[] = { + "\033P0;1|17/0d\033\\", + "\033P0!u%5\033\\", + "\033P1!uA\033\\", + "\033P1;0;1;10;0;2;0;1{~\033\\", + "\033P1;0;0q#0~~\033\\", + "\033P1pS(E)\033\\", + "\033P?~junk\033\\", + }; + for (size_t i = 0; i < sizeof(seqs) / sizeof(seqs[0]); i++) { + struct term *t = new_term(80, 24); + write_seq(t, "A"); + write_seq(t, seqs[i]); + write_seq(t, "B"); + assert(row_is(t, 0, "AB")); + assert(tt_cursor_x(t) == 2 && tt_cursor_y(t) == 0); + expect_no_response(); + term_free(t); + } +} + +static void test_xtversion(void) +{ + struct term *t = new_term(80, 24); + response_len = 0; + write_seq(t, "\033[>0q"); + expect_response("\033P>|wsap_term\033\\"); + term_free(t); +} + +static void test_bel_terminator(void) +{ + struct term *t = new_term(80, 24); + response_len = 0; + write_seq(t, "\033P+q6b696e64\007"); + expect_response("\033P1+r6b696e64=1b5b313b3242\033\\"); + + response_len = 0; + write_seq(t, "\033P$qm\007"); + expect_response("\033P1$rm\033\\"); + term_free(t); +} + +/* A query far past the old 256-byte DCS buffer is answered in full, + * whether it arrives byte by byte or in one write. */ +static void test_dcs_long_query(void) +{ + enum { NAMES = 60 }; + char hk[16], hv[32]; + hex_encode(hk, "kind"); + hex_encode(hv, "\033[1;2B"); + static char req[4096], want[8192]; + int rn = snprintf(req, sizeof(req), "\033P+q"); + int wn = snprintf(want, sizeof(want), "\033P1+r"); + for (int i = 0; i < NAMES; i++) { + rn += snprintf(req + rn, sizeof(req) - (size_t)rn, "%s%s", i ? ";" : "", hk); + wn += snprintf(want + wn, sizeof(want) - (size_t)wn, "%s%s=%s", + i ? ";" : "", hk, hv); + } + snprintf(req + rn, sizeof(req) - (size_t)rn, "\033\\"); + snprintf(want + wn, sizeof(want) - (size_t)wn, "\033\\"); + assert(strlen(req) > 256); + + struct term *t = new_term(80, 24); + write_seq(t, req); + expect_response(want); + response_len = 0; + tt_write(t, req, strlen(req)); + expect_response(want); + write_seq(t, "X"); + assert(row_is(t, 0, "X")); + term_free(t); +} + +static void test_dcs_abort_c0(void) +{ + struct term *t = new_term(80, 24); + response_len = 0; + write_seq(t, "\033P+q6b69\nX"); + expect_no_response(); + assert(row_is(t, 1, "X")); + term_free(t); +} + +static void test_dcs_abort_esc(void) +{ + struct term *t = new_term(80, 24); + response_len = 0; + write_seq(t, "\033P$qm\033[5n"); + expect_response("\033[0n"); + term_free(t); +} + +int main(void) +{ + test_decrqss_sgr(); + test_decrqss_margins_and_page(); + test_decrqss_cursor_style(); + test_decrqss_misc(); + test_decrqss_invalid(); + test_xtgettcap(); + test_xtgettcap_multi(); + test_xtgettcap_malformed(); + test_xtsettcap(); + test_xtgetxres(); + test_decrsps_cir(); + test_decrsps_tabsr(); + test_synchronized_output(); + test_ignored_dcs(); + test_xtversion(); + test_bel_terminator(); + test_dcs_long_query(); + test_dcs_abort_c0(); + test_dcs_abort_esc(); + return 0; +} |
