From 93f8ba3f6b9f314b50704578a93dc8fcb5612092 Mon Sep 17 00:00:00 2001 From: Anuraag Agrawal Date: Fri, 2 Sep 2022 16:18:20 +0900 Subject: [PATCH 1/4] feat: make request timeout configurable --- cmd/run.go | 2 ++ 1 file changed, 2 insertions(+) diff --git a/cmd/run.go b/cmd/run.go index deae162..d879595 100644 --- a/cmd/run.go +++ b/cmd/run.go @@ -3,6 +3,7 @@ package cmd import ( "fmt" "os" + "time" "github.com/kyokomi/emoji" "github.com/rs/zerolog" @@ -56,4 +57,5 @@ func init() { runCmd.Flags().StringP("dir", "d", ".", "recursively find yaml tests in this directory") runCmd.Flags().BoolP("quiet", "q", false, "do not show test by test, only results") runCmd.Flags().BoolP("time", "t", false, "show time spent per test") + runCmd.Flags().Duration("request-timeout", 1*time.Second, "timeout for individual requests during test execution") } From 7751fd27680263de5f76567f61a0b4163fb6b6d8 Mon Sep 17 00:00:00 2001 From: Anuraag Agrawal Date: Fri, 2 Sep 2022 16:49:53 +0900 Subject: [PATCH 2/4] Allow timeouts to be configured --- cmd/run.go | 7 +- ftwhttp/client.go | 31 +- ftwhttp/client_test.go | 200 +++++----- ftwhttp/connection.go | 6 +- ftwhttp/response_test.go | 6 +- ftwhttp/types.go | 17 +- runner/run.go | 8 +- runner/run_test.go | 819 ++++++++++++++++++++------------------- 8 files changed, 557 insertions(+), 537 deletions(-) diff --git a/cmd/run.go b/cmd/run.go index d879595..f294ee1 100644 --- a/cmd/run.go +++ b/cmd/run.go @@ -26,6 +26,8 @@ var runCmd = &cobra.Command{ dir, _ := cmd.Flags().GetString("dir") showTime, _ := cmd.Flags().GetBool("time") quiet, _ := cmd.Flags().GetBool("quiet") + connectTimeout, _ := cmd.Flags().GetDuration("connect-timeout") + readTimeout, _ := cmd.Flags().GetDuration("read-timeout") if !quiet { log.Info().Msgf(emoji.Sprintf(":hammer_and_wrench: Starting tests!\n")) } else { @@ -44,7 +46,7 @@ var runCmd = &cobra.Command{ log.Fatal().Err(err) } - currentRun := runner.Run(include, exclude, showTime, quiet, tests) + currentRun := runner.Run(include, exclude, showTime, quiet, connectTimeout, readTimeout, tests) os.Exit(currentRun.Stats.TotalFailed()) }, } @@ -57,5 +59,6 @@ func init() { runCmd.Flags().StringP("dir", "d", ".", "recursively find yaml tests in this directory") runCmd.Flags().BoolP("quiet", "q", false, "do not show test by test, only results") runCmd.Flags().BoolP("time", "t", false, "show time spent per test") - runCmd.Flags().Duration("request-timeout", 1*time.Second, "timeout for individual requests during test execution") + runCmd.Flags().Duration("connect-timeout", 3*time.Second, "timeout for connecting to endpoints during test execution") + runCmd.Flags().Duration("read-timeout", 1*time.Second, "timeout for receiving responses during test execution") } diff --git a/ftwhttp/client.go b/ftwhttp/client.go index 1db65c1..e02332b 100644 --- a/ftwhttp/client.go +++ b/ftwhttp/client.go @@ -3,26 +3,32 @@ package ftwhttp import ( "crypto/tls" "fmt" + "github.com/rs/zerolog/log" + "golang.org/x/net/publicsuffix" "net" "net/http/cookiejar" "strings" "time" - - "github.com/rs/zerolog/log" - "golang.org/x/net/publicsuffix" ) +// NewClientConfig returns a new ClientConfig with reasonable defaults. +func NewClientConfig() ClientConfig { + return ClientConfig{ + ConnectTimeout: 3 * time.Second, + ReadTimeout: 1 * time.Second, + } +} + // NewClient initializes the http client, creating the cookiejar -func NewClient() *Client { +func NewClient(config ClientConfig) *Client { // All users of cookiejar should import "golang.org/x/net/publicsuffix" jar, err := cookiejar.New(&cookiejar.Options{PublicSuffixList: publicsuffix.List}) if err != nil { log.Fatal().Err(err) } c := &Client{ - Jar: jar, - // default Timeout - Timeout: 3 * time.Second, + Jar: jar, + config: config, } return c } @@ -38,16 +44,17 @@ func (c *Client) NewConnection(d Destination) error { // strings.HasSuffix(err.String(), "connection refused") { if strings.ToLower(d.Protocol) == "https" { // Commenting InsecureSkipVerify: true. - netConn, err = tls.DialWithDialer(&net.Dialer{Timeout: c.Timeout}, "tcp", hostPort, &tls.Config{MinVersion: tls.VersionTLS12}) + netConn, err = tls.DialWithDialer(&net.Dialer{Timeout: c.config.ConnectTimeout}, "tcp", hostPort, &tls.Config{MinVersion: tls.VersionTLS12}) } else { - netConn, err = net.DialTimeout("tcp", hostPort, c.Timeout) + netConn, err = net.DialTimeout("tcp", hostPort, c.config.ConnectTimeout) } if err == nil { c.Transport = &Connection{ - connection: netConn, - protocol: d.Protocol, - duration: NewRoundTripTime(), + connection: netConn, + protocol: d.Protocol, + readTimeout: c.config.ReadTimeout, + duration: NewRoundTripTime(), } } diff --git a/ftwhttp/client_test.go b/ftwhttp/client_test.go index b5c4624..d447b2c 100644 --- a/ftwhttp/client_test.go +++ b/ftwhttp/client_test.go @@ -1,152 +1,152 @@ package ftwhttp import ( - "testing" + "testing" ) func TestNewClient(t *testing.T) { - c := NewClient() + c := NewClient(NewClientConfig()) - if c.Jar == nil { - t.Logf("Error creating Client") - } + if c.Jar == nil { + t.Logf("Error creating Client") + } } func TestConnectDestinationHTTPS(t *testing.T) { - d := &Destination{ - DestAddr: "example.com", - Port: 443, - Protocol: "https", - } - - c := NewClient() - - err := c.NewConnection(*d) - if err != nil { - t.Logf("This should not error") - } - - if c.Transport.protocol != "https" { - t.Logf("Error connecting to example.com using https") - } + d := &Destination{ + DestAddr: "example.com", + Port: 443, + Protocol: "https", + } + + c := NewClient(NewClientConfig()) + + err := c.NewConnection(*d) + if err != nil { + t.Logf("This should not error") + } + + if c.Transport.protocol != "https" { + t.Logf("Error connecting to example.com using https") + } } func TestDoRequest(t *testing.T) { - d := &Destination{ - DestAddr: "httpbin.org", - Port: 443, - Protocol: "https", - } + d := &Destination{ + DestAddr: "httpbin.org", + Port: 443, + Protocol: "https", + } - c := NewClient() + c := NewClient(NewClientConfig()) - req := generateBaseRequestForTesting() + req := generateBaseRequestForTesting() - err := c.NewConnection(*d) - if err != nil { - t.Logf("This should not error") - } + err := c.NewConnection(*d) + if err != nil { + t.Logf("This should not error") + } - _, err = c.Do(*req) + _, err = c.Do(*req) - if err == nil { - t.Logf("This should return error") - } + if err == nil { + t.Logf("This should return error") + } } func TestGetTrackedTime(t *testing.T) { - d := &Destination{ - DestAddr: "httpbin.org", - Port: 443, - Protocol: "https", - } + d := &Destination{ + DestAddr: "httpbin.org", + Port: 443, + Protocol: "https", + } - c := NewClient() + c := NewClient(NewClientConfig()) - rl := &RequestLine{ - Method: "POST", - URI: "/post", - Version: "HTTP/1.1", - } + rl := &RequestLine{ + Method: "POST", + URI: "/post", + Version: "HTTP/1.1", + } - h := Header{"Accept": "*/*", "User-Agent": "go-ftw test agent", "Host": "localhost"} + h := Header{"Accept": "*/*", "User-Agent": "go-ftw test agent", "Host": "localhost"} - data := []byte(`test=me&one=two&one=twice`) - req := NewRequest(rl, h, data, true) + data := []byte(`test=me&one=two&one=twice`) + req := NewRequest(rl, h, data, true) - err := c.NewConnection(*d) - if err != nil { - t.Logf("This should not error") - } + err := c.NewConnection(*d) + if err != nil { + t.Logf("This should not error") + } - c.StartTrackingTime() + c.StartTrackingTime() - resp, err := c.Do(*req) + resp, err := c.Do(*req) - c.StopTrackingTime() + c.StopTrackingTime() - if err != nil { - t.Logf("This should not error") - } + if err != nil { + t.Logf("This should not error") + } - if resp.Parsed.StatusCode != 200 { - t.Logf("Error in calling website") - } + if resp.Parsed.StatusCode != 200 { + t.Logf("Error in calling website") + } - rtt := c.GetRoundTripTime() + rtt := c.GetRoundTripTime() - if rtt.RoundTripDuration() < 0 { - t.Logf("Error getting RTT") - } + if rtt.RoundTripDuration() < 0 { + t.Logf("Error getting RTT") + } } func TestClientMultipartFormDataRequest(t *testing.T) { - d := &Destination{ - DestAddr: "httpbin.org", - Port: 443, - Protocol: "https", - } - - c := NewClient() - - rl := &RequestLine{ - Method: "POST", - URI: "/post", - Version: "HTTP/1.1", - } - - h := Header{ - "Accept": "*/*", "User-Agent": "go-ftw test agent", "Host": "localhost", - "Content-Type": "multipart/form-data; boundary=--------397236876", - } - - data := []byte(`----------397236876 + d := &Destination{ + DestAddr: "httpbin.org", + Port: 443, + Protocol: "https", + } + + c := NewClient(NewClientConfig()) + + rl := &RequestLine{ + Method: "POST", + URI: "/post", + Version: "HTTP/1.1", + } + + h := Header{ + "Accept": "*/*", "User-Agent": "go-ftw test agent", "Host": "localhost", + "Content-Type": "multipart/form-data; boundary=--------397236876", + } + + data := []byte(`----------397236876 Content-Disposition: form-data; name="fileRap"; filename="test.txt" Content-Type: text/plain Some-file-test-here ----------397236876--`) - req := NewRequest(rl, h, data, true) + req := NewRequest(rl, h, data, true) - err := c.NewConnection(*d) + err := c.NewConnection(*d) - if err != nil { - t.Logf("This should not error") - } + if err != nil { + t.Logf("This should not error") + } - c.StartTrackingTime() + c.StartTrackingTime() - resp, err := c.Do(*req) + resp, err := c.Do(*req) - c.StopTrackingTime() + c.StopTrackingTime() - if err != nil { - t.Logf("This should not error") - } + if err != nil { + t.Logf("This should not error") + } - if resp.Parsed.StatusCode != 200 { - t.Logf("Error in calling website") + if resp.Parsed.StatusCode != 200 { + t.Logf("Error in calling website") } } diff --git a/ftwhttp/connection.go b/ftwhttp/connection.go index c78d0ae..a29f0a8 100644 --- a/ftwhttp/connection.go +++ b/ftwhttp/connection.go @@ -70,13 +70,9 @@ func (c *Connection) receive() ([]byte, error) { var err error var buf []byte - // Set a deadline for reading. Read operation will fail if no data - // is received after deadline. - timeoutDuration := 1000 * time.Millisecond - // We assume the response body can be handled in memory without problems // That's why we use io.ReadAll - if err = c.connection.SetReadDeadline(time.Now().Add(timeoutDuration)); err == nil { + if err = c.connection.SetReadDeadline(time.Now().Add(c.readTimeout)); err == nil { buf, err = io.ReadAll(c.connection) } diff --git a/ftwhttp/response_test.go b/ftwhttp/response_test.go index 3ca8bfb..56b8ce2 100644 --- a/ftwhttp/response_test.go +++ b/ftwhttp/response_test.go @@ -88,7 +88,7 @@ func TestResponse(t *testing.T) { } req := generateRequestForTesting(true) - client := NewClient() + client := NewClient(NewClientConfig()) err = client.NewConnection(*d) if err != nil { @@ -98,7 +98,7 @@ func TestResponse(t *testing.T) { response, err := client.Do(*req) if err != nil { - t.Logf("Failed !") + t.Fatal(err) } if response.GetBodyAsString() != "Hello, client\n" { @@ -118,7 +118,7 @@ func TestResponseWithCookies(t *testing.T) { } req := generateRequestForTesting(true) - client := NewClient() + client := NewClient(NewClientConfig()) err = client.NewConnection(*d) if err != nil { diff --git a/ftwhttp/types.go b/ftwhttp/types.go index a679191..de29c47 100644 --- a/ftwhttp/types.go +++ b/ftwhttp/types.go @@ -6,18 +6,27 @@ import ( "time" ) +// ClientConfig provides configuration options for the HTTP client. +type ClientConfig struct { + // ConnectTimeout is the timeout for connecting to a server. + ConnectTimeout time.Duration + // ReadTimeout is the timeout for reading a response. + ReadTimeout time.Duration +} + // Client is the top level abstraction in http type Client struct { Transport *Connection Jar http.CookieJar - Timeout time.Duration + config ClientConfig } // Connection is the type used for sending/receiving data type Connection struct { - connection net.Conn - protocol string - duration *RoundTripTime + connection net.Conn + protocol string + readTimeout time.Duration + duration *RoundTripTime } // RoundTripTime abstracts the time a transaction takes diff --git a/runner/run.go b/runner/run.go index 8e58f7d..5f73a14 100644 --- a/runner/run.go +++ b/runner/run.go @@ -21,12 +21,16 @@ import ( // testid is the name of the unique test you want to run // exclude is a regexp that matches the test name: e.g. "920*", excludes all tests starting with "920" // Returns error if some test failed -func Run(include string, exclude string, showTime bool, output bool, ftwtests []test.FTWTest) TestRunContext { +func Run(include string, exclude string, showTime bool, output bool, + connectTimeout time.Duration, readTimeout time.Duration, ftwtests []test.FTWTest) TestRunContext { printUnlessQuietMode(output, ":rocket:Running go-ftw!\n") logLines := waflog.NewFTWLogLines(waflog.WithLogFile(config.FTWConfig.LogFile)) - client := ftwhttp.NewClient() + conf := ftwhttp.NewClientConfig() + conf.ConnectTimeout = connectTimeout + conf.ReadTimeout = readTimeout + client := ftwhttp.NewClient(conf) runContext := TestRunContext{ Include: include, Exclude: exclude, diff --git a/runner/run_test.go b/runner/run_test.go index 43eeb12..3075aef 100644 --- a/runner/run_test.go +++ b/runner/run_test.go @@ -1,18 +1,19 @@ package runner import ( - "fmt" - "net/http" - "net/http/httptest" - "os" - "testing" - - "github.com/fzipi/go-ftw/check" - "github.com/fzipi/go-ftw/config" - "github.com/fzipi/go-ftw/ftwhttp" - "github.com/fzipi/go-ftw/test" - - "github.com/rs/zerolog/log" + "fmt" + "net/http" + "net/http/httptest" + "os" + "testing" + "time" + + "github.com/fzipi/go-ftw/check" + "github.com/fzipi/go-ftw/config" + "github.com/fzipi/go-ftw/ftwhttp" + "github.com/fzipi/go-ftw/test" + + "github.com/rs/zerolog/log" ) var yamlConfig = ` @@ -277,448 +278,448 @@ tests: // Error checking omitted for brevity func newTestServer(t *testing.T, logLines string) (destination *ftwhttp.Destination, logFilePath string) { - logFilePath = setUpLogFileForTestServer(t) + logFilePath = setUpLogFileForTestServer(t) - ts := httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) { - w.WriteHeader(http.StatusOK) - _, _ = w.Write([]byte("Hello, client")) + ts := httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) { + w.WriteHeader(http.StatusOK) + _, _ = w.Write([]byte("Hello, client")) - writeTestServerLog(t, logLines, logFilePath, r) - })) + writeTestServerLog(t, logLines, logFilePath, r) + })) - // close server after test - t.Cleanup(ts.Close) + // close server after test + t.Cleanup(ts.Close) - dest, err := ftwhttp.DestinationFromString(ts.URL) - if err != nil { - t.Error(err) - t.FailNow() - } - return dest, logFilePath + dest, err := ftwhttp.DestinationFromString(ts.URL) + if err != nil { + t.Error(err) + t.FailNow() + } + return dest, logFilePath } // Error checking omitted for brevity func newTestServerForCloudTest(t *testing.T, responseStatus int, logLines string) (server *httptest.Server, destination *ftwhttp.Destination) { - server = httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) { - w.WriteHeader(responseStatus) - _, _ = w.Write([]byte("Hello, client")) - })) - - // close server after test - t.Cleanup(server.Close) - - dest, err := ftwhttp.DestinationFromString(server.URL) - if err != nil { - t.Error(err) - t.FailNow() - } - return server, dest + server = httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) { + w.WriteHeader(responseStatus) + _, _ = w.Write([]byte("Hello, client")) + })) + + // close server after test + t.Cleanup(server.Close) + + dest, err := ftwhttp.DestinationFromString(server.URL) + if err != nil { + t.Error(err) + t.FailNow() + } + return server, dest } func setUpLogFileForTestServer(t *testing.T) (logFilePath string) { - // log to the configured file - if config.FTWConfig != nil && config.FTWConfig.RunMode == config.DefaultRunMode { - logFilePath = config.FTWConfig.LogFile - } - // if no file has been configured, create one and handle cleanup - if logFilePath == "" { - file, err := os.CreateTemp("", "go-ftw-test-*.log") - if err != nil { - t.Error(err) - } - logFilePath = file.Name() - t.Cleanup(func() { - os.Remove(logFilePath) - log.Info().Msgf("Deleting temporary file '%s'", logFilePath) - }) - } - return logFilePath + // log to the configured file + if config.FTWConfig != nil && config.FTWConfig.RunMode == config.DefaultRunMode { + logFilePath = config.FTWConfig.LogFile + } + // if no file has been configured, create one and handle cleanup + if logFilePath == "" { + file, err := os.CreateTemp("", "go-ftw-test-*.log") + if err != nil { + t.Error(err) + } + logFilePath = file.Name() + t.Cleanup(func() { + os.Remove(logFilePath) + log.Info().Msgf("Deleting temporary file '%s'", logFilePath) + }) + } + return logFilePath } func writeTestServerLog(t *testing.T, logLines string, logFilePath string, r *http.Request) { - // write supplied log lines, emulating the output of the rule engine - logMessage := logLines - // if the request has the special test header, log the request instead - // this emulates the log marker rule - if r.Header.Get(config.FTWConfig.LogMarkerHeaderName) != "" { - logMessage = fmt.Sprintf("request line: %s %s %s, headers: %s\n", r.Method, r.RequestURI, r.Proto, r.Header) - } - file, err := os.OpenFile(logFilePath, os.O_WRONLY|os.O_APPEND|os.O_CREATE, 0600) - if err != nil { - t.Error(err) - t.FailNow() - } - defer file.Close() - - _, err = file.WriteString(logMessage) - if err != nil { - t.Error(err) - t.FailNow() - } + // write supplied log lines, emulating the output of the rule engine + logMessage := logLines + // if the request has the special test header, log the request instead + // this emulates the log marker rule + if r.Header.Get(config.FTWConfig.LogMarkerHeaderName) != "" { + logMessage = fmt.Sprintf("request line: %s %s %s, headers: %s\n", r.Method, r.RequestURI, r.Proto, r.Header) + } + file, err := os.OpenFile(logFilePath, os.O_WRONLY|os.O_APPEND|os.O_CREATE, 0600) + if err != nil { + t.Error(err) + t.FailNow() + } + defer file.Close() + + _, err = file.WriteString(logMessage) + if err != nil { + t.Error(err) + t.FailNow() + } } func replaceDestinationInTest(ftwTest *test.FTWTest, d ftwhttp.Destination) { - // This function doesn't use `range` because we want to modify the struct in place. - // Range (and assignments in general) create copies of structs, not references. - // Maps, slices, etc. on the other hand, are assigned as references. - for testIndex := 0; testIndex < len(ftwTest.Tests); testIndex++ { - testCase := &ftwTest.Tests[testIndex] - for stageIndex := 0; stageIndex < len(testCase.Stages); stageIndex++ { - input := &testCase.Stages[stageIndex].Stage.Input - - if *input.DestAddr == "TEST_ADDR" { - input.DestAddr = &d.DestAddr - } - if input.Headers.Get("Host") == "TEST_ADDR" { - input.Headers.Set("Host", d.DestAddr) - } - if input.Port != nil && *input.Port == -1 { - input.Port = &d.Port - } - } - } + // This function doesn't use `range` because we want to modify the struct in place. + // Range (and assignments in general) create copies of structs, not references. + // Maps, slices, etc. on the other hand, are assigned as references. + for testIndex := 0; testIndex < len(ftwTest.Tests); testIndex++ { + testCase := &ftwTest.Tests[testIndex] + for stageIndex := 0; stageIndex < len(testCase.Stages); stageIndex++ { + input := &testCase.Stages[stageIndex].Stage.Input + + if *input.DestAddr == "TEST_ADDR" { + input.DestAddr = &d.DestAddr + } + if input.Headers.Get("Host") == "TEST_ADDR" { + input.Headers.Set("Host", d.DestAddr) + } + if input.Port != nil && *input.Port == -1 { + input.Port = &d.Port + } + } + } } func replaceDestinationInConfiguration(dest ftwhttp.Destination) { - replaceableAddress := "TEST_ADDR" - replaceablePort := -1 - - input := &config.FTWConfig.TestOverride.Input - if input.DestAddr != nil && *input.DestAddr == replaceableAddress { - input.DestAddr = &dest.DestAddr - } - if input.Port != nil && *input.Port == replaceablePort { - input.Port = &dest.Port - } + replaceableAddress := "TEST_ADDR" + replaceablePort := -1 + + input := &config.FTWConfig.TestOverride.Input + if input.DestAddr != nil && *input.DestAddr == replaceableAddress { + input.DestAddr = &dest.DestAddr + } + if input.Port != nil && *input.Port == replaceablePort { + input.Port = &dest.Port + } } func TestRun(t *testing.T) { - t.Cleanup(config.Reset) - - err := config.NewConfigFromString(yamlConfig) - if err != nil { - t.Errorf("Failed!") - } - - // setup test webserver (not a waf) - dest, logFilePath := newTestServer(t, logText) - config.FTWConfig.LogFile = logFilePath - ftwTest, err := test.GetTestFromYaml([]byte(yamlTest)) - if err != nil { - t.Error(err) - } - replaceDestinationInTest(&ftwTest, *dest) - - t.Run("show time and execute all", func(t *testing.T) { - if res := Run("", "", true, false, []test.FTWTest{ftwTest}); res.Stats.TotalFailed() > 0 { - t.Errorf("Oops, %d tests failed to run!", res.Stats.TotalFailed()) - } - }) - - t.Run("be verbose and execute all", func(t *testing.T) { - if res := Run("0*", "", true, true, []test.FTWTest{ftwTest}); res.Stats.TotalFailed() > 0 { - t.Error("Oops, test run failed!") - } - }) - - t.Run("don't show time and execute all", func(t *testing.T) { - if res := Run("0*", "", false, false, []test.FTWTest{ftwTest}); res.Stats.TotalFailed() > 0 { - t.Error("Oops, test run failed!") - } - }) - - t.Run("execute only test 008 but exclude all", func(t *testing.T) { - if res := Run("008", "0*", false, false, []test.FTWTest{ftwTest}); res.Stats.TotalFailed() > 0 { - t.Error("Oops, test run failed!") - } - }) - - t.Run("exclude test 010", func(t *testing.T) { - if res := Run("*", "010", false, false, []test.FTWTest{ftwTest}); res.Stats.TotalFailed() > 0 { - t.Error("Oops, test run failed!") - } - }) - - t.Run("test exceptions 1", func(t *testing.T) { - if res := Run("1*", "0*", false, true, []test.FTWTest{ftwTest}); res.Stats.TotalFailed() > 0 { - t.Error("Oops, test run failed!") - } - }) + t.Cleanup(config.Reset) + + err := config.NewConfigFromString(yamlConfig) + if err != nil { + t.Errorf("Failed!") + } + + // setup test webserver (not a waf) + dest, logFilePath := newTestServer(t, logText) + config.FTWConfig.LogFile = logFilePath + ftwTest, err := test.GetTestFromYaml([]byte(yamlTest)) + if err != nil { + t.Error(err) + } + replaceDestinationInTest(&ftwTest, *dest) + + t.Run("show time and execute all", func(t *testing.T) { + if res := Run("", "", true, false, 1*time.Second, 1*time.Second, []test.FTWTest{ftwTest}); res.Stats.TotalFailed() > 0 { + t.Errorf("Oops, %d tests failed to run!", res.Stats.TotalFailed()) + } + }) + + t.Run("be verbose and execute all", func(t *testing.T) { + if res := Run("0*", "", true, true, 1*time.Second, 1*time.Second, []test.FTWTest{ftwTest}); res.Stats.TotalFailed() > 0 { + t.Error("Oops, test run failed!") + } + }) + + t.Run("don't show time and execute all", func(t *testing.T) { + if res := Run("0*", "", false, false, 1*time.Second, 1*time.Second, []test.FTWTest{ftwTest}); res.Stats.TotalFailed() > 0 { + t.Error("Oops, test run failed!") + } + }) + + t.Run("execute only test 008 but exclude all", func(t *testing.T) { + if res := Run("008", "0*", false, false, 1*time.Second, 1*time.Second, []test.FTWTest{ftwTest}); res.Stats.TotalFailed() > 0 { + t.Error("Oops, test run failed!") + } + }) + + t.Run("exclude test 010", func(t *testing.T) { + if res := Run("*", "010", false, false, 1*time.Second, 1*time.Second, []test.FTWTest{ftwTest}); res.Stats.TotalFailed() > 0 { + t.Error("Oops, test run failed!") + } + }) + + t.Run("test exceptions 1", func(t *testing.T) { + if res := Run("1*", "0*", false, true, 1*time.Second, 1*time.Second, []test.FTWTest{ftwTest}); res.Stats.TotalFailed() > 0 { + t.Error("Oops, test run failed!") + } + }) } func TestOverrideRun(t *testing.T) { - t.Cleanup(config.Reset) - - // setup test webserver (not a waf) - err := config.NewConfigFromString(yamlConfigOverride) - if err != nil { - t.Error(err) - } - - dest, logFilePath := newTestServer(t, logText) - - replaceDestinationInConfiguration(*dest) - config.FTWConfig.LogFile = logFilePath - - // replace host and port with values that can be overridden by config - fakeDestination, err := ftwhttp.DestinationFromString("http://example.com:1234") - if err != nil { - t.Fatalf("Failed to parse fake destination") - } - - ftwTest, err := test.GetTestFromYaml([]byte(yamlTestOverride)) - if err != nil { - t.Error(err) - } - replaceDestinationInTest(&ftwTest, *fakeDestination) - - t.Run("override and execute all", func(t *testing.T) { - if res := Run("", "", false, true, []test.FTWTest{ftwTest}); res.Stats.TotalFailed() > 0 { - t.Error("Oops, test run failed!") - } - }) + t.Cleanup(config.Reset) + + // setup test webserver (not a waf) + err := config.NewConfigFromString(yamlConfigOverride) + if err != nil { + t.Error(err) + } + + dest, logFilePath := newTestServer(t, logText) + + replaceDestinationInConfiguration(*dest) + config.FTWConfig.LogFile = logFilePath + + // replace host and port with values that can be overridden by config + fakeDestination, err := ftwhttp.DestinationFromString("http://example.com:1234") + if err != nil { + t.Fatalf("Failed to parse fake destination") + } + + ftwTest, err := test.GetTestFromYaml([]byte(yamlTestOverride)) + if err != nil { + t.Error(err) + } + replaceDestinationInTest(&ftwTest, *fakeDestination) + + t.Run("override and execute all", func(t *testing.T) { + if res := Run("", "", false, true, 1*time.Second, 1*time.Second, []test.FTWTest{ftwTest}); res.Stats.TotalFailed() > 0 { + t.Error("Oops, test run failed!") + } + }) } func TestBrokenOverrideRun(t *testing.T) { - t.Cleanup(config.Reset) - - err := config.NewConfigFromString(yamlBrokenConfigOverride) - if err != nil { - t.Errorf("Failed!") - } - - dest, logFilePath := newTestServer(t, logText) - - replaceDestinationInConfiguration(*dest) - config.FTWConfig.LogFile = logFilePath - - // replace host and port with values that can be overridden by config - fakeDestination, err := ftwhttp.DestinationFromString("http://example.com:1234") - if err != nil { - t.Fatalf("Failed to parse fake destination") - } - - ftwTest, err := test.GetTestFromYaml([]byte(yamlTestOverride)) - if err != nil { - t.Error(err) - } - replaceDestinationInTest(&ftwTest, *fakeDestination) - - // the test should succeed, despite the unknown override property - t.Run("showtime and execute all", func(t *testing.T) { - if res := Run("", "", false, true, []test.FTWTest{ftwTest}); res.Stats.TotalFailed() > 0 { - t.Error("Oops, test run failed!") - } - }) + t.Cleanup(config.Reset) + + err := config.NewConfigFromString(yamlBrokenConfigOverride) + if err != nil { + t.Errorf("Failed!") + } + + dest, logFilePath := newTestServer(t, logText) + + replaceDestinationInConfiguration(*dest) + config.FTWConfig.LogFile = logFilePath + + // replace host and port with values that can be overridden by config + fakeDestination, err := ftwhttp.DestinationFromString("http://example.com:1234") + if err != nil { + t.Fatalf("Failed to parse fake destination") + } + + ftwTest, err := test.GetTestFromYaml([]byte(yamlTestOverride)) + if err != nil { + t.Error(err) + } + replaceDestinationInTest(&ftwTest, *fakeDestination) + + // the test should succeed, despite the unknown override property + t.Run("showtime and execute all", func(t *testing.T) { + if res := Run("", "", false, true, 1*time.Second, 1*time.Second, []test.FTWTest{ftwTest}); res.Stats.TotalFailed() > 0 { + t.Error("Oops, test run failed!") + } + }) } func TestBrokenPortOverrideRun(t *testing.T) { - t.Cleanup(config.Reset) - - // TestServer initialized first to retrieve the correct port number - dest, logFilePath := newTestServer(t, logText) - - // replace destination port inside the yaml with the retrieved one - err := config.NewConfigFromString(fmt.Sprintf(yamlConfigPortOverride, dest.Port)) - if err != nil { - t.Errorf("Failed!") - } - - replaceDestinationInConfiguration(*dest) - config.FTWConfig.LogFile = logFilePath - - // replace host and port with values that can be overridden by config - fakeDestination, err := ftwhttp.DestinationFromString("http://example.com:1234") - if err != nil { - t.Fatalf("Failed to parse fake destination") - } - - ftwTest, err := test.GetTestFromYaml([]byte(yamlTestOverrideWithNoPort)) - if err != nil { - t.Error(err) - } - replaceDestinationInTest(&ftwTest, *fakeDestination) - - // the test should succeed, despite the unknown override property - t.Run("showtime and execute all", func(t *testing.T) { - if res := Run("", "", false, true, []test.FTWTest{ftwTest}); res.Stats.TotalFailed() > 0 { - t.Error("Oops, test run failed!") - } - }) + t.Cleanup(config.Reset) + + // TestServer initialized first to retrieve the correct port number + dest, logFilePath := newTestServer(t, logText) + + // replace destination port inside the yaml with the retrieved one + err := config.NewConfigFromString(fmt.Sprintf(yamlConfigPortOverride, dest.Port)) + if err != nil { + t.Errorf("Failed!") + } + + replaceDestinationInConfiguration(*dest) + config.FTWConfig.LogFile = logFilePath + + // replace host and port with values that can be overridden by config + fakeDestination, err := ftwhttp.DestinationFromString("http://example.com:1234") + if err != nil { + t.Fatalf("Failed to parse fake destination") + } + + ftwTest, err := test.GetTestFromYaml([]byte(yamlTestOverrideWithNoPort)) + if err != nil { + t.Error(err) + } + replaceDestinationInTest(&ftwTest, *fakeDestination) + + // the test should succeed, despite the unknown override property + t.Run("showtime and execute all", func(t *testing.T) { + if res := Run("", "", false, true, 1*time.Second, 1*time.Second, []test.FTWTest{ftwTest}); res.Stats.TotalFailed() > 0 { + t.Error("Oops, test run failed!") + } + }) } func TestDisabledRun(t *testing.T) { - t.Cleanup(config.Reset) - - err := config.NewConfigFromString(yamlConfig) - if err != nil { - t.Errorf("Failed!") - } - - fakeDestination, err := ftwhttp.DestinationFromString("http://example.com:1234") - if err != nil { - t.Fatalf("Failed to parse fake destination") - } - - ftwTest, err := test.GetTestFromYaml([]byte(yamlDisabledTest)) - if err != nil { - t.Error(err) - } - replaceDestinationInTest(&ftwTest, *fakeDestination) - - t.Run("showtime and execute all", func(t *testing.T) { - if res := Run("*", "", false, true, []test.FTWTest{ftwTest}); res.Stats.TotalFailed() > 0 { - t.Error("Oops, test run failed!") - } - }) + t.Cleanup(config.Reset) + + err := config.NewConfigFromString(yamlConfig) + if err != nil { + t.Errorf("Failed!") + } + + fakeDestination, err := ftwhttp.DestinationFromString("http://example.com:1234") + if err != nil { + t.Fatalf("Failed to parse fake destination") + } + + ftwTest, err := test.GetTestFromYaml([]byte(yamlDisabledTest)) + if err != nil { + t.Error(err) + } + replaceDestinationInTest(&ftwTest, *fakeDestination) + + t.Run("showtime and execute all", func(t *testing.T) { + if res := Run("*", "", false, true, 1*time.Second, 1*time.Second, []test.FTWTest{ftwTest}); res.Stats.TotalFailed() > 0 { + t.Error("Oops, test run failed!") + } + }) } func TestLogsRun(t *testing.T) { - t.Cleanup(config.Reset) - - // setup test webserver (not a waf) - dest, logFilePath := newTestServer(t, logText) - - err := config.NewConfigFromString(yamlConfig) - if err != nil { - t.Errorf("Failed!") - } - replaceDestinationInConfiguration(*dest) - config.FTWConfig.LogFile = logFilePath - - ftwTest, err := test.GetTestFromYaml([]byte(yamlTestLogs)) - if err != nil { - t.Error(err) - } - replaceDestinationInTest(&ftwTest, *dest) - - t.Run("showtime and execute all", func(t *testing.T) { - if res := Run("", "", false, true, []test.FTWTest{ftwTest}); res.Stats.TotalFailed() > 0 { - t.Error("Oops, test run failed!") - } - }) + t.Cleanup(config.Reset) + + // setup test webserver (not a waf) + dest, logFilePath := newTestServer(t, logText) + + err := config.NewConfigFromString(yamlConfig) + if err != nil { + t.Errorf("Failed!") + } + replaceDestinationInConfiguration(*dest) + config.FTWConfig.LogFile = logFilePath + + ftwTest, err := test.GetTestFromYaml([]byte(yamlTestLogs)) + if err != nil { + t.Error(err) + } + replaceDestinationInTest(&ftwTest, *dest) + + t.Run("showtime and execute all", func(t *testing.T) { + if res := Run("", "", false, true, 1*time.Second, 1*time.Second, []test.FTWTest{ftwTest}); res.Stats.TotalFailed() > 0 { + t.Error("Oops, test run failed!") + } + }) } func TestCloudRun(t *testing.T) { - t.Cleanup(config.Reset) - - err := config.NewConfigFromString(yamlCloudConfig) - if err != nil { - t.Errorf("Failed!") - } - - ftwTestDummy, err := test.GetTestFromYaml([]byte(yamlTestLogs)) - if err != nil { - t.Error(err) - } - - t.Run("don't show time and execute all", func(t *testing.T) { - for testCaseIndex, testCaseDummy := range ftwTestDummy.Tests { - for stageIndex := range testCaseDummy.Stages { - // Read the tests for every stage so we can replace the destination - // in each run. The server needs to be configured for each stage - // individually. - ftwTest, err := test.GetTestFromYaml([]byte(yamlTestLogs)) - if err != nil { - t.Error(err) - } - testCase := &ftwTest.Tests[testCaseIndex] - stage := &testCase.Stages[stageIndex].Stage - - ftwCheck := check.NewCheck(config.FTWConfig) - - // this mirrors check.SetCloudMode() - responseStatus := 200 - if stage.Output.LogContains != "" { - responseStatus = 403 - } else if stage.Output.NoLogContains != "" { - responseStatus = 405 - } - server, dest := newTestServerForCloudTest(t, responseStatus, logText) - - replaceDestinationInConfiguration(*dest) - - replaceDestinationInTest(&ftwTest, *dest) - if err != nil { - t.Error(err) - } - runContext := TestRunContext{ - Include: "", - Exclude: "", - ShowTime: false, - Output: true, - Client: ftwhttp.NewClient(), - LogLines: nil, - } - - RunStage(&runContext, ftwCheck, *testCase, *stage) - if runContext.Stats.TotalFailed() > 0 { - t.Error("Oops, test run failed!") - } - - server.Close() - } - } - }) + t.Cleanup(config.Reset) + + err := config.NewConfigFromString(yamlCloudConfig) + if err != nil { + t.Errorf("Failed!") + } + + ftwTestDummy, err := test.GetTestFromYaml([]byte(yamlTestLogs)) + if err != nil { + t.Error(err) + } + + t.Run("don't show time and execute all", func(t *testing.T) { + for testCaseIndex, testCaseDummy := range ftwTestDummy.Tests { + for stageIndex := range testCaseDummy.Stages { + // Read the tests for every stage so we can replace the destination + // in each run. The server needs to be configured for each stage + // individually. + ftwTest, err := test.GetTestFromYaml([]byte(yamlTestLogs)) + if err != nil { + t.Error(err) + } + testCase := &ftwTest.Tests[testCaseIndex] + stage := &testCase.Stages[stageIndex].Stage + + ftwCheck := check.NewCheck(config.FTWConfig) + + // this mirrors check.SetCloudMode() + responseStatus := 200 + if stage.Output.LogContains != "" { + responseStatus = 403 + } else if stage.Output.NoLogContains != "" { + responseStatus = 405 + } + server, dest := newTestServerForCloudTest(t, responseStatus, logText) + + replaceDestinationInConfiguration(*dest) + + replaceDestinationInTest(&ftwTest, *dest) + if err != nil { + t.Error(err) + } + runContext := TestRunContext{ + Include: "", + Exclude: "", + ShowTime: false, + Output: true, + Client: ftwhttp.NewClient(ftwhttp.NewClientConfig()), + LogLines: nil, + } + + RunStage(&runContext, ftwCheck, *testCase, *stage) + if runContext.Stats.TotalFailed() > 0 { + t.Error("Oops, test run failed!") + } + + server.Close() + } + } + }) } func TestFailedTestsRun(t *testing.T) { - t.Cleanup(config.Reset) - - err := config.NewConfigFromString(yamlConfig) - dest, logFilePath := newTestServer(t, logText) - if err != nil { - t.Errorf("Failed!") - } - replaceDestinationInConfiguration(*dest) - config.FTWConfig.LogFile = logFilePath - - ftwTest, err := test.GetTestFromYaml([]byte(yamlFailedTest)) - if err != nil { - t.Error(err.Error()) - } - replaceDestinationInTest(&ftwTest, *dest) - - t.Run("run test that fails", func(t *testing.T) { - if res := Run("*", "", false, false, []test.FTWTest{ftwTest}); res.Stats.TotalFailed() != 1 { - t.Error("Oops, test run failed!") - } - }) + t.Cleanup(config.Reset) + + err := config.NewConfigFromString(yamlConfig) + dest, logFilePath := newTestServer(t, logText) + if err != nil { + t.Errorf("Failed!") + } + replaceDestinationInConfiguration(*dest) + config.FTWConfig.LogFile = logFilePath + + ftwTest, err := test.GetTestFromYaml([]byte(yamlFailedTest)) + if err != nil { + t.Error(err.Error()) + } + replaceDestinationInTest(&ftwTest, *dest) + + t.Run("run test that fails", func(t *testing.T) { + if res := Run("*", "", false, false, 1*time.Second, 1*time.Second, []test.FTWTest{ftwTest}); res.Stats.TotalFailed() != 1 { + t.Error("Oops, test run failed!") + } + }) } func TestApplyInputOverrideSetHostFromDestAddr(t *testing.T) { - t.Cleanup(config.Reset) - - originalHost := "original.com" - overrideHost := "override.com" - testInput := test.Input{ - DestAddr: &originalHost, - } - config.FTWConfig = &config.FTWConfiguration{ - TestOverride: config.FTWTestOverride{ - Input: test.Input{ - DestAddr: &overrideHost, - }, - }, - } - - err := applyInputOverride(&testInput) - if err != nil { - t.Error("Failed to apply input overrides", err) - } - - if *testInput.DestAddr != overrideHost { - t.Error("`dest_addr` should have been overridden") - } - if testInput.Headers == nil { - t.Error("Header map must exist after overriding `dest_addr`") - } - - hostHeader := testInput.Headers.Get("Host") - if hostHeader == "" { - t.Error("Host header must be set after overriding `dest_addr`") - } - if hostHeader != overrideHost { - t.Error("Host header must be identical to `dest_addr` after overrding `dest_addr`") - } + t.Cleanup(config.Reset) + + originalHost := "original.com" + overrideHost := "override.com" + testInput := test.Input{ + DestAddr: &originalHost, + } + config.FTWConfig = &config.FTWConfiguration{ + TestOverride: config.FTWTestOverride{ + Input: test.Input{ + DestAddr: &overrideHost, + }, + }, + } + + err := applyInputOverride(&testInput) + if err != nil { + t.Error("Failed to apply input overrides", err) + } + + if *testInput.DestAddr != overrideHost { + t.Error("`dest_addr` should have been overridden") + } + if testInput.Headers == nil { + t.Error("Header map must exist after overriding `dest_addr`") + } + + hostHeader := testInput.Headers.Get("Host") + if hostHeader == "" { + t.Error("Host header must be set after overriding `dest_addr`") + } + if hostHeader != overrideHost { + t.Error("Host header must be identical to `dest_addr` after overrding `dest_addr`") + } } From 85fe31b7e792bceb4e1e3fbc9a5d973b0103c6ea Mon Sep 17 00:00:00 2001 From: Anuraag Agrawal Date: Mon, 5 Sep 2022 11:43:01 +0900 Subject: [PATCH 3/4] Add Config --- cmd/run.go | 20 +- runner/run.go | 47 ++- runner/run_test.go | 835 +++++++++++++++++++++++---------------------- runner/types.go | 21 +- 4 files changed, 485 insertions(+), 438 deletions(-) diff --git a/cmd/run.go b/cmd/run.go index f294ee1..edd612a 100644 --- a/cmd/run.go +++ b/cmd/run.go @@ -3,6 +3,7 @@ package cmd import ( "fmt" "os" + "regexp" "time" "github.com/kyokomi/emoji" @@ -46,7 +47,24 @@ var runCmd = &cobra.Command{ log.Fatal().Err(err) } - currentRun := runner.Run(include, exclude, showTime, quiet, connectTimeout, readTimeout, tests) + var includeRE *regexp.Regexp + if include != "" { + includeRE = regexp.MustCompile(include) + } + var excludeRE *regexp.Regexp + if exclude != "" { + excludeRE = regexp.MustCompile(exclude) + } + + currentRun := runner.Run(tests, runner.Config{ + Include: includeRE, + Exclude: excludeRE, + ShowTime: showTime, + Quiet: quiet, + ConnectTimeout: connectTimeout, + ReadTimeout: readTimeout, + }) + os.Exit(currentRun.Stats.TotalFailed()) }, } diff --git a/runner/run.go b/runner/run.go index 5f73a14..cef5d79 100644 --- a/runner/run.go +++ b/runner/run.go @@ -17,35 +17,35 @@ import ( "github.com/rs/zerolog/log" ) -// Run runs your tests -// testid is the name of the unique test you want to run -// exclude is a regexp that matches the test name: e.g. "920*", excludes all tests starting with "920" -// Returns error if some test failed -func Run(include string, exclude string, showTime bool, output bool, - connectTimeout time.Duration, readTimeout time.Duration, ftwtests []test.FTWTest) TestRunContext { - printUnlessQuietMode(output, ":rocket:Running go-ftw!\n") +// Run runs your tests with the specified Config. Returns error if some test failed +func Run(tests []test.FTWTest, c Config) TestRunContext { + printUnlessQuietMode(c.Quiet, ":rocket:Running go-ftw!\n") logLines := waflog.NewFTWLogLines(waflog.WithLogFile(config.FTWConfig.LogFile)) conf := ftwhttp.NewClientConfig() - conf.ConnectTimeout = connectTimeout - conf.ReadTimeout = readTimeout + if c.ConnectTimeout != 0 { + conf.ConnectTimeout = c.ConnectTimeout + } + if c.ReadTimeout != 0 { + conf.ReadTimeout = c.ReadTimeout + } client := ftwhttp.NewClient(conf) runContext := TestRunContext{ - Include: include, - Exclude: exclude, - ShowTime: showTime, - Output: output, + Include: c.Include, + Exclude: c.Exclude, + ShowTime: c.ShowTime, + Output: c.Quiet, Client: client, LogLines: logLines, RunMode: config.FTWConfig.RunMode, } - for _, test := range ftwtests { + for _, test := range tests { RunTest(&runContext, test) } - printSummary(output, runContext.Stats) + printSummary(c.Quiet, runContext.Stats) defer cleanLogs(logLines) @@ -210,16 +210,15 @@ 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.LogLines.FileName) } -func needToSkipTest(include string, exclude string, title string, enabled bool) bool { +func needToSkipTest(include *regexp.Regexp, exclude *regexp.Regexp, title string, enabled bool) bool { // skip disabled tests if !enabled { return true } // never skip enabled explicit inclusions - if include != "" { - ok, err := regexp.MatchString(include, title) - if ok && err == nil { + if include != nil { + if include.MatchString(title) { // inclusion always wins over exclusion return false } @@ -228,18 +227,16 @@ func needToSkipTest(include string, exclude string, title string, enabled bool) result := false // if we need to exclude tests, and the title matches, // it needs to be skipped - if exclude != "" { - ok, err := regexp.MatchString(exclude, title) - if ok && err == nil { + if exclude != nil { + if exclude.MatchString(title) { result = true } } // if we need to include tests, but the title does not match // it needs to be skipped - if include != "" { - ok, err := regexp.MatchString(include, title) - if !ok && err == nil { + if include != nil { + if !include.MatchString(title) { result = true } } diff --git a/runner/run_test.go b/runner/run_test.go index 3075aef..c1e9928 100644 --- a/runner/run_test.go +++ b/runner/run_test.go @@ -1,19 +1,19 @@ package runner import ( - "fmt" - "net/http" - "net/http/httptest" - "os" - "testing" - "time" - - "github.com/fzipi/go-ftw/check" - "github.com/fzipi/go-ftw/config" - "github.com/fzipi/go-ftw/ftwhttp" - "github.com/fzipi/go-ftw/test" - - "github.com/rs/zerolog/log" + "fmt" + "net/http" + "net/http/httptest" + "os" + "regexp" + "testing" + + "github.com/rs/zerolog/log" + + "github.com/fzipi/go-ftw/check" + "github.com/fzipi/go-ftw/config" + "github.com/fzipi/go-ftw/ftwhttp" + "github.com/fzipi/go-ftw/test" ) var yamlConfig = ` @@ -278,448 +278,463 @@ tests: // Error checking omitted for brevity func newTestServer(t *testing.T, logLines string) (destination *ftwhttp.Destination, logFilePath string) { - logFilePath = setUpLogFileForTestServer(t) + logFilePath = setUpLogFileForTestServer(t) - ts := httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) { - w.WriteHeader(http.StatusOK) - _, _ = w.Write([]byte("Hello, client")) + ts := httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) { + w.WriteHeader(http.StatusOK) + _, _ = w.Write([]byte("Hello, client")) - writeTestServerLog(t, logLines, logFilePath, r) - })) + writeTestServerLog(t, logLines, logFilePath, r) + })) - // close server after test - t.Cleanup(ts.Close) + // close server after test + t.Cleanup(ts.Close) - dest, err := ftwhttp.DestinationFromString(ts.URL) - if err != nil { - t.Error(err) - t.FailNow() - } - return dest, logFilePath + dest, err := ftwhttp.DestinationFromString(ts.URL) + if err != nil { + t.Error(err) + t.FailNow() + } + return dest, logFilePath } // Error checking omitted for brevity func newTestServerForCloudTest(t *testing.T, responseStatus int, logLines string) (server *httptest.Server, destination *ftwhttp.Destination) { - server = httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) { - w.WriteHeader(responseStatus) - _, _ = w.Write([]byte("Hello, client")) - })) - - // close server after test - t.Cleanup(server.Close) - - dest, err := ftwhttp.DestinationFromString(server.URL) - if err != nil { - t.Error(err) - t.FailNow() - } - return server, dest + server = httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) { + w.WriteHeader(responseStatus) + _, _ = w.Write([]byte("Hello, client")) + })) + + // close server after test + t.Cleanup(server.Close) + + dest, err := ftwhttp.DestinationFromString(server.URL) + if err != nil { + t.Error(err) + t.FailNow() + } + return server, dest } func setUpLogFileForTestServer(t *testing.T) (logFilePath string) { - // log to the configured file - if config.FTWConfig != nil && config.FTWConfig.RunMode == config.DefaultRunMode { - logFilePath = config.FTWConfig.LogFile - } - // if no file has been configured, create one and handle cleanup - if logFilePath == "" { - file, err := os.CreateTemp("", "go-ftw-test-*.log") - if err != nil { - t.Error(err) - } - logFilePath = file.Name() - t.Cleanup(func() { - os.Remove(logFilePath) - log.Info().Msgf("Deleting temporary file '%s'", logFilePath) - }) - } - return logFilePath + // log to the configured file + if config.FTWConfig != nil && config.FTWConfig.RunMode == config.DefaultRunMode { + logFilePath = config.FTWConfig.LogFile + } + // if no file has been configured, create one and handle cleanup + if logFilePath == "" { + file, err := os.CreateTemp("", "go-ftw-test-*.log") + if err != nil { + t.Error(err) + } + logFilePath = file.Name() + t.Cleanup(func() { + os.Remove(logFilePath) + log.Info().Msgf("Deleting temporary file '%s'", logFilePath) + }) + } + return logFilePath } func writeTestServerLog(t *testing.T, logLines string, logFilePath string, r *http.Request) { - // write supplied log lines, emulating the output of the rule engine - logMessage := logLines - // if the request has the special test header, log the request instead - // this emulates the log marker rule - if r.Header.Get(config.FTWConfig.LogMarkerHeaderName) != "" { - logMessage = fmt.Sprintf("request line: %s %s %s, headers: %s\n", r.Method, r.RequestURI, r.Proto, r.Header) - } - file, err := os.OpenFile(logFilePath, os.O_WRONLY|os.O_APPEND|os.O_CREATE, 0600) - if err != nil { - t.Error(err) - t.FailNow() - } - defer file.Close() - - _, err = file.WriteString(logMessage) - if err != nil { - t.Error(err) - t.FailNow() - } + // write supplied log lines, emulating the output of the rule engine + logMessage := logLines + // if the request has the special test header, log the request instead + // this emulates the log marker rule + if r.Header.Get(config.FTWConfig.LogMarkerHeaderName) != "" { + logMessage = fmt.Sprintf("request line: %s %s %s, headers: %s\n", r.Method, r.RequestURI, r.Proto, r.Header) + } + file, err := os.OpenFile(logFilePath, os.O_WRONLY|os.O_APPEND|os.O_CREATE, 0600) + if err != nil { + t.Error(err) + t.FailNow() + } + defer file.Close() + + _, err = file.WriteString(logMessage) + if err != nil { + t.Error(err) + t.FailNow() + } } func replaceDestinationInTest(ftwTest *test.FTWTest, d ftwhttp.Destination) { - // This function doesn't use `range` because we want to modify the struct in place. - // Range (and assignments in general) create copies of structs, not references. - // Maps, slices, etc. on the other hand, are assigned as references. - for testIndex := 0; testIndex < len(ftwTest.Tests); testIndex++ { - testCase := &ftwTest.Tests[testIndex] - for stageIndex := 0; stageIndex < len(testCase.Stages); stageIndex++ { - input := &testCase.Stages[stageIndex].Stage.Input - - if *input.DestAddr == "TEST_ADDR" { - input.DestAddr = &d.DestAddr - } - if input.Headers.Get("Host") == "TEST_ADDR" { - input.Headers.Set("Host", d.DestAddr) - } - if input.Port != nil && *input.Port == -1 { - input.Port = &d.Port - } - } - } + // This function doesn't use `range` because we want to modify the struct in place. + // Range (and assignments in general) create copies of structs, not references. + // Maps, slices, etc. on the other hand, are assigned as references. + for testIndex := 0; testIndex < len(ftwTest.Tests); testIndex++ { + testCase := &ftwTest.Tests[testIndex] + for stageIndex := 0; stageIndex < len(testCase.Stages); stageIndex++ { + input := &testCase.Stages[stageIndex].Stage.Input + + if *input.DestAddr == "TEST_ADDR" { + input.DestAddr = &d.DestAddr + } + if input.Headers.Get("Host") == "TEST_ADDR" { + input.Headers.Set("Host", d.DestAddr) + } + if input.Port != nil && *input.Port == -1 { + input.Port = &d.Port + } + } + } } func replaceDestinationInConfiguration(dest ftwhttp.Destination) { - replaceableAddress := "TEST_ADDR" - replaceablePort := -1 - - input := &config.FTWConfig.TestOverride.Input - if input.DestAddr != nil && *input.DestAddr == replaceableAddress { - input.DestAddr = &dest.DestAddr - } - if input.Port != nil && *input.Port == replaceablePort { - input.Port = &dest.Port - } + replaceableAddress := "TEST_ADDR" + replaceablePort := -1 + + input := &config.FTWConfig.TestOverride.Input + if input.DestAddr != nil && *input.DestAddr == replaceableAddress { + input.DestAddr = &dest.DestAddr + } + if input.Port != nil && *input.Port == replaceablePort { + input.Port = &dest.Port + } } func TestRun(t *testing.T) { - t.Cleanup(config.Reset) - - err := config.NewConfigFromString(yamlConfig) - if err != nil { - t.Errorf("Failed!") - } - - // setup test webserver (not a waf) - dest, logFilePath := newTestServer(t, logText) - config.FTWConfig.LogFile = logFilePath - ftwTest, err := test.GetTestFromYaml([]byte(yamlTest)) - if err != nil { - t.Error(err) - } - replaceDestinationInTest(&ftwTest, *dest) - - t.Run("show time and execute all", func(t *testing.T) { - if res := Run("", "", true, false, 1*time.Second, 1*time.Second, []test.FTWTest{ftwTest}); res.Stats.TotalFailed() > 0 { - t.Errorf("Oops, %d tests failed to run!", res.Stats.TotalFailed()) - } - }) - - t.Run("be verbose and execute all", func(t *testing.T) { - if res := Run("0*", "", true, true, 1*time.Second, 1*time.Second, []test.FTWTest{ftwTest}); res.Stats.TotalFailed() > 0 { - t.Error("Oops, test run failed!") - } - }) - - t.Run("don't show time and execute all", func(t *testing.T) { - if res := Run("0*", "", false, false, 1*time.Second, 1*time.Second, []test.FTWTest{ftwTest}); res.Stats.TotalFailed() > 0 { - t.Error("Oops, test run failed!") - } - }) - - t.Run("execute only test 008 but exclude all", func(t *testing.T) { - if res := Run("008", "0*", false, false, 1*time.Second, 1*time.Second, []test.FTWTest{ftwTest}); res.Stats.TotalFailed() > 0 { - t.Error("Oops, test run failed!") - } - }) - - t.Run("exclude test 010", func(t *testing.T) { - if res := Run("*", "010", false, false, 1*time.Second, 1*time.Second, []test.FTWTest{ftwTest}); res.Stats.TotalFailed() > 0 { - t.Error("Oops, test run failed!") - } - }) - - t.Run("test exceptions 1", func(t *testing.T) { - if res := Run("1*", "0*", false, true, 1*time.Second, 1*time.Second, []test.FTWTest{ftwTest}); res.Stats.TotalFailed() > 0 { - t.Error("Oops, test run failed!") - } - }) + t.Cleanup(config.Reset) + + err := config.NewConfigFromString(yamlConfig) + if err != nil { + t.Errorf("Failed!") + } + + // setup test webserver (not a waf) + dest, logFilePath := newTestServer(t, logText) + config.FTWConfig.LogFile = logFilePath + ftwTest, err := test.GetTestFromYaml([]byte(yamlTest)) + if err != nil { + t.Error(err) + } + replaceDestinationInTest(&ftwTest, *dest) + + t.Run("show time and execute all", func(t *testing.T) { + if res := Run([]test.FTWTest{ftwTest}, Config{ + ShowTime: true, + Quiet: true, + }); res.Stats.TotalFailed() > 0 { + t.Errorf("Oops, %d tests failed to run!", res.Stats.TotalFailed()) + } + }) + + t.Run("be verbose and execute all", func(t *testing.T) { + if res := Run([]test.FTWTest{ftwTest}, Config{ + Include: regexp.MustCompile("0*"), + ShowTime: true, + }); res.Stats.TotalFailed() > 0 { + t.Error("Oops, test run failed!") + } + }) + + t.Run("don't show time and execute all", func(t *testing.T) { + if res := Run([]test.FTWTest{ftwTest}, Config{ + Include: regexp.MustCompile("0*"), + }); res.Stats.TotalFailed() > 0 { + t.Error("Oops, test run failed!") + } + }) + + t.Run("execute only test 008 but exclude all", func(t *testing.T) { + if res := Run([]test.FTWTest{ftwTest}, Config{ + Include: regexp.MustCompile("008"), + Exclude: regexp.MustCompile("0*"), + }); res.Stats.TotalFailed() > 0 { + t.Error("Oops, test run failed!") + } + }) + + t.Run("exclude test 010", func(t *testing.T) { + if res := Run([]test.FTWTest{ftwTest}, Config{ + Exclude: regexp.MustCompile("010"), + }); res.Stats.TotalFailed() > 0 { + t.Error("Oops, test run failed!") + } + }) + + t.Run("test exceptions 1", func(t *testing.T) { + if res := Run([]test.FTWTest{ftwTest}, Config{ + Include: regexp.MustCompile("1*"), + Exclude: regexp.MustCompile("0*"), + Quiet: true, + }); res.Stats.TotalFailed() > 0 { + t.Error("Oops, test run failed!") + } + }) } func TestOverrideRun(t *testing.T) { - t.Cleanup(config.Reset) - - // setup test webserver (not a waf) - err := config.NewConfigFromString(yamlConfigOverride) - if err != nil { - t.Error(err) - } - - dest, logFilePath := newTestServer(t, logText) - - replaceDestinationInConfiguration(*dest) - config.FTWConfig.LogFile = logFilePath - - // replace host and port with values that can be overridden by config - fakeDestination, err := ftwhttp.DestinationFromString("http://example.com:1234") - if err != nil { - t.Fatalf("Failed to parse fake destination") - } - - ftwTest, err := test.GetTestFromYaml([]byte(yamlTestOverride)) - if err != nil { - t.Error(err) - } - replaceDestinationInTest(&ftwTest, *fakeDestination) - - t.Run("override and execute all", func(t *testing.T) { - if res := Run("", "", false, true, 1*time.Second, 1*time.Second, []test.FTWTest{ftwTest}); res.Stats.TotalFailed() > 0 { - t.Error("Oops, test run failed!") - } - }) + t.Cleanup(config.Reset) + + // setup test webserver (not a waf) + err := config.NewConfigFromString(yamlConfigOverride) + if err != nil { + t.Error(err) + } + + dest, logFilePath := newTestServer(t, logText) + + replaceDestinationInConfiguration(*dest) + config.FTWConfig.LogFile = logFilePath + + // replace host and port with values that can be overridden by config + fakeDestination, err := ftwhttp.DestinationFromString("http://example.com:1234") + if err != nil { + t.Fatalf("Failed to parse fake destination") + } + + ftwTest, err := test.GetTestFromYaml([]byte(yamlTestOverride)) + if err != nil { + t.Error(err) + } + replaceDestinationInTest(&ftwTest, *fakeDestination) + + if res := Run([]test.FTWTest{ftwTest}, Config{ + Quiet: true, + }); res.Stats.TotalFailed() > 0 { + t.Error("Oops, test run failed!") + } } func TestBrokenOverrideRun(t *testing.T) { - t.Cleanup(config.Reset) - - err := config.NewConfigFromString(yamlBrokenConfigOverride) - if err != nil { - t.Errorf("Failed!") - } - - dest, logFilePath := newTestServer(t, logText) - - replaceDestinationInConfiguration(*dest) - config.FTWConfig.LogFile = logFilePath - - // replace host and port with values that can be overridden by config - fakeDestination, err := ftwhttp.DestinationFromString("http://example.com:1234") - if err != nil { - t.Fatalf("Failed to parse fake destination") - } - - ftwTest, err := test.GetTestFromYaml([]byte(yamlTestOverride)) - if err != nil { - t.Error(err) - } - replaceDestinationInTest(&ftwTest, *fakeDestination) - - // the test should succeed, despite the unknown override property - t.Run("showtime and execute all", func(t *testing.T) { - if res := Run("", "", false, true, 1*time.Second, 1*time.Second, []test.FTWTest{ftwTest}); res.Stats.TotalFailed() > 0 { - t.Error("Oops, test run failed!") - } - }) + t.Cleanup(config.Reset) + + err := config.NewConfigFromString(yamlBrokenConfigOverride) + if err != nil { + t.Errorf("Failed!") + } + + dest, logFilePath := newTestServer(t, logText) + + replaceDestinationInConfiguration(*dest) + config.FTWConfig.LogFile = logFilePath + + // replace host and port with values that can be overridden by config + fakeDestination, err := ftwhttp.DestinationFromString("http://example.com:1234") + if err != nil { + t.Fatalf("Failed to parse fake destination") + } + + ftwTest, err := test.GetTestFromYaml([]byte(yamlTestOverride)) + if err != nil { + t.Error(err) + } + replaceDestinationInTest(&ftwTest, *fakeDestination) + + // the test should succeed, despite the unknown override property + if res := Run([]test.FTWTest{ftwTest}, Config{ + Quiet: true, + }); res.Stats.TotalFailed() > 0 { + t.Error("Oops, test run failed!") + } } func TestBrokenPortOverrideRun(t *testing.T) { - t.Cleanup(config.Reset) - - // TestServer initialized first to retrieve the correct port number - dest, logFilePath := newTestServer(t, logText) - - // replace destination port inside the yaml with the retrieved one - err := config.NewConfigFromString(fmt.Sprintf(yamlConfigPortOverride, dest.Port)) - if err != nil { - t.Errorf("Failed!") - } - - replaceDestinationInConfiguration(*dest) - config.FTWConfig.LogFile = logFilePath - - // replace host and port with values that can be overridden by config - fakeDestination, err := ftwhttp.DestinationFromString("http://example.com:1234") - if err != nil { - t.Fatalf("Failed to parse fake destination") - } - - ftwTest, err := test.GetTestFromYaml([]byte(yamlTestOverrideWithNoPort)) - if err != nil { - t.Error(err) - } - replaceDestinationInTest(&ftwTest, *fakeDestination) - - // the test should succeed, despite the unknown override property - t.Run("showtime and execute all", func(t *testing.T) { - if res := Run("", "", false, true, 1*time.Second, 1*time.Second, []test.FTWTest{ftwTest}); res.Stats.TotalFailed() > 0 { - t.Error("Oops, test run failed!") - } - }) + t.Cleanup(config.Reset) + + // TestServer initialized first to retrieve the correct port number + dest, logFilePath := newTestServer(t, logText) + + // replace destination port inside the yaml with the retrieved one + err := config.NewConfigFromString(fmt.Sprintf(yamlConfigPortOverride, dest.Port)) + if err != nil { + t.Errorf("Failed!") + } + + replaceDestinationInConfiguration(*dest) + config.FTWConfig.LogFile = logFilePath + + // replace host and port with values that can be overridden by config + fakeDestination, err := ftwhttp.DestinationFromString("http://example.com:1234") + if err != nil { + t.Fatalf("Failed to parse fake destination") + } + + ftwTest, err := test.GetTestFromYaml([]byte(yamlTestOverrideWithNoPort)) + if err != nil { + t.Error(err) + } + replaceDestinationInTest(&ftwTest, *fakeDestination) + + // the test should succeed, despite the unknown override property + if res := Run([]test.FTWTest{ftwTest}, Config{ + Quiet: true, + }); res.Stats.TotalFailed() > 0 { + t.Error("Oops, test run failed!") + } } func TestDisabledRun(t *testing.T) { - t.Cleanup(config.Reset) - - err := config.NewConfigFromString(yamlConfig) - if err != nil { - t.Errorf("Failed!") - } - - fakeDestination, err := ftwhttp.DestinationFromString("http://example.com:1234") - if err != nil { - t.Fatalf("Failed to parse fake destination") - } - - ftwTest, err := test.GetTestFromYaml([]byte(yamlDisabledTest)) - if err != nil { - t.Error(err) - } - replaceDestinationInTest(&ftwTest, *fakeDestination) - - t.Run("showtime and execute all", func(t *testing.T) { - if res := Run("*", "", false, true, 1*time.Second, 1*time.Second, []test.FTWTest{ftwTest}); res.Stats.TotalFailed() > 0 { - t.Error("Oops, test run failed!") - } - }) + t.Cleanup(config.Reset) + + err := config.NewConfigFromString(yamlConfig) + if err != nil { + t.Errorf("Failed!") + } + + fakeDestination, err := ftwhttp.DestinationFromString("http://example.com:1234") + if err != nil { + t.Fatalf("Failed to parse fake destination") + } + + ftwTest, err := test.GetTestFromYaml([]byte(yamlDisabledTest)) + if err != nil { + t.Error(err) + } + replaceDestinationInTest(&ftwTest, *fakeDestination) + + if res := Run([]test.FTWTest{ftwTest}, Config{ + Quiet: true, + }); res.Stats.TotalFailed() > 0 { + t.Error("Oops, test run failed!") + } } func TestLogsRun(t *testing.T) { - t.Cleanup(config.Reset) - - // setup test webserver (not a waf) - dest, logFilePath := newTestServer(t, logText) - - err := config.NewConfigFromString(yamlConfig) - if err != nil { - t.Errorf("Failed!") - } - replaceDestinationInConfiguration(*dest) - config.FTWConfig.LogFile = logFilePath - - ftwTest, err := test.GetTestFromYaml([]byte(yamlTestLogs)) - if err != nil { - t.Error(err) - } - replaceDestinationInTest(&ftwTest, *dest) - - t.Run("showtime and execute all", func(t *testing.T) { - if res := Run("", "", false, true, 1*time.Second, 1*time.Second, []test.FTWTest{ftwTest}); res.Stats.TotalFailed() > 0 { - t.Error("Oops, test run failed!") - } - }) + t.Cleanup(config.Reset) + + // setup test webserver (not a waf) + dest, logFilePath := newTestServer(t, logText) + + err := config.NewConfigFromString(yamlConfig) + if err != nil { + t.Errorf("Failed!") + } + replaceDestinationInConfiguration(*dest) + config.FTWConfig.LogFile = logFilePath + + ftwTest, err := test.GetTestFromYaml([]byte(yamlTestLogs)) + if err != nil { + t.Error(err) + } + replaceDestinationInTest(&ftwTest, *dest) + + if res := Run([]test.FTWTest{ftwTest}, Config{ + Quiet: true, + }); res.Stats.TotalFailed() > 0 { + t.Error("Oops, test run failed!") + } } func TestCloudRun(t *testing.T) { - t.Cleanup(config.Reset) - - err := config.NewConfigFromString(yamlCloudConfig) - if err != nil { - t.Errorf("Failed!") - } - - ftwTestDummy, err := test.GetTestFromYaml([]byte(yamlTestLogs)) - if err != nil { - t.Error(err) - } - - t.Run("don't show time and execute all", func(t *testing.T) { - for testCaseIndex, testCaseDummy := range ftwTestDummy.Tests { - for stageIndex := range testCaseDummy.Stages { - // Read the tests for every stage so we can replace the destination - // in each run. The server needs to be configured for each stage - // individually. - ftwTest, err := test.GetTestFromYaml([]byte(yamlTestLogs)) - if err != nil { - t.Error(err) - } - testCase := &ftwTest.Tests[testCaseIndex] - stage := &testCase.Stages[stageIndex].Stage - - ftwCheck := check.NewCheck(config.FTWConfig) - - // this mirrors check.SetCloudMode() - responseStatus := 200 - if stage.Output.LogContains != "" { - responseStatus = 403 - } else if stage.Output.NoLogContains != "" { - responseStatus = 405 - } - server, dest := newTestServerForCloudTest(t, responseStatus, logText) - - replaceDestinationInConfiguration(*dest) - - replaceDestinationInTest(&ftwTest, *dest) - if err != nil { - t.Error(err) - } - runContext := TestRunContext{ - Include: "", - Exclude: "", - ShowTime: false, - Output: true, - Client: ftwhttp.NewClient(ftwhttp.NewClientConfig()), - LogLines: nil, - } - - RunStage(&runContext, ftwCheck, *testCase, *stage) - if runContext.Stats.TotalFailed() > 0 { - t.Error("Oops, test run failed!") - } - - server.Close() - } - } - }) + t.Cleanup(config.Reset) + + err := config.NewConfigFromString(yamlCloudConfig) + if err != nil { + t.Errorf("Failed!") + } + + ftwTestDummy, err := test.GetTestFromYaml([]byte(yamlTestLogs)) + if err != nil { + t.Error(err) + } + + t.Run("don't show time and execute all", func(t *testing.T) { + for testCaseIndex, testCaseDummy := range ftwTestDummy.Tests { + for stageIndex := range testCaseDummy.Stages { + // Read the tests for every stage so we can replace the destination + // in each run. The server needs to be configured for each stage + // individually. + ftwTest, err := test.GetTestFromYaml([]byte(yamlTestLogs)) + if err != nil { + t.Error(err) + } + testCase := &ftwTest.Tests[testCaseIndex] + stage := &testCase.Stages[stageIndex].Stage + + ftwCheck := check.NewCheck(config.FTWConfig) + + // this mirrors check.SetCloudMode() + responseStatus := 200 + if stage.Output.LogContains != "" { + responseStatus = 403 + } else if stage.Output.NoLogContains != "" { + responseStatus = 405 + } + server, dest := newTestServerForCloudTest(t, responseStatus, logText) + + replaceDestinationInConfiguration(*dest) + + replaceDestinationInTest(&ftwTest, *dest) + if err != nil { + t.Error(err) + } + runContext := TestRunContext{ + Include: nil, + Exclude: nil, + ShowTime: false, + Output: true, + Client: ftwhttp.NewClient(ftwhttp.NewClientConfig()), + LogLines: nil, + } + + RunStage(&runContext, ftwCheck, *testCase, *stage) + if runContext.Stats.TotalFailed() > 0 { + t.Error("Oops, test run failed!") + } + + server.Close() + } + } + }) } func TestFailedTestsRun(t *testing.T) { - t.Cleanup(config.Reset) - - err := config.NewConfigFromString(yamlConfig) - dest, logFilePath := newTestServer(t, logText) - if err != nil { - t.Errorf("Failed!") - } - replaceDestinationInConfiguration(*dest) - config.FTWConfig.LogFile = logFilePath - - ftwTest, err := test.GetTestFromYaml([]byte(yamlFailedTest)) - if err != nil { - t.Error(err.Error()) - } - replaceDestinationInTest(&ftwTest, *dest) - - t.Run("run test that fails", func(t *testing.T) { - if res := Run("*", "", false, false, 1*time.Second, 1*time.Second, []test.FTWTest{ftwTest}); res.Stats.TotalFailed() != 1 { - t.Error("Oops, test run failed!") - } - }) + t.Cleanup(config.Reset) + + err := config.NewConfigFromString(yamlConfig) + dest, logFilePath := newTestServer(t, logText) + if err != nil { + t.Errorf("Failed!") + } + replaceDestinationInConfiguration(*dest) + config.FTWConfig.LogFile = logFilePath + + ftwTest, err := test.GetTestFromYaml([]byte(yamlFailedTest)) + if err != nil { + t.Error(err.Error()) + } + replaceDestinationInTest(&ftwTest, *dest) + + if res := Run([]test.FTWTest{ftwTest}, Config{}); res.Stats.TotalFailed() != 1 { + t.Error("Oops, test run failed!") + } } func TestApplyInputOverrideSetHostFromDestAddr(t *testing.T) { - t.Cleanup(config.Reset) - - originalHost := "original.com" - overrideHost := "override.com" - testInput := test.Input{ - DestAddr: &originalHost, - } - config.FTWConfig = &config.FTWConfiguration{ - TestOverride: config.FTWTestOverride{ - Input: test.Input{ - DestAddr: &overrideHost, - }, - }, - } - - err := applyInputOverride(&testInput) - if err != nil { - t.Error("Failed to apply input overrides", err) - } - - if *testInput.DestAddr != overrideHost { - t.Error("`dest_addr` should have been overridden") - } - if testInput.Headers == nil { - t.Error("Header map must exist after overriding `dest_addr`") - } - - hostHeader := testInput.Headers.Get("Host") - if hostHeader == "" { - t.Error("Host header must be set after overriding `dest_addr`") - } - if hostHeader != overrideHost { - t.Error("Host header must be identical to `dest_addr` after overrding `dest_addr`") - } + t.Cleanup(config.Reset) + + originalHost := "original.com" + overrideHost := "override.com" + testInput := test.Input{ + DestAddr: &originalHost, + } + config.FTWConfig = &config.FTWConfiguration{ + TestOverride: config.FTWTestOverride{ + Input: test.Input{ + DestAddr: &overrideHost, + }, + }, + } + + err := applyInputOverride(&testInput) + if err != nil { + t.Error("Failed to apply input overrides", err) + } + + if *testInput.DestAddr != overrideHost { + t.Error("`dest_addr` should have been overridden") + } + if testInput.Headers == nil { + t.Error("Header map must exist after overriding `dest_addr`") + } + + hostHeader := testInput.Headers.Get("Host") + if hostHeader == "" { + t.Error("Host header must be set after overriding `dest_addr`") + } + if hostHeader != overrideHost { + t.Error("Host header must be identical to `dest_addr` after overrding `dest_addr`") + } } diff --git a/runner/types.go b/runner/types.go index 9cc1ce6..a4bf40d 100644 --- a/runner/types.go +++ b/runner/types.go @@ -1,6 +1,7 @@ package runner import ( + "regexp" "time" "github.com/fzipi/go-ftw/config" @@ -8,12 +9,28 @@ import ( "github.com/fzipi/go-ftw/waflog" ) +// Config provides configuration for the test runner. +type Config struct { + // Include is a regular expression to filter tests to include. If nil, all tests are included. + Include *regexp.Regexp + // Exclude is a regular expression to filter tests to exclude. If nil, no tests are excluded. + Exclude *regexp.Regexp + // ShowTime determines whether to show the time taken to run each test. + ShowTime bool + // Quiet determines whether to output informational messages. + Quiet bool + // ConnectTimeout is the timeout for connecting to endpoints during test execution. + ConnectTimeout time.Duration + // ReadTimeout is the timeout for receiving responses during test execution. + ReadTimeout time.Duration +} + // TestRunContext carries information about the current test run. // This includes both configuration information as well as statistics // and results. type TestRunContext struct { - Include string - Exclude string + Include *regexp.Regexp + Exclude *regexp.Regexp ShowTime bool Output bool Stats TestStats From fa9612c366fe0da37905b9b44786ff30a9ce7fa2 Mon Sep 17 00:00:00 2001 From: Anuraag Agrawal Date: Mon, 5 Sep 2022 11:46:01 +0900 Subject: [PATCH 4/4] Formatting --- ftwhttp/client.go | 5 +++-- 1 file changed, 3 insertions(+), 2 deletions(-) diff --git a/ftwhttp/client.go b/ftwhttp/client.go index e02332b..3b4331a 100644 --- a/ftwhttp/client.go +++ b/ftwhttp/client.go @@ -3,12 +3,13 @@ package ftwhttp import ( "crypto/tls" "fmt" - "github.com/rs/zerolog/log" - "golang.org/x/net/publicsuffix" "net" "net/http/cookiejar" "strings" "time" + + "github.com/rs/zerolog/log" + "golang.org/x/net/publicsuffix" ) // NewClientConfig returns a new ClientConfig with reasonable defaults.