github.com/getgauge/gauge@v1.6.9/filter/specItemFilter.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 filter 8 9 import ( 10 "errors" 11 "go/constant" 12 "go/token" 13 "go/types" 14 "regexp" 15 "strconv" 16 "strings" 17 18 "github.com/getgauge/gauge/env" 19 20 "fmt" 21 22 "github.com/getgauge/gauge/gauge" 23 "github.com/getgauge/gauge/logger" 24 ) 25 26 type scenarioFilterBasedOnSpan struct { 27 lineNumbers []int 28 } 29 type ScenarioFilterBasedOnTags struct { 30 specTags []string 31 tagExpression string 32 } 33 34 type scenarioFilterBasedOnName struct { 35 scenariosName []string 36 } 37 38 func NewScenarioFilterBasedOnSpan(lineNumbers []int) *scenarioFilterBasedOnSpan { 39 return &scenarioFilterBasedOnSpan{lineNumbers} 40 } 41 42 func (filter *scenarioFilterBasedOnSpan) Filter(item gauge.Item) bool { 43 for _, lineNumber := range filter.lineNumbers { 44 if item.(*gauge.Scenario).InSpan(lineNumber) { 45 return false 46 } 47 } 48 return true 49 } 50 51 func NewScenarioFilterBasedOnTags(specTags []string, tagExp string) *ScenarioFilterBasedOnTags { 52 return &ScenarioFilterBasedOnTags{specTags, tagExp} 53 } 54 55 func (filter *ScenarioFilterBasedOnTags) Filter(item gauge.Item) bool { 56 tags := item.(*gauge.Scenario).Tags 57 if tags == nil { 58 return !filter.filterTags(filter.specTags) 59 } 60 return !filter.filterTags(append(tags.Values(), filter.specTags...)) 61 } 62 63 func newScenarioFilterBasedOnName(scenariosName []string) *scenarioFilterBasedOnName { 64 return &scenarioFilterBasedOnName{scenariosName} 65 } 66 67 func (filter *scenarioFilterBasedOnName) Filter(item gauge.Item) bool { 68 return !item.(*gauge.Scenario).HasAnyHeading(filter.scenariosName) 69 } 70 71 func sanitize(tag string) string { 72 if _, err := strconv.ParseBool(tag); err == nil { 73 return fmt.Sprintf("{%s}", tag) 74 } 75 if env.AllowCaseSensitiveTags() { 76 return tag 77 } 78 return strings.ToLower(tag) 79 } 80 81 func (filter *ScenarioFilterBasedOnTags) filterTags(stags []string) bool { 82 tagsMap := make(map[string]bool) 83 for _, tag := range stags { 84 tag = sanitize(tag) 85 tagsMap[strings.Replace(tag, " ", "", -1)] = true 86 } 87 filter.replaceSpecialChar() 88 value, _ := filter.formatAndEvaluateExpression(tagsMap, filter.isTagPresent) 89 return value 90 } 91 92 func (filter *ScenarioFilterBasedOnTags) replaceSpecialChar() { 93 filter.tagExpression = strings.Replace(strings.Replace(strings.Replace(strings.Replace(filter.tagExpression, " ", "", -1), ",", "&", -1), "&&", "&", -1), "||", "|", -1) 94 } 95 96 func (filter *ScenarioFilterBasedOnTags) formatAndEvaluateExpression(tagsMap map[string]bool, isTagQualified func(tagsMap map[string]bool, tagName string) bool) (bool, error) { 97 tagExpressionParts, tags := filter.parseTagExpression() 98 for _, tag := range tags { 99 for i, txp := range tagExpressionParts { 100 if strings.TrimSpace(txp) == strings.TrimSpace(tag) { 101 tagExpressionParts[i] = strconv.FormatBool(isTagQualified(tagsMap, strings.TrimSpace(tag))) 102 } 103 } 104 } 105 return filter.evaluateExp(filter.handleNegation(strings.Join(tagExpressionParts, ""))) 106 } 107 108 func (filter *ScenarioFilterBasedOnTags) handleNegation(tagExpression string) string { 109 tagExpression = strings.Replace(strings.Replace(tagExpression, "!true", "false", -1), "!false", "true", -1) 110 for strings.Contains(tagExpression, "!(") { 111 tagExpression = filter.evaluateBrackets(tagExpression) 112 } 113 return tagExpression 114 } 115 116 func (filter *ScenarioFilterBasedOnTags) evaluateBrackets(tagExpression string) string { 117 if strings.Contains(tagExpression, "!(") { 118 innerText := filter.resolveBracketExpression(tagExpression) 119 return strings.Replace(tagExpression, "!("+innerText+")", filter.evaluateBrackets(innerText), -1) 120 } 121 value, _ := filter.evaluateExp(tagExpression) 122 return strconv.FormatBool(!value) 123 } 124 125 func (filter *ScenarioFilterBasedOnTags) resolveBracketExpression(tagExpression string) string { 126 indexOfOpenBracket := strings.Index(tagExpression, "!(") + 1 127 bracketStack := make([]string, 0) 128 i := indexOfOpenBracket 129 for ; i < len(tagExpression); i++ { 130 if tagExpression[i] == '(' { 131 bracketStack = append(bracketStack, "(") 132 } else if tagExpression[i] == ')' { 133 bracketStack = bracketStack[:len(bracketStack)-1] 134 } 135 if len(bracketStack) == 0 { 136 break 137 } 138 } 139 return tagExpression[indexOfOpenBracket+1 : i] 140 } 141 142 func (filter *ScenarioFilterBasedOnTags) evaluateExp(tagExpression string) (bool, error) { 143 tre := regexp.MustCompile("true") 144 fre := regexp.MustCompile("false") 145 146 s := fre.ReplaceAllString(tre.ReplaceAllString(tagExpression, "1"), "0") 147 148 val, err := types.Eval(token.NewFileSet(), nil, 0, s) 149 if err != nil { 150 return false, errors.New("Invalid Expression.\n" + err.Error()) 151 } 152 res, _ := constant.Uint64Val(val.Value) 153 154 var final bool 155 if res == 1 { 156 final = true 157 } else { 158 final = false 159 } 160 161 return final, nil 162 } 163 164 func (filter *ScenarioFilterBasedOnTags) isTagPresent(tagsMap map[string]bool, tagName string) bool { 165 _, ok := tagsMap[tagName] 166 return ok 167 } 168 169 func (filter *ScenarioFilterBasedOnTags) parseTagExpression() (tagExpressionParts []string, tags []string) { 170 isValidOperator := func(r rune) bool { return r == '&' || r == '|' || r == '(' || r == ')' || r == '!' } 171 var word string 172 var wordValue = func() string { 173 return sanitize(strings.TrimSpace(word)) 174 } 175 for _, c := range filter.tagExpression { 176 c1, _ := strconv.Unquote(strconv.QuoteRuneToASCII(c)) 177 if isValidOperator(c) { 178 if word != "" { 179 tagExpressionParts = append(tagExpressionParts, wordValue()) 180 tags = append(tags, wordValue()) 181 } 182 tagExpressionParts = append(tagExpressionParts, c1) 183 word = "" 184 } else { 185 word += c1 186 } 187 } 188 if word != "" { 189 tagExpressionParts = append(tagExpressionParts, wordValue()) 190 tags = append(tags, wordValue()) 191 } 192 return 193 } 194 195 func filterSpecsByTags(specs []*gauge.Specification, tagExpression string) ([]*gauge.Specification, []*gauge.Specification) { 196 filteredSpecs := make([]*gauge.Specification, 0) 197 otherSpecs := make([]*gauge.Specification, 0) 198 for _, spec := range specs { 199 tagValues := make([]string, 0) 200 if spec.Tags != nil { 201 tagValues = spec.Tags.Values() 202 } 203 specWithFilteredItems, specWithOtherItems := spec.Filter(NewScenarioFilterBasedOnTags(tagValues, tagExpression)) 204 if len(specWithFilteredItems.Scenarios) != 0 { 205 filteredSpecs = append(filteredSpecs, specWithFilteredItems) 206 } 207 if len(specWithOtherItems.Scenarios) != 0 { 208 otherSpecs = append(otherSpecs, specWithOtherItems) 209 } 210 } 211 return filteredSpecs, otherSpecs 212 } 213 214 func validateTagExpression(tagExpression string) { 215 filter := &ScenarioFilterBasedOnTags{tagExpression: tagExpression} 216 filter.replaceSpecialChar() 217 _, err := filter.formatAndEvaluateExpression(make(map[string]bool), func(a map[string]bool, b string) bool { return true }) 218 if err != nil { 219 logger.Fatal(true, err.Error()) 220 } 221 } 222 223 func filterSpecsByScenarioName(specs []*gauge.Specification, scenariosName []string) []*gauge.Specification { 224 filteredSpecs := make([]*gauge.Specification, 0) 225 scenarios := filterValidScenarios(specs, scenariosName) 226 for _, spec := range specs { 227 s, _ := spec.Filter(newScenarioFilterBasedOnName(scenarios)) 228 if len(s.Scenarios) != 0 { 229 filteredSpecs = append(filteredSpecs, s) 230 } 231 } 232 return filteredSpecs 233 } 234 235 func filterValidScenarios(specs []*gauge.Specification, headings []string) []string { 236 filteredScenarios := make([]string, 0) 237 allScenarios := GetAllScenarios(specs) 238 var exists = func(scenarios []string, heading string) bool { 239 for _, scenario := range scenarios { 240 if strings.Compare(scenario, heading) == 0 { 241 return true 242 } 243 } 244 return false 245 } 246 for _, heading := range headings { 247 if exists(allScenarios, heading) { 248 filteredScenarios = append(filteredScenarios, heading) 249 } else { 250 logger.Warningf(true, "Warning: scenario name - \"%s\" not found", heading) 251 } 252 } 253 return filteredScenarios 254 } 255 256 func GetAllScenarios(specs []*gauge.Specification) []string { 257 allScenarios := []string{} 258 for _, spec := range specs { 259 for _, scenario := range spec.Scenarios { 260 allScenarios = append(allScenarios, scenario.Heading.Value) 261 } 262 } 263 return allScenarios 264 }