| 123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588589590591592593594595596597598599600601602603604605606607608609610611612613614615616617618619620621622623624625626627628629630631632633634635636637638639640641642643644645646647648649650651652653654655656657658659660661662663664665666667668669670671672673674675676677678679680681682683684685686687688689690691692693694695696697698699700701702703704705706707708709710711712713714715716717718719720721722723724725726727728729730731732733734735736737738739740741742743744745746747748749750751752753754755756757758759760761762763764765766767768769770771772773774775776777778779780781782783784785786787788789790791792793794795796797798799800801802803804805806807808809810811812813814815816817818819820821822823824825826827828829830831832833834835836837838839840841842843844845846847848849850851852853854855856857858859860861862863864865866867868869870871872873874875876877878879880 |
- package input
- import (
- "bytes"
- "context"
- "errors"
- "flag"
- "fmt"
- "image/color"
- "io"
- "math/rand"
- "reflect"
- "regexp"
- "runtime"
- "sort"
- "strings"
- "sync"
- "testing"
- "time"
- "github.com/charmbracelet/x/ansi"
- "github.com/charmbracelet/x/ansi/kitty"
- )
- var sequences = buildKeysTable(FlagTerminfo, "dumb")
- func TestKeyString(t *testing.T) {
- t.Run("alt+space", func(t *testing.T) {
- k := KeyPressEvent{Code: KeySpace, Mod: ModAlt}
- if got := k.String(); got != "alt+space" {
- t.Fatalf(`expected a "alt+space", got %q`, got)
- }
- })
- t.Run("runes", func(t *testing.T) {
- k := KeyPressEvent{Code: 'a', Text: "a"}
- if got := k.String(); got != "a" {
- t.Fatalf(`expected an "a", got %q`, got)
- }
- })
- t.Run("invalid", func(t *testing.T) {
- k := KeyPressEvent{Code: 99999}
- if got := k.String(); got != "𘚟" {
- t.Fatalf(`expected a "unknown", got %q`, got)
- }
- })
- t.Run("space", func(t *testing.T) {
- k := KeyPressEvent{Code: KeySpace, Text: " "}
- if got := k.String(); got != "space" {
- t.Fatalf(`expected a "space", got %q`, got)
- }
- })
- t.Run("shift+space", func(t *testing.T) {
- k := KeyPressEvent{Code: KeySpace, Mod: ModShift}
- if got := k.String(); got != "shift+space" {
- t.Fatalf(`expected a "shift+space", got %q`, got)
- }
- })
- t.Run("?", func(t *testing.T) {
- k := KeyPressEvent{Code: '/', Mod: ModShift, Text: "?"}
- if got := k.String(); got != "?" {
- t.Fatalf(`expected a "?", got %q`, got)
- }
- })
- }
- type seqTest struct {
- seq []byte
- Events []Event
- }
- var f3CurPosRegexp = regexp.MustCompile(`\x1b\[1;(\d+)R`)
- // buildBaseSeqTests returns sequence tests that are valid for the
- // detectSequence() function.
- func buildBaseSeqTests() []seqTest {
- td := []seqTest{}
- for seq, key := range sequences {
- k := KeyPressEvent(key)
- st := seqTest{seq: []byte(seq), Events: []Event{k}}
- // XXX: This is a special case to handle F3 key sequence and cursor
- // position report having the same sequence. See [parseCsi] for more
- // information.
- if f3CurPosRegexp.MatchString(seq) {
- st.Events = []Event{k, CursorPositionEvent{Y: 0, X: int(key.Mod)}}
- }
- td = append(td, st)
- }
- // Additional special cases.
- td = append(td,
- // Unrecognized CSI sequence.
- seqTest{
- []byte{'\x1b', '[', '-', '-', '-', '-', 'X'},
- []Event{
- UnknownEvent([]byte{'\x1b', '[', '-', '-', '-', '-', 'X'}),
- },
- },
- // A lone space character.
- seqTest{
- []byte{' '},
- []Event{
- KeyPressEvent{Code: KeySpace, Text: " "},
- },
- },
- // An escape character with the alt modifier.
- seqTest{
- []byte{'\x1b', ' '},
- []Event{
- KeyPressEvent{Code: KeySpace, Mod: ModAlt},
- },
- },
- )
- return td
- }
- func TestParseSequence(t *testing.T) {
- td := buildBaseSeqTests()
- td = append(td,
- // Background color.
- seqTest{
- []byte("\x1b]11;rgb:1234/1234/1234\x07"),
- []Event{BackgroundColorEvent{
- Color: color.RGBA{R: 0x12, G: 0x12, B: 0x12, A: 0xff},
- }},
- },
- seqTest{
- []byte("\x1b]11;rgb:1234/1234/1234\x1b\\"),
- []Event{BackgroundColorEvent{
- Color: color.RGBA{R: 0x12, G: 0x12, B: 0x12, A: 0xff},
- }},
- },
- seqTest{
- []byte("\x1b]11;rgb:1234/1234/1234\x1b"), // Incomplete sequences are ignored.
- []Event{
- UnknownEvent("\x1b]11;rgb:1234/1234/1234\x1b"),
- },
- },
- // Kitty Graphics response.
- seqTest{
- []byte("\x1b_Ga=t;OK\x1b\\"),
- []Event{KittyGraphicsEvent{
- Options: kitty.Options{Action: kitty.Transmit},
- Payload: []byte("OK"),
- }},
- },
- seqTest{
- []byte("\x1b_Gi=99,I=13;OK\x1b\\"),
- []Event{KittyGraphicsEvent{
- Options: kitty.Options{ID: 99, Number: 13},
- Payload: []byte("OK"),
- }},
- },
- seqTest{
- []byte("\x1b_Gi=1337,q=1;EINVAL:your face\x1b\\"),
- []Event{KittyGraphicsEvent{
- Options: kitty.Options{ID: 1337, Quite: 1},
- Payload: []byte("EINVAL:your face"),
- }},
- },
- // Xterm modifyOtherKeys CSI 27 ; <modifier> ; <code> ~
- seqTest{
- []byte("\x1b[27;3;20320~"),
- []Event{KeyPressEvent{Code: '你', Mod: ModAlt}},
- },
- seqTest{
- []byte("\x1b[27;3;65~"),
- []Event{KeyPressEvent{Code: 'A', Mod: ModAlt}},
- },
- seqTest{
- []byte("\x1b[27;3;8~"),
- []Event{KeyPressEvent{Code: KeyBackspace, Mod: ModAlt}},
- },
- seqTest{
- []byte("\x1b[27;3;27~"),
- []Event{KeyPressEvent{Code: KeyEscape, Mod: ModAlt}},
- },
- seqTest{
- []byte("\x1b[27;3;127~"),
- []Event{KeyPressEvent{Code: KeyBackspace, Mod: ModAlt}},
- },
- // Xterm report window text area size.
- seqTest{
- []byte("\x1b[4;24;80t"),
- []Event{
- WindowOpEvent{Op: 4, Args: []int{24, 80}},
- },
- },
- // Kitty keyboard / CSI u (fixterms)
- seqTest{
- []byte("\x1b[1B"),
- []Event{KeyPressEvent{Code: KeyDown}},
- },
- seqTest{
- []byte("\x1b[1;B"),
- []Event{KeyPressEvent{Code: KeyDown}},
- },
- seqTest{
- []byte("\x1b[1;4B"),
- []Event{KeyPressEvent{Mod: ModShift | ModAlt, Code: KeyDown}},
- },
- seqTest{
- []byte("\x1b[1;4:1B"),
- []Event{KeyPressEvent{Mod: ModShift | ModAlt, Code: KeyDown}},
- },
- seqTest{
- []byte("\x1b[1;4:2B"),
- []Event{KeyPressEvent{Mod: ModShift | ModAlt, Code: KeyDown, IsRepeat: true}},
- },
- seqTest{
- []byte("\x1b[1;4:3B"),
- []Event{KeyReleaseEvent{Mod: ModShift | ModAlt, Code: KeyDown}},
- },
- seqTest{
- []byte("\x1b[8~"),
- []Event{KeyPressEvent{Code: KeyEnd}},
- },
- seqTest{
- []byte("\x1b[8;~"),
- []Event{KeyPressEvent{Code: KeyEnd}},
- },
- seqTest{
- []byte("\x1b[8;10~"),
- []Event{KeyPressEvent{Mod: ModShift | ModMeta, Code: KeyEnd}},
- },
- seqTest{
- []byte("\x1b[27;4u"),
- []Event{KeyPressEvent{Mod: ModShift | ModAlt, Code: KeyEscape}},
- },
- seqTest{
- []byte("\x1b[127;4u"),
- []Event{KeyPressEvent{Mod: ModShift | ModAlt, Code: KeyBackspace}},
- },
- seqTest{
- []byte("\x1b[57358;4u"),
- []Event{KeyPressEvent{Mod: ModShift | ModAlt, Code: KeyCapsLock}},
- },
- seqTest{
- []byte("\x1b[9;2u"),
- []Event{KeyPressEvent{Mod: ModShift, Code: KeyTab}},
- },
- seqTest{
- []byte("\x1b[195;u"),
- []Event{KeyPressEvent{Text: "Ã", Code: 'Ã'}},
- },
- seqTest{
- []byte("\x1b[20320;2u"),
- []Event{KeyPressEvent{Text: "你", Mod: ModShift, Code: '你'}},
- },
- seqTest{
- []byte("\x1b[195;:1u"),
- []Event{KeyPressEvent{Text: "Ã", Code: 'Ã'}},
- },
- seqTest{
- []byte("\x1b[195;2:3u"),
- []Event{KeyReleaseEvent{Code: 'Ã', Text: "Ã", Mod: ModShift}},
- },
- seqTest{
- []byte("\x1b[195;2:2u"),
- []Event{KeyPressEvent{Code: 'Ã', Text: "Ã", IsRepeat: true, Mod: ModShift}},
- },
- seqTest{
- []byte("\x1b[195;2:1u"),
- []Event{KeyPressEvent{Code: 'Ã', Text: "Ã", Mod: ModShift}},
- },
- seqTest{
- []byte("\x1b[195;2:3u"),
- []Event{KeyReleaseEvent{Code: 'Ã', Text: "Ã", Mod: ModShift}},
- },
- seqTest{
- []byte("\x1b[97;2;65u"),
- []Event{KeyPressEvent{Code: 'a', Text: "A", Mod: ModShift}},
- },
- seqTest{
- []byte("\x1b[97;;229u"),
- []Event{KeyPressEvent{Code: 'a', Text: "å"}},
- },
- // focus/blur
- seqTest{
- []byte{'\x1b', '[', 'I'},
- []Event{
- FocusEvent{},
- },
- },
- seqTest{
- []byte{'\x1b', '[', 'O'},
- []Event{
- BlurEvent{},
- },
- },
- // Mouse event.
- seqTest{
- []byte{'\x1b', '[', 'M', byte(32) + 0b0100_0000, byte(65), byte(49)},
- []Event{
- MouseWheelEvent{X: 32, Y: 16, Button: MouseWheelUp},
- },
- },
- // SGR Mouse event.
- seqTest{
- []byte("\x1b[<0;33;17M"),
- []Event{
- MouseClickEvent{X: 32, Y: 16, Button: MouseLeft},
- },
- },
- // Runes.
- seqTest{
- []byte{'a'},
- []Event{
- KeyPressEvent{Code: 'a', Text: "a"},
- },
- },
- seqTest{
- []byte{'\x1b', 'a'},
- []Event{
- KeyPressEvent{Code: 'a', Mod: ModAlt},
- },
- },
- seqTest{
- []byte{'a', 'a', 'a'},
- []Event{
- KeyPressEvent{Code: 'a', Text: "a"},
- KeyPressEvent{Code: 'a', Text: "a"},
- KeyPressEvent{Code: 'a', Text: "a"},
- },
- },
- // Multi-byte rune.
- seqTest{
- []byte("☃"),
- []Event{
- KeyPressEvent{Code: '☃', Text: "☃"},
- },
- },
- seqTest{
- []byte("\x1b☃"),
- []Event{
- KeyPressEvent{Code: '☃', Mod: ModAlt},
- },
- },
- // Standalone control characters.
- seqTest{
- []byte{'\x1b'},
- []Event{
- KeyPressEvent{Code: KeyEscape},
- },
- },
- seqTest{
- []byte{ansi.SOH},
- []Event{
- KeyPressEvent{Code: 'a', Mod: ModCtrl},
- },
- },
- seqTest{
- []byte{'\x1b', ansi.SOH},
- []Event{
- KeyPressEvent{Code: 'a', Mod: ModCtrl | ModAlt},
- },
- },
- seqTest{
- []byte{ansi.NUL},
- []Event{
- KeyPressEvent{Code: KeySpace, Mod: ModCtrl},
- },
- },
- seqTest{
- []byte{'\x1b', ansi.NUL},
- []Event{
- KeyPressEvent{Code: KeySpace, Mod: ModCtrl | ModAlt},
- },
- },
- // C1 control characters.
- seqTest{
- []byte{'\x80'},
- []Event{
- KeyPressEvent{Code: rune(0x80 - '@'), Mod: ModCtrl | ModAlt},
- },
- },
- )
- if runtime.GOOS != "windows" {
- // Sadly, utf8.DecodeRune([]byte(0xfe)) returns a valid rune on windows.
- // This is incorrect, but it makes our test fail if we try it out.
- td = append(td, seqTest{
- []byte{'\xfe'},
- []Event{
- UnknownEvent(rune(0xfe)),
- },
- })
- }
- var p Parser
- for _, tc := range td {
- t.Run(fmt.Sprintf("%q", string(tc.seq)), func(t *testing.T) {
- var events []Event
- buf := tc.seq
- for len(buf) > 0 {
- width, Event := p.parseSequence(buf)
- switch Event := Event.(type) {
- case MultiEvent:
- events = append(events, Event...)
- default:
- events = append(events, Event)
- }
- buf = buf[width:]
- }
- if !reflect.DeepEqual(tc.Events, events) {
- t.Errorf("\nexpected event for %q:\n %#v\ngot:\n %#v", tc.seq, tc.Events, events)
- }
- })
- }
- }
- func TestReadLongInput(t *testing.T) {
- expect := make([]Event, 1000)
- for i := range 1000 {
- expect[i] = KeyPressEvent{Code: 'a', Text: "a"}
- }
- input := strings.Repeat("a", 1000)
- drv, err := NewReader(strings.NewReader(input), "dumb", 0)
- if err != nil {
- t.Fatalf("unexpected input driver error: %v", err)
- }
- var Events []Event
- for {
- events, err := drv.ReadEvents()
- if err == io.EOF {
- break
- }
- if err != nil {
- t.Fatalf("unexpected input error: %v", err)
- }
- Events = append(Events, events...)
- }
- if !reflect.DeepEqual(expect, Events) {
- t.Errorf("unexpected messages, expected:\n %+v\ngot:\n %+v", expect, Events)
- }
- }
- func TestReadInput(t *testing.T) {
- type test struct {
- keyname string
- in []byte
- out []Event
- }
- testData := []test{
- {
- "a",
- []byte{'a'},
- []Event{
- KeyPressEvent{Code: 'a', Text: "a"},
- },
- },
- {
- "space",
- []byte{' '},
- []Event{
- KeyPressEvent{Code: KeySpace, Text: " "},
- },
- },
- {
- "a alt+a",
- []byte{'a', '\x1b', 'a'},
- []Event{
- KeyPressEvent{Code: 'a', Text: "a"},
- KeyPressEvent{Code: 'a', Mod: ModAlt},
- },
- },
- {
- "a alt+a a",
- []byte{'a', '\x1b', 'a', 'a'},
- []Event{
- KeyPressEvent{Code: 'a', Text: "a"},
- KeyPressEvent{Code: 'a', Mod: ModAlt},
- KeyPressEvent{Code: 'a', Text: "a"},
- },
- },
- {
- "ctrl+a",
- []byte{byte(ansi.SOH)},
- []Event{
- KeyPressEvent{Code: 'a', Mod: ModCtrl},
- },
- },
- {
- "ctrl+a ctrl+b",
- []byte{byte(ansi.SOH), byte(ansi.STX)},
- []Event{
- KeyPressEvent{Code: 'a', Mod: ModCtrl},
- KeyPressEvent{Code: 'b', Mod: ModCtrl},
- },
- },
- {
- "alt+a",
- []byte{byte(0x1b), 'a'},
- []Event{
- KeyPressEvent{Code: 'a', Mod: ModAlt},
- },
- },
- {
- "a b c d",
- []byte{'a', 'b', 'c', 'd'},
- []Event{
- KeyPressEvent{Code: 'a', Text: "a"},
- KeyPressEvent{Code: 'b', Text: "b"},
- KeyPressEvent{Code: 'c', Text: "c"},
- KeyPressEvent{Code: 'd', Text: "d"},
- },
- },
- {
- "up",
- []byte("\x1b[A"),
- []Event{
- KeyPressEvent{Code: KeyUp},
- },
- },
- {
- "wheel up",
- []byte{'\x1b', '[', 'M', byte(32) + 0b0100_0000, byte(65), byte(49)},
- []Event{
- MouseWheelEvent{X: 32, Y: 16, Button: MouseWheelUp},
- },
- },
- {
- "left motion release",
- []byte{
- '\x1b', '[', 'M', byte(32) + 0b0010_0000, byte(32 + 33), byte(16 + 33),
- '\x1b', '[', 'M', byte(32) + 0b0000_0011, byte(64 + 33), byte(32 + 33),
- },
- []Event{
- MouseMotionEvent{X: 32, Y: 16, Button: MouseLeft},
- MouseReleaseEvent{X: 64, Y: 32, Button: MouseNone},
- },
- },
- {
- "shift+tab",
- []byte{'\x1b', '[', 'Z'},
- []Event{
- KeyPressEvent{Code: KeyTab, Mod: ModShift},
- },
- },
- {
- "enter",
- []byte{'\r'},
- []Event{KeyPressEvent{Code: KeyEnter}},
- },
- {
- "alt+enter",
- []byte{'\x1b', '\r'},
- []Event{
- KeyPressEvent{Code: KeyEnter, Mod: ModAlt},
- },
- },
- {
- "insert",
- []byte{'\x1b', '[', '2', '~'},
- []Event{
- KeyPressEvent{Code: KeyInsert},
- },
- },
- {
- "ctrl+alt+a",
- []byte{'\x1b', byte(ansi.SOH)},
- []Event{
- KeyPressEvent{Code: 'a', Mod: ModCtrl | ModAlt},
- },
- },
- {
- "CSI?----X?",
- []byte{'\x1b', '[', '-', '-', '-', '-', 'X'},
- []Event{UnknownEvent([]byte{'\x1b', '[', '-', '-', '-', '-', 'X'})},
- },
- // Powershell sequences.
- {
- "up",
- []byte{'\x1b', 'O', 'A'},
- []Event{KeyPressEvent{Code: KeyUp}},
- },
- {
- "down",
- []byte{'\x1b', 'O', 'B'},
- []Event{KeyPressEvent{Code: KeyDown}},
- },
- {
- "right",
- []byte{'\x1b', 'O', 'C'},
- []Event{KeyPressEvent{Code: KeyRight}},
- },
- {
- "left",
- []byte{'\x1b', 'O', 'D'},
- []Event{KeyPressEvent{Code: KeyLeft}},
- },
- {
- "alt+enter",
- []byte{'\x1b', '\x0d'},
- []Event{KeyPressEvent{Code: KeyEnter, Mod: ModAlt}},
- },
- {
- "alt+backspace",
- []byte{'\x1b', '\x7f'},
- []Event{KeyPressEvent{Code: KeyBackspace, Mod: ModAlt}},
- },
- {
- "ctrl+space",
- []byte{'\x00'},
- []Event{KeyPressEvent{Code: KeySpace, Mod: ModCtrl}},
- },
- {
- "ctrl+alt+space",
- []byte{'\x1b', '\x00'},
- []Event{KeyPressEvent{Code: KeySpace, Mod: ModCtrl | ModAlt}},
- },
- {
- "esc",
- []byte{'\x1b'},
- []Event{KeyPressEvent{Code: KeyEscape}},
- },
- {
- "alt+esc",
- []byte{'\x1b', '\x1b'},
- []Event{KeyPressEvent{Code: KeyEscape, Mod: ModAlt}},
- },
- {
- "a b o",
- []byte{
- '\x1b', '[', '2', '0', '0', '~',
- 'a', ' ', 'b',
- '\x1b', '[', '2', '0', '1', '~',
- 'o',
- },
- []Event{
- PasteStartEvent{},
- PasteEvent("a b"),
- PasteEndEvent{},
- KeyPressEvent{Code: 'o', Text: "o"},
- },
- },
- {
- "a\x03\nb",
- []byte{
- '\x1b', '[', '2', '0', '0', '~',
- 'a', '\x03', '\n', 'b',
- '\x1b', '[', '2', '0', '1', '~',
- },
- []Event{
- PasteStartEvent{},
- PasteEvent("a\x03\nb"),
- PasteEndEvent{},
- },
- },
- {
- "?0xfe?",
- []byte{'\xfe'},
- []Event{
- UnknownEvent(rune(0xfe)),
- },
- },
- {
- "a ?0xfe? b",
- []byte{'a', '\xfe', ' ', 'b'},
- []Event{
- KeyPressEvent{Code: 'a', Text: "a"},
- UnknownEvent(rune(0xfe)),
- KeyPressEvent{Code: KeySpace, Text: " "},
- KeyPressEvent{Code: 'b', Text: "b"},
- },
- },
- }
- for i, td := range testData {
- t.Run(fmt.Sprintf("%d: %s", i, td.keyname), func(t *testing.T) {
- Events := testReadInputs(t, bytes.NewReader(td.in))
- var buf strings.Builder
- for i, Event := range Events {
- if i > 0 {
- buf.WriteByte(' ')
- }
- if s, ok := Event.(fmt.Stringer); ok {
- buf.WriteString(s.String())
- } else {
- fmt.Fprintf(&buf, "%#v:%T", Event, Event)
- }
- }
- if len(Events) != len(td.out) {
- t.Fatalf("unexpected message list length: got %d, expected %d\n got: %#v\n expected: %#v\n", len(Events), len(td.out), Events, td.out)
- }
- if !reflect.DeepEqual(td.out, Events) {
- t.Fatalf("expected:\n%#v\ngot:\n%#v", td.out, Events)
- }
- })
- }
- }
- func testReadInputs(t *testing.T, input io.Reader) []Event {
- // We'll check that the input reader finishes at the end
- // without error.
- var wg sync.WaitGroup
- var inputErr error
- ctx, cancel := context.WithCancel(context.Background())
- defer func() {
- cancel()
- wg.Wait()
- if inputErr != nil && !errors.Is(inputErr, io.EOF) {
- t.Fatalf("unexpected input error: %v", inputErr)
- }
- }()
- dr, err := NewReader(input, "dumb", 0)
- if err != nil {
- t.Fatalf("unexpected input driver error: %v", err)
- }
- // The messages we're consuming.
- EventsC := make(chan Event)
- // Start the reader in the background.
- wg.Add(1)
- go func() {
- defer wg.Done()
- var events []Event
- events, inputErr = dr.ReadEvents()
- out:
- for _, ev := range events {
- select {
- case EventsC <- ev:
- case <-ctx.Done():
- break out
- }
- }
- EventsC <- nil
- }()
- var Events []Event
- loop:
- for {
- select {
- case Event := <-EventsC:
- if Event == nil {
- // end of input marker for the test.
- break loop
- }
- Events = append(Events, Event)
- case <-time.After(2 * time.Second):
- t.Errorf("timeout waiting for input event")
- break loop
- }
- }
- return Events
- }
- // randTest defines the test input and expected output for a sequence
- // of interleaved control sequences and control characters.
- type randTest struct {
- data []byte
- lengths []int
- names []string
- }
- // seed is the random seed to randomize the input. This helps check
- // that all the sequences get ultimately exercised.
- var seed = flag.Int64("seed", 0, "random seed (0 to autoselect)")
- // genRandomData generates a randomized test, with a random seed unless
- // the seed flag was set.
- func genRandomData(logfn func(int64), length int) randTest {
- // We'll use a random source. However, we give the user the option
- // to override it to a specific value for reproducibility.
- s := *seed
- if s == 0 {
- s = time.Now().UnixNano()
- }
- // Inform the user so they know what to reuse to get the same data.
- logfn(s)
- return genRandomDataWithSeed(s, length)
- }
- // genRandomDataWithSeed generates a randomized test with a fixed seed.
- func genRandomDataWithSeed(s int64, length int) randTest {
- src := rand.NewSource(s)
- r := rand.New(src)
- // allseqs contains all the sequences, in sorted order. We sort
- // to make the test deterministic (when the seed is also fixed).
- type seqpair struct {
- seq string
- name string
- }
- var allseqs []seqpair
- for seq, key := range sequences {
- allseqs = append(allseqs, seqpair{seq, key.String()})
- }
- sort.Slice(allseqs, func(i, j int) bool { return allseqs[i].seq < allseqs[j].seq })
- // res contains the computed test.
- var res randTest
- for len(res.data) < length {
- alt := r.Intn(2)
- prefix := ""
- esclen := 0
- if alt == 1 {
- prefix = "alt+"
- esclen = 1
- }
- kind := r.Intn(3)
- switch kind {
- case 0:
- // A control character.
- if alt == 1 {
- res.data = append(res.data, '\x1b')
- }
- res.data = append(res.data, 1)
- res.names = append(res.names, "ctrl+"+prefix+"a")
- res.lengths = append(res.lengths, 1+esclen)
- case 1, 2:
- // A sequence.
- seqi := r.Intn(len(allseqs))
- s := allseqs[seqi]
- if strings.Contains(s.name, "alt+") || strings.Contains(s.name, "meta+") {
- esclen = 0
- prefix = ""
- alt = 0
- }
- if alt == 1 {
- res.data = append(res.data, '\x1b')
- }
- res.data = append(res.data, s.seq...)
- if strings.HasPrefix(s.name, "ctrl+") {
- prefix = "ctrl+" + prefix
- }
- name := prefix + strings.TrimPrefix(s.name, "ctrl+")
- res.names = append(res.names, name)
- res.lengths = append(res.lengths, len(s.seq)+esclen)
- }
- }
- return res
- }
- func FuzzParseSequence(f *testing.F) {
- var p Parser
- for seq := range sequences {
- f.Add(seq)
- }
- f.Add("\x1b]52;?\x07") // OSC 52
- f.Add("\x1b]11;rgb:0000/0000/0000\x1b\\") // OSC 11
- f.Add("\x1bP>|charm terminal(0.1.2)\x1b\\") // DCS (XTVERSION)
- f.Add("\x1b_Gi=123\x1b\\") // APC
- f.Fuzz(func(t *testing.T, seq string) {
- n, _ := p.parseSequence([]byte(seq))
- if n == 0 && seq != "" {
- t.Errorf("expected a non-zero width for %q", seq)
- }
- })
- }
- // BenchmarkDetectSequenceMap benchmarks the map-based sequence
- // detector.
- func BenchmarkDetectSequenceMap(b *testing.B) {
- var p Parser
- td := genRandomDataWithSeed(123, 10000)
- for i := 0; i < b.N; i++ {
- for j, w := 0, 0; j < len(td.data); j += w {
- w, _ = p.parseSequence(td.data[j:])
- }
- }
- }
|