Chromium Code Reviews
chromiumcodereview-hr@appspot.gserviceaccount.com (chromiumcodereview-hr) | Please choose your nickname with Settings | Help | Chromium Project | Gerrit Changes | Sign out
(175)

Unified Diff: client/cmd/isolate/upload_tracker_test.go

Issue 2992693002: isolate: add uploadtracker tests for symlinks and isolated files (Closed)
Patch Set: address review comments Created 3 years, 5 months ago
Use n/p to move between diff chunks; N/P to move between comments. Draft comments are only viewable by you.
Jump to:
View side-by-side diff with in-line comments
Download patch
« no previous file with comments | « client/cmd/isolate/upload_tracker.go ('k') | client/cmd/isolate/uploader.go » ('j') | no next file with comments »
Expand Comments ('e') | Collapse Comments ('c') | Show Comments Hide Comments ('s')
Index: client/cmd/isolate/upload_tracker_test.go
diff --git a/client/cmd/isolate/upload_tracker_test.go b/client/cmd/isolate/upload_tracker_test.go
new file mode 100644
index 0000000000000000000000000000000000000000..ca45233ac985821177091c90e2bf4d3985b041c7
--- /dev/null
+++ b/client/cmd/isolate/upload_tracker_test.go
@@ -0,0 +1,248 @@
+// Copyright 2017 The LUCI Authors.
+//
+// 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 main
+
+import (
+ "bytes"
+ "fmt"
+ "io"
+ "os"
+ "reflect"
+ "testing"
+
+ "github.com/luci/luci-go/common/isolated"
+ "github.com/luci/luci-go/common/isolatedclient"
+ . "github.com/smartystreets/goconvey/convey"
+)
+
+// Fake OS imitiates a filesystem by storing file contents in a map.
+// It also provides a dummy Readlink implementation.
+type fakeOS struct {
+ files map[string]*bytes.Buffer
+}
+
+// shouldResembleByteMap asserts that actual (a map[string]*bytes.Buffer) contains
+// the same data as expected (a map[string][]byte).
+// The types of actual and expected differ to make writing tests with fakeOS more convenient.
+func shouldResembleByteMap(actual interface{}, expected ...interface{}) string {
+ act, ok := actual.(map[string]*bytes.Buffer)
+ if !ok {
+ return "actual is not a map[string]*bytes.Buffer"
+ }
+ if len(expected) != 1 {
+ return "expected is not a map[string][]byte"
+ }
+ exp, ok := expected[0].(map[string][]byte)
+ if !ok {
+ return "expected is not a map[string][]byte"
+ }
+
+ if len(act) != len(exp) {
+ return fmt.Sprintf("len(actual) != len(expected): %v != %v", len(act), len(exp))
+ }
+
+ for k, v := range act {
+ if got, want := v.Bytes(), exp[k]; !reflect.DeepEqual(got, want) {
+ return fmt.Sprintf("actual[%q] != expected[%q]: %q != %q", k, k, got, want)
+ }
+ }
+ return ""
+}
+
+func (fos *fakeOS) Readlink(name string) (string, error) {
+ return "link:" + name, nil
+}
+
+type nopWriteCloser struct {
+ io.Writer
+}
+
+func (nopWriteCloser) Close() error { return nil }
+
+// implements OpenFile by returning a writer that writes to a bytes.Buffer.
+func (fos *fakeOS) OpenFile(name string, flag int, perm os.FileMode) (io.WriteCloser, error) {
+ if fos.files == nil {
+ fos.files = make(map[string]*bytes.Buffer)
+ }
+ fos.files[name] = &bytes.Buffer{}
+ return nopWriteCloser{fos.files[name]}, nil
+}
+
+// fakeChecker implements Checker by responding to method invocations by
+// invoking the supplied callback with the supplied item, and a hard-coded *PushState.
+type fakeChecker struct {
+ ps *isolatedclient.PushState
+}
+
+type checkerAddItemArgs struct {
+ item *Item
+ isolated bool
+}
+
+type checkerAddItemResponse struct {
+ item *Item
+ ps *isolatedclient.PushState
+}
+
+func (checker *fakeChecker) AddItem(item *Item, isolated bool, callback CheckerCallback) {
+ callback(item, checker.ps)
+}
+
+func (checker *fakeChecker) Close() error { return nil }
+
+// fakeChecker implements Uploader while recording method arguments.
+type fakeUploader struct {
+ // uploadBytesCalls is a record of the arguments to each call of UploadBytes.
+ uploadBytesCalls []uploaderUploadBytesArgs
+
+ Uploader // TODO(mcgreevy): implement other methods.
+}
+
+type uploaderUploadBytesArgs struct {
+ relPath string
+ isolJSON []byte
+ ps *isolatedclient.PushState
+}
+
+func (uploader *fakeUploader) UploadBytes(relPath string, isolJSON []byte, ps *isolatedclient.PushState, f func()) {
+ uploader.uploadBytesCalls = append(uploader.uploadBytesCalls, uploaderUploadBytesArgs{relPath, isolJSON, ps})
+}
+
+func (uploader *fakeUploader) Close() error { return nil }
+
+func TestSkipsUpload(t *testing.T) {
+ t.Parallel()
+ Convey(`nil push state signals that upload should be skipped`, t, func() {
+ isol := &isolated.Isolated{}
+
+ // nil PushState means skip the upload.
+ checker := &fakeChecker{ps: nil}
+
+ uploader := &fakeUploader{}
+
+ ut := NewUploadTracker(checker, uploader, isol)
+ fos := &fakeOS{}
+ ut.lOS = fos // Override filesystem calls with fake.
+
+ parts := partitionedDeps{} // no actual deps.
+ err := ut.UploadDeps(parts)
+ So(err, ShouldBeNil)
+
+ // No deps, so Files should be empty.
+ wantFiles := map[string]isolated.File{}
+ So(ut.isol.Files, ShouldResemble, wantFiles)
+
+ isolSummary, err := ut.Finalize("/a/isolatedPath")
+ So(err, ShouldBeNil)
+
+ // In this test, the only item that is checked and uploaded is the generated isolated file.
+ wantIsolJSON := []byte(`{"algo":"","version":""}`)
+ So(fos.files, shouldResembleByteMap, map[string][]byte{"/a/isolatedPath": wantIsolJSON})
+
+ So(isolSummary.Digest, ShouldResemble, isolated.HashBytes(wantIsolJSON))
+ So(isolSummary.Name, ShouldEqual, "isolatedPath")
+
+ So(uploader.uploadBytesCalls, ShouldEqual, nil)
+ })
+}
+
+func TestDontSkipUpload(t *testing.T) {
+ t.Parallel()
+ Convey(`passing non-nil push state signals that upload should be performed`, t, func() {
+ isol := &isolated.Isolated{}
+
+ // non-nil PushState means don't skip the upload.
+ checker := &fakeChecker{ps: &isolatedclient.PushState{}}
+
+ uploader := &fakeUploader{}
+
+ ut := NewUploadTracker(checker, uploader, isol)
+ fos := &fakeOS{}
+ ut.lOS = fos // Override filesystem calls with fake.
+
+ parts := partitionedDeps{} // no actual deps.
+ err := ut.UploadDeps(parts)
+ So(err, ShouldBeNil)
+
+ // No deps, so Files should be empty.
+ wantFiles := map[string]isolated.File{}
+ So(ut.isol.Files, ShouldResemble, wantFiles)
+
+ isolSummary, err := ut.Finalize("/a/isolatedPath")
+ So(err, ShouldBeNil)
+
+ // In this test, the only item that is checked and uploaded is the generated isolated file.
+ wantIsolJSON := []byte(`{"algo":"","version":""}`)
+ So(fos.files, shouldResembleByteMap, map[string][]byte{"/a/isolatedPath": wantIsolJSON})
+
+ So(isolSummary.Digest, ShouldResemble, isolated.HashBytes(wantIsolJSON))
+ So(isolSummary.Name, ShouldEqual, "isolatedPath")
+
+ // Upload was not skipped.
+ So(uploader.uploadBytesCalls, ShouldResemble, []uploaderUploadBytesArgs{
+ {"isolatedPath", wantIsolJSON, checker.ps},
+ })
+ })
+}
+
+func TestHandlesSymlinks(t *testing.T) {
+ t.Parallel()
+ Convey(`Symlinks should be stored in the isolated json`, t, func() {
+ isol := &isolated.Isolated{}
+
+ // The checker and uploader will only be used for uploading the isolated JSON.
+ // The symlinks themselves do not need to be checked or uploaded.
+ // non-nil PushState means don't skip the upload.
+ checker := &fakeChecker{ps: &isolatedclient.PushState{}}
+ uploader := &fakeUploader{}
+
+ ut := NewUploadTracker(checker, uploader, isol)
+ fos := &fakeOS{}
+ ut.lOS = fos // Override filesystem calls with fake.
+
+ parts := partitionedDeps{
+ links: itemGroup{
+ items: []*Item{
+ {Path: "/a/b/c", RelPath: "c"},
+ },
+ totalSize: 9,
+ },
+ }
+ err := ut.UploadDeps(parts)
+ So(err, ShouldBeNil)
+
+ path := "link:/a/b/c"
+ wantFiles := map[string]isolated.File{"c": {Link: &path}}
+
+ So(ut.isol.Files, ShouldResemble, wantFiles)
+
+ // Symlinks are not uploaded.
+ So(uploader.uploadBytesCalls, ShouldEqual, nil)
+
+ isolSummary, err := ut.Finalize("/a/isolatedPath")
+ So(err, ShouldBeNil)
+
+ // In this test, the only item that is checked and uploaded is the generated isolated file.
+ wantIsolJSON := []byte(`{"algo":"","files":{"c":{"l":"link:/a/b/c"}},"version":""}`)
+ So(fos.files, shouldResembleByteMap, map[string][]byte{"/a/isolatedPath": wantIsolJSON})
+
+ So(isolSummary.Digest, ShouldResemble, isolated.HashBytes(wantIsolJSON))
+ So(isolSummary.Name, ShouldEqual, "isolatedPath")
+
+ So(uploader.uploadBytesCalls, ShouldResemble, []uploaderUploadBytesArgs{
+ {"isolatedPath", wantIsolJSON, checker.ps},
+ })
+ })
+}
« no previous file with comments | « client/cmd/isolate/upload_tracker.go ('k') | client/cmd/isolate/uploader.go » ('j') | no next file with comments »

Powered by Google App Engine
This is Rietveld 408576698