github.com/anth0d/nomad@v0.0.0-20221214183521-ae3a0a2cad06/e2e/framework/framework.go (about) 1 package framework 2 3 import ( 4 "flag" 5 "fmt" 6 "log" 7 "reflect" 8 "strings" 9 "testing" 10 11 "github.com/hashicorp/nomad/ci" 12 "github.com/stretchr/testify/require" 13 ) 14 15 const frameworkHelp = ` 16 Usage: go test -v ./e2e [options] 17 18 These flags are coarse overrides for the test environment. 19 20 -forceRun skip all environment checks when filtering test suites 21 -local denotes execution is against a local environment 22 -slow include execution of slow test suites 23 -suite run specified test suite 24 -showHelp shows this help text 25 26 TestSuites can request Constraints on the Framework.Environment so that tests 27 are only run in the appropriate conditions. These environment flags provide 28 the information for those constraints. 29 30 -env=string name of the environment 31 -env.arch=string cpu architecture of the targets 32 -env.os=string operating system of the targets 33 -env.provider=string cloud provider of the environment 34 -env.tags=string comma delimited list of tags for the environment 35 36 ` 37 38 var fHelp = flag.Bool("showHelp", false, "print the help screen") 39 var fLocal = flag.Bool("local", false, 40 "denotes execution is against a local environment") 41 var fSlow = flag.Bool("slow", false, "toggles execution of slow test suites") 42 var fForceRun = flag.Bool("forceRun", false, 43 "if set, skips all environment checks when filtering test suites") 44 var fSuite = flag.String("suite", "", "run specified test suite") 45 46 // Environment flags 47 // TODO: 48 // It would be nice if we could match the target environment against 49 // the tests automatically so that we always avoid running tests that 50 // don't apply, and then have these flags override that behavior. 51 var fEnv = flag.String("env", "", "name of the environment executing against") 52 var fProvider = flag.String("env.provider", "", 53 "cloud provider for which environment is executing against") 54 var fOS = flag.String("env.os", "", 55 "operating system for which the environment is executing against") 56 var fArch = flag.String("env.arch", "", 57 "cpu architecture for which the environment is executing against") 58 var fTags = flag.String("env.tags", "", 59 "comma delimited list of tags associated with the environment") 60 61 type Framework struct { 62 suites []*TestSuite 63 provisioner Provisioner 64 env Environment 65 66 isLocalRun bool 67 slow bool 68 force bool 69 suite string 70 } 71 72 // Environment contains information about the test target environment, used 73 // to constrain the set of tests run. See the environment flags above. 74 type Environment struct { 75 Name string 76 Provider string 77 OS string 78 Arch string 79 Tags map[string]struct{} 80 } 81 82 // New creates a Framework 83 func New() *Framework { 84 flag.Parse() 85 if *fHelp { 86 log.Fatal(frameworkHelp) 87 } 88 env := Environment{ 89 Name: *fEnv, 90 Provider: *fProvider, 91 OS: *fOS, 92 Arch: *fArch, 93 Tags: map[string]struct{}{}, 94 } 95 for _, tag := range strings.Split(*fTags, ",") { 96 env.Tags[tag] = struct{}{} 97 } 98 return &Framework{ 99 provisioner: DefaultProvisioner, 100 env: env, 101 isLocalRun: *fLocal, 102 slow: *fSlow, 103 force: *fForceRun, 104 suite: *fSuite, 105 } 106 } 107 108 // AddSuites adds a set of test suites to a Framework 109 func (f *Framework) AddSuites(s ...*TestSuite) *Framework { 110 f.suites = append(f.suites, s...) 111 return f 112 } 113 114 var pkgSuites []*TestSuite 115 116 // AddSuites adds a set of test suites to the package scoped Framework 117 func AddSuites(s ...*TestSuite) { 118 pkgSuites = append(pkgSuites, s...) 119 } 120 121 // Run starts the test framework, running each TestSuite 122 func (f *Framework) Run(t *testing.T) { 123 info, err := f.provisioner.SetupTestRun(t, SetupOptions{}) 124 if err != nil { 125 require.NoError(t, err, "could not provision cluster") 126 } 127 defer f.provisioner.TearDownTestRun(t, info.ID) 128 129 for _, s := range f.suites { 130 t.Run(s.Component, func(t *testing.T) { 131 skip, err := f.runSuite(t, s) 132 if skip { 133 t.Skipf("skipping suite '%s': %v", s.Component, err) 134 return 135 } 136 if err != nil { 137 t.Errorf("error starting suite '%s': %v", s.Component, err) 138 } 139 }) 140 } 141 } 142 143 // Run starts the package scoped Framework, running each TestSuite 144 func Run(t *testing.T) { 145 f := New() 146 f.AddSuites(pkgSuites...) 147 f.Run(t) 148 } 149 150 // runSuite is called from Framework.Run inside of a sub test for each TestSuite. 151 // If skip is returned as true, the test suite is skipped with the error text added 152 // to the Skip reason 153 // If skip is false and an error is returned, the test suite is failed. 154 func (f *Framework) runSuite(t *testing.T, s *TestSuite) (skip bool, err error) { 155 156 // If -forceRun is set, skip all constraint checks 157 if !f.force { 158 // If this is a local run, check that the suite supports running locally 159 if !s.CanRunLocal && f.isLocalRun { 160 return true, fmt.Errorf("local run detected and suite cannot run locally") 161 } 162 163 // Check that constraints are met 164 if err := s.Constraints.matches(f.env); err != nil { 165 return true, fmt.Errorf("constraint failed: %v", err) 166 } 167 168 // Check the slow toggle and if the suite's slow flag is that same 169 if f.slow != s.Slow { 170 return true, fmt.Errorf("framework slow suite configuration is %v but suite is %v", f.slow, s.Slow) 171 } 172 } 173 174 // If -suite is set, skip any suite that is not the one specified. 175 if f.suite != "" && f.suite != s.Component { 176 return true, fmt.Errorf("only running suite %q", f.suite) 177 } 178 179 info, err := f.provisioner.SetupTestSuite(t, SetupOptions{ 180 Name: s.Component, 181 ExpectConsul: s.Consul, 182 ExpectVault: s.Vault, 183 }) 184 require.NoError(t, err, "could not provision cluster") 185 defer f.provisioner.TearDownTestSuite(t, info.ID) 186 187 for _, c := range s.Cases { 188 f.runCase(t, s, c) 189 } 190 191 return false, nil 192 } 193 194 func (f *Framework) runCase(t *testing.T, s *TestSuite, c TestCase) { 195 196 // The test name is set to the name of the implementing type, including package 197 name := fmt.Sprintf("%T", c) 198 199 // The ClusterInfo handle should be used by each TestCase to isolate 200 // job/task state created during the test. 201 info, err := f.provisioner.SetupTestCase(t, SetupOptions{ 202 Name: name, 203 ExpectConsul: s.Consul, 204 ExpectVault: s.Vault, 205 }) 206 if err != nil { 207 t.Errorf("could not provision cluster for case: %v", err) 208 } 209 defer f.provisioner.TearDownTestCase(t, info.ID) 210 c.setClusterInfo(info) 211 212 // Each TestCase runs as a subtest of the TestSuite 213 t.Run(c.Name(), func(t *testing.T) { 214 // If the TestSuite has Parallel set, all cases run in parallel 215 if s.Parallel { 216 ci.Parallel(t) 217 } 218 219 f := newF(t) 220 221 // Check if the case includes a before all function 222 if beforeAllTests, ok := c.(BeforeAllTests); ok { 223 beforeAllTests.BeforeAll(f) 224 } 225 226 // Check if the case includes an after all function at the end 227 defer func() { 228 if afterAllTests, ok := c.(AfterAllTests); ok { 229 afterAllTests.AfterAll(f) 230 } 231 }() 232 233 // Here we need to iterate through the methods of the case to find 234 // ones that are test functions 235 reflectC := reflect.TypeOf(c) 236 for i := 0; i < reflectC.NumMethod(); i++ { 237 method := reflectC.Method(i) 238 if ok := isTestMethod(method.Name); !ok { 239 continue 240 } 241 // Each test is run as its own sub test of the case 242 // Test cases are never parallel 243 t.Run(method.Name, func(t *testing.T) { 244 245 cF := newFFromParent(f, t) 246 if BeforeEachTest, ok := c.(BeforeEachTest); ok { 247 BeforeEachTest.BeforeEach(cF) 248 } 249 defer func() { 250 if afterEachTest, ok := c.(AfterEachTest); ok { 251 afterEachTest.AfterEach(cF) 252 } 253 }() 254 255 //Call the method 256 method.Func.Call([]reflect.Value{reflect.ValueOf(c), reflect.ValueOf(cF)}) 257 }) 258 } 259 }) 260 } 261 262 func isTestMethod(m string) bool { 263 return strings.HasPrefix(m, "Test") 264 }