Skip to content

Commit 34697ec

Browse files
committed
ociregistry/ocitest: add facility for generic method testing
This makes it easier to run a test against an arbitrary invocation of all the `ociregistry.Interface` method calls, for example when testing middleware that wraps `Interface`. We update the `ociclient` auth tests to use it. Signed-off-by: Roger Peppe <[email protected]> Change-Id: If75081713ff6f09ce33ad8fd472e4cef2bd525a4
1 parent 43b81f4 commit 34697ec

File tree

3 files changed

+227
-69
lines changed

3 files changed

+227
-69
lines changed

Diff for: ociregistry/ociclient/auth_test.go

+29-69
Original file line numberDiff line numberDiff line change
@@ -5,15 +5,14 @@ import (
55
"net/http"
66
"net/http/httptest"
77
"net/url"
8-
"strings"
98
"testing"
109

1110
"cuelabs.dev/go/oci/ociregistry"
1211
"cuelabs.dev/go/oci/ociregistry/ociauth"
1312
"cuelabs.dev/go/oci/ociregistry/ocimem"
1413
"cuelabs.dev/go/oci/ociregistry/ociserver"
14+
"cuelabs.dev/go/oci/ociregistry/ocitest"
1515
"github.com/go-quicktest/qt"
16-
"github.com/opencontainers/go-digest"
1716
)
1817

1918
func TestAuthScopes(t *testing.T) {
@@ -26,74 +25,35 @@ func TestAuthScopes(t *testing.T) {
2625
defer srv.Close()
2726
srvURL, _ := url.Parse(srv.URL)
2827

29-
assertScope := func(scope string, f func(ctx context.Context, r ociregistry.Interface)) {
30-
assertAuthScope(t, srvURL.Host, scope, f)
28+
wantScopes := map[ocitest.Method]string{
29+
ocitest.GetBlob: "repository:foo/read:pull",
30+
ocitest.GetBlobRange: "repository:foo/read:pull",
31+
ocitest.GetManifest: "repository:foo/read:pull",
32+
ocitest.GetTag: "repository:foo/read:pull",
33+
ocitest.ResolveBlob: "repository:foo/read:pull",
34+
ocitest.ResolveManifest: "repository:foo/read:pull",
35+
ocitest.ResolveTag: "repository:foo/read:pull",
36+
ocitest.PushBlob: "repository:foo/write:push",
37+
ocitest.PushBlobChunked: "repository:foo/write:push",
38+
ocitest.PushBlobChunkedResume: "repository:foo/write:push",
39+
ocitest.MountBlob: "repository:foo/read:pull repository:foo/write:push",
40+
ocitest.PushManifest: "repository:foo/write:push",
41+
ocitest.DeleteBlob: "repository:foo/write:push",
42+
ocitest.DeleteManifest: "repository:foo/write:push",
43+
ocitest.DeleteTag: "repository:foo/write:push",
44+
ocitest.Repositories: "registry:catalog:*",
45+
ocitest.Tags: "repository:foo/read:pull",
46+
ocitest.Referrers: "repository:foo/read:pull",
3147
}
32-
33-
assertScope("repository:foo/bar:pull", func(ctx context.Context, r ociregistry.Interface) {
34-
r.GetBlob(ctx, "foo/bar", "sha256:ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff")
35-
})
36-
assertScope("repository:foo/bar:pull", func(ctx context.Context, r ociregistry.Interface) {
37-
r.GetBlobRange(ctx, "foo/bar", "sha256:ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff", 100, 200)
38-
})
39-
assertScope("repository:foo/bar:pull", func(ctx context.Context, r ociregistry.Interface) {
40-
r.GetManifest(ctx, "foo/bar", "sha256:ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff")
41-
})
42-
assertScope("repository:foo/bar:pull", func(ctx context.Context, r ociregistry.Interface) {
43-
r.GetTag(ctx, "foo/bar", "sometag")
44-
})
45-
assertScope("repository:foo/bar:pull", func(ctx context.Context, r ociregistry.Interface) {
46-
r.ResolveBlob(ctx, "foo/bar", "sha256:ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff")
47-
})
48-
assertScope("repository:foo/bar:pull", func(ctx context.Context, r ociregistry.Interface) {
49-
r.ResolveManifest(ctx, "foo/bar", "sha256:ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff")
50-
})
51-
assertScope("repository:foo/bar:pull", func(ctx context.Context, r ociregistry.Interface) {
52-
r.ResolveTag(ctx, "foo/bar", "sometag")
53-
})
54-
assertScope("repository:foo/bar:push", func(ctx context.Context, r ociregistry.Interface) {
55-
r.PushBlob(ctx, "foo/bar", ociregistry.Descriptor{
56-
MediaType: "application/json",
57-
Digest: "sha256:ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff",
58-
Size: 3,
59-
}, strings.NewReader("foo"))
60-
})
61-
assertScope("repository:foo/bar:push", func(ctx context.Context, r ociregistry.Interface) {
62-
w, err := r.PushBlobChunked(ctx, "foo/bar", 0)
63-
qt.Assert(t, qt.IsNil(err))
64-
w.Write([]byte("foo"))
65-
w.Close()
66-
67-
id := w.ID()
68-
w, err = r.PushBlobChunkedResume(ctx, "foo/bar", id, 3, 0)
69-
qt.Assert(t, qt.IsNil(err))
70-
w.Write([]byte("bar"))
71-
_, err = w.Commit(digest.FromString("foobar"))
72-
qt.Assert(t, qt.IsNil(err))
73-
})
74-
assertScope("repository:x/y:pull repository:z/w:push", func(ctx context.Context, r ociregistry.Interface) {
75-
r.MountBlob(ctx, "x/y", "z/w", "sha256:ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff")
76-
})
77-
assertScope("repository:foo/bar:push", func(ctx context.Context, r ociregistry.Interface) {
78-
r.PushManifest(ctx, "foo/bar", "sometag", []byte("something"), "application/json")
79-
})
80-
assertScope("repository:foo/bar:push", func(ctx context.Context, r ociregistry.Interface) {
81-
r.DeleteBlob(ctx, "foo/bar", "sha256:ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff")
82-
})
83-
assertScope("repository:foo/bar:push", func(ctx context.Context, r ociregistry.Interface) {
84-
r.DeleteManifest(ctx, "foo/bar", "sha256:ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff")
85-
})
86-
assertScope("repository:foo/bar:push", func(ctx context.Context, r ociregistry.Interface) {
87-
r.DeleteTag(ctx, "foo/bar", "sometag")
88-
})
89-
assertScope("registry:catalog:*", func(ctx context.Context, r ociregistry.Interface) {
90-
ociregistry.All(r.Repositories(ctx, ""))
91-
})
92-
assertScope("repository:foo/bar:pull", func(ctx context.Context, r ociregistry.Interface) {
93-
ociregistry.All(r.Tags(ctx, "foo/bar", ""))
94-
})
95-
assertScope("repository:foo/bar:pull", func(ctx context.Context, r ociregistry.Interface) {
96-
ociregistry.All(r.Referrers(ctx, "foo/bar", "sha256:ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff", ""))
48+
// TODO(go1.23) for method, call := range ocitest.MethodCalls() {
49+
ocitest.MethodCalls()(func(method ocitest.Method, call ocitest.MethodCall) bool {
50+
t.Run(method.String(), func(t *testing.T) {
51+
assertAuthScope(t, srvURL.Host, wantScopes[method], func(ctx context.Context, r ociregistry.Interface) {
52+
err := call(ctx, r)
53+
t.Logf("call error: %v", err)
54+
})
55+
})
56+
return true
9757
})
9858
}
9959

Diff for: ociregistry/ocitest/call.go

+157
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,157 @@
1+
package ocitest
2+
3+
import (
4+
"context"
5+
"strings"
6+
7+
"cuelabs.dev/go/oci/ociregistry"
8+
"github.com/opencontainers/go-digest"
9+
)
10+
11+
type MethodCall = func(ctx context.Context, r ociregistry.Interface) error
12+
13+
//go:generate stringer -type Method
14+
15+
type Method int
16+
17+
const (
18+
UnknownMethod Method = iota
19+
GetManifest
20+
GetBlob
21+
GetBlobRange
22+
GetTag
23+
ResolveBlob
24+
ResolveManifest
25+
ResolveTag
26+
PushBlob
27+
PushBlobChunked
28+
PushBlobChunkedResume
29+
MountBlob
30+
PushManifest
31+
DeleteBlob
32+
DeleteManifest
33+
DeleteTag
34+
Repositories
35+
Tags
36+
Referrers
37+
)
38+
39+
// MethodCalls returns an iterator that produces an element
40+
// for each ociregistry.Interface method holding the name
41+
// of that method and a function that can call that method
42+
// on a registry.
43+
// Read operations always act on the repository foo/read;
44+
// write operations act on foo/write.
45+
// Other arguments are arbitrary.
46+
//
47+
// NOTE: this API is experimental and may change arbitrarily
48+
// in future updates.
49+
func MethodCalls() func(yield func(method Method, call MethodCall) bool) {
50+
return func(yield func(method Method, call MethodCall) bool) {
51+
stopped := false
52+
yield1 := func(method Method, call MethodCall) {
53+
if stopped {
54+
return
55+
}
56+
stopped = !yield(method, call)
57+
}
58+
59+
yield1(GetBlob, func(ctx context.Context, r ociregistry.Interface) error {
60+
rd, err := r.GetBlob(ctx, "foo/read", "sha256:ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff")
61+
if rd != nil {
62+
rd.Close()
63+
}
64+
return err
65+
})
66+
yield1(GetBlobRange, func(ctx context.Context, r ociregistry.Interface) error {
67+
rd, err := r.GetBlobRange(ctx, "foo/read", "sha256:ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff", 100, 200)
68+
if rd != nil {
69+
rd.Close()
70+
}
71+
return err
72+
})
73+
yield1(GetManifest, func(ctx context.Context, r ociregistry.Interface) error {
74+
rd, err := r.GetManifest(ctx, "foo/read", "sha256:ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff")
75+
if rd != nil {
76+
rd.Close()
77+
}
78+
return err
79+
})
80+
yield1(GetTag, func(ctx context.Context, r ociregistry.Interface) error {
81+
rd, err := r.GetTag(ctx, "foo/read", "sometag")
82+
if rd != nil {
83+
rd.Close()
84+
}
85+
return err
86+
})
87+
yield1(ResolveBlob, func(ctx context.Context, r ociregistry.Interface) error {
88+
_, err := r.ResolveBlob(ctx, "foo/read", "sha256:ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff")
89+
return err
90+
})
91+
yield1(ResolveManifest, func(ctx context.Context, r ociregistry.Interface) error {
92+
_, err := r.ResolveManifest(ctx, "foo/read", "sha256:ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff")
93+
return err
94+
})
95+
yield1(ResolveTag, func(ctx context.Context, r ociregistry.Interface) error {
96+
_, err := r.ResolveTag(ctx, "foo/read", "sometag")
97+
return err
98+
})
99+
yield1(PushBlob, func(ctx context.Context, r ociregistry.Interface) error {
100+
_, err := r.PushBlob(ctx, "foo/write", ociregistry.Descriptor{
101+
MediaType: "application/json",
102+
Digest: "sha256:ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff",
103+
Size: 3,
104+
}, strings.NewReader("foo"))
105+
return err
106+
})
107+
yield1(PushBlobChunked, func(ctx context.Context, r ociregistry.Interface) error {
108+
w, err := r.PushBlobChunked(ctx, "foo/write", 0)
109+
if err != nil {
110+
return err
111+
}
112+
w.Close()
113+
return nil
114+
})
115+
yield1(PushBlobChunkedResume, func(ctx context.Context, r ociregistry.Interface) error {
116+
w, err := r.PushBlobChunkedResume(ctx, "foo/write", "/someid", 3, 0)
117+
if err != nil {
118+
return err
119+
}
120+
data := []byte("some data")
121+
if _, err := w.Write(data); err != nil {
122+
return err
123+
}
124+
_, err = w.Commit(digest.FromBytes(data))
125+
return err
126+
})
127+
yield1(MountBlob, func(ctx context.Context, r ociregistry.Interface) error {
128+
_, err := r.MountBlob(ctx, "foo/read", "foo/write", "sha256:ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff")
129+
return err
130+
})
131+
yield1(PushManifest, func(ctx context.Context, r ociregistry.Interface) error {
132+
_, err := r.PushManifest(ctx, "foo/write", "sometag", []byte("something"), "application/json")
133+
return err
134+
})
135+
yield1(DeleteBlob, func(ctx context.Context, r ociregistry.Interface) error {
136+
return r.DeleteBlob(ctx, "foo/write", "sha256:ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff")
137+
})
138+
yield1(DeleteManifest, func(ctx context.Context, r ociregistry.Interface) error {
139+
return r.DeleteManifest(ctx, "foo/write", "sha256:ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff")
140+
})
141+
yield1(DeleteTag, func(ctx context.Context, r ociregistry.Interface) error {
142+
return r.DeleteTag(ctx, "foo/write", "sometag")
143+
})
144+
yield1(Repositories, func(ctx context.Context, r ociregistry.Interface) error {
145+
_, err := ociregistry.All(r.Repositories(ctx, ""))
146+
return err
147+
})
148+
yield1(Tags, func(ctx context.Context, r ociregistry.Interface) error {
149+
_, err := ociregistry.All(r.Tags(ctx, "foo/read", ""))
150+
return err
151+
})
152+
yield1(Referrers, func(ctx context.Context, r ociregistry.Interface) error {
153+
_, err := ociregistry.All(r.Referrers(ctx, "foo/read", "sha256:ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff", ""))
154+
return err
155+
})
156+
}
157+
}

Diff for: ociregistry/ocitest/method_string.go

+41
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

0 commit comments

Comments
 (0)