github.com/keybase/client/go@v0.0.0-20240309051027-028f7c731f8b/terminalescaper/escaper_test.go (about) 1 package terminalescaper 2 3 import ( 4 "bytes" 5 "fmt" 6 "reflect" 7 "testing" 8 "unicode" 9 "unsafe" 10 ) 11 12 var tests = map[string]string{ 13 14 // The vt100 escape character \033 (i.e. \x1b) is substituted with '^', even as part of escape sequence 15 "\x1b": "^[", 16 "aaa\x1b[3Gbbb": "aaa^[[3Gbbb", 17 "a\033[12laa": "a^[[12laa", 18 // character movement 19 "aaa\033[2Db": "aaa^[[2Db", 20 "aaa\033[4D\033[2Cb": "aaa^[[4D^[[2Cb", 21 // color 22 "aaa \033[25;25mtest": "aaa ^[[25;25mtest", 23 "bbb \033]4;1;rgb:38/54/71\033\\test": "bbb ^[]4;1;rgb:38/54/71^[\\test", 24 "ccc \033]4;1;rgb:38/54/71test": "ccc ^[]4;1;rgb:38/54/71test", 25 26 // '\' and '/' are preserved 27 "bbb\\raaa": "bbb\\raaa", 28 "bbb/raaa": "bbb/raaa", 29 30 // newline and tab are preserved, even in combination with other escpae codes 31 "\n": "\n", 32 "\t": "\t", 33 "bbb\naaa": "bbb\naaa", 34 "bbb\taaa": "bbb\taaa", 35 "b\naaa\b\b\033[4P": "b\naaa^[[4P", 36 "x\naaa\b\b\033[2Ka": "x\naaa^[[2Ka", 37 38 // valid non ASCII characters 39 "⌘": "⌘", 40 "⌘a\n⌘": "⌘a\n⌘", 41 42 // backspace, carriage return and other similar special characters (except for \n, \t) are stripped out 43 "aaa\b\bb": "aaab", 44 "aaa\b\b\033[1K": "aaa^[[1K", 45 "bbb\raaa": "bbbaaa", // carriage return 46 47 // Colors are acceptable, including multiple in the same string, 48 "foo\x1b[30mbar": "foo\x1b[30mbar", 49 50 // But non-color escapes should be escaped properly 51 "fo\x1b[4Po\x1b[30mbarf\x1b[34moobarfo\x1b[0mobarfoobar\x1b312": "fo^[[4Po\x1b[30mbarf\x1b[34moobarfo\x1b[0mobarfoobar^[312", 52 53 // Edge-cases with colors 54 "foo\x1b[30mbar\x1b": "foo\x1b[30mbar^[", 55 "\x1bfoo\x1b[30mbar\x1b": "^[foo\x1b[30mbar^[", 56 "\x1bfoo\x1b[30mbar\x1b[36": "^[foo\x1b[30mbar^[[36", 57 "\x1bfoo\x1b[30mbar\x1b[36m": "^[foo\x1b[30mbar\x1b[36m", 58 } 59 60 func TestMain(t *testing.T) { 61 for a, b := range tests { 62 tmp := Clean(a) 63 if tmp != b { 64 t.Logf("Clean() failed: %#v -> %#v != %#v\n", a, tmp, b) 65 t.Fail() 66 } 67 } 68 } 69 70 func TestWriter(t *testing.T) { 71 var c bytes.Buffer 72 w := &Writer{Writer: &c} 73 74 for a, b := range tests { 75 c.Reset() 76 n, err := w.Write([]byte(a)) 77 if c.String() != b { 78 t.Logf("Write failed: %#v -> %#v != %#v\n", a, c.String(), b) 79 t.Fail() 80 } 81 if err != nil { 82 t.Logf("Write for %#v failed: %#v \n", a, err) 83 t.Fail() 84 } 85 if n != len([]byte(a)) { 86 t.Logf("Write for %#v returned wrong length: %#v \n", a, n) 87 t.Fail() 88 } 89 } 90 } 91 92 func TestWriterStopsOnErr(t *testing.T) { 93 w := &Writer{Writer: &mockWriter{}} 94 95 a := []byte("test") 96 97 if n, err := w.Write(a); n != len(a) || err != nil { 98 t.Logf("Write returned unexpected output: %#v, %#v \n", n, err) 99 t.Fail() 100 } 101 102 // This write should fail, as the mock writer errors 103 if n, err := w.Write(a); n != 0 || err == nil { 104 t.Logf("Write returned unexpected output: %#v, %#v \n", n, err) 105 t.Fail() 106 } 107 108 // This write should fail, even if the mock writer would allow it 109 if n, err := w.Write(a); n != 0 || err == nil { 110 t.Logf("Write returned unexpected output: %#v, %#v \n", n, err) 111 t.Fail() 112 } 113 } 114 115 // Allows even calls, errors on odd calls 116 type mockWriter struct { 117 i int 118 } 119 120 func (m *mockWriter) Write(p []byte) (n int, err error) { 121 if m.i == 0 { 122 m.i++ 123 return len(p), nil 124 } 125 126 m.i-- 127 return 1, fmt.Errorf("error writing") 128 } 129 130 // Tests for the replace function are adapted from the ones for strings.Map 131 132 func tenRunes(ch rune) string { 133 r := make([]rune, 10) 134 for i := range r { 135 r[i] = ch 136 } 137 return string(r) 138 } 139 140 // User-defined self-inverse mapping function 141 func rot13(r rune) rune { 142 step := rune(13) 143 if r >= 'a' && r <= 'z' { 144 return ((r - 'a' + step) % 26) + 'a' 145 } 146 if r >= 'A' && r <= 'Z' { 147 return ((r - 'A' + step) % 26) + 'A' 148 } 149 return r 150 } 151 func Test_replace(t *testing.T) { 152 // Run a couple of awful growth/shrinkage tests 153 a := tenRunes('a') 154 // 1. Grow. This triggers two reallocations in Map. 155 maxRune := func(rune) rune { return unicode.MaxRune } 156 m := replace(maxRune, a) 157 expect := tenRunes(unicode.MaxRune) 158 if m != expect { 159 t.Errorf("growing: expected %q got %q", expect, m) 160 } 161 162 // 2. Shrink 163 minRune := func(rune) rune { return 'a' } 164 m = replace(minRune, tenRunes(unicode.MaxRune)) 165 expect = a 166 if m != expect { 167 t.Errorf("shrinking: expected %q got %q", expect, m) 168 } 169 170 // 3. Rot13 171 m = replace(rot13, "a to zed") 172 expect = "n gb mrq" 173 if m != expect { 174 t.Errorf("rot13: expected %q got %q", expect, m) 175 } 176 177 // 4. Rot13^2 178 m = replace(rot13, replace(rot13, "a to zed")) 179 expect = "a to zed" 180 if m != expect { 181 t.Errorf("rot13: expected %q got %q", expect, m) 182 } 183 184 // 5. Drop^[ 185 dropNotLatin := func(r rune) rune { 186 if unicode.Is(unicode.Latin, r) { 187 return r 188 } 189 return -2 190 } 191 m = replace(dropNotLatin, "Hello, 세계") 192 expect = "Hello" 193 if m != expect { 194 t.Errorf("drop: expected %q got %q", expect, m) 195 } 196 197 // 6. Identity 198 identity := func(r rune) rune { 199 return r 200 } 201 orig := "Input string that we expect not to be copied." 202 m = replace(identity, orig) 203 if (*reflect.StringHeader)(unsafe.Pointer(&orig)).Data != 204 (*reflect.StringHeader)(unsafe.Pointer(&m)).Data { 205 t.Error("unexpected copy during identity map") 206 } 207 208 // 7. Handle invalid UTF-8 sequence 209 replaceNotLatin := func(r rune) rune { 210 if unicode.Is(unicode.Latin, r) { 211 return r 212 } 213 return '?' 214 } 215 m = replace(replaceNotLatin, "Hello\255World") 216 expect = "Hello?World" 217 if m != expect { 218 t.Errorf("replace invalid sequence: expected %q got %q", expect, m) 219 } 220 221 // 8. Handle special case of -1 to '^[' 222 aToEscape := func(r rune) rune { 223 if r == 'a' { 224 return -1 225 } 226 return r 227 } 228 m = replace(aToEscape, "a") 229 expect = "^[" 230 if m != expect { 231 t.Errorf("Escaping: expected %q got %q", expect, m) 232 } 233 m = replace(aToEscape, "aa") 234 expect = "^[^[" 235 if m != expect { 236 t.Errorf("Escaping: expected %q got %q", expect, m) 237 } 238 m = replace(aToEscape, "abaaba") 239 expect = "^[b^[^[b^[" 240 if m != expect { 241 t.Errorf("Escaping: expected %q got %q", expect, m) 242 } 243 }