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 }