From d5877393161c0b7de53857e68f58a3a3e06ca49a Mon Sep 17 00:00:00 2001 From: Istvan Kispal Date: Mon, 12 Nov 2018 23:50:32 +0100 Subject: [PATCH 1/2] Add test to ManifestV2() --- registry/.gitignore | 2 + registry/manifest_test.go | 68 ++++++++++ registry/testdata/registry_tests.public.json | 22 +++ registry/testing_test.go | 133 +++++++++++++++++++ 4 files changed, 225 insertions(+) create mode 100644 registry/.gitignore create mode 100644 registry/manifest_test.go create mode 100644 registry/testdata/registry_tests.public.json create mode 100644 registry/testing_test.go diff --git a/registry/.gitignore b/registry/.gitignore new file mode 100644 index 00000000..cc977668 --- /dev/null +++ b/registry/.gitignore @@ -0,0 +1,2 @@ +private_test.go +testdata/registry_tests.private.json diff --git a/registry/manifest_test.go b/registry/manifest_test.go new file mode 100644 index 00000000..c53eb094 --- /dev/null +++ b/registry/manifest_test.go @@ -0,0 +1,68 @@ +package registry_test + +import ( + "testing" + + "github.com/docker/distribution" + "github.com/docker/distribution/manifest/schema1" + "github.com/docker/distribution/manifest/schema2" + "github.com/opencontainers/go-digest" +) + +func checkManifest(t *testing.T, tc *TestCase, + wantDigest *string, wantMediaType string, + getManifest func(t *testing.T) (distribution.Manifest, error)) { + + if *wantDigest == "" { + return + } + + t.Run(tc.Name(), func(t *testing.T) { + got, err := getManifest(t) + if err != nil { + t.Error(err) + return + } + mediaType, payload, err := got.Payload() + if err != nil { + t.Error("Payload() error:", err) + return + } + d := digest.FromBytes(payload).String() + + if !*_testDataUpdate { + // do actual testing of manifest + if mediaType != wantMediaType { + t.Errorf("MediaType = %v, want %v", mediaType, wantMediaType) + } + if d != *wantDigest { + t.Errorf("digest = %v, want %v", d, *wantDigest) + } + if !blobSlicesAreEqual(got.References(), tc.Blobs) { + t.Errorf("\nblobs:\n%v,\nwant:\n%v", got.References(), tc.Blobs) + } + } else { + // update TestCase to reflect the result of the tested method + *wantDigest = d + tc.Blobs = got.References() + } + }) +} + +func TestRegistry_Manifest(t *testing.T) { + for _, tc := range testCases(t) { + checkManifest(t, tc, &tc.ManifestV1Digest, schema1.MediaTypeSignedManifest, func(t *testing.T) (distribution.Manifest, error) { + return tc.Registry(t).Manifest(tc.Repository, tc.Reference) + }) + } + updateTestData(t) +} + +func TestRegistry_ManifestV2(t *testing.T) { + for _, tc := range testCases(t) { + checkManifest(t, tc, &tc.ManifestV2Digest, schema2.MediaTypeManifest, func(t *testing.T) (distribution.Manifest, error) { + return tc.Registry(t).ManifestV2(tc.Repository, tc.Reference) + }) + } + updateTestData(t) +} diff --git a/registry/testdata/registry_tests.public.json b/registry/testdata/registry_tests.public.json new file mode 100644 index 00000000..2cd9a26f --- /dev/null +++ b/registry/testdata/registry_tests.public.json @@ -0,0 +1,22 @@ +[ + { + "url": "https://registry-1.docker.io", + "repository": "library/alpine", + "reference": "3.8", + "expected": { + "manifest_v2_digest": "sha256:02892826401a9d18f0ea01f8a2f35d328ef039db4e1edcc45c630314a0457d5b", + "blobs": [ + { + "mediaType": "application/vnd.docker.container.image.v1+json", + "size": 1512, + "digest": "sha256:196d12cf6ab19273823e700516e98eb1910b03b17840f9d5509f03858484d321" + }, + { + "mediaType": "application/vnd.docker.image.rootfs.diff.tar.gzip", + "size": 2206931, + "digest": "sha256:4fe2ade4980c2dda4fc95858ebb981489baec8c1e4bd282ab1c3560be8ff9bde" + } + ] + } + } +] diff --git a/registry/testing_test.go b/registry/testing_test.go new file mode 100644 index 00000000..dbee406a --- /dev/null +++ b/registry/testing_test.go @@ -0,0 +1,133 @@ +package registry_test + +import ( + "encoding/json" + "flag" + "fmt" + "os" + "path/filepath" + "reflect" + "sort" + "testing" + + "github.com/docker/distribution" + "github.com/heroku/docker-registry-client/registry" +) + +// Options stores optional parameters for constructing a new Registry +type Options struct { + Username string `json:"username,omitempty"` + Password string `json:"password,omitempty"` + Insecure bool `json:"insecure,omitempty"` + Logf registry.LogfCallback `json:"-"` + DoInitialPing bool `json:"do_initial_ping,omitempty"` + DisableBasicAuth bool `json:"disable_basicauth,omitempty"` +} + +// Expected stores the expected results of various tests +type Expected struct { + ManifestV1Digest string `json:"manifest_v1_digest,omitempty"` + ManifestV2Digest string `json:"manifest_v2_digest,omitempty"` + ManifestListDigest string `json:"manifestlist_digest,omitempty"` + Blobs []distribution.Descriptor `json:"blobs,omitempty"` +} + +// TestCase represents a test case normally read from a test data file. +type TestCase struct { + Url string `json:"url"` + Repository string `json:"repository"` + Reference string `json:"reference"` + Writeable bool `json:"writeable,omitempty"` + + Options + registry *registry.Registry + + Expected `json:"expected"` + Origin string `json:"-"` // name of the test data file that this TestCase was read from +} + +func (tc TestCase) Name() string { + return fmt.Sprintf("%s/%s@%s,%v", tc.Url, tc.Repository, tc.Reference, tc.Writeable) +} + +func (tc *TestCase) Registry(t *testing.T) *registry.Registry { + if tc.registry == nil { + var err error + tc.registry, err = registry.New(tc.Url, tc.Username, tc.Password) + if err != nil { + t.Fatal("failed to create registry client:", err) + } + } + return tc.registry +} + +const testDataFilePattern = "testdata/registry_tests*.json" + +var _testDataUpdate = flag.Bool("update", false, "update testdata files") +var _testCases []*TestCase + +// testCases loads all test data files and returns with the union of all testcases read +func testCases(t *testing.T) []*TestCase { + if _testCases != nil { + return _testCases + } + + tdFilenames, err := filepath.Glob(testDataFilePattern) + if err != nil { + t.Fatal("failed to list test data files:", testDataFilePattern) + } + + for _, tdFilename := range tdFilenames { + tdFile, err := os.Open(tdFilename) + if err != nil { + t.Fatal("failed to open test data file:", tdFilename) + } + + var tcs []*TestCase + err = json.NewDecoder(tdFile).Decode(&tcs) + if err != nil { + t.Fatalf("failed to load test data from %s: %s", tdFilename, err) + } + for i := range tcs { + tcs[i].Origin = tdFilename + } + _testCases = append(_testCases, tcs...) + } + return _testCases +} + +// updateTestData writes the actual results back to the test data files +// if the --update flag was given to the test +func updateTestData(t *testing.T) { + if !*_testDataUpdate { + return + } + + tdFiles := make(map[string][]*TestCase) + for _, tc := range testCases(t) { + tdFiles[tc.Origin] = append(tdFiles[tc.Origin], tc) + } + for tdFilename, tcs := range tdFiles { + tdFile, err := os.Create(tdFilename) + if err != nil { + t.Fatal(err) + } + enc := json.NewEncoder(tdFile) + enc.SetIndent("", " ") + err = enc.Encode(tcs) + if err != nil { + t.Fatal(err) + } + } +} + +// blobSlicesAreEqual checks if the two given slices are equal +// WARNING: this will modify (i.e. sort) both a and b +func blobSlicesAreEqual(a, b []distribution.Descriptor) bool { + if len(a) != len(b) { + return false + } + sort.Slice(a, func(i, j int) bool { return a[i].Digest.String() < a[j].Digest.String() }) + sort.Slice(b, func(i, j int) bool { return b[i].Digest.String() < b[j].Digest.String() }) + return reflect.DeepEqual(a, b) +} From 7f3137473d3945f2b5ea60fd847c58dea051c7c9 Mon Sep 17 00:00:00 2001 From: Istvan Kispal Date: Tue, 13 Nov 2018 23:24:04 +0100 Subject: [PATCH 2/2] Set go version to 1.8 in travis.yml --- .travis.yml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.travis.yml b/.travis.yml index 1a4b983e..e2b8b76f 100644 --- a/.travis.yml +++ b/.travis.yml @@ -1,5 +1,5 @@ language: go -go: 1.7.1 +go: 1.8 sudo: false script: make travis notifications: