github.com/munnerz/test-infra@v0.0.0-20190108210205-ce3d181dc989/prow/spyglass/gcsartifact_test.go (about) 1 /* 2 Copyright 2018 The Kubernetes Authors. 3 4 Licensed under the Apache License, Version 2.0 (the "License"); 5 you may not use this file except in compliance with the License. 6 You may obtain a copy of the License at 7 8 http://www.apache.org/licenses/LICENSE-2.0 9 10 Unless required by applicable law or agreed to in writing, software 11 distributed under the License is distributed on an "AS IS" BASIS, 12 WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 See the License for the specific language governing permissions and 14 limitations under the License. 15 */ 16 17 package spyglass 18 19 import ( 20 "bytes" 21 "compress/gzip" 22 "context" 23 "fmt" 24 "io" 25 "testing" 26 27 "cloud.google.com/go/storage" 28 ) 29 30 type ByteReadCloser struct { 31 io.Reader 32 } 33 34 func (rc *ByteReadCloser) Close() error { 35 return nil 36 } 37 38 func (rc *ByteReadCloser) Read(p []byte) (int, error) { 39 read, err := rc.Reader.Read(p) 40 if err != nil { 41 return 0, err 42 } 43 if bytes.Equal(p[:read], []byte("deeper unreadable contents")) { 44 return 0, fmt.Errorf("it's just turtes all the way down") 45 } 46 return read, nil 47 } 48 49 type fakeArtifactHandle struct { 50 oAttrs *storage.ObjectAttrs 51 contents []byte 52 } 53 54 func (h *fakeArtifactHandle) Attrs(ctx context.Context) (*storage.ObjectAttrs, error) { 55 if bytes.Equal(h.contents, []byte("no attrs")) { 56 return nil, fmt.Errorf("error getting attrs") 57 } 58 return h.oAttrs, nil 59 } 60 61 func (h *fakeArtifactHandle) NewRangeReader(ctx context.Context, offset, length int64) (io.ReadCloser, error) { 62 if bytes.Equal(h.contents, []byte("unreadable contents")) { 63 return nil, fmt.Errorf("cannot read unreadable contents") 64 } 65 lenContents := int64(len(h.contents)) 66 var err error 67 var toRead int64 68 if length < 0 { 69 toRead = lenContents - offset 70 err = io.EOF 71 } else { 72 toRead = length 73 if offset+length > lenContents { 74 toRead = lenContents - offset 75 err = io.EOF 76 } 77 } 78 return &ByteReadCloser{bytes.NewReader(h.contents[offset : offset+toRead])}, err 79 } 80 81 func (h *fakeArtifactHandle) NewReader(ctx context.Context) (io.ReadCloser, error) { 82 var buf bytes.Buffer 83 zw := gzip.NewWriter(&buf) 84 _, err := zw.Write([]byte("unreadable contents")) 85 if err != nil { 86 return nil, fmt.Errorf("Failed to gzip log text, err: %v", err) 87 } 88 if err := zw.Close(); err != nil { 89 return nil, fmt.Errorf("Failed to close gzip writer, err: %v", err) 90 } 91 if bytes.Equal(h.contents, buf.Bytes()) { 92 return nil, fmt.Errorf("cannot read unreadable contents, even if they're gzipped") 93 } 94 if bytes.Equal(h.contents, []byte("unreadable contents")) { 95 return nil, fmt.Errorf("cannot read unreadable contents") 96 } 97 return &ByteReadCloser{bytes.NewReader(h.contents)}, nil 98 } 99 100 // Tests reading the tail n bytes of data from an artifact 101 func TestReadTail(t *testing.T) { 102 var buf bytes.Buffer 103 zw := gzip.NewWriter(&buf) 104 _, err := zw.Write([]byte("Oh wow\nlogs\nthis is\ncrazy")) 105 if err != nil { 106 t.Fatalf("Failed to gzip log text, err: %v", err) 107 } 108 if err := zw.Close(); err != nil { 109 t.Fatalf("Failed to close gzip writer, err: %v", err) 110 } 111 gzippedLog := buf.Bytes() 112 testCases := []struct { 113 name string 114 n int64 115 contents []byte 116 encoding string 117 expected []byte 118 expectErr bool 119 }{ 120 { 121 name: "ReadTail example build log", 122 n: 4, 123 contents: []byte("Oh wow\nlogs\nthis is\ncrazy"), 124 expected: []byte("razy"), 125 expectErr: false, 126 }, 127 { 128 name: "ReadTail build log, gzipped", 129 n: 23, 130 contents: gzippedLog, 131 encoding: "gzip", 132 expectErr: true, 133 }, 134 { 135 name: "ReadTail build log, claimed gzipped but not actually gzipped", 136 n: 2333, 137 contents: []byte("Oh wow\nlogs\nthis is\ncrazy"), 138 encoding: "gzip", 139 expectErr: true, 140 }, 141 { 142 name: "ReadTail N>size of build log", 143 n: 2222, 144 contents: []byte("Oh wow\nlogs\nthis is\ncrazy"), 145 expected: []byte("Oh wow\nlogs\nthis is\ncrazy"), 146 expectErr: false, 147 }, 148 } 149 for _, tc := range testCases { 150 artifact := NewGCSArtifact(context.Background(), &fakeArtifactHandle{ 151 contents: tc.contents, 152 oAttrs: &storage.ObjectAttrs{ 153 Bucket: "foo-bucket", 154 Name: "build-log.txt", 155 Size: int64(len(tc.contents)), 156 ContentEncoding: tc.encoding, 157 }, 158 }, "", "build-log.txt", 500e6) 159 actualBytes, err := artifact.ReadTail(tc.n) 160 if err != nil && !tc.expectErr { 161 t.Fatalf("Test %s failed with err: %v", tc.name, err) 162 } 163 if err == nil && tc.expectErr { 164 t.Errorf("Test %s did not produce error when expected", tc.name) 165 } 166 if !bytes.Equal(actualBytes, tc.expected) { 167 t.Errorf("Test %s failed.\nExpected: %s\nActual: %s", tc.name, tc.expected, actualBytes) 168 } 169 } 170 } 171 172 // Tests reading at most n bytes of data from files in GCS 173 func TestReadAtMost(t *testing.T) { 174 var buf bytes.Buffer 175 zw := gzip.NewWriter(&buf) 176 _, err := zw.Write([]byte("Oh wow\nlogs\nthis is\ncrazy")) 177 if err != nil { 178 t.Fatalf("Failed to gzip log text, err: %v", err) 179 } 180 if err := zw.Close(); err != nil { 181 t.Fatalf("Failed to close gzip writer, err: %v", err) 182 } 183 testCases := []struct { 184 name string 185 n int64 186 contents []byte 187 encoding string 188 expected []byte 189 expectErr bool 190 expectEOF bool 191 }{ 192 { 193 name: "ReadAtMost example build log", 194 n: 4, 195 contents: []byte("Oh wow\nlogs\nthis is\ncrazy"), 196 expected: []byte("Oh w"), 197 expectErr: false, 198 }, 199 { 200 name: "ReadAtMost build log, transparently gzipped", 201 n: 8, 202 contents: []byte("Oh wow\nlogs\nthis is\ncrazy"), 203 expected: []byte("Oh wow\nl"), 204 encoding: "gzip", 205 expectErr: false, 206 }, 207 { 208 name: "ReadAtMost unreadable contents", 209 n: 2, 210 contents: []byte("unreadable contents"), 211 expectErr: true, 212 }, 213 { 214 name: "ReadAtMost unreadable contents", 215 n: 45, 216 contents: []byte("deeper unreadable contents"), 217 expectErr: true, 218 }, 219 { 220 name: "ReadAtMost N>size of build log", 221 n: 2222, 222 contents: []byte("Oh wow\nlogs\nthis is\ncrazy"), 223 expected: []byte("Oh wow\nlogs\nthis is\ncrazy"), 224 expectErr: true, 225 expectEOF: true, 226 }, 227 } 228 for _, tc := range testCases { 229 artifact := NewGCSArtifact(context.Background(), &fakeArtifactHandle{ 230 contents: tc.contents, 231 oAttrs: &storage.ObjectAttrs{ 232 Bucket: "foo-bucket", 233 Name: "build-log.txt", 234 Size: int64(len(tc.contents)), 235 ContentEncoding: tc.encoding, 236 }, 237 }, "", "build-log.txt", 500e6) 238 actualBytes, err := artifact.ReadAtMost(tc.n) 239 if err != nil && !tc.expectErr { 240 if tc.expectEOF && err != io.EOF { 241 t.Fatalf("Test %s failed with err: %v, expected EOF", tc.name, err) 242 } 243 t.Fatalf("Test %s failed with err: %v", tc.name, err) 244 } 245 if err != nil && tc.expectEOF && err != io.EOF { 246 t.Fatalf("Test %s failed with err: %v, expected EOF", tc.name, err) 247 } 248 if err == nil && tc.expectErr { 249 t.Errorf("Test %s did not produce error when expected", tc.name) 250 } 251 if !bytes.Equal(actualBytes, tc.expected) { 252 t.Errorf("Test %s failed.\nExpected: %s\nActual: %s", tc.name, tc.expected, actualBytes) 253 } 254 } 255 } 256 257 // Tests reading at offset from files in GCS 258 func TestReadAt(t *testing.T) { 259 var buf bytes.Buffer 260 zw := gzip.NewWriter(&buf) 261 _, err := zw.Write([]byte("Oh wow\nlogs\nthis is\ncrazy")) 262 if err != nil { 263 t.Fatalf("Failed to gzip log text, err: %v", err) 264 } 265 if err := zw.Close(); err != nil { 266 t.Fatalf("Failed to close gzip writer, err: %v", err) 267 } 268 gzippedLog := buf.Bytes() 269 testCases := []struct { 270 name string 271 n int64 272 offset int64 273 contents []byte 274 encoding string 275 expected []byte 276 expectErr bool 277 }{ 278 { 279 name: "ReadAt example build log", 280 n: 4, 281 offset: 6, 282 contents: []byte("Oh wow\nlogs\nthis is\ncrazy"), 283 expected: []byte("\nlog"), 284 expectErr: false, 285 }, 286 { 287 name: "ReadAt offset past file size", 288 n: 4, 289 offset: 400, 290 contents: []byte("Oh wow\nlogs\nthis is\ncrazy"), 291 expectErr: true, 292 }, 293 { 294 name: "ReadAt build log, gzipped", 295 n: 23, 296 contents: gzippedLog, 297 encoding: "gzip", 298 expectErr: true, 299 }, 300 { 301 name: "ReadAt, claimed gzipped but not actually gzipped", 302 n: 2333, 303 contents: []byte("Oh wow\nlogs\nthis is\ncrazy"), 304 encoding: "gzip", 305 expectErr: true, 306 }, 307 { 308 name: "ReadAt offset negative", 309 offset: -3, 310 n: 32, 311 contents: []byte("Oh wow\nlogs\nthis is\ncrazy"), 312 expectErr: true, 313 }, 314 } 315 for _, tc := range testCases { 316 artifact := NewGCSArtifact(context.Background(), &fakeArtifactHandle{ 317 contents: tc.contents, 318 oAttrs: &storage.ObjectAttrs{ 319 Bucket: "foo-bucket", 320 Name: "build-log.txt", 321 Size: int64(len(tc.contents)), 322 ContentEncoding: tc.encoding, 323 }, 324 }, "", "build-log.txt", 500e6) 325 p := make([]byte, tc.n) 326 bytesRead, err := artifact.ReadAt(p, tc.offset) 327 if err != nil && !tc.expectErr { 328 t.Fatalf("Test %s failed with err: %v", tc.name, err) 329 } 330 if err == nil && tc.expectErr { 331 t.Errorf("Test %s did not produce error when expected", tc.name) 332 } 333 readBytes := p[:bytesRead] 334 if !bytes.Equal(readBytes, tc.expected) { 335 t.Errorf("Test %s failed.\nExpected: %s\nActual: %s", tc.name, tc.expected, readBytes) 336 } 337 } 338 339 } 340 341 // Tests reading all data from files in GCS 342 func TestReadAll(t *testing.T) { 343 testCases := []struct { 344 name string 345 sizeLimit int64 346 contents []byte 347 expectErr bool 348 expected []byte 349 }{ 350 { 351 name: "ReadAll example build log", 352 contents: []byte("Oh wow\nlogs\nthis is\ncrazy"), 353 sizeLimit: 500e6, 354 expected: []byte("Oh wow\nlogs\nthis is\ncrazy"), 355 }, 356 { 357 name: "ReadAll example too large build log", 358 sizeLimit: 20, 359 contents: []byte("Oh wow\nlogs\nthis is\ncrazy"), 360 expectErr: true, 361 expected: nil, 362 }, 363 { 364 name: "ReadAll unable to get reader", 365 sizeLimit: 500e6, 366 contents: []byte("unreadable contents"), 367 expectErr: true, 368 expected: nil, 369 }, 370 { 371 name: "ReadAll unable to read contents", 372 sizeLimit: 500e6, 373 contents: []byte("deeper unreadable contents"), 374 expectErr: true, 375 expected: nil, 376 }, 377 } 378 for _, tc := range testCases { 379 artifact := NewGCSArtifact(context.Background(), &fakeArtifactHandle{ 380 contents: tc.contents, 381 oAttrs: &storage.ObjectAttrs{ 382 Bucket: "foo-bucket", 383 Name: "build-log.txt", 384 Size: int64(len(tc.contents)), 385 }, 386 }, "", "build-log.txt", tc.sizeLimit) 387 388 actualBytes, err := artifact.ReadAll() 389 if err != nil && !tc.expectErr { 390 t.Fatalf("Test %s failed with err: %v", tc.name, err) 391 } 392 if err == nil && tc.expectErr { 393 t.Errorf("Test %s did not produce error when expected", tc.name) 394 } 395 if !bytes.Equal(actualBytes, tc.expected) { 396 t.Errorf("Test %s failed.\nExpected: %s\nActual: %s", tc.name, tc.expected, actualBytes) 397 } 398 } 399 } 400 401 func TestSize_GCS(t *testing.T) { 402 fakeGCSClient := fakeGCSServer.Client() 403 fakeGCSBucket := fakeGCSClient.Bucket("test-bucket") 404 startedContent := []byte("hi jason, im started") 405 testCases := []struct { 406 name string 407 handle artifactHandle 408 expected int64 409 expectErr bool 410 }{ 411 { 412 name: "Test size simple", 413 handle: &fakeArtifactHandle{ 414 contents: startedContent, 415 oAttrs: &storage.ObjectAttrs{ 416 Bucket: "foo-bucket", 417 Name: "started.json", 418 Size: int64(len(startedContent)), 419 }, 420 }, 421 expected: int64(len(startedContent)), 422 expectErr: false, 423 }, 424 { 425 name: "Test size from attrs error", 426 handle: &fakeArtifactHandle{ 427 contents: []byte("no attrs"), 428 oAttrs: &storage.ObjectAttrs{ 429 Bucket: "foo-bucket", 430 Name: "started.json", 431 Size: 8, 432 }, 433 }, 434 expectErr: true, 435 }, 436 { 437 name: "Size of nonexistentArtifact", 438 handle: &gcsArtifactHandle{fakeGCSBucket.Object("logs/example-ci-run/404/started.json")}, 439 expectErr: true, 440 }, 441 } 442 for _, tc := range testCases { 443 artifact := NewGCSArtifact(context.Background(), tc.handle, "", "started.json", 500e6) 444 actual, err := artifact.Size() 445 if err != nil && !tc.expectErr { 446 t.Fatalf("%s failed getting size for artifact %s, err: %v", tc.name, artifact.JobPath(), err) 447 } 448 if err == nil && tc.expectErr { 449 t.Errorf("%s did not produce error when error was expected.", tc.name) 450 } 451 if tc.expected != actual { 452 t.Errorf("Test %s failed.\nExpected:\n%d\nActual:\n%d", tc.name, tc.expected, actual) 453 } 454 } 455 }