1package terminalquery
2
3import (
4 "testing"
5
6 "github.com/stretchr/testify/require"
7)
8
9func TestProbeRequiresKittyAndDA1(t *testing.T) {
10 for _, tc := range []struct {
11 name string
12 data string
13 kitty bool
14 da1 bool
15 unrelated string
16 }{
17 {name: "both and input", data: "x\x1b[?1;2c" + "\x1b_Gi=31;OK\x1b\\y", kitty: true, da1: true, unrelated: "xy"},
18 {name: "kitty DA1 with trailing separator", data: "\x1b_Gi=31;OK\x1b\\\x1b[?62;52;c", kitty: true, da1: true},
19 {name: "secondary DA is insufficient", data: "\x1b[>1;2c\x1b_Gi=31;OK\x1b\\", kitty: true, unrelated: "\x1b[>1;2c"},
20 {name: "ordinary input", data: "hello", unrelated: "hello"},
21 } {
22 t.Run(tc.name, func(t *testing.T) {
23 var p Probe
24 got := append(p.Feed([]byte(tc.data)), p.Finish()...)
25 require.Equal(t, tc.kitty, p.KittyGraphics())
26 require.Equal(t, tc.da1, p.DA1())
27 require.Equal(t, tc.unrelated, string(got))
28 })
29 }
30}
31
32func TestProbeRecognizesFragmentedResponsesAndReplaysInput(t *testing.T) {
33 var p Probe
34 var unrelated []byte
35 for _, fragment := range []string{"a\x1b", "[?1;2", "c", "b\x1b_Gi=31;", "OK\x1b", "\\z"} {
36 unrelated = append(unrelated, p.Feed([]byte(fragment))...)
37 }
38 unrelated = append(unrelated, p.Finish()...)
39 require.True(t, p.Ready())
40 require.Equal(t, "abz", string(unrelated))
41}
42
43func TestProbeRequiresExactCapabilityResponses(t *testing.T) {
44 for _, data := range []string{
45 "\x1b_Gi=31;OK,extra\x1b\\",
46 "\x1b_Gi=310;OK\x1b\\",
47 "\x1b_Gi=31;OKAY\x1b\\",
48 "\x1b[?1;;2c",
49 "\x1b[?x;2c",
50 } {
51 var p Probe
52 got := append(p.Feed([]byte(data)), p.Finish()...)
53 require.False(t, p.Ready(), "response %q was accepted", data)
54 require.Equal(t, data, string(got))
55 }
56}
57
58func FuzzProbeNeverPanics(f *testing.F) {
59 f.Add([]byte("text\x1b[?1;2c\x1b_Gi=31;OK\x1b\\"))
60 f.Add([]byte("\x1b_G"))
61 f.Fuzz(func(t *testing.T, data []byte) {
62 var p Probe
63 _ = p.Feed(data)
64 _ = p.Finish()
65 })
66}
67
68func TestProbeDoesNotRetainLargeInputFragments(t *testing.T) {
69 var p Probe
70 input := append(make([]byte, 1<<20), []byte("\x1b_G")...)
71 got := append(p.Feed(input), p.Finish()...)
72 require.Equal(t, input, got)
73 require.LessOrEqual(t, cap(p.pending), maxProbeResponseBytes+2)
74}
75
76func TestProbeBoundsUnterminatedResponse(t *testing.T) {
77 var p Probe
78 input := append([]byte("prefix"), []byte("\x1b_G")...)
79 input = append(input, make([]byte, maxProbeResponseBytes+1)...)
80 got := append(p.Feed(input), p.Finish()...)
81 require.False(t, p.Ready())
82 require.Equal(t, input, got)
83}
84
85func TestProbeRecognizesKittyKeyboardResponse(t *testing.T) {
86 for _, tc := range []struct {
87 name string
88 fragments []string
89 keyboard bool
90 da1 bool
91 unrelated string
92 }{
93 {name: "supported", fragments: []string{"\x1b[?0u\x1b[?62;c"}, keyboard: true, da1: true},
94 {name: "supported with flags", fragments: []string{"\x1b[?15u"}, keyboard: true},
95 {name: "unsupported", fragments: []string{"\x1b[?62;22c"}, da1: true},
96 {name: "fragmented", fragments: []string{"a\x1b", "[?", "1", "u", "b"}, keyboard: true, unrelated: "ab"},
97 {name: "empty flags rejected", fragments: []string{"\x1b[?u"}, unrelated: "\x1b[?u"},
98 {name: "non digit rejected", fragments: []string{"\x1b[?1;2u"}, unrelated: "\x1b[?1;2u"},
99 {name: "no private marker rejected", fragments: []string{"\x1b[1u"}, unrelated: "\x1b[1u"},
100 {name: "truncated flushes", fragments: []string{"\x1b[?1"}, unrelated: "\x1b[?1"},
101 } {
102 t.Run(tc.name, func(t *testing.T) {
103 var p Probe
104 var got []byte
105 for _, f := range tc.fragments {
106 got = append(got, p.Feed([]byte(f))...)
107 }
108 got = append(got, p.Finish()...)
109 require.Equal(t, tc.keyboard, p.KittyKeyboard())
110 require.Equal(t, tc.da1, p.DA1())
111 require.Equal(t, tc.unrelated, string(got))
112 })
113 }
114}