Repository: google/go-containerregistry
Pull request #553 (2019-10-01 21:53:38)

Implement pkg/internal/compare, use it
This drops a lot of testing boilerplate.

This is under pkg/internal for now because I'm not sure if I love the interface.

codecov-io: # [Codecov](https://codecov.io/gh/google/go-containerregistry/pull/553?src=pr&el=h1) Report
> Merging [#553](https://codecov.io/gh/google/go-containerregistry/pull/553?src=pr&el=desc) into [master](https://codecov.io/gh/google/go-containerregistry/commit/cf66aa2c31a900626cc59d2747a644d7d8f05ef3?src=pr&el=desc) will **decrease** coverage by `0.08%`.
> The diff coverage is `75.75%`.

[![Impacted file tree graph](https://codecov.io/gh/google/go-containerregistry/pull/553/graphs/tree.svg?width=650&token=wG7iiDCdZ4&height=150&src=pr)](https://codecov.io/gh/google/go-containerregistry/pull/553?src=pr&el=tree)

```diff
@@            Coverage Diff             @@
##           master     #553      +/-   ##
==========================================
- Coverage    72.6%   72.52%   -0.09%     
==========================================
  Files          94       97       +3     
  Lines        4246     4378     +132     
==========================================
+ Hits         3083     3175      +92     
- Misses        769      793      +24     
- Partials      394      410      +16
```


| [Impacted Files](https://codecov.io/gh/google/go-containerregistry/pull/553?src=pr&el=tree) | Coverage Δ | |
|---|---|---|
| [pkg/internal/compare/index.go](https://codecov.io/gh/google/go-containerregistry/pull/553/diff?src=pr&el=tree#diff-cGtnL2ludGVybmFsL2NvbXBhcmUvaW5kZXguZ28=) | `72.97% <72.97%> (ø)` | |
| [pkg/internal/compare/image.go](https://codecov.io/gh/google/go-containerregistry/pull/553/diff?src=pr&el=tree#diff-cGtnL2ludGVybmFsL2NvbXBhcmUvaW1hZ2UuZ28=) | `75.86% <75.86%> (ø)` | |
| [pkg/internal/compare/layer.go](https://codecov.io/gh/google/go-containerregistry/pull/553/diff?src=pr&el=tree#diff-cGtnL2ludGVybmFsL2NvbXBhcmUvbGF5ZXIuZ28=) | `78.37% <78.37%> (ø)` | |
| [pkg/v1/tarball/layer.go](https://codecov.io/gh/google/go-containerregistry/pull/553/diff?src=pr&el=tree#diff-cGtnL3YxL3RhcmJhbGwvbGF5ZXIuZ28=) | `63.88% <0%> (-13.89%)` | :arrow_down: |
| [pkg/v1/tarball/image.go](https://codecov.io/gh/google/go-containerregistry/pull/553/diff?src=pr&el=tree#diff-cGtnL3YxL3RhcmJhbGwvaW1hZ2UuZ28=) | `74.01% <0%> (+1.12%)` | :arrow_up: |

------

[Continue to review full report at Codecov](https://codecov.io/gh/google/go-containerregistry/pull/553?src=pr&el=continue).
> **Legend** - [Click here to learn more](https://docs.codecov.io/docs/codecov-delta)
> `Δ = absolute <relative> (impact)`, `ø = not affected`, `? = missing data`
> Powered by [Codecov](https://codecov.io/gh/google/go-containerregistry/pull/553?src=pr&el=footer). Last update [cf66aa2...939b5a6](https://codecov.io/gh/google/go-containerregistry/pull/553?src=pr&el=lastupdated). Read the [comment docs](https://docs.codecov.io/docs/pull-request-comments).


Patch:
diff --git a/go.mod b/go.mod
index 8c2053d3..24b4a632 100644
--- a/go.mod
+++ b/go.mod
@@ -41,7 +41,7 @@ require (
 	golang.org/x/sync v0.0.0-20190423024810-112230192c58
 	golang.org/x/text v0.3.2 // indirect
 	golang.org/x/time v0.0.0-20180412165947-fbb02b2291d2 // indirect
-	golang.org/x/tools v0.0.0-20191001184121-329c8d646ebe // indirect
+	golang.org/x/tools v0.0.0-20191003162220-c56b4b191e2d // indirect
 	google.golang.org/appengine v1.1.0 // indirect
 	gopkg.in/inf.v0 v0.9.1 // indirect
 	gotest.tools v2.2.0+incompatible // indirect
diff --git a/go.sum b/go.sum
index e1bf4279..745779f0 100644
--- a/go.sum
+++ b/go.sum
@@ -163,6 +163,10 @@ golang.org/x/tools v0.0.0-20190926165942-a8d5d34286bd h1:L7bTtbmMojUZYEAt0OrTU0Z
 golang.org/x/tools v0.0.0-20190926165942-a8d5d34286bd/go.mod h1:b+2E5dAYhXwXZwtnZ6UAqBI28+e2cm9otk0dWdXHAEo=
 golang.org/x/tools v0.0.0-20191001184121-329c8d646ebe h1:hFr8KcN0dM0/dqbUW0KZYN+YXJeZBpBWIG9ZkMuX1vQ=
 golang.org/x/tools v0.0.0-20191001184121-329c8d646ebe/go.mod h1:b+2E5dAYhXwXZwtnZ6UAqBI28+e2cm9otk0dWdXHAEo=
+golang.org/x/tools v0.0.0-20191002161851-3769738f410b h1:H3xgcuTEMnlg2n7BT6mWCOGuMchcPTaZ/uCC9GuV05M=
+golang.org/x/tools v0.0.0-20191002161851-3769738f410b/go.mod h1:b+2E5dAYhXwXZwtnZ6UAqBI28+e2cm9otk0dWdXHAEo=
+golang.org/x/tools v0.0.0-20191003162220-c56b4b191e2d h1:xzbfnqkhzcf2qHVgbBaXlaj+JlFOxT6P/oe2+yVuwBE=
+golang.org/x/tools v0.0.0-20191003162220-c56b4b191e2d/go.mod h1:b+2E5dAYhXwXZwtnZ6UAqBI28+e2cm9otk0dWdXHAEo=
 golang.org/x/xerrors v0.0.0-20190717185122-a985d3407aa7/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0=
 google.golang.org/appengine v1.1.0 h1:igQkv0AAhEIvTEpD5LIpAfav2eeVO9HBTjvKHVJPRSs=
 google.golang.org/appengine v1.1.0/go.mod h1:EbEs0AVv82hx2wNQdGPgUI5lhzA/G0D9YwlJXL52JkM=
diff --git a/pkg/authn/keychain.go b/pkg/authn/keychain.go
index d3dcb48e..ff455350 100644
--- a/pkg/authn/keychain.go
+++ b/pkg/authn/keychain.go
@@ -17,6 +17,7 @@ package authn
 import (
 	"github.com/docker/cli/cli/config"
 	"github.com/docker/cli/cli/config/types"
+	"github.com/google/go-containerregistry/pkg/name"
 )
 
 // Resource represents a registry or repository that can be authenticated against.
@@ -50,6 +51,10 @@ var (
 	configDir = ""
 )
 
+const (
+	defaultAuthKey = "https://" + name.DefaultRegistry + "/v1/"
+)
+
 // Resolve implements Keychain.
 func (dk *defaultKeychain) Resolve(target Resource) (Authenticator, error) {
 	cf, err := config.Load(configDir)
@@ -57,7 +62,15 @@ func (dk *defaultKeychain) Resolve(target Resource) (Authenticator, error) {
 		return nil, err
 	}
 
-	cfg, err := cf.GetAuthConfig(target.RegistryStr())
+	// See:
+	// https://github.com/google/ko/issues/90
+	// https://github.com/moby/moby/blob/fc01c2b481097a6057bec3cd1ab2d7b4488c50c4/registry/config.go#L397-L404
+	key := target.RegistryStr()
+	if key == name.DefaultRegistry {
+		key = defaultAuthKey
+	}
+
+	cfg, err := cf.GetAuthConfig(key)
 	if err != nil {
 		return nil, err
 	}
diff --git a/pkg/internal/compare/doc.go b/pkg/internal/compare/doc.go
new file mode 100644
index 00000000..c8ca4979
--- /dev/null
+++ b/pkg/internal/compare/doc.go
@@ -0,0 +1,16 @@
+// Copyright 2019 Google LLC All Rights Reserved.
+//
+// Licensed under the Apache License, Version 2.0 (the "License");
+// you may not use this file except in compliance with the License.
+// You may obtain a copy of the License at
+//
+//    http://www.apache.org/licenses/LICENSE-2.0
+//
+// Unless required by applicable law or agreed to in writing, software
+// distributed under the License is distributed on an "AS IS" BASIS,
+// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+// See the License for the specific language governing permissions and
+// limitations under the License.
+
+// Package compare provides methods for comparing images, indexes, and layers.
+package compare
diff --git a/pkg/internal/compare/image.go b/pkg/internal/compare/image.go
new file mode 100644
index 00000000..fe5fd4cd
--- /dev/null
+++ b/pkg/internal/compare/image.go
@@ -0,0 +1,111 @@
+// Copyright 2019 Google LLC All Rights Reserved.
+//
+// Licensed under the Apache License, Version 2.0 (the "License");
+// you may not use this file except in compliance with the License.
+// You may obtain a copy of the License at
+//
+//    http://www.apache.org/licenses/LICENSE-2.0
+//
+// Unless required by applicable law or agreed to in writing, software
+// distributed under the License is distributed on an "AS IS" BASIS,
+// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+// See the License for the specific language governing permissions and
+// limitations under the License.
+
+package compare
+
+import (
+	"errors"
+	"fmt"
+	"reflect"
+	"strings"
+
+	v1 "github.com/google/go-containerregistry/pkg/v1"
+	"github.com/google/go-containerregistry/pkg/v1/types"
+)
+
+// Images compares the given images to each other and returns an error if they
+// differ.
+func Images(a, b v1.Image) error {
+	digests := []v1.Hash{}
+	manifests := []*v1.Manifest{}
+	cns := []v1.Hash{}
+	sizes := []int64{}
+	mts := []types.MediaType{}
+	layerss := [][]v1.Layer{}
+
+	errs := []string{}
+
+	for _, img := range []v1.Image{a, b} {
+		layers, err := img.Layers()
+		if err != nil {
+			return err
+		}
+		layerss = append(layerss, layers)
+
+		digest, err := img.Digest()
+		if err != nil {
+			return err
+		}
+		digests = append(digests, digest)
+
+		manifest, err := img.Manifest()
+		if err != nil {
+			return err
+		}
+		manifests = append(manifests, manifest)
+
+		cn, err := img.ConfigName()
+		if err != nil {
+			return err
+		}
+		cns = append(cns, cn)
+
+		size, err := img.Size()
+		if err != nil {
+			return err
+		}
+		sizes = append(sizes, size)
+
+		mt, err := img.MediaType()
+		if err != nil {
+			return err
+		}
+		mts = append(mts, mt)
+	}
+
+	if want, got := digests[0], digests[1]; want != got {
+		errs = append(errs, fmt.Sprintf("a.Digest() != b.Digest(); %s != %s", want, got))
+	}
+	if want, got := cns[0], cns[1]; want != got {
+		errs = append(errs, fmt.Sprintf("a.ConfigName() != b.ConfigName(); %s != %s", want, got))
+	}
+	if want, got := manifests[0], manifests[1]; !reflect.DeepEqual(want, got) {
+		errs = append(errs, fmt.Sprintf("a.Manifest() != b.Manifest(); %v != %v", want, got))
+	}
+	if want, got := sizes[0], sizes[1]; want != got {
+		errs = append(errs, fmt.Sprintf("a.Size() != b.Size(); %d != %d", want, got))
+	}
+	if want, got := mts[0], mts[1]; want != got {
+		errs = append(errs, fmt.Sprintf("a.MediaType() != b.MediaType(); %s != %s", want, got))
+	}
+
+	if len(layerss[0]) != len(layerss[1]) {
+		// If we have fewer layers than the first image, abort with an error so we don't panic.
+		return errors.New("len(a.Layers()) != len(b.Layers())")
+	}
+
+	// Compare each layer.
+	for i := 0; i < len(layerss[0]); i++ {
+		if err := Layers(layerss[0][i], layerss[1][i]); err != nil {
+			// Wrap the error in newlines to delineate layer errors.
+			errs = append(errs, fmt.Sprintf("Layers[%d]: %v\n", i, err))
+		}
+	}
+
+	if len(errs) != 0 {
+		return errors.New("Images differ:\n" + strings.Join(errs, "\n"))
+	}
+
+	return nil
+}
diff --git a/pkg/internal/compare/index.go b/pkg/internal/compare/index.go
new file mode 100644
index 00000000..c19620b6
--- /dev/null
+++ b/pkg/internal/compare/index.go
@@ -0,0 +1,83 @@
+// Copyright 2019 Google LLC All Rights Reserved.
+//
+// Licensed under the Apache License, Version 2.0 (the "License");
+// you may not use this file except in compliance with the License.
+// You may obtain a copy of the License at
+//
+//    http://www.apache.org/licenses/LICENSE-2.0
+//
+// Unless required by applicable law or agreed to in writing, software
+// distributed under the License is distributed on an "AS IS" BASIS,
+// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+// See the License for the specific language governing permissions and
+// limitations under the License.
+
+package compare
+
+import (
+	"errors"
+	"fmt"
+	"reflect"
+	"strings"
+
+	v1 "github.com/google/go-containerregistry/pkg/v1"
+	"github.com/google/go-containerregistry/pkg/v1/types"
+)
+
+// Indexes compares the given indexes to each other and returns an error if
+// they differ.
+func Indexes(a, b v1.ImageIndex) error {
+	digests := []v1.Hash{}
+	manifests := []*v1.IndexManifest{}
+	sizes := []int64{}
+	mts := []types.MediaType{}
+
+	errs := []string{}
+
+	for _, idx := range []v1.ImageIndex{a, b} {
+		digest, err := idx.Digest()
+		if err != nil {
+			return err
+		}
+		digests = append(digests, digest)
+
+		manifest, err := idx.IndexManifest()
+		if err != nil {
+			return err
+		}
+		manifests = append(manifests, manifest)
+
+		size, err := idx.Size()
+		if err != nil {
+			return err
+		}
+		sizes = append(sizes, size)
+
+		mt, err := idx.MediaType()
+		if err != nil {
+			return err
+		}
+		mts = append(mts, mt)
+	}
+
+	if want, got := digests[0], digests[1]; want != got {
+		errs = append(errs, fmt.Sprintf("a.Digest() != b.Digest(); %s != %s", want, got))
+	}
+	if want, got := manifests[0], manifests[1]; !reflect.DeepEqual(want, got) {
+		errs = append(errs, fmt.Sprintf("a.Manifest() != b.Manifest(); %v != %v", want, got))
+	}
+	if want, got := sizes[0], sizes[1]; want != got {
+		errs = append(errs, fmt.Sprintf("a.Size() != b.Size(); %d != %d", want, got))
+	}
+	if want, got := mts[0], mts[1]; want != got {
+		errs = append(errs, fmt.Sprintf("a.MediaType() != b.MediaType(); %s != %s", want, got))
+	}
+
+	// TODO(jonjohnsonjr): Iterate over Manifest and compare Image and ImageIndex results.
+
+	if len(errs) != 0 {
+		return errors.New("Indexes differ:\n" + strings.Join(errs, "\n"))
+	}
+
+	return nil
+}
diff --git a/pkg/internal/compare/layer.go b/pkg/internal/compare/layer.go
new file mode 100644
index 00000000..01e63aa6
--- /dev/null
+++ b/pkg/internal/compare/layer.go
@@ -0,0 +1,80 @@
+// Copyright 2019 Google LLC All Rights Reserved.
+//
+// Licensed under the Apache License, Version 2.0 (the "License");
+// you may not use this file except in compliance with the License.
+// You may obtain a copy of the License at
+//
+//    http://www.apache.org/licenses/LICENSE-2.0
+//
+// Unless required by applicable law or agreed to in writing, software
+// distributed under the License is distributed on an "AS IS" BASIS,
+// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+// See the License for the specific language governing permissions and
+// limitations under the License.
+
+package compare
+
+import (
+	"errors"
+	"fmt"
+	"strings"
+
+	v1 "github.com/google/go-containerregistry/pkg/v1"
+	"github.com/google/go-containerregistry/pkg/v1/types"
+)
+
+// Layers compares the given layers to each other and returns an error if they
+// differ.  Note that this does not compare the actual contents (by calling
+// Compressed or Uncompressed).
+func Layers(a, b v1.Layer) error {
+	digests := []v1.Hash{}
+	diffids := []v1.Hash{}
+	sizes := []int64{}
+	mts := []types.MediaType{}
+	errs := []string{}
+
+	for _, layer := range []v1.Layer{a, b} {
+		digest, err := layer.Digest()
+		if err != nil {
+			return err
+		}
+		digests = append(digests, digest)
+
+		diffid, err := layer.DiffID()
+		if err != nil {
+			return err
+		}
+		diffids = append(diffids, diffid)
+
+		size, err := layer.Size()
+		if err != nil {
+			return err
+		}
+		sizes = append(sizes, size)
+
+		mt, err := layer.MediaType()
+		if err != nil {
+			return err
+		}
+		mts = append(mts, mt)
+	}
+
+	if want, got := digests[0], digests[1]; want != got {
+		errs = append(errs, fmt.Sprintf("a.Digest() != b.Digest(); %s != %s", want, got))
+	}
+	if want, got := diffids[0], diffids[1]; want != got {
+		errs = append(errs, fmt.Sprintf("a.DiffID() != b.DiffID(); %s != %s", want, got))
+	}
+	if want, got := sizes[0], sizes[1]; want != got {
+		errs = append(errs, fmt.Sprintf("a.Size() != b.Size(); %d != %d", want, got))
+	}
+	if want, got := mts[0], mts[1]; want != got {
+		errs = append(errs, fmt.Sprintf("a.MediaType() != b.MediaType(); %s != %s", want, got))
+	}
+
+	if len(errs) != 0 {
+		return errors.New("Layers differ:\n" + strings.Join(errs, "\n"))
+	}
+
+	return nil
+}
diff --git a/pkg/v1/remote/image.go b/pkg/v1/remote/image.go
index e98af7e2..947f8bca 100644
--- a/pkg/v1/remote/image.go
+++ b/pkg/v1/remote/image.go
@@ -43,15 +43,7 @@ var _ partial.CompressedImageCore = (*remoteImage)(nil)
 
 // Image provides access to a remote image reference.
 func Image(ref name.Reference, options ...Option) (v1.Image, error) {
-	acceptable := []types.MediaType{
-		types.DockerManifestSchema2,
-		types.OCIManifestSchema1,
-		// We resolve these to images later.
-		types.DockerManifestList,
-		types.OCIImageIndex,
-	}
-
-	desc, err := get(ref, acceptable, options...)
+	desc, err := Get(ref, options...)
 	if err != nil {
 		return nil, err
 	}


Test patch:
diff --git a/pkg/authn/keychain_test.go b/pkg/authn/keychain_test.go
index a318f859..7fb2fd20 100644
--- a/pkg/authn/keychain_test.go
+++ b/pkg/authn/keychain_test.go
@@ -27,8 +27,9 @@ import (
 )
 
 var (
-	fresh           = 0
-	testRegistry, _ = name.NewRegistry("test.io", name.WeakValidation)
+	fresh              = 0
+	testRegistry, _    = name.NewRegistry("test.io", name.WeakValidation)
+	defaultRegistry, _ = name.NewRegistry(name.DefaultRegistry, name.WeakValidation)
 )
 
 // setupConfigDir sets up an isolated configDir() for this test.
@@ -85,19 +86,30 @@ func TestVariousPaths(t *testing.T) {
 	tests := []struct {
 		content string
 		wantErr bool
+		target  name.Registry
 		cfg     *AuthConfig
 	}{{
+		target:  testRegistry,
 		content: `}{`,
 		wantErr: true,
 	}, {
+		target:  testRegistry,
 		content: `{"credsStore":"#definitely-does-not-exist"}`,
 		wantErr: true,
 	}, {
+		target:  testRegistry,
 		content: fmt.Sprintf(`{"auths": {"test.io": {"auth": %q}}}`, encode("foo", "bar")),
 		cfg: &AuthConfig{
 			Username: "foo",
 			Password: "bar",
 		},
+	}, {
+		target:  defaultRegistry,
+		content: fmt.Sprintf(`{"auths": {"%s": {"auth": %q}}}`, defaultAuthKey, encode("foo", "bar")),
+		cfg: &AuthConfig{
+			Username: "foo",
+			Password: "bar",
+		},
 	}}
 
 	for _, test := range tests {
@@ -105,7 +117,7 @@ func TestVariousPaths(t *testing.T) {
 		// For some reason, these tempdirs don't get cleaned up.
 		defer os.RemoveAll(filepath.Dir(cd))
 
-		auth, err := DefaultKeychain.Resolve(testRegistry)
+		auth, err := DefaultKeychain.Resolve(test.target)
 		if test.wantErr {
 			if err == nil {
 				t.Fatal("wanted err, got nil")
diff --git a/pkg/internal/compare/image_test.go b/pkg/internal/compare/image_test.go
new file mode 100644
index 00000000..75272308
--- /dev/null
+++ b/pkg/internal/compare/image_test.go
@@ -0,0 +1,47 @@
+// Copyright 2019 Google LLC All Rights Reserved.
+//
+// Licensed under the Apache License, Version 2.0 (the "License");
+// you may not use this file except in compliance with the License.
+// You may obtain a copy of the License at
+//
+//    http://www.apache.org/licenses/LICENSE-2.0
+//
+// Unless required by applicable law or agreed to in writing, software
+// distributed under the License is distributed on an "AS IS" BASIS,
+// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+// See the License for the specific language governing permissions and
+// limitations under the License.
+
+package compare
+
+import (
+	"testing"
+
+	"github.com/google/go-containerregistry/pkg/v1/random"
+)
+
+func TestDifferentImages(t *testing.T) {
+	a, err := random.Image(100, 3)
+	if err != nil {
+		t.Fatal(err)
+	}
+	b, err := random.Image(100, 2)
+	if err != nil {
+		t.Fatal(err)
+	}
+
+	if err := Images(a, b); err == nil {
+		t.Errorf("got nil err, should have something")
+	}
+}
+
+func TestEqualImages(t *testing.T) {
+	a, err := random.Image(100, 2)
+	if err != nil {
+		t.Fatal(err)
+	}
+
+	if err := Images(a, a); err != nil {
+		t.Errorf("got err: %v", err)
+	}
+}
diff --git a/pkg/internal/compare/index_test.go b/pkg/internal/compare/index_test.go
new file mode 100644
index 00000000..77402aea
--- /dev/null
+++ b/pkg/internal/compare/index_test.go
@@ -0,0 +1,47 @@
+// Copyright 2019 Google LLC All Rights Reserved.
+//
+// Licensed under the Apache License, Version 2.0 (the "License");
+// you may not use this file except in compliance with the License.
+// You may obtain a copy of the License at
+//
+//    http://www.apache.org/licenses/LICENSE-2.0
+//
+// Unless required by applicable law or agreed to in writing, software
+// distributed under the License is distributed on an "AS IS" BASIS,
+// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+// See the License for the specific language governing permissions and
+// limitations under the License.
+
+package compare
+
+import (
+	"testing"
+
+	"github.com/google/go-containerregistry/pkg/v1/random"
+)
+
+func TestDifferentIndexes(t *testing.T) {
+	a, err := random.Index(100, 3, 3)
+	if err != nil {
+		t.Fatal(err)
+	}
+	b, err := random.Index(100, 2, 2)
+	if err != nil {
+		t.Fatal(err)
+	}
+
+	if err := Indexes(a, b); err == nil {
+		t.Errorf("got nil err, should have something")
+	}
+}
+
+func TestEqualIndexes(t *testing.T) {
+	a, err := random.Index(100, 2, 2)
+	if err != nil {
+		t.Fatal(err)
+	}
+
+	if err := Indexes(a, a); err != nil {
+		t.Errorf("got err: %v", err)
+	}
+}
diff --git a/pkg/internal/compare/layer_test.go b/pkg/internal/compare/layer_test.go
new file mode 100644
index 00000000..6db9751c
--- /dev/null
+++ b/pkg/internal/compare/layer_test.go
@@ -0,0 +1,48 @@
+// Copyright 2019 Google LLC All Rights Reserved.
+//
+// Licensed under the Apache License, Version 2.0 (the "License");
+// you may not use this file except in compliance with the License.
+// You may obtain a copy of the License at
+//
+//    http://www.apache.org/licenses/LICENSE-2.0
+//
+// Unless required by applicable law or agreed to in writing, software
+// distributed under the License is distributed on an "AS IS" BASIS,
+// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+// See the License for the specific language governing permissions and
+// limitations under the License.
+
+package compare
+
+import (
+	"testing"
+
+	"github.com/google/go-containerregistry/pkg/v1/random"
+	"github.com/google/go-containerregistry/pkg/v1/types"
+)
+
+func TestDifferentLayers(t *testing.T) {
+	a, err := random.Layer(100, types.DockerLayer)
+	if err != nil {
+		t.Fatal(err)
+	}
+	b, err := random.Layer(100, types.OCILayer)
+	if err != nil {
+		t.Fatal(err)
+	}
+
+	if err := Layers(a, b); err == nil {
+		t.Errorf("got nil err, should have something")
+	}
+}
+
+func TestEqualLayers(t *testing.T) {
+	a, err := random.Layer(100, types.DockerLayer)
+	if err != nil {
+		t.Fatal(err)
+	}
+
+	if err := Layers(a, a); err != nil {
+		t.Errorf("got err: %v", err)
+	}
+}
diff --git a/pkg/v1/daemon/image_test.go b/pkg/v1/daemon/image_test.go
index ebe3b703..3cbe33cc 100644
--- a/pkg/v1/daemon/image_test.go
+++ b/pkg/v1/daemon/image_test.go
@@ -18,9 +18,9 @@ import (
 	"context"
 	"io"
 	"os"
-	"reflect"
 	"testing"
 
+	"github.com/google/go-containerregistry/pkg/internal/compare"
 	"github.com/google/go-containerregistry/pkg/name"
 
 	"github.com/google/go-containerregistry/pkg/v1/tarball"
@@ -43,7 +43,7 @@ func init() {
 }
 
 func TestImage(t *testing.T) {
-	testImage, err := tarball.ImageFromPath(imagePath, nil)
+	img, err := tarball.ImageFromPath(imagePath, nil)
 	if err != nil {
 		t.Fatalf("error loading test image: %s", err)
 	}
@@ -60,21 +60,12 @@ func TestImage(t *testing.T) {
 		} else {
 			bufferedOption = WithUnbufferedOpener()
 		}
-		daemonImage, err := Image(tag, bufferedOption)
+		dmn, err := Image(tag, bufferedOption)
 		if err != nil {
 			t.Errorf("Error loading daemon image: %s", err)
 		}
-
-		dmfst, err := daemonImage.Manifest()
-		if err != nil {
-			t.Errorf("Error getting daemon manifest: %s", err)
-		}
-		tmfst, err := testImage.Manifest()
-		if err != nil {
-			t.Errorf("Error getting test manifest: %s", err)
-		}
-		if !reflect.DeepEqual(dmfst, tmfst) {
-			t.Errorf("%v != %v", testImage, daemonImage)
+		if err := compare.Images(img, dmn); err != nil {
+			t.Errorf("compare.Images: %v", err)
 		}
 	}
 
diff --git a/pkg/v1/remote/layer_test.go b/pkg/v1/remote/layer_test.go
index cbadc348..4708d83d 100644
--- a/pkg/v1/remote/layer_test.go
+++ b/pkg/v1/remote/layer_test.go
@@ -15,18 +15,17 @@
 package remote
 
 import (
-	"bytes"
 	"fmt"
-	"io/ioutil"
 	"net/http/httptest"
 	"net/url"
 	"testing"
 
+	"github.com/google/go-containerregistry/pkg/internal/compare"
 	"github.com/google/go-containerregistry/pkg/name"
 	"github.com/google/go-containerregistry/pkg/registry"
-	v1 "github.com/google/go-containerregistry/pkg/v1"
 	"github.com/google/go-containerregistry/pkg/v1/random"
 	"github.com/google/go-containerregistry/pkg/v1/types"
+	"github.com/google/go-containerregistry/pkg/v1/validate"
 )
 
 func TestRemoteLayer(t *testing.T) {
@@ -70,53 +69,10 @@ func TestRemoteLayer(t *testing.T) {
 		t.Errorf("reading MediaType: %v", err)
 	}
 
-	compareLayers(t, got, layer)
-}
-
-func compareLayers(t *testing.T, got, want v1.Layer) {
-	t.Helper()
-
-	gotDigest, err := got.Digest()
-	if err != nil {
-		t.Fatal(err)
-	}
-	wantDigest, err := want.Digest()
-	if err != nil {
-		t.Fatal(err)
-	}
-	if gotDigest != wantDigest {
-		t.Errorf("%s != %s", gotDigest, wantDigest)
-	}
-
-	gotSize, err := got.Size()
-	if err != nil {
-		t.Fatal(err)
-	}
-	wantSize, err := want.Size()
-	if err != nil {
-		t.Fatal(err)
-	}
-	if gotSize != wantSize {
-		t.Errorf("%d != %d", gotSize, wantSize)
-	}
-
-	rc, err := got.Compressed()
-	if err != nil {
-		t.Fatal(err)
-	}
-	gotBytes, err := ioutil.ReadAll(rc)
-	if err != nil {
-		t.Fatal(err)
-	}
-	rc, err = want.Compressed()
-	if err != nil {
-		t.Fatal(err)
-	}
-	wantBytes, err := ioutil.ReadAll(rc)
-	if err != nil {
-		t.Fatal(err)
+	if err := compare.Layers(got, layer); err != nil {
+		t.Errorf("compare.Layers: %v", err)
 	}
-	if !bytes.Equal(gotBytes, wantBytes) {
-		t.Error("gotBytes != wantBytes")
+	if err := validate.Layer(got); err != nil {
+		t.Errorf("validate.Layer: %v", err)
 	}
 }
diff --git a/pkg/v1/tarball/layer_test.go b/pkg/v1/tarball/layer_test.go
index 1e1ba273..4d4ddc3e 100644
--- a/pkg/v1/tarball/layer_test.go
+++ b/pkg/v1/tarball/layer_test.go
@@ -22,8 +22,8 @@ import (
 	"os"
 	"testing"
 
-	"github.com/google/go-cmp/cmp"
-	v1 "github.com/google/go-containerregistry/pkg/v1"
+	"github.com/google/go-containerregistry/pkg/internal/compare"
+	"github.com/google/go-containerregistry/pkg/v1/validate"
 )
 
 func TestLayerFromFile(t *testing.T) {
@@ -40,12 +40,16 @@ func TestLayerFromFile(t *testing.T) {
 		t.Fatalf("Unable to create layer from compressed tar file: %v", err)
 	}
 
-	assertDigestsAreEqual(t, tarLayer, tarGzLayer)
-	assertDiffIDsAreEqual(t, tarLayer, tarGzLayer)
-	assertCompressedStreamsAreEqual(t, tarLayer, tarGzLayer)
-	assertUncompressedStreamsAreEqual(t, tarLayer, tarGzLayer)
-	assertSizesAreEqual(t, tarLayer, tarGzLayer)
-	assertMediaTypesAreEqual(t, tarLayer, tarGzLayer)
+	if err := compare.Layers(tarLayer, tarGzLayer); err != nil {
+		t.Errorf("compare.Layers: %v", err)
+	}
+
+	if err := validate.Layer(tarLayer); err != nil {
+		t.Errorf("validate.Layer(tarLayer): %v", err)
+	}
+	if err := validate.Layer(tarGzLayer); err != nil {
+		t.Errorf("validate.Layer(tarGzLayer): %v", err)
+	}
 }
 
 func TestLayerFromOpenerReader(t *testing.T) {
@@ -76,12 +80,9 @@ func TestLayerFromOpenerReader(t *testing.T) {
 		t.Fatalf("Unable to create layer from tar file: %v", err)
 	}
 
-	assertDigestsAreEqual(t, tarLayer, tarGzLayer)
-	assertDiffIDsAreEqual(t, tarLayer, tarGzLayer)
-	assertCompressedStreamsAreEqual(t, tarLayer, tarGzLayer)
-	assertUncompressedStreamsAreEqual(t, tarLayer, tarGzLayer)
-	assertSizesAreEqual(t, tarLayer, tarGzLayer)
-	assertMediaTypesAreEqual(t, tarLayer, tarGzLayer)
+	if err := compare.Layers(tarLayer, tarGzLayer); err != nil {
+		t.Errorf("compare.Layers: %v", err)
+	}
 }
 
 func TestLayerFromReader(t *testing.T) {
@@ -106,139 +107,8 @@ func TestLayerFromReader(t *testing.T) {
 		t.Fatalf("Unable to create layer from tar file: %v", err)
 	}
 
-	assertDigestsAreEqual(t, tarLayer, tarGzLayer)
-	assertDiffIDsAreEqual(t, tarLayer, tarGzLayer)
-	assertCompressedStreamsAreEqual(t, tarLayer, tarGzLayer)
-	assertUncompressedStreamsAreEqual(t, tarLayer, tarGzLayer)
-	assertSizesAreEqual(t, tarLayer, tarGzLayer)
-	assertMediaTypesAreEqual(t, tarLayer, tarGzLayer)
-}
-
-func assertDigestsAreEqual(t *testing.T, a, b v1.Layer) {
-	t.Helper()
-
-	sa, err := a.Digest()
-	if err != nil {
-		t.Fatalf("Unable to fetch digest for layer: %v", err)
-	}
-
-	sb, err := b.Digest()
-	if err != nil {
-		t.Fatalf("Unable to fetch digest for layer: %v", err)
-	}
-
-	if sa != sb {
-		t.Fatalf("Digest of each layer is different - %v != %v", sa, sb)
-	}
-}
-
-func assertDiffIDsAreEqual(t *testing.T, a, b v1.Layer) {
-	t.Helper()
-
-	sa, err := a.DiffID()
-	if err != nil {
-		t.Fatalf("Unable to fetch diffID for layer: %v", err)
-	}
-
-	sb, err := b.DiffID()
-	if err != nil {
-		t.Fatalf("Unable to fetch diffID for layer: %v", err)
-	}
-
-	if sa != sb {
-		t.Fatalf("diffID of each layer is different - %v != %v", sa, sb)
-	}
-}
-
-func assertCompressedStreamsAreEqual(t *testing.T, a, b v1.Layer) {
-	t.Helper()
-
-	sa, err := a.Compressed()
-	if err != nil {
-		t.Fatalf("Unable to fetch compressed for layer: %v", err)
-	}
-
-	saBytes, err := ioutil.ReadAll(sa)
-	if err != nil {
-		t.Fatalf("Unable to read bytes for layer: %v", err)
-	}
-
-	sb, err := b.Compressed()
-	if err != nil {
-		t.Fatalf("Unable to fetch compressed for layer: %v", err)
-	}
-
-	sbBytes, err := ioutil.ReadAll(sb)
-	if err != nil {
-		t.Fatalf("Unable to read bytes for layer: %v", err)
-	}
-
-	if diff := cmp.Diff(saBytes, sbBytes); diff != "" {
-		t.Fatalf("Compressed streams were different: %v", diff)
-	}
-}
-
-func assertUncompressedStreamsAreEqual(t *testing.T, a, b v1.Layer) {
-	t.Helper()
-
-	sa, err := a.Uncompressed()
-	if err != nil {
-		t.Fatalf("Unable to fetch uncompressed for layer: %v", err)
-	}
-
-	saBytes, err := ioutil.ReadAll(sa)
-	if err != nil {
-		t.Fatalf("Unable to read bytes for layer: %v", err)
-	}
-
-	sb, err := b.Uncompressed()
-	if err != nil {
-		t.Fatalf("Unable to fetch uncompressed for layer: %v", err)
-	}
-
-	sbBytes, err := ioutil.ReadAll(sb)
-	if err != nil {
-		t.Fatalf("Unable to read bytes for layer: %v", err)
-	}
-
-	if diff := cmp.Diff(saBytes, sbBytes); diff != "" {
-		t.Fatalf("Uncompressed streams were different: %v", diff)
-	}
-}
-
-func assertMediaTypesAreEqual(t *testing.T, a, b v1.Layer) {
-	t.Helper()
-
-	ma, err := a.MediaType()
-	if err != nil {
-		t.Fatalf("Unable to fetch MediaType for layer: %v", err)
-	}
-
-	mb, err := b.MediaType()
-	if err != nil {
-		t.Fatalf("Unable to fetch MediaType for layer: %v", err)
-	}
-
-	if ma != mb {
-		t.Fatalf("MediaType of each layer is different - %s != %s", ma, mb)
-	}
-}
-
-func assertSizesAreEqual(t *testing.T, a, b v1.Layer) {
-	t.Helper()
-
-	sa, err := a.Size()
-	if err != nil {
-		t.Fatalf("Unable to fetch size for layer: %v", err)
-	}
-
-	sb, err := b.Size()
-	if err != nil {
-		t.Fatalf("Unable to fetch size for layer: %v", err)
-	}
-
-	if sa != sb {
-		t.Fatalf("Size of each layer is different - %d != %d", sa, sb)
+	if err := compare.Layers(tarLayer, tarGzLayer); err != nil {
+		t.Errorf("compare.Layers: %v", err)
 	}
 }
 
diff --git a/pkg/v1/tarball/write_test.go b/pkg/v1/tarball/write_test.go
index c6c69ea8..ff66c284 100644
--- a/pkg/v1/tarball/write_test.go
+++ b/pkg/v1/tarball/write_test.go
@@ -19,8 +19,7 @@ import (
 	"os"
 	"testing"
 
-	"github.com/google/go-cmp/cmp"
-
+	"github.com/google/go-containerregistry/pkg/internal/compare"
 	"github.com/google/go-containerregistry/pkg/name"
 	v1 "github.com/google/go-containerregistry/pkg/v1"
 	"github.com/google/go-containerregistry/pkg/v1/mutate"
@@ -61,23 +60,13 @@ func TestWrite(t *testing.T) {
 			t.Fatalf("Unexpected error reading tarball: %v", err)
 		}
 
-		tarManifest, err := tarImage.Manifest()
-		if err != nil {
-			t.Fatalf("Unexpected error reading tarball: %v", err)
-		}
-		randManifest, err := randImage.Manifest()
-		if err != nil {
-			t.Fatalf("Unexpected error reading tarball: %v", err)
-		}
-
-		if diff := cmp.Diff(randManifest, tarManifest); diff != "" {
-			t.Errorf("Manifests not equal. (-rand +tar) %s", diff)
-		}
-
 		if err := validate.Image(tarImage); err != nil {
 			t.Errorf("validate.Image: %v", err)
 		}
-		assertLayersAreIdentical(t, randImage, tarImage)
+
+		if err := compare.Images(randImage, tarImage); err != nil {
+			t.Errorf("compare.Images: %v", err)
+		}
 	}
 
 	// Try loading a different tag, it should error.
@@ -139,24 +128,13 @@ func TestMultiWriteSameImage(t *testing.T) {
 			t.Fatalf("Unexpected error reading tarball: %v", err)
 		}
 
-		tarManifest, err := tarImage.Manifest()
-		if err != nil {
-			t.Fatalf("Unexpected error reading tarball: %v", err)
-		}
-		randManifest, err := randImage.Manifest()
-		if err != nil {
-			t.Fatalf("Unexpected error reading tarball: %v", err)
-		}
-
-		if diff := cmp.Diff(randManifest, tarManifest); diff != "" {
-			t.Errorf("Manifests not equal. (-rand +tar) %s", diff)
-		}
-
 		if err := validate.Image(tarImage); err != nil {
 			t.Errorf("validate.Image: %v", err)
 		}
 
-		assertLayersAreIdentical(t, randImage, tarImage)
+		if err := compare.Images(randImage, tarImage); err != nil {
+			t.Errorf("compare.Images: %v", err)
+		}
 	}
 }
 
@@ -221,23 +199,13 @@ func TestMultiWriteDifferentImages(t *testing.T) {
 			t.Fatalf("Unexpected error reading tarball: %v", err)
 		}
 
-		tarManifest, err := tarImage.Manifest()
-		if err != nil {
-			t.Fatalf("Unexpected error reading tarball: %v", err)
-		}
-		randManifest, err := img.Manifest()
-		if err != nil {
-			t.Fatalf("Unexpected error reading tarball: %v", err)
-		}
-
-		if diff := cmp.Diff(randManifest, tarManifest); diff != "" {
-			t.Errorf("Manifests not equal. (-rand +tar) %s", diff)
-		}
-
 		if err := validate.Image(tarImage); err != nil {
 			t.Errorf("validate.Image: %v", err)
 		}
-		assertLayersAreIdentical(t, img, tarImage)
+
+		if err := compare.Images(img, tarImage); err != nil {
+			t.Errorf("compare.Images: %v", err)
+		}
 	}
 }
 
@@ -298,55 +266,3 @@ func TestWriteForeignLayers(t *testing.T) {
 		t.Errorf("Wrong URLs: %s != %s", got, want)
 	}
 }
-
-func assertLayersAreIdentical(t *testing.T, a, b v1.Image) {
-	t.Helper()
-
-	aLayers, err := a.Layers()
-	if err != nil {
-		t.Fatalf("error getting layers to compare: %v", err)
-	}
-
-	bLayers, err := b.Layers()
-	if err != nil {
-		t.Fatalf("error getting layers to compare: %v", err)
-	}
-
-	if diff := cmp.Diff(getDigests(t, aLayers), getDigests(t, bLayers)); diff != "" {
-		t.Fatalf("layers digests are not identical (-rand +tar) %s", diff)
-	}
-
-	if diff := cmp.Diff(getDiffIDs(t, aLayers), getDiffIDs(t, bLayers)); diff != "" {
-		t.Fatalf("layers digests are not identical (-rand +tar) %s", diff)
-	}
-}
-
-func getDigests(t *testing.T, layers []v1.Layer) []v1.Hash {
-	t.Helper()
-
-	digests := make([]v1.Hash, 0, len(layers))
-	for _, layer := range layers {
-		digest, err := layer.Digest()
-		if err != nil {
-			t.Fatalf("error getting digests: %s", err)
-		}
-		digests = append(digests, digest)
-	}
-
-	return digests
-}
-
-func getDiffIDs(t *testing.T, layers []v1.Layer) []v1.Hash {
-	t.Helper()
-
-	diffIDs := make([]v1.Hash, 0, len(layers))
-	for _, layer := range layers {
-		diffID, err := layer.DiffID()
-		if err != nil {
-			t.Fatalf("error getting diffID: %s", err)
-		}
-		diffIDs = append(diffIDs, diffID)
-	}
-
-	return diffIDs
-}