Skip to content

Commit

Permalink
Add lots of tests to pkg/crane (#970)
Browse files Browse the repository at this point in the history
* Cover crane.Optimize

* Cover crane.WithPlatform

* Cover crane.Append

* cover crane.Option
  • Loading branch information
jonjohnsonjr committed Mar 16, 2021
1 parent b111f87 commit 70c58c0
Show file tree
Hide file tree
Showing 3 changed files with 263 additions and 9 deletions.
29 changes: 21 additions & 8 deletions pkg/crane/append.go
Original file line number Diff line number Diff line change
Expand Up @@ -40,20 +40,33 @@ func Append(base v1.Image, paths ...string) (v1.Image, error) {
}

func getLayer(path string) (v1.Layer, error) {
f, err := streamFile(path)
if err != nil {
return nil, err
}
if f != nil {
return stream.NewLayer(f), nil
}

return tarball.LayerFromFile(path)
}

// If we're dealing with a named pipe, trying to open it multiple times will
// fail, so we need to do a streaming upload.
//
// returns nil, nil for non-streaming files
func streamFile(path string) (*os.File, error) {
if path == "-" {
return os.Stdin, nil
}
fi, err := os.Stat(path)
if err != nil {
return nil, err
}

// If we're dealing with a named pipe, trying to open it multiple times will
// fail, so we need to do a streaming upload.
if !fi.Mode().IsRegular() {
rc, err := os.Open(path)
if err != nil {
return nil, err
}
return stream.NewLayer(rc), nil
return os.Open(path)
}

return tarball.LayerFromFile(path)
return nil, nil
}
132 changes: 131 additions & 1 deletion pkg/crane/crane_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -24,12 +24,18 @@ import (
"net/http/httptest"
"net/url"
"os"
"path"
"strings"
"testing"

"github.com/google/go-containerregistry/pkg/authn"
"github.com/google/go-containerregistry/pkg/crane"
"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/empty"
"github.com/google/go-containerregistry/pkg/v1/mutate"
"github.com/google/go-containerregistry/pkg/v1/random"
"github.com/google/go-containerregistry/pkg/v1/remote"
)
Expand Down Expand Up @@ -115,7 +121,8 @@ func TestCraneRegistry(t *testing.T) {
}

// Make sure what we copied is equivalent.
copied, err := crane.Pull(dst, crane.Insecure, crane.WithTransport(http.DefaultTransport))
// Also, get options coverage in a dumb way.
copied, err := crane.Pull(dst, crane.Insecure, crane.WithTransport(http.DefaultTransport), crane.WithAuth(authn.Anonymous), crane.WithAuthFromKeychain(authn.DefaultKeychain), crane.WithUserAgent("crane/tests"))
if err != nil {
t.Fatal(err)
}
Expand Down Expand Up @@ -248,6 +255,83 @@ func TestCraneCopyIndex(t *testing.T) {
}
}

func TestWithPlatform(t *testing.T) {
// Set up a fake registry with a platform-specific image.
s := httptest.NewServer(registry.New())
defer s.Close()
u, err := url.Parse(s.URL)
if err != nil {
t.Fatal(err)
}

imgs := []mutate.IndexAddendum{}
for _, plat := range []string{
"linux/amd64",
"linux/arm",
} {
img, err := crane.Image(map[string][]byte{
"platform.txt": []byte(plat),
})
if err != nil {
t.Fatal(err)
}
parts := strings.Split(plat, "/")
imgs = append(imgs, mutate.IndexAddendum{
Add: img,
Descriptor: v1.Descriptor{
Platform: &v1.Platform{
OS: parts[0],
Architecture: parts[1],
},
},
})
}

idx := mutate.AppendManifests(empty.Index, imgs...)

src := path.Join(u.Host, "src")
dst := path.Join(u.Host, "dst")

ref, err := name.ParseReference(src)
if err != nil {
t.Fatal(err)
}

// Populate registry so we can copy from it.
if err := remote.WriteIndex(ref, idx); err != nil {
t.Fatal(err)
}

if err := crane.Copy(src, dst, crane.WithPlatform(imgs[1].Platform)); err != nil {
t.Fatal(err)
}

want, err := crane.Manifest(src, crane.WithPlatform(imgs[1].Platform))
if err != nil {
t.Fatal(err)
}
got, err := crane.Manifest(dst)
if err != nil {
t.Fatal(err)
}

if string(got) != string(want) {
t.Errorf("Manifest(%q) != Manifest(%q): (\n\n%s\n\n!=\n\n%s\n\n)", dst, src, string(got), string(want))
}

arch := "real fake doors"

// Now do a fake platform, should fail
if _, err := crane.Manifest(src, crane.WithPlatform(&v1.Platform{
OS: "does-not-exist",
Architecture: arch,
})); err == nil {
t.Error("crane.Manifest(fake platform): got nil want err")
} else if !strings.Contains(err.Error(), arch) {
t.Errorf("crane.Manifest(fake platform): expected %q in error, got: %v", arch, err)
}
}

func TestCraneTarball(t *testing.T) {
t.Parallel()
// Write an image as a tarball.
Expand Down Expand Up @@ -381,6 +465,49 @@ func TestCraneFilesystem(t *testing.T) {
}
}

func TestStreamingAppend(t *testing.T) {
// Stdin will be an uncompressed layer.
layer, err := crane.Layer(map[string][]byte{
"hello": []byte(`world`),
})
if err != nil {
t.Fatal(err)
}
rc, err := layer.Uncompressed()
if err != nil {
t.Fatal(err)
}

tmp, err := ioutil.TempFile("", "crane-append")
if err != nil {
t.Fatal(err)
}
defer os.Remove(tmp.Name())

if _, err := io.Copy(tmp, rc); err != nil {
t.Fatal(err)
}

stdin := os.Stdin
defer func() {
os.Stdin = stdin
}()

os.Stdin = tmp

img, err := crane.Append(empty.Image, "-")
if err != nil {
t.Fatal(err)
}
ll, err := img.Layers()
if err != nil {
t.Fatal(err)
}
if want, got := 1, len(ll); want != got {
t.Errorf("crane.Append(stdin) - len(layers): want %d != got %d", want, got)
}
}

func TestBadInputs(t *testing.T) {
t.Parallel()
invalid := "/dev/null/@@@@@@"
Expand Down Expand Up @@ -416,6 +543,9 @@ func TestBadInputs(t *testing.T) {
{"Tag(invalid, invalid)", crane.Tag(invalid, invalid)},
{"Tag(404, invalid)", crane.Tag(valid404, invalid)},
{"Tag(404, 404)", crane.Tag(valid404, valid404)},
{"Optimize(invalid, invalid)", crane.Optimize(invalid, invalid, []string{})},
{"Optimize(404, invalid)", crane.Optimize(valid404, invalid, []string{})},
{"Optimize(404, 404)", crane.Optimize(valid404, valid404, []string{})},
// These return multiple values, which are hard to use as expressions.
{"Pull(invalid)", e(crane.Pull(invalid))},
{"Digest(invalid)", e(crane.Digest(invalid))},
Expand Down
111 changes: 111 additions & 0 deletions pkg/crane/optimize_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -15,11 +15,20 @@
package crane

import (
"net/http/httptest"
"net/url"
"path"
"strings"
"testing"

"github.com/google/go-cmp/cmp"
"github.com/google/go-cmp/cmp/cmpopts"
"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/empty"
"github.com/google/go-containerregistry/pkg/v1/mutate"
"github.com/google/go-containerregistry/pkg/v1/remote"
)

func TestStringSet(t *testing.T) {
Expand Down Expand Up @@ -66,3 +75,105 @@ func TestStringSet(t *testing.T) {
}
}
}

func TestOptimize(t *testing.T) {
// Set up a fake registry.
s := httptest.NewServer(registry.New())
defer s.Close()
u, err := url.Parse(s.URL)
if err != nil {
t.Fatal(err)
}

imgs := []mutate.IndexAddendum{}
for _, plat := range []string{
"linux/amd64",
"linux/arm",
} {
img, err := Image(map[string][]byte{
"unimportant": []byte(strings.Repeat("deadbeef", 128)),
"important": []byte(`abc`),
"platform.txt": []byte(plat),
})
if err != nil {
t.Fatal(err)
}
parts := strings.Split(plat, "/")
imgs = append(imgs, mutate.IndexAddendum{
Add: img,
Descriptor: v1.Descriptor{
Platform: &v1.Platform{
OS: parts[0],
Architecture: parts[1],
},
},
})
}

idx := mutate.AppendManifests(empty.Index, imgs...)

slow := path.Join(u.Host, "slow")
fast := path.Join(u.Host, "fast")

ref, err := name.ParseReference(slow)
if err != nil {
t.Fatal(err)
}

if err := remote.WriteIndex(ref, idx); err != nil {
t.Fatal(err)
}

if err := Optimize(slow, fast, []string{"important"}); err != nil {
t.Fatal(err)
}

if err := Optimize(slow, fast, []string{"important"}, WithPlatform(imgs[1].Platform)); err != nil {
t.Fatal(err)
}

// Compare optimize WithPlatform path to optimizing just an image.
got, err := Digest(fast)
if err != nil {
t.Fatal(err)
}

dig, err := Digest(slow, WithPlatform(imgs[1].Platform))
if err != nil {
t.Fatal(err)
}

slowImgRef := slow + "@" + dig
if err := Optimize(slowImgRef, fast, []string{"important"}, WithPlatform(imgs[1].Platform)); err != nil {
t.Fatal(err)
}

want, err := Digest(fast)
if err != nil {
t.Fatal(err)
}
if got != want {
t.Errorf("Optimize(WithPlatform) != Optimize(bydigest): %q != %q", got, want)
}

for i, ref := range []string{slow, slow, slowImgRef} {
opts := []Option{}
// Silly, but use WithPlatform to get some more coverage.
if i != 0 {
opts = []Option{WithPlatform(imgs[1].Platform)}
}
dig, err := Digest(ref, opts...)
if err != nil {
t.Errorf("Digest(%q): %v", ref, err)
continue
}
// Make sure we fail if there's a missing file in the optimize set
// Use the image digest because it's ~impossible to exist in img.
if err := Optimize(ref, fast, []string{dig}, opts...); err == nil {
t.Errorf("Optimize(%q, prioritize=%q): got nil, want err", ref, dig)
} else if !strings.Contains(err.Error(), dig) {
// Make sure this contains the missing file (dig)
t.Errorf("Optimize(%q) error should contain %q, got: %v", ref, dig, err)
}
}
}

0 comments on commit 70c58c0

Please sign in to comment.