660 lines
		
	
	
		
			16 KiB
		
	
	
	
		
			Go
		
	
	
			
		
		
	
	
			660 lines
		
	
	
		
			16 KiB
		
	
	
	
		
			Go
		
	
	
| package reference
 | |
| 
 | |
| import (
 | |
| 	_ "crypto/sha256"
 | |
| 	_ "crypto/sha512"
 | |
| 	"encoding/json"
 | |
| 	"strconv"
 | |
| 	"strings"
 | |
| 	"testing"
 | |
| 
 | |
| 	"github.com/opencontainers/go-digest"
 | |
| )
 | |
| 
 | |
| func TestReferenceParse(t *testing.T) {
 | |
| 	// referenceTestcases is a unified set of testcases for
 | |
| 	// testing the parsing of references
 | |
| 	referenceTestcases := []struct {
 | |
| 		// input is the repository name or name component testcase
 | |
| 		input string
 | |
| 		// err is the error expected from Parse, or nil
 | |
| 		err error
 | |
| 		// repository is the string representation for the reference
 | |
| 		repository string
 | |
| 		// domain is the domain expected in the reference
 | |
| 		domain string
 | |
| 		// tag is the tag for the reference
 | |
| 		tag string
 | |
| 		// digest is the digest for the reference (enforces digest reference)
 | |
| 		digest string
 | |
| 	}{
 | |
| 		{
 | |
| 			input:      "test_com",
 | |
| 			repository: "test_com",
 | |
| 		},
 | |
| 		{
 | |
| 			input:      "test.com:tag",
 | |
| 			repository: "test.com",
 | |
| 			tag:        "tag",
 | |
| 		},
 | |
| 		{
 | |
| 			input:      "test.com:5000",
 | |
| 			repository: "test.com",
 | |
| 			tag:        "5000",
 | |
| 		},
 | |
| 		{
 | |
| 			input:      "test.com/repo:tag",
 | |
| 			domain:     "test.com",
 | |
| 			repository: "test.com/repo",
 | |
| 			tag:        "tag",
 | |
| 		},
 | |
| 		{
 | |
| 			input:      "test:5000/repo",
 | |
| 			domain:     "test:5000",
 | |
| 			repository: "test:5000/repo",
 | |
| 		},
 | |
| 		{
 | |
| 			input:      "test:5000/repo:tag",
 | |
| 			domain:     "test:5000",
 | |
| 			repository: "test:5000/repo",
 | |
| 			tag:        "tag",
 | |
| 		},
 | |
| 		{
 | |
| 			input:      "test:5000/repo@sha256:ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff",
 | |
| 			domain:     "test:5000",
 | |
| 			repository: "test:5000/repo",
 | |
| 			digest:     "sha256:ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff",
 | |
| 		},
 | |
| 		{
 | |
| 			input:      "test:5000/repo:tag@sha256:ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff",
 | |
| 			domain:     "test:5000",
 | |
| 			repository: "test:5000/repo",
 | |
| 			tag:        "tag",
 | |
| 			digest:     "sha256:ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff",
 | |
| 		},
 | |
| 		{
 | |
| 			input:      "test:5000/repo",
 | |
| 			domain:     "test:5000",
 | |
| 			repository: "test:5000/repo",
 | |
| 		},
 | |
| 		{
 | |
| 			input: "",
 | |
| 			err:   ErrNameEmpty,
 | |
| 		},
 | |
| 		{
 | |
| 			input: ":justtag",
 | |
| 			err:   ErrReferenceInvalidFormat,
 | |
| 		},
 | |
| 		{
 | |
| 			input: "@sha256:ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff",
 | |
| 			err:   ErrReferenceInvalidFormat,
 | |
| 		},
 | |
| 		{
 | |
| 			input: "repo@sha256:ffffffffffffffffffffffffffffffffff",
 | |
| 			err:   digest.ErrDigestInvalidLength,
 | |
| 		},
 | |
| 		{
 | |
| 			input: "validname@invaliddigest:ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff",
 | |
| 			err:   digest.ErrDigestUnsupported,
 | |
| 		},
 | |
| 		{
 | |
| 			input: "Uppercase:tag",
 | |
| 			err:   ErrNameContainsUppercase,
 | |
| 		},
 | |
| 		// FIXME "Uppercase" is incorrectly handled as a domain-name here, therefore passes.
 | |
| 		// See https://github.com/distribution/distribution/pull/1778, and https://github.com/docker/docker/pull/20175
 | |
| 		//{
 | |
| 		//	input: "Uppercase/lowercase:tag",
 | |
| 		//	err:   ErrNameContainsUppercase,
 | |
| 		//},
 | |
| 		{
 | |
| 			input: "test:5000/Uppercase/lowercase:tag",
 | |
| 			err:   ErrNameContainsUppercase,
 | |
| 		},
 | |
| 		{
 | |
| 			input:      "lowercase:Uppercase",
 | |
| 			repository: "lowercase",
 | |
| 			tag:        "Uppercase",
 | |
| 		},
 | |
| 		{
 | |
| 			input: strings.Repeat("a/", 128) + "a:tag",
 | |
| 			err:   ErrNameTooLong,
 | |
| 		},
 | |
| 		{
 | |
| 			input:      strings.Repeat("a/", 127) + "a:tag-puts-this-over-max",
 | |
| 			domain:     "a",
 | |
| 			repository: strings.Repeat("a/", 127) + "a",
 | |
| 			tag:        "tag-puts-this-over-max",
 | |
| 		},
 | |
| 		{
 | |
| 			input: "aa/asdf$$^/aa",
 | |
| 			err:   ErrReferenceInvalidFormat,
 | |
| 		},
 | |
| 		{
 | |
| 			input:      "sub-dom1.foo.com/bar/baz/quux",
 | |
| 			domain:     "sub-dom1.foo.com",
 | |
| 			repository: "sub-dom1.foo.com/bar/baz/quux",
 | |
| 		},
 | |
| 		{
 | |
| 			input:      "sub-dom1.foo.com/bar/baz/quux:some-long-tag",
 | |
| 			domain:     "sub-dom1.foo.com",
 | |
| 			repository: "sub-dom1.foo.com/bar/baz/quux",
 | |
| 			tag:        "some-long-tag",
 | |
| 		},
 | |
| 		{
 | |
| 			input:      "b.gcr.io/test.example.com/my-app:test.example.com",
 | |
| 			domain:     "b.gcr.io",
 | |
| 			repository: "b.gcr.io/test.example.com/my-app",
 | |
| 			tag:        "test.example.com",
 | |
| 		},
 | |
| 		{
 | |
| 			input:      "xn--n3h.com/myimage:xn--n3h.com", // ☃.com in punycode
 | |
| 			domain:     "xn--n3h.com",
 | |
| 			repository: "xn--n3h.com/myimage",
 | |
| 			tag:        "xn--n3h.com",
 | |
| 		},
 | |
| 		{
 | |
| 			input:      "xn--7o8h.com/myimage:xn--7o8h.com@sha512:ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff", // 🐳.com in punycode
 | |
| 			domain:     "xn--7o8h.com",
 | |
| 			repository: "xn--7o8h.com/myimage",
 | |
| 			tag:        "xn--7o8h.com",
 | |
| 			digest:     "sha512:ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff",
 | |
| 		},
 | |
| 		{
 | |
| 			input:      "foo_bar.com:8080",
 | |
| 			repository: "foo_bar.com",
 | |
| 			tag:        "8080",
 | |
| 		},
 | |
| 		{
 | |
| 			input:      "foo/foo_bar.com:8080",
 | |
| 			domain:     "foo",
 | |
| 			repository: "foo/foo_bar.com",
 | |
| 			tag:        "8080",
 | |
| 		},
 | |
| 	}
 | |
| 	for _, testcase := range referenceTestcases {
 | |
| 		failf := func(format string, v ...interface{}) {
 | |
| 			t.Logf(strconv.Quote(testcase.input)+": "+format, v...)
 | |
| 			t.Fail()
 | |
| 		}
 | |
| 
 | |
| 		repo, err := Parse(testcase.input)
 | |
| 		if testcase.err != nil {
 | |
| 			if err == nil {
 | |
| 				failf("missing expected error: %v", testcase.err)
 | |
| 			} else if testcase.err != err {
 | |
| 				failf("mismatched error: got %v, expected %v", err, testcase.err)
 | |
| 			}
 | |
| 			continue
 | |
| 		} else if err != nil {
 | |
| 			failf("unexpected parse error: %v", err)
 | |
| 			continue
 | |
| 		}
 | |
| 		if repo.String() != testcase.input {
 | |
| 			failf("mismatched repo: got %q, expected %q", repo.String(), testcase.input)
 | |
| 		}
 | |
| 
 | |
| 		if named, ok := repo.(Named); ok {
 | |
| 			if named.Name() != testcase.repository {
 | |
| 				failf("unexpected repository: got %q, expected %q", named.Name(), testcase.repository)
 | |
| 			}
 | |
| 			domain, _ := SplitHostname(named)
 | |
| 			if domain != testcase.domain {
 | |
| 				failf("unexpected domain: got %q, expected %q", domain, testcase.domain)
 | |
| 			}
 | |
| 		} else if testcase.repository != "" || testcase.domain != "" {
 | |
| 			failf("expected named type, got %T", repo)
 | |
| 		}
 | |
| 
 | |
| 		tagged, ok := repo.(Tagged)
 | |
| 		if testcase.tag != "" {
 | |
| 			if ok {
 | |
| 				if tagged.Tag() != testcase.tag {
 | |
| 					failf("unexpected tag: got %q, expected %q", tagged.Tag(), testcase.tag)
 | |
| 				}
 | |
| 			} else {
 | |
| 				failf("expected tagged type, got %T", repo)
 | |
| 			}
 | |
| 		} else if ok {
 | |
| 			failf("unexpected tagged type")
 | |
| 		}
 | |
| 
 | |
| 		digested, ok := repo.(Digested)
 | |
| 		if testcase.digest != "" {
 | |
| 			if ok {
 | |
| 				if digested.Digest().String() != testcase.digest {
 | |
| 					failf("unexpected digest: got %q, expected %q", digested.Digest().String(), testcase.digest)
 | |
| 				}
 | |
| 			} else {
 | |
| 				failf("expected digested type, got %T", repo)
 | |
| 			}
 | |
| 		} else if ok {
 | |
| 			failf("unexpected digested type")
 | |
| 		}
 | |
| 
 | |
| 	}
 | |
| }
 | |
| 
 | |
| // TestWithNameFailure tests cases where WithName should fail. Cases where it
 | |
| // should succeed are covered by TestSplitHostname, below.
 | |
| func TestWithNameFailure(t *testing.T) {
 | |
| 	testcases := []struct {
 | |
| 		input string
 | |
| 		err   error
 | |
| 	}{
 | |
| 		{
 | |
| 			input: "",
 | |
| 			err:   ErrNameEmpty,
 | |
| 		},
 | |
| 		{
 | |
| 			input: ":justtag",
 | |
| 			err:   ErrReferenceInvalidFormat,
 | |
| 		},
 | |
| 		{
 | |
| 			input: "@sha256:ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff",
 | |
| 			err:   ErrReferenceInvalidFormat,
 | |
| 		},
 | |
| 		{
 | |
| 			input: "validname@invaliddigest:ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff",
 | |
| 			err:   ErrReferenceInvalidFormat,
 | |
| 		},
 | |
| 		{
 | |
| 			input: strings.Repeat("a/", 128) + "a:tag",
 | |
| 			err:   ErrNameTooLong,
 | |
| 		},
 | |
| 		{
 | |
| 			input: "aa/asdf$$^/aa",
 | |
| 			err:   ErrReferenceInvalidFormat,
 | |
| 		},
 | |
| 	}
 | |
| 	for _, testcase := range testcases {
 | |
| 		failf := func(format string, v ...interface{}) {
 | |
| 			t.Logf(strconv.Quote(testcase.input)+": "+format, v...)
 | |
| 			t.Fail()
 | |
| 		}
 | |
| 
 | |
| 		_, err := WithName(testcase.input)
 | |
| 		if err == nil {
 | |
| 			failf("no error parsing name. expected: %s", testcase.err)
 | |
| 		}
 | |
| 	}
 | |
| }
 | |
| 
 | |
| func TestSplitHostname(t *testing.T) {
 | |
| 	testcases := []struct {
 | |
| 		input  string
 | |
| 		domain string
 | |
| 		name   string
 | |
| 	}{
 | |
| 		{
 | |
| 			input:  "test.com/foo",
 | |
| 			domain: "test.com",
 | |
| 			name:   "foo",
 | |
| 		},
 | |
| 		{
 | |
| 			input:  "test_com/foo",
 | |
| 			domain: "",
 | |
| 			name:   "test_com/foo",
 | |
| 		},
 | |
| 		{
 | |
| 			input:  "test:8080/foo",
 | |
| 			domain: "test:8080",
 | |
| 			name:   "foo",
 | |
| 		},
 | |
| 		{
 | |
| 			input:  "test.com:8080/foo",
 | |
| 			domain: "test.com:8080",
 | |
| 			name:   "foo",
 | |
| 		},
 | |
| 		{
 | |
| 			input:  "test-com:8080/foo",
 | |
| 			domain: "test-com:8080",
 | |
| 			name:   "foo",
 | |
| 		},
 | |
| 		{
 | |
| 			input:  "xn--n3h.com:18080/foo",
 | |
| 			domain: "xn--n3h.com:18080",
 | |
| 			name:   "foo",
 | |
| 		},
 | |
| 	}
 | |
| 	for _, testcase := range testcases {
 | |
| 		failf := func(format string, v ...interface{}) {
 | |
| 			t.Logf(strconv.Quote(testcase.input)+": "+format, v...)
 | |
| 			t.Fail()
 | |
| 		}
 | |
| 
 | |
| 		named, err := WithName(testcase.input)
 | |
| 		if err != nil {
 | |
| 			failf("error parsing name: %s", err)
 | |
| 		}
 | |
| 		domain, name := SplitHostname(named)
 | |
| 		if domain != testcase.domain {
 | |
| 			failf("unexpected domain: got %q, expected %q", domain, testcase.domain)
 | |
| 		}
 | |
| 		if name != testcase.name {
 | |
| 			failf("unexpected name: got %q, expected %q", name, testcase.name)
 | |
| 		}
 | |
| 	}
 | |
| }
 | |
| 
 | |
| type serializationType struct {
 | |
| 	Description string
 | |
| 	Field       Field
 | |
| }
 | |
| 
 | |
| func TestSerialization(t *testing.T) {
 | |
| 	testcases := []struct {
 | |
| 		description string
 | |
| 		input       string
 | |
| 		name        string
 | |
| 		tag         string
 | |
| 		digest      string
 | |
| 		err         error
 | |
| 	}{
 | |
| 		{
 | |
| 			description: "empty value",
 | |
| 			err:         ErrNameEmpty,
 | |
| 		},
 | |
| 		{
 | |
| 			description: "just a name",
 | |
| 			input:       "example.com:8000/named",
 | |
| 			name:        "example.com:8000/named",
 | |
| 		},
 | |
| 		{
 | |
| 			description: "name with a tag",
 | |
| 			input:       "example.com:8000/named:tagged",
 | |
| 			name:        "example.com:8000/named",
 | |
| 			tag:         "tagged",
 | |
| 		},
 | |
| 		{
 | |
| 			description: "name with digest",
 | |
| 			input:       "other.com/named@sha256:1234567890098765432112345667890098765432112345667890098765432112",
 | |
| 			name:        "other.com/named",
 | |
| 			digest:      "sha256:1234567890098765432112345667890098765432112345667890098765432112",
 | |
| 		},
 | |
| 	}
 | |
| 	for _, testcase := range testcases {
 | |
| 		failf := func(format string, v ...interface{}) {
 | |
| 			t.Logf(strconv.Quote(testcase.input)+": "+format, v...)
 | |
| 			t.Fail()
 | |
| 		}
 | |
| 
 | |
| 		m := map[string]string{
 | |
| 			"Description": testcase.description,
 | |
| 			"Field":       testcase.input,
 | |
| 		}
 | |
| 		b, err := json.Marshal(m)
 | |
| 		if err != nil {
 | |
| 			failf("error marshalling: %v", err)
 | |
| 		}
 | |
| 		t := serializationType{}
 | |
| 
 | |
| 		if err := json.Unmarshal(b, &t); err != nil {
 | |
| 			if testcase.err == nil {
 | |
| 				failf("error unmarshalling: %v", err)
 | |
| 			}
 | |
| 			if err != testcase.err {
 | |
| 				failf("wrong error, expected %v, got %v", testcase.err, err)
 | |
| 			}
 | |
| 
 | |
| 			continue
 | |
| 		} else if testcase.err != nil {
 | |
| 			failf("expected error unmarshalling: %v", testcase.err)
 | |
| 		}
 | |
| 
 | |
| 		if t.Description != testcase.description {
 | |
| 			failf("wrong description, expected %q, got %q", testcase.description, t.Description)
 | |
| 		}
 | |
| 
 | |
| 		ref := t.Field.Reference()
 | |
| 
 | |
| 		if named, ok := ref.(Named); ok {
 | |
| 			if named.Name() != testcase.name {
 | |
| 				failf("unexpected repository: got %q, expected %q", named.Name(), testcase.name)
 | |
| 			}
 | |
| 		} else if testcase.name != "" {
 | |
| 			failf("expected named type, got %T", ref)
 | |
| 		}
 | |
| 
 | |
| 		tagged, ok := ref.(Tagged)
 | |
| 		if testcase.tag != "" {
 | |
| 			if ok {
 | |
| 				if tagged.Tag() != testcase.tag {
 | |
| 					failf("unexpected tag: got %q, expected %q", tagged.Tag(), testcase.tag)
 | |
| 				}
 | |
| 			} else {
 | |
| 				failf("expected tagged type, got %T", ref)
 | |
| 			}
 | |
| 		} else if ok {
 | |
| 			failf("unexpected tagged type")
 | |
| 		}
 | |
| 
 | |
| 		digested, ok := ref.(Digested)
 | |
| 		if testcase.digest != "" {
 | |
| 			if ok {
 | |
| 				if digested.Digest().String() != testcase.digest {
 | |
| 					failf("unexpected digest: got %q, expected %q", digested.Digest().String(), testcase.digest)
 | |
| 				}
 | |
| 			} else {
 | |
| 				failf("expected digested type, got %T", ref)
 | |
| 			}
 | |
| 		} else if ok {
 | |
| 			failf("unexpected digested type")
 | |
| 		}
 | |
| 
 | |
| 		t = serializationType{
 | |
| 			Description: testcase.description,
 | |
| 			Field:       AsField(ref),
 | |
| 		}
 | |
| 
 | |
| 		b2, err := json.Marshal(t)
 | |
| 		if err != nil {
 | |
| 			failf("error marshing serialization type: %v", err)
 | |
| 		}
 | |
| 
 | |
| 		if string(b) != string(b2) {
 | |
| 			failf("unexpected serialized value: expected %q, got %q", string(b), string(b2))
 | |
| 		}
 | |
| 
 | |
| 		// Ensure t.Field is not implementing "Reference" directly, getting
 | |
| 		// around the Reference type system
 | |
| 		var fieldInterface interface{} = t.Field
 | |
| 		if _, ok := fieldInterface.(Reference); ok {
 | |
| 			failf("field should not implement Reference interface")
 | |
| 		}
 | |
| 
 | |
| 	}
 | |
| }
 | |
| 
 | |
| func TestWithTag(t *testing.T) {
 | |
| 	testcases := []struct {
 | |
| 		name     string
 | |
| 		digest   digest.Digest
 | |
| 		tag      string
 | |
| 		combined string
 | |
| 	}{
 | |
| 		{
 | |
| 			name:     "test.com/foo",
 | |
| 			tag:      "tag",
 | |
| 			combined: "test.com/foo:tag",
 | |
| 		},
 | |
| 		{
 | |
| 			name:     "foo",
 | |
| 			tag:      "tag2",
 | |
| 			combined: "foo:tag2",
 | |
| 		},
 | |
| 		{
 | |
| 			name:     "test.com:8000/foo",
 | |
| 			tag:      "tag4",
 | |
| 			combined: "test.com:8000/foo:tag4",
 | |
| 		},
 | |
| 		{
 | |
| 			name:     "test.com:8000/foo",
 | |
| 			tag:      "TAG5",
 | |
| 			combined: "test.com:8000/foo:TAG5",
 | |
| 		},
 | |
| 		{
 | |
| 			name:     "test.com:8000/foo",
 | |
| 			digest:   "sha256:1234567890098765432112345667890098765",
 | |
| 			tag:      "TAG5",
 | |
| 			combined: "test.com:8000/foo:TAG5@sha256:1234567890098765432112345667890098765",
 | |
| 		},
 | |
| 	}
 | |
| 	for _, testcase := range testcases {
 | |
| 		failf := func(format string, v ...interface{}) {
 | |
| 			t.Logf(strconv.Quote(testcase.name)+": "+format, v...)
 | |
| 			t.Fail()
 | |
| 		}
 | |
| 
 | |
| 		named, err := WithName(testcase.name)
 | |
| 		if err != nil {
 | |
| 			failf("error parsing name: %s", err)
 | |
| 		}
 | |
| 		if testcase.digest != "" {
 | |
| 			canonical, err := WithDigest(named, testcase.digest)
 | |
| 			if err != nil {
 | |
| 				failf("error adding digest")
 | |
| 			}
 | |
| 			named = canonical
 | |
| 		}
 | |
| 
 | |
| 		tagged, err := WithTag(named, testcase.tag)
 | |
| 		if err != nil {
 | |
| 			failf("WithTag failed: %s", err)
 | |
| 		}
 | |
| 		if tagged.String() != testcase.combined {
 | |
| 			failf("unexpected: got %q, expected %q", tagged.String(), testcase.combined)
 | |
| 		}
 | |
| 	}
 | |
| }
 | |
| 
 | |
| func TestWithDigest(t *testing.T) {
 | |
| 	testcases := []struct {
 | |
| 		name     string
 | |
| 		digest   digest.Digest
 | |
| 		tag      string
 | |
| 		combined string
 | |
| 	}{
 | |
| 		{
 | |
| 			name:     "test.com/foo",
 | |
| 			digest:   "sha256:1234567890098765432112345667890098765",
 | |
| 			combined: "test.com/foo@sha256:1234567890098765432112345667890098765",
 | |
| 		},
 | |
| 		{
 | |
| 			name:     "foo",
 | |
| 			digest:   "sha256:1234567890098765432112345667890098765",
 | |
| 			combined: "foo@sha256:1234567890098765432112345667890098765",
 | |
| 		},
 | |
| 		{
 | |
| 			name:     "test.com:8000/foo",
 | |
| 			digest:   "sha256:1234567890098765432112345667890098765",
 | |
| 			combined: "test.com:8000/foo@sha256:1234567890098765432112345667890098765",
 | |
| 		},
 | |
| 		{
 | |
| 			name:     "test.com:8000/foo",
 | |
| 			digest:   "sha256:1234567890098765432112345667890098765",
 | |
| 			tag:      "latest",
 | |
| 			combined: "test.com:8000/foo:latest@sha256:1234567890098765432112345667890098765",
 | |
| 		},
 | |
| 	}
 | |
| 	for _, testcase := range testcases {
 | |
| 		failf := func(format string, v ...interface{}) {
 | |
| 			t.Logf(strconv.Quote(testcase.name)+": "+format, v...)
 | |
| 			t.Fail()
 | |
| 		}
 | |
| 
 | |
| 		named, err := WithName(testcase.name)
 | |
| 		if err != nil {
 | |
| 			failf("error parsing name: %s", err)
 | |
| 		}
 | |
| 		if testcase.tag != "" {
 | |
| 			tagged, err := WithTag(named, testcase.tag)
 | |
| 			if err != nil {
 | |
| 				failf("error adding tag")
 | |
| 			}
 | |
| 			named = tagged
 | |
| 		}
 | |
| 		digested, err := WithDigest(named, testcase.digest)
 | |
| 		if err != nil {
 | |
| 			failf("WithDigest failed: %s", err)
 | |
| 		}
 | |
| 		if digested.String() != testcase.combined {
 | |
| 			failf("unexpected: got %q, expected %q", digested.String(), testcase.combined)
 | |
| 		}
 | |
| 	}
 | |
| }
 | |
| 
 | |
| func TestParseNamed(t *testing.T) {
 | |
| 	testcases := []struct {
 | |
| 		input  string
 | |
| 		domain string
 | |
| 		name   string
 | |
| 		err    error
 | |
| 	}{
 | |
| 		{
 | |
| 			input:  "test.com/foo",
 | |
| 			domain: "test.com",
 | |
| 			name:   "foo",
 | |
| 		},
 | |
| 		{
 | |
| 			input:  "test:8080/foo",
 | |
| 			domain: "test:8080",
 | |
| 			name:   "foo",
 | |
| 		},
 | |
| 		{
 | |
| 			input: "test_com/foo",
 | |
| 			err:   ErrNameNotCanonical,
 | |
| 		},
 | |
| 		{
 | |
| 			input: "test.com",
 | |
| 			err:   ErrNameNotCanonical,
 | |
| 		},
 | |
| 		{
 | |
| 			input: "foo",
 | |
| 			err:   ErrNameNotCanonical,
 | |
| 		},
 | |
| 		{
 | |
| 			input: "library/foo",
 | |
| 			err:   ErrNameNotCanonical,
 | |
| 		},
 | |
| 		{
 | |
| 			input:  "docker.io/library/foo",
 | |
| 			domain: "docker.io",
 | |
| 			name:   "library/foo",
 | |
| 		},
 | |
| 		// Ambiguous case, parser will add "library/" to foo
 | |
| 		{
 | |
| 			input: "docker.io/foo",
 | |
| 			err:   ErrNameNotCanonical,
 | |
| 		},
 | |
| 	}
 | |
| 	for _, testcase := range testcases {
 | |
| 		failf := func(format string, v ...interface{}) {
 | |
| 			t.Logf(strconv.Quote(testcase.input)+": "+format, v...)
 | |
| 			t.Fail()
 | |
| 		}
 | |
| 
 | |
| 		named, err := ParseNamed(testcase.input)
 | |
| 		if err != nil && testcase.err == nil {
 | |
| 			failf("error parsing name: %s", err)
 | |
| 			continue
 | |
| 		} else if err == nil && testcase.err != nil {
 | |
| 			failf("parsing succeeded: expected error %v", testcase.err)
 | |
| 			continue
 | |
| 		} else if err != testcase.err {
 | |
| 			failf("unexpected error %v, expected %v", err, testcase.err)
 | |
| 			continue
 | |
| 		} else if err != nil {
 | |
| 			continue
 | |
| 		}
 | |
| 
 | |
| 		domain, name := SplitHostname(named)
 | |
| 		if domain != testcase.domain {
 | |
| 			failf("unexpected domain: got %q, expected %q", domain, testcase.domain)
 | |
| 		}
 | |
| 		if name != testcase.name {
 | |
| 			failf("unexpected name: got %q, expected %q", name, testcase.name)
 | |
| 		}
 | |
| 	}
 | |
| }
 |