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

Side by Side Diff: client/cmd/isolate/upload_tracker_test.go

Issue 2992113002: isolate: test uploading of individual regular files (Closed)
Patch Set: Created 3 years, 4 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 unified diff | Download patch
« no previous file with comments | « client/cmd/isolate/upload_tracker.go ('k') | no next file » | no next file with comments »
Toggle Intra-line Diffs ('i') | Expand Comments ('e') | Collapse Comments ('c') | Show Comments Hide Comments ('s')
OLDNEW
1 // Copyright 2017 The LUCI Authors. 1 // Copyright 2017 The LUCI Authors.
2 // 2 //
3 // Licensed under the Apache License, Version 2.0 (the "License"); 3 // Licensed under the Apache License, Version 2.0 (the "License");
4 // you may not use this file except in compliance with the License. 4 // you may not use this file except in compliance with the License.
5 // You may obtain a copy of the License at 5 // You may obtain a copy of the License at
6 // 6 //
7 // http://www.apache.org/licenses/LICENSE-2.0 7 // http://www.apache.org/licenses/LICENSE-2.0
8 // 8 //
9 // Unless required by applicable law or agreed to in writing, software 9 // Unless required by applicable law or agreed to in writing, software
10 // distributed under the License is distributed on an "AS IS" BASIS, 10 // distributed under the License is distributed on an "AS IS" BASIS,
11 // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 11 // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
12 // See the License for the specific language governing permissions and 12 // See the License for the specific language governing permissions and
13 // limitations under the License. 13 // limitations under the License.
14 14
15 package main 15 package main
16 16
17 import ( 17 import (
18 "bytes" 18 "bytes"
19 "fmt" 19 "fmt"
20 "io" 20 "io"
21 "io/ioutil"
21 "os" 22 "os"
22 "reflect" 23 "reflect"
24 "strings"
23 "testing" 25 "testing"
24 26
25 "github.com/luci/luci-go/common/isolated" 27 "github.com/luci/luci-go/common/isolated"
26 "github.com/luci/luci-go/common/isolatedclient" 28 "github.com/luci/luci-go/common/isolatedclient"
27 . "github.com/smartystreets/goconvey/convey" 29 . "github.com/smartystreets/goconvey/convey"
28 ) 30 )
29 31
30 // Fake OS imitiates a filesystem by storing file contents in a map. 32 // Fake OS imitiates a filesystem by storing file contents in a map.
31 // It also provides a dummy Readlink implementation. 33 // It also provides a dummy Readlink implementation.
32 type fakeOS struct { 34 type fakeOS struct {
33 » files map[string]*bytes.Buffer 35 » writeFiles map[string]*bytes.Buffer
36 » readFiles map[string]io.Reader
34 } 37 }
35 38
36 // shouldResembleByteMap asserts that actual (a map[string]*bytes.Buffer) contai ns 39 // shouldResembleByteMap asserts that actual (a map[string]*bytes.Buffer) contai ns
37 // the same data as expected (a map[string][]byte). 40 // the same data as expected (a map[string][]byte).
38 // The types of actual and expected differ to make writing tests with fakeOS mor e convenient. 41 // The types of actual and expected differ to make writing tests with fakeOS mor e convenient.
39 func shouldResembleByteMap(actual interface{}, expected ...interface{}) string { 42 func shouldResembleByteMap(actual interface{}, expected ...interface{}) string {
40 act, ok := actual.(map[string]*bytes.Buffer) 43 act, ok := actual.(map[string]*bytes.Buffer)
41 if !ok { 44 if !ok {
42 return "actual is not a map[string]*bytes.Buffer" 45 return "actual is not a map[string]*bytes.Buffer"
43 } 46 }
(...skipping 22 matching lines...) Expand all
66 } 69 }
67 70
68 type nopWriteCloser struct { 71 type nopWriteCloser struct {
69 io.Writer 72 io.Writer
70 } 73 }
71 74
72 func (nopWriteCloser) Close() error { return nil } 75 func (nopWriteCloser) Close() error { return nil }
73 76
74 // implements OpenFile by returning a writer that writes to a bytes.Buffer. 77 // implements OpenFile by returning a writer that writes to a bytes.Buffer.
75 func (fos *fakeOS) OpenFile(name string, flag int, perm os.FileMode) (io.WriteCl oser, error) { 78 func (fos *fakeOS) OpenFile(name string, flag int, perm os.FileMode) (io.WriteCl oser, error) {
76 » if fos.files == nil { 79 » if fos.writeFiles == nil {
77 » » fos.files = make(map[string]*bytes.Buffer) 80 » » fos.writeFiles = make(map[string]*bytes.Buffer)
78 } 81 }
79 » fos.files[name] = &bytes.Buffer{} 82 » fos.writeFiles[name] = &bytes.Buffer{}
80 » return nopWriteCloser{fos.files[name]}, nil 83 » return nopWriteCloser{fos.writeFiles[name]}, nil
84 }
85
86 // implements Open by returning a pre-configured Reader.
87 func (fos *fakeOS) Open(name string) (io.ReadCloser, error) {
88 » r, ok := fos.readFiles[name]
89 » if !ok {
90 » » panic(fmt.Sprintf("fakeOS: file not found (%s); not implemented. ", name))
91 » }
92 » return ioutil.NopCloser(r), nil
81 } 93 }
82 94
83 // fakeChecker implements Checker by responding to method invocations by 95 // fakeChecker implements Checker by responding to method invocations by
84 // invoking the supplied callback with the supplied item, and a hard-coded *Push State. 96 // invoking the supplied callback with the supplied item, and a hard-coded *Push State.
85 type fakeChecker struct { 97 type fakeChecker struct {
86 ps *isolatedclient.PushState 98 ps *isolatedclient.PushState
87 } 99 }
88 100
89 type checkerAddItemArgs struct { 101 type checkerAddItemArgs struct {
90 item *Item 102 item *Item
91 isolated bool 103 isolated bool
92 } 104 }
93 105
94 type checkerAddItemResponse struct { 106 type checkerAddItemResponse struct {
95 item *Item 107 item *Item
96 ps *isolatedclient.PushState 108 ps *isolatedclient.PushState
97 } 109 }
98 110
99 func (checker *fakeChecker) AddItem(item *Item, isolated bool, callback CheckerC allback) { 111 func (checker *fakeChecker) AddItem(item *Item, isolated bool, callback CheckerC allback) {
100 callback(item, checker.ps) 112 callback(item, checker.ps)
101 } 113 }
102 114
103 func (checker *fakeChecker) Close() error { return nil } 115 func (checker *fakeChecker) Close() error { return nil }
104 116
105 // fakeChecker implements Uploader while recording method arguments. 117 // fakeChecker implements Uploader while recording method arguments.
106 type fakeUploader struct { 118 type fakeUploader struct {
107 // uploadBytesCalls is a record of the arguments to each call of UploadB ytes. 119 // uploadBytesCalls is a record of the arguments to each call of UploadB ytes.
108 uploadBytesCalls []uploaderUploadBytesArgs 120 uploadBytesCalls []uploaderUploadBytesArgs
121 // uploadFileCalls is a record of the arguments to each call of UploadFi le.
122 uploadFileCalls []uploaderUploadFileArgs
109 123
110 Uploader // TODO(mcgreevy): implement other methods. 124 Uploader // TODO(mcgreevy): implement other methods.
111 } 125 }
112 126
113 type uploaderUploadBytesArgs struct { 127 type uploaderUploadBytesArgs struct {
114 relPath string 128 relPath string
115 isolJSON []byte 129 isolJSON []byte
116 ps *isolatedclient.PushState 130 ps *isolatedclient.PushState
117 } 131 }
118 132
133 type uploaderUploadFileArgs struct {
134 item *Item
135 ps *isolatedclient.PushState
136 }
137
119 func (uploader *fakeUploader) UploadBytes(relPath string, isolJSON []byte, ps *i solatedclient.PushState, f func()) { 138 func (uploader *fakeUploader) UploadBytes(relPath string, isolJSON []byte, ps *i solatedclient.PushState, f func()) {
120 uploader.uploadBytesCalls = append(uploader.uploadBytesCalls, uploaderUp loadBytesArgs{relPath, isolJSON, ps}) 139 uploader.uploadBytesCalls = append(uploader.uploadBytesCalls, uploaderUp loadBytesArgs{relPath, isolJSON, ps})
121 } 140 }
122 141
142 func (uploader *fakeUploader) UploadFile(item *Item, ps *isolatedclient.PushStat e, done func()) {
143 uploader.uploadFileCalls = append(uploader.uploadFileCalls, uploaderUplo adFileArgs{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
144 }
123 func (uploader *fakeUploader) Close() error { return nil } 145 func (uploader *fakeUploader) Close() error { return nil }
124 146
125 func TestSkipsUpload(t *testing.T) { 147 func TestSkipsUpload(t *testing.T) {
126 t.Parallel() 148 t.Parallel()
127 Convey(`nil push state signals that upload should be skipped`, t, func() { 149 Convey(`nil push state signals that upload should be skipped`, t, func() {
128 isol := &isolated.Isolated{} 150 isol := &isolated.Isolated{}
129 151
130 // nil PushState means skip the upload. 152 // nil PushState means skip the upload.
131 checker := &fakeChecker{ps: nil} 153 checker := &fakeChecker{ps: nil}
132 154
133 uploader := &fakeUploader{} 155 uploader := &fakeUploader{}
134 156
135 ut := NewUploadTracker(checker, uploader, isol) 157 ut := NewUploadTracker(checker, uploader, isol)
136 fos := &fakeOS{} 158 fos := &fakeOS{}
137 ut.lOS = fos // Override filesystem calls with fake. 159 ut.lOS = fos // Override filesystem calls with fake.
138 160
139 parts := partitionedDeps{} // no actual deps. 161 parts := partitionedDeps{} // no actual deps.
140 err := ut.UploadDeps(parts) 162 err := ut.UploadDeps(parts)
141 So(err, ShouldBeNil) 163 So(err, ShouldBeNil)
142 164
143 // No deps, so Files should be empty. 165 // No deps, so Files should be empty.
144 wantFiles := map[string]isolated.File{} 166 wantFiles := map[string]isolated.File{}
145 So(ut.isol.Files, ShouldResemble, wantFiles) 167 So(ut.isol.Files, ShouldResemble, wantFiles)
146 168
147 isolSummary, err := ut.Finalize("/a/isolatedPath") 169 isolSummary, err := ut.Finalize("/a/isolatedPath")
148 So(err, ShouldBeNil) 170 So(err, ShouldBeNil)
149 171
150 // In this test, the only item that is checked and uploaded is t he generated isolated file. 172 // In this test, the only item that is checked and uploaded is t he generated isolated file.
151 wantIsolJSON := []byte(`{"algo":"","version":""}`) 173 wantIsolJSON := []byte(`{"algo":"","version":""}`)
152 » » So(fos.files, shouldResembleByteMap, map[string][]byte{"/a/isola tedPath": wantIsolJSON}) 174 » » So(fos.writeFiles, shouldResembleByteMap, map[string][]byte{"/a/ isolatedPath": wantIsolJSON})
153 175
154 So(isolSummary.Digest, ShouldResemble, isolated.HashBytes(wantIs olJSON)) 176 So(isolSummary.Digest, ShouldResemble, isolated.HashBytes(wantIs olJSON))
155 So(isolSummary.Name, ShouldEqual, "isolatedPath") 177 So(isolSummary.Name, ShouldEqual, "isolatedPath")
156 178
157 So(uploader.uploadBytesCalls, ShouldEqual, nil) 179 So(uploader.uploadBytesCalls, ShouldEqual, nil)
158 }) 180 })
159 } 181 }
160 182
161 func TestDontSkipUpload(t *testing.T) { 183 func TestDontSkipUpload(t *testing.T) {
162 t.Parallel() 184 t.Parallel()
(...skipping 15 matching lines...) Expand all
178 200
179 // No deps, so Files should be empty. 201 // No deps, so Files should be empty.
180 wantFiles := map[string]isolated.File{} 202 wantFiles := map[string]isolated.File{}
181 So(ut.isol.Files, ShouldResemble, wantFiles) 203 So(ut.isol.Files, ShouldResemble, wantFiles)
182 204
183 isolSummary, err := ut.Finalize("/a/isolatedPath") 205 isolSummary, err := ut.Finalize("/a/isolatedPath")
184 So(err, ShouldBeNil) 206 So(err, ShouldBeNil)
185 207
186 // In this test, the only item that is checked and uploaded is t he generated isolated file. 208 // In this test, the only item that is checked and uploaded is t he generated isolated file.
187 wantIsolJSON := []byte(`{"algo":"","version":""}`) 209 wantIsolJSON := []byte(`{"algo":"","version":""}`)
188 » » So(fos.files, shouldResembleByteMap, map[string][]byte{"/a/isola tedPath": wantIsolJSON}) 210 » » 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
189 211
190 So(isolSummary.Digest, ShouldResemble, isolated.HashBytes(wantIs olJSON)) 212 So(isolSummary.Digest, ShouldResemble, isolated.HashBytes(wantIs olJSON))
191 So(isolSummary.Name, ShouldEqual, "isolatedPath") 213 So(isolSummary.Name, ShouldEqual, "isolatedPath")
192 214
193 // Upload was not skipped. 215 // Upload was not skipped.
194 So(uploader.uploadBytesCalls, ShouldResemble, []uploaderUploadBy tesArgs{ 216 So(uploader.uploadBytesCalls, ShouldResemble, []uploaderUploadBy tesArgs{
195 {"isolatedPath", wantIsolJSON, checker.ps}, 217 {"isolatedPath", wantIsolJSON, checker.ps},
196 }) 218 })
197 }) 219 })
198 } 220 }
(...skipping 30 matching lines...) Expand all
229 So(ut.isol.Files, ShouldResemble, wantFiles) 251 So(ut.isol.Files, ShouldResemble, wantFiles)
230 252
231 // Symlinks are not uploaded. 253 // Symlinks are not uploaded.
232 So(uploader.uploadBytesCalls, ShouldEqual, nil) 254 So(uploader.uploadBytesCalls, ShouldEqual, nil)
233 255
234 isolSummary, err := ut.Finalize("/a/isolatedPath") 256 isolSummary, err := ut.Finalize("/a/isolatedPath")
235 So(err, ShouldBeNil) 257 So(err, ShouldBeNil)
236 258
237 // In this test, the only item that is checked and uploaded is t he generated isolated file. 259 // In this test, the only item that is checked and uploaded is t he generated isolated file.
238 wantIsolJSON := []byte(`{"algo":"","files":{"c":{"l":"link:/a/b/ c"}},"version":""}`) 260 wantIsolJSON := []byte(`{"algo":"","files":{"c":{"l":"link:/a/b/ c"}},"version":""}`)
239 » » So(fos.files, shouldResembleByteMap, map[string][]byte{"/a/isola tedPath": wantIsolJSON}) 261 » » So(fos.writeFiles, shouldResembleByteMap, map[string][]byte{"/a/ isolatedPath": wantIsolJSON})
240 262
241 So(isolSummary.Digest, ShouldResemble, isolated.HashBytes(wantIs olJSON)) 263 So(isolSummary.Digest, ShouldResemble, isolated.HashBytes(wantIs olJSON))
242 So(isolSummary.Name, ShouldEqual, "isolatedPath") 264 So(isolSummary.Name, ShouldEqual, "isolatedPath")
243 265
244 So(uploader.uploadBytesCalls, ShouldResemble, []uploaderUploadBy tesArgs{ 266 So(uploader.uploadBytesCalls, ShouldResemble, []uploaderUploadBy tesArgs{
245 {"isolatedPath", wantIsolJSON, checker.ps}, 267 {"isolatedPath", wantIsolJSON, checker.ps},
246 }) 268 })
247 }) 269 })
248 } 270 }
271
272 func TestHandlesIndividualFiles(t *testing.T) {
273 t.Parallel()
274 Convey(`Individual files should be stored in the isolated json and uploa ded`, t, func() {
275 isol := &isolated.Isolated{}
276
277 // 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
278 pushState := &isolatedclient.PushState{}
279 checker := &fakeChecker{ps: pushState}
280 uploader := &fakeUploader{}
281
282 ut := NewUploadTracker(checker, uploader, isol)
283 fos := &fakeOS{
284 readFiles: map[string]io.Reader{
285 "/a/b/foo": strings.NewReader("foo contents"),
286 "/a/b/bar": strings.NewReader("bar contents"),
287 },
288 }
289 ut.lOS = fos // Override filesystem calls with fake.
290
291 parts := partitionedDeps{
292 indivFiles: itemGroup{
293 items: []*Item{
294 {Path: "/a/b/foo", RelPath: "foo", Size: 1, Mode: 006},
295 {Path: "/a/b/bar", RelPath: "bar", Size: 2, Mode: 006},
296 },
297 totalSize: 3,
298 },
299 }
300 err := ut.UploadDeps(parts)
301 So(err, ShouldBeNil)
302
303 fooHash := isolated.HashBytes([]byte("foo contents"))
304 barHash := isolated.HashBytes([]byte("bar contents"))
305 wantFiles := map[string]isolated.File{
306 "foo": {
307 Digest: fooHash,
308 Mode: Int(6),
309 Size: Int64(1)},
310 "bar": {
311 Digest: barHash,
312 Mode: Int(6),
313 Size: Int64(2)},
314 }
315
316 So(ut.isol.Files, ShouldResemble, wantFiles)
317
318 So(uploader.uploadBytesCalls, ShouldEqual, nil)
319
320 isolSummary, err := ut.Finalize("/a/isolatedPath")
321 So(err, ShouldBeNil)
322
323 wantIsolJSONTmpl := `{"algo":"","files":{"bar":{"h":"%s","m":6," s":2},"foo":{"h":"%s","m":6,"s":1}},"version":""}`
324 wantIsolJSON := []byte(fmt.Sprintf(wantIsolJSONTmpl, barHash, fo oHash))
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
325 So(fos.writeFiles, shouldResembleByteMap, map[string][]byte{"/a/ isolatedPath": wantIsolJSON})
326
327 So(isolSummary.Digest, ShouldResemble, isolated.HashBytes(wantIs olJSON))
328 So(isolSummary.Name, ShouldEqual, "isolatedPath")
329
330 So(uploader.uploadBytesCalls, ShouldResemble, []uploaderUploadBy tesArgs{
331 {"isolatedPath", wantIsolJSON, checker.ps},
332 })
333 So(uploader.uploadFileCalls, ShouldResemble, []uploaderUploadFil eArgs{
334 {
335 item: &Item{
336 Path: "/a/b/foo",
337 RelPath: "foo",
338 Size: 1,
339 Mode: os.FileMode(6),
340 Digest: fooHash,
341 },
342 ps: pushState,
343 },
344 {
345 item: &Item{
346 Path: "/a/b/bar",
347 RelPath: "bar",
348 Size: 2,
349 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.
350 Digest: barHash,
351 },
352 ps: pushState,
353 },
354 })
355 })
356 }
357
358 func Int(i int) *int { return &i }
359 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
OLDNEW
« no previous file with comments | « client/cmd/isolate/upload_tracker.go ('k') | no next file » | no next file with comments »

Powered by Google App Engine
This is Rietveld 408576698