history_vth3_golden_external_test.go RawHistoryFolder
1package vt_test
2
3import (
4 "bytes"
5 "encoding/binary"
6 "encoding/hex"
7 "testing"
8
9 "github.com/bnema/vev-vt"
10 "github.com/bnema/vev-vt/core"
11)
12
13func TestExternalVTH3GoldenVectors(t *testing.T) {
14 vectors := []struct {
15 name string
16 hex string
17 stats vt.DecodeStats
18 }{
19 {
20 name: "empty",
21 hex: "565448310300000000000000000000002a",
22 stats: vt.DecodeStats{},
23 },
24 {
25 name: "plain and continuation cells",
26 hex: "565448310300000001000000000000002a0000000100000000000000290000000200000041000000ffffffffffffffffffffffffffffffff00000000000000ffffffffffffffff00000000000000010000ffffffffffffffffffffffffffffffff00000000000000ffffffffffffffff0000000000000200",
27 stats: vt.DecodeStats{Chunks: 1, Rows: 1, Cells: 2, Styles: 2, Bytes: 82},
28 },
29 {
30 name: "all style fields",
31 hex: "565448310300000001000000000000002b00000001000000000000002a000000010000754cff000ffffffffffffffff9000000000000010101020304050605fffffffffffffff70708090000000101",
32 stats: vt.DecodeStats{Chunks: 1, Rows: 1, Cells: 1, Styles: 1, Bytes: 41},
33 },
34 }
35
36 for _, vector := range vectors {
37 t.Run(vector.name, func(t *testing.T) {
38 data, err := hex.DecodeString(vector.hex)
39 if err != nil {
40 t.Fatal(err)
41 }
42 stats, err := vt.PreflightHistoryBlob(data)
43 if err != nil {
44 t.Fatalf("PreflightHistoryBlob: %v", err)
45 }
46 if stats != vector.stats {
47 t.Fatalf("decode stats = %#v, want %#v", stats, vector.stats)
48 }
49
50 view, err := vt.UnmarshalHistory(data)
51 if err != nil {
52 t.Fatalf("UnmarshalHistory: %v", err)
53 }
54 reencoded, err := vt.MarshalHistory(view)
55 if err != nil {
56 t.Fatalf("MarshalHistory: %v", err)
57 }
58 if !bytes.Equal(reencoded, data) {
59 t.Fatalf("canonical bytes changed:\n got %x\nwant %x", reencoded, data)
60 }
61
62 switch vector.name {
63 case "empty":
64 if view.Len() != 0 || view.ChunkCount() != 0 || view.NextRowID() != 42 {
65 t.Fatalf("empty view = len %d chunks %d next %d", view.Len(), view.ChunkCount(), view.NextRowID())
66 }
67 case "plain and continuation cells":
68 if view.Len() != 1 || view.RowID(0) != 41 || view.Bound(0) != (vt.LineBound{End: 2}) || view.NextRowID() != 42 {
69 t.Fatalf("plain view = len %d row %d bound %#v next %d", view.Len(), view.RowID(0), view.Bound(0), view.NextRowID())
70 }
71 row := view.Row(0)
72 if len(row) != 2 || row[0].Rune != 'A' || row[1].Rune != 0 || !row[1].Continuation {
73 t.Fatalf("plain row = %#v", row)
74 }
75 case "all style fields":
76 wantStyle := vtcore.Style{
77 Bold: true,
78 Italic: true,
79 Inverse: true,
80 Attrs: vtcore.StyleAttrs(0x000f),
81 Foreground: -7,
82 Background: 257,
83 HasForegroundRGB: true,
84 ForegroundRGB: vtcore.RGB{R: 1, G: 2, B: 3},
85 HasBackgroundRGB: true,
86 BackgroundRGB: vtcore.RGB{R: 4, G: 5, B: 6},
87 UnderlineStyle: vtcore.UnderlineDashed,
88 UnderlineColor: -9,
89 HasUnderlineColorRGB: true,
90 UnderlineColorRGB: vtcore.RGB{R: 7, G: 8, B: 9},
91 }
92 if view.Len() != 1 || view.RowID(0) != 42 || view.Bound(0) != (vt.LineBound{End: 1, Soft: true}) || view.NextRowID() != 43 {
93 t.Fatalf("styled view = len %d row %d bound %#v next %d", view.Len(), view.RowID(0), view.Bound(0), view.NextRowID())
94 }
95 row := view.Row(0)
96 if len(row) != 1 || row[0].Rune != '界' || !row[0].Style.Equal(wantStyle) {
97 t.Fatalf("styled row = %#v, want %#v", row, wantStyle)
98 }
99 }
100 })
101 }
102}
103
104func TestExternalVTH3RejectsMalformedVectors(t *testing.T) {
105 valid, err := hex.DecodeString("565448310300000001000000000000002a0000000100000000000000290000000200000041000000ffffffffffffffffffffffffffffffff00000000000000ffffffffffffffff00000000000000010000ffffffffffffffffffffffffffffffff00000000000000ffffffffffffffff0000000000000200")
106 if err != nil {
107 t.Fatal(err)
108 }
109
110 mutate := func(fn func([]byte)) []byte {
111 out := append([]byte(nil), valid...)
112 fn(out)
113 return out
114 }
115 const (
116 rowIDOffset = 21
117 cellOffset = 33
118 boundOffset = 115
119 )
120 cases := []struct {
121 name string
122 data []byte
123 }{
124 {name: "truncated", data: valid[:len(valid)-1]},
125 {name: "trailing bytes", data: append(append([]byte(nil), valid...), 0)},
126 {name: "bad magic", data: mutate(func(data []byte) { data[0] ^= 0xff })},
127 {name: "bad version", data: mutate(func(data []byte) { data[4] = 2 })},
128 {name: "zero next row ID", data: mutate(func(data []byte) { clear(data[9:17]) })},
129 {name: "zero row ID", data: mutate(func(data []byte) { clear(data[rowIDOffset : rowIDOffset+8]) })},
130 {name: "invalid rune", data: mutate(func(data []byte) { binary.BigEndian.PutUint32(data[cellOffset:], ^uint32(0)) })},
131 {name: "invalid underline style", data: mutate(func(data []byte) { data[cellOffset+29] = byte(vtcore.UnderlineDashed + 1) })},
132 {name: "bound exceeds row", data: mutate(func(data []byte) { binary.BigEndian.PutUint32(data[boundOffset:], 3) })},
133 }
134
135 for _, test := range cases {
136 t.Run(test.name, func(t *testing.T) {
137 if _, err := vt.PreflightHistoryBlob(test.data); err == nil {
138 t.Fatal("PreflightHistoryBlob accepted malformed data")
139 }
140 if _, err := vt.UnmarshalHistory(test.data); err == nil {
141 t.Fatal("UnmarshalHistory accepted malformed data")
142 }
143 })
144 }
145 for length := 0; length < len(valid); length++ {
146 if _, err := vt.UnmarshalHistory(valid[:length]); err == nil {
147 t.Fatalf("accepted truncated prefix of length %d", length)
148 }
149 }
150}