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

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

Issue 2992113002: isolate: test uploading of individual regular files (Closed)
Patch Set: (revert accidental patchset 3) 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
119 func (uploader *fakeUploader) UploadBytes(relPath string, isolJSON []byte, ps *i solatedclient.PushState, f func()) { 133 type uploaderUploadFileArgs struct {
120 » uploader.uploadBytesCalls = append(uploader.uploadBytesCalls, uploaderUp loadBytesArgs{relPath, isolJSON, ps}) 134 » item *Item
135 » ps *isolatedclient.PushState
121 } 136 }
122 137
138 func (uploader *fakeUploader) UploadBytes(relPath string, isolJSON []byte, ps *i solatedclient.PushState, done func()) {
139 uploader.uploadBytesCalls = append(uploader.uploadBytesCalls, uploaderUp loadBytesArgs{relPath, isolJSON, ps})
140 done()
141 }
142
143 func (uploader *fakeUploader) UploadFile(item *Item, ps *isolatedclient.PushStat e, done func()) {
144 uploader.uploadFileCalls = append(uploader.uploadFileCalls, uploaderUplo adFileArgs{item, ps})
145 done()
146 }
123 func (uploader *fakeUploader) Close() error { return nil } 147 func (uploader *fakeUploader) Close() error { return nil }
124 148
125 func TestSkipsUpload(t *testing.T) { 149 func TestSkipsUpload(t *testing.T) {
126 t.Parallel() 150 t.Parallel()
127 Convey(`nil push state signals that upload should be skipped`, t, func() { 151 Convey(`nil push state signals that upload should be skipped`, t, func() {
128 isol := &isolated.Isolated{} 152 isol := &isolated.Isolated{}
129 153
130 // nil PushState means skip the upload. 154 // nil PushState means skip the upload.
131 checker := &fakeChecker{ps: nil} 155 checker := &fakeChecker{ps: nil}
132 156
133 uploader := &fakeUploader{} 157 uploader := &fakeUploader{}
134 158
135 ut := NewUploadTracker(checker, uploader, isol) 159 ut := NewUploadTracker(checker, uploader, isol)
136 fos := &fakeOS{} 160 fos := &fakeOS{}
137 ut.lOS = fos // Override filesystem calls with fake. 161 ut.lOS = fos // Override filesystem calls with fake.
138 162
139 parts := partitionedDeps{} // no actual deps. 163 parts := partitionedDeps{} // no actual deps.
140 err := ut.UploadDeps(parts) 164 err := ut.UploadDeps(parts)
141 So(err, ShouldBeNil) 165 So(err, ShouldBeNil)
142 166
143 // No deps, so Files should be empty. 167 // No deps, so Files should be empty.
144 wantFiles := map[string]isolated.File{} 168 wantFiles := map[string]isolated.File{}
145 So(ut.isol.Files, ShouldResemble, wantFiles) 169 So(ut.isol.Files, ShouldResemble, wantFiles)
146 170
147 isolSummary, err := ut.Finalize("/a/isolatedPath") 171 isolSummary, err := ut.Finalize("/a/isolatedPath")
148 So(err, ShouldBeNil) 172 So(err, ShouldBeNil)
149 173
150 // In this test, the only item that is checked and uploaded is t he generated isolated file. 174 // In this test, the only item that is checked and uploaded is t he generated isolated file.
151 wantIsolJSON := []byte(`{"algo":"","version":""}`) 175 wantIsolJSON := []byte(`{"algo":"","version":""}`)
152 » » So(fos.files, shouldResembleByteMap, map[string][]byte{"/a/isola tedPath": wantIsolJSON}) 176 » » So(fos.writeFiles, shouldResembleByteMap, map[string][]byte{"/a/ isolatedPath": wantIsolJSON})
153 177
154 So(isolSummary.Digest, ShouldResemble, isolated.HashBytes(wantIs olJSON)) 178 So(isolSummary.Digest, ShouldResemble, isolated.HashBytes(wantIs olJSON))
155 So(isolSummary.Name, ShouldEqual, "isolatedPath") 179 So(isolSummary.Name, ShouldEqual, "isolatedPath")
156 180
157 So(uploader.uploadBytesCalls, ShouldEqual, nil) 181 So(uploader.uploadBytesCalls, ShouldEqual, nil)
158 }) 182 })
159 } 183 }
160 184
161 func TestDontSkipUpload(t *testing.T) { 185 func TestDontSkipUpload(t *testing.T) {
162 t.Parallel() 186 t.Parallel()
(...skipping 15 matching lines...) Expand all
178 202
179 // No deps, so Files should be empty. 203 // No deps, so Files should be empty.
180 wantFiles := map[string]isolated.File{} 204 wantFiles := map[string]isolated.File{}
181 So(ut.isol.Files, ShouldResemble, wantFiles) 205 So(ut.isol.Files, ShouldResemble, wantFiles)
182 206
183 isolSummary, err := ut.Finalize("/a/isolatedPath") 207 isolSummary, err := ut.Finalize("/a/isolatedPath")
184 So(err, ShouldBeNil) 208 So(err, ShouldBeNil)
185 209
186 // In this test, the only item that is checked and uploaded is t he generated isolated file. 210 // In this test, the only item that is checked and uploaded is t he generated isolated file.
187 wantIsolJSON := []byte(`{"algo":"","version":""}`) 211 wantIsolJSON := []byte(`{"algo":"","version":""}`)
188 » » So(fos.files, shouldResembleByteMap, map[string][]byte{"/a/isola tedPath": wantIsolJSON}) 212 » » So(fos.writeFiles, shouldResembleByteMap, map[string][]byte{"/a/ isolatedPath": wantIsolJSON})
189 213
190 So(isolSummary.Digest, ShouldResemble, isolated.HashBytes(wantIs olJSON)) 214 So(isolSummary.Digest, ShouldResemble, isolated.HashBytes(wantIs olJSON))
191 So(isolSummary.Name, ShouldEqual, "isolatedPath") 215 So(isolSummary.Name, ShouldEqual, "isolatedPath")
192 216
193 // Upload was not skipped. 217 // Upload was not skipped.
194 So(uploader.uploadBytesCalls, ShouldResemble, []uploaderUploadBy tesArgs{ 218 So(uploader.uploadBytesCalls, ShouldResemble, []uploaderUploadBy tesArgs{
195 {"isolatedPath", wantIsolJSON, checker.ps}, 219 {"isolatedPath", wantIsolJSON, checker.ps},
196 }) 220 })
197 }) 221 })
198 } 222 }
(...skipping 30 matching lines...) Expand all
229 So(ut.isol.Files, ShouldResemble, wantFiles) 253 So(ut.isol.Files, ShouldResemble, wantFiles)
230 254
231 // Symlinks are not uploaded. 255 // Symlinks are not uploaded.
232 So(uploader.uploadBytesCalls, ShouldEqual, nil) 256 So(uploader.uploadBytesCalls, ShouldEqual, nil)
233 257
234 isolSummary, err := ut.Finalize("/a/isolatedPath") 258 isolSummary, err := ut.Finalize("/a/isolatedPath")
235 So(err, ShouldBeNil) 259 So(err, ShouldBeNil)
236 260
237 // In this test, the only item that is checked and uploaded is t he generated isolated file. 261 // 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":""}`) 262 wantIsolJSON := []byte(`{"algo":"","files":{"c":{"l":"link:/a/b/ c"}},"version":""}`)
239 » » So(fos.files, shouldResembleByteMap, map[string][]byte{"/a/isola tedPath": wantIsolJSON}) 263 » » So(fos.writeFiles, shouldResembleByteMap, map[string][]byte{"/a/ isolatedPath": wantIsolJSON})
240 264
241 So(isolSummary.Digest, ShouldResemble, isolated.HashBytes(wantIs olJSON)) 265 So(isolSummary.Digest, ShouldResemble, isolated.HashBytes(wantIs olJSON))
242 So(isolSummary.Name, ShouldEqual, "isolatedPath") 266 So(isolSummary.Name, ShouldEqual, "isolatedPath")
267
268 So(uploader.uploadBytesCalls, ShouldResemble, []uploaderUploadBy tesArgs{
269 {"isolatedPath", wantIsolJSON, checker.ps},
270 })
271 })
272 }
273
274 func TestHandlesIndividualFiles(t *testing.T) {
275 t.Parallel()
276 Convey(`Individual files should be stored in the isolated json and uploa ded`, t, func() {
277 isol := &isolated.Isolated{}
278
279 // non-nil PushState means don't skip the upload.
280 pushState := &isolatedclient.PushState{}
281 checker := &fakeChecker{ps: pushState}
282 uploader := &fakeUploader{}
283
284 ut := NewUploadTracker(checker, uploader, isol)
285 fos := &fakeOS{
286 readFiles: map[string]io.Reader{
287 "/a/b/foo": strings.NewReader("foo contents"),
288 "/a/b/bar": strings.NewReader("bar contents"),
289 },
290 }
291 ut.lOS = fos // Override filesystem calls with fake.
292
293 parts := partitionedDeps{
294 indivFiles: itemGroup{
295 items: []*Item{
296 {Path: "/a/b/foo", RelPath: "foo", Size: 1, Mode: 004},
297 {Path: "/a/b/bar", RelPath: "bar", Size: 2, Mode: 006},
298 },
299 totalSize: 3,
300 },
301 }
302 err := ut.UploadDeps(parts)
303 So(err, ShouldBeNil)
304
305 fooHash := isolated.HashBytes([]byte("foo contents"))
306 barHash := isolated.HashBytes([]byte("bar contents"))
307 wantFiles := map[string]isolated.File{
308 "foo": {
309 Digest: fooHash,
310 Mode: Int(4),
311 Size: Int64(1)},
312 "bar": {
313 Digest: barHash,
314 Mode: Int(6),
315 Size: Int64(2)},
316 }
317
318 So(ut.isol.Files, ShouldResemble, wantFiles)
319
320 So(uploader.uploadBytesCalls, ShouldEqual, nil)
321
322 isolSummary, err := ut.Finalize("/a/isolatedPath")
323 So(err, ShouldBeNil)
324
325 wantIsolJSONTmpl := `{"algo":"","files":{"bar":{"h":"%s","m":6," s":2},"foo":{"h":"%s","m":4,"s":1}},"version":""}`
326 wantIsolJSON := []byte(fmt.Sprintf(wantIsolJSONTmpl, barHash, fo oHash))
327 So(fos.writeFiles, shouldResembleByteMap, map[string][]byte{"/a/ isolatedPath": wantIsolJSON})
328
329 So(isolSummary.Digest, ShouldResemble, isolated.HashBytes(wantIs olJSON))
330 So(isolSummary.Name, ShouldEqual, "isolatedPath")
243 331
244 So(uploader.uploadBytesCalls, ShouldResemble, []uploaderUploadBy tesArgs{ 332 So(uploader.uploadBytesCalls, ShouldResemble, []uploaderUploadBy tesArgs{
245 {"isolatedPath", wantIsolJSON, checker.ps}, 333 {"isolatedPath", wantIsolJSON, checker.ps},
246 }) 334 })
335 So(uploader.uploadFileCalls, ShouldResemble, []uploaderUploadFil eArgs{
336 {
337 item: &Item{
338 Path: "/a/b/foo",
339 RelPath: "foo",
340 Size: 1,
341 Mode: os.FileMode(4),
342 Digest: fooHash,
343 },
344 ps: pushState,
345 },
346 {
347 item: &Item{
348 Path: "/a/b/bar",
349 RelPath: "bar",
350 Size: 2,
351 Mode: os.FileMode(6),
352 Digest: barHash,
353 },
354 ps: pushState,
355 },
356 })
247 }) 357 })
248 } 358 }
359
360 // Int is a helper routine that allocates a new int value to store v and returns a pointer to it.
361 func Int(i int) *int { return &i }
362
363 // Int64 is a helper routine that allocates a new int64 value to store v and ret urns a pointer to it.
364 func Int64(i int64) *int64 { return &i }
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