Skip to content

Commit

Permalink
feat: make inclusion to the test case list based on matching tags (#317)
Browse files Browse the repository at this point in the history
Use tags to narrow down test cases to those labeled with strings matching
the regex passed as a config parameter.
  • Loading branch information
huberts90 committed Jun 19, 2024
1 parent c33cb00 commit f19698d
Show file tree
Hide file tree
Showing 11 changed files with 114 additions and 9 deletions.
1 change: 1 addition & 0 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -133,6 +133,7 @@ Flags:
-r, --rate-limit duration Limit the request rate to the server to 1 request per specified duration. 0 is the default, and disables rate limiting.
--read-timeout duration timeout for receiving responses during test execution (default 10s)
--show-failures-only shows only the results of failed tests
-T, --include-tags string include tests tagged with labels matching this Go regular expression (e.g. to include all tests being tagged with "cookie", use "^cookie$").
-t, --time show time spent per test
--wait-delay duration Time to wait between retries for all wait operations. (default 1s)
--wait-for-connection-timeout duration Http connection timeout, The timeout includes connection time, any redirects, and reading the response body. (default 3s)
Expand Down
17 changes: 15 additions & 2 deletions cmd/run.go
Original file line number Diff line number Diff line change
Expand Up @@ -33,6 +33,7 @@ func NewRunCommand() *cobra.Command {

runCmd.Flags().StringP("exclude", "e", "", "exclude tests matching this Go regular expression (e.g. to exclude all tests beginning with \"91\", use \"^91.*\"). \nIf you want more permanent exclusion, check the 'exclude' option in the config file.")
runCmd.Flags().StringP("include", "i", "", "include only tests matching this Go regular expression (e.g. to include only tests beginning with \"91\", use \"^91.*\"). \\nIf you want more permanent inclusion, check the 'include' option in the config file.\"")
runCmd.Flags().StringP("include-tags", "T", "", "include tests tagged with labels matching this Go regular expression (e.g. to include all tests being tagged with \"cookie\", use \"^cookie$\").")
runCmd.Flags().StringP("dir", "d", ".", "recursively find yaml tests in this directory")
runCmd.Flags().StringP("output", "o", "normal", "output type for ftw tests. \"normal\" is the default.")
runCmd.Flags().StringP("file", "f", "", "output file path for ftw tests. Prints to standard output by default.")
Expand Down Expand Up @@ -65,6 +66,7 @@ func runE(cmd *cobra.Command, _ []string) error {
cmd.SilenceUsage = true
exclude, _ := cmd.Flags().GetString("exclude")
include, _ := cmd.Flags().GetString("include")
includeTags, _ := cmd.Flags().GetString("include-tags")
dir, _ := cmd.Flags().GetString("dir")
outputFilename, _ := cmd.Flags().GetString("file")
logFilePath, _ := cmd.Flags().GetString("log-file")
Expand Down Expand Up @@ -113,11 +115,21 @@ func runE(cmd *cobra.Command, _ []string) error {

var includeRE *regexp.Regexp
if include != "" {
includeRE = regexp.MustCompile(include)
if includeRE, err = regexp.Compile(include); err != nil {
return fmt.Errorf("invalid --include regular expression: %w", err)
}
}
var excludeRE *regexp.Regexp
if exclude != "" {
excludeRE = regexp.MustCompile(exclude)
if excludeRE, err = regexp.Compile(exclude); err != nil {
return fmt.Errorf("invalid --exclude regular expression: %w", err)
}
}
var includeTagsRE *regexp.Regexp
if includeTags != "" {
if includeTagsRE, err = regexp.Compile(includeTags); err != nil {
return fmt.Errorf("invalid --include-tags regular expression: %w", err)
}
}

// Add wait4x checkers
Expand Down Expand Up @@ -167,6 +179,7 @@ func runE(cmd *cobra.Command, _ []string) error {
currentRun, err := runner.Run(cfg, tests, runner.RunnerConfig{
Include: includeRE,
Exclude: excludeRE,
IncludeTags: includeTagsRE,
ShowTime: showTime,
ShowOnlyFailed: showOnlyFailed,
ConnectTimeout: connectTimeout,
Expand Down
1 change: 1 addition & 0 deletions config/config.go
Original file line number Diff line number Diff line change
Expand Up @@ -28,6 +28,7 @@ func NewDefaultConfig() *FTWConfiguration {
MaxMarkerLogLines: DefaultMaxMarkerLogLines,
IncludeTests: make(map[*FTWRegexp]string),
ExcludeTests: make(map[*FTWRegexp]string),
IncludeTags: make(map[*FTWRegexp]string),
}
return cfg
}
Expand Down
2 changes: 2 additions & 0 deletions config/config_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -20,6 +20,8 @@ include:
'^9.*': 'Include all tests starting with 9'
exclude:
'^920400-2$': 'Exclude this test'
include_tags:
'^cookie$': 'Run test tagged with this label'
testoverride:
input:
dest_addr: 'httpbingo.org'
Expand Down
2 changes: 2 additions & 0 deletions config/types.go
Original file line number Diff line number Diff line change
Expand Up @@ -48,6 +48,8 @@ type FTWConfiguration struct {
IncludeTests map[*FTWRegexp]string `koanf:"include"`
// ExcludeTests is a list of tests to exclude (same as --exclude)
ExcludeTests map[*FTWRegexp]string `koanf:"exclude"`
// IncludeTags is a list of tags matching tests to run (same as --tag)
IncludeTags map[*FTWRegexp]string `koanf:"include_tags"`
}

type PlatformOverrides struct {
Expand Down
25 changes: 18 additions & 7 deletions runner/run.go
Original file line number Diff line number Diff line change
Expand Up @@ -6,7 +6,6 @@ package runner
import (
"errors"
"fmt"
"regexp"
"time"

schema "github.com/coreruleset/ftw-tests-schema/v2/types"
Expand Down Expand Up @@ -52,6 +51,7 @@ func Run(cfg *config.FTWConfiguration, tests []*test.FTWTest, c RunnerConfig, ou
RunnerConfig: &c,
Include: c.Include,
Exclude: c.Exclude,
IncludeTags: c.IncludeTags,
ShowTime: c.ShowTime,
Output: out,
ShowOnlyFailed: c.ShowOnlyFailed,
Expand Down Expand Up @@ -84,7 +84,7 @@ func RunTest(runContext *TestRunContext, ftwTest *test.FTWTest) error {

for _, testCase := range ftwTest.Tests {
// if we received a particular test ID, skip until we find it
if needToSkipTest(runContext.Include, runContext.Exclude, &testCase) {
if needToSkipTest(runContext, &testCase) {
runContext.Stats.addResultToStats(Skipped, &testCase)
continue
}
Expand Down Expand Up @@ -257,7 +257,11 @@ func markAndFlush(runContext *TestRunContext, dest *ftwhttp.Destination, stageID
return nil, fmt.Errorf("can't find log marker. Am I reading the correct log? Log file: %s", runContext.Config.LogFile)
}

func needToSkipTest(include *regexp.Regexp, exclude *regexp.Regexp, testCase *schema.Test) bool {
func needToSkipTest(runContext *TestRunContext, testCase *schema.Test) bool {
include := runContext.Include
exclude := runContext.Exclude
includeTags := runContext.IncludeTags

// never skip enabled explicit inclusions
if include != nil {
if include.MatchString(testCase.IdString()) {
Expand All @@ -266,24 +270,31 @@ func needToSkipTest(include *regexp.Regexp, exclude *regexp.Regexp, testCase *sc
}
}

result := false
// if the test's tags do not match the passed ones
// it needs to be skipped
if includeTags != nil {
if !utils.MatchSlice(includeTags, testCase.Tags) {
return true
}
}

// if we need to exclude tests, and the ID matches,
// it needs to be skipped
if exclude != nil {
if exclude.MatchString(testCase.IdString()) {
result = true
return true
}
}

// if we need to include tests, but the ID does not match
// it needs to be skipped
if include != nil {
if !include.MatchString(testCase.IdString()) {
result = true
return true
}
}

return result
return false
}

func checkTestSanity(stage *schema.Stage) error {
Expand Down
18 changes: 18 additions & 0 deletions runner/run_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -313,6 +313,24 @@ func (s *runTestSuite) TestRunTests_Run() {
s.Equal(res.Stats.TotalFailed(), 0, "failed to exclude test")
})

s.Run("count tests tagged with `tag-10`", func() {
res, err := Run(s.cfg, s.ftwTests, RunnerConfig{
IncludeTags: regexp.MustCompile("^tag-10$"),
}, s.out)
s.Require().NoError(err)
s.Len(res.Stats.Success, 1, "failed to incorporate tagged test")
s.Equal(res.Stats.TotalFailed(), 0, "failed to incorporate tagged test")
})

s.Run("count tests tagged with `tag-8` and `tag-10`", func() {
res, err := Run(s.cfg, s.ftwTests, RunnerConfig{
IncludeTags: regexp.MustCompile("^tag-8$|^tag-10$"),
}, s.out)
s.Require().NoError(err)
s.Len(res.Stats.Success, 2, "failed to incorporate tagged test")
s.Equal(res.Stats.TotalFailed(), 0, "failed to incorporate tagged test")
})

s.Run("test exceptions 1", func() {
res, err := Run(s.cfg, s.ftwTests, RunnerConfig{
Include: regexp.MustCompile("-1.*"),
Expand Down
6 changes: 6 additions & 0 deletions runner/testdata/TestRunTests_Run.yaml
Original file line number Diff line number Diff line change
Expand Up @@ -17,6 +17,9 @@ tests:
expect_error: False
status: 200
- test_id: 8
tags:
- tag-8
- local
description: "this test is number 8"
stages:
- input:
Expand All @@ -29,6 +32,9 @@ tests:
output:
status: 200
- test_id: 10
tags:
- tag-10
- other
stages:
- input:
dest_addr: "{{ .TestAddr }}"
Expand Down
3 changes: 3 additions & 0 deletions runner/types.go
Original file line number Diff line number Diff line change
Expand Up @@ -19,6 +19,8 @@ type RunnerConfig struct {
Include *regexp.Regexp
// Exclude is a regular expression to filter tests to exclude. If nil, no tests are excluded.
Exclude *regexp.Regexp
// IncludeTags is a regular expression to filter tests to count the ones tagged with the mathing label. If nil, no impact on test runner.
IncludeTags *regexp.Regexp
// ShowTime determines whether to show the time taken to run each test.
ShowTime bool
// ShowOnlyFailed will only output information related to failed tests
Expand All @@ -43,6 +45,7 @@ type TestRunContext struct {
RunnerConfig *RunnerConfig
Include *regexp.Regexp
Exclude *regexp.Regexp
IncludeTags *regexp.Regexp
ShowTime bool
ShowOnlyFailed bool
Output *output.Output
Expand Down
18 changes: 18 additions & 0 deletions utils/slice.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,18 @@
// Copyright 2023 OWASP ModSecurity Core Rule Set Project
// SPDX-License-Identifier: Apache-2.0

package utils

import (
"regexp"
)

func MatchSlice(regex *regexp.Regexp, hayStack []string) bool {
for _, str := range hayStack {
if regex.MatchString(str) {
return true
}
}

return false
}
30 changes: 30 additions & 0 deletions utils/slice_test.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,30 @@
// Copyright 2023 OWASP ModSecurity Core Rule Set Project
// SPDX-License-Identifier: Apache-2.0

package utils

import (
"regexp"
"testing"

"github.com/stretchr/testify/suite"
)

type sliceTestSuite struct {
suite.Suite
}

func TestSliceTestSuite(t *testing.T) {
suite.Run(t, new(timeTestSuite))
}

func (s *timeTestSuite) TestMatchSlice() {
re := regexp.MustCompile("^cookie$")

s.False(MatchSlice(re, []string{}))
s.False(MatchSlice(re, []string{""}))
s.False(MatchSlice(re, []string{"cooke", "php"}))
s.False(MatchSlice(re, []string{"cookies", "php"}))
s.True(MatchSlice(re, []string{"cookie", "php"}))
s.True(MatchSlice(re, []string{"js", "cookie"}))
}

0 comments on commit f19698d

Please sign in to comment.