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 }