1package tuple
2
3import (
4	"bytes"
5	"encoding/gob"
6	"flag"
7	"math/rand"
8	"os"
9	"testing"
10)
11
12var update = flag.Bool("update", false, "update .golden files")
13
14func loadGolden(t *testing.T) (golden map[string][]byte) {
15	f, err := os.Open("testdata/tuples.golden")
16	if err != nil {
17		t.Fatalf("failed to open golden file: %s", err)
18	}
19	defer f.Close()
20
21	err = gob.NewDecoder(f).Decode(&golden)
22	if err != nil {
23		t.Fatalf("failed to decode golden file: %s", err)
24	}
25	return
26}
27
28func writeGolden(t *testing.T, golden map[string][]byte) {
29	f, err := os.Create("testdata/tuples.golden")
30	if err != nil {
31		t.Fatalf("failed to open golden file: %s", err)
32	}
33	defer f.Close()
34
35	err = gob.NewEncoder(f).Encode(golden)
36	if err != nil {
37		t.Fatalf("failed to encode golden file: %s", err)
38	}
39}
40
41var testUUID = UUID{
42	0x11, 0x00, 0xAA, 0xBB, 0xCC, 0xDD, 0xEE, 0xFF,
43	0x11, 0x00, 0xAA, 0xBB, 0xCC, 0xDD, 0xEE, 0xFF,
44}
45
46func genBytes() interface{}     { return []byte("namespace") }
47func genBytesNil() interface{}  { return []byte{0xFF, 0x00, 0xFF} }
48func genString() interface{}    { return "namespace" }
49func genStringNil() interface{} { return "nam\x00es\xFFpace" }
50func genInt() interface{}       { return rand.Int63() }
51func genFloat() interface{}     { return float32(rand.NormFloat64()) }
52func genDouble() interface{}    { return rand.NormFloat64() }
53
54func mktuple(gen func() interface{}, count int) Tuple {
55	tt := make(Tuple, count)
56	for i := 0; i < count; i++ {
57		tt[i] = gen()
58	}
59	return tt
60}
61
62var testCases = []struct {
63	name  string
64	tuple Tuple
65}{
66	{"Simple", Tuple{testUUID, "foobarbaz", 1234, nil}},
67	{"Namespaces", Tuple{testUUID, "github", "com", "apple", "foundationdb", "tree"}},
68	{"ManyStrings", mktuple(genString, 8)},
69	{"ManyStringsNil", mktuple(genStringNil, 8)},
70	{"ManyBytes", mktuple(genBytes, 20)},
71	{"ManyBytesNil", mktuple(genBytesNil, 20)},
72	{"LargeBytes", Tuple{testUUID, bytes.Repeat([]byte("abcd"), 20)}},
73	{"LargeBytesNil", Tuple{testUUID, bytes.Repeat([]byte{0xFF, 0x0, 0xFF}, 20)}},
74	{"Integers", mktuple(genInt, 20)},
75	{"Floats", mktuple(genFloat, 20)},
76	{"Doubles", mktuple(genDouble, 20)},
77	{"UUIDs", Tuple{testUUID, true, testUUID, false, testUUID, true, testUUID, false, testUUID, true}},
78	{"NilCases", Tuple{"\x00", "\x00\xFF", "\x00\x00\x00", "\xFF\x00", ""}},
79	{"Nested", Tuple{testUUID, mktuple(genInt, 4), nil, mktuple(genBytes, 4), nil, mktuple(genDouble, 4), nil}},
80}
81
82func TestTuplePacking(t *testing.T) {
83	var golden map[string][]byte
84
85	if *update {
86		golden = make(map[string][]byte)
87	} else {
88		golden = loadGolden(t)
89	}
90
91	for _, tt := range testCases {
92		t.Run(tt.name, func(t *testing.T) {
93			result := tt.tuple.Pack()
94
95			if *update {
96				golden[tt.name] = result
97				return
98			}
99
100			if !bytes.Equal(result, golden[tt.name]) {
101				t.Errorf("packing mismatch: expected %v, got %v", golden[tt.name], result)
102			}
103		})
104	}
105
106	if *update {
107		writeGolden(t, golden)
108	}
109}
110
111func BenchmarkTuplePacking(b *testing.B) {
112	for _, bm := range testCases {
113		b.Run(bm.name, func(b *testing.B) {
114			tuple := bm.tuple
115			for i := 0; i < b.N; i++ {
116				_ = tuple.Pack()
117			}
118		})
119	}
120}
121