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  }