github.com/onsi/ginkgo@v1.16.6-0.20211118180735-4e1925ba4c95/ginkgo/internal/run.go (about) 1 package internal 2 3 import ( 4 "bytes" 5 "fmt" 6 "io" 7 "os" 8 "os/exec" 9 "regexp" 10 "strings" 11 "syscall" 12 "time" 13 14 "github.com/onsi/ginkgo/formatter" 15 "github.com/onsi/ginkgo/ginkgo/command" 16 "github.com/onsi/ginkgo/internal/parallel_support" 17 "github.com/onsi/ginkgo/reporters" 18 "github.com/onsi/ginkgo/types" 19 ) 20 21 func RunCompiledSuite(suite TestSuite, ginkgoConfig types.SuiteConfig, reporterConfig types.ReporterConfig, cliConfig types.CLIConfig, goFlagsConfig types.GoFlagsConfig, additionalArgs []string) TestSuite { 22 suite.State = TestSuiteStateFailed 23 suite.HasProgrammaticFocus = false 24 25 if suite.PathToCompiledTest == "" { 26 return suite 27 } 28 29 if suite.IsGinkgo && cliConfig.ComputedProcs() > 1 { 30 suite = runParallel(suite, ginkgoConfig, reporterConfig, cliConfig, goFlagsConfig, additionalArgs) 31 } else if suite.IsGinkgo { 32 suite = runSerial(suite, ginkgoConfig, reporterConfig, cliConfig, goFlagsConfig, additionalArgs) 33 } else { 34 suite = runGoTest(suite, cliConfig, goFlagsConfig) 35 } 36 runAfterRunHook(cliConfig.AfterRunHook, reporterConfig.NoColor, suite) 37 return suite 38 } 39 40 func buildAndStartCommand(suite TestSuite, args []string, pipeToStdout bool) (*exec.Cmd, *bytes.Buffer) { 41 buf := &bytes.Buffer{} 42 cmd := exec.Command(suite.PathToCompiledTest, args...) 43 cmd.Dir = suite.Path 44 if pipeToStdout { 45 cmd.Stderr = io.MultiWriter(os.Stdout, buf) 46 cmd.Stdout = os.Stdout 47 } else { 48 cmd.Stderr = buf 49 cmd.Stdout = buf 50 } 51 err := cmd.Start() 52 command.AbortIfError("Failed to start test suite", err) 53 54 return cmd, buf 55 } 56 57 func checkForNoTestsWarning(buf *bytes.Buffer) bool { 58 if strings.Contains(buf.String(), "warning: no tests to run") { 59 fmt.Fprintf(os.Stderr, `Found no test suites, did you forget to run "ginkgo bootstrap"?`) 60 return true 61 } 62 return false 63 } 64 65 func runGoTest(suite TestSuite, cliConfig types.CLIConfig, goFlagsConfig types.GoFlagsConfig) TestSuite { 66 args, err := types.GenerateGoTestRunArgs(goFlagsConfig) 67 command.AbortIfError("Failed to generate test run arguments", err) 68 cmd, buf := buildAndStartCommand(suite, args, true) 69 70 cmd.Wait() 71 72 exitStatus := cmd.ProcessState.Sys().(syscall.WaitStatus).ExitStatus() 73 passed := (exitStatus == 0) || (exitStatus == types.GINKGO_FOCUS_EXIT_CODE) 74 passed = !(checkForNoTestsWarning(buf) && cliConfig.RequireSuite) && passed 75 if passed { 76 suite.State = TestSuiteStatePassed 77 } else { 78 suite.State = TestSuiteStateFailed 79 } 80 81 return suite 82 } 83 84 func runSerial(suite TestSuite, ginkgoConfig types.SuiteConfig, reporterConfig types.ReporterConfig, cliConfig types.CLIConfig, goFlagsConfig types.GoFlagsConfig, additionalArgs []string) TestSuite { 85 if goFlagsConfig.Cover { 86 goFlagsConfig.CoverProfile = AbsPathForGeneratedAsset(goFlagsConfig.CoverProfile, suite, cliConfig, 0) 87 } 88 if goFlagsConfig.BlockProfile != "" { 89 goFlagsConfig.BlockProfile = AbsPathForGeneratedAsset(goFlagsConfig.BlockProfile, suite, cliConfig, 0) 90 } 91 if goFlagsConfig.CPUProfile != "" { 92 goFlagsConfig.CPUProfile = AbsPathForGeneratedAsset(goFlagsConfig.CPUProfile, suite, cliConfig, 0) 93 } 94 if goFlagsConfig.MemProfile != "" { 95 goFlagsConfig.MemProfile = AbsPathForGeneratedAsset(goFlagsConfig.MemProfile, suite, cliConfig, 0) 96 } 97 if goFlagsConfig.MutexProfile != "" { 98 goFlagsConfig.MutexProfile = AbsPathForGeneratedAsset(goFlagsConfig.MutexProfile, suite, cliConfig, 0) 99 } 100 if reporterConfig.JSONReport != "" { 101 reporterConfig.JSONReport = AbsPathForGeneratedAsset(reporterConfig.JSONReport, suite, cliConfig, 0) 102 } 103 if reporterConfig.JUnitReport != "" { 104 reporterConfig.JUnitReport = AbsPathForGeneratedAsset(reporterConfig.JUnitReport, suite, cliConfig, 0) 105 } 106 if reporterConfig.TeamcityReport != "" { 107 reporterConfig.TeamcityReport = AbsPathForGeneratedAsset(reporterConfig.TeamcityReport, suite, cliConfig, 0) 108 } 109 110 args, err := types.GenerateGinkgoTestRunArgs(ginkgoConfig, reporterConfig, goFlagsConfig) 111 command.AbortIfError("Failed to generate test run arguments", err) 112 args = append([]string{"--test.timeout=0"}, args...) 113 args = append(args, additionalArgs...) 114 115 cmd, buf := buildAndStartCommand(suite, args, true) 116 117 cmd.Wait() 118 119 exitStatus := cmd.ProcessState.Sys().(syscall.WaitStatus).ExitStatus() 120 suite.HasProgrammaticFocus = (exitStatus == types.GINKGO_FOCUS_EXIT_CODE) 121 passed := (exitStatus == 0) || (exitStatus == types.GINKGO_FOCUS_EXIT_CODE) 122 passed = !(checkForNoTestsWarning(buf) && cliConfig.RequireSuite) && passed 123 if passed { 124 suite.State = TestSuiteStatePassed 125 } else { 126 suite.State = TestSuiteStateFailed 127 } 128 129 return suite 130 } 131 132 func runParallel(suite TestSuite, ginkgoConfig types.SuiteConfig, reporterConfig types.ReporterConfig, cliConfig types.CLIConfig, goFlagsConfig types.GoFlagsConfig, additionalArgs []string) TestSuite { 133 type procResult struct { 134 passed bool 135 hasProgrammaticFocus bool 136 } 137 138 numProcs := cliConfig.ComputedProcs() 139 procOutput := make([]*bytes.Buffer, numProcs) 140 coverProfiles := []string{} 141 142 blockProfiles := []string{} 143 cpuProfiles := []string{} 144 memProfiles := []string{} 145 mutexProfiles := []string{} 146 147 procResults := make(chan procResult) 148 149 server, err := parallel_support.NewServer(numProcs, reporters.NewDefaultReporter(reporterConfig, formatter.ColorableStdOut)) 150 command.AbortIfError("Failed to start parallel spec server", err) 151 server.Start() 152 defer server.Close() 153 154 if reporterConfig.JSONReport != "" { 155 reporterConfig.JSONReport = AbsPathForGeneratedAsset(reporterConfig.JSONReport, suite, cliConfig, 0) 156 } 157 if reporterConfig.JUnitReport != "" { 158 reporterConfig.JUnitReport = AbsPathForGeneratedAsset(reporterConfig.JUnitReport, suite, cliConfig, 0) 159 } 160 if reporterConfig.TeamcityReport != "" { 161 reporterConfig.TeamcityReport = AbsPathForGeneratedAsset(reporterConfig.TeamcityReport, suite, cliConfig, 0) 162 } 163 164 for proc := 1; proc <= numProcs; proc++ { 165 procGinkgoConfig := ginkgoConfig 166 procGinkgoConfig.ParallelProcess, procGinkgoConfig.ParallelTotal, procGinkgoConfig.ParallelHost = proc, numProcs, server.Address() 167 168 procGoFlagsConfig := goFlagsConfig 169 if goFlagsConfig.Cover { 170 procGoFlagsConfig.CoverProfile = AbsPathForGeneratedAsset(goFlagsConfig.CoverProfile, suite, cliConfig, proc) 171 coverProfiles = append(coverProfiles, procGoFlagsConfig.CoverProfile) 172 } 173 if goFlagsConfig.BlockProfile != "" { 174 procGoFlagsConfig.BlockProfile = AbsPathForGeneratedAsset(goFlagsConfig.BlockProfile, suite, cliConfig, proc) 175 blockProfiles = append(blockProfiles, procGoFlagsConfig.BlockProfile) 176 } 177 if goFlagsConfig.CPUProfile != "" { 178 procGoFlagsConfig.CPUProfile = AbsPathForGeneratedAsset(goFlagsConfig.CPUProfile, suite, cliConfig, proc) 179 cpuProfiles = append(cpuProfiles, procGoFlagsConfig.CPUProfile) 180 } 181 if goFlagsConfig.MemProfile != "" { 182 procGoFlagsConfig.MemProfile = AbsPathForGeneratedAsset(goFlagsConfig.MemProfile, suite, cliConfig, proc) 183 memProfiles = append(memProfiles, procGoFlagsConfig.MemProfile) 184 } 185 if goFlagsConfig.MutexProfile != "" { 186 procGoFlagsConfig.MutexProfile = AbsPathForGeneratedAsset(goFlagsConfig.MutexProfile, suite, cliConfig, proc) 187 mutexProfiles = append(mutexProfiles, procGoFlagsConfig.MutexProfile) 188 } 189 190 args, err := types.GenerateGinkgoTestRunArgs(procGinkgoConfig, reporterConfig, procGoFlagsConfig) 191 command.AbortIfError("Failed to generate test run argumnets", err) 192 args = append([]string{"--test.timeout=0"}, args...) 193 args = append(args, additionalArgs...) 194 195 cmd, buf := buildAndStartCommand(suite, args, false) 196 procOutput[proc-1] = buf 197 server.RegisterAlive(proc, func() bool { return cmd.ProcessState == nil || !cmd.ProcessState.Exited() }) 198 199 go func() { 200 cmd.Wait() 201 exitStatus := cmd.ProcessState.Sys().(syscall.WaitStatus).ExitStatus() 202 procResults <- procResult{ 203 passed: (exitStatus == 0) || (exitStatus == types.GINKGO_FOCUS_EXIT_CODE), 204 hasProgrammaticFocus: exitStatus == types.GINKGO_FOCUS_EXIT_CODE, 205 } 206 }() 207 } 208 209 passed := true 210 for proc := 1; proc <= cliConfig.ComputedProcs(); proc++ { 211 result := <-procResults 212 passed = passed && result.passed 213 suite.HasProgrammaticFocus = suite.HasProgrammaticFocus || result.hasProgrammaticFocus 214 } 215 if passed { 216 suite.State = TestSuiteStatePassed 217 } else { 218 suite.State = TestSuiteStateFailed 219 } 220 221 select { 222 case <-server.GetSuiteDone(): 223 fmt.Println("") 224 case <-time.After(time.Second): 225 //the serve never got back to us. Something must have gone wrong. 226 fmt.Fprintln(os.Stderr, "** Ginkgo timed out waiting for all parallel procs to report back. **") 227 fmt.Fprintf(os.Stderr, "%s (%s)\n", suite.PackageName, suite.Path) 228 for proc := 1; proc <= cliConfig.ComputedProcs(); proc++ { 229 fmt.Fprintf(os.Stderr, "Output from proc %d:\n", proc) 230 fmt.Fprintln(os.Stderr, formatter.Fi(1, "%s", procOutput[proc-1].String())) 231 } 232 fmt.Fprintf(os.Stderr, "** End **") 233 } 234 235 for proc := 1; proc <= cliConfig.ComputedProcs(); proc++ { 236 output := procOutput[proc-1].String() 237 if proc == 1 && checkForNoTestsWarning(procOutput[0]) && cliConfig.RequireSuite { 238 suite.State = TestSuiteStateFailed 239 } 240 if strings.Contains(output, "deprecated Ginkgo functionality") { 241 fmt.Fprintln(os.Stderr, output) 242 } 243 } 244 245 if len(coverProfiles) > 0 { 246 coverProfile := AbsPathForGeneratedAsset(goFlagsConfig.CoverProfile, suite, cliConfig, 0) 247 err := MergeAndCleanupCoverProfiles(coverProfiles, coverProfile) 248 command.AbortIfError("Failed to combine cover profiles", err) 249 250 coverage, err := GetCoverageFromCoverProfile(coverProfile) 251 command.AbortIfError("Failed to compute coverage", err) 252 if coverage == 0 { 253 fmt.Fprintln(os.Stdout, "coverage: [no statements]") 254 } else { 255 fmt.Fprintf(os.Stdout, "coverage: %.1f%% of statements\n", coverage) 256 } 257 } 258 if len(blockProfiles) > 0 { 259 blockProfile := AbsPathForGeneratedAsset(goFlagsConfig.BlockProfile, suite, cliConfig, 0) 260 err := MergeProfiles(blockProfiles, blockProfile) 261 command.AbortIfError("Failed to combine blockprofiles", err) 262 } 263 if len(cpuProfiles) > 0 { 264 cpuProfile := AbsPathForGeneratedAsset(goFlagsConfig.CPUProfile, suite, cliConfig, 0) 265 err := MergeProfiles(cpuProfiles, cpuProfile) 266 command.AbortIfError("Failed to combine cpuprofiles", err) 267 } 268 if len(memProfiles) > 0 { 269 memProfile := AbsPathForGeneratedAsset(goFlagsConfig.MemProfile, suite, cliConfig, 0) 270 err := MergeProfiles(memProfiles, memProfile) 271 command.AbortIfError("Failed to combine memprofiles", err) 272 } 273 if len(mutexProfiles) > 0 { 274 mutexProfile := AbsPathForGeneratedAsset(goFlagsConfig.MutexProfile, suite, cliConfig, 0) 275 err := MergeProfiles(mutexProfiles, mutexProfile) 276 command.AbortIfError("Failed to combine mutexprofiles", err) 277 } 278 279 return suite 280 } 281 282 func runAfterRunHook(command string, noColor bool, suite TestSuite) { 283 if command == "" { 284 return 285 } 286 f := formatter.NewWithNoColorBool(noColor) 287 288 // Allow for string replacement to pass input to the command 289 passed := "[FAIL]" 290 if suite.State.Is(TestSuiteStatePassed) { 291 passed = "[PASS]" 292 } 293 command = strings.Replace(command, "(ginkgo-suite-passed)", passed, -1) 294 command = strings.Replace(command, "(ginkgo-suite-name)", suite.PackageName, -1) 295 296 // Must break command into parts 297 splitArgs := regexp.MustCompile(`'.+'|".+"|\S+`) 298 parts := splitArgs.FindAllString(command, -1) 299 300 output, err := exec.Command(parts[0], parts[1:]...).CombinedOutput() 301 if err != nil { 302 fmt.Fprintln(formatter.ColorableStdOut, f.Fi(0, "{{red}}{{bold}}After-run-hook failed:{{/}}")) 303 fmt.Fprintln(formatter.ColorableStdOut, f.Fi(1, "{{red}}%s{{/}}", output)) 304 } else { 305 fmt.Fprintln(formatter.ColorableStdOut, f.Fi(0, "{{green}}{{bold}}After-run-hook succeeded:{{/}}")) 306 fmt.Fprintln(formatter.ColorableStdOut, f.Fi(1, "{{green}}%s{{/}}", output)) 307 } 308 }