github.com/getgauge/gauge@v1.6.9/execution/specExecutor.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 "path/filepath" 12 "strconv" 13 "strings" 14 15 "github.com/getgauge/gauge-proto/go/gauge_messages" 16 "github.com/getgauge/gauge/config" 17 "github.com/getgauge/gauge/execution/event" 18 "github.com/getgauge/gauge/execution/result" 19 "github.com/getgauge/gauge/filter" 20 "github.com/getgauge/gauge/gauge" 21 "github.com/getgauge/gauge/logger" 22 "github.com/getgauge/gauge/parser" 23 "github.com/getgauge/gauge/plugin" 24 "github.com/getgauge/gauge/runner" 25 "github.com/getgauge/gauge/validation" 26 ) 27 28 type specExecutor struct { 29 specification *gauge.Specification 30 runner runner.Runner 31 pluginHandler plugin.Handler 32 currentExecutionInfo *gauge_messages.ExecutionInfo 33 specResult *result.SpecResult 34 errMap *gauge.BuildErrors 35 stream int 36 scenarioExecutor executor 37 } 38 39 func newSpecExecutor(s *gauge.Specification, r runner.Runner, ph plugin.Handler, e *gauge.BuildErrors, stream int) *specExecutor { 40 ei := &gauge_messages.ExecutionInfo{ 41 CurrentSpec: &gauge_messages.SpecInfo{ 42 Name: s.Heading.Value, 43 FileName: s.FileName, 44 IsFailed: false, 45 Tags: getTagValue(s.Tags)}, 46 ProjectName: filepath.Base(config.ProjectRoot), 47 NumberOfExecutionStreams: int32(NumberOfExecutionStreams), 48 RunnerId: int32(stream), 49 ExecutionArgs: gauge.ConvertToProtoExecutionArg(ExecutionArgs), 50 } 51 52 return &specExecutor{ 53 specification: s, 54 runner: r, 55 pluginHandler: ph, 56 errMap: e, 57 stream: stream, 58 currentExecutionInfo: ei, 59 scenarioExecutor: newScenarioExecutor(r, ph, ei, e, s.Contexts, s.TearDownSteps, stream), 60 } 61 } 62 63 func (e *specExecutor) execute(executeBefore, execute, executeAfter bool) *result.SpecResult { 64 e.specResult = gauge.NewSpecResult(e.specification) 65 if e.runner.Info().Killed { 66 e.specResult.SetSkipped(true) 67 return e.specResult 68 } 69 if errs, ok := e.errMap.SpecErrs[e.specification]; ok { 70 if hasParseError(errs) { 71 e.failSpec() 72 return e.specResult 73 } 74 } 75 lookup, err := e.dataTableLookup() 76 if err != nil { 77 logger.Fatalf(true, "Failed to resolve Specifications : %s", err.Error()) 78 } 79 resolvedSpecItems, err := resolveItems(e.specification.GetSpecItems(), lookup, e.setSkipInfo) 80 if err != nil { 81 logger.Fatalf(true, "Failed to resolve Specifications : %s", err.Error()) 82 } 83 e.specResult.AddSpecItems(resolvedSpecItems) 84 if executeBefore { 85 event.Notify(event.NewExecutionEvent(event.SpecStart, e.specification, e.specResult, e.stream, e.currentExecutionInfo)) 86 if _, ok := e.errMap.SpecErrs[e.specification]; !ok { 87 if res := e.initSpecDataStore(); res.GetFailed() { 88 e.skipSpecForError(fmt.Errorf("Failed to initialize spec datastore. Error: %s", res.GetErrorMessage())) 89 } else { 90 e.notifyBeforeSpecHook() 91 } 92 } else { 93 e.specResult.SetSkipped(true) 94 e.specResult.Errors = e.convertErrors(e.errMap.SpecErrs[e.specification]) 95 } 96 } 97 if execute && !e.specResult.GetFailed() { 98 if e.specification.DataTable.Table.GetRowCount() == 0 { 99 others, tableDriven := parser.FilterTableRelatedScenarios(e.specification.Scenarios, func(s *gauge.Scenario) bool { 100 return s.ScenarioDataTableRow.IsInitialized() 101 }) 102 results, err := e.executeScenarios(others) 103 if err != nil { 104 logger.Fatalf(true, "Failed to resolve Specifications : %s", err.Error()) 105 } 106 e.specResult.AddScenarioResults(results) 107 scnMap := make(map[int]bool) 108 for _, s := range tableDriven { 109 if _, ok := scnMap[s.Span.Start]; !ok { 110 scnMap[s.Span.Start] = true 111 } 112 r, err := e.executeScenario(s) 113 if err != nil { 114 logger.Fatalf(true, "Failed to resolve Specifications : %s", err.Error()) 115 } 116 e.specResult.AddTableDrivenScenarioResult(r, gauge.ConvertToProtoTable(s.DataTable.Table), 117 s.ScenarioDataTableRowIndex, s.SpecDataTableRowIndex, s.SpecDataTableRow.IsInitialized()) 118 } 119 e.specResult.ScenarioCount += len(scnMap) 120 } else { 121 err := e.executeSpec() 122 if err != nil { 123 logger.Fatalf(true, "Failed to execute Specification %s : %s", e.specification.Heading.Value, err.Error()) 124 } 125 } 126 } 127 e.specResult.SetSkipped(e.specResult.Skipped || e.specResult.ScenarioSkippedCount == len(e.specification.Scenarios)) 128 if executeAfter { 129 if _, ok := e.errMap.SpecErrs[e.specification]; !ok { 130 e.notifyAfterSpecHook() 131 } 132 event.Notify(event.NewExecutionEvent(event.SpecEnd, e.specification, e.specResult, e.stream, e.currentExecutionInfo)) 133 } 134 return e.specResult 135 } 136 137 func (e *specExecutor) executeTableRelatedScenarios(scenarios []*gauge.Scenario) error { 138 if len(scenarios) > 0 { 139 index := e.specification.Scenarios[0].SpecDataTableRowIndex 140 sceRes, err := e.executeScenarios(scenarios) 141 if err != nil { 142 return err 143 } 144 specResult := [][]result.Result{sceRes} 145 e.specResult.AddTableRelatedScenarioResult(specResult, index) 146 } 147 return nil 148 } 149 150 func (e *specExecutor) executeSpec() error { 151 parser.GetResolvedDataTablerows(e.specification.DataTable.Table) 152 nonTableRelatedScenarios, tableRelatedScenarios := parser.FilterTableRelatedScenarios(e.specification.Scenarios, func(s *gauge.Scenario) bool { 153 return s.SpecDataTableRow.IsInitialized() 154 }) 155 res, err := e.executeScenarios(nonTableRelatedScenarios) 156 if err != nil { 157 return err 158 } 159 e.specResult.AddScenarioResults(res) 160 err = e.executeTableRelatedScenarios(tableRelatedScenarios) 161 if err != nil { 162 return err 163 } 164 return nil 165 } 166 167 func (e *specExecutor) initSpecDataStore() *gauge_messages.ProtoExecutionResult { 168 initSpecDataStoreMessage := &gauge_messages.Message{MessageType: gauge_messages.Message_SpecDataStoreInit, 169 SpecDataStoreInitRequest: &gauge_messages.SpecDataStoreInitRequest{Stream: int32(e.stream)}} 170 return e.runner.ExecuteAndGetStatus(initSpecDataStoreMessage) 171 } 172 173 func (e *specExecutor) notifyBeforeSpecHook() { 174 m := &gauge_messages.Message{MessageType: gauge_messages.Message_SpecExecutionStarting, 175 SpecExecutionStartingRequest: &gauge_messages.SpecExecutionStartingRequest{CurrentExecutionInfo: e.currentExecutionInfo, Stream: int32(e.stream)}} 176 e.pluginHandler.NotifyPlugins(m) 177 res := executeHook(m, e.specResult, e.runner) 178 e.specResult.ProtoSpec.PreHookMessages = res.Message 179 e.specResult.ProtoSpec.PreHookScreenshotFiles = res.ScreenshotFiles 180 if res.GetFailed() { 181 setSpecFailure(e.currentExecutionInfo) 182 handleHookFailure(e.specResult, res, result.AddPreHook) 183 } 184 m.SpecExecutionStartingRequest.SpecResult = gauge.ConvertToProtoSpecResult(e.specResult) 185 e.pluginHandler.NotifyPlugins(m) 186 } 187 188 func (e *specExecutor) notifyAfterSpecHook() { 189 e.currentExecutionInfo.CurrentScenario = nil 190 m := &gauge_messages.Message{MessageType: gauge_messages.Message_SpecExecutionEnding, 191 SpecExecutionEndingRequest: &gauge_messages.SpecExecutionEndingRequest{CurrentExecutionInfo: e.currentExecutionInfo, Stream: int32(e.stream)}} 192 res := executeHook(m, e.specResult, e.runner) 193 e.specResult.ProtoSpec.PostHookMessages = res.Message 194 e.specResult.ProtoSpec.PostHookScreenshotFiles = res.ScreenshotFiles 195 if res.GetFailed() { 196 setSpecFailure(e.currentExecutionInfo) 197 handleHookFailure(e.specResult, res, result.AddPostHook) 198 } 199 m.SpecExecutionEndingRequest.SpecResult = gauge.ConvertToProtoSpecResult(e.specResult) 200 e.pluginHandler.NotifyPlugins(m) 201 } 202 203 func (e *specExecutor) skipSpecForError(err error) { 204 logger.Error(true, err.Error()) 205 validationError := validation.NewStepValidationError(&gauge.Step{LineNo: e.specification.Heading.LineNo, LineText: e.specification.Heading.Value}, 206 err.Error(), e.specification.FileName, nil, "") 207 for _, scenario := range e.specification.Scenarios { 208 e.errMap.ScenarioErrs[scenario] = []error{validationError} 209 } 210 e.errMap.SpecErrs[e.specification] = []error{validationError} 211 e.specResult.Errors = e.convertErrors(e.errMap.SpecErrs[e.specification]) 212 e.specResult.SetSkipped(true) 213 } 214 215 func (e *specExecutor) failSpec() { 216 e.specResult.Errors = e.convertErrors(e.errMap.SpecErrs[e.specification]) 217 e.specResult.SetFailure() 218 } 219 220 func (e *specExecutor) convertErrors(specErrors []error) []*gauge_messages.Error { 221 var errors []*gauge_messages.Error 222 for _, e := range specErrors { 223 switch err := e.(type) { 224 case parser.ParseError: 225 errors = append(errors, &gauge_messages.Error{ 226 Message: err.Error(), 227 LineNumber: int32(err.LineNo), 228 Filename: err.FileName, 229 Type: gauge_messages.Error_PARSE_ERROR, 230 }) 231 case validation.StepValidationError, validation.SpecValidationError: 232 errors = append(errors, &gauge_messages.Error{ 233 Message: e.Error(), 234 Type: gauge_messages.Error_VALIDATION_ERROR, 235 }) 236 } 237 } 238 return errors 239 } 240 241 func (e *specExecutor) setSkipInfo(protoStep *gauge_messages.ProtoStep, step *gauge.Step) { 242 protoStep.StepExecutionResult = &gauge_messages.ProtoStepExecutionResult{} 243 protoStep.StepExecutionResult.Skipped = false 244 if _, ok := e.errMap.StepErrs[step]; ok { 245 protoStep.StepExecutionResult.Skipped = true 246 protoStep.StepExecutionResult.SkippedReason = "Step implementation not found" 247 } 248 } 249 250 func (e *specExecutor) getItemsForScenarioExecution(steps []*gauge.Step) ([]*gauge_messages.ProtoItem, error) { 251 items := make([]gauge.Item, len(steps)) 252 for i, context := range steps { 253 items[i] = context 254 } 255 lookup, err := e.dataTableLookup() 256 if err != nil { 257 return nil, err 258 } 259 return resolveItems(items, lookup, e.setSkipInfo) 260 } 261 262 func (e *specExecutor) dataTableLookup() (*gauge.ArgLookup, error) { 263 l := new(gauge.ArgLookup) 264 err := l.ReadDataTableRow(e.specification.DataTable.Table, 0) 265 return l, err 266 } 267 268 func (e *specExecutor) executeScenarios(scenarios []*gauge.Scenario) ([]result.Result, error) { 269 var scenarioResults []result.Result 270 for _, scenario := range scenarios { 271 sceResult, err := e.executeScenario(scenario) 272 if err != nil { 273 return nil, err 274 } 275 scenarioResults = append(scenarioResults, sceResult) 276 } 277 return scenarioResults, nil 278 } 279 280 func (e *specExecutor) executeScenario(scenario *gauge.Scenario) (*result.ScenarioResult, error) { 281 var scenarioResult *result.ScenarioResult 282 283 shouldRetry := RetryOnlyTags == "" 284 285 if !shouldRetry { 286 spec := e.specification 287 tagValues := make([]string, 0) 288 if spec.Tags != nil { 289 tagValues = spec.Tags.Values() 290 } 291 292 specFilter := filter.NewScenarioFilterBasedOnTags(tagValues, RetryOnlyTags) 293 294 shouldRetry = !(specFilter.Filter(scenario)) 295 } 296 retriesCount := 0 297 for i := 0; i < MaxRetriesCount; i++ { 298 e.currentExecutionInfo.CurrentScenario = &gauge_messages.ScenarioInfo{ 299 Name: scenario.Heading.Value, 300 Tags: getTagValue(scenario.Tags), 301 IsFailed: false, 302 Retries: &gauge_messages.ScenarioRetriesInfo{ 303 MaxRetries: int32(MaxRetriesCount) - 1, 304 CurrentRetry: int32(retriesCount), 305 }, 306 } 307 308 scenarioResult = &result.ScenarioResult{ 309 ProtoScenario: gauge.NewProtoScenario(scenario), 310 ScenarioDataTableRow: gauge.ConvertToProtoTable(&scenario.ScenarioDataTableRow), 311 ScenarioDataTableRowIndex: scenario.ScenarioDataTableRowIndex, 312 ScenarioDataTable: gauge.ConvertToProtoTable(scenario.DataTable.Table), 313 } 314 if err := e.addAllItemsForScenarioExecution(scenario, scenarioResult); err != nil { 315 return nil, err 316 } 317 e.scenarioExecutor.execute(scenario, scenarioResult) 318 retriesCount++ 319 if scenarioResult.ProtoScenario.GetExecutionStatus() == gauge_messages.ExecutionStatus_SKIPPED { 320 e.specResult.ScenarioSkippedCount++ 321 } 322 323 if !(shouldRetry && scenarioResult.GetFailed()) { 324 break 325 } 326 } 327 scenarioResult.ProtoScenario.RetriesCount = int64(retriesCount) 328 return scenarioResult, nil 329 } 330 331 func (e *specExecutor) addAllItemsForScenarioExecution(scenario *gauge.Scenario, scenarioResult *result.ScenarioResult) error { 332 contexts, err := e.getItemsForScenarioExecution(e.specification.Contexts) 333 if err != nil { 334 return err 335 } 336 scenarioResult.AddContexts(contexts) 337 tearDownSteps, err := e.getItemsForScenarioExecution(e.specification.TearDownSteps) 338 if err != nil { 339 return err 340 } 341 scenarioResult.AddTearDownSteps(tearDownSteps) 342 lookup, err := e.dataTableLookup() 343 if err != nil { 344 return err 345 } 346 if scenario.ScenarioDataTableRow.IsInitialized() { 347 parser.GetResolvedDataTablerows(&scenario.ScenarioDataTableRow) 348 if err = lookup.ReadDataTableRow(&scenario.ScenarioDataTableRow, 0); err != nil { 349 return err 350 } 351 } 352 items, err := resolveItems(scenario.Items, lookup, e.setSkipInfo) 353 if err != nil { 354 return err 355 } 356 scenarioResult.AddItems(items) 357 return nil 358 } 359 360 func getTagValue(tags *gauge.Tags) []string { 361 var tagValues []string 362 if tags != nil { 363 tagValues = append(tagValues, tags.Values()...) 364 } 365 return tagValues 366 } 367 368 func setSpecFailure(executionInfo *gauge_messages.ExecutionInfo) { 369 executionInfo.CurrentSpec.IsFailed = true 370 } 371 372 func shouldExecuteForRow(i int) bool { 373 if len(tableRowsIndexes) < 1 { 374 return true 375 } 376 for _, index := range tableRowsIndexes { 377 if index == i { 378 return true 379 } 380 } 381 return false 382 } 383 384 func getDataTableRows(tableRows string) (tableRowIndexes []int) { 385 switch { 386 case strings.TrimSpace(tableRows) == "": 387 return 388 case strings.Contains(tableRows, "-"): 389 indexes := strings.Split(tableRows, "-") 390 startRow, _ := strconv.Atoi(strings.TrimSpace(indexes[0])) 391 endRow, _ := strconv.Atoi(strings.TrimSpace(indexes[1])) 392 for i := startRow - 1; i < endRow; i++ { 393 tableRowIndexes = append(tableRowIndexes, i) 394 } 395 default: 396 indexes := strings.Split(tableRows, ",") 397 for _, i := range indexes { 398 rowNumber, _ := strconv.Atoi(strings.TrimSpace(i)) 399 tableRowIndexes = append(tableRowIndexes, rowNumber-1) 400 } 401 } 402 return 403 } 404 405 func executeHook(message *gauge_messages.Message, execTimeTracker result.ExecTimeTracker, r runner.Runner) *gauge_messages.ProtoExecutionResult { 406 executionResult := r.ExecuteAndGetStatus(message) 407 execTimeTracker.AddExecTime(executionResult.GetExecutionTime()) 408 return executionResult 409 } 410 411 func hasParseError(errs []error) bool { 412 for _, e := range errs { 413 if _, ok := e.(parser.ParseError); ok { 414 return true 415 } 416 } 417 return false 418 }