summaryrefslogtreecommitdiff
path: root/tests/fuzz_gen.h
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/fuzz_gen.h
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/fuzz_gen.h')
-rw-r--r--tests/fuzz_gen.h188
1 files changed, 188 insertions, 0 deletions
diff --git a/tests/fuzz_gen.h b/tests/fuzz_gen.h
new file mode 100644
index 0000000..166d729
--- /dev/null
+++ b/tests/fuzz_gen.h
@@ -0,0 +1,188 @@
+#ifndef WSAP_TERM_FUZZ_GEN_H
+#define WSAP_TERM_FUZZ_GEN_H
+
+/*
+ * The input generator that write-fuzz-test and model-test share. gen()
+ * makes whole sequences, malformed UTF-8, and raw bytes. The model handles
+ * every byte that gen() makes.
+ */
+
+#include <stdarg.h>
+#include <stdint.h>
+#include <stdio.h>
+#include <string.h>
+
+static uint64_t rng;
+
+static uint32_t rnd(void)
+{
+ rng ^= rng << 13;
+ rng ^= rng >> 7;
+ rng ^= rng << 17;
+ return (uint32_t)rng;
+}
+
+static int rn(int n)
+{
+ return (int)(rnd() % (uint32_t)n);
+}
+
+static char buf[1 << 16];
+static size_t blen;
+
+static void putb(char c)
+{
+ if (blen + 1 < sizeof(buf))
+ buf[blen++] = c;
+}
+
+static void put(const char *s)
+{
+ size_t n = strlen(s);
+ if (blen + n < sizeof(buf)) {
+ memcpy(buf + blen, s, n);
+ blen += n;
+ }
+}
+
+static void __attribute__((format(printf, 1, 2))) putf(const char *fmt, ...)
+{
+ char tmp[64];
+ va_list args;
+ va_start(args, fmt);
+ vsnprintf(tmp, sizeof(tmp), fmt, args);
+ va_end(args);
+ put(tmp);
+}
+
+#define PICK(list) (list)[rn((int)(sizeof(list) / sizeof((list)[0])))]
+
+static void gen_sgr(void)
+{
+ static const int plain[] = {
+ 0, 1, 2, 3, 4, 5, 7, 8, 9, 21, 22, 23, 24, 27, 29, 39, 49, 53, 55, 59,
+ };
+ put("\033[");
+ int n = 1 + rn(3);
+ for (int i = 0; i < n; i++) {
+ if (i) put(";");
+ switch (rn(9)) {
+ case 0: putf("38;5;%d", rn(300)); break;
+ case 1: putf("48;5;%d", rn(300)); break;
+ case 2: putf("38;2;%d;%d;%d", rn(256), rn(256), rn(256)); break;
+ case 3: putf("48:2::%d:%d:%d", rn(256), rn(256), rn(256)); break;
+ case 4: putf("4:%d", rn(7)); break;
+ case 5: putf("%d", 30 + rn(8) + (rn(2) ? 60 : 0)); break;
+ case 6: putf("%d", 40 + rn(8) + (rn(2) ? 60 : 0)); break;
+ case 7: putf("58;5;%d", rn(256)); break;
+ default: putf("%d", PICK(plain)); break;
+ }
+ }
+ put("m");
+}
+
+static inline void gen(int ops, int cols, int rows)
+{
+ static const char *words[] = {
+ "a", "hello", "x y", "0123456789", "日本", "é", "中文字",
+ "e\xcc\x81", "👍", " ",
+ };
+ /* Include valid text, malformed UTF-8, and incomplete UTF-8. */
+ static const char *utf8_3[] = {
+ "中", "文", "字", "€", "ア", "\xe2\x83\x90", "\xed\xa0\x80",
+ "\xe0\x80\xaf", "\xef\xbf\xbd", "한", "a", "\xe2\x80\x8d", "\xe3\x80",
+ };
+ /* Emoji, flags, modifiers, ZWJ sequences, presentation selectors,
+ * stacked marks, and C1 controls in UTF-8 form. */
+ static const char *clusters[] = {
+ "😀", "\xf0\x9f\x87\xa9\xf0\x9f\x87\xaa", "👍🏽", "👨‍👩‍👧", "❤️", "☺︎",
+ "a\xcc\x81\xcc\x82", "\xe2\x80\x8b", "\xc2\x85", "\xc2\x84", "\xc2\x8d",
+ "\xc2\x88", "\xf0\x9f\x87\xa9", "\xcc\x81",
+ };
+ static const char *modes[] = {
+ "\033[4h", "\033[4l", "\033[?7l", "\033[?7h",
+ "\033[?1049h", "\033[?1049l", "\033[?6h", "\033[?6l",
+ "\033[20h", "\033[20l", "\033[?25l", "\033[?25h",
+ "\033[?5h", "\033[?5l",
+ };
+ static const char *escs[] = {
+ "\033D", "\033M", "\033E", "\0337", "\0338", "\033[s", "\033[u", "\033c",
+ "\033[?1048h", "\033[?1048l", "\033H", "\033=", "\033>", "\033%G",
+ };
+ static const char *charsets[] = {
+ "\033(0", "\033(B", "\033)0", "\033)B", "\x0e", "\x0f",
+ };
+ static const char *queries[] = {
+ "\033[5n", "\033[6n", "\033[c", "\033[0c", "\033[>c", "\033[1c",
+ "\033[14t", "\033P$qm\033\\", "\033P$qr\033\\", "\033[?6$p",
+ "\033[4$p", "\033[20$p",
+ };
+ /* C0 controls inside CSI, cancelled sequences, and ignored strings. */
+ static const char *odd[] = {
+ "\033[2\n;3H", "\033[1\bC", "\033[3\x18m", "\033[1\x1a;2H",
+ "\033[\1772C", "\x0b", "\x0c", "\x7f", "\x18", "\x1a",
+ "\033]0;title\007", "\033]2;x\033\\", "\033_apc\033\\", "\033^pm\007",
+ "\033X\033\\", "\033]133;A\007", "\033]133;A\033\\", "\033]133;D\007",
+ "\033#8",
+ };
+ static const char csi_final[] = "ABCDEFGHIJKLMPSTXZ@`abdefghlmnrsu";
+
+ for (int i = 0; i < ops; i++) {
+ switch (rn(36)) {
+ case 0: case 1: case 2: put(PICK(words)); break;
+ case 3: {
+ int n = rn(cols * 2 + 2);
+ for (int k = 0; k < n; k++)
+ putb((char)(0x21 + rn(94)));
+ break;
+ }
+ case 4: put("\r\n"); break;
+ case 5: put("\n"); break;
+ case 6: put("\r"); break;
+ case 7: put("\t"); break;
+ case 8: put("\b"); break;
+ case 9: putf("\033[%d;%dH", rn(rows + 2), rn(cols + 2)); break;
+ case 10: putf("\033[%d%c", rn(4), "JK"[rn(2)]); break;
+ case 11: putf("\033[%d%c", rn(cols), "XP@LMST"[rn(7)]); break;
+ case 12: putf("\033[%d;%dm", 30 + rn(8), 40 + rn(8)); break;
+ case 13: put(rn(2) ? "\033[m" : "\033[0m"); break;
+ case 14: putf("\033[48;2;%d;%d;1m", rn(256), rn(256)); break;
+ case 15: putf("\033[%d%c", rn(cols + 2), csi_final[rn((int)sizeof(csi_final) - 1)]); break;
+ case 16: putf("\033[%d;%dr", rn(rows) + 1, rn(rows) + 1); break;
+ case 17: put(PICK(modes)); break;
+ case 18: put(PICK(escs)); break;
+ case 19: putf("\033[%d%c", rn(5), "IZb"[rn(3)]); break;
+ case 20: putf("\033[%d;%d;1;1$r", rn(rows) + 1, rn(cols) + 1); break;
+ case 21: put("\033[1;7m"); break;
+ case 22: case 23: {
+ /* Keep ESC out of raw bytes so later input reaches the fast paths. */
+ int n = 64 + rn(400);
+ for (int k = 0; k < n; k++) {
+ unsigned char b = (unsigned char)rnd();
+ putb((char)(b == 0x1b ? 'e' : b));
+ }
+ break;
+ }
+ case 24: case 25: {
+ int n = 4 + rn(40);
+ for (int k = 0; k < n; k++)
+ put(utf8_3[rn(20) < 14 ? rn(3) : rn(13)]);
+ break;
+ }
+ case 26: case 27: gen_sgr(); break;
+ case 28: put(PICK(clusters)); break;
+ case 29: put(PICK(charsets)); break;
+ case 30: put(PICK(queries)); break;
+ case 31: put(PICK(odd)); break;
+ case 32: putf("\033[%d q", rn(7)); break;
+ case 33: putf("\033[%dg", rn(4)); break;
+ case 34:
+ putf("\033[%d;%d;%d;%d;%d$r", rn(rows) + 1, rn(cols) + 1,
+ rn(rows) + 1, rn(cols) + 1, PICK(((const int[]){ 0, 1, 4, 7, 22, 24, 27, 31, 42 })));
+ break;
+ case 35: putf("\033[%d*x", rn(3)); break;
+ }
+ }
+}
+
+#endif