Download
88 lines 2.5 KiBGoModified 2026-10-31 raw
12345678910111213141516171819202122232425262728293031323334353637383940414243444546474849505152535455565758596061626364656667686970717273747576777879808182838485868788
package tui

import (
	"strings"
	"testing"
	"unicode/utf8"
)

var sanitizeSeeds = []string{
	"plain\ntext",
	"visible\x1b]0;x rest-of-output-hidden\nline2\nline3",
	"\x1b[31mred\x1b[0m \x1b[2J\x1b[H",
	"\x1b]8;;https://e.example\x1b\\link\x1b]8;;\x1b\\",
	"\x1bP+q\x1b\\ \x1b_apc\x07 \x1b^pm\x1b\\",
	"esc\x1b\nnext",
	"c1\u009b31m \u009d0;t\u009c",
	"\x9b\xff invalid",
	"bidi\u202eevil\u2066x\u200f",
	"tag\U000e0041\U000e0042",
	"a\r\nb\rc\t",
}

// Sanitized text holds no control characters but newlines, no escape
// sequences, no bidi controls, and is valid UTF-8.
func checkSanitized(t *testing.T, in, out string) {
	t.Helper()
	if !utf8.ValidString(out) {
		t.Fatalf("Sanitize(%q) = %q: invalid UTF-8", in, out)
	}
	for _, r := range out {
		if r != '\n' && (r < 0x20 || r == 0x7f || r >= 0x80 && r < 0xa0) ||
			r >= '\u202a' && r <= '\u202e' || r >= '\u2066' && r <= '\u2069' || r == '\u200e' || r == '\u200f' ||
			r >= '\U000e0000' && r <= '\U000e007f' {
			t.Fatalf("Sanitize(%q) = %q: holds %U", in, out, r)
		}
	}
}

// Sanitizing is line by line: no sequence, finished or not, reaches past
// the end of its line, so nothing can take later lines out of sight.
func FuzzSanitize(f *testing.F) {
	for _, s := range sanitizeSeeds {
		f.Add(s)
	}
	f.Fuzz(func(t *testing.T, s string) {
		out := Sanitize(s)
		checkSanitized(t, s, out)
		lines := strings.Split(s, "\n")
		for i, l := range lines {
			lines[i] = Sanitize(l)
		}
		if want := strings.Join(lines, "\n"); out != want {
			t.Fatalf("Sanitize(%q) = %q, but line by line %q", s, out, want)
		}
	})
}

// SanitizeColors keeps only color and erase-line sequences; the rest is
// as Sanitize leaves it.
func FuzzSanitizeColors(f *testing.F) {
	for _, s := range append(sanitizeSeeds, "\x1b]8;;\x1b[31m\ax", "\x1b[38;2;1;2;3mrgb\x1b[K\x1b[0K\x1b[1K") {
		f.Add(s)
	}
	f.Fuzz(func(t *testing.T, s string) {
		out := SanitizeColors(s)
		var text strings.Builder
		for rest := out; ; {
			i := strings.IndexByte(rest, 0x1b)
			if i < 0 {
				text.WriteString(rest)
				break
			}
			text.WriteString(rest[:i])
			j := i + 2
			if j > len(rest) || rest[i+1] != '[' {
				t.Fatalf("SanitizeColors(%q) = %q: a bare ESC", s, out)
			}
			for j < len(rest) && (rest[j] >= '0' && rest[j] <= '9' || rest[j] == ';' || rest[j] == ':') {
				j++
			}
			if j >= len(rest) || rest[j] != 'm' && !(rest[j] == 'K' && (j == i+2 || rest[i+2:j] == "0")) {
				t.Fatalf("SanitizeColors(%q) = %q: keeps a sequence that is not a color", s, out)
			}
			rest = rest[j+1:]
		}
		checkSanitized(t, s, text.String())
	})
}