github.com/yrj2011/jx-test-infra@v0.0.0-20190529031832-7a2065ee98eb/prow/tide/status_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 tide
    18  
    19  import (
    20  	"fmt"
    21  	"testing"
    22  
    23  	"github.com/shurcooL/githubv4"
    24  	"github.com/sirupsen/logrus"
    25  
    26  	"k8s.io/test-infra/prow/config"
    27  	"k8s.io/test-infra/prow/github"
    28  )
    29  
    30  func TestExpectedStatus(t *testing.T) {
    31  	neededLabels := []string{"need-1", "need-2", "need-a-very-super-duper-extra-not-short-at-all-label-name"}
    32  	forbiddenLabels := []string{"forbidden-1", "forbidden-2"}
    33  	testcases := []struct {
    34  		name string
    35  
    36  		baseref         string
    37  		branchWhitelist []string
    38  		branchBlacklist []string
    39  		labels          []string
    40  		milestone       string
    41  		contexts        []Context
    42  		inPool          bool
    43  
    44  		state string
    45  		desc  string
    46  	}{
    47  		{
    48  			name:   "in pool",
    49  			inPool: true,
    50  
    51  			state: github.StatusSuccess,
    52  			desc:  statusInPool,
    53  		},
    54  		{
    55  			name:   "check truncation of label list",
    56  			inPool: false,
    57  
    58  			state: github.StatusPending,
    59  			desc:  fmt.Sprintf(statusNotInPool, " Needs need-1, need-2 labels."),
    60  		},
    61  		{
    62  			name:   "check truncation of label list is not excessive",
    63  			labels: append([]string{}, neededLabels[:2]...),
    64  			inPool: false,
    65  
    66  			state: github.StatusPending,
    67  			desc:  fmt.Sprintf(statusNotInPool, " Needs need-a-very-super-duper-extra-not-short-at-all-label-name label."),
    68  		},
    69  		{
    70  			name:   "has forbidden labels",
    71  			labels: append(append([]string{}, neededLabels...), forbiddenLabels...),
    72  			inPool: false,
    73  
    74  			state: github.StatusPending,
    75  			desc:  fmt.Sprintf(statusNotInPool, " Should not have forbidden-1, forbidden-2 labels."),
    76  		},
    77  		{
    78  			name:   "has one forbidden label",
    79  			labels: append(append([]string{}, neededLabels...), forbiddenLabels[0]),
    80  			inPool: false,
    81  
    82  			state: github.StatusPending,
    83  			desc:  fmt.Sprintf(statusNotInPool, " Should not have forbidden-1 label."),
    84  		},
    85  		{
    86  			name:   "only mention one requirement class",
    87  			labels: append(append([]string{}, neededLabels[1:]...), forbiddenLabels[0]),
    88  			inPool: false,
    89  
    90  			state: github.StatusPending,
    91  			desc:  fmt.Sprintf(statusNotInPool, " Needs need-1 label."),
    92  		},
    93  		{
    94  			name:            "against excluded branch",
    95  			baseref:         "bad",
    96  			branchBlacklist: []string{"bad"},
    97  			labels:          neededLabels,
    98  			inPool:          false,
    99  
   100  			state: github.StatusPending,
   101  			desc:  fmt.Sprintf(statusNotInPool, " Merging to branch bad is forbidden."),
   102  		},
   103  		{
   104  			name:            "not against included branch",
   105  			baseref:         "bad",
   106  			branchWhitelist: []string{"good"},
   107  			labels:          neededLabels,
   108  			inPool:          false,
   109  
   110  			state: github.StatusPending,
   111  			desc:  fmt.Sprintf(statusNotInPool, " Merging to branch bad is forbidden."),
   112  		},
   113  		{
   114  			name:      "only failed tide context",
   115  			labels:    neededLabels,
   116  			milestone: "v1.0",
   117  			contexts:  []Context{{Context: githubv4.String(statusContext), State: githubv4.StatusStateError}},
   118  			inPool:    false,
   119  
   120  			state: github.StatusPending,
   121  			desc:  fmt.Sprintf(statusNotInPool, ""),
   122  		},
   123  		{
   124  			name:     "single bad context",
   125  			labels:   neededLabels,
   126  			contexts: []Context{{Context: githubv4.String("job-name"), State: githubv4.StatusStateError}},
   127  			inPool:   false,
   128  
   129  			state: github.StatusPending,
   130  			desc:  fmt.Sprintf(statusNotInPool, " Job job-name has not succeeded."),
   131  		},
   132  		{
   133  			name:   "multiple bad contexts",
   134  			labels: neededLabels,
   135  			contexts: []Context{
   136  				{Context: githubv4.String("job-name"), State: githubv4.StatusStateError},
   137  				{Context: githubv4.String("other-job-name"), State: githubv4.StatusStateError},
   138  			},
   139  			inPool: false,
   140  
   141  			state: github.StatusPending,
   142  			desc:  fmt.Sprintf(statusNotInPool, " Jobs job-name, other-job-name have not succeeded."),
   143  		},
   144  		{
   145  			name:      "wrong milestone",
   146  			labels:    neededLabels,
   147  			milestone: "v1.1",
   148  			contexts:  []Context{{Context: githubv4.String("job-name"), State: githubv4.StatusStateSuccess}},
   149  			inPool:    false,
   150  
   151  			state: github.StatusPending,
   152  			desc:  fmt.Sprintf(statusNotInPool, " Must be in milestone v1.0."),
   153  		},
   154  		{
   155  			name:      "unknown requirement",
   156  			labels:    neededLabels,
   157  			milestone: "v1.0",
   158  			contexts:  []Context{{Context: githubv4.String("job-name"), State: githubv4.StatusStateSuccess}},
   159  			inPool:    false,
   160  
   161  			state: github.StatusPending,
   162  			desc:  fmt.Sprintf(statusNotInPool, ""),
   163  		},
   164  		{
   165  			name:   "check that min diff query is used",
   166  			labels: []string{"3", "4", "5", "6", "7"},
   167  			inPool: false,
   168  
   169  			state: github.StatusPending,
   170  			desc:  fmt.Sprintf(statusNotInPool, " Needs 1, 2 labels."),
   171  		},
   172  	}
   173  
   174  	for _, tc := range testcases {
   175  		t.Logf("Test Case: %q\n", tc.name)
   176  		queriesByRepo := config.QueryMap(map[string]config.TideQueries{
   177  			"": {
   178  				config.TideQuery{
   179  					ExcludedBranches: tc.branchBlacklist,
   180  					IncludedBranches: tc.branchWhitelist,
   181  					Labels:           neededLabels,
   182  					MissingLabels:    forbiddenLabels,
   183  					Milestone:        "v1.0",
   184  				},
   185  				config.TideQuery{
   186  					Labels: []string{"1", "2", "3", "4", "5", "6", "7"}, // lots of requirements
   187  				},
   188  			},
   189  		})
   190  		var pr PullRequest
   191  		pr.BaseRef = struct {
   192  			Name   githubv4.String
   193  			Prefix githubv4.String
   194  		}{
   195  			Name: githubv4.String(tc.baseref),
   196  		}
   197  		for _, label := range tc.labels {
   198  			pr.Labels.Nodes = append(
   199  				pr.Labels.Nodes,
   200  				struct{ Name githubv4.String }{Name: githubv4.String(label)},
   201  			)
   202  		}
   203  		if len(tc.contexts) > 0 {
   204  			pr.HeadRefOID = githubv4.String("head")
   205  			pr.Commits.Nodes = append(
   206  				pr.Commits.Nodes,
   207  				struct{ Commit Commit }{
   208  					Commit: Commit{
   209  						Status: struct{ Contexts []Context }{
   210  							Contexts: tc.contexts,
   211  						},
   212  						OID: githubv4.String("head"),
   213  					},
   214  				},
   215  			)
   216  		}
   217  		if tc.milestone != "" {
   218  			pr.Milestone = &struct {
   219  				Title githubv4.String
   220  			}{githubv4.String(tc.milestone)}
   221  		}
   222  		var pool map[string]PullRequest
   223  		if tc.inPool {
   224  			pool = map[string]PullRequest{"#0": {}}
   225  		}
   226  
   227  		state, desc := expectedStatus(queriesByRepo, &pr, pool, &config.TideContextPolicy{})
   228  		if state != tc.state {
   229  			t.Errorf("Expected status state %q, but got %q.", string(tc.state), string(state))
   230  		}
   231  		if desc != tc.desc {
   232  			t.Errorf("Expected status description %q, but got %q.", tc.desc, desc)
   233  		}
   234  	}
   235  }
   236  
   237  func TestSetStatuses(t *testing.T) {
   238  	statusNotInPoolEmpty := fmt.Sprintf(statusNotInPool, "")
   239  	testcases := []struct {
   240  		name string
   241  
   242  		inPool     bool
   243  		hasContext bool
   244  		state      githubv4.StatusState
   245  		desc       string
   246  
   247  		shouldSet bool
   248  	}{
   249  		{
   250  			name: "in pool with proper context",
   251  
   252  			inPool:     true,
   253  			hasContext: true,
   254  			state:      githubv4.StatusStateSuccess,
   255  			desc:       statusInPool,
   256  
   257  			shouldSet: false,
   258  		},
   259  		{
   260  			name: "in pool without context",
   261  
   262  			inPool:     true,
   263  			hasContext: false,
   264  
   265  			shouldSet: true,
   266  		},
   267  		{
   268  			name: "in pool with improper context",
   269  
   270  			inPool:     true,
   271  			hasContext: true,
   272  			state:      githubv4.StatusStateSuccess,
   273  			desc:       statusNotInPoolEmpty,
   274  
   275  			shouldSet: true,
   276  		},
   277  		{
   278  			name: "in pool with wrong state",
   279  
   280  			inPool:     true,
   281  			hasContext: true,
   282  			state:      githubv4.StatusStatePending,
   283  			desc:       statusInPool,
   284  
   285  			shouldSet: true,
   286  		},
   287  		{
   288  			name: "not in pool with proper context",
   289  
   290  			inPool:     false,
   291  			hasContext: true,
   292  			state:      githubv4.StatusStatePending,
   293  			desc:       statusNotInPoolEmpty,
   294  
   295  			shouldSet: false,
   296  		},
   297  		{
   298  			name: "not in pool with improper context",
   299  
   300  			inPool:     false,
   301  			hasContext: true,
   302  			state:      githubv4.StatusStatePending,
   303  			desc:       statusInPool,
   304  
   305  			shouldSet: true,
   306  		},
   307  		{
   308  			name: "not in pool with no context",
   309  
   310  			inPool:     false,
   311  			hasContext: false,
   312  
   313  			shouldSet: true,
   314  		},
   315  	}
   316  	for _, tc := range testcases {
   317  		var pr PullRequest
   318  		pr.Commits.Nodes = []struct{ Commit Commit }{{}}
   319  		if tc.hasContext {
   320  			pr.Commits.Nodes[0].Commit.Status.Contexts = []Context{
   321  				{
   322  					Context:     githubv4.String(statusContext),
   323  					State:       tc.state,
   324  					Description: githubv4.String(tc.desc),
   325  				},
   326  			}
   327  		}
   328  		pool := make(map[string]PullRequest)
   329  		if tc.inPool {
   330  			pool[prKey(&pr)] = pr
   331  		}
   332  		fc := &fgc{}
   333  		ca := &config.Agent{}
   334  		ca.Set(&config.Config{})
   335  		// setStatuses logs instead of returning errors.
   336  		// Construct a logger to watch for errors to be printed.
   337  		log := logrus.WithField("component", "tide")
   338  		initialLog, err := log.String()
   339  		if err != nil {
   340  			t.Fatalf("Failed to get log output before testing: %v", err)
   341  		}
   342  
   343  		sc := &statusController{ghc: fc, ca: ca, logger: log}
   344  		sc.setStatuses([]PullRequest{pr}, pool)
   345  		if str, err := log.String(); err != nil {
   346  			t.Fatalf("For case %s: failed to get log output: %v", tc.name, err)
   347  		} else if str != initialLog {
   348  			t.Errorf("For case %s: error setting status: %s", tc.name, str)
   349  		}
   350  		if tc.shouldSet && !fc.setStatus {
   351  			t.Errorf("For case %s: should set but didn't", tc.name)
   352  		} else if !tc.shouldSet && fc.setStatus {
   353  			t.Errorf("For case %s: should not set but did", tc.name)
   354  		}
   355  	}
   356  }
   357  
   358  func TestTargetUrl(t *testing.T) {
   359  	testcases := []struct {
   360  		name   string
   361  		pr     *PullRequest
   362  		config config.Tide
   363  
   364  		expectedURL string
   365  	}{
   366  		{
   367  			name:        "no config",
   368  			pr:          &PullRequest{},
   369  			config:      config.Tide{},
   370  			expectedURL: "",
   371  		},
   372  		{
   373  			name:        "tide overview config",
   374  			pr:          &PullRequest{},
   375  			config:      config.Tide{TargetURL: "tide.com"},
   376  			expectedURL: "tide.com",
   377  		},
   378  		{
   379  			name:        "PR dashboard config and overview config",
   380  			pr:          &PullRequest{},
   381  			config:      config.Tide{TargetURL: "tide.com", PRStatusBaseURL: "pr.status.com"},
   382  			expectedURL: "tide.com",
   383  		},
   384  		{
   385  			name: "PR dashboard config",
   386  			pr: &PullRequest{
   387  				Author: struct {
   388  					Login githubv4.String
   389  				}{Login: githubv4.String("author")},
   390  				Repository: struct {
   391  					Name          githubv4.String
   392  					NameWithOwner githubv4.String
   393  					Owner         struct {
   394  						Login githubv4.String
   395  					}
   396  				}{NameWithOwner: githubv4.String("org/repo")},
   397  				HeadRefName: "head",
   398  			},
   399  			config:      config.Tide{PRStatusBaseURL: "pr.status.com"},
   400  			expectedURL: "pr.status.com?query=is%3Apr+repo%3Aorg%2Frepo+author%3Aauthor+head%3Ahead",
   401  		},
   402  	}
   403  
   404  	for _, tc := range testcases {
   405  		ca := &config.Agent{}
   406  		ca.Set(&config.Config{ProwConfig: config.ProwConfig{Tide: tc.config}})
   407  		log := logrus.WithField("controller", "status-update")
   408  		if actual, expected := targetURL(ca, tc.pr, log), tc.expectedURL; actual != expected {
   409  			t.Errorf("%s: expected target URL %s but got %s", tc.name, expected, actual)
   410  		}
   411  	}
   412  }