1package el
2
3import (
4	"fmt"
5	"reflect"
6	"testing"
7
8	"github.com/pascaldekloe/goe/verify"
9)
10
11type strType string
12
13// strptr returns a pointer to s.
14// Go does not allow pointers to literals.
15func strptr(s string) *string {
16	return &s
17}
18
19type Vals struct {
20	B bool
21	I int64
22	U uint64
23	F float64
24	C complex128
25	S string
26}
27
28type Ptrs struct {
29	BP *bool
30	IP *int64
31	UP *uint64
32	FP *float64
33	CP *complex128
34	SP *string
35}
36
37type Node struct {
38	Name  *string
39	Child *Node
40	child *Node
41	X     interface{}
42	A     [2]interface{}
43	S     []interface{}
44}
45
46var testV = Vals{
47	B: true,
48	I: -2,
49	U: 4,
50	F: 8,
51	C: 16i,
52	S: "32",
53}
54
55var testPV = Ptrs{
56	BP: &testV.B,
57	IP: &testV.I,
58	UP: &testV.U,
59	FP: &testV.F,
60	CP: &testV.C,
61	SP: &testV.S,
62}
63
64type goldenCase struct {
65	expr string
66	root interface{}
67	want interface{}
68}
69
70var goldenPaths = []goldenCase{
71	0:  {"/B", testV, testV.B},
72	1:  {"/IP", &testPV, testV.I},
73	2:  {"/X/X/U", Node{X: Node{X: testV}}, testV.U},
74	3:  {"/X/../X/FP", Node{X: &testPV}, testV.F},
75	4:  {"/X/./X/C", &Node{X: &Node{X: &testV}}, testV.C},
76	5:  {"/", &testPV.SP, testV.S},
77	6:  {"/.[0]", "hello", uint64('h')},
78	7:  {"/S/.[0]", &Node{S: []interface{}{testV.I}}, testV.I},
79	8:  {"/A[1]", Node{A: [2]interface{}{testV.F, testV.S}}, testV.S},
80	9:  {"/.[true]", map[bool]string{true: "y"}, "y"},
81	10: {`/.["I \x2f O"]`, map[strType]float64{"I / O": 99.8}, 99.8},
82	11: {"/.[1]/.[2]", map[int]map[uint]string{1: {2: "1.2"}}, "1.2"},
83	12: {"/.[*]/.[*]", map[int]map[uint]string{3: {4: "3.4"}}, "3.4"},
84}
85
86func TestPaths(t *testing.T) {
87	for i, gold := range goldenPaths {
88		testGoldenCase(t, reflect.ValueOf(Bool), gold, i)
89		testGoldenCase(t, reflect.ValueOf(Int), gold, i)
90		testGoldenCase(t, reflect.ValueOf(Uint), gold, i)
91		testGoldenCase(t, reflect.ValueOf(Float), gold, i)
92		testGoldenCase(t, reflect.ValueOf(Complex), gold, i)
93		testGoldenCase(t, reflect.ValueOf(String), gold, i)
94	}
95}
96
97var goldenPathFails = []goldenCase{
98	0:  {"/Name", (*Node)(nil), nil},
99	1:  {"/Child", Node{}, nil},
100	2:  {"/Child/Name", Node{}, nil},
101	3:  {"Malformed", Node{}, nil},
102	4:  {"/Mis", Node{}, nil},
103	5:  {"/.[broken]", [2]bool{}, nil},
104	6:  {"/.[yes]", map[bool]bool{}, nil},
105	7:  {"/X", Node{X: testV}, nil},
106	8:  {"/.[3]", testV, nil},
107	9:  {"/S[4]", Node{}, nil},
108	10: {"/A[5]", Node{}, nil},
109	11: {"/.[6.66]", map[float64]bool{}, nil},
110}
111
112func TestPathFails(t *testing.T) {
113	for i, gold := range goldenPathFails {
114		testGoldenCase(t, reflect.ValueOf(Bool), gold, i)
115		testGoldenCase(t, reflect.ValueOf(Int), gold, i)
116		testGoldenCase(t, reflect.ValueOf(Uint), gold, i)
117		testGoldenCase(t, reflect.ValueOf(Float), gold, i)
118		testGoldenCase(t, reflect.ValueOf(Complex), gold, i)
119		testGoldenCase(t, reflect.ValueOf(String), gold, i)
120	}
121}
122
123func testGoldenCase(t *testing.T, f reflect.Value, gold goldenCase, goldIndex int) {
124	args := []reflect.Value{
125		reflect.ValueOf(gold.expr),
126		reflect.ValueOf(gold.root),
127	}
128	result := f.Call(args)
129
130	typ := result[0].Type()
131	wantMatch := gold.want != nil && typ == reflect.TypeOf(gold.want)
132
133	if got := result[1].Bool(); got != wantMatch {
134		t.Errorf("%d: Got %s OK %t, want %t for %q", goldIndex, typ, got, wantMatch, gold.expr)
135		return
136	}
137
138	if got := result[0].Interface(); wantMatch && got != gold.want {
139		t.Errorf("%d: Got %s %#v, want %#v for %q", goldIndex, typ, got, gold.want, gold.expr)
140	}
141}
142
143func BenchmarkLookups(b *testing.B) {
144	todo := b.N
145	for {
146		for _, g := range goldenPaths {
147			String(g.expr, g.root)
148			todo--
149			if todo == 0 {
150				return
151			}
152		}
153	}
154}
155
156func TestWildCards(t *testing.T) {
157	data := &Node{
158		A: [2]interface{}{99, 100},
159		S: []interface{}{"a", "b", 3},
160	}
161	valueMix := []interface{}{testV.B, testV.I, testV.U, testV.F, testV.C, testV.S, testV}
162
163	tests := []struct {
164		got, want interface{}
165	}{
166		0: {Bools("/*", testV), []bool{testV.B}},
167		1: {Ints("/*", testV), []int64{testV.I}},
168		2: {Uints("/*", testV), []uint64{testV.U}},
169		3: {Floats("/*", testV), []float64{testV.F}},
170		4: {Complexes("/*", testV), []complex128{testV.C}},
171		5: {Strings("/*", testV), []string{testV.S}},
172
173		6:  {Any("/*", Ptrs{}), []interface{}(nil)},
174		7:  {Bools("/*", Ptrs{}), []bool(nil)},
175		8:  {Ints("/*", Ptrs{}), []int64(nil)},
176		9:  {Uints("/*", Ptrs{}), []uint64(nil)},
177		10: {Floats("/*", Ptrs{}), []float64(nil)},
178		11: {Complexes("/*", Ptrs{}), []complex128(nil)},
179		12: {Strings("/*", Ptrs{}), []string(nil)},
180
181		13: {Ints("/A[*]", data), []int64{99, 100}},
182		14: {Strings("/*[*]", data), []string{"a", "b"}},
183
184		15: {Any("/.[*]", valueMix), valueMix},
185		16: {Any("/", valueMix), []interface{}{valueMix}},
186		17: {Any("/MisMatch", valueMix), []interface{}(nil)},
187	}
188
189	for i, test := range tests {
190		name := fmt.Sprintf("%d: wildcard match", i)
191		verify.Values(t, name, test.got, test.want)
192	}
193
194}
195
196type goldenAssign struct {
197	path  string
198	root  interface{}
199	value interface{}
200
201	// updates is the wanted number of updates.
202	updates int
203	// result is the wanted content at path.
204	result []string
205}
206
207func newGoldenAssigns() []goldenAssign {
208	return []goldenAssign{
209		{"/", strptr("hello"), "hell", 1, []string{"hell"}},
210		{"/.", strptr("hello"), "hell", 1, []string{"hell"}},
211		{"/", strptr("hello"), strptr("poin"), 1, []string{"poin"}},
212
213		{"/S", &struct{ S string }{}, "hell", 1, []string{"hell"}},
214		{"/SC", &struct{ SC string }{}, strType("hell"), 1, []string{"hell"}},
215		{"/CC", &struct{ CC strType }{}, strType("hell"), 1, []string{"hell"}},
216		{"/CS", &struct{ CS strType }{}, "hell", 1, []string{"hell"}},
217
218		{"/P", &struct{ P *string }{P: new(string)}, "poin", 1, []string{"poin"}},
219		{"/PP", &struct{ PP **string }{PP: new(*string)}, "doub", 1, []string{"doub"}},
220		{"/PPP", &struct{ PPP ***string }{PPP: new(**string)}, "trip", 1, []string{"trip"}},
221
222		{"/I", &struct{ I interface{} }{}, "in", 1, []string{"in"}},
223		{"/U", &struct{ U interface{} }{U: true}, "up", 1, []string{"up"}},
224
225		{"/X/S", &struct{ X *struct{ S string } }{}, "hell", 1, []string{"hell"}},
226		{"/X/P", &struct{ X **struct{ P *string } }{}, "poin", 1, []string{"poin"}},
227		{"/X/PP", &struct{ X **struct{ PP **string } }{}, "doub", 1, []string{"doub"}},
228
229		{"/Child/Child/Child/Name", &Node{}, "Grand Grand", 1, []string{"Grand Grand"}},
230
231		{"/.[1]", &[3]*string{}, "up", 1, []string{"up"}},
232		{"/.[2]", &[]string{"1", "2", "3"}, "up", 1, []string{"up"}},
233		{"/.[3]", &[]*string{}, "in", 1, []string{"in"}},
234		{"/.['p']", &map[byte]*string{}, "in", 1, []string{"in"}},
235		{"/.['q']", &map[int16]*string{'q': strptr("orig")}, "up", 1, []string{"up"}},
236		{"/.['r']", &map[uint]string{}, "in", 1, []string{"in"}},
237		{"/.['s']", &map[int64]string{'s': "orig"}, "up", 1, []string{"up"}},
238		{"/.[*]", &map[byte]*string{'x': strptr("orig"), 'y': nil}, "up", 2, []string{"up", "up"}},
239
240		{"/.[11]/.[12]", &map[int32]map[int64]string{}, "11.12", 1, []string{"11.12"}},
241		{"/.[13]/.[14]", &map[int8]**map[int16]string{}, "13.14", 1, []string{"13.14"}},
242		{"/.['w']/X/Y", &map[byte]struct{ X struct{ Y ***string } }{}, "z", 1, []string{"z"}},
243	}
244}
245
246func newGoldenAssignFails() []goldenAssign {
247	return []goldenAssign{
248		// No expression
249		{"", strptr("hello"), "fail", 0, nil},
250
251		// Nil root
252		{"/", nil, "fail", 0, nil},
253
254		// Nil value
255		{"/", strptr("hello"), nil, 0, []string{"hello"}},
256
257		// Not addresable
258		{"/", "hello", "fail", 0, []string{"hello"}},
259
260		// Too abstract
261		{"/X/anyField", &Node{}, "fail", 0, nil},
262
263		// Wrong type
264		{"/Sp", &struct{ Sp *string }{}, 9.98, 0, []string{""}},
265
266		// String modification
267		{"/.[6]", strptr("immutable"), '-', 0, nil},
268
269		// Out of bounds
270		{"/.[8]", &[2]string{}, "fail", 0, nil},
271
272		// Malformed map keys
273		{"/Sk[''']", &struct{ Sk map[string]string }{}, "fail", 0, nil},
274		{"/Ik[''']", &struct{ Ik map[int]string }{}, "fail", 0, nil},
275		{"/Ik[z]", &struct{ Ik map[int]string }{}, "fail", 0, nil},
276		{"/Uk[''']", &struct{ Uk map[uint]string }{}, "fail", 0, nil},
277		{"/Uk[z]", &struct{ Uk map[uint]string }{}, "fail", 0, nil},
278		{"/Fk[z]", &struct{ Fk map[float32]string }{}, "fail", 0, nil},
279		{"/Ck[z]", &struct{ Ck map[complex128]string }{}, "fail", 0, nil},
280		{"/Ck[]", &struct{ Ck map[complex128]string }{}, "fail", 0, nil},
281
282		// Non-exported
283		{`/child/Name`, &Node{}, "fail", 0, nil},
284		{`/ns`, &struct{ ns *string }{}, "fail", 0, nil},
285
286		// Non-exported array
287		{`/na[0]`, &struct{ na [2]string }{}, "fail", 0, []string{""}},
288		{`/na[1]`, &struct{ na [2]*string }{}, "fail", 0, nil},
289		{`/na[*]`, &struct{ na [2]string }{}, "fail", 0, []string{"", ""}},
290
291		// Non-exported slice
292		{`/ns[0]`, &struct{ ns []string }{}, "fail", 0, nil},
293		{`/ns[1]`, &struct{ ns []*string }{ns: []*string{nil, strptr("b")}}, "fail", 0, []string{"b"}},
294		{`/ns[*]`, &struct{ ns []string }{ns: []string{"a"}}, "fail", 0, []string{"a"}},
295
296		// Non-exported map
297		{`/nm[0]`, &struct{ nm map[int]string }{}, "fail", 0, nil},
298		{`/nm[1]`, &struct{ nm map[int]*string }{nm: map[int]*string{1: strptr("b")}}, "fail", 0, []string{"b"}},
299		{`/nm[*]`, &struct{ nm map[int]string }{nm: map[int]string{2: "c"}}, "fail", 0, []string{"c"}},
300	}
301}
302
303func TestAssigns(t *testing.T) {
304	for _, gold := range append(newGoldenAssigns(), newGoldenAssignFails()...) {
305		n := Assign(gold.root, gold.path, gold.value)
306		if n != gold.updates {
307			t.Errorf("Got n=%d, want %d for %s", n, gold.updates, gold.path)
308		}
309
310		got := Strings(gold.path, gold.root)
311		verify.Values(t, gold.path, got, gold.result)
312	}
313}
314
315func BenchmarkAssigns(b *testing.B) {
316	b.StopTimer()
317	todo := b.N
318	for {
319		cases := newGoldenAssigns()
320		b.StartTimer()
321		for _, g := range cases {
322			Assign(g.root, g.path, g.value)
323			todo--
324			if todo == 0 {
325				return
326			}
327		}
328		b.StopTimer()
329	}
330}
331