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