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  }