1package snapshot
2
3import (
4 "bytes"
5 "encoding/hex"
6 "testing"
7
8 vt "github.com/bnema/vev-vt"
9 "github.com/bnema/vev/internal/usecase/layout"
10)
11
12func TestVEVSV4GoldenRestoreCompatibility(t *testing.T) {
13 const golden = "5645565300040000000001291707ae820007726573746f7265000000000000007b0000000100037461620050001800000000000000020001700000017000010001700006737461626c6500042f746d700000000100000078565448310300000001000000000000002a0000000100000000000000290000000200000041000000ffffffffffffffffffffffffffffffff00000000000000ffffffffffffffff00000000000000010000ffffffffffffffffffffffffffffffff00000000000000ffffffffffffffff000000000000020000000011565448310300000000000000000000002a0000004f565448310300000001000000000000002b00000001000000000000002a000000010000754cff000ffffffffffffffff9000000000000010101020304050605fffffffffffffff7070809000000010100"
14 data, err := hex.DecodeString(golden)
15 if err != nil {
16 t.Fatal(err)
17 }
18
19 got, err := Unmarshal(data)
20 if err != nil {
21 t.Fatalf("Unmarshal golden VEVS: %v", err)
22 }
23 if got.Name != "restore" || got.CreatedAt != 123 || len(got.Tabs) != 1 {
24 t.Fatalf("session header = %#v", got)
25 }
26 tab := got.Tabs[0]
27 if tab.StableID != "tab" || tab.Cols != 80 || tab.Rows != 24 || tab.NextPaneID != 2 || tab.Focus != layout.PaneID("p") || len(tab.Panes) != 1 {
28 t.Fatalf("tab header = %#v", tab)
29 }
30 pane := tab.Panes[0]
31 if pane.ID != layout.PaneID("p") || pane.StableID != "stable" || pane.Cwd != "/tmp" || pane.Process != nil || len(pane.SealedChunks) != 1 {
32 t.Fatalf("pane header = %#v", pane)
33 }
34
35 sealed, err := vt.UnmarshalHistory(pane.SealedChunks[0])
36 if err != nil {
37 t.Fatalf("sealed history: %v", err)
38 }
39 if sealed.RowID(0) != 41 || sealed.Row(0)[0].Rune != 'A' {
40 t.Fatalf("sealed history = row %d %#v", sealed.RowID(0), sealed.Row(0))
41 }
42 tail, err := vt.UnmarshalHistory(pane.Tail)
43 if err != nil {
44 t.Fatalf("history tail: %v", err)
45 }
46 if tail.Len() != 0 || tail.NextRowID() != 42 {
47 t.Fatalf("history tail = len %d next %d", tail.Len(), tail.NextRowID())
48 }
49 transcript, err := vt.UnmarshalHistory(pane.Transcript)
50 if err != nil {
51 t.Fatalf("recovery transcript: %v", err)
52 }
53 if transcript.RowID(0) != 42 || transcript.Row(0)[0].Rune != '界' {
54 t.Fatalf("recovery transcript = row %d %#v", transcript.RowID(0), transcript.Row(0))
55 }
56
57 screen, err := vt.NewScreenWithRecoveryTranscript(8, 2, vt.HistoryConfig{MaxRows: 8, ChunkRows: 2}, pane.SealedChunks, pane.Tail, pane.Transcript)
58 if err != nil {
59 t.Fatalf("restore VT history: %v", err)
60 }
61 view := screen.History().View()
62 if view.Len() != 2 || view.RowID(0) != 41 || view.RowID(1) != 42 || view.NextRowID() != 43 {
63 t.Fatalf("restored history = len %d IDs %d/%d next %d", view.Len(), view.RowID(0), view.RowID(1), view.NextRowID())
64 }
65}
66
67func TestVEVSV4GoldenRejectsMalformedInput(t *testing.T) {
68 const golden = "5645565300040000000001291707ae820007726573746f7265000000000000007b0000000100037461620050001800000000000000020001700000017000010001700006737461626c6500042f746d700000000100000078565448310300000001000000000000002a0000000100000000000000290000000200000041000000ffffffffffffffffffffffffffffffff00000000000000ffffffffffffffff00000000000000010000ffffffffffffffffffffffffffffffff00000000000000ffffffffffffffff000000000000020000000011565448310300000000000000000000002a0000004f565448310300000001000000000000002b00000001000000000000002a000000010000754cff000ffffffffffffffff9000000000000010101020304050605fffffffffffffff7070809000000010100"
69 valid, err := hex.DecodeString(golden)
70 if err != nil {
71 t.Fatal(err)
72 }
73 mutate := func(fn func([]byte)) []byte {
74 out := append([]byte(nil), valid...)
75 fn(out)
76 return out
77 }
78 cases := []struct {
79 name string
80 data []byte
81 }{
82 {name: "truncated", data: valid[:len(valid)-1]},
83 {name: "trailing bytes", data: append(append([]byte(nil), valid...), 0)},
84 {name: "bad magic", data: mutate(func(data []byte) { data[0] ^= 0xff })},
85 {name: "bad version", data: mutate(func(data []byte) { data[4] = 3 })},
86 {name: "bad flags", data: mutate(func(data []byte) { data[7] = 1 })},
87 {name: "bad crc", data: mutate(func(data []byte) { data[15] ^= 0xff })},
88 }
89
90 body := append([]byte(nil), valid[16:]...)
91 if offset := bytes.Index(body, []byte("VTH1")); offset < 0 {
92 t.Fatal("golden has no VT blob")
93 } else {
94 body[offset+4] = 2
95 cases = append(cases, struct {
96 name string
97 data []byte
98 }{name: "bad nested VT version", data: v4Envelope(4, 0, body)})
99 }
100
101 for _, test := range cases {
102 t.Run(test.name, func(t *testing.T) {
103 reject(t, test.data, nil)
104 })
105 }
106 for length := 0; length < len(valid); length++ {
107 reject(t, valid[:length], nil)
108 }
109}