github.com/getgauge/gauge@v1.6.9/execution/execute.go (about) 1 /*---------------------------------------------------------------- 2 * Copyright (c) ThoughtWorks, Inc. 3 * Licensed under the Apache License, Version 2.0 4 * See LICENSE in the project root for license information. 5 *----------------------------------------------------------------*/ 6 7 /* 8 Package execution handles gauge's execution of spec/scenario/steps 9 10 Execution can be of two types 11 - Simple execution 12 - Paralell execution 13 14 Execution Flow : 15 - Checks for updates 16 - Validation 17 - Init Registry 18 - Saving Execution result 19 20 Strategy 21 - Lazy : Lazy is a parallelization strategy for execution. In this case tests assignment will be dynamic during execution, i.e. assign the next spec in line to the stream that has completed it’s previous execution and is waiting for more work. 22 - Eager : Eager is a parallelization strategy for execution. In this case tests are distributed before execution, thus making them an equal number based distribution. 23 */ 24 package execution 25 26 import ( 27 "strconv" 28 "time" 29 30 "github.com/getgauge/gauge/skel" 31 32 "fmt" 33 34 "strings" 35 36 "os" 37 38 "sync" 39 40 "encoding/json" 41 "path/filepath" 42 43 "github.com/getgauge/common" 44 "github.com/getgauge/gauge/config" 45 "github.com/getgauge/gauge/env" 46 "github.com/getgauge/gauge/execution/event" 47 "github.com/getgauge/gauge/execution/rerun" 48 "github.com/getgauge/gauge/execution/result" 49 "github.com/getgauge/gauge/gauge" 50 "github.com/getgauge/gauge/logger" 51 "github.com/getgauge/gauge/plugin/install" 52 "github.com/getgauge/gauge/reporter" 53 "github.com/getgauge/gauge/validation" 54 ) 55 56 const ( 57 executionStatusFile = "executionStatus.json" 58 ) 59 60 // Count of iterations 61 var MaxRetriesCount int 62 63 // Tags to filter specs/scenarios to retry 64 var RetryOnlyTags string 65 66 // NumberOfExecutionStreams shows the number of execution streams, in parallel execution. 67 var NumberOfExecutionStreams int 68 69 // InParallel if true executes the specs in parallel else in serial. 70 var InParallel bool 71 72 var TagsToFilterForParallelRun string 73 74 // Verbose if true prints additional details about the execution 75 var Verbose bool 76 77 // MachineReadable indicates that the output is in json format 78 var MachineReadable bool 79 80 var ExecutionArgs []*gauge.ExecutionArg 81 82 type suiteExecutor interface { 83 run() *result.SuiteResult 84 } 85 86 type executor interface { 87 execute(i gauge.Item, r result.Result) 88 } 89 90 // ExecuteSpecs : Check for updates, validates the specs (by invoking the respective language runners), initiates the registry which is needed for console reporting, execution API and Rerunning of specs 91 // and finally saves the execution result as binary in .gauge folder. 92 var ExecuteSpecs = func(specDirs []string) int { 93 err := validateFlags() 94 if err != nil { 95 logger.Fatal(true, err.Error()) 96 } 97 if config.CheckUpdates() { 98 i := &install.UpdateFacade{} 99 i.BufferUpdateDetails() 100 defer i.PrintUpdateBuffer() 101 } 102 skel.SetupPlugins(MachineReadable) 103 err = os.Setenv(gaugeParallelStreamCountEnv, strconv.Itoa(NumberOfExecutionStreams)) 104 if err != nil { 105 logger.Fatalf(true, "failed to set env %s. %s", gaugeParallelStreamCountEnv, err.Error()) 106 } 107 108 res := validation.ValidateSpecs(specDirs, false) 109 if len(res.Errs) > 0 { 110 if res.ParseOk { 111 return ParseFailed 112 } 113 return ValidationFailed 114 } 115 if res.SpecCollection.Size() < 1 { 116 logger.Infof(true, "No specifications found in %s.", strings.Join(specDirs, ", ")) 117 err := res.Runner.Kill() 118 if err != nil { 119 logger.Errorf(false, "unable to kill runner: %s", err.Error()) 120 } 121 if res.ParseOk { 122 return Success 123 } 124 return ExecutionFailed 125 } 126 event.InitRegistry() 127 wg := &sync.WaitGroup{} 128 reporter.ListenExecutionEvents(wg) 129 rerun.ListenFailedScenarios(wg, specDirs) 130 if env.SaveExecutionResult() { 131 ListenSuiteEndAndSaveResult(wg) 132 } 133 defer wg.Wait() 134 ei := newExecutionInfo(res.SpecCollection, res.Runner, nil, res.ErrMap, InParallel, 0) 135 136 e := ei.getExecutor() 137 logger.Debug(true, "Run started") 138 return printExecutionResult(e.run(), res.ParseOk) 139 } 140 141 func writeExecutionResult(content string) { 142 executionStatusFile := filepath.Join(config.ProjectRoot, common.DotGauge, executionStatusFile) 143 dotGaugeDir := filepath.Join(config.ProjectRoot, common.DotGauge) 144 if err := os.MkdirAll(dotGaugeDir, common.NewDirectoryPermissions); err != nil { 145 logger.Fatalf(true, "Failed to create directory in %s. Reason: %s", dotGaugeDir, err.Error()) 146 } 147 err := os.WriteFile(executionStatusFile, []byte(content), common.NewFilePermissions) 148 if err != nil { 149 logger.Fatalf(true, "Failed to write to %s. Reason: %s", executionStatusFile, err.Error()) 150 } 151 } 152 153 // ReadLastExecutionResult returns the result of previous execution in JSON format 154 // This is stored in $GAUGE_PROJECT_ROOT/.gauge/executionStatus.json file after every execution 155 func ReadLastExecutionResult() (interface{}, error) { 156 contents, err := common.ReadFileContents(filepath.Join(config.ProjectRoot, common.DotGauge, executionStatusFile)) 157 if err != nil { 158 logger.Fatalf(true, "Failed to read execution status information. Reason: %s", err.Error()) 159 } 160 meta := &executionStatus{} 161 if err = json.Unmarshal([]byte(contents), meta); err != nil { 162 logger.Fatalf(true, "Invalid execution status information. Reason: %s", err.Error()) 163 return meta, err 164 } 165 return meta, nil 166 } 167 168 func printExecutionResult(suiteResult *result.SuiteResult, isParsingOk bool) int { 169 nSkippedSpecs := suiteResult.SpecsSkippedCount 170 var nExecutedSpecs int 171 if len(suiteResult.SpecResults) != 0 { 172 nExecutedSpecs = len(suiteResult.SpecResults) - nSkippedSpecs 173 } 174 nFailedSpecs := suiteResult.SpecsFailedCount 175 nPassedSpecs := nExecutedSpecs - nFailedSpecs 176 177 nExecutedScenarios := 0 178 nFailedScenarios := 0 179 nPassedScenarios := 0 180 nSkippedScenarios := 0 181 for _, specResult := range suiteResult.SpecResults { 182 nExecutedScenarios += specResult.ScenarioCount 183 nFailedScenarios += specResult.ScenarioFailedCount 184 nSkippedScenarios += specResult.ScenarioSkippedCount 185 } 186 nExecutedScenarios -= nSkippedScenarios 187 nPassedScenarios = nExecutedScenarios - nFailedScenarios 188 if nExecutedScenarios < 0 { 189 nExecutedScenarios = 0 190 } 191 192 if nPassedScenarios < 0 { 193 nPassedScenarios = 0 194 } 195 196 s := statusJSON(nExecutedSpecs, nPassedSpecs, nFailedSpecs, nSkippedSpecs, nExecutedScenarios, nPassedScenarios, nFailedScenarios, nSkippedScenarios) 197 logger.Infof(true, "Specifications:\t%d executed\t%d passed\t%d failed\t%d skipped", nExecutedSpecs, nPassedSpecs, nFailedSpecs, nSkippedSpecs) 198 logger.Infof(true, "Scenarios:\t%d executed\t%d passed\t%d failed\t%d skipped", nExecutedScenarios, nPassedScenarios, nFailedScenarios, nSkippedScenarios) 199 logger.Infof(true, "\nTotal time taken: %s", time.Millisecond*time.Duration(suiteResult.ExecutionTime)) 200 writeExecutionResult(s) 201 202 if !isParsingOk { 203 return ParseFailed 204 } 205 if suiteResult.IsFailed { 206 return ExecutionFailed 207 } 208 return Success 209 } 210 211 func validateFlags() error { 212 if MaxRetriesCount < 1 { 213 return fmt.Errorf("invalid input(%s) to --max-retries-count flag", strconv.Itoa(MaxRetriesCount)) 214 } 215 if !InParallel { 216 return nil 217 } 218 if NumberOfExecutionStreams < 1 { 219 return fmt.Errorf("invalid input(%s) to --n flag", strconv.Itoa(NumberOfExecutionStreams)) 220 } 221 if !isValidStrategy(Strategy) { 222 return fmt.Errorf("invalid input(%s) to --strategy flag", Strategy) 223 } 224 return nil 225 }