Skip to content

Commit b886cfd

Browse files
authored
Merge pull request #26 from fzipi/feature-override-test
feat(tests): add test result overrides
2 parents 4166cb8 + 3b8002a commit b886cfd

File tree

12 files changed

+205
-35
lines changed

12 files changed

+205
-35
lines changed

README.md

Lines changed: 30 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -142,7 +142,7 @@ And the result should be similar to:
142142
```
143143
Happy testing!
144144

145-
## Additional features implemented already
145+
## Additional features
146146

147147
You can add functions to your tests, to simplify bulk writing, or even read values from the environment while executing. This is because `data:` sections in tests will be parse for Go [text/template](https://golang.org/pkg/text/template/) additional syntax, and with the power of additional [Sprig functions](https://masterminds.github.io/sprig/).
148148

@@ -172,5 +172,34 @@ data: 'username=fzipi
172172
173173
Other interesting functions you can use are: `randBytes`, `htpasswd`, `encryptAES`, etc.
174174

175+
## Overriding test results
176+
177+
Sometimes you have tests that work well in some platform combination, e.g. Apache + modsecurity2, but fail in other, e.g. Nginx + modsecurity3. Taking that into account, you can override test results using the `testoverride` config param. The test will be run, but the _result_ would be overriden, and your comment will be printed out.
178+
179+
Example:
180+
181+
```yaml
182+
...
183+
testoverride:
184+
ignore:
185+
# text comes from our friends at https://github.com/digitalwave/ftwrunner
186+
'941190-3': 'known MSC bug - PR #2023 (Cookie without value)'
187+
'941330-1': 'know MSC bug - #2148 (double escape)'
188+
'942480-2': 'known MSC bug - PR #2023 (Cookie without value)'
189+
'944100-11': 'known MSC bug - PR #2045, ISSUE #2146'
190+
forcefail:
191+
'123456-01': 'I want this test to fail, even if passing'
192+
forcepass:
193+
'123456-02': 'This test will always pass'
194+
```
195+
196+
You can combine any of `ignore`, `forcefail` and `forcepass` to make it work for you.
197+
198+
## Truncating logs
199+
200+
Log files can get really big. Searching patterns are performed using reverse text search in the file. Because the test tool is *really* fast, we sometimes see failures in nginx depending on how fast the tests are performed, mainly because log times in nginx are truncated to one second.
201+
202+
To overcome this, you can use the new config value `logtruncate: True`. This will, as it says, call _truncate_ on the file, actively modifying it between each test. You will need permissions to write the logfile, implying you might need to call the go-ftw binary using sudo.
203+
175204
## License
176205
[![FOSSA Status](https://app.fossa.com/api/projects/git%2Bgithub.com%2Ffzipi%2Fgo-ftw.svg?type=large)](https://app.fossa.com/projects/git%2Bgithub.com%2Ffzipi%2Fgo-ftw?ref=badge_large)

check/base.go

Lines changed: 28 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -9,10 +9,11 @@ import (
99
"github.com/rs/zerolog/log"
1010
)
1111

12-
// FTWCheck is the base struct for checks
12+
// FTWCheck is the base struct for checking test results
1313
type FTWCheck struct {
14-
log *waflog.FTWLogLines
15-
expected *test.Output
14+
log *waflog.FTWLogLines
15+
expected *test.Output
16+
overrides *config.FTWTestOverride
1617
}
1718

1819
// NewCheck creates a new FTWCheck, allowing to inject the configuration
@@ -25,11 +26,15 @@ func NewCheck(c *config.FTWConfiguration) *FTWCheck {
2526
Since: time.Now(),
2627
Until: time.Now(),
2728
TimeTruncate: c.LogType.TimeTruncate,
29+
LogTruncate: c.LogTruncate,
2830
},
29-
expected: &test.Output{},
31+
expected: &test.Output{},
32+
overrides: &c.TestOverride,
3033
}
3134

32-
log.Trace().Msgf("check/base: truncate set to %s", check.log.TimeTruncate)
35+
log.Trace().Msgf("check/base: time will be truncated to %s", check.log.TimeTruncate)
36+
log.Trace().Msgf("check/base: logfile will be truncated? %t", check.log.LogTruncate)
37+
3338
return check
3439
}
3540

@@ -68,3 +73,21 @@ func (c *FTWCheck) SetLogContains(contains string) {
6873
func (c *FTWCheck) SetNoLogContains(contains string) {
6974
c.expected.NoLogContains = contains
7075
}
76+
77+
// ForcedIgnore check if this id need to be ignored from results
78+
func (c *FTWCheck) ForcedIgnore(id string) bool {
79+
_, ok := c.overrides.Ignore[id]
80+
return ok
81+
}
82+
83+
// ForcedPass check if this id need to be ignored from results
84+
func (c *FTWCheck) ForcedPass(id string) bool {
85+
_, ok := c.overrides.ForcePass[id]
86+
return ok
87+
}
88+
89+
// ForcedFail check if this id need to be ignored from results
90+
func (c *FTWCheck) ForcedFail(id string) bool {
91+
_, ok := c.overrides.ForceFail[id]
92+
return ok
93+
}

check/base_test.go

Lines changed: 27 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -24,6 +24,9 @@ logtype:
2424
timeregex: '(\d{4}\/\d{2}\/\d{2} \d{2}:\d{2}:\d{2})'
2525
timeformat: 'YYYY/MM/DD HH:mm:ss'
2626
timetruncate: 1s
27+
testoverride:
28+
ignore:
29+
'942200-1': 'Ignore Me'
2730
`
2831

2932
func TestNewCheck(t *testing.T) {
@@ -34,4 +37,28 @@ func TestNewCheck(t *testing.T) {
3437
if c.log.TimeTruncate != time.Second {
3538
t.Errorf("Failed")
3639
}
40+
41+
for _, text := range c.overrides.Ignore {
42+
if text != "Ignore Me" {
43+
t.Errorf("Well, didn't match Ignore Me")
44+
}
45+
}
46+
}
47+
48+
func TestForced(t *testing.T) {
49+
config.ImportFromString(yamlNginxConfig)
50+
51+
c := NewCheck(config.FTWConfig)
52+
53+
if !c.ForcedIgnore("942200-1") {
54+
t.Errorf("Can't find ignored value")
55+
}
56+
57+
if c.ForcedFail("1245") {
58+
t.Errorf("Valued should not be found")
59+
}
60+
61+
if c.ForcedPass("1245") {
62+
t.Errorf("Valued should not be found")
63+
}
3764
}

cmd/root.go

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -40,6 +40,7 @@ func init() {
4040

4141
func initConfig() {
4242
config.Init(cfgFile)
43+
zerolog.SetGlobalLevel(zerolog.InfoLevel)
4344
if debug {
4445
zerolog.SetGlobalLevel(zerolog.TraceLevel)
4546
}

cmd/run.go

Lines changed: 5 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -4,6 +4,7 @@ import (
44
"fmt"
55

66
"github.com/kyokomi/emoji"
7+
"github.com/rs/zerolog"
78
"github.com/rs/zerolog/log"
89
"github.com/spf13/cobra"
910

@@ -23,7 +24,9 @@ var runCmd = &cobra.Command{
2324
showTime, _ := cmd.Flags().GetBool("time")
2425
quiet, _ := cmd.Flags().GetBool("quiet")
2526
if !quiet {
26-
emoji.Println(":hammer_and_wrench: Starting tests!")
27+
log.Info().Msgf(emoji.Sprintf(":hammer_and_wrench: Starting tests!\n"))
28+
} else {
29+
zerolog.SetGlobalLevel(zerolog.Disabled)
2730
}
2831
files := fmt.Sprintf("%s/**/*.yaml", dir)
2932
tests, err := test.GetTestsFromFiles(files)
@@ -37,7 +40,7 @@ var runCmd = &cobra.Command{
3740
func init() {
3841
rootCmd.AddCommand(runCmd)
3942
runCmd.Flags().StringP("id", "", "", "set test id to run")
40-
runCmd.Flags().StringP("exclude", "", "", "exclude tests matching this Go regexp (e.g. to exclude all tests beginning with \"91\", use \"91.*\")")
43+
runCmd.Flags().StringP("exclude", "", "", "exclude tests matching this Go regexp (e.g. to exclude all tests beginning with \"91\", use \"91.*\"). If you want more permanent exclusion, check the 'testmodify' option in the config file.")
4144
runCmd.Flags().StringP("dir", "d", ".", "recursively find yaml tests in this directory")
4245
runCmd.Flags().BoolP("quiet", "q", false, "do not show test by test, only results")
4346
runCmd.Flags().BoolP("time", "t", false, "show time spent per test")

config/config.go

Lines changed: 14 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -9,8 +9,10 @@ var FTWConfig *FTWConfiguration
99

1010
// FTWConfiguration FTW global Configuration
1111
type FTWConfiguration struct {
12-
LogFile string
13-
LogType FTWLogType
12+
LogFile string
13+
LogType FTWLogType
14+
LogTruncate bool
15+
TestOverride FTWTestOverride
1416
}
1517

1618
// FTWLogType log readers must implement this one
@@ -22,3 +24,13 @@ type FTWLogType struct {
2224
TimeFormat string
2325
TimeTruncate time.Duration
2426
}
27+
28+
// FTWTestOverride holds three lists:
29+
// Ignore is for tests you want to ignore. It will still execute the test, but ignore the results. You should add a comment on why you ignore the test
30+
// ForcePass is for tests you want to pass unconditionally. Test will be executed, and pass even when the test fails. You should add a comment on why you force pass the test
31+
// ForceFail is for tests you want to fail unconditionally. Test will be executed, and fail even when the test passes. You should add a comment on why you force fail the test
32+
type FTWTestOverride struct {
33+
Ignore map[string]string
34+
ForcePass map[string]string
35+
ForceFail map[string]string
36+
}

config/config_test.go

Lines changed: 21 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -3,6 +3,7 @@ package config
33
import (
44
"io/ioutil"
55
"os"
6+
"strings"
67
"testing"
78
"time"
89

@@ -16,6 +17,9 @@ logtype:
1617
name: 'apache'
1718
timeregex: '\[([A-Z][a-z]{2} [A-z][a-z]{2} \d{1,2} \d{1,2}\:\d{1,2}\:\d{1,2}\.\d+? \d{4})\]'
1819
timeformat: 'ddd MMM DD HH:mm:ss.S YYYY'
20+
testoverride:
21+
ignore:
22+
'920400-1': 'This test result must be ignored'
1923
`
2024

2125
var yamlBadConfig = `
@@ -30,6 +34,7 @@ logtype:
3034
var yamlTruncateConfig = `
3135
---
3236
logfile: 'tests/logs/modsec3-nginx/nginx/error.log'
37+
logtruncate: True
3338
logtype:
3439
name: nginx
3540
timetruncate: 1s
@@ -53,6 +58,19 @@ func TestInitConfig(t *testing.T) {
5358
if FTWConfig.LogType.TimeFormat != "ddd MMM DD HH:mm:ss.S YYYY" {
5459
t.Errorf("Failed !")
5560
}
61+
62+
if len(FTWConfig.TestOverride.Ignore) == 0 {
63+
t.Errorf("Failed! Len must be > 0")
64+
}
65+
66+
for id, text := range FTWConfig.TestOverride.Ignore {
67+
if !strings.Contains(id, "920400-1") {
68+
t.Errorf("Looks like we could not find item to ignore")
69+
}
70+
if text != "This test result must be ignored" {
71+
t.Errorf("Text doesn't match")
72+
}
73+
}
5674
}
5775

5876
func TestInitBadFileConfig(t *testing.T) {
@@ -107,6 +125,9 @@ func TestTimeTruncateConfig(t *testing.T) {
107125
t.Errorf("Failed !")
108126
}
109127

128+
if FTWConfig.LogTruncate != true {
129+
t.Errorf("Trucate file is wrong !")
130+
}
110131
if FTWConfig.LogType.TimeTruncate != time.Second {
111132
t.Errorf("Failed !")
112133
}

http/response_test.go

Lines changed: 0 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -42,7 +42,6 @@ func TestResponse(t *testing.T) {
4242

4343
req := generateRequestForTesting()
4444

45-
fmt.Printf("%+v\n", d)
4645
client, err := NewConnection(*d)
4746

4847
if err != nil {

runner/run.go

Lines changed: 23 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -58,7 +58,7 @@ func Run(testid string, exclude string, showTime bool, output bool, ftwtests []t
5858

5959
// Check sanity first
6060
if checkTestSanity(testRequest) {
61-
log.Fatal().Msgf("bad test: choose between data, encoded_request, or raw_request")
61+
log.Fatal().Msgf("ftw/run: bad test: choose between data, encoded_request, or raw_request")
6262
}
6363

6464
var client *http.Connection
@@ -83,8 +83,9 @@ func Run(testid string, exclude string, showTime bool, output bool, ftwtests []t
8383

8484
log.Debug().Msgf("ftw/run: sending request")
8585

86-
startSendingRequest := time.Now()
86+
startSendingRequest := time.Now().Local()
8787

88+
log.Trace().Msgf("ftw/run: started at %s", startSendingRequest.String())
8889
client, err = client.Request(req)
8990
if err != nil {
9091
log.Error().Msgf("ftw/run: error sending request: %s\n", err.Error())
@@ -95,8 +96,8 @@ func Run(testid string, exclude string, showTime bool, output bool, ftwtests []t
9596

9697
// We wrap go stdlib http for the response
9798
log.Debug().Msgf("ftw/check: getting response")
98-
startReceivingResponse := time.Now()
99-
99+
startReceivingResponse := time.Now().Local()
100+
log.Trace().Msgf("ftw/run: started receiving at %s", startReceivingResponse.String())
100101
response, responseError := client.Response()
101102
if responseError != nil {
102103
log.Debug().Msgf("ftw/run: error receiving response: %s\n", responseError.Error())
@@ -118,7 +119,7 @@ func Run(testid string, exclude string, showTime bool, output bool, ftwtests []t
118119
ftwcheck.SetExpectTestOutput(&expectedOutput)
119120

120121
// now get the test result based on output
121-
testResult = checkResult(ftwcheck, responseCode, responseError, responseText)
122+
testResult = checkResult(ftwcheck, t.TestTitle, responseCode, responseError, responseText)
122123

123124
duration = client.GetRoundTripTime().RoundTripDuration()
124125

@@ -154,17 +155,20 @@ func displayResult(quiet bool, result TestResult, duration time.Duration) {
154155
printUnlessQuietMode(quiet, ":check_mark:passed in %s\n", duration)
155156
case Failed:
156157
printUnlessQuietMode(quiet, ":collision:failed in %s\n", duration)
158+
case Ignored:
159+
printUnlessQuietMode(quiet, ":equal:test result ignored in %s\n", duration)
157160
default:
158161
// don't print anything if skipped test
159162
}
160163
}
161164

162165
// checkResult has the logic for verifying the result for the test sent
163-
func checkResult(c *check.FTWCheck, responseCode int, responseError error, responseText string) TestResult {
166+
func checkResult(c *check.FTWCheck, id string, responseCode int, responseError error, responseText string) TestResult {
164167
var result TestResult
165168

166169
// Set to failed initially
167170
result = Failed
171+
168172
// Request might return an error, but it could be expected, we check that first
169173
if c.AssertExpectError(responseError) {
170174
log.Debug().Msgf("ftw/check: found expected error")
@@ -191,6 +195,19 @@ func checkResult(c *check.FTWCheck, responseCode int, responseError error, respo
191195
result = Success
192196
}
193197

198+
// After all normal checks, see if the test result has been explicitly forced
199+
if c.ForcedIgnore(id) {
200+
result = Ignored
201+
}
202+
203+
if c.ForcedFail(id) {
204+
result = ForceFail
205+
}
206+
207+
if c.ForcedPass(id) {
208+
result = ForcePass
209+
}
210+
194211
return result
195212
}
196213

0 commit comments

Comments
 (0)