github.com/zppinho/prow@v0.0.0-20240510014325-1738badeb017/cmd/external-plugins/needs-rebase/plugin/plugin_test.go (about)

     1  /*
     2  Copyright 2017 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 plugin
    18  
    19  import (
    20  	"context"
    21  	"errors"
    22  	"fmt"
    23  	"os"
    24  	"reflect"
    25  	"sort"
    26  	"testing"
    27  	"time"
    28  
    29  	"github.com/google/go-cmp/cmp"
    30  	"github.com/google/go-cmp/cmp/cmpopts"
    31  	githubql "github.com/shurcooL/githubv4"
    32  	"github.com/sirupsen/logrus"
    33  	"sigs.k8s.io/prow/pkg/github"
    34  	"sigs.k8s.io/prow/pkg/labels"
    35  	"sigs.k8s.io/prow/pkg/plugins"
    36  )
    37  
    38  func testKey(org, repo string, num int) string {
    39  	return fmt.Sprintf("%s/%s#%d", org, repo, num)
    40  }
    41  
    42  type fghc struct {
    43  	allPRs []struct {
    44  		PullRequest pullRequest `graphql:"... on PullRequest"`
    45  	}
    46  	pr *github.PullRequest
    47  
    48  	initialLabels []github.Label
    49  	mergeable     bool
    50  
    51  	// The following are maps are keyed using 'testKey'
    52  	commentCreated, commentDeleted       map[string]bool
    53  	IssueLabelsAdded, IssueLabelsRemoved map[string][]string
    54  }
    55  
    56  func newFakeClient(prs []pullRequest, initialLabels []string, mergeable bool, pr *github.PullRequest) *fghc {
    57  	f := &fghc{
    58  		mergeable:          mergeable,
    59  		commentCreated:     make(map[string]bool),
    60  		commentDeleted:     make(map[string]bool),
    61  		IssueLabelsAdded:   make(map[string][]string),
    62  		IssueLabelsRemoved: make(map[string][]string),
    63  		pr:                 pr,
    64  	}
    65  	for _, pr := range prs {
    66  		s := struct {
    67  			PullRequest pullRequest `graphql:"... on PullRequest"`
    68  		}{pr}
    69  		f.allPRs = append(f.allPRs, s)
    70  	}
    71  	for _, label := range initialLabels {
    72  		f.initialLabels = append(f.initialLabels, github.Label{Name: label})
    73  	}
    74  	return f
    75  }
    76  
    77  func (f *fghc) GetIssueLabels(org, repo string, number int) ([]github.Label, error) {
    78  	return f.initialLabels, nil
    79  }
    80  
    81  func (f *fghc) CreateCommentWithContext(_ context.Context, org, repo string, number int, comment string) error {
    82  	f.commentCreated[testKey(org, repo, number)] = true
    83  	return nil
    84  }
    85  
    86  func (f *fghc) BotUserChecker() (func(candidate string) bool, error) {
    87  	return func(candidate string) bool { return candidate == "k8s-ci-robot" }, nil
    88  }
    89  
    90  func (f *fghc) AddLabelWithContext(_ context.Context, org, repo string, number int, label string) error {
    91  	key := testKey(org, repo, number)
    92  	f.IssueLabelsAdded[key] = append(f.IssueLabelsAdded[key], label)
    93  	return nil
    94  }
    95  
    96  func (f *fghc) RemoveLabelWithContext(_ context.Context, org, repo string, number int, label string) error {
    97  	key := testKey(org, repo, number)
    98  	f.IssueLabelsRemoved[key] = append(f.IssueLabelsRemoved[key], label)
    99  	return nil
   100  }
   101  
   102  func (f *fghc) IsMergeable(org, repo string, number int, sha string) (bool, error) {
   103  	return f.mergeable, nil
   104  }
   105  
   106  func (f *fghc) DeleteStaleCommentsWithContext(_ context.Context, org, repo string, number int, comments []github.IssueComment, isStale func(github.IssueComment) bool) error {
   107  	f.commentDeleted[testKey(org, repo, number)] = true
   108  	return nil
   109  }
   110  
   111  func (f *fghc) QueryWithGitHubAppsSupport(_ context.Context, q interface{}, _ map[string]interface{}, _ string) error {
   112  	query, ok := q.(*searchQuery)
   113  	if !ok {
   114  		return errors.New("invalid query format")
   115  	}
   116  	query.Search.Nodes = f.allPRs
   117  	return nil
   118  }
   119  
   120  func (f *fghc) GetPullRequest(org, repo string, number int) (*github.PullRequest, error) {
   121  	if f.pr != nil {
   122  		return f.pr, nil
   123  	}
   124  	return nil, fmt.Errorf("didn't find pull request %s/%s#%d", org, repo, number)
   125  }
   126  
   127  func (f *fghc) compareExpected(t *testing.T, org, repo string, num int, expectedAdded []string, expectedRemoved []string, expectComment bool, expectDeletion bool) {
   128  	key := testKey(org, repo, num)
   129  	sort.Strings(expectedAdded)
   130  	sort.Strings(expectedRemoved)
   131  	sort.Strings(f.IssueLabelsAdded[key])
   132  	sort.Strings(f.IssueLabelsRemoved[key])
   133  	if !reflect.DeepEqual(expectedAdded, f.IssueLabelsAdded[key]) {
   134  		t.Errorf("Expected the following labels to be added to %s: %q, but got %q.", key, expectedAdded, f.IssueLabelsAdded[key])
   135  	}
   136  	if !reflect.DeepEqual(expectedRemoved, f.IssueLabelsRemoved[key]) {
   137  		t.Errorf("Expected the following labels to be removed from %s: %q, but got %q.", key, expectedRemoved, f.IssueLabelsRemoved[key])
   138  	}
   139  	if expectComment && !f.commentCreated[key] {
   140  		t.Errorf("Expected a comment to be created on %s, but none was.", key)
   141  	} else if !expectComment && f.commentCreated[key] {
   142  		t.Errorf("Unexpected comment on %s.", key)
   143  	}
   144  	if expectDeletion && !f.commentDeleted[key] {
   145  		t.Errorf("Expected a comment to be deleted from %s, but none was.", key)
   146  	} else if !expectDeletion && f.commentDeleted[key] {
   147  		t.Errorf("Unexpected comment deletion on %s.", key)
   148  	}
   149  }
   150  
   151  func TestMain(m *testing.M) {
   152  	sleep = func(time.Duration) {}
   153  	code := m.Run()
   154  	os.Exit(code)
   155  }
   156  
   157  func TestHandleIssueCommentEvent(t *testing.T) {
   158  	t.Parallel()
   159  	pr := func() *github.PullRequest {
   160  		pr := github.PullRequest{
   161  			Base: github.PullRequestBranch{
   162  				Repo: github.Repo{
   163  					Name:  "repo",
   164  					Owner: github.User{Login: "org"},
   165  				},
   166  			},
   167  			Number: 5,
   168  		}
   169  		return &pr
   170  	}
   171  
   172  	testCases := []struct {
   173  		name string
   174  		pr   *github.PullRequest
   175  
   176  		mergeable bool
   177  		labels    []string
   178  		state     string
   179  		merged    bool
   180  
   181  		expectedAdded   []string
   182  		expectedRemoved []string
   183  		expectComment   bool
   184  		expectDeletion  bool
   185  	}{
   186  		{
   187  			name: "No pull request, ignoring",
   188  		},
   189  		{
   190  			name:      "mergeable no-op",
   191  			pr:        pr(),
   192  			mergeable: true,
   193  			labels:    []string{labels.LGTM, labels.Approved},
   194  			state:     github.PullRequestStateOpen,
   195  		},
   196  		{
   197  			name:      "unmergeable no-op",
   198  			pr:        pr(),
   199  			mergeable: false,
   200  			labels:    []string{labels.LGTM, labels.Approved, labels.NeedsRebase},
   201  			state:     github.PullRequestStateOpen,
   202  		},
   203  		{
   204  			name:      "mergeable -> unmergeable",
   205  			pr:        pr(),
   206  			mergeable: false,
   207  			labels:    []string{labels.LGTM, labels.Approved},
   208  			state:     github.PullRequestStateOpen,
   209  
   210  			expectedAdded: []string{labels.NeedsRebase},
   211  			expectComment: true,
   212  		},
   213  		{
   214  			name:      "unmergeable -> mergeable",
   215  			pr:        pr(),
   216  			mergeable: true,
   217  			labels:    []string{labels.LGTM, labels.Approved, labels.NeedsRebase},
   218  			state:     github.PullRequestStateOpen,
   219  
   220  			expectedRemoved: []string{labels.NeedsRebase},
   221  			expectDeletion:  true,
   222  		},
   223  		{
   224  			name:      "merged pr is ignored",
   225  			pr:        pr(),
   226  			mergeable: false,
   227  			state:     github.PullRequestStateClosed,
   228  			merged:    true,
   229  		},
   230  		{
   231  			name:      "closed pr is ignored",
   232  			pr:        pr(),
   233  			mergeable: false,
   234  			state:     github.PullRequestStateClosed,
   235  		},
   236  	}
   237  
   238  	for _, tc := range testCases {
   239  		t.Run(tc.name, func(t *testing.T) {
   240  			fake := newFakeClient(nil, tc.labels, tc.mergeable, tc.pr)
   241  			ice := &github.IssueCommentEvent{}
   242  			if tc.pr != nil {
   243  				ice.Issue.PullRequest = &struct{}{}
   244  				tc.pr.Merged = tc.merged
   245  				tc.pr.State = tc.state
   246  			}
   247  			cache := NewCache(0)
   248  			if err := HandleIssueCommentEvent(logrus.WithField("plugin", PluginName), fake, ice, cache); err != nil {
   249  				t.Fatalf("error handling issue comment event: %v", err)
   250  			}
   251  			fake.compareExpected(t, "org", "repo", 5, tc.expectedAdded, tc.expectedRemoved, tc.expectComment, tc.expectDeletion)
   252  		})
   253  	}
   254  }
   255  
   256  func TestHandlePullRequestEvent(t *testing.T) {
   257  	t.Parallel()
   258  
   259  	testCases := []struct {
   260  		name string
   261  
   262  		mergeable bool
   263  		labels    []string
   264  		state     string
   265  		merged    bool
   266  
   267  		expectedAdded   []string
   268  		expectedRemoved []string
   269  		expectComment   bool
   270  		expectDeletion  bool
   271  	}{
   272  		{
   273  			name:      "mergeable no-op",
   274  			mergeable: true,
   275  			labels:    []string{labels.LGTM, labels.Approved},
   276  			state:     github.PullRequestStateOpen,
   277  		},
   278  		{
   279  			name:      "unmergeable no-op",
   280  			mergeable: false,
   281  			labels:    []string{labels.LGTM, labels.Approved, labels.NeedsRebase},
   282  			state:     github.PullRequestStateOpen,
   283  		},
   284  		{
   285  			name:      "mergeable -> unmergeable",
   286  			mergeable: false,
   287  			labels:    []string{labels.LGTM, labels.Approved},
   288  			state:     github.PullRequestStateOpen,
   289  
   290  			expectedAdded: []string{labels.NeedsRebase},
   291  			expectComment: true,
   292  		},
   293  		{
   294  			name:      "unmergeable -> mergeable",
   295  			mergeable: true,
   296  			labels:    []string{labels.LGTM, labels.Approved, labels.NeedsRebase},
   297  			state:     github.PullRequestStateOpen,
   298  
   299  			expectedRemoved: []string{labels.NeedsRebase},
   300  			expectDeletion:  true,
   301  		},
   302  		{
   303  			name:   "merged pr is ignored",
   304  			merged: true,
   305  			state:  github.PullRequestStateClosed,
   306  		},
   307  		{
   308  			name:  "closed pr is ignored",
   309  			state: github.PullRequestStateClosed,
   310  		},
   311  	}
   312  
   313  	for _, tc := range testCases {
   314  		t.Run(tc.name, func(t *testing.T) {
   315  			fake := newFakeClient(nil, tc.labels, tc.mergeable, nil)
   316  			pre := &github.PullRequestEvent{
   317  				Action: github.PullRequestActionSynchronize,
   318  				PullRequest: github.PullRequest{
   319  					Base: github.PullRequestBranch{
   320  						Repo: github.Repo{
   321  							Name:  "repo",
   322  							Owner: github.User{Login: "org"},
   323  						},
   324  					},
   325  					Merged: tc.merged,
   326  					State:  tc.state,
   327  					Number: 5,
   328  				},
   329  			}
   330  			t.Logf("Running test scenario: %q", tc.name)
   331  			if err := HandlePullRequestEvent(logrus.WithField("plugin", PluginName), fake, pre); err != nil {
   332  				t.Fatalf("Unexpected error handling event: %v.", err)
   333  			}
   334  			fake.compareExpected(t, "org", "repo", 5, tc.expectedAdded, tc.expectedRemoved, tc.expectComment, tc.expectDeletion)
   335  		})
   336  	}
   337  }
   338  
   339  func TestHandleAll(t *testing.T) {
   340  	t.Parallel()
   341  	testPRs := []struct {
   342  		name string
   343  
   344  		labels    []string
   345  		mergeable bool
   346  		state     githubql.PullRequestState
   347  
   348  		expectedAdded, expectedRemoved []string
   349  		expectComment, expectDeletion  bool
   350  	}{
   351  		{
   352  			name:      "PR State Merged",
   353  			mergeable: false,
   354  			state:     githubql.PullRequestStateMerged,
   355  			labels:    []string{labels.LGTM, labels.Approved},
   356  		},
   357  		{
   358  			name:      "PR State Closed",
   359  			mergeable: false,
   360  			state:     githubql.PullRequestStateClosed,
   361  			labels:    []string{labels.LGTM, labels.Approved},
   362  		},
   363  		{
   364  			name:      "PR State Closed with need-rebase label",
   365  			mergeable: false,
   366  			state:     githubql.PullRequestStateClosed,
   367  			labels:    []string{labels.LGTM, labels.Approved, labels.NeedsRebase},
   368  		},
   369  		{
   370  			name:      "PR State Open with non-mergeable",
   371  			mergeable: false,
   372  			state:     githubql.PullRequestStateOpen,
   373  			labels:    []string{labels.LGTM, labels.Approved},
   374  
   375  			expectedAdded: []string{labels.NeedsRebase},
   376  			expectComment: true,
   377  		},
   378  		{
   379  			name:      "PR State Open with mergeable",
   380  			mergeable: true,
   381  			state:     githubql.PullRequestStateOpen,
   382  			labels:    []string{labels.LGTM, labels.Approved, labels.NeedsRebase},
   383  
   384  			expectedRemoved: []string{labels.NeedsRebase},
   385  			expectDeletion:  true,
   386  		},
   387  	}
   388  
   389  	prs := []pullRequest{}
   390  	for i, testPR := range testPRs {
   391  		pr := pullRequest{
   392  			Number: githubql.Int(i),
   393  			State:  testPR.state,
   394  		}
   395  		if testPR.mergeable {
   396  			pr.Mergeable = githubql.MergeableStateMergeable
   397  		} else {
   398  			pr.Mergeable = githubql.MergeableStateConflicting
   399  		}
   400  		for _, label := range testPR.labels {
   401  			s := struct {
   402  				Name githubql.String
   403  			}{
   404  				Name: githubql.String(label),
   405  			}
   406  			pr.Labels.Nodes = append(pr.Labels.Nodes, s)
   407  		}
   408  		prs = append(prs, pr)
   409  	}
   410  	fake := newFakeClient(prs, nil, false, nil)
   411  	config := &plugins.Configuration{
   412  		Plugins: plugins.Plugins{"/": {Plugins: []string{labels.LGTM, PluginName}}},
   413  
   414  		ExternalPlugins: map[string][]plugins.ExternalPlugin{"/": {{Name: PluginName}}},
   415  	}
   416  	issueCache := NewFakeCache(0)
   417  
   418  	if err := HandleAll(logrus.WithField("plugin", PluginName), fake, config, false, issueCache); err != nil {
   419  		t.Fatalf("Unexpected error handling all prs: %v.", err)
   420  	}
   421  	for i, pr := range testPRs {
   422  		t.Run(pr.name, func(t *testing.T) {
   423  			fake.compareExpected(t, "", "", i, pr.expectedAdded, pr.expectedRemoved, pr.expectComment, pr.expectDeletion)
   424  		})
   425  	}
   426  }
   427  
   428  func TestConstructQueries(t *testing.T) {
   429  	t.Parallel()
   430  	testCases := []struct {
   431  		name         string
   432  		orgs         []string
   433  		repos        []string
   434  		usesAppsAuth bool
   435  
   436  		expected map[string][]string
   437  	}{
   438  		{
   439  			name: "Kubernetes and Kubernetes-Sigs org, no repos",
   440  			orgs: []string{"kubernetes", "kubernetes-sigs"},
   441  
   442  			expected: map[string][]string{
   443  				"kubernetes": {
   444  					`archived:false is:pr is:open org:"kubernetes" -repo:"kubernetes/kubernetes"`,
   445  					`archived:false is:pr is:open repo:"kubernetes/kubernetes" created:>=0000-11-02`,
   446  					`archived:false is:pr is:open repo:"kubernetes/kubernetes" created:<0000-11-02`,
   447  				},
   448  				"kubernetes-sigs": {`archived:false is:pr is:open org:"kubernetes-sigs"`},
   449  			},
   450  		},
   451  		{
   452  			name:         "Kubernetes and Kubernetes-Sigs org, no repos, apps auth",
   453  			orgs:         []string{"kubernetes", "kubernetes-sigs"},
   454  			usesAppsAuth: true,
   455  
   456  			expected: map[string][]string{
   457  				"kubernetes": {
   458  					`archived:false is:pr is:open org:"kubernetes" -repo:"kubernetes/kubernetes"`,
   459  					`archived:false is:pr is:open repo:"kubernetes/kubernetes" created:>=0000-11-02`,
   460  					`archived:false is:pr is:open repo:"kubernetes/kubernetes" created:<0000-11-02`,
   461  				},
   462  				"kubernetes-sigs": {`archived:false is:pr is:open org:"kubernetes-sigs"`},
   463  			},
   464  		},
   465  		{
   466  			name: "Other orgs, no repos",
   467  			orgs: []string{"other", "other-sigs"},
   468  
   469  			expected: map[string][]string{
   470  				"other":      {`archived:false is:pr is:open org:"other"`},
   471  				"other-sigs": {`archived:false is:pr is:open org:"other-sigs"`},
   472  			},
   473  		},
   474  		{
   475  			name:         "Other org, no repos, apps auth",
   476  			orgs:         []string{"other", "other-sigs"},
   477  			usesAppsAuth: true,
   478  
   479  			expected: map[string][]string{
   480  				"other":      {`archived:false is:pr is:open org:"other"`},
   481  				"other-sigs": {`archived:false is:pr is:open org:"other-sigs"`},
   482  			},
   483  		},
   484  		{
   485  			name:  "Some repos, no orgs",
   486  			repos: []string{"org/repo", "other/repo"},
   487  
   488  			expected: map[string][]string{"": {`archived:false is:pr is:open repo:"org/repo" repo:"other/repo"`}},
   489  		},
   490  		{
   491  			name:         "Some repos, no orgs, apps auth",
   492  			repos:        []string{"org/repo", "other/repo"},
   493  			usesAppsAuth: true,
   494  
   495  			expected: map[string][]string{
   496  				"org":   {`archived:false is:pr is:open repo:"org/repo"`},
   497  				"other": {`archived:false is:pr is:open repo:"other/repo"`},
   498  			},
   499  		},
   500  		{
   501  			name:  "Invalid repo is ignored",
   502  			repos: []string{"repo"},
   503  		},
   504  		{
   505  			name:  "Org and repo in that org, repo is ignored",
   506  			orgs:  []string{"org"},
   507  			repos: []string{"org/repo"},
   508  
   509  			expected: map[string][]string{"org": {`archived:false is:pr is:open org:"org"`}},
   510  		},
   511  		{
   512  			name:  "Org and repo in that org, repo is ignored, apps auth",
   513  			orgs:  []string{"org"},
   514  			repos: []string{"org/repo"},
   515  
   516  			expected: map[string][]string{"org": {`archived:false is:pr is:open org:"org"`}},
   517  		},
   518  		{
   519  			name:  "Some orgs and some repos",
   520  			orgs:  []string{"org", "other"},
   521  			repos: []string{"repoorg/repo", "otherrepoorg/repo"},
   522  
   523  			expected: map[string][]string{
   524  				"":      {`archived:false is:pr is:open repo:"repoorg/repo" repo:"otherrepoorg/repo"`},
   525  				"org":   {`archived:false is:pr is:open org:"org"`},
   526  				"other": {`archived:false is:pr is:open org:"other"`},
   527  			},
   528  		},
   529  		{
   530  			name:         "Some orgs and some repos, apps auth",
   531  			orgs:         []string{"org", "other"},
   532  			repos:        []string{"repoorg/repo", "otherrepoorg/repo"},
   533  			usesAppsAuth: true,
   534  
   535  			expected: map[string][]string{
   536  				"org":          {`archived:false is:pr is:open org:"org"`},
   537  				"other":        {`archived:false is:pr is:open org:"other"`},
   538  				"otherrepoorg": {`archived:false is:pr is:open repo:"otherrepoorg/repo"`},
   539  				"repoorg":      {`archived:false is:pr is:open repo:"repoorg/repo"`},
   540  			},
   541  		},
   542  		{
   543  			name:  "Multiple repos in the same org",
   544  			repos: []string{"org/a", "org/b"},
   545  
   546  			expected: map[string][]string{"": {`archived:false is:pr is:open repo:"org/a" repo:"org/b"`}},
   547  		},
   548  		{
   549  			name:         "Multiple repos in the same org, apps auth",
   550  			repos:        []string{"org/a", "org/b"},
   551  			usesAppsAuth: true,
   552  
   553  			expected: map[string][]string{"org": {`archived:false is:pr is:open repo:"org/a" repo:"org/b"`}},
   554  		},
   555  	}
   556  
   557  	for _, tc := range testCases {
   558  		t.Run(tc.name, func(t *testing.T) {
   559  			result := constructQueries(logrus.WithField("test", tc.name), time.Time{}, tc.orgs, tc.repos, tc.usesAppsAuth)
   560  			if diff := cmp.Diff(tc.expected, result, cmpopts.EquateEmpty()); diff != "" {
   561  				t.Errorf("expected result differs from actual: %s", diff)
   562  			}
   563  		})
   564  	}
   565  }
   566  
   567  func NewFakeCache(validTime time.Duration) *Cache {
   568  	return &Cache{
   569  		cache:       make(map[int]time.Time),
   570  		validTime:   time.Second * validTime,
   571  		currentTime: getFakeTime(),
   572  	}
   573  }
   574  
   575  func getFakeTime() timeNow {
   576  	var i = 0
   577  	now := time.Date(2022, 1, 1, 0, 0, 0, 0, time.UTC)
   578  	return func() time.Time {
   579  		i = i + 1
   580  		return now.Add(time.Duration(i) * time.Second)
   581  	}
   582  }
   583  
   584  func TestCache(t *testing.T) {
   585  	t.Parallel()
   586  	testCases := []struct {
   587  		name      string
   588  		validTime time.Duration
   589  		keys      []int
   590  
   591  		expected []bool
   592  	}{
   593  		{
   594  			name:      "Test cache - disabled",
   595  			validTime: 0,
   596  
   597  			keys:     []int{11, 22, 22, 11},
   598  			expected: []bool{false, false, false, false},
   599  		},
   600  		{
   601  			name:      "Test cache - all miss",
   602  			validTime: 100,
   603  
   604  			keys:     []int{11, 22, 33, 44},
   605  			expected: []bool{false, false, false, false},
   606  		},
   607  		{
   608  			name:      "Test cache - one key hits, other missed",
   609  			validTime: 100,
   610  
   611  			keys:     []int{11, 22, 33, 11},
   612  			expected: []bool{false, false, false, true},
   613  		},
   614  		{
   615  			name:      "Test cache - repeated requested same key",
   616  			validTime: 100,
   617  
   618  			keys:     []int{11, 11, 11, 11},
   619  			expected: []bool{false, true, true, true},
   620  		},
   621  	}
   622  
   623  	for _, tc := range testCases {
   624  		t.Run(tc.name, func(t *testing.T) {
   625  			fake := NewFakeCache(tc.validTime)
   626  			t.Logf("Running test scenario: %q", tc.name)
   627  			for idx, key := range tc.keys {
   628  				age := fake.Get(key)
   629  				if age != tc.expected[idx] {
   630  					t.Errorf("Unexpected cache age %t, expected %t.", age, tc.expected[idx])
   631  				}
   632  				fake.Set(key)
   633  			}
   634  		})
   635  	}
   636  }