1package query 2 3import ( 4 "fmt" 5 "io/ioutil" 6 "sort" 7 "strings" 8 "testing" 9 "time" 10 "github.com/pelletier/go-toml" 11) 12 13type queryTestNode struct { 14 value interface{} 15 position toml.Position 16} 17 18func valueString(root interface{}) string { 19 result := "" //fmt.Sprintf("%T:", root) 20 switch node := root.(type) { 21 case *Result: 22 items := []string{} 23 for i, v := range node.Values() { 24 items = append(items, fmt.Sprintf("%s:%s", 25 node.Positions()[i].String(), valueString(v))) 26 } 27 sort.Strings(items) 28 result = "[" + strings.Join(items, ", ") + "]" 29 case queryTestNode: 30 result = fmt.Sprintf("%s:%s", 31 node.position.String(), valueString(node.value)) 32 case []interface{}: 33 items := []string{} 34 for _, v := range node { 35 items = append(items, valueString(v)) 36 } 37 sort.Strings(items) 38 result = "[" + strings.Join(items, ", ") + "]" 39 case *toml.Tree: 40 // workaround for unreliable map key ordering 41 items := []string{} 42 for _, k := range node.Keys() { 43 v := node.GetPath([]string{k}) 44 items = append(items, k+":"+valueString(v)) 45 } 46 sort.Strings(items) 47 result = "{" + strings.Join(items, ", ") + "}" 48 case map[string]interface{}: 49 // workaround for unreliable map key ordering 50 items := []string{} 51 for k, v := range node { 52 items = append(items, k+":"+valueString(v)) 53 } 54 sort.Strings(items) 55 result = "{" + strings.Join(items, ", ") + "}" 56 case int64: 57 result += fmt.Sprintf("%d", node) 58 case string: 59 result += "'" + node + "'" 60 case float64: 61 result += fmt.Sprintf("%f", node) 62 case bool: 63 result += fmt.Sprintf("%t", node) 64 case time.Time: 65 result += fmt.Sprintf("'%v'", node) 66 } 67 return result 68} 69 70func assertValue(t *testing.T, result, ref interface{}) { 71 pathStr := valueString(result) 72 refStr := valueString(ref) 73 if pathStr != refStr { 74 t.Errorf("values do not match") 75 t.Log("test:", pathStr) 76 t.Log("ref: ", refStr) 77 } 78} 79 80func assertQueryPositions(t *testing.T, tomlDoc string, query string, ref []interface{}) { 81 tree, err := toml.Load(tomlDoc) 82 if err != nil { 83 t.Errorf("Non-nil toml parse error: %v", err) 84 return 85 } 86 q, err := Compile(query) 87 if err != nil { 88 t.Error(err) 89 return 90 } 91 results := q.Execute(tree) 92 assertValue(t, results, ref) 93} 94 95func TestQueryRoot(t *testing.T) { 96 assertQueryPositions(t, 97 "a = 42", 98 "$", 99 []interface{}{ 100 queryTestNode{ 101 map[string]interface{}{ 102 "a": int64(42), 103 }, toml.Position{1, 1}, 104 }, 105 }) 106} 107 108func TestQueryKey(t *testing.T) { 109 assertQueryPositions(t, 110 "[foo]\na = 42", 111 "$.foo.a", 112 []interface{}{ 113 queryTestNode{ 114 int64(42), toml.Position{2, 1}, 115 }, 116 }) 117} 118 119func TestQueryKeyString(t *testing.T) { 120 assertQueryPositions(t, 121 "[foo]\na = 42", 122 "$.foo['a']", 123 []interface{}{ 124 queryTestNode{ 125 int64(42), toml.Position{2, 1}, 126 }, 127 }) 128} 129 130func TestQueryIndex(t *testing.T) { 131 assertQueryPositions(t, 132 "[foo]\na = [1,2,3,4,5,6,7,8,9,0]", 133 "$.foo.a[5]", 134 []interface{}{ 135 queryTestNode{ 136 int64(6), toml.Position{2, 1}, 137 }, 138 }) 139} 140 141func TestQuerySliceRange(t *testing.T) { 142 assertQueryPositions(t, 143 "[foo]\na = [1,2,3,4,5,6,7,8,9,0]", 144 "$.foo.a[0:5]", 145 []interface{}{ 146 queryTestNode{ 147 int64(1), toml.Position{2, 1}, 148 }, 149 queryTestNode{ 150 int64(2), toml.Position{2, 1}, 151 }, 152 queryTestNode{ 153 int64(3), toml.Position{2, 1}, 154 }, 155 queryTestNode{ 156 int64(4), toml.Position{2, 1}, 157 }, 158 queryTestNode{ 159 int64(5), toml.Position{2, 1}, 160 }, 161 }) 162} 163 164func TestQuerySliceStep(t *testing.T) { 165 assertQueryPositions(t, 166 "[foo]\na = [1,2,3,4,5,6,7,8,9,0]", 167 "$.foo.a[0:5:2]", 168 []interface{}{ 169 queryTestNode{ 170 int64(1), toml.Position{2, 1}, 171 }, 172 queryTestNode{ 173 int64(3), toml.Position{2, 1}, 174 }, 175 queryTestNode{ 176 int64(5), toml.Position{2, 1}, 177 }, 178 }) 179} 180 181func TestQueryAny(t *testing.T) { 182 assertQueryPositions(t, 183 "[foo.bar]\na=1\nb=2\n[foo.baz]\na=3\nb=4", 184 "$.foo.*", 185 []interface{}{ 186 queryTestNode{ 187 map[string]interface{}{ 188 "a": int64(1), 189 "b": int64(2), 190 }, toml.Position{1, 1}, 191 }, 192 queryTestNode{ 193 map[string]interface{}{ 194 "a": int64(3), 195 "b": int64(4), 196 }, toml.Position{4, 1}, 197 }, 198 }) 199} 200func TestQueryUnionSimple(t *testing.T) { 201 assertQueryPositions(t, 202 "[foo.bar]\na=1\nb=2\n[baz.foo]\na=3\nb=4\n[gorf.foo]\na=5\nb=6", 203 "$.*[bar,foo]", 204 []interface{}{ 205 queryTestNode{ 206 map[string]interface{}{ 207 "a": int64(1), 208 "b": int64(2), 209 }, toml.Position{1, 1}, 210 }, 211 queryTestNode{ 212 map[string]interface{}{ 213 "a": int64(3), 214 "b": int64(4), 215 }, toml.Position{4, 1}, 216 }, 217 queryTestNode{ 218 map[string]interface{}{ 219 "a": int64(5), 220 "b": int64(6), 221 }, toml.Position{7, 1}, 222 }, 223 }) 224} 225 226func TestQueryRecursionAll(t *testing.T) { 227 assertQueryPositions(t, 228 "[foo.bar]\na=1\nb=2\n[baz.foo]\na=3\nb=4\n[gorf.foo]\na=5\nb=6", 229 "$..*", 230 []interface{}{ 231 queryTestNode{ 232 map[string]interface{}{ 233 "foo": map[string]interface{}{ 234 "bar": map[string]interface{}{ 235 "a": int64(1), 236 "b": int64(2), 237 }, 238 }, 239 "baz": map[string]interface{}{ 240 "foo": map[string]interface{}{ 241 "a": int64(3), 242 "b": int64(4), 243 }, 244 }, 245 "gorf": map[string]interface{}{ 246 "foo": map[string]interface{}{ 247 "a": int64(5), 248 "b": int64(6), 249 }, 250 }, 251 }, toml.Position{1, 1}, 252 }, 253 queryTestNode{ 254 map[string]interface{}{ 255 "bar": map[string]interface{}{ 256 "a": int64(1), 257 "b": int64(2), 258 }, 259 }, toml.Position{1, 1}, 260 }, 261 queryTestNode{ 262 map[string]interface{}{ 263 "a": int64(1), 264 "b": int64(2), 265 }, toml.Position{1, 1}, 266 }, 267 queryTestNode{ 268 int64(1), toml.Position{2, 1}, 269 }, 270 queryTestNode{ 271 int64(2), toml.Position{3, 1}, 272 }, 273 queryTestNode{ 274 map[string]interface{}{ 275 "foo": map[string]interface{}{ 276 "a": int64(3), 277 "b": int64(4), 278 }, 279 }, toml.Position{4, 1}, 280 }, 281 queryTestNode{ 282 map[string]interface{}{ 283 "a": int64(3), 284 "b": int64(4), 285 }, toml.Position{4, 1}, 286 }, 287 queryTestNode{ 288 int64(3), toml.Position{5, 1}, 289 }, 290 queryTestNode{ 291 int64(4), toml.Position{6, 1}, 292 }, 293 queryTestNode{ 294 map[string]interface{}{ 295 "foo": map[string]interface{}{ 296 "a": int64(5), 297 "b": int64(6), 298 }, 299 }, toml.Position{7, 1}, 300 }, 301 queryTestNode{ 302 map[string]interface{}{ 303 "a": int64(5), 304 "b": int64(6), 305 }, toml.Position{7, 1}, 306 }, 307 queryTestNode{ 308 int64(5), toml.Position{8, 1}, 309 }, 310 queryTestNode{ 311 int64(6), toml.Position{9, 1}, 312 }, 313 }) 314} 315 316func TestQueryRecursionUnionSimple(t *testing.T) { 317 assertQueryPositions(t, 318 "[foo.bar]\na=1\nb=2\n[baz.foo]\na=3\nb=4\n[gorf.foo]\na=5\nb=6", 319 "$..['foo','bar']", 320 []interface{}{ 321 queryTestNode{ 322 map[string]interface{}{ 323 "bar": map[string]interface{}{ 324 "a": int64(1), 325 "b": int64(2), 326 }, 327 }, toml.Position{1, 1}, 328 }, 329 queryTestNode{ 330 map[string]interface{}{ 331 "a": int64(3), 332 "b": int64(4), 333 }, toml.Position{4, 1}, 334 }, 335 queryTestNode{ 336 map[string]interface{}{ 337 "a": int64(1), 338 "b": int64(2), 339 }, toml.Position{1, 1}, 340 }, 341 queryTestNode{ 342 map[string]interface{}{ 343 "a": int64(5), 344 "b": int64(6), 345 }, toml.Position{7, 1}, 346 }, 347 }) 348} 349 350func TestQueryFilterFn(t *testing.T) { 351 buff, err := ioutil.ReadFile("../example.toml") 352 if err != nil { 353 t.Error(err) 354 return 355 } 356 357 assertQueryPositions(t, string(buff), 358 "$..[?(int)]", 359 []interface{}{ 360 queryTestNode{ 361 int64(8001), toml.Position{13, 1}, 362 }, 363 queryTestNode{ 364 int64(8001), toml.Position{13, 1}, 365 }, 366 queryTestNode{ 367 int64(8002), toml.Position{13, 1}, 368 }, 369 queryTestNode{ 370 int64(5000), toml.Position{14, 1}, 371 }, 372 }) 373 374 assertQueryPositions(t, string(buff), 375 "$..[?(string)]", 376 []interface{}{ 377 queryTestNode{ 378 "TOML Example", toml.Position{3, 1}, 379 }, 380 queryTestNode{ 381 "Tom Preston-Werner", toml.Position{6, 1}, 382 }, 383 queryTestNode{ 384 "GitHub", toml.Position{7, 1}, 385 }, 386 queryTestNode{ 387 "GitHub Cofounder & CEO\nLikes tater tots and beer.", 388 toml.Position{8, 1}, 389 }, 390 queryTestNode{ 391 "192.168.1.1", toml.Position{12, 1}, 392 }, 393 queryTestNode{ 394 "10.0.0.1", toml.Position{21, 3}, 395 }, 396 queryTestNode{ 397 "eqdc10", toml.Position{22, 3}, 398 }, 399 queryTestNode{ 400 "10.0.0.2", toml.Position{25, 3}, 401 }, 402 queryTestNode{ 403 "eqdc10", toml.Position{26, 3}, 404 }, 405 }) 406 407 assertQueryPositions(t, string(buff), 408 "$..[?(float)]", 409 []interface{}{ 410 // no float values in document 411 }) 412 413 tv, _ := time.Parse(time.RFC3339, "1979-05-27T07:32:00Z") 414 assertQueryPositions(t, string(buff), 415 "$..[?(tree)]", 416 []interface{}{ 417 queryTestNode{ 418 map[string]interface{}{ 419 "name": "Tom Preston-Werner", 420 "organization": "GitHub", 421 "bio": "GitHub Cofounder & CEO\nLikes tater tots and beer.", 422 "dob": tv, 423 }, toml.Position{5, 1}, 424 }, 425 queryTestNode{ 426 map[string]interface{}{ 427 "server": "192.168.1.1", 428 "ports": []interface{}{int64(8001), int64(8001), int64(8002)}, 429 "connection_max": int64(5000), 430 "enabled": true, 431 }, toml.Position{11, 1}, 432 }, 433 queryTestNode{ 434 map[string]interface{}{ 435 "alpha": map[string]interface{}{ 436 "ip": "10.0.0.1", 437 "dc": "eqdc10", 438 }, 439 "beta": map[string]interface{}{ 440 "ip": "10.0.0.2", 441 "dc": "eqdc10", 442 }, 443 }, toml.Position{17, 1}, 444 }, 445 queryTestNode{ 446 map[string]interface{}{ 447 "ip": "10.0.0.1", 448 "dc": "eqdc10", 449 }, toml.Position{20, 3}, 450 }, 451 queryTestNode{ 452 map[string]interface{}{ 453 "ip": "10.0.0.2", 454 "dc": "eqdc10", 455 }, toml.Position{24, 3}, 456 }, 457 queryTestNode{ 458 map[string]interface{}{ 459 "data": []interface{}{ 460 []interface{}{"gamma", "delta"}, 461 []interface{}{int64(1), int64(2)}, 462 }, 463 }, toml.Position{28, 1}, 464 }, 465 }) 466 467 assertQueryPositions(t, string(buff), 468 "$..[?(time)]", 469 []interface{}{ 470 queryTestNode{ 471 tv, toml.Position{9, 1}, 472 }, 473 }) 474 475 assertQueryPositions(t, string(buff), 476 "$..[?(bool)]", 477 []interface{}{ 478 queryTestNode{ 479 true, toml.Position{15, 1}, 480 }, 481 }) 482} 483