proto_test.gno
6.88 Kb · 223 lines
1package proto
2
3import (
4 "encoding/binary"
5 "testing"
6 "time"
7
8 "gno.land/p/nt/uassert/v0"
9 "gno.land/p/nt/urequire/v0"
10)
11
12func TestAppendLengthDelimited(t *testing.T) {
13 bz := AppendLengthDelimited(nil, 1, []byte("hello world"))
14
15 uassert.Equal(t, "\n\vhello world", string(bz))
16}
17
18func TestAppendVarint(t *testing.T) {
19 bz := AppendVarint(nil, 1, 42)
20
21 uassert.Equal(t, "\b*", string(bz))
22}
23
24func TestAppendFixed64(t *testing.T) {
25 bz := AppendFixed64(nil, 1, 42)
26
27 uassert.Equal(t, "\t*\x00\x00\x00\x00\x00\x00\x00", string(bz))
28}
29
30func TestTimeMarshal(t *testing.T) {
31 t.Run("zero time omits both fields", func(t *testing.T) {
32 // time.Time{} has Unix == -62135596800 (year 1) but the proto3
33 // encoding here treats it as a varint: nonzero seconds, zero nanos.
34 bz := TimeMarshal(time.Unix(0, 0).UTC())
35 // seconds=0 omitted, nanos=0 omitted
36 uassert.Equal(t, 0, len(bz))
37 })
38
39 t.Run("seconds only", func(t *testing.T) {
40 bz := TimeMarshal(time.Unix(1700000000, 0).UTC())
41 // field 1 seconds: tag 0x08, varint 1700000000 = 0x80 0xe2 0xcf 0xaa 0x06
42 uassert.Equal(t, "\x08\x80\xe2\xcf\xaa\x06", string(bz))
43 })
44
45 t.Run("seconds and nanos", func(t *testing.T) {
46 bz := TimeMarshal(time.Unix(1, 500).UTC())
47 // field 1: 08 01 ; field 2: 10 f4 03 (nanos = 500)
48 uassert.Equal(t, "\x08\x01\x10\xf4\x03", string(bz))
49 })
50}
51
52func TestAppendTime(t *testing.T) {
53 bz := AppendTime(nil, 3, time.Unix(1700000000, 0).UTC())
54 // field 3 LEN: tag 0x1a, length 6, then TimeMarshal output
55 uassert.Equal(t, "\x1a\x06\x08\x80\xe2\xcf\xaa\x06", string(bz))
56}
57
58func TestDurationMarshal(t *testing.T) {
59 t.Run("zero duration encodes to nothing", func(t *testing.T) {
60 bz := DurationMarshal(0)
61 uassert.Equal(t, 0, len(bz))
62 })
63
64 t.Run("seconds only", func(t *testing.T) {
65 bz := DurationMarshal(3 * time.Second)
66 // field 1 seconds=3: tag 0x08, varint 0x03
67 uassert.Equal(t, "\x08\x03", string(bz))
68 })
69
70 t.Run("nanos only", func(t *testing.T) {
71 bz := DurationMarshal(500 * time.Nanosecond)
72 // field 2 nanos=500: tag 0x10, varint 500 = 0xf4 0x03
73 uassert.Equal(t, "\x10\xf4\x03", string(bz))
74 })
75
76 t.Run("seconds and nanos", func(t *testing.T) {
77 bz := DurationMarshal(time.Second + 500*time.Nanosecond)
78 uassert.Equal(t, "\x08\x01\x10\xf4\x03", string(bz))
79 })
80
81 t.Run("negative nanos are normalized into the next-lower second", func(t *testing.T) {
82 // proto3 Duration requires nanos in [0, 1e9). For a negative
83 // total duration we add a full second to nanos to get a
84 // non-negative remainder.
85 bz := DurationMarshal(-500 * time.Nanosecond)
86 // seconds = 0 (omitted), nanos = 1e9 - 500 = 999_999_500
87 // 999_999_500 in varint: 0x8c 0x90 0xeb 0xdc 0x03
88 uassert.Equal(t, "\x10\x8c\x90\xeb\xdc\x03", string(bz))
89 })
90}
91
92func TestAppendDuration(t *testing.T) {
93 bz := AppendDuration(nil, 4, 3*time.Second)
94 // field 4 LEN: tag 0x22, length 2, then DurationMarshal output
95 uassert.Equal(t, "\x22\x02\x08\x03", string(bz))
96}
97
98func TestMarshalAny(t *testing.T) {
99 t.Run("typeURL only, empty value", func(t *testing.T) {
100 bz := MarshalAny("/x.Y", nil)
101 // Field 1 (typeURL): tag 0x0a, len 4, "/x.Y"
102 uassert.Equal(t, "\n\x04/x.Y", string(bz))
103 })
104
105 t.Run("typeURL and value", func(t *testing.T) {
106 bz := MarshalAny("/x.Y", []byte{0xab, 0xcd})
107 // Field 1 (typeURL): tag 0x0a, len 4, "/x.Y"
108 // Field 2 (value): tag 0x12, len 2, 0xab 0xcd
109 uassert.Equal(t, "\n\x04/x.Y\x12\x02\xab\xcd", string(bz))
110 })
111}
112
113func TestAppendPackedVarintInt32(t *testing.T) {
114 t.Run("empty omits field", func(t *testing.T) {
115 bz := AppendPackedVarintInt32(nil, 1, nil)
116 uassert.Equal(t, 0, len(bz))
117
118 bz = AppendPackedVarintInt32(nil, 1, []int32{})
119 uassert.Equal(t, 0, len(bz))
120 })
121
122 t.Run("includes zero values inside the packed payload", func(t *testing.T) {
123 bz := AppendPackedVarintInt32(nil, 1, []int32{0, 1})
124 // tag = 1<<3 | 2 = 0x0a, length = 2, payload = 0x00 0x01
125 uassert.Equal(t, "\n\x02\x00\x01", string(bz))
126 })
127
128 t.Run("multi-byte varints", func(t *testing.T) {
129 bz := AppendPackedVarintInt32(nil, 2, []int32{300, 1})
130 // tag = 2<<3 | 2 = 0x12; 300 = 0xac 0x02 (varint); 1 = 0x01
131 // length = 3
132 uassert.Equal(t, "\x12\x03\xac\x02\x01", string(bz))
133 })
134
135 t.Run("appends to existing buffer", func(t *testing.T) {
136 bz := AppendPackedVarintInt32([]byte{0xff}, 1, []int32{1})
137 uassert.Equal(t, "\xff\n\x01\x01", string(bz))
138 })
139}
140
141func TestDecodeVarint(t *testing.T) {
142 maxUint64Varint := []byte{0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0x01}
143 overlongVarint := []byte{0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0x02}
144
145 testCases := []struct {
146 name string
147 buf []byte
148 pos int
149 expVal uint64
150 expPos int
151 expErr string
152 }{
153 {"single byte zero", []byte{0x00}, 0, 0, 1, ""},
154 {"single byte 1", []byte{0x01}, 0, 1, 1, ""},
155 {"two-byte 150", []byte{0x96, 0x01}, 0, 150, 2, ""},
156 {"non-zero start position", []byte{0xff, 0x96, 0x01, 0xff}, 1, 150, 3, ""},
157 {"max uint64 (10-byte varint)", maxUint64Varint, 0, 1<<64 - 1, 10, ""},
158 {"empty buffer", []byte{}, 0, 0, 0, "buffer underflow"},
159 {"truncated continuation", []byte{0x80}, 0, 0, 0, "buffer underflow"},
160 {"10-byte varint overflowing uint64 rejected", overlongVarint, 0, 0, 10, "varint overflows uint64"},
161 }
162
163 for _, tc := range testCases {
164 t.Run(tc.name, func(t *testing.T) {
165 v, p, err := DecodeVarint(tc.buf, tc.pos)
166 if tc.expErr == "" {
167 urequire.NoError(t, err)
168 uassert.Equal(t, tc.expVal, v)
169 uassert.Equal(t, tc.expPos, p)
170 return
171 }
172 urequire.ErrorContains(t, err, tc.expErr)
173 uassert.Equal(t, tc.expPos, p)
174 })
175 }
176}
177
178func TestDecodeString(t *testing.T) {
179 t.Run("happy path", func(t *testing.T) {
180 buf := append(binary.AppendUvarint(nil, 5), []byte("hello")...)
181 got, p, err := DecodeString(buf, 0)
182 urequire.NoError(t, err)
183 uassert.Equal(t, "hello", got)
184 uassert.Equal(t, len(buf), p)
185 })
186
187 t.Run("empty string", func(t *testing.T) {
188 buf := binary.AppendUvarint(nil, 0)
189 got, p, err := DecodeString(buf, 0)
190 urequire.NoError(t, err)
191 uassert.Equal(t, "", got)
192 uassert.Equal(t, len(buf), p)
193 })
194
195 t.Run("truncated body", func(t *testing.T) {
196 buf := append(binary.AppendUvarint(nil, 10), []byte("abc")...)
197 _, _, err := DecodeString(buf, 0)
198 urequire.ErrorContains(t, err, "buffer underflow")
199 })
200
201 t.Run("length 2^63 must not panic on int cast", func(t *testing.T) {
202 buf := append(binary.AppendUvarint(nil, 1<<63), []byte("hi")...)
203 _, _, err := DecodeString(buf, 0)
204 urequire.ErrorContains(t, err, "buffer underflow")
205 })
206
207 t.Run("varint length parse error propagates", func(t *testing.T) {
208 buf := []byte{0x80}
209 _, _, err := DecodeString(buf, 0)
210 urequire.ErrorContains(t, err, "varint")
211 })
212
213 t.Run("non-zero start position", func(t *testing.T) {
214 // prefix byte + varint(5) + "hello" + trailing byte
215 buf := []byte{0xff}
216 buf = append(buf, binary.AppendUvarint(nil, 5)...)
217 buf = append(buf, []byte("hello\xff")...)
218 got, p, err := DecodeString(buf, 1)
219 urequire.NoError(t, err)
220 uassert.Equal(t, "hello", got)
221 uassert.Equal(t, len(buf)-1, p)
222 })
223}