Skip to content

Commit 3d4a463

Browse files
committed
Finish up test suite port for Use
1 parent 8cebb21 commit 3d4a463

File tree

7 files changed

+194
-46
lines changed

7 files changed

+194
-46
lines changed

pkg/envtest/setup/env/env.go

Lines changed: 8 additions & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -46,7 +46,14 @@ func WithFS(fs fs.FS) Option {
4646
// If no options are provided, it will be created with a production store.Store and remote.Client
4747
// and an OS file system.
4848
func New(options ...Option) (*Env, error) {
49-
env := &Env{}
49+
env := &Env{
50+
// this is the minimal configuration that won't panic
51+
Client: &remote.Client{
52+
Bucket: remote.DefaultBucket,
53+
Server: remote.DefaultServer,
54+
Log: logr.Discard(),
55+
},
56+
}
5057

5158
for _, option := range options {
5259
option(env)
@@ -60,14 +67,5 @@ func New(options ...Option) (*Env, error) {
6067
env.Store = store.NewAt(dir)
6168
}
6269

63-
if env.Client == nil {
64-
// this is the minimal configuration that won't panic
65-
env.Client = &remote.Client{
66-
Bucket: remote.DefaultBucket,
67-
Server: remote.DefaultServer,
68-
Log: logr.Discard(),
69-
}
70-
}
71-
7270
return env, nil
7371
}

pkg/envtest/setup/remote/client.go

Lines changed: 5 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -23,6 +23,10 @@ import (
2323
const DefaultBucket = "kubebuilder-tools"
2424
const DefaultServer = "storage.googleapis.com"
2525

26+
// ErrChecksumMismatch is returned when checksum verification is enabled,
27+
// but the checksum does not match
28+
var ErrChecksumMismatch = errors.New("checksum mismatch")
29+
2630
// objectList is the parts we need of the GCS "list-objects-in-bucket" endpoint.
2731
type objectList struct {
2832
Items []bucketObject `json:"items"`
@@ -180,7 +184,7 @@ func (c *Client) GetVersion(ctx context.Context, version versions.Concrete, plat
180184
sum := base64.StdEncoding.EncodeToString(checksum.Sum(nil))
181185

182186
if sum != platform.MD5 {
183-
return fmt.Errorf("checksum mismatch for %s: %s (computed) != %s (reported from GCS)", itemName, sum, platform.MD5)
187+
return fmt.Errorf("%w for %s: %s (computed) != %s (reported from GCS)", ErrChecksumMismatch, itemName, sum, platform.MD5)
184188
}
185189
} else if _, err := io.Copy(out, resp.Body); err != nil {
186190
return fmt.Errorf("unable to download %s: %w", itemName, err)

pkg/envtest/setup/testhelpers/package.go

Lines changed: 6 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -41,12 +41,12 @@ func ContentsFor(filename string) ([]byte, error) { //nolint:revive
4141
return out.Bytes(), nil
4242
}
4343

44-
func verWith(name string, contents []byte) item {
45-
res := item{
46-
meta: bucketObject{Name: name},
47-
contents: contents,
44+
func verWith(name string, contents []byte) Item {
45+
res := Item{
46+
Meta: BucketObject{Name: name},
47+
Contents: contents,
4848
}
49-
hash := md5.Sum(res.contents) //nolint:gosec
50-
res.meta.Hash = base64.StdEncoding.EncodeToString(hash[:])
49+
hash := md5.Sum(res.Contents) //nolint:gosec
50+
res.Meta.Hash = base64.StdEncoding.EncodeToString(hash[:])
5151
return res
5252
}

pkg/envtest/setup/testhelpers/remote.go

Lines changed: 23 additions & 20 deletions
Original file line numberDiff line numberDiff line change
@@ -10,18 +10,18 @@ import (
1010

1111
// objectList is the parts we need of the GCS "list-objects-in-bucket" endpoint.
1212
type objectList struct {
13-
Items []bucketObject `json:"items"`
13+
Items []BucketObject `json:"items"`
1414
}
1515

16-
// bucketObject is the parts we need of the GCS object metadata.
17-
type bucketObject struct {
16+
// BucketObject is the parts we need of the GCS object metadata.
17+
type BucketObject struct {
1818
Name string `json:"name"`
1919
Hash string `json:"md5Hash"`
2020
}
2121

22-
type item struct {
23-
meta bucketObject
24-
contents []byte
22+
type Item struct {
23+
Meta BucketObject
24+
Contents []byte
2525
}
2626

2727
var (
@@ -65,13 +65,13 @@ var (
6565
"kubebuilder-tools-v1.19.2-linux-ppc64le.tar.gz",
6666
}
6767

68-
contents map[string]item
68+
contents map[string]Item
6969
)
7070

71-
func makeContents(names []string) ([]item, error) {
72-
res := make([]item, len(names))
71+
func makeContents(names []string) ([]Item, error) {
72+
res := make([]Item, len(names))
7373
if contents == nil {
74-
contents = make(map[string]item, len(RemoteNames))
74+
contents = make(map[string]Item, len(RemoteNames))
7575
}
7676

7777
var errs error
@@ -103,26 +103,29 @@ func makeContents(names []string) ([]item, error) {
103103
// The package names should be a subset of RemoteNames.
104104
//
105105
// The returned shutdown function should be called at the end of the test
106-
func NewServer() (addr string, shutdown func(), err error) {
107-
versions, err := makeContents(RemoteNames)
108-
if err != nil {
109-
return
106+
func NewServer(items ...Item) (addr string, shutdown func(), err error) {
107+
if items == nil {
108+
versions, err := makeContents(RemoteNames)
109+
if err != nil {
110+
return "", nil, err
111+
}
112+
items = versions
110113
}
111114

112115
server := ghttp.NewServer()
113116

114-
list := objectList{Items: make([]bucketObject, len(versions))}
115-
for i, ver := range versions {
117+
list := objectList{Items: make([]BucketObject, len(items))}
118+
for i, ver := range items {
116119
ver := ver // copy to avoid capturing the iteration variable
117-
list.Items[i] = ver.meta
118-
server.RouteToHandler("GET", "/storage/v1/b/kubebuilder-tools-test/o/"+ver.meta.Name, func(resp http.ResponseWriter, req *http.Request) {
120+
list.Items[i] = ver.Meta
121+
server.RouteToHandler("GET", "/storage/v1/b/kubebuilder-tools-test/o/"+ver.Meta.Name, func(resp http.ResponseWriter, req *http.Request) {
119122
if req.URL.Query().Get("alt") == "media" {
120123
resp.WriteHeader(http.StatusOK)
121-
gomega.Expect(resp.Write(ver.contents)).To(gomega.Equal(len(ver.contents)))
124+
gomega.Expect(resp.Write(ver.Contents)).To(gomega.Equal(len(ver.Contents)))
122125
} else {
123126
ghttp.RespondWithJSONEncoded(
124127
http.StatusOK,
125-
ver.meta,
128+
ver.Meta,
126129
)(resp, req)
127130
}
128131
})

pkg/envtest/setup/use/config.go

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -53,7 +53,7 @@ func WithEnvOptions(opts ...env.Option) Option {
5353
}
5454

5555
// VerifySum turns on md5 verification of the downloaded package
56-
func VerifySum(verify bool) Option { return func(c *config) { c.verifySum = !verify } }
56+
func VerifySum(verify bool) Option { return func(c *config) { c.verifySum = verify } }
5757

5858
func configure(options ...Option) *config {
5959
cfg := &config{

pkg/envtest/setup/use/use.go

Lines changed: 16 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -48,21 +48,21 @@ func Use(ctx context.Context, version versions.Spec, options ...Option) (Result,
4848
return Result{}, err
4949
}
5050

51-
if !cfg.forceDownload && !version.CheckLatest || cfg.noDownload {
51+
if cfg.noDownload {
5252
if selectedLocal != (store.Item{}) {
53-
return Result{
54-
Path: env.PathTo(&selectedLocal.Version, selectedLocal.Platform),
55-
Version: versions.Spec{Selector: selectedLocal.Version},
56-
Platform: selectedLocal.Platform,
57-
}, nil
53+
return toResult(env, selectedLocal, ""), nil
5854
}
5955

6056
return Result{}, fmt.Errorf("%w: no local version matching %s found, but you specified NoDownload()", ErrNoMatchingVersion, version)
6157
}
6258

59+
if !cfg.forceDownload && !version.CheckLatest && selectedLocal != (store.Item{}) {
60+
return toResult(env, selectedLocal, ""), nil
61+
}
62+
6363
selectedVersion, selectedPlatform, err := env.SelectRemoteVersion(ctx, version, cfg.platform)
6464
if err != nil {
65-
return Result{}, err
65+
return Result{}, fmt.Errorf("%w: %w", ErrNoMatchingVersion, err)
6666
}
6767

6868
if selectedLocal != (store.Item{}) && !selectedVersion.NewerThan(selectedLocal.Version) {
@@ -84,3 +84,12 @@ func Use(ctx context.Context, version versions.Spec, options ...Option) (Result,
8484
MD5: selectedPlatform.MD5,
8585
}, nil
8686
}
87+
88+
func toResult(env *env.Env, item store.Item, md5 string) Result {
89+
return Result{
90+
Version: versions.Spec{Selector: item.Version},
91+
Platform: item.Platform,
92+
Path: env.PathTo(&item.Version, item.Platform),
93+
MD5: md5,
94+
}
95+
}

pkg/envtest/setup/use/use_test.go

Lines changed: 135 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -200,11 +200,145 @@ var _ = Describe("Use", func() {
200200
Patch: versions.AnyPoint,
201201
},
202202
},
203-
use.WithPlatform("*", "*"),
203+
use.WithPlatform("linux", "amd64"),
204204
use.WithEnvOptions(defaultEnvOpts...),
205205
)
206206
Expect(err).NotTo(HaveOccurred())
207207
Expect(result.Version).To(Equal(versions.Spec{Selector: versions.Concrete{Major: 1, Minor: 14, Patch: 26}}))
208208
})
209209
})
210+
211+
It("should check for a local match first", func() {
212+
result, err := use.Use(
213+
ctx,
214+
versions.Spec{
215+
Selector: versions.TildeSelector{
216+
Concrete: versions.Concrete{Major: 1, Minor: 16, Patch: 0},
217+
},
218+
},
219+
use.WithPlatform("linux", "amd64"),
220+
use.WithEnvOptions(append(defaultEnvOpts, env.WithClient(nil))...),
221+
)
222+
Expect(err).NotTo(HaveOccurred())
223+
Expect(result.Version).To(Equal(versions.Spec{Selector: versions.Concrete{Major: 1, Minor: 16, Patch: 1}}))
224+
})
225+
226+
It("should fall back to the network if no local matches are found", func() {
227+
result, err := use.Use(
228+
ctx,
229+
versions.Spec{
230+
Selector: versions.TildeSelector{
231+
Concrete: versions.Concrete{Major: 1, Minor: 19, Patch: 0},
232+
},
233+
},
234+
use.WithPlatform("linux", "amd64"),
235+
use.WithEnvOptions(defaultEnvOpts...),
236+
)
237+
Expect(err).NotTo(HaveOccurred())
238+
Expect(result.Version).To(Equal(versions.Spec{Selector: versions.Concrete{Major: 1, Minor: 19, Patch: 2}}))
239+
})
240+
241+
It("should error out if no matches can be found anywhere", func() {
242+
_, err := use.Use(
243+
ctx,
244+
versions.Spec{
245+
Selector: versions.Concrete{Major: 1, Minor: 13, Patch: 0},
246+
},
247+
use.WithPlatform("*", "*"),
248+
use.WithEnvOptions(defaultEnvOpts...),
249+
)
250+
251+
Expect(err).To(MatchError(use.ErrNoMatchingVersion))
252+
})
253+
254+
It("should skip local version matches with non-matching platform", func() {
255+
_, err := use.Use(
256+
ctx,
257+
versions.Spec{
258+
Selector: versions.Concrete{Minor: 1, Major: 16, Patch: 2},
259+
},
260+
use.WithPlatform("linux", "amd64"),
261+
use.NoDownload(true),
262+
use.WithEnvOptions(defaultEnvOpts...),
263+
)
264+
265+
Expect(err).To(MatchError(use.ErrNoMatchingVersion))
266+
})
267+
268+
It("should skip remote version matches with non-matching platform", func() {
269+
_, err := use.Use(
270+
ctx,
271+
versions.Spec{
272+
Selector: versions.Concrete{Minor: 1, Major: 11, Patch: 1},
273+
},
274+
use.WithPlatform("linux", "amd64"),
275+
use.NoDownload(true),
276+
use.WithEnvOptions(defaultEnvOpts...),
277+
)
278+
279+
Expect(err).To(MatchError(use.ErrNoMatchingVersion))
280+
})
281+
282+
Context("with an invalid checksum", func() {
283+
var client *remote.Client
284+
BeforeEach(func() {
285+
name := "kubebuilder-tools-86.75.309-linux-amd64.tar.gz"
286+
contents, err := testhelpers.ContentsFor(name)
287+
Expect(err).NotTo(HaveOccurred())
288+
289+
server, stop, err := testhelpers.NewServer(testhelpers.Item{
290+
Meta: testhelpers.BucketObject{
291+
Name: name,
292+
Hash: "not the right one!",
293+
},
294+
Contents: contents,
295+
})
296+
Expect(err).NotTo(HaveOccurred())
297+
DeferCleanup(stop)
298+
299+
client = &remote.Client{
300+
Bucket: "kubebuilder-tools-test",
301+
Server: server,
302+
Insecure: true,
303+
Log: testLog.WithName("test-remote-client"),
304+
}
305+
})
306+
307+
When("validating the checksum", func() {
308+
It("should fail with an appropriate error", func() {
309+
_, err := use.Use(
310+
ctx,
311+
versions.Spec{
312+
Selector: versions.Concrete{
313+
Major: 86,
314+
Minor: 75,
315+
Patch: 309,
316+
},
317+
},
318+
use.VerifySum(true),
319+
use.WithEnvOptions(append(defaultEnvOpts, env.WithClient(client))...),
320+
)
321+
322+
Expect(err).To(MatchError(remote.ErrChecksumMismatch))
323+
})
324+
})
325+
326+
When("not validating checksum", func() {
327+
It("should return the version without error", func() {
328+
result, err := use.Use(
329+
ctx,
330+
versions.Spec{
331+
Selector: versions.Concrete{
332+
Major: 86,
333+
Minor: 75,
334+
Patch: 309,
335+
},
336+
},
337+
use.WithEnvOptions(append(defaultEnvOpts, env.WithClient(client))...),
338+
)
339+
Expect(err).NotTo(HaveOccurred())
340+
Expect(result.Version).To(Equal(versions.Spec{Selector: versions.Concrete{Major: 86, Minor: 75, Patch: 309}}))
341+
})
342+
})
343+
})
210344
})

0 commit comments

Comments
 (0)