github.com/mika/distribution@v2.2.2-0.20160108133430-a75790e3d8e0+incompatible/reference/reference_test.go (about) 1 package reference 2 3 import ( 4 "encoding/json" 5 "strconv" 6 "strings" 7 "testing" 8 9 "github.com/docker/distribution/digest" 10 ) 11 12 func TestReferenceParse(t *testing.T) { 13 // referenceTestcases is a unified set of testcases for 14 // testing the parsing of references 15 referenceTestcases := []struct { 16 // input is the repository name or name component testcase 17 input string 18 // err is the error expected from Parse, or nil 19 err error 20 // repository is the string representation for the reference 21 repository string 22 // hostname is the hostname expected in the reference 23 hostname string 24 // tag is the tag for the reference 25 tag string 26 // digest is the digest for the reference (enforces digest reference) 27 digest string 28 }{ 29 { 30 input: "test_com", 31 repository: "test_com", 32 }, 33 { 34 input: "test.com:tag", 35 repository: "test.com", 36 tag: "tag", 37 }, 38 { 39 input: "test.com:5000", 40 repository: "test.com", 41 tag: "5000", 42 }, 43 { 44 input: "test.com/repo:tag", 45 hostname: "test.com", 46 repository: "test.com/repo", 47 tag: "tag", 48 }, 49 { 50 input: "test:5000/repo", 51 hostname: "test:5000", 52 repository: "test:5000/repo", 53 }, 54 { 55 input: "test:5000/repo:tag", 56 hostname: "test:5000", 57 repository: "test:5000/repo", 58 tag: "tag", 59 }, 60 { 61 input: "test:5000/repo@sha256:ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff", 62 hostname: "test:5000", 63 repository: "test:5000/repo", 64 digest: "sha256:ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff", 65 }, 66 { 67 input: "test:5000/repo:tag@sha256:ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff", 68 hostname: "test:5000", 69 repository: "test:5000/repo", 70 tag: "tag", 71 digest: "sha256:ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff", 72 }, 73 { 74 input: "test:5000/repo", 75 hostname: "test:5000", 76 repository: "test:5000/repo", 77 }, 78 { 79 input: "", 80 err: ErrNameEmpty, 81 }, 82 { 83 input: ":justtag", 84 err: ErrReferenceInvalidFormat, 85 }, 86 { 87 input: "@sha256:ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff", 88 err: ErrReferenceInvalidFormat, 89 }, 90 { 91 input: "repo@sha256:ffffffffffffffffffffffffffffffffff", 92 err: digest.ErrDigestInvalidLength, 93 }, 94 { 95 input: "validname@invaliddigest:ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff", 96 err: digest.ErrDigestUnsupported, 97 }, 98 { 99 input: strings.Repeat("a/", 128) + "a:tag", 100 err: ErrNameTooLong, 101 }, 102 { 103 input: strings.Repeat("a/", 127) + "a:tag-puts-this-over-max", 104 hostname: "a", 105 repository: strings.Repeat("a/", 127) + "a", 106 tag: "tag-puts-this-over-max", 107 }, 108 { 109 input: "aa/asdf$$^/aa", 110 err: ErrReferenceInvalidFormat, 111 }, 112 { 113 input: "sub-dom1.foo.com/bar/baz/quux", 114 hostname: "sub-dom1.foo.com", 115 repository: "sub-dom1.foo.com/bar/baz/quux", 116 }, 117 { 118 input: "sub-dom1.foo.com/bar/baz/quux:some-long-tag", 119 hostname: "sub-dom1.foo.com", 120 repository: "sub-dom1.foo.com/bar/baz/quux", 121 tag: "some-long-tag", 122 }, 123 { 124 input: "b.gcr.io/test.example.com/my-app:test.example.com", 125 hostname: "b.gcr.io", 126 repository: "b.gcr.io/test.example.com/my-app", 127 tag: "test.example.com", 128 }, 129 { 130 input: "xn--n3h.com/myimage:xn--n3h.com", // ☃.com in punycode 131 hostname: "xn--n3h.com", 132 repository: "xn--n3h.com/myimage", 133 tag: "xn--n3h.com", 134 }, 135 { 136 input: "xn--7o8h.com/myimage:xn--7o8h.com@sha512:ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff", // 🐳.com in punycode 137 hostname: "xn--7o8h.com", 138 repository: "xn--7o8h.com/myimage", 139 tag: "xn--7o8h.com", 140 digest: "sha512:ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff", 141 }, 142 { 143 input: "foo_bar.com:8080", 144 repository: "foo_bar.com", 145 tag: "8080", 146 }, 147 { 148 input: "foo/foo_bar.com:8080", 149 hostname: "foo", 150 repository: "foo/foo_bar.com", 151 tag: "8080", 152 }, 153 } 154 for _, testcase := range referenceTestcases { 155 failf := func(format string, v ...interface{}) { 156 t.Logf(strconv.Quote(testcase.input)+": "+format, v...) 157 t.Fail() 158 } 159 160 repo, err := Parse(testcase.input) 161 if testcase.err != nil { 162 if err == nil { 163 failf("missing expected error: %v", testcase.err) 164 } else if testcase.err != err { 165 failf("mismatched error: got %v, expected %v", err, testcase.err) 166 } 167 continue 168 } else if err != nil { 169 failf("unexpected parse error: %v", err) 170 continue 171 } 172 if repo.String() != testcase.input { 173 failf("mismatched repo: got %q, expected %q", repo.String(), testcase.input) 174 } 175 176 if named, ok := repo.(Named); ok { 177 if named.Name() != testcase.repository { 178 failf("unexpected repository: got %q, expected %q", named.Name(), testcase.repository) 179 } 180 hostname, _ := SplitHostname(named) 181 if hostname != testcase.hostname { 182 failf("unexpected hostname: got %q, expected %q", hostname, testcase.hostname) 183 } 184 } else if testcase.repository != "" || testcase.hostname != "" { 185 failf("expected named type, got %T", repo) 186 } 187 188 tagged, ok := repo.(Tagged) 189 if testcase.tag != "" { 190 if ok { 191 if tagged.Tag() != testcase.tag { 192 failf("unexpected tag: got %q, expected %q", tagged.Tag(), testcase.tag) 193 } 194 } else { 195 failf("expected tagged type, got %T", repo) 196 } 197 } else if ok { 198 failf("unexpected tagged type") 199 } 200 201 digested, ok := repo.(Digested) 202 if testcase.digest != "" { 203 if ok { 204 if digested.Digest().String() != testcase.digest { 205 failf("unexpected digest: got %q, expected %q", digested.Digest().String(), testcase.digest) 206 } 207 } else { 208 failf("expected digested type, got %T", repo) 209 } 210 } else if ok { 211 failf("unexpected digested type") 212 } 213 214 } 215 } 216 217 // TestWithNameFailure tests cases where WithName should fail. Cases where it 218 // should succeed are covered by TestSplitHostname, below. 219 func TestWithNameFailure(t *testing.T) { 220 testcases := []struct { 221 input string 222 err error 223 }{ 224 { 225 input: "", 226 err: ErrNameEmpty, 227 }, 228 { 229 input: ":justtag", 230 err: ErrReferenceInvalidFormat, 231 }, 232 { 233 input: "@sha256:ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff", 234 err: ErrReferenceInvalidFormat, 235 }, 236 { 237 input: "validname@invaliddigest:ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff", 238 err: ErrReferenceInvalidFormat, 239 }, 240 { 241 input: strings.Repeat("a/", 128) + "a:tag", 242 err: ErrNameTooLong, 243 }, 244 { 245 input: "aa/asdf$$^/aa", 246 err: ErrReferenceInvalidFormat, 247 }, 248 } 249 for _, testcase := range testcases { 250 failf := func(format string, v ...interface{}) { 251 t.Logf(strconv.Quote(testcase.input)+": "+format, v...) 252 t.Fail() 253 } 254 255 _, err := WithName(testcase.input) 256 if err == nil { 257 failf("no error parsing name. expected: %s", testcase.err) 258 } 259 } 260 } 261 262 func TestSplitHostname(t *testing.T) { 263 testcases := []struct { 264 input string 265 hostname string 266 name string 267 }{ 268 { 269 input: "test.com/foo", 270 hostname: "test.com", 271 name: "foo", 272 }, 273 { 274 input: "test_com/foo", 275 hostname: "", 276 name: "test_com/foo", 277 }, 278 { 279 input: "test:8080/foo", 280 hostname: "test:8080", 281 name: "foo", 282 }, 283 { 284 input: "test.com:8080/foo", 285 hostname: "test.com:8080", 286 name: "foo", 287 }, 288 { 289 input: "test-com:8080/foo", 290 hostname: "test-com:8080", 291 name: "foo", 292 }, 293 { 294 input: "xn--n3h.com:18080/foo", 295 hostname: "xn--n3h.com:18080", 296 name: "foo", 297 }, 298 } 299 for _, testcase := range testcases { 300 failf := func(format string, v ...interface{}) { 301 t.Logf(strconv.Quote(testcase.input)+": "+format, v...) 302 t.Fail() 303 } 304 305 named, err := WithName(testcase.input) 306 if err != nil { 307 failf("error parsing name: %s", err) 308 } 309 hostname, name := SplitHostname(named) 310 if hostname != testcase.hostname { 311 failf("unexpected hostname: got %q, expected %q", hostname, testcase.hostname) 312 } 313 if name != testcase.name { 314 failf("unexpected name: got %q, expected %q", name, testcase.name) 315 } 316 } 317 } 318 319 type serializationType struct { 320 Description string 321 Field Field 322 } 323 324 func TestSerialization(t *testing.T) { 325 testcases := []struct { 326 description string 327 input string 328 name string 329 tag string 330 digest string 331 err error 332 }{ 333 { 334 description: "empty value", 335 err: ErrNameEmpty, 336 }, 337 { 338 description: "just a name", 339 input: "example.com:8000/named", 340 name: "example.com:8000/named", 341 }, 342 { 343 description: "name with a tag", 344 input: "example.com:8000/named:tagged", 345 name: "example.com:8000/named", 346 tag: "tagged", 347 }, 348 { 349 description: "name with digest", 350 input: "other.com/named@sha256:1234567890098765432112345667890098765432112345667890098765432112", 351 name: "other.com/named", 352 digest: "sha256:1234567890098765432112345667890098765432112345667890098765432112", 353 }, 354 } 355 for _, testcase := range testcases { 356 failf := func(format string, v ...interface{}) { 357 t.Logf(strconv.Quote(testcase.input)+": "+format, v...) 358 t.Fail() 359 } 360 361 m := map[string]string{ 362 "Description": testcase.description, 363 "Field": testcase.input, 364 } 365 b, err := json.Marshal(m) 366 if err != nil { 367 failf("error marshalling: %v", err) 368 } 369 t := serializationType{} 370 371 if err := json.Unmarshal(b, &t); err != nil { 372 if testcase.err == nil { 373 failf("error unmarshalling: %v", err) 374 } 375 if err != testcase.err { 376 failf("wrong error, expected %v, got %v", testcase.err, err) 377 } 378 379 continue 380 } else if testcase.err != nil { 381 failf("expected error unmarshalling: %v", testcase.err) 382 } 383 384 if t.Description != testcase.description { 385 failf("wrong description, expected %q, got %q", testcase.description, t.Description) 386 } 387 388 ref := t.Field.Reference() 389 390 if named, ok := ref.(Named); ok { 391 if named.Name() != testcase.name { 392 failf("unexpected repository: got %q, expected %q", named.Name(), testcase.name) 393 } 394 } else if testcase.name != "" { 395 failf("expected named type, got %T", ref) 396 } 397 398 tagged, ok := ref.(Tagged) 399 if testcase.tag != "" { 400 if ok { 401 if tagged.Tag() != testcase.tag { 402 failf("unexpected tag: got %q, expected %q", tagged.Tag(), testcase.tag) 403 } 404 } else { 405 failf("expected tagged type, got %T", ref) 406 } 407 } else if ok { 408 failf("unexpected tagged type") 409 } 410 411 digested, ok := ref.(Digested) 412 if testcase.digest != "" { 413 if ok { 414 if digested.Digest().String() != testcase.digest { 415 failf("unexpected digest: got %q, expected %q", digested.Digest().String(), testcase.digest) 416 } 417 } else { 418 failf("expected digested type, got %T", ref) 419 } 420 } else if ok { 421 failf("unexpected digested type") 422 } 423 424 t = serializationType{ 425 Description: testcase.description, 426 Field: AsField(ref), 427 } 428 429 b2, err := json.Marshal(t) 430 if err != nil { 431 failf("error marshing serialization type: %v", err) 432 } 433 434 if string(b) != string(b2) { 435 failf("unexpected serialized value: expected %q, got %q", string(b), string(b2)) 436 } 437 438 // Ensure t.Field is not implementing "Reference" directly, getting 439 // around the Reference type system 440 var fieldInterface interface{} = t.Field 441 if _, ok := fieldInterface.(Reference); ok { 442 failf("field should not implement Reference interface") 443 } 444 445 } 446 } 447 448 func TestWithTag(t *testing.T) { 449 testcases := []struct { 450 name string 451 tag string 452 combined string 453 }{ 454 { 455 name: "test.com/foo", 456 tag: "tag", 457 combined: "test.com/foo:tag", 458 }, 459 { 460 name: "foo", 461 tag: "tag2", 462 combined: "foo:tag2", 463 }, 464 { 465 name: "test.com:8000/foo", 466 tag: "tag4", 467 combined: "test.com:8000/foo:tag4", 468 }, 469 { 470 name: "test.com:8000/foo", 471 tag: "TAG5", 472 combined: "test.com:8000/foo:TAG5", 473 }, 474 } 475 for _, testcase := range testcases { 476 failf := func(format string, v ...interface{}) { 477 t.Logf(strconv.Quote(testcase.name)+": "+format, v...) 478 t.Fail() 479 } 480 481 named, err := WithName(testcase.name) 482 if err != nil { 483 failf("error parsing name: %s", err) 484 } 485 tagged, err := WithTag(named, testcase.tag) 486 if err != nil { 487 failf("WithTag failed: %s", err) 488 } 489 if tagged.String() != testcase.combined { 490 failf("unexpected: got %q, expected %q", tagged.String(), testcase.combined) 491 } 492 } 493 } 494 495 func TestWithDigest(t *testing.T) { 496 testcases := []struct { 497 name string 498 digest digest.Digest 499 combined string 500 }{ 501 { 502 name: "test.com/foo", 503 digest: "sha256:1234567890098765432112345667890098765", 504 combined: "test.com/foo@sha256:1234567890098765432112345667890098765", 505 }, 506 { 507 name: "foo", 508 digest: "sha256:1234567890098765432112345667890098765", 509 combined: "foo@sha256:1234567890098765432112345667890098765", 510 }, 511 { 512 name: "test.com:8000/foo", 513 digest: "sha256:1234567890098765432112345667890098765", 514 combined: "test.com:8000/foo@sha256:1234567890098765432112345667890098765", 515 }, 516 } 517 for _, testcase := range testcases { 518 failf := func(format string, v ...interface{}) { 519 t.Logf(strconv.Quote(testcase.name)+": "+format, v...) 520 t.Fail() 521 } 522 523 named, err := WithName(testcase.name) 524 if err != nil { 525 failf("error parsing name: %s", err) 526 } 527 digested, err := WithDigest(named, testcase.digest) 528 if err != nil { 529 failf("WithDigest failed: %s", err) 530 } 531 if digested.String() != testcase.combined { 532 failf("unexpected: got %q, expected %q", digested.String(), testcase.combined) 533 } 534 } 535 }