github.com/getgauge/gauge@v1.6.9/execution/scenarioExecutor.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  package execution
     8  
     9  import (
    10  	"fmt"
    11  
    12  	"errors"
    13  
    14  	"github.com/getgauge/gauge-proto/go/gauge_messages"
    15  	"github.com/getgauge/gauge/execution/event"
    16  	"github.com/getgauge/gauge/execution/result"
    17  	"github.com/getgauge/gauge/gauge"
    18  	"github.com/getgauge/gauge/logger"
    19  	"github.com/getgauge/gauge/plugin"
    20  	"github.com/getgauge/gauge/runner"
    21  	"github.com/getgauge/gauge/validation"
    22  )
    23  
    24  type scenarioExecutor struct {
    25  	runner               runner.Runner
    26  	pluginHandler        plugin.Handler
    27  	currentExecutionInfo *gauge_messages.ExecutionInfo
    28  	errMap               *gauge.BuildErrors
    29  	stream               int
    30  	contexts             []*gauge.Step
    31  	teardowns            []*gauge.Step
    32  }
    33  
    34  func newScenarioExecutor(r runner.Runner, ph plugin.Handler, ei *gauge_messages.ExecutionInfo, errMap *gauge.BuildErrors, contexts []*gauge.Step, teardowns []*gauge.Step, stream int) *scenarioExecutor {
    35  	return &scenarioExecutor{
    36  		runner:               r,
    37  		pluginHandler:        ph,
    38  		currentExecutionInfo: ei,
    39  		errMap:               errMap,
    40  		stream:               stream,
    41  		contexts:             contexts,
    42  		teardowns:            teardowns,
    43  	}
    44  }
    45  
    46  func (e *scenarioExecutor) execute(i gauge.Item, r result.Result) {
    47  	scenario := i.(*gauge.Scenario)
    48  	scenarioResult := r.(*result.ScenarioResult)
    49  	scenarioResult.ProtoScenario.ExecutionStatus = gauge_messages.ExecutionStatus_PASSED
    50  	if e.runner.Info().Killed {
    51  		e.errMap.ScenarioErrs[scenario] = append([]error{errors.New("skipped Reason: Runner is not alive")}, e.errMap.ScenarioErrs[scenario]...)
    52  		setSkipInfoInResult(scenarioResult, scenario, e.errMap)
    53  		return
    54  	}
    55  	if scenario.SpecDataTableRow.IsInitialized() && !shouldExecuteForRow(scenario.SpecDataTableRowIndex) {
    56  		e.errMap.ScenarioErrs[scenario] = append([]error{errors.New("skipped Reason: Doesn't satisfy --table-rows flag condition")}, e.errMap.ScenarioErrs[scenario]...)
    57  		setSkipInfoInResult(scenarioResult, scenario, e.errMap)
    58  		return
    59  	}
    60  	if _, ok := e.errMap.ScenarioErrs[scenario]; ok {
    61  		setSkipInfoInResult(scenarioResult, scenario, e.errMap)
    62  		event.Notify(event.NewExecutionEvent(event.ScenarioStart, scenario, scenarioResult, e.stream, e.currentExecutionInfo))
    63  		event.Notify(event.NewExecutionEvent(event.ScenarioEnd, scenario, scenarioResult, e.stream, e.currentExecutionInfo))
    64  		return
    65  	}
    66  	event.Notify(event.NewExecutionEvent(event.ScenarioStart, scenario, scenarioResult, e.stream, e.currentExecutionInfo))
    67  	defer event.Notify(event.NewExecutionEvent(event.ScenarioEnd, scenario, scenarioResult, e.stream, e.currentExecutionInfo))
    68  
    69  	res := e.initScenarioDataStore()
    70  	if res.GetFailed() {
    71  		e.handleScenarioDataStoreFailure(scenarioResult, scenario, fmt.Errorf("Failed to initialize scenario datastore. Error: %s", res.GetErrorMessage()))
    72  		return
    73  	}
    74  	e.notifyBeforeScenarioHook(scenarioResult)
    75  
    76  	if !(scenarioResult.GetFailed() || scenarioResult.GetSkippedScenario()) {
    77  		protoContexts := scenarioResult.ProtoScenario.GetContexts()
    78  		protoScenItems := scenarioResult.ProtoScenario.GetScenarioItems()
    79  		// context and steps are not appended together since sometime it cause the issue and the steps in step list and proto step list differs.
    80  		// This is done to fix https://github.com/getgauge/gauge/issues/1629
    81  		if e.executeSteps(e.contexts, protoContexts, scenarioResult) {
    82  			if !scenarioResult.GetSkippedScenario() {
    83  				e.executeSteps(scenario.Steps, protoScenItems, scenarioResult)
    84  			}
    85  		}
    86  		// teardowns are not appended to previous call to executeSteps to ensure they are run irrespective of context/step failure
    87  		e.executeSteps(e.teardowns, scenarioResult.ProtoScenario.GetTearDownSteps(), scenarioResult)
    88  	}
    89  
    90  	if scenarioResult.GetSkippedScenario() {
    91  		e.skippedScenarioUpdateErrMap(i, r)
    92  		setSkipInfoInResult(scenarioResult, scenario, e.errMap)
    93  	}
    94  
    95  	e.notifyAfterScenarioHook(scenarioResult)
    96  	scenarioResult.UpdateExecutionTime()
    97  }
    98  
    99  func (e *scenarioExecutor) initScenarioDataStore() *gauge_messages.ProtoExecutionResult {
   100  	msg := &gauge_messages.Message{MessageType: gauge_messages.Message_ScenarioDataStoreInit,
   101  		ScenarioDataStoreInitRequest: &gauge_messages.ScenarioDataStoreInitRequest{Stream: int32(e.stream)}}
   102  	return e.runner.ExecuteAndGetStatus(msg)
   103  }
   104  
   105  func (e *scenarioExecutor) handleScenarioDataStoreFailure(scenarioResult *result.ScenarioResult, scenario *gauge.Scenario, err error) {
   106  	logger.Error(true, err.Error())
   107  	validationError := validation.NewStepValidationError(&gauge.Step{LineNo: scenario.Heading.LineNo, LineText: scenario.Heading.Value},
   108  		err.Error(), e.currentExecutionInfo.CurrentSpec.GetFileName(), nil, "")
   109  	e.errMap.ScenarioErrs[scenario] = []error{validationError}
   110  	setSkipInfoInResult(scenarioResult, scenario, e.errMap)
   111  }
   112  
   113  func setSkipInfoInResult(scenarioResult *result.ScenarioResult, scenario *gauge.Scenario, errMap *gauge.BuildErrors) {
   114  	scenarioResult.ProtoScenario.ExecutionStatus = gauge_messages.ExecutionStatus_SKIPPED
   115  	var errs []string
   116  	for _, err := range errMap.ScenarioErrs[scenario] {
   117  		errs = append(errs, err.Error())
   118  	}
   119  	scenarioResult.ProtoScenario.SkipErrors = errs
   120  }
   121  
   122  func (e *scenarioExecutor) notifyBeforeScenarioHook(scenarioResult *result.ScenarioResult) {
   123  	message := &gauge_messages.Message{MessageType: gauge_messages.Message_ScenarioExecutionStarting,
   124  		ScenarioExecutionStartingRequest: &gauge_messages.ScenarioExecutionStartingRequest{CurrentExecutionInfo: e.currentExecutionInfo, Stream: int32(e.stream)}}
   125  	e.pluginHandler.NotifyPlugins(message)
   126  	res := executeHook(message, scenarioResult, e.runner)
   127  	scenarioResult.ProtoScenario.PreHookMessages = res.Message
   128  	scenarioResult.ProtoScenario.PreHookScreenshotFiles = res.ScreenshotFiles
   129  	if res.GetFailed() {
   130  		setScenarioFailure(e.currentExecutionInfo)
   131  		handleHookFailure(scenarioResult, res, result.AddPreHook)
   132  	}
   133  	if res.GetSkipScenario() {
   134  		scenarioResult.SetSkippedScenario()
   135  		scenarioResult.ProtoScenario.PreHookMessages = []string{res.ErrorMessage}
   136  	}
   137  	message.ScenarioExecutionStartingRequest.ScenarioResult = gauge.ConvertToProtoScenarioResult(scenarioResult)
   138  	e.pluginHandler.NotifyPlugins(message)
   139  }
   140  
   141  func (e *scenarioExecutor) notifyAfterScenarioHook(scenarioResult *result.ScenarioResult) {
   142  	message := &gauge_messages.Message{MessageType: gauge_messages.Message_ScenarioExecutionEnding,
   143  		ScenarioExecutionEndingRequest: &gauge_messages.ScenarioExecutionEndingRequest{CurrentExecutionInfo: e.currentExecutionInfo, Stream: int32(e.stream)}}
   144  	res := executeHook(message, scenarioResult, e.runner)
   145  	scenarioResult.ProtoScenario.PostHookMessages = res.Message
   146  	scenarioResult.ProtoScenario.PostHookScreenshotFiles = res.ScreenshotFiles
   147  	if res.GetFailed() {
   148  		setScenarioFailure(e.currentExecutionInfo)
   149  		handleHookFailure(scenarioResult, res, result.AddPostHook)
   150  	}
   151  	message.ScenarioExecutionEndingRequest.ScenarioResult = gauge.ConvertToProtoScenarioResult(scenarioResult)
   152  	e.pluginHandler.NotifyPlugins(message)
   153  }
   154  
   155  func (e *scenarioExecutor) notifyBeforeConceptHook(conceptResult *result.ScenarioResult) *gauge_messages.ProtoExecutionResult {
   156  	message := &gauge_messages.Message{MessageType: gauge_messages.Message_ConceptExecutionStarting,
   157  		ConceptExecutionStartingRequest: &gauge_messages.ConceptExecutionStartingRequest{CurrentExecutionInfo: e.currentExecutionInfo, Stream: int32(e.stream)}}
   158  	var res *gauge_messages.ProtoExecutionResult = nil
   159  	if e.runner.Info().ConceptMessages {
   160  		res = e.runner.ExecuteAndGetStatus(message)
   161  		conceptResult.ProtoScenario.PostHookMessages = res.Message
   162  		conceptResult.ProtoScenario.PostHookScreenshotFiles = res.ScreenshotFiles
   163  		if res.GetFailed() {
   164  			setScenarioFailure(e.currentExecutionInfo)
   165  			handleHookFailure(conceptResult, res, result.AddPreHook)
   166  		}
   167  	}
   168  	e.notifyBeforeConcept(conceptResult)
   169  	return res
   170  }
   171  
   172  func (e *scenarioExecutor) notifyAfterConceptHook(conceptResult *result.ScenarioResult) *gauge_messages.ProtoExecutionResult {
   173  	message := &gauge_messages.Message{MessageType: gauge_messages.Message_ConceptExecutionEnding,
   174  		ConceptExecutionEndingRequest: &gauge_messages.ConceptExecutionEndingRequest{CurrentExecutionInfo: e.currentExecutionInfo, Stream: int32(e.stream)}}
   175  	var res *gauge_messages.ProtoExecutionResult = nil
   176  	if e.runner.Info().ConceptMessages {
   177  		res = e.runner.ExecuteAndGetStatus(message)
   178  		conceptResult.ProtoScenario.PostHookMessages = res.Message
   179  		conceptResult.ProtoScenario.PostHookScreenshotFiles = res.ScreenshotFiles
   180  		if res.GetFailed() {
   181  			setScenarioFailure(e.currentExecutionInfo)
   182  			handleHookFailure(conceptResult, res, result.AddPostHook)
   183  		}
   184  	}
   185  	e.notifyAfterConcept(conceptResult)
   186  	return res
   187  }
   188  
   189  func (e *scenarioExecutor) notifyBeforeConcept(conceptResult *result.ScenarioResult) {
   190  	message := &gauge_messages.Message{MessageType: gauge_messages.Message_ConceptExecutionStarting,
   191  		ConceptExecutionStartingRequest: &gauge_messages.ConceptExecutionStartingRequest{CurrentExecutionInfo: e.currentExecutionInfo, Stream: int32(e.stream)}}
   192  	e.pluginHandler.NotifyPlugins(message)
   193  }
   194  
   195  func (e *scenarioExecutor) notifyAfterConcept(conceptResult *result.ScenarioResult) {
   196  	message := &gauge_messages.Message{MessageType: gauge_messages.Message_ConceptExecutionEnding,
   197  		ConceptExecutionEndingRequest: &gauge_messages.ConceptExecutionEndingRequest{CurrentExecutionInfo: e.currentExecutionInfo, Stream: int32(e.stream)}}
   198  	e.pluginHandler.NotifyPlugins(message)
   199  }
   200  
   201  func (e *scenarioExecutor) createStepRequest(protoStep *gauge_messages.ProtoStep) *gauge_messages.ExecuteStepRequest {
   202  	stepRequest := &gauge_messages.ExecuteStepRequest{ParsedStepText: protoStep.GetParsedText(), ActualStepText: protoStep.GetActualText(), Stream: int32(e.stream)}
   203  	stepRequest.Parameters = getParameters(protoStep.GetFragments())
   204  	return stepRequest
   205  }
   206  
   207  func (e *scenarioExecutor) executeSteps(steps []*gauge.Step, protoItems []*gauge_messages.ProtoItem, scenarioResult *result.ScenarioResult) bool {
   208  	var stepsIndex int
   209  	for _, protoItem := range protoItems {
   210  		if protoItem.GetItemType() == gauge_messages.ProtoItem_Concept || protoItem.GetItemType() == gauge_messages.ProtoItem_Step {
   211  			failed, recoverable := e.executeStep(steps[stepsIndex], protoItem, scenarioResult)
   212  			stepsIndex++
   213  			if failed {
   214  				scenarioResult.SetFailure()
   215  				if !recoverable {
   216  					return false
   217  				}
   218  			}
   219  			if scenarioResult.GetSkippedScenario() {
   220  				// The step execution resulted in SkipScenario.
   221  				// The rest of steps execution is skipped
   222  				break
   223  			}
   224  		}
   225  	}
   226  	return true
   227  }
   228  
   229  func (e *scenarioExecutor) executeStep(step *gauge.Step, protoItem *gauge_messages.ProtoItem, scenarioResult *result.ScenarioResult) (bool, bool) {
   230  	var failed, recoverable bool
   231  	if protoItem.GetItemType() == gauge_messages.ProtoItem_Concept {
   232  		protoConcept := protoItem.GetConcept()
   233  		res := e.executeConcept(step, protoConcept, scenarioResult)
   234  		failed = res.GetFailed()
   235  		recoverable = res.GetRecoverable()
   236  
   237  	} else if protoItem.GetItemType() == gauge_messages.ProtoItem_Step {
   238  		se := &stepExecutor{runner: e.runner, pluginHandler: e.pluginHandler, currentExecutionInfo: e.currentExecutionInfo, stream: e.stream}
   239  		res := se.executeStep(step, protoItem.GetStep())
   240  		protoItem.GetStep().StepExecutionResult = res.ProtoStepExecResult()
   241  		if res.ProtoStepExecResult().ExecutionResult.GetSkipScenario() {
   242  			scenarioResult.SetSkippedScenario()
   243  		}
   244  		failed = res.GetFailed()
   245  		recoverable = res.ProtoStepExecResult().GetExecutionResult().GetRecoverableError()
   246  	}
   247  	return failed, recoverable
   248  }
   249  
   250  func (e *scenarioExecutor) executeConcept(item *gauge.Step, protoConcept *gauge_messages.ProtoConcept, scenarioResult *result.ScenarioResult) *result.ConceptResult {
   251  	cptResult := result.NewConceptResult(protoConcept)
   252  
   253  	// Add the Concept step data to the Execution info that is sent to plugins
   254  	stepRequest := e.createStepRequest(protoConcept.ConceptStep)
   255  	e.currentExecutionInfo.CurrentStep = &gauge_messages.StepInfo{Step: stepRequest, IsFailed: false}
   256  	event.Notify(event.NewExecutionEvent(event.ConceptStart, item, nil, e.stream, e.currentExecutionInfo))
   257  	if e.notifyBeforeConceptHook(scenarioResult).GetFailed() {
   258  		scenarioResult.SetFailure()
   259  		cptResult.UpdateConceptExecResult()
   260  		event.Notify(event.NewExecutionEvent(event.ConceptEnd, nil, cptResult, e.stream, e.currentExecutionInfo))
   261  		e.notifyAfterConcept(scenarioResult)
   262  		return cptResult
   263  	}
   264  
   265  	var conceptStepIndex int
   266  	for _, protoStep := range protoConcept.Steps {
   267  		if protoStep.GetItemType() == gauge_messages.ProtoItem_Concept || protoStep.GetItemType() == gauge_messages.ProtoItem_Step {
   268  			failed, recoverable := e.executeStep(item.ConceptSteps[conceptStepIndex], protoStep, scenarioResult)
   269  			conceptStepIndex++
   270  			if failed {
   271  				scenarioResult.SetFailure()
   272  				cptResult.UpdateConceptExecResult()
   273  				if recoverable {
   274  					continue
   275  				}
   276  				// The concept is finishing after a step failure
   277  				// Restore the Concept step data in the Execution info that is sent to plugins
   278  				e.currentExecutionInfo.CurrentStep = &gauge_messages.StepInfo{Step: stepRequest, IsFailed: false}
   279  				event.Notify(event.NewExecutionEvent(event.ConceptEnd, nil, cptResult, e.stream, e.currentExecutionInfo))
   280  				e.notifyAfterConcept(scenarioResult)
   281  
   282  				return cptResult
   283  			}
   284  			if scenarioResult.GetSkippedScenario() {
   285  				// The step execution resulted in SkipScenario.
   286  				// The rest of steps execution is skipped
   287  				break
   288  			}
   289  		}
   290  	}
   291  	cptResult.UpdateConceptExecResult()
   292  
   293  	// Restore the Concept step to the Execution info that is sent to plugins
   294  	e.currentExecutionInfo.CurrentStep = &gauge_messages.StepInfo{Step: stepRequest, IsFailed: false}
   295  	event.Notify(event.NewExecutionEvent(event.ConceptEnd, nil, cptResult, e.stream, e.currentExecutionInfo))
   296  	if e.notifyAfterConceptHook(scenarioResult).GetFailed() {
   297  		scenarioResult.SetFailure()
   298  		cptResult.UpdateConceptExecResult()
   299  	}
   300  
   301  	return cptResult
   302  }
   303  
   304  func setStepFailure(executionInfo *gauge_messages.ExecutionInfo) {
   305  	setScenarioFailure(executionInfo)
   306  	executionInfo.CurrentStep.IsFailed = true
   307  }
   308  
   309  func getParameters(fragments []*gauge_messages.Fragment) []*gauge_messages.Parameter {
   310  	var parameters []*gauge_messages.Parameter
   311  	for _, fragment := range fragments {
   312  		if fragment.GetFragmentType() == gauge_messages.Fragment_Parameter {
   313  			parameters = append(parameters, fragment.GetParameter())
   314  		}
   315  	}
   316  	return parameters
   317  }
   318  
   319  func setScenarioFailure(executionInfo *gauge_messages.ExecutionInfo) {
   320  	setSpecFailure(executionInfo)
   321  	executionInfo.CurrentScenario.IsFailed = true
   322  }
   323  
   324  func (e *scenarioExecutor) skippedScenarioUpdateErrMap(i gauge.Item, r result.Result) {
   325  	scenario := i.(*gauge.Scenario)
   326  	scenarioResult := r.(*result.ScenarioResult)
   327  	if len(scenarioResult.ProtoScenario.PreHookMessages) > 0 {
   328  		e.errMap.ScenarioErrs[scenario] = append([]error{errors.New(scenarioResult.ProtoScenario.PreHookMessages[0])}, e.errMap.ScenarioErrs[scenario]...)
   329  		scenarioResult.ProtoScenario.SkipErrors = scenarioResult.ProtoScenario.PreHookMessages
   330  	} else {
   331  		e.errMap.ScenarioErrs[scenario] = append([]error{errors.New(e.currentExecutionInfo.CurrentStep.ErrorMessage)}, e.errMap.ScenarioErrs[scenario]...)
   332  		scenarioResult.ProtoScenario.SkipErrors = []string{e.currentExecutionInfo.CurrentStep.ErrorMessage}
   333  	}
   334  }