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  }