Chromium Code Reviews| 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 |
| index ca45233ac985821177091c90e2bf4d3985b041c7..62585228d85cddff462328892795a78a3ad5eb99 100644 |
| --- a/client/cmd/isolate/upload_tracker_test.go |
| +++ b/client/cmd/isolate/upload_tracker_test.go |
| @@ -18,8 +18,10 @@ import ( |
| "bytes" |
| "fmt" |
| "io" |
| + "io/ioutil" |
| "os" |
| "reflect" |
| + "strings" |
| "testing" |
| "github.com/luci/luci-go/common/isolated" |
| @@ -30,7 +32,8 @@ import ( |
| // 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 |
| + writeFiles map[string]*bytes.Buffer |
| + readFiles map[string]io.Reader |
| } |
| // shouldResembleByteMap asserts that actual (a map[string]*bytes.Buffer) contains |
| @@ -73,11 +76,20 @@ 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) |
| + if fos.writeFiles == nil { |
| + fos.writeFiles = make(map[string]*bytes.Buffer) |
| } |
| - fos.files[name] = &bytes.Buffer{} |
| - return nopWriteCloser{fos.files[name]}, nil |
| + fos.writeFiles[name] = &bytes.Buffer{} |
| + return nopWriteCloser{fos.writeFiles[name]}, nil |
| +} |
| + |
| +// implements Open by returning a pre-configured Reader. |
| +func (fos *fakeOS) Open(name string) (io.ReadCloser, error) { |
| + r, ok := fos.readFiles[name] |
| + if !ok { |
| + panic(fmt.Sprintf("fakeOS: file not found (%s); not implemented.", name)) |
| + } |
| + return ioutil.NopCloser(r), nil |
| } |
| // fakeChecker implements Checker by responding to method invocations by |
| @@ -106,6 +118,8 @@ func (checker *fakeChecker) Close() error { return nil } |
| type fakeUploader struct { |
| // uploadBytesCalls is a record of the arguments to each call of UploadBytes. |
| uploadBytesCalls []uploaderUploadBytesArgs |
| + // uploadFileCalls is a record of the arguments to each call of UploadFile. |
| + uploadFileCalls []uploaderUploadFileArgs |
| Uploader // TODO(mcgreevy): implement other methods. |
| } |
| @@ -116,10 +130,18 @@ type uploaderUploadBytesArgs struct { |
| ps *isolatedclient.PushState |
| } |
| +type uploaderUploadFileArgs struct { |
| + item *Item |
| + 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) UploadFile(item *Item, ps *isolatedclient.PushState, done func()) { |
| + uploader.uploadFileCalls = append(uploader.uploadFileCalls, uploaderUploadFileArgs{item, ps}) |
|
mithro
2017/08/01 04:45:42
Do you ever call the done function? Don't you need
mcgreevy
2017/08/02 03:59:30
I didn't bother because the done function is only
|
| +} |
| func (uploader *fakeUploader) Close() error { return nil } |
| func TestSkipsUpload(t *testing.T) { |
| @@ -149,7 +171,7 @@ func TestSkipsUpload(t *testing.T) { |
| // 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(fos.writeFiles, shouldResembleByteMap, map[string][]byte{"/a/isolatedPath": wantIsolJSON}) |
| So(isolSummary.Digest, ShouldResemble, isolated.HashBytes(wantIsolJSON)) |
| So(isolSummary.Name, ShouldEqual, "isolatedPath") |
| @@ -185,7 +207,7 @@ func TestDontSkipUpload(t *testing.T) { |
| // 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(fos.writeFiles, shouldResembleByteMap, map[string][]byte{"/a/isolatedPath": wantIsolJSON}) |
|
mithro
2017/08/01 04:45:42
Thought to consider (no action needed in this CL..
mcgreevy
2017/08/02 03:59:30
I notice that after I uploaded this CL. But then
|
| So(isolSummary.Digest, ShouldResemble, isolated.HashBytes(wantIsolJSON)) |
| So(isolSummary.Name, ShouldEqual, "isolatedPath") |
| @@ -236,7 +258,7 @@ func TestHandlesSymlinks(t *testing.T) { |
| // 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(fos.writeFiles, shouldResembleByteMap, map[string][]byte{"/a/isolatedPath": wantIsolJSON}) |
| So(isolSummary.Digest, ShouldResemble, isolated.HashBytes(wantIsolJSON)) |
| So(isolSummary.Name, ShouldEqual, "isolatedPath") |
| @@ -246,3 +268,92 @@ func TestHandlesSymlinks(t *testing.T) { |
| }) |
| }) |
| } |
| + |
| +func TestHandlesIndividualFiles(t *testing.T) { |
| + t.Parallel() |
| + Convey(`Individual files should be stored in the isolated json and uploaded`, t, func() { |
| + isol := &isolated.Isolated{} |
| + |
| + // non-nil PushState means don't skip the upload. |
|
mithro
2017/08/01 04:45:42
I'm a bit confused at how the push state is being
mcgreevy
2017/08/02 03:59:30
The PushState is not really meant to be inspected
|
| + pushState := &isolatedclient.PushState{} |
| + checker := &fakeChecker{ps: pushState} |
| + uploader := &fakeUploader{} |
| + |
| + ut := NewUploadTracker(checker, uploader, isol) |
| + fos := &fakeOS{ |
| + readFiles: map[string]io.Reader{ |
| + "/a/b/foo": strings.NewReader("foo contents"), |
| + "/a/b/bar": strings.NewReader("bar contents"), |
| + }, |
| + } |
| + ut.lOS = fos // Override filesystem calls with fake. |
| + |
| + parts := partitionedDeps{ |
| + indivFiles: itemGroup{ |
| + items: []*Item{ |
| + {Path: "/a/b/foo", RelPath: "foo", Size: 1, Mode: 006}, |
| + {Path: "/a/b/bar", RelPath: "bar", Size: 2, Mode: 006}, |
| + }, |
| + totalSize: 3, |
| + }, |
| + } |
| + err := ut.UploadDeps(parts) |
| + So(err, ShouldBeNil) |
| + |
| + fooHash := isolated.HashBytes([]byte("foo contents")) |
| + barHash := isolated.HashBytes([]byte("bar contents")) |
| + wantFiles := map[string]isolated.File{ |
| + "foo": { |
| + Digest: fooHash, |
| + Mode: Int(6), |
| + Size: Int64(1)}, |
| + "bar": { |
| + Digest: barHash, |
| + Mode: Int(6), |
| + Size: Int64(2)}, |
| + } |
| + |
| + So(ut.isol.Files, ShouldResemble, wantFiles) |
| + |
| + So(uploader.uploadBytesCalls, ShouldEqual, nil) |
| + |
| + isolSummary, err := ut.Finalize("/a/isolatedPath") |
| + So(err, ShouldBeNil) |
| + |
| + wantIsolJSONTmpl := `{"algo":"","files":{"bar":{"h":"%s","m":6,"s":2},"foo":{"h":"%s","m":6,"s":1}},"version":""}` |
| + wantIsolJSON := []byte(fmt.Sprintf(wantIsolJSONTmpl, barHash, fooHash)) |
|
mithro
2017/08/01 04:45:42
Is there any reason particular reason to define wa
mcgreevy
2017/08/02 03:59:29
Line length.
It's not Go style to split function
|
| + So(fos.writeFiles, 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}, |
| + }) |
| + So(uploader.uploadFileCalls, ShouldResemble, []uploaderUploadFileArgs{ |
| + { |
| + item: &Item{ |
| + Path: "/a/b/foo", |
| + RelPath: "foo", |
| + Size: 1, |
| + Mode: os.FileMode(6), |
| + Digest: fooHash, |
| + }, |
| + ps: pushState, |
| + }, |
| + { |
| + item: &Item{ |
| + Path: "/a/b/bar", |
| + RelPath: "bar", |
| + Size: 2, |
| + Mode: os.FileMode(6), |
|
mithro
2017/08/01 04:45:42
You should be trying with different File modes may
mcgreevy
2017/08/02 03:59:30
Done.
|
| + Digest: barHash, |
| + }, |
| + ps: pushState, |
| + }, |
| + }) |
| + }) |
| +} |
| + |
| +func Int(i int) *int { return &i } |
| +func Int64(i int64) *int64 { return &i } |
|
mithro
2017/08/01 04:45:43
Could you add a comment about why these casts are
mcgreevy
2017/08/02 03:59:30
Done.
FYI they're not casts, they're just helpers
|