Search Apps Documentation Source Content File Folder Download Copy Actions Download

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}