1package runtime 2 3import ( 4 "fmt" 5 "reflect" 6 "strings" 7 "testing" 8 9 "github.com/grpc-ecosystem/grpc-gateway/utilities" 10) 11 12const ( 13 validVersion = 1 14 anything = 0 15) 16 17func TestNewPattern(t *testing.T) { 18 for _, spec := range []struct { 19 ops []int 20 pool []string 21 verb string 22 23 stackSizeWant, tailLenWant int 24 }{ 25 {}, 26 { 27 ops: []int{int(utilities.OpNop), anything}, 28 stackSizeWant: 0, 29 tailLenWant: 0, 30 }, 31 { 32 ops: []int{int(utilities.OpPush), anything}, 33 stackSizeWant: 1, 34 tailLenWant: 0, 35 }, 36 { 37 ops: []int{int(utilities.OpLitPush), 0}, 38 pool: []string{"abc"}, 39 stackSizeWant: 1, 40 tailLenWant: 0, 41 }, 42 { 43 ops: []int{int(utilities.OpPushM), anything}, 44 stackSizeWant: 1, 45 tailLenWant: 0, 46 }, 47 { 48 ops: []int{ 49 int(utilities.OpPush), anything, 50 int(utilities.OpConcatN), 1, 51 }, 52 stackSizeWant: 1, 53 tailLenWant: 0, 54 }, 55 { 56 ops: []int{ 57 int(utilities.OpPush), anything, 58 int(utilities.OpConcatN), 1, 59 int(utilities.OpCapture), 0, 60 }, 61 pool: []string{"abc"}, 62 stackSizeWant: 1, 63 tailLenWant: 0, 64 }, 65 { 66 ops: []int{ 67 int(utilities.OpPush), anything, 68 int(utilities.OpLitPush), 0, 69 int(utilities.OpLitPush), 1, 70 int(utilities.OpPushM), anything, 71 int(utilities.OpConcatN), 2, 72 int(utilities.OpCapture), 2, 73 }, 74 pool: []string{"lit1", "lit2", "var1"}, 75 stackSizeWant: 4, 76 tailLenWant: 0, 77 }, 78 { 79 ops: []int{ 80 int(utilities.OpPushM), anything, 81 int(utilities.OpConcatN), 1, 82 int(utilities.OpCapture), 2, 83 int(utilities.OpLitPush), 0, 84 int(utilities.OpLitPush), 1, 85 }, 86 pool: []string{"lit1", "lit2", "var1"}, 87 stackSizeWant: 2, 88 tailLenWant: 2, 89 }, 90 { 91 ops: []int{ 92 int(utilities.OpLitPush), 0, 93 int(utilities.OpLitPush), 1, 94 int(utilities.OpPushM), anything, 95 int(utilities.OpLitPush), 2, 96 int(utilities.OpConcatN), 3, 97 int(utilities.OpLitPush), 3, 98 int(utilities.OpCapture), 4, 99 }, 100 pool: []string{"lit1", "lit2", "lit3", "lit4", "var1"}, 101 stackSizeWant: 4, 102 tailLenWant: 2, 103 }, 104 { 105 ops: []int{int(utilities.OpLitPush), 0}, 106 pool: []string{"abc"}, 107 verb: "LOCK", 108 stackSizeWant: 1, 109 tailLenWant: 0, 110 }, 111 } { 112 pat, err := NewPattern(validVersion, spec.ops, spec.pool, spec.verb) 113 if err != nil { 114 t.Errorf("NewPattern(%d, %v, %q, %q) failed with %v; want success", validVersion, spec.ops, spec.pool, spec.verb, err) 115 continue 116 } 117 if got, want := pat.stacksize, spec.stackSizeWant; got != want { 118 t.Errorf("pat.stacksize = %d; want %d", got, want) 119 } 120 if got, want := pat.tailLen, spec.tailLenWant; got != want { 121 t.Errorf("pat.stacksize = %d; want %d", got, want) 122 } 123 } 124} 125 126func TestNewPatternWithWrongOp(t *testing.T) { 127 for _, spec := range []struct { 128 ops []int 129 pool []string 130 verb string 131 }{ 132 { 133 // op code out of bound 134 ops: []int{-1, anything}, 135 }, 136 { 137 // op code out of bound 138 ops: []int{int(utilities.OpEnd), 0}, 139 }, 140 { 141 // odd number of items 142 ops: []int{int(utilities.OpPush)}, 143 }, 144 { 145 // negative index 146 ops: []int{int(utilities.OpLitPush), -1}, 147 pool: []string{"abc"}, 148 }, 149 { 150 // index out of bound 151 ops: []int{int(utilities.OpLitPush), 1}, 152 pool: []string{"abc"}, 153 }, 154 { 155 // negative # of segments 156 ops: []int{int(utilities.OpConcatN), -1}, 157 pool: []string{"abc"}, 158 }, 159 { 160 // negative index 161 ops: []int{int(utilities.OpCapture), -1}, 162 pool: []string{"abc"}, 163 }, 164 { 165 // index out of bound 166 ops: []int{int(utilities.OpCapture), 1}, 167 pool: []string{"abc"}, 168 }, 169 { 170 // pushM appears twice 171 ops: []int{ 172 int(utilities.OpPushM), anything, 173 int(utilities.OpLitPush), 0, 174 int(utilities.OpPushM), anything, 175 }, 176 pool: []string{"abc"}, 177 }, 178 } { 179 _, err := NewPattern(validVersion, spec.ops, spec.pool, spec.verb) 180 if err == nil { 181 t.Errorf("NewPattern(%d, %v, %q, %q) succeeded; want failure with %v", validVersion, spec.ops, spec.pool, spec.verb, ErrInvalidPattern) 182 continue 183 } 184 if err != ErrInvalidPattern { 185 t.Errorf("NewPattern(%d, %v, %q, %q) failed with %v; want failure with %v", validVersion, spec.ops, spec.pool, spec.verb, err, ErrInvalidPattern) 186 continue 187 } 188 } 189} 190 191func TestNewPatternWithStackUnderflow(t *testing.T) { 192 for _, spec := range []struct { 193 ops []int 194 pool []string 195 verb string 196 }{ 197 { 198 ops: []int{int(utilities.OpConcatN), 1}, 199 }, 200 { 201 ops: []int{int(utilities.OpCapture), 0}, 202 pool: []string{"abc"}, 203 }, 204 } { 205 _, err := NewPattern(validVersion, spec.ops, spec.pool, spec.verb) 206 if err == nil { 207 t.Errorf("NewPattern(%d, %v, %q, %q) succeeded; want failure with %v", validVersion, spec.ops, spec.pool, spec.verb, ErrInvalidPattern) 208 continue 209 } 210 if err != ErrInvalidPattern { 211 t.Errorf("NewPattern(%d, %v, %q, %q) failed with %v; want failure with %v", validVersion, spec.ops, spec.pool, spec.verb, err, ErrInvalidPattern) 212 continue 213 } 214 } 215} 216 217func TestMatch(t *testing.T) { 218 for _, spec := range []struct { 219 ops []int 220 pool []string 221 verb string 222 223 match []string 224 notMatch []string 225 }{ 226 { 227 match: []string{""}, 228 notMatch: []string{"example"}, 229 }, 230 { 231 ops: []int{int(utilities.OpNop), anything}, 232 match: []string{""}, 233 notMatch: []string{"example", "path/to/example"}, 234 }, 235 { 236 ops: []int{int(utilities.OpPush), anything}, 237 match: []string{"abc", "def"}, 238 notMatch: []string{"", "abc/def"}, 239 }, 240 { 241 ops: []int{int(utilities.OpLitPush), 0}, 242 pool: []string{"v1"}, 243 match: []string{"v1"}, 244 notMatch: []string{"", "v2"}, 245 }, 246 { 247 ops: []int{int(utilities.OpPushM), anything}, 248 match: []string{"", "abc", "abc/def", "abc/def/ghi"}, 249 }, 250 { 251 ops: []int{ 252 int(utilities.OpPushM), anything, 253 int(utilities.OpLitPush), 0, 254 }, 255 pool: []string{"tail"}, 256 match: []string{"tail", "abc/tail", "abc/def/tail"}, 257 notMatch: []string{ 258 "", "abc", "abc/def", 259 "tail/extra", "abc/tail/extra", "abc/def/tail/extra", 260 }, 261 }, 262 { 263 ops: []int{ 264 int(utilities.OpLitPush), 0, 265 int(utilities.OpLitPush), 1, 266 int(utilities.OpPush), anything, 267 int(utilities.OpConcatN), 1, 268 int(utilities.OpCapture), 2, 269 }, 270 pool: []string{"v1", "bucket", "name"}, 271 match: []string{"v1/bucket/my-bucket", "v1/bucket/our-bucket"}, 272 notMatch: []string{ 273 "", 274 "v1", 275 "v1/bucket", 276 "v2/bucket/my-bucket", 277 "v1/pubsub/my-topic", 278 }, 279 }, 280 { 281 ops: []int{ 282 int(utilities.OpLitPush), 0, 283 int(utilities.OpLitPush), 1, 284 int(utilities.OpPushM), anything, 285 int(utilities.OpConcatN), 2, 286 int(utilities.OpCapture), 2, 287 }, 288 pool: []string{"v1", "o", "name"}, 289 match: []string{ 290 "v1/o", 291 "v1/o/my-bucket", 292 "v1/o/our-bucket", 293 "v1/o/my-bucket/dir", 294 "v1/o/my-bucket/dir/dir2", 295 "v1/o/my-bucket/dir/dir2/obj", 296 }, 297 notMatch: []string{ 298 "", 299 "v1", 300 "v2/o/my-bucket", 301 "v1/b/my-bucket", 302 }, 303 }, 304 { 305 ops: []int{ 306 int(utilities.OpLitPush), 0, 307 int(utilities.OpLitPush), 1, 308 int(utilities.OpPush), anything, 309 int(utilities.OpConcatN), 2, 310 int(utilities.OpCapture), 2, 311 int(utilities.OpLitPush), 3, 312 int(utilities.OpPush), anything, 313 int(utilities.OpConcatN), 1, 314 int(utilities.OpCapture), 4, 315 }, 316 pool: []string{"v2", "b", "name", "o", "oname"}, 317 match: []string{ 318 "v2/b/my-bucket/o/obj", 319 "v2/b/our-bucket/o/obj", 320 "v2/b/my-bucket/o/dir", 321 }, 322 notMatch: []string{ 323 "", 324 "v2", 325 "v2/b", 326 "v2/b/my-bucket", 327 "v2/b/my-bucket/o", 328 }, 329 }, 330 { 331 ops: []int{int(utilities.OpLitPush), 0}, 332 pool: []string{"v1"}, 333 verb: "LOCK", 334 match: []string{"v1:LOCK"}, 335 notMatch: []string{"v1", "LOCK"}, 336 }, 337 } { 338 pat, err := NewPattern(validVersion, spec.ops, spec.pool, spec.verb) 339 if err != nil { 340 t.Errorf("NewPattern(%d, %v, %q, %q) failed with %v; want success", validVersion, spec.ops, spec.pool, spec.verb, err) 341 continue 342 } 343 344 for _, path := range spec.match { 345 _, err = pat.Match(segments(path)) 346 if err != nil { 347 t.Errorf("pat.Match(%q) failed with %v; want success; pattern = (%v, %q)", path, err, spec.ops, spec.pool) 348 } 349 } 350 351 for _, path := range spec.notMatch { 352 _, err = pat.Match(segments(path)) 353 if err == nil { 354 t.Errorf("pat.Match(%q) succeeded; want failure with %v; pattern = (%v, %q)", path, ErrNotMatch, spec.ops, spec.pool) 355 continue 356 } 357 if err != ErrNotMatch { 358 t.Errorf("pat.Match(%q) failed with %v; want failure with %v; pattern = (%v, %q)", spec.notMatch, err, ErrNotMatch, spec.ops, spec.pool) 359 } 360 } 361 } 362} 363 364func TestMatchWithBinding(t *testing.T) { 365 for _, spec := range []struct { 366 ops []int 367 pool []string 368 path string 369 verb string 370 371 want map[string]string 372 }{ 373 { 374 want: make(map[string]string), 375 }, 376 { 377 ops: []int{int(utilities.OpNop), anything}, 378 want: make(map[string]string), 379 }, 380 { 381 ops: []int{int(utilities.OpPush), anything}, 382 path: "abc", 383 want: make(map[string]string), 384 }, 385 { 386 ops: []int{int(utilities.OpPush), anything}, 387 verb: "LOCK", 388 path: "abc:LOCK", 389 want: make(map[string]string), 390 }, 391 { 392 ops: []int{int(utilities.OpLitPush), 0}, 393 pool: []string{"endpoint"}, 394 path: "endpoint", 395 want: make(map[string]string), 396 }, 397 { 398 ops: []int{int(utilities.OpPushM), anything}, 399 path: "abc/def/ghi", 400 want: make(map[string]string), 401 }, 402 { 403 ops: []int{ 404 int(utilities.OpLitPush), 0, 405 int(utilities.OpLitPush), 1, 406 int(utilities.OpPush), anything, 407 int(utilities.OpConcatN), 1, 408 int(utilities.OpCapture), 2, 409 }, 410 pool: []string{"v1", "bucket", "name"}, 411 path: "v1/bucket/my-bucket", 412 want: map[string]string{ 413 "name": "my-bucket", 414 }, 415 }, 416 { 417 ops: []int{ 418 int(utilities.OpLitPush), 0, 419 int(utilities.OpLitPush), 1, 420 int(utilities.OpPush), anything, 421 int(utilities.OpConcatN), 1, 422 int(utilities.OpCapture), 2, 423 }, 424 pool: []string{"v1", "bucket", "name"}, 425 verb: "LOCK", 426 path: "v1/bucket/my-bucket:LOCK", 427 want: map[string]string{ 428 "name": "my-bucket", 429 }, 430 }, 431 { 432 ops: []int{ 433 int(utilities.OpLitPush), 0, 434 int(utilities.OpLitPush), 1, 435 int(utilities.OpPushM), anything, 436 int(utilities.OpConcatN), 2, 437 int(utilities.OpCapture), 2, 438 }, 439 pool: []string{"v1", "o", "name"}, 440 path: "v1/o/my-bucket/dir/dir2/obj", 441 want: map[string]string{ 442 "name": "o/my-bucket/dir/dir2/obj", 443 }, 444 }, 445 { 446 ops: []int{ 447 int(utilities.OpLitPush), 0, 448 int(utilities.OpLitPush), 1, 449 int(utilities.OpPushM), anything, 450 int(utilities.OpLitPush), 2, 451 int(utilities.OpConcatN), 3, 452 int(utilities.OpCapture), 4, 453 int(utilities.OpLitPush), 3, 454 }, 455 pool: []string{"v1", "o", ".ext", "tail", "name"}, 456 path: "v1/o/my-bucket/dir/dir2/obj/.ext/tail", 457 want: map[string]string{ 458 "name": "o/my-bucket/dir/dir2/obj/.ext", 459 }, 460 }, 461 { 462 ops: []int{ 463 int(utilities.OpLitPush), 0, 464 int(utilities.OpLitPush), 1, 465 int(utilities.OpPush), anything, 466 int(utilities.OpConcatN), 2, 467 int(utilities.OpCapture), 2, 468 int(utilities.OpLitPush), 3, 469 int(utilities.OpPush), anything, 470 int(utilities.OpConcatN), 1, 471 int(utilities.OpCapture), 4, 472 }, 473 pool: []string{"v2", "b", "name", "o", "oname"}, 474 path: "v2/b/my-bucket/o/obj", 475 want: map[string]string{ 476 "name": "b/my-bucket", 477 "oname": "obj", 478 }, 479 }, 480 } { 481 pat, err := NewPattern(validVersion, spec.ops, spec.pool, spec.verb) 482 if err != nil { 483 t.Errorf("NewPattern(%d, %v, %q, %q) failed with %v; want success", validVersion, spec.ops, spec.pool, spec.verb, err) 484 continue 485 } 486 487 got, err := pat.Match(segments(spec.path)) 488 if err != nil { 489 t.Errorf("pat.Match(%q) failed with %v; want success; pattern = (%v, %q)", spec.path, err, spec.ops, spec.pool) 490 } 491 if !reflect.DeepEqual(got, spec.want) { 492 t.Errorf("pat.Match(%q) = %q; want %q; pattern = (%v, %q)", spec.path, got, spec.want, spec.ops, spec.pool) 493 } 494 } 495} 496 497func segments(path string) (components []string, verb string) { 498 if path == "" { 499 return nil, "" 500 } 501 components = strings.Split(path, "/") 502 l := len(components) 503 c := components[l-1] 504 if idx := strings.LastIndex(c, ":"); idx >= 0 { 505 components[l-1], verb = c[:idx], c[idx+1:] 506 } 507 return components, verb 508} 509 510func TestPatternString(t *testing.T) { 511 for _, spec := range []struct { 512 ops []int 513 pool []string 514 515 want string 516 }{ 517 { 518 want: "/", 519 }, 520 { 521 ops: []int{int(utilities.OpNop), anything}, 522 want: "/", 523 }, 524 { 525 ops: []int{int(utilities.OpPush), anything}, 526 want: "/*", 527 }, 528 { 529 ops: []int{int(utilities.OpLitPush), 0}, 530 pool: []string{"endpoint"}, 531 want: "/endpoint", 532 }, 533 { 534 ops: []int{int(utilities.OpPushM), anything}, 535 want: "/**", 536 }, 537 { 538 ops: []int{ 539 int(utilities.OpPush), anything, 540 int(utilities.OpConcatN), 1, 541 }, 542 want: "/*", 543 }, 544 { 545 ops: []int{ 546 int(utilities.OpPush), anything, 547 int(utilities.OpConcatN), 1, 548 int(utilities.OpCapture), 0, 549 }, 550 pool: []string{"name"}, 551 want: "/{name=*}", 552 }, 553 { 554 ops: []int{ 555 int(utilities.OpLitPush), 0, 556 int(utilities.OpLitPush), 1, 557 int(utilities.OpPush), anything, 558 int(utilities.OpConcatN), 2, 559 int(utilities.OpCapture), 2, 560 int(utilities.OpLitPush), 3, 561 int(utilities.OpPushM), anything, 562 int(utilities.OpLitPush), 4, 563 int(utilities.OpConcatN), 3, 564 int(utilities.OpCapture), 6, 565 int(utilities.OpLitPush), 5, 566 }, 567 pool: []string{"v1", "buckets", "bucket_name", "objects", ".ext", "tail", "name"}, 568 want: "/v1/{bucket_name=buckets/*}/{name=objects/**/.ext}/tail", 569 }, 570 } { 571 p, err := NewPattern(validVersion, spec.ops, spec.pool, "") 572 if err != nil { 573 t.Errorf("NewPattern(%d, %v, %q, %q) failed with %v; want success", validVersion, spec.ops, spec.pool, "", err) 574 continue 575 } 576 if got, want := p.String(), spec.want; got != want { 577 t.Errorf("%#v.String() = %q; want %q", p, got, want) 578 } 579 580 verb := "LOCK" 581 p, err = NewPattern(validVersion, spec.ops, spec.pool, verb) 582 if err != nil { 583 t.Errorf("NewPattern(%d, %v, %q, %q) failed with %v; want success", validVersion, spec.ops, spec.pool, verb, err) 584 continue 585 } 586 if got, want := p.String(), fmt.Sprintf("%s:%s", spec.want, verb); got != want { 587 t.Errorf("%#v.String() = %q; want %q", p, got, want) 588 } 589 } 590} 591