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  }