sigs.k8s.io/prow@v0.0.0-20240503223140-c5e374dc7eb1/pkg/statusreconciler/status_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 statusreconciler
    18  
    19  import (
    20  	"context"
    21  	"os"
    22  	"reflect"
    23  	"sort"
    24  	"testing"
    25  	"time"
    26  
    27  	"github.com/sirupsen/logrus"
    28  	v1 "k8s.io/api/core/v1"
    29  	"sigs.k8s.io/yaml"
    30  
    31  	"sigs.k8s.io/prow/pkg/config"
    32  	configflagutil "sigs.k8s.io/prow/pkg/flagutil/config"
    33  	"sigs.k8s.io/prow/pkg/io"
    34  )
    35  
    36  type testOpener struct{}
    37  
    38  func (t *testOpener) Reader(ctx context.Context, path string) (io.ReadCloser, error) {
    39  	return os.Open(path)
    40  }
    41  
    42  func (t *testOpener) Writer(ctx context.Context, path string, _ ...io.WriterOptions) (io.WriteCloser, error) {
    43  	return os.Create(path)
    44  }
    45  
    46  func TestLoadState(t *testing.T) {
    47  	config := config.Config{
    48  		ProwConfig: config.ProwConfig{
    49  			ProwJobNamespace: "default",
    50  		},
    51  		JobConfig: config.JobConfig{
    52  			PresubmitsStatic: map[string][]config.Presubmit{
    53  				"org/repo": getPresubmits([]string{"foo"}),
    54  			},
    55  		},
    56  	}
    57  	configFile, cleanup := getConfigFile(t, config)
    58  	defer cleanup()
    59  
    60  	sc := statusController{
    61  		logger:    logrus.NewEntry(logrus.StandardLogger()),
    62  		statusURI: configFile,
    63  		opener:    &testOpener{},
    64  	}
    65  
    66  	got, err := sc.loadState()
    67  	if err != nil {
    68  		t.Fatalf("Unexpected error: %v", err)
    69  	}
    70  	if !reflect.DeepEqual(got.Config, config) {
    71  		t.Errorf("Expected result %#v', got %#v", config, got)
    72  	}
    73  }
    74  
    75  func TestLoad(t *testing.T) {
    76  	presubmitFoo := "presubmit-foo"
    77  	presubmitBar := "presubmit-bar"
    78  
    79  	savedConfig := config.Config{
    80  		ProwConfig: config.ProwConfig{
    81  			ProwJobNamespace: "default",
    82  		},
    83  		JobConfig: config.JobConfig{
    84  			PresubmitsStatic: map[string][]config.Presubmit{
    85  				"org/repo": getPresubmits([]string{presubmitFoo}),
    86  			},
    87  		},
    88  	}
    89  
    90  	newProwConfig := config.ProwConfig{
    91  		ProwJobNamespace: "foo",
    92  	}
    93  
    94  	newJobConfig := config.JobConfig{
    95  		PresubmitsStatic: map[string][]config.Presubmit{
    96  			"org/repo": getPresubmits([]string{presubmitFoo, presubmitBar}),
    97  		},
    98  	}
    99  
   100  	testCases := []struct {
   101  		name               string
   102  		existingStatusFile bool
   103  		savedNamespace     string
   104  		savedPresubmits    []string
   105  		newNamespace       string
   106  		newPresubmits      []string
   107  	}{
   108  		{
   109  			name:          "no status file should not cause any errors",
   110  			newNamespace:  "foo",
   111  			newPresubmits: []string{"presubmit-bar", "presubmit-foo"},
   112  		},
   113  		{
   114  			name:               "With an existing status file, configuration changes since last saved should be identified",
   115  			existingStatusFile: true,
   116  			savedNamespace:     "default",
   117  			savedPresubmits:    []string{"presubmit-foo"},
   118  			newNamespace:       "foo",
   119  			newPresubmits:      []string{"presubmit-bar", "presubmit-foo"},
   120  		},
   121  	}
   122  
   123  	for _, tc := range testCases {
   124  		t.Run(tc.name, func(t *testing.T) {
   125  			statusURI := ""
   126  			if tc.existingStatusFile {
   127  				statusFile, cleanupStatusFile := getConfigFile(t, savedConfig)
   128  				defer cleanupStatusFile()
   129  				statusURI = statusFile
   130  			}
   131  
   132  			configFile, cleanupConfig := getConfigFile(t, config.Config{ProwConfig: newProwConfig})
   133  			defer cleanupConfig()
   134  
   135  			jobConfigFile, cleanupJobConfig := getConfigFile(t, config.Config{JobConfig: newJobConfig})
   136  			defer cleanupJobConfig()
   137  
   138  			sc := statusController{
   139  				logger:    logrus.NewEntry(logrus.StandardLogger()),
   140  				statusURI: statusURI,
   141  				opener:    &testOpener{},
   142  				configOpts: configflagutil.ConfigOptions{
   143  					ConfigPath:    configFile,
   144  					JobConfigPath: jobConfigFile,
   145  				},
   146  			}
   147  			changes, err := sc.Load()
   148  			if err != nil {
   149  				t.Fatalf("%s: unexpected error %v", tc.name, err)
   150  			}
   151  			select {
   152  			case change := <-changes:
   153  				verify(t, tc.name+"/before", change.Before, tc.savedNamespace, tc.savedPresubmits)
   154  				verify(t, tc.name+"/after", change.After, tc.newNamespace, tc.newPresubmits)
   155  			case <-time.After(3 * time.Second):
   156  				t.Fatalf("%s: unexpected timeout while waiting for configuration changes", tc.name)
   157  			}
   158  		})
   159  	}
   160  }
   161  
   162  func TestSave(t *testing.T) {
   163  	presubmitFoo := "presubmit-foo"
   164  	presubmitBar := "presubmit-bar"
   165  	prowConfig := config.ProwConfig{
   166  		ProwJobNamespace: "default",
   167  	}
   168  	jobConfig := config.JobConfig{
   169  		PresubmitsStatic: map[string][]config.Presubmit{
   170  			"org/repo": getPresubmits([]string{presubmitFoo, presubmitBar}),
   171  		},
   172  	}
   173  
   174  	configFile, cleanupConfig := getConfigFile(t, config.Config{ProwConfig: prowConfig})
   175  	defer cleanupConfig()
   176  
   177  	jobConfigFile, cleanupJobConfig := getConfigFile(t, config.Config{JobConfig: jobConfig})
   178  	defer cleanupJobConfig()
   179  
   180  	testCases := []struct {
   181  		name               string
   182  		specifyStatusFile  bool
   183  		expectedNamespace  string
   184  		expectedPresubmits []string
   185  	}{
   186  		{
   187  			name: "not specifying a status file should not cause any errors",
   188  		},
   189  		{
   190  			name:               "save stores the configuration in the specified statusURI",
   191  			expectedNamespace:  "default",
   192  			expectedPresubmits: []string{"presubmit-bar", "presubmit-foo"},
   193  		},
   194  	}
   195  
   196  	for _, tc := range testCases {
   197  		statusURI := ""
   198  		if tc.specifyStatusFile {
   199  			statusFile, cleanupStatusFile := getConfigFile(t, config.Config{})
   200  			defer cleanupStatusFile()
   201  			statusURI = statusFile
   202  		}
   203  
   204  		t.Run(tc.name, func(t *testing.T) {
   205  			sc := statusController{
   206  				logger:    logrus.NewEntry(logrus.StandardLogger()),
   207  				statusURI: statusURI,
   208  				opener:    &testOpener{},
   209  				configOpts: configflagutil.ConfigOptions{
   210  					ConfigPath:    configFile,
   211  					JobConfigPath: jobConfigFile,
   212  				},
   213  			}
   214  			if err := sc.Save(); err != nil {
   215  				t.Fatalf("%s: unexpected error: %v", tc.name, err)
   216  			}
   217  
   218  			if statusURI != "" {
   219  				buf, err := os.ReadFile(statusURI)
   220  				if err != nil {
   221  					t.Fatalf("%s: unexpected error reading status file: %v", tc.name, err)
   222  				}
   223  
   224  				var got config.Config
   225  				if err := yaml.Unmarshal(buf, &got); err != nil {
   226  					t.Fatalf("%s: unexpected error unmarshaling status file contents %v", tc.name, err)
   227  				}
   228  				verify(t, tc.name, got, tc.expectedNamespace, tc.expectedPresubmits)
   229  			}
   230  		})
   231  	}
   232  }
   233  
   234  func getConfigFile(t *testing.T, config config.Config) (string, func()) {
   235  	tempFile, err := os.CreateTemp("/tmp", "prow-test")
   236  	if err != nil {
   237  		t.Fatalf("failed to get tempfile: %v", err)
   238  	}
   239  	cleanup := func() {
   240  		if err := tempFile.Close(); err != nil {
   241  			t.Errorf("failed to close tempFile: %v", err)
   242  		}
   243  		if err := os.Remove(tempFile.Name()); err != nil {
   244  			t.Errorf("failed to remove tempfile: %v", err)
   245  		}
   246  	}
   247  
   248  	buf, err := yaml.Marshal(config)
   249  	if err != nil {
   250  		t.Fatalf("Cannot marshal config: %v", err)
   251  	}
   252  
   253  	if _, err := tempFile.Write(buf); err != nil {
   254  		t.Fatalf("failed to write to tempfile: %v", err)
   255  	}
   256  
   257  	return tempFile.Name(), cleanup
   258  }
   259  
   260  func getPresubmits(names []string) []config.Presubmit {
   261  	spec := &v1.PodSpec{
   262  		Containers: []v1.Container{
   263  			{
   264  				Image: "image",
   265  			},
   266  		},
   267  	}
   268  
   269  	var presubmits []config.Presubmit
   270  	for _, name := range names {
   271  		ps := config.Presubmit{
   272  			JobBase: config.JobBase{
   273  				Name: name,
   274  				Spec: spec,
   275  			},
   276  			AlwaysRun: true,
   277  			Reporter:  config.Reporter{Context: name},
   278  		}
   279  		presubmits = append(presubmits, ps)
   280  	}
   281  	return presubmits
   282  }
   283  
   284  func verify(t *testing.T, testCase string, config config.Config, Namespace string, presubmits []string) {
   285  	if config.ProwConfig.ProwJobNamespace != Namespace {
   286  		t.Errorf("%s: expected namespace %s, got %s", testCase, Namespace, config.ProwConfig.ProwJobNamespace)
   287  	}
   288  	var names []string
   289  	for _, ps := range config.JobConfig.PresubmitsStatic["org/repo"] {
   290  		names = append(names, ps.JobBase.Name)
   291  	}
   292  	sort.Strings(names)
   293  	sort.Strings(presubmits)
   294  	if !reflect.DeepEqual(names, presubmits) {
   295  		t.Errorf("%s: expected presubmit names %v, got %v", testCase, presubmits, names)
   296  	}
   297  }