istio.io/istio@v0.0.0-20240520182934-d79c90f27776/pkg/test/framework/integration/framework_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 integration
    16  
    17  import (
    18  	"fmt"
    19  	"testing"
    20  	"time"
    21  
    22  	"istio.io/istio/pkg/test/framework"
    23  )
    24  
    25  func TestSynchronous(t *testing.T) {
    26  	// Root is always run sync.
    27  	newLevels().
    28  		// Level 1: bf=5(sync)
    29  		Add(5, false).
    30  		// Level 2: bf=2(sync)
    31  		Add(2, false).
    32  		// Level 3: bf=2(sync)
    33  		Add(2, false).
    34  		Build().
    35  		Run(t)
    36  }
    37  
    38  func TestParallel(t *testing.T) {
    39  	// Root is always run sync.
    40  	newLevels().
    41  		// Level 1: bf=20(parallel)
    42  		Add(20, true).
    43  		// Level 2: bf=5(parallel)
    44  		Add(5, true).
    45  		// Level 3: bf=2(parallel)
    46  		Add(2, true).
    47  		Build().
    48  		Run(t)
    49  }
    50  
    51  func TestMix(t *testing.T) {
    52  	// Root is always run sync.
    53  	newLevels().
    54  		// Level 1: bf=20(parallel)
    55  		Add(20, true).
    56  		// Level 2: bf=5(sync)
    57  		Add(5, false).
    58  		// Level 3: bf=2(parallel)
    59  		Add(2, true).
    60  		Build().
    61  		Run(t)
    62  }
    63  
    64  func newLevels() levels {
    65  	return levels{
    66  		{
    67  			name: "root",
    68  		},
    69  	}
    70  }
    71  
    72  type level struct {
    73  	name string
    74  
    75  	// branchFactor indicates how the parent should branch for this level
    76  	// (i.e. how many child tests should be created per parent test in the previous level).
    77  	branchFactor int
    78  
    79  	// runParallel if true, all tests in this level will be run in parallel.
    80  	runParallel bool
    81  }
    82  
    83  func (l level) runParallelString() string {
    84  	if l.runParallel {
    85  		return "parallel"
    86  	}
    87  	return "sync"
    88  }
    89  
    90  type levels []level
    91  
    92  func (ls levels) Add(branchFactor int, runParallel bool) levels {
    93  	return append(ls, level{
    94  		name:         fmt.Sprintf("level%d", len(ls)),
    95  		branchFactor: branchFactor,
    96  		runParallel:  runParallel,
    97  	})
    98  }
    99  
   100  func (ls levels) Build() *test {
   101  	// Create the root test, which will always be run synchronously.
   102  	root := &test{
   103  		name: ls[0].name + "[sync]",
   104  	}
   105  
   106  	testsInPrevLevel := []*test{root}
   107  	var testsInCurrentLevel []*test
   108  	for _, l := range ls[1:] {
   109  		for _, parent := range testsInPrevLevel {
   110  			parent.runChildrenParallel = l.runParallel
   111  			for i := 0; i < l.branchFactor; i++ {
   112  				child := &test{
   113  					name: fmt.Sprintf("%s_%d[%s]", l.name, len(testsInCurrentLevel), l.runParallelString()),
   114  				}
   115  				parent.children = append(parent.children, child)
   116  				testsInCurrentLevel = append(testsInCurrentLevel, child)
   117  			}
   118  		}
   119  
   120  		// Update for next iteration.
   121  		testsInPrevLevel = testsInCurrentLevel
   122  		testsInCurrentLevel = nil
   123  	}
   124  
   125  	return root
   126  }
   127  
   128  type test struct {
   129  	name                string
   130  	c                   *component
   131  	runStart            time.Time
   132  	runEnd              time.Time
   133  	cleanupStart        time.Time
   134  	cleanupEnd          time.Time
   135  	componentCloseStart time.Time
   136  	componentCloseEnd   time.Time
   137  
   138  	runChildrenParallel bool
   139  	children            []*test
   140  }
   141  
   142  func (tst *test) Run(t *testing.T) {
   143  	t.Helper()
   144  	framework.NewTest(t).
   145  		Run(func(t framework.TestContext) {
   146  			t.NewSubTest(tst.name).Run(tst.runInternal)
   147  		})
   148  
   149  	if err := tst.doCheck(); err != nil {
   150  		t.Fatal(err)
   151  	}
   152  }
   153  
   154  func (tst *test) runInternal(t framework.TestContext) {
   155  	tst.runStart = time.Now()
   156  	t.Cleanup(tst.cleanup)
   157  	tst.c = newComponent(t, t.Name(), tst.componentClosed)
   158  
   159  	if len(tst.children) == 0 {
   160  		doWork()
   161  	} else {
   162  		for _, child := range tst.children {
   163  			subTest := t.NewSubTest(child.name)
   164  			if tst.runChildrenParallel {
   165  				subTest.RunParallel(child.runInternal)
   166  			} else {
   167  				subTest.Run(child.runInternal)
   168  			}
   169  		}
   170  	}
   171  	tst.runEnd = time.Now()
   172  }
   173  
   174  func (tst *test) timeRange() timeRange {
   175  	return timeRange{
   176  		start: tst.runStart,
   177  		end:   tst.cleanupEnd,
   178  	}
   179  }
   180  
   181  func (tst *test) doCheck() error {
   182  	// Make sure the component was closed after the test's run method exited.
   183  	if tst.componentCloseStart.Before(tst.runEnd) {
   184  		return fmt.Errorf("test %s: componentCloseStart (%v) occurred before runEnd (%v)",
   185  			tst.name, tst.componentCloseStart, tst.runEnd)
   186  	}
   187  
   188  	// Make sure the component was closed before the cleanup for this test was performed.
   189  	if tst.cleanupStart.Before(tst.componentCloseEnd) {
   190  		return fmt.Errorf("test %s: closeStart (%v) occurred before componentCloseEnd (%v)",
   191  			tst.name, tst.cleanupStart, tst.componentCloseEnd)
   192  	}
   193  
   194  	// Now make sure children cleanup occurred after cleanup for this test.
   195  	for _, child := range tst.children {
   196  		if child.cleanupEnd.After(tst.cleanupStart) {
   197  			return fmt.Errorf("child %s cleanupEnd (%v) occurred after parent %s cleanupStart (%v)",
   198  				child.name, child.cleanupEnd, tst.name, tst.cleanupStart)
   199  		}
   200  	}
   201  
   202  	if !tst.runChildrenParallel {
   203  		// The children were run synchronously. Make sure they don't overlap in time.
   204  		for i := 0; i < len(tst.children); i++ {
   205  			ci := tst.children[i]
   206  			ciRange := ci.timeRange()
   207  			for j := i + 1; j < len(tst.children); j++ {
   208  				cj := tst.children[j]
   209  				cjRange := cj.timeRange()
   210  				if ciRange.overlaps(cjRange) {
   211  					return fmt.Errorf("test %s: child %s[%s] overlaps with child %s[%s]",
   212  						tst.name, ci.name, ciRange, cj.name, cjRange)
   213  				}
   214  			}
   215  		}
   216  	}
   217  
   218  	// Now check the children.
   219  	for _, child := range tst.children {
   220  		if err := child.doCheck(); err != nil {
   221  			return err
   222  		}
   223  	}
   224  	return nil
   225  }
   226  
   227  func (tst *test) componentClosed(*component) {
   228  	tst.componentCloseStart = time.Now()
   229  	doWork()
   230  	tst.componentCloseEnd = time.Now()
   231  }
   232  
   233  func (tst *test) cleanup() {
   234  	tst.cleanupStart = time.Now()
   235  	doWork()
   236  	tst.cleanupEnd = time.Now()
   237  }
   238  
   239  func doWork() {
   240  	time.Sleep(time.Millisecond * 10)
   241  }
   242  
   243  type timeRange struct {
   244  	start, end time.Time
   245  }
   246  
   247  func (r timeRange) overlaps(o timeRange) bool {
   248  	return r.start.Before(o.end) && r.end.After(o.start)
   249  }
   250  
   251  func (r timeRange) String() string {
   252  	return fmt.Sprintf("start=%v, end=%v", r.start, r.end)
   253  }