github.com/mattdotmatt/gauge@v0.3.2-0.20160421115137-425a4cdccb62/validation/validate.go (about)

     1  // Copyright 2015 ThoughtWorks, Inc.
     2  
     3  // This file is part of Gauge.
     4  
     5  // Gauge is free software: you can redistribute it and/or modify
     6  // it under the terms of the GNU General Public License as published by
     7  // the Free Software Foundation, either version 3 of the License, or
     8  // (at your option) any later version.
     9  
    10  // Gauge is distributed in the hope that it will be useful,
    11  // but WITHOUT ANY WARRANTY; without even the implied warranty of
    12  // MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
    13  // GNU General Public License for more details.
    14  
    15  // You should have received a copy of the GNU General Public License
    16  // along with Gauge.  If not, see <http://www.gnu.org/licenses/>.
    17  
    18  package validation
    19  
    20  import (
    21  	"errors"
    22  	"fmt"
    23  	"os"
    24  	"strconv"
    25  	"strings"
    26  
    27  	"github.com/getgauge/gauge/api"
    28  	"github.com/getgauge/gauge/config"
    29  	"github.com/getgauge/gauge/conn"
    30  	"github.com/getgauge/gauge/filter"
    31  	"github.com/getgauge/gauge/gauge"
    32  	"github.com/getgauge/gauge/gauge_messages"
    33  	"github.com/getgauge/gauge/logger"
    34  	"github.com/getgauge/gauge/manifest"
    35  	"github.com/getgauge/gauge/parser"
    36  	"github.com/getgauge/gauge/runner"
    37  	"github.com/golang/protobuf/proto"
    38  )
    39  
    40  type ValidationErrMaps struct {
    41  	SpecErrs     map[*gauge.Specification][]*StepValidationError
    42  	ScenarioErrs map[*gauge.Scenario][]*StepValidationError
    43  	StepErrs     map[*gauge.Step]*StepValidationError
    44  }
    45  
    46  type validator struct {
    47  	manifest           *manifest.Manifest
    48  	specsToExecute     []*gauge.Specification
    49  	runner             *runner.TestRunner
    50  	conceptsDictionary *gauge.ConceptDictionary
    51  }
    52  
    53  type specValidator struct {
    54  	specification        *gauge.Specification
    55  	runner               *runner.TestRunner
    56  	conceptsDictionary   *gauge.ConceptDictionary
    57  	stepValidationErrors []*StepValidationError
    58  	stepValidationCache  map[string]*StepValidationError
    59  }
    60  
    61  type StepValidationError struct {
    62  	step      *gauge.Step
    63  	message   string
    64  	fileName  string
    65  	errorType *gauge_messages.StepValidateResponse_ErrorType
    66  }
    67  
    68  func (s *StepValidationError) Error() string {
    69  	return fmt.Sprintf("%s:%d: %s. %s", s.fileName, s.step.LineNo, s.message, s.step.LineText)
    70  }
    71  
    72  func Validate(args []string) {
    73  	runner := startAPI()
    74  	_, errMap := ValidateSpecs(args, runner)
    75  	runner.Kill()
    76  	if len(errMap.StepErrs) > 0 {
    77  		os.Exit(1)
    78  	}
    79  	logger.Info("No error found.")
    80  }
    81  
    82  //TODO : duplicate in execute.go. Need to fix runner init.
    83  func startAPI() *runner.TestRunner {
    84  	sc := api.StartAPI()
    85  	select {
    86  	case runner := <-sc.RunnerChan:
    87  		return runner
    88  	case err := <-sc.ErrorChan:
    89  		logger.Fatalf("Failed to start gauge API: %s", err.Error())
    90  	}
    91  	return nil
    92  }
    93  
    94  func ValidateSpecs(args []string, r *runner.TestRunner) (*gauge.SpecCollection, *ValidationErrMaps) {
    95  	s, c := parseSpecs(args)
    96  	manifest, err := manifest.ProjectManifest()
    97  	if err != nil {
    98  		logger.Fatalf(err.Error())
    99  	}
   100  	v := newValidator(manifest, s, r, c)
   101  	vErrs := v.validate()
   102  	errMap := &ValidationErrMaps{
   103  		SpecErrs:     make(map[*gauge.Specification][]*StepValidationError),
   104  		ScenarioErrs: make(map[*gauge.Scenario][]*StepValidationError),
   105  		StepErrs:     make(map[*gauge.Step]*StepValidationError),
   106  	}
   107  
   108  	if len(vErrs) > 0 {
   109  		printValidationFailures(vErrs)
   110  		fillErrors(errMap, vErrs)
   111  	}
   112  	return gauge.NewSpecCollection(s), errMap
   113  }
   114  
   115  func parseSpecs(args []string) ([]*gauge.Specification, *gauge.ConceptDictionary) {
   116  	conceptsDictionary, conceptParseResult := parser.CreateConceptsDictionary(false, args)
   117  	parser.HandleParseResult(conceptParseResult)
   118  	specsToExecute, _ := filter.GetSpecsToExecute(conceptsDictionary, args)
   119  	if len(specsToExecute) == 0 {
   120  		logger.Info("No specifications found in %s.", strings.Join(args, ", "))
   121  		os.Exit(0)
   122  	}
   123  	return specsToExecute, conceptsDictionary
   124  }
   125  
   126  func fillErrors(errMap *ValidationErrMaps, validationErrors validationErrors) {
   127  	for spec, errors := range validationErrors {
   128  		for _, err := range errors {
   129  			errMap.StepErrs[err.step] = err
   130  		}
   131  		for _, scenario := range spec.Scenarios {
   132  			fillScenarioErrors(scenario, errMap, scenario.Steps)
   133  		}
   134  		fillSpecErrors(spec, errMap, append(spec.Contexts, spec.TearDownSteps...))
   135  	}
   136  }
   137  
   138  func fillScenarioErrors(scenario *gauge.Scenario, errMap *ValidationErrMaps, steps []*gauge.Step) {
   139  	for _, step := range steps {
   140  		if step.IsConcept {
   141  			fillScenarioErrors(scenario, errMap, step.ConceptSteps)
   142  		}
   143  		if err, ok := errMap.StepErrs[step]; ok {
   144  			errMap.ScenarioErrs[scenario] = append(errMap.ScenarioErrs[scenario], err)
   145  		}
   146  	}
   147  }
   148  
   149  func fillSpecErrors(spec *gauge.Specification, errMap *ValidationErrMaps, steps []*gauge.Step) {
   150  	for _, context := range steps {
   151  		if context.IsConcept {
   152  			fillSpecErrors(spec, errMap, context.ConceptSteps)
   153  		}
   154  		if err, ok := errMap.StepErrs[context]; ok {
   155  			errMap.SpecErrs[spec] = append(errMap.SpecErrs[spec], err)
   156  			for _, scenario := range spec.Scenarios {
   157  				if _, ok := errMap.ScenarioErrs[scenario]; !ok {
   158  					errMap.ScenarioErrs[scenario] = append(errMap.ScenarioErrs[scenario], err)
   159  				}
   160  			}
   161  		}
   162  	}
   163  }
   164  
   165  func printValidationFailures(validationErrors validationErrors) {
   166  	logger.Errorf("Validation failed. The following steps have errors")
   167  	for _, errs := range validationErrors {
   168  		for _, e := range errs {
   169  			logger.Errorf(e.Error())
   170  		}
   171  	}
   172  }
   173  
   174  func NewValidationError(s *gauge.Step, m string, f string, e *gauge_messages.StepValidateResponse_ErrorType) *StepValidationError {
   175  	return &StepValidationError{step: s, message: m, fileName: f, errorType: e}
   176  }
   177  
   178  type validationErrors map[*gauge.Specification][]*StepValidationError
   179  
   180  func newValidator(m *manifest.Manifest, s []*gauge.Specification, r *runner.TestRunner, c *gauge.ConceptDictionary) *validator {
   181  	return &validator{manifest: m, specsToExecute: s, runner: r, conceptsDictionary: c}
   182  }
   183  
   184  func (v *validator) validate() validationErrors {
   185  	validationStatus := make(validationErrors)
   186  	specValidator := &specValidator{runner: v.runner, conceptsDictionary: v.conceptsDictionary, stepValidationCache: make(map[string]*StepValidationError)}
   187  	for _, spec := range v.specsToExecute {
   188  		specValidator.specification = spec
   189  		validationErrors := specValidator.validate()
   190  		if len(validationErrors) != 0 {
   191  			validationStatus[spec] = validationErrors
   192  		}
   193  	}
   194  	if len(validationStatus) > 0 {
   195  		return validationStatus
   196  	}
   197  	return nil
   198  }
   199  
   200  func (v *specValidator) validate() []*StepValidationError {
   201  	v.specification.Traverse(v)
   202  	return v.stepValidationErrors
   203  }
   204  
   205  func (v *specValidator) Step(s *gauge.Step) {
   206  	if s.IsConcept {
   207  		for _, c := range s.ConceptSteps {
   208  			v.Step(c)
   209  		}
   210  		return
   211  	}
   212  	val, ok := v.stepValidationCache[s.Value]
   213  	if !ok {
   214  		err := v.validateStep(s)
   215  		if err != nil {
   216  			v.stepValidationErrors = append(v.stepValidationErrors, err)
   217  		}
   218  		v.stepValidationCache[s.Value] = err
   219  		return
   220  	}
   221  	if val != nil {
   222  		v.stepValidationErrors = append(v.stepValidationErrors,
   223  			NewValidationError(s, val.message, v.specification.FileName, val.errorType))
   224  	}
   225  }
   226  
   227  var invalidResponse gauge_messages.StepValidateResponse_ErrorType = -1
   228  
   229  func (v *specValidator) validateStep(s *gauge.Step) *StepValidationError {
   230  	m := &gauge_messages.Message{MessageType: gauge_messages.Message_StepValidateRequest.Enum(),
   231  		StepValidateRequest: &gauge_messages.StepValidateRequest{StepText: proto.String(s.Value), NumberOfParameters: proto.Int(len(s.Args))}}
   232  	r, err := conn.GetResponseForMessageWithTimeout(m, v.runner.Connection, config.RunnerRequestTimeout())
   233  	if err != nil {
   234  		return NewValidationError(s, err.Error(), v.specification.FileName, nil)
   235  	}
   236  	if r.GetMessageType() == gauge_messages.Message_StepValidateResponse {
   237  		res := r.GetStepValidateResponse()
   238  		if !res.GetIsValid() {
   239  			msg := getMessage(res.ErrorType.String())
   240  			return NewValidationError(s, msg, v.specification.FileName, res.ErrorType)
   241  		}
   242  		return nil
   243  	}
   244  	return NewValidationError(s, "Invalid response from runner for Validation request", v.specification.FileName, &invalidResponse)
   245  }
   246  
   247  func getMessage(message string) string {
   248  	lower := strings.ToLower(strings.Replace(message, "_", " ", -1))
   249  	return strings.ToUpper(lower[:1]) + lower[1:]
   250  }
   251  
   252  func (v *specValidator) ContextStep(step *gauge.Step) {
   253  	v.Step(step)
   254  }
   255  
   256  func (v *specValidator) TearDown(step *gauge.TearDown) {
   257  }
   258  
   259  func (v *specValidator) SpecHeading(heading *gauge.Heading) {
   260  	v.stepValidationErrors = make([]*StepValidationError, 0)
   261  }
   262  
   263  func (v *specValidator) SpecTags(tags *gauge.Tags) {
   264  }
   265  
   266  func (v *specValidator) ScenarioTags(tags *gauge.Tags) {
   267  
   268  }
   269  
   270  func (v *specValidator) DataTable(dataTable *gauge.Table) {
   271  
   272  }
   273  
   274  func (v *specValidator) Scenario(scenario *gauge.Scenario) {
   275  
   276  }
   277  
   278  func (v *specValidator) ScenarioHeading(heading *gauge.Heading) {
   279  }
   280  
   281  func (v *specValidator) Comment(comment *gauge.Comment) {
   282  }
   283  
   284  func (v *specValidator) ExternalDataTable(dataTable *gauge.DataTable) {
   285  
   286  }
   287  
   288  func ValidateTableRowsRange(start string, end string, rowCount int) (int, int, error) {
   289  	message := "Table rows range validation failed."
   290  	startRow, err := strconv.Atoi(start)
   291  	if err != nil {
   292  		return 0, 0, errors.New(message)
   293  	}
   294  	endRow, err := strconv.Atoi(end)
   295  	if err != nil {
   296  		return 0, 0, errors.New(message)
   297  	}
   298  	if startRow > endRow || endRow > rowCount || startRow < 1 || endRow < 1 {
   299  		return 0, 0, errors.New(message)
   300  	}
   301  	return startRow - 1, endRow - 1, nil
   302  }