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 }