github.com/onsi/ginkgo@v1.16.6-0.20211118180735-4e1925ba4c95/ginkgo/internal/test_suite.go (about) 1 package internal 2 3 import ( 4 "errors" 5 "math/rand" 6 "os" 7 "path" 8 "path/filepath" 9 "regexp" 10 "strings" 11 12 "github.com/onsi/ginkgo/types" 13 ) 14 15 const TIMEOUT_ELAPSED_FAILURE_REASON = "Suite did not run because the timeout elapsed" 16 const PRIOR_FAILURES_FAILURE_REASON = "Suite did not run because prior suites failed and --keep-going is not set" 17 const EMPTY_SKIP_FAILURE_REASON = "Suite did not run go test reported that no test files were found" 18 19 type TestSuiteState uint 20 21 const ( 22 TestSuiteStateInvalid TestSuiteState = iota 23 24 TestSuiteStateUncompiled 25 TestSuiteStateCompiled 26 27 TestSuiteStatePassed 28 29 TestSuiteStateSkippedDueToEmptyCompilation 30 TestSuiteStateSkippedByFilter 31 TestSuiteStateSkippedDueToPriorFailures 32 33 TestSuiteStateFailed 34 TestSuiteStateFailedDueToTimeout 35 TestSuiteStateFailedToCompile 36 ) 37 38 var TestSuiteStateFailureStates = []TestSuiteState{TestSuiteStateFailed, TestSuiteStateFailedDueToTimeout, TestSuiteStateFailedToCompile} 39 40 func (state TestSuiteState) Is(states ...TestSuiteState) bool { 41 for _, suiteState := range states { 42 if suiteState == state { 43 return true 44 } 45 } 46 47 return false 48 } 49 50 type TestSuite struct { 51 Path string 52 PackageName string 53 IsGinkgo bool 54 55 Precompiled bool 56 PathToCompiledTest string 57 CompilationError error 58 59 HasProgrammaticFocus bool 60 State TestSuiteState 61 } 62 63 func (ts TestSuite) AbsPath() string { 64 path, _ := filepath.Abs(ts.Path) 65 return path 66 } 67 68 func (ts TestSuite) NamespacedName() string { 69 name := relPath(ts.Path) 70 name = strings.TrimLeft(name, "."+string(filepath.Separator)) 71 name = strings.ReplaceAll(name, string(filepath.Separator), "_") 72 name = strings.ReplaceAll(name, " ", "_") 73 if name == "" { 74 return ts.PackageName 75 } 76 return name 77 } 78 79 type TestSuites []TestSuite 80 81 func (ts TestSuites) AnyHaveProgrammaticFocus() bool { 82 for _, suite := range ts { 83 if suite.HasProgrammaticFocus { 84 return true 85 } 86 } 87 88 return false 89 } 90 91 func (ts TestSuites) ThatAreGinkgoSuites() TestSuites { 92 out := TestSuites{} 93 for _, suite := range ts { 94 if suite.IsGinkgo { 95 out = append(out, suite) 96 } 97 } 98 return out 99 } 100 101 func (ts TestSuites) CountWithState(states ...TestSuiteState) int { 102 n := 0 103 for _, suite := range ts { 104 if suite.State.Is(states...) { 105 n += 1 106 } 107 } 108 109 return n 110 } 111 112 func (ts TestSuites) WithState(states ...TestSuiteState) TestSuites { 113 out := TestSuites{} 114 for _, suite := range ts { 115 if suite.State.Is(states...) { 116 out = append(out, suite) 117 } 118 } 119 120 return out 121 } 122 123 func (ts TestSuites) WithoutState(states ...TestSuiteState) TestSuites { 124 out := TestSuites{} 125 for _, suite := range ts { 126 if !suite.State.Is(states...) { 127 out = append(out, suite) 128 } 129 } 130 131 return out 132 } 133 134 func (ts TestSuites) ShuffledCopy(seed int64) TestSuites { 135 out := make(TestSuites, len(ts)) 136 permutation := rand.New(rand.NewSource(seed)).Perm(len(ts)) 137 for i, j := range permutation { 138 out[i] = ts[j] 139 } 140 return out 141 } 142 143 func FindSuites(args []string, cliConfig types.CLIConfig, allowPrecompiled bool) TestSuites { 144 suites := TestSuites{} 145 146 if len(args) > 0 { 147 for _, arg := range args { 148 if allowPrecompiled { 149 suite, err := precompiledTestSuite(arg) 150 if err == nil { 151 suites = append(suites, suite) 152 continue 153 } 154 } 155 recurseForSuite := cliConfig.Recurse 156 if strings.HasSuffix(arg, "/...") && arg != "/..." { 157 arg = arg[:len(arg)-4] 158 recurseForSuite = true 159 } 160 suites = append(suites, suitesInDir(arg, recurseForSuite)...) 161 } 162 } else { 163 suites = suitesInDir(".", cliConfig.Recurse) 164 } 165 166 if cliConfig.SkipPackage != "" { 167 skipFilters := strings.Split(cliConfig.SkipPackage, ",") 168 for idx := range suites { 169 for _, skipFilter := range skipFilters { 170 if strings.Contains(suites[idx].Path, skipFilter) { 171 suites[idx].State = TestSuiteStateSkippedByFilter 172 break 173 } 174 } 175 } 176 } 177 178 return suites 179 } 180 181 func precompiledTestSuite(path string) (TestSuite, error) { 182 info, err := os.Stat(path) 183 if err != nil { 184 return TestSuite{}, err 185 } 186 187 if info.IsDir() { 188 return TestSuite{}, errors.New("this is a directory, not a file") 189 } 190 191 if filepath.Ext(path) != ".test" && filepath.Ext(path) != ".exe" { 192 return TestSuite{}, errors.New("this is not a .test binary") 193 } 194 195 if filepath.Ext(path) == ".test" && info.Mode()&0111 == 0 { 196 return TestSuite{}, errors.New("this is not executable") 197 } 198 199 dir := relPath(filepath.Dir(path)) 200 packageName := strings.TrimSuffix(filepath.Base(path), ".exe") 201 packageName = strings.TrimSuffix(packageName, ".test") 202 203 path, err = filepath.Abs(path) 204 if err != nil { 205 return TestSuite{}, err 206 } 207 208 return TestSuite{ 209 Path: dir, 210 PackageName: packageName, 211 IsGinkgo: true, 212 Precompiled: true, 213 PathToCompiledTest: path, 214 State: TestSuiteStateCompiled, 215 }, nil 216 } 217 218 func suitesInDir(dir string, recurse bool) TestSuites { 219 suites := TestSuites{} 220 221 if path.Base(dir) == "vendor" { 222 return suites 223 } 224 225 files, _ := os.ReadDir(dir) 226 re := regexp.MustCompile(`^[^._].*_test\.go$`) 227 for _, file := range files { 228 if !file.IsDir() && re.Match([]byte(file.Name())) { 229 suite := TestSuite{ 230 Path: relPath(dir), 231 PackageName: packageNameForSuite(dir), 232 IsGinkgo: filesHaveGinkgoSuite(dir, files), 233 State: TestSuiteStateUncompiled, 234 } 235 suites = append(suites, suite) 236 break 237 } 238 } 239 240 if recurse { 241 re = regexp.MustCompile(`^[._]`) 242 for _, file := range files { 243 if file.IsDir() && !re.Match([]byte(file.Name())) { 244 suites = append(suites, suitesInDir(dir+"/"+file.Name(), recurse)...) 245 } 246 } 247 } 248 249 return suites 250 } 251 252 func relPath(dir string) string { 253 dir, _ = filepath.Abs(dir) 254 cwd, _ := os.Getwd() 255 dir, _ = filepath.Rel(cwd, filepath.Clean(dir)) 256 257 if string(dir[0]) != "." { 258 dir = "." + string(filepath.Separator) + dir 259 } 260 261 return dir 262 } 263 264 func packageNameForSuite(dir string) string { 265 path, _ := filepath.Abs(dir) 266 return filepath.Base(path) 267 } 268 269 func filesHaveGinkgoSuite(dir string, files []os.DirEntry) bool { 270 reTestFile := regexp.MustCompile(`_test\.go$`) 271 reGinkgo := regexp.MustCompile(`package ginkgo|\/ginkgo"`) 272 273 for _, file := range files { 274 if !file.IsDir() && reTestFile.Match([]byte(file.Name())) { 275 contents, _ := os.ReadFile(dir + "/" + file.Name()) 276 if reGinkgo.Match(contents) { 277 return true 278 } 279 } 280 } 281 282 return false 283 }