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  }