github.com/GoogleCloudPlatform/testgrid@v0.0.174/util/gcs/fake/sort_test.go (about) 1 /* 2 Copyright 2021 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 fake // Needs to be in fake package to access the fakes 18 19 import ( 20 "context" 21 "errors" 22 "fmt" 23 "net/http" 24 "testing" 25 "time" 26 27 "cloud.google.com/go/storage" 28 "github.com/GoogleCloudPlatform/testgrid/util/gcs" 29 "github.com/google/go-cmp/cmp" 30 "github.com/sirupsen/logrus" 31 "google.golang.org/api/googleapi" 32 ) 33 34 func mustPath(s string) *gcs.Path { 35 p, err := gcs.NewPath(s) 36 if err != nil { 37 panic(err) 38 } 39 return p 40 } 41 42 func TestStatExisting(t *testing.T) { 43 cases := []struct { 44 name string 45 stats Stater 46 paths []gcs.Path 47 48 want []*storage.ObjectAttrs 49 }{ 50 { 51 name: "basic", 52 want: []*storage.ObjectAttrs{}, 53 }, 54 { 55 name: "err", 56 stats: Stater{ 57 *mustPath("gs://bucket/path/to/boom"): Stat{ 58 Err: errors.New("boom"), 59 }, 60 }, 61 paths: []gcs.Path{ 62 *mustPath("gs://bucket/path/to/boom"), 63 }, 64 want: []*storage.ObjectAttrs{nil}, 65 }, 66 { 67 name: "not found", 68 stats: Stater{ 69 *mustPath("gs://bucket/path/to/boom"): Stat{ 70 Err: storage.ErrObjectNotExist, 71 }, 72 *mustPath("gs://bucket/path/to/wrapped"): Stat{ 73 Err: fmt.Errorf("wrap: %w", storage.ErrObjectNotExist), 74 }, 75 }, 76 paths: []gcs.Path{ 77 *mustPath("gs://bucket/path/to/boom"), 78 *mustPath("gs://bucket/path/to/wrapped"), 79 }, 80 want: []*storage.ObjectAttrs{{}, {}}, 81 }, 82 { 83 name: "found", 84 stats: Stater{ 85 *mustPath("gs://bucket/path/to/boom"): Stat{ 86 Attrs: storage.ObjectAttrs{ 87 Name: "yo", 88 }, 89 }, 90 }, 91 paths: []gcs.Path{ 92 *mustPath("gs://bucket/path/to/boom"), 93 }, 94 want: []*storage.ObjectAttrs{ 95 { 96 Name: "yo", 97 }, 98 }, 99 }, 100 } 101 for _, tc := range cases { 102 t.Run(tc.name, func(t *testing.T) { 103 client := &ConditionalClient{ 104 UploadClient: UploadClient{ 105 Stater: tc.stats, 106 }, 107 } 108 got := gcs.StatExisting(context.Background(), logrus.WithField("name", tc.name), client, tc.paths...) 109 if diff := cmp.Diff(tc.want, got); diff != "" { 110 t.Errorf("gridAttrs() got unexpected diff (-want +got):\n%s", diff) 111 } 112 }) 113 } 114 } 115 116 func TestLeastRecentlyUpdated(t *testing.T) { 117 now := time.Now() 118 cases := []struct { 119 name string 120 ctx context.Context 121 client Stater 122 paths []gcs.Path 123 wantPaths []gcs.Path 124 wantGens map[gcs.Path]int64 125 }{ 126 { 127 name: "already sorted", 128 client: Stater{ 129 *mustPath("gs://bucket/first"): { 130 Attrs: storage.ObjectAttrs{ 131 Generation: 101, 132 }, 133 }, 134 *mustPath("gs://bucket/second"): { 135 Attrs: storage.ObjectAttrs{ 136 Generation: 102, 137 Updated: now.Add(time.Minute), 138 }, 139 }, 140 *mustPath("gs://bucket/third"): { 141 Attrs: storage.ObjectAttrs{ 142 Generation: 103, 143 Updated: now.Add(2 * time.Minute), 144 }, 145 }, 146 }, 147 paths: []gcs.Path{ 148 *mustPath("gs://bucket/first"), 149 *mustPath("gs://bucket/second"), 150 *mustPath("gs://bucket/third"), 151 }, 152 wantPaths: []gcs.Path{ 153 *mustPath("gs://bucket/first"), 154 *mustPath("gs://bucket/second"), 155 *mustPath("gs://bucket/third"), 156 }, 157 wantGens: map[gcs.Path]int64{ 158 *mustPath("gs://bucket/first"): 101, 159 *mustPath("gs://bucket/second"): 102, 160 *mustPath("gs://bucket/third"): 103, 161 }, 162 }, 163 { 164 name: "unsorted", 165 client: Stater{ 166 *mustPath("gs://bucket/first"): { 167 Attrs: storage.ObjectAttrs{ 168 Generation: 101, 169 }, 170 }, 171 *mustPath("gs://bucket/second"): { 172 Attrs: storage.ObjectAttrs{ 173 Generation: 102, 174 Updated: now.Add(time.Minute), 175 }, 176 }, 177 *mustPath("gs://bucket/third"): { 178 Attrs: storage.ObjectAttrs{ 179 Generation: 103, 180 Updated: now.Add(2 * time.Minute), 181 }, 182 }, 183 }, 184 paths: []gcs.Path{ 185 *mustPath("gs://bucket/third"), 186 *mustPath("gs://bucket/first"), 187 *mustPath("gs://bucket/second"), 188 }, 189 wantPaths: []gcs.Path{ 190 *mustPath("gs://bucket/first"), 191 *mustPath("gs://bucket/second"), 192 *mustPath("gs://bucket/third"), 193 }, 194 wantGens: map[gcs.Path]int64{ 195 *mustPath("gs://bucket/first"): 101, 196 *mustPath("gs://bucket/second"): 102, 197 *mustPath("gs://bucket/third"): 103, 198 }, 199 }, 200 { 201 name: "missing", 202 client: Stater{ 203 *mustPath("gs://bucket/first"): { 204 Attrs: storage.ObjectAttrs{ 205 Generation: 101, 206 }, 207 }, 208 *mustPath("gs://bucket/third"): { 209 Attrs: storage.ObjectAttrs{ 210 Generation: 103, 211 Updated: now.Add(2 * time.Minute), 212 }, 213 }, 214 }, 215 paths: []gcs.Path{ 216 *mustPath("gs://bucket/third"), 217 *mustPath("gs://bucket/first"), 218 *mustPath("gs://bucket/missing"), 219 }, 220 wantPaths: []gcs.Path{ 221 *mustPath("gs://bucket/missing"), 222 *mustPath("gs://bucket/first"), 223 *mustPath("gs://bucket/third"), 224 }, 225 wantGens: map[gcs.Path]int64{ 226 *mustPath("gs://bucket/first"): 101, 227 *mustPath("gs://bucket/third"): 103, 228 *mustPath("gs://bucket/missing"): 0, 229 }, 230 }, 231 { 232 name: "error", 233 client: Stater{ 234 *mustPath("gs://bucket/first"): { 235 Attrs: storage.ObjectAttrs{ 236 Generation: 101, 237 }, 238 }, 239 *mustPath("gs://bucket/third"): { 240 Attrs: storage.ObjectAttrs{ 241 Generation: 103, 242 Updated: now.Add(2 * time.Minute), 243 }, 244 }, 245 *mustPath("gs://bucket/error"): { 246 Err: errors.New("injected error"), 247 }, 248 }, 249 paths: []gcs.Path{ 250 *mustPath("gs://bucket/third"), 251 *mustPath("gs://bucket/first"), 252 *mustPath("gs://bucket/error"), 253 }, 254 wantPaths: []gcs.Path{ 255 *mustPath("gs://bucket/error"), 256 *mustPath("gs://bucket/first"), 257 *mustPath("gs://bucket/third"), 258 }, 259 wantGens: map[gcs.Path]int64{ 260 *mustPath("gs://bucket/first"): 101, 261 *mustPath("gs://bucket/third"): 103, 262 *mustPath("gs://bucket/error"): -1, 263 }, 264 }, 265 } 266 267 for _, tc := range cases { 268 t.Run(tc.name, func(t *testing.T) { 269 ctx := tc.ctx 270 if ctx == nil { 271 ctx = context.Background() 272 } 273 ctx, cancel := context.WithCancel(ctx) 274 defer cancel() 275 276 got := gcs.LeastRecentlyUpdated(ctx, logrus.WithField("name", tc.name), tc.client, tc.paths) 277 if diff := cmp.Diff(tc.wantGens, got, cmp.AllowUnexported(gcs.Path{})); diff != "" { 278 t.Errorf("unexpected generation diff (-want +got):\n%s", diff) 279 } 280 if diff := cmp.Diff(tc.wantPaths, tc.paths, cmp.AllowUnexported(gcs.Path{})); diff != "" { 281 t.Errorf("unexpected path diffs (-want +got):\n%s", diff) 282 } 283 }) 284 } 285 } 286 287 func TestTouch(t *testing.T) { 288 path := mustPath("gs://bucket/obj") 289 290 cases := []struct { 291 name string 292 ctx context.Context 293 client *ConditionalClient 294 generation int64 295 buf []byte 296 expected Uploader 297 badCond bool 298 err bool 299 }{ 300 { 301 name: "canceled context", 302 ctx: func() context.Context { 303 ctx, cancel := context.WithCancel(context.Background()) 304 cancel() 305 return ctx 306 }(), 307 generation: 123, 308 client: &ConditionalClient{ 309 UploadClient: UploadClient{ 310 Uploader: Uploader{ 311 *path: { 312 Buf: []byte("hi"), 313 Generation: 123, 314 }, 315 }, 316 Stater: Stater{ 317 *path: Stat{ 318 Attrs: storage.ObjectAttrs{ 319 Generation: 123, 320 }, 321 }, 322 }, 323 }, 324 }, 325 err: true, 326 }, 327 { 328 name: "same gen", 329 generation: 123, 330 client: &ConditionalClient{ 331 UploadClient: UploadClient{ 332 Uploader: Uploader{ 333 *path: { 334 Buf: []byte("hi"), 335 Generation: 123, 336 }, 337 }, 338 Stater: Stater{ 339 *path: Stat{ 340 Attrs: storage.ObjectAttrs{ 341 Generation: 123, 342 }, 343 }, 344 }, 345 }, 346 }, 347 expected: Uploader{ 348 *path: { 349 Buf: []byte("hi"), 350 Generation: 124, 351 }, 352 }, 353 }, 354 { 355 name: "wrong read gen", 356 generation: 123, 357 client: &ConditionalClient{ 358 UploadClient: UploadClient{ 359 Uploader: Uploader{ 360 *path: { 361 Err: errors.New("should not get here"), 362 }, 363 }, 364 Stater: Stater{ 365 *path: Stat{ 366 Attrs: storage.ObjectAttrs{ 367 Generation: 777, 368 }, 369 }, 370 }, 371 }, 372 }, 373 badCond: true, 374 err: true, 375 }, 376 { 377 name: "fail copy", 378 generation: 123, 379 client: &ConditionalClient{ 380 UploadClient: UploadClient{ 381 Uploader: Uploader{ 382 *path: { 383 Err: errors.New("upload should fail"), 384 }, 385 }, 386 Stater: Stater{ 387 *path: Stat{ 388 Attrs: storage.ObjectAttrs{ 389 Generation: 123, 390 }, 391 }, 392 }, 393 }, 394 }, 395 err: true, 396 }, 397 { 398 name: "upload", 399 client: &ConditionalClient{ 400 UploadClient: UploadClient{ 401 Uploader: Uploader{}, 402 Stater: Stater{}, 403 }, 404 }, 405 buf: []byte("hello"), 406 expected: Uploader{ 407 *path: { 408 Buf: []byte("hello"), 409 Generation: 1, 410 CacheControl: "no-cache", 411 }, 412 }, 413 }, 414 { 415 name: "fail upload", 416 client: &ConditionalClient{ 417 UploadClient: UploadClient{ 418 Uploader: Uploader{ 419 *path: { 420 Err: errors.New("upload should fail"), 421 }, 422 }, 423 Stater: Stater{}, 424 }, 425 }, 426 err: true, 427 }, 428 } 429 430 for _, tc := range cases { 431 t.Run(tc.name, func(t *testing.T) { 432 ctx := tc.ctx 433 if ctx == nil { 434 ctx = context.Background() 435 } 436 ctx, cancel := context.WithCancel(ctx) 437 defer cancel() 438 439 _, err := gcs.Touch(ctx, tc.client, *path, tc.generation, tc.buf) 440 switch { 441 case err != nil: 442 if !tc.err { 443 t.Errorf("got unexpected error: %v", err) 444 } 445 if tc.badCond { 446 var ee *googleapi.Error 447 if !errors.As(err, &ee) { 448 t.Errorf("wanted googleapi.Error, got %v", err) 449 } 450 if got, want := ee.Code, http.StatusPreconditionFailed; want != got { 451 t.Errorf("wanted %v got %v", want, got) 452 } 453 } 454 case tc.err: 455 t.Error("failed to receive an error") 456 default: 457 if diff := cmp.Diff(tc.expected, tc.client.Uploader); diff != "" { 458 t.Errorf("got unexpected diff (-want +got):\n%s", diff) 459 } 460 } 461 }) 462 } 463 }