istio.io/istio@v0.0.0-20240520182934-d79c90f27776/pkg/test/framework/suite_test.go (about)

     1  // Copyright Istio Authors
     2  //
     3  // Licensed under the Apache License, Version 2.0 (the "License");
     4  // you may not use this file except in compliance with the License.
     5  // You may obtain a copy of the License at
     6  //
     7  //     http://www.apache.org/licenses/LICENSE-2.0
     8  //
     9  // Unless required by applicable law or agreed to in writing, software
    10  // distributed under the License is distributed on an "AS IS" BASIS,
    11  // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
    12  // See the License for the specific language governing permissions and
    13  // limitations under the License.
    14  
    15  package framework
    16  
    17  import (
    18  	"fmt"
    19  	"os"
    20  	"sync"
    21  	"testing"
    22  
    23  	. "github.com/onsi/gomega"
    24  
    25  	"istio.io/istio/pkg/test/framework/components/environment/kube"
    26  	"istio.io/istio/pkg/test/framework/label"
    27  	"istio.io/istio/pkg/test/framework/resource"
    28  )
    29  
    30  func defaultExitFn(_ int) {}
    31  
    32  func settingsFn(s *resource.Settings) func(string) (*resource.Settings, error) {
    33  	return func(testID string) (*resource.Settings, error) {
    34  		s.TestID = testID
    35  		s.BaseDir = os.TempDir()
    36  		return s, nil
    37  	}
    38  }
    39  
    40  func defaultSettingsFn(testID string) (*resource.Settings, error) {
    41  	s := resource.DefaultSettings()
    42  	s.TestID = testID
    43  	s.BaseDir = os.TempDir()
    44  
    45  	return s, nil
    46  }
    47  
    48  func cleanupRT() {
    49  	rtMu.Lock()
    50  	defer rtMu.Unlock()
    51  	rt = nil
    52  }
    53  
    54  // Create a bogus environment for testing. This can be removed when "environments" are removed
    55  func newTestSuite(testID string, fn mRunFn, osExit func(int), getSettingsFn getSettingsFunc) *suiteImpl {
    56  	s := newSuite(testID, fn, osExit, getSettingsFn)
    57  	s.envFactory = func(ctx resource.Context) (resource.Environment, error) {
    58  		return kube.FakeEnvironment{}, nil
    59  	}
    60  	return s
    61  }
    62  
    63  func TestSuite_Basic(t *testing.T) {
    64  	defer cleanupRT()
    65  	g := NewWithT(t)
    66  
    67  	var runCalled bool
    68  	var runSkipped bool
    69  	var exitCode int
    70  	runFn := func(ctx *suiteContext) int {
    71  		runCalled = true
    72  		runSkipped = ctx.skipped
    73  		return -1
    74  	}
    75  	exitFn := func(code int) {
    76  		exitCode = code
    77  	}
    78  
    79  	s := newTestSuite("tid", runFn, exitFn, defaultSettingsFn)
    80  	s.Run()
    81  
    82  	g.Expect(runCalled).To(BeTrue())
    83  	g.Expect(runSkipped).To(BeFalse())
    84  	g.Expect(exitCode).To(Equal(-1))
    85  }
    86  
    87  func TestSuite_Label_SuiteFilter(t *testing.T) {
    88  	defer cleanupRT()
    89  	g := NewWithT(t)
    90  
    91  	var runSkipped bool
    92  	runFn := func(ctx *suiteContext) int {
    93  		runSkipped = ctx.skipped
    94  		return 0
    95  	}
    96  
    97  	sel, err := label.ParseSelector("-customsetup")
    98  	g.Expect(err).To(BeNil())
    99  	settings := resource.DefaultSettings()
   100  	settings.Selector = sel
   101  
   102  	s := newTestSuite("tid", runFn, defaultExitFn, settingsFn(settings))
   103  	s.Label(label.CustomSetup)
   104  	s.Run()
   105  
   106  	g.Expect(runSkipped).To(BeTrue())
   107  }
   108  
   109  func TestSuite_Label_SuiteAllow(t *testing.T) {
   110  	defer cleanupRT()
   111  	g := NewWithT(t)
   112  
   113  	var runCalled bool
   114  	var runSkipped bool
   115  	runFn := func(ctx *suiteContext) int {
   116  		runCalled = true
   117  		runSkipped = ctx.skipped
   118  		return 0
   119  	}
   120  
   121  	sel, err := label.ParseSelector("+postsubmit")
   122  	g.Expect(err).To(BeNil())
   123  	settings := resource.DefaultSettings()
   124  	settings.Selector = sel
   125  
   126  	s := newTestSuite("tid", runFn, defaultExitFn, settingsFn(settings))
   127  	s.Label(label.CustomSetup)
   128  	s.Run()
   129  
   130  	g.Expect(runCalled).To(BeTrue())
   131  	g.Expect(runSkipped).To(BeFalse())
   132  }
   133  
   134  func TestSuite_RequireMinMaxClusters(t *testing.T) {
   135  	cases := []struct {
   136  		name       string
   137  		min        int
   138  		max        int
   139  		actual     int
   140  		expectSkip bool
   141  	}{
   142  		{
   143  			name:       "min less than zero",
   144  			min:        0,
   145  			max:        100,
   146  			actual:     1,
   147  			expectSkip: false,
   148  		},
   149  		{
   150  			name:       "less than min",
   151  			min:        2,
   152  			max:        100,
   153  			actual:     1,
   154  			expectSkip: true,
   155  		},
   156  		{
   157  			name:       "equal to min",
   158  			min:        1,
   159  			max:        100,
   160  			actual:     1,
   161  			expectSkip: false,
   162  		},
   163  		{
   164  			name:       "greater than min",
   165  			min:        1,
   166  			max:        100,
   167  			actual:     2,
   168  			expectSkip: false,
   169  		},
   170  		{
   171  			name:       "max less than zero",
   172  			min:        1,
   173  			max:        0,
   174  			actual:     1,
   175  			expectSkip: false,
   176  		},
   177  		{
   178  			name:       "greater than max",
   179  			min:        1,
   180  			max:        1,
   181  			actual:     2,
   182  			expectSkip: true,
   183  		},
   184  		{
   185  			name:       "equal to max",
   186  			min:        1,
   187  			max:        2,
   188  			actual:     2,
   189  			expectSkip: false,
   190  		},
   191  		{
   192  			name:       "less than max",
   193  			min:        1,
   194  			max:        2,
   195  			actual:     1,
   196  			expectSkip: false,
   197  		},
   198  	}
   199  
   200  	for _, c := range cases {
   201  		t.Run(c.name, func(t *testing.T) {
   202  			defer cleanupRT()
   203  			g := NewWithT(t)
   204  
   205  			var runCalled bool
   206  			var runSkipped bool
   207  			runFn := func(ctx *suiteContext) int {
   208  				runCalled = true
   209  				runSkipped = ctx.skipped
   210  				return 0
   211  			}
   212  
   213  			// Set the kube config flag.
   214  			kubeConfigs := make([]string, c.actual)
   215  			for i := 0; i < c.actual; i++ {
   216  				kubeConfigs[i] = "~/.kube/config"
   217  			}
   218  
   219  			settings := resource.DefaultSettings()
   220  
   221  			s := newTestSuite("tid", runFn, defaultExitFn, settingsFn(settings))
   222  			s.envFactory = newFakeEnvironmentFactory(c.actual)
   223  			s.RequireMinClusters(c.min)
   224  			s.RequireMaxClusters(c.max)
   225  			s.Run()
   226  
   227  			g.Expect(runCalled).To(BeTrue())
   228  			if c.expectSkip {
   229  				g.Expect(runSkipped).To(BeTrue())
   230  			} else {
   231  				g.Expect(runSkipped).To(BeFalse())
   232  			}
   233  		})
   234  	}
   235  }
   236  
   237  func TestSuite_Setup(t *testing.T) {
   238  	defer cleanupRT()
   239  	g := NewWithT(t)
   240  
   241  	var runCalled bool
   242  	var runSkipped bool
   243  	runFn := func(ctx *suiteContext) int {
   244  		runCalled = true
   245  		runSkipped = ctx.skipped
   246  		return 0
   247  	}
   248  
   249  	s := newTestSuite("tid", runFn, defaultExitFn, defaultSettingsFn)
   250  
   251  	var setupCalled bool
   252  	s.Setup(func(c resource.Context) error {
   253  		setupCalled = true
   254  		return nil
   255  	})
   256  	s.Run()
   257  
   258  	g.Expect(setupCalled).To(BeTrue())
   259  	g.Expect(runCalled).To(BeTrue())
   260  	g.Expect(runSkipped).To(BeFalse())
   261  }
   262  
   263  func TestSuite_SetupFail(t *testing.T) {
   264  	defer cleanupRT()
   265  	g := NewWithT(t)
   266  
   267  	var runCalled bool
   268  	runFn := func(ctx *suiteContext) int {
   269  		runCalled = true
   270  		return 0
   271  	}
   272  
   273  	s := newTestSuite("tid", runFn, defaultExitFn, defaultSettingsFn)
   274  
   275  	var setupCalled bool
   276  	s.Setup(func(c resource.Context) error {
   277  		setupCalled = true
   278  		return fmt.Errorf("can't run this")
   279  	})
   280  	s.Run()
   281  
   282  	g.Expect(setupCalled).To(BeTrue())
   283  	g.Expect(runCalled).To(BeFalse())
   284  }
   285  
   286  func TestSuite_SetupFail_Dump(t *testing.T) {
   287  	defer cleanupRT()
   288  	g := NewWithT(t)
   289  
   290  	var runCalled bool
   291  	runFn := func(_ *suiteContext) int {
   292  		runCalled = true
   293  		return 0
   294  	}
   295  
   296  	settings := resource.DefaultSettings()
   297  	settings.CIMode = true
   298  
   299  	s := newTestSuite("tid", runFn, defaultExitFn, settingsFn(settings))
   300  
   301  	var setupCalled bool
   302  	s.Setup(func(c resource.Context) error {
   303  		setupCalled = true
   304  		return fmt.Errorf("can't run this")
   305  	})
   306  	s.Run()
   307  
   308  	g.Expect(setupCalled).To(BeTrue())
   309  	g.Expect(runCalled).To(BeFalse())
   310  }
   311  
   312  func TestSuite_Cleanup(t *testing.T) {
   313  	t.Run("cleanup", func(t *testing.T) {
   314  		defer cleanupRT()
   315  		g := NewWithT(t)
   316  
   317  		var cleanupCalled bool
   318  		var conditionalCleanupCalled bool
   319  		var waitForRun1 sync.WaitGroup
   320  		waitForRun1.Add(1)
   321  		runFn := func(ctx *suiteContext) int {
   322  			waitForRun1.Done()
   323  			return 0
   324  		}
   325  		settings := resource.DefaultSettings()
   326  		settings.NoCleanup = false
   327  
   328  		s := newTestSuite("tid", runFn, defaultExitFn, settingsFn(settings))
   329  		s.Setup(func(ctx resource.Context) error {
   330  			ctx.Cleanup(func() {
   331  				cleanupCalled = true
   332  			})
   333  			ctx.CleanupConditionally(func() {
   334  				conditionalCleanupCalled = true
   335  			})
   336  			return nil
   337  		})
   338  		s.Run()
   339  		waitForRun1.Wait()
   340  
   341  		g.Expect(cleanupCalled).To(BeTrue())
   342  		g.Expect(conditionalCleanupCalled).To(BeTrue())
   343  	})
   344  	t.Run("nocleanup", func(t *testing.T) {
   345  		defer cleanupRT()
   346  		g := NewWithT(t)
   347  
   348  		var cleanupCalled bool
   349  		var conditionalCleanupCalled bool
   350  		var waitForRun1 sync.WaitGroup
   351  		waitForRun1.Add(1)
   352  		runFn := func(ctx *suiteContext) int {
   353  			waitForRun1.Done()
   354  			return 0
   355  		}
   356  		settings := resource.DefaultSettings()
   357  		settings.NoCleanup = true
   358  
   359  		s := newTestSuite("tid", runFn, defaultExitFn, settingsFn(settings))
   360  		s.Setup(func(ctx resource.Context) error {
   361  			ctx.Cleanup(func() {
   362  				cleanupCalled = true
   363  			})
   364  			ctx.CleanupConditionally(func() {
   365  				conditionalCleanupCalled = true
   366  			})
   367  			return nil
   368  		})
   369  		s.Run()
   370  		waitForRun1.Wait()
   371  
   372  		g.Expect(cleanupCalled).To(BeTrue())
   373  		g.Expect(conditionalCleanupCalled).To(BeFalse())
   374  	})
   375  }
   376  
   377  func TestSuite_DoubleInit_Error(t *testing.T) {
   378  	defer cleanupRT()
   379  	g := NewWithT(t)
   380  
   381  	var waitForRun1 sync.WaitGroup
   382  	waitForRun1.Add(1)
   383  	var waitForTestCompletion sync.WaitGroup
   384  	waitForTestCompletion.Add(1)
   385  	runFn1 := func(ctx *suiteContext) int {
   386  		waitForRun1.Done()
   387  		waitForTestCompletion.Wait()
   388  		return 0
   389  	}
   390  
   391  	runFn2 := func(ctx *suiteContext) int {
   392  		return 0
   393  	}
   394  
   395  	var waitForExit1Call sync.WaitGroup
   396  	waitForExit1Call.Add(1)
   397  	var errCode1 int
   398  	exitFn1 := func(errCode int) {
   399  		errCode1 = errCode
   400  		waitForExit1Call.Done()
   401  	}
   402  
   403  	var exit2Called bool
   404  	var errCode2 int
   405  	exitFn2 := func(errCode int) {
   406  		exit2Called = true
   407  		errCode2 = errCode
   408  	}
   409  
   410  	s := newTestSuite("tid1", runFn1, exitFn1, defaultSettingsFn)
   411  
   412  	s2 := newTestSuite("tid2", runFn2, exitFn2, defaultSettingsFn)
   413  
   414  	go s.Run()
   415  	waitForRun1.Wait()
   416  
   417  	s2.Run()
   418  	waitForTestCompletion.Done()
   419  	waitForExit1Call.Wait()
   420  
   421  	g.Expect(exit2Called).To(Equal(true))
   422  	g.Expect(errCode1).To(Equal(0))
   423  	g.Expect(errCode2).NotTo(Equal(0))
   424  }
   425  
   426  func TestSuite_GetResource(t *testing.T) {
   427  	defer cleanupRT()
   428  
   429  	act := func(refPtr any, trackedResource resource.Resource) error {
   430  		var err error
   431  		runFn := func(ctx *suiteContext) int {
   432  			err = ctx.GetResource(refPtr)
   433  			return 0
   434  		}
   435  		s := newTestSuite("tid", runFn, defaultExitFn, defaultSettingsFn)
   436  		s.Setup(func(c resource.Context) error {
   437  			c.TrackResource(trackedResource)
   438  			return nil
   439  		})
   440  		s.Run()
   441  		return err
   442  	}
   443  
   444  	t.Run("struct reference", func(t *testing.T) {
   445  		g := NewWithT(t)
   446  		var ref *resource.FakeResource
   447  		tracked := &resource.FakeResource{IDValue: "1"}
   448  		// notice that we pass **fakeCluster:
   449  		// GetResource requires *T where T implements resource.Resource.
   450  		// *fakeCluster implements it but fakeCluster does not.
   451  		err := act(&ref, tracked)
   452  		g.Expect(err).To(BeNil())
   453  		g.Expect(tracked).To(Equal(ref))
   454  	})
   455  	t.Run("interface reference", func(t *testing.T) {
   456  		g := NewWithT(t)
   457  		var ref OtherInterface
   458  		tracked := &resource.FakeResource{IDValue: "1"}
   459  		err := act(&ref, tracked)
   460  		g.Expect(err).To(BeNil())
   461  		g.Expect(tracked).To(Equal(ref))
   462  	})
   463  	t.Run("slice reference", func(t *testing.T) {
   464  		g := NewWithT(t)
   465  		existing := &resource.FakeResource{IDValue: "1"}
   466  		tracked := &resource.FakeResource{IDValue: "2"}
   467  		ref := []OtherInterface{existing}
   468  		err := act(&ref, tracked)
   469  		g.Expect(err).To(BeNil())
   470  		g.Expect(ref).To(HaveLen(2))
   471  		g.Expect(existing).To(Equal(ref[0]))
   472  		g.Expect(tracked).To(Equal(ref[1]))
   473  	})
   474  	t.Run("non pointer ref", func(t *testing.T) {
   475  		g := NewWithT(t)
   476  		err := act(resource.FakeResource{}, &resource.FakeResource{})
   477  		g.Expect(err).NotTo(BeNil())
   478  	})
   479  }
   480  
   481  func TestDeriveSuiteName(t *testing.T) {
   482  	cases := []struct {
   483  		caller   string
   484  		expected string
   485  	}{
   486  		{
   487  			caller:   "/home/me/go/src/istio.io/istio/some/path/mytest.go",
   488  			expected: "some_path",
   489  		},
   490  		{
   491  			caller:   "/home/me/go/src/istio.io/istio.io/some/path/mytest.go",
   492  			expected: "some_path",
   493  		},
   494  		{
   495  			caller:   "/home/me/go/src/istio.io/istio/tests/integration/some/path/mytest.go",
   496  			expected: "some_path",
   497  		},
   498  		{
   499  			caller:   "/work/some/path/mytest.go",
   500  			expected: "some_path",
   501  		},
   502  		{
   503  			caller:   "/work/tests/integration/some/path/mytest.go",
   504  			expected: "some_path",
   505  		},
   506  	}
   507  
   508  	for _, c := range cases {
   509  		t.Run(c.caller, func(t *testing.T) {
   510  			g := NewWithT(t)
   511  			actual := deriveSuiteName(c.caller)
   512  			g.Expect(actual).To(Equal(c.expected))
   513  		})
   514  	}
   515  }
   516  
   517  func newFakeEnvironmentFactory(numClusters int) resource.EnvironmentFactory {
   518  	e := kube.FakeEnvironment{NumClusters: numClusters}
   519  	return func(ctx resource.Context) (resource.Environment, error) {
   520  		return e, nil
   521  	}
   522  }
   523  
   524  type OtherInterface interface {
   525  	GetOtherValue() string
   526  }