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 }