From 9400f7bf07c112e79dbadfb0cc4acd075bb87190 Mon Sep 17 00:00:00 2001 From: Xunzhuo Date: Tue, 7 Feb 2023 15:28:40 +0800 Subject: [PATCH] refactor: higress test framework (#178) Signed-off-by: bitliu --- .../httproute-hostname-same-namespace.go | 122 ++++++++---- .../tests/httproute-rewrite-path.go | 24 ++- .../tests/httproute-simple-same-namespace.go | 21 ++- test/ingress/conformance/utils/http/http.go | 176 +++++++++--------- 4 files changed, 211 insertions(+), 132 deletions(-) diff --git a/test/ingress/conformance/tests/httproute-hostname-same-namespace.go b/test/ingress/conformance/tests/httproute-hostname-same-namespace.go index 362613485..20a822de5 100644 --- a/test/ingress/conformance/tests/httproute-hostname-same-namespace.go +++ b/test/ingress/conformance/tests/httproute-hostname-same-namespace.go @@ -30,42 +30,94 @@ var HTTPRouteHostNameSameNamespace = suite.ConformanceTest{ Description: "A Ingress in the higress-conformance-infra namespace demonstrates host match ability", Manifests: []string{"tests/httproute-hostname-same-namespace.yaml"}, Test: func(t *testing.T, suite *suite.ConformanceTestSuite) { + testcases := []http.Assertion{ + { + Meta: http.AssertionMeta{ + TargetBackend: "infra-backend-v1", + TargetNamespace: "higress-conformance-infra", + }, + Request: http.AssertionRequest{ + ActualRequest: http.Request{ + Path: "/foo", + Host: "foo.com", + }, + }, + Response: http.AssertionResponse{ + ExpectedResponse: http.Response{ + StatusCode: 200, + }, + }, + }, { + Meta: http.AssertionMeta{ + TargetBackend: "infra-backend-v2", + TargetNamespace: "higress-conformance-infra", + }, + Request: http.AssertionRequest{ + ActualRequest: http.Request{ + Path: "/foo", + Host: "bar.com", + }, + }, + Response: http.AssertionResponse{ + ExpectedResponse: http.Response{ + StatusCode: 200, + }, + }, + }, { + Meta: http.AssertionMeta{ + TargetBackend: "infra-backend-v2", + TargetNamespace: "higress-conformance-infra", + }, + Request: http.AssertionRequest{ + ActualRequest: http.Request{ + Path: "/bar", + Host: "foo.com", + }, + }, + Response: http.AssertionResponse{ + ExpectedResponse: http.Response{ + StatusCode: 200, + }, + }, + }, { + Meta: http.AssertionMeta{ + TargetBackend: "infra-backend-v3", + TargetNamespace: "higress-conformance-infra", + }, + Request: http.AssertionRequest{ + ActualRequest: http.Request{ + Path: "/bar", + Host: "bar.com", + }, + }, + Response: http.AssertionResponse{ + ExpectedResponse: http.Response{ + StatusCode: 200, + }, + }, + }, { + Meta: http.AssertionMeta{ + TargetBackend: "infra-backend-v1", + TargetNamespace: "higress-conformance-infra", + }, + Request: http.AssertionRequest{ + ActualRequest: http.Request{ + Path: "/any", + Host: "any.bar.com", + }, + }, + Response: http.AssertionResponse{ + ExpectedResponse: http.Response{ + StatusCode: 200, + }, + }, + }, + } - t.Run("Simple HTTP request should reach infra-backend", func(t *testing.T) { - http.MakeRequestAndExpectEventuallyConsistentResponse(t, suite.RoundTripper, suite.TimeoutConfig, suite.GatewayAddress, http.ExpectedResponse{ - Request: http.Request{Path: "/foo", Host: "foo.com"}, - Response: http.Response{StatusCode: 200}, - Backend: "infra-backend-v1", - Namespace: "higress-conformance-infra", - }) - - http.MakeRequestAndExpectEventuallyConsistentResponse(t, suite.RoundTripper, suite.TimeoutConfig, suite.GatewayAddress, http.ExpectedResponse{ - Request: http.Request{Path: "/foo", Host: "bar.com"}, - Response: http.Response{StatusCode: 200}, - Backend: "infra-backend-v2", - Namespace: "higress-conformance-infra", - }) - - http.MakeRequestAndExpectEventuallyConsistentResponse(t, suite.RoundTripper, suite.TimeoutConfig, suite.GatewayAddress, http.ExpectedResponse{ - Request: http.Request{Path: "/bar", Host: "foo.com"}, - Response: http.Response{StatusCode: 200}, - Backend: "infra-backend-v2", - Namespace: "higress-conformance-infra", - }) - - http.MakeRequestAndExpectEventuallyConsistentResponse(t, suite.RoundTripper, suite.TimeoutConfig, suite.GatewayAddress, http.ExpectedResponse{ - Request: http.Request{Path: "/bar", Host: "bar.com"}, - Response: http.Response{StatusCode: 200}, - Backend: "infra-backend-v3", - Namespace: "higress-conformance-infra", - }) - - http.MakeRequestAndExpectEventuallyConsistentResponse(t, suite.RoundTripper, suite.TimeoutConfig, suite.GatewayAddress, http.ExpectedResponse{ - Request: http.Request{Path: "/any", Host: "any.bar.com"}, - Response: http.Response{StatusCode: 200}, - Backend: "infra-backend-v1", - Namespace: "higress-conformance-infra", - }) + t.Run("HTTP request should reach infra-backend with different hostname", func(t *testing.T) { + for _, testcase := range testcases { + http.MakeRequestAndExpectEventuallyConsistentResponse(t, suite.RoundTripper, suite.TimeoutConfig, suite.GatewayAddress, testcase) + } }) }, } diff --git a/test/ingress/conformance/tests/httproute-rewrite-path.go b/test/ingress/conformance/tests/httproute-rewrite-path.go index 7c5cd635f..fcfa62494 100644 --- a/test/ingress/conformance/tests/httproute-rewrite-path.go +++ b/test/ingress/conformance/tests/httproute-rewrite-path.go @@ -32,14 +32,24 @@ var HTTPRouteRewritePath = suite.ConformanceTest{ Test: func(t *testing.T, suite *suite.ConformanceTestSuite) { t.Run("Rewrite HTTPRoute Path", func(t *testing.T) { - http.MakeRequestAndExpectEventuallyConsistentResponse(t, suite.RoundTripper, suite.TimeoutConfig, suite.GatewayAddress, http.ExpectedResponse{ - Request: http.Request{Path: "/svc/foo"}, - ExpectedRequest: &http.ExpectedRequest{ - Request: http.Request{Path: "/foo"}, + http.MakeRequestAndExpectEventuallyConsistentResponse(t, suite.RoundTripper, suite.TimeoutConfig, suite.GatewayAddress, http.Assertion{ + Request: http.AssertionRequest{ + ActualRequest: http.Request{Path: "/svc/foo"}, + ExpectedRequest: &http.ExpectedRequest{ + Request: http.Request{Path: "/foo"}, + }, + }, + + Response: http.AssertionResponse{ + ExpectedResponse: http.Response{ + StatusCode: 200, + }, + }, + + Meta: http.AssertionMeta{ + TargetBackend: "infra-backend-v1", + TargetNamespace: "higress-conformance-infra", }, - Response: http.Response{StatusCode: 200}, - Backend: "infra-backend-v1", - Namespace: "higress-conformance-infra", }) }) }, diff --git a/test/ingress/conformance/tests/httproute-simple-same-namespace.go b/test/ingress/conformance/tests/httproute-simple-same-namespace.go index 36ff94d35..80e7e6f02 100644 --- a/test/ingress/conformance/tests/httproute-simple-same-namespace.go +++ b/test/ingress/conformance/tests/httproute-simple-same-namespace.go @@ -30,13 +30,22 @@ var HTTPRouteSimpleSameNamespace = suite.ConformanceTest{ Description: "A single Ingress in the higress-conformance-infra namespace demonstrates basic routing ability", Manifests: []string{"tests/httproute-simple-same-namespace.yaml"}, Test: func(t *testing.T, suite *suite.ConformanceTestSuite) { - t.Run("Simple HTTP request should reach infra-backend", func(t *testing.T) { - http.MakeRequestAndExpectEventuallyConsistentResponse(t, suite.RoundTripper, suite.TimeoutConfig, suite.GatewayAddress, http.ExpectedResponse{ - Request: http.Request{Path: "/hello-world"}, - Response: http.Response{StatusCode: 200}, - Backend: "infra-backend-v1", - Namespace: "higress-conformance-infra", + http.MakeRequestAndExpectEventuallyConsistentResponse(t, suite.RoundTripper, suite.TimeoutConfig, suite.GatewayAddress, http.Assertion{ + Meta: http.AssertionMeta{ + TargetBackend: "infra-backend-v1", + TargetNamespace: "higress-conformance-infra", + }, + Request: http.AssertionRequest{ + ActualRequest: http.Request{ + Path: "/hello-world", + }, + }, + Response: http.AssertionResponse{ + ExpectedResponse: http.Response{ + StatusCode: 200, + }, + }, }) }) }, diff --git a/test/ingress/conformance/utils/http/http.go b/test/ingress/conformance/utils/http/http.go index 96fb79311..321d61ae9 100644 --- a/test/ingress/conformance/utils/http/http.go +++ b/test/ingress/conformance/utils/http/http.go @@ -25,32 +25,40 @@ import ( "github.com/alibaba/higress/test/ingress/conformance/utils/roundtripper" ) -// ExpectedResponse defines the response expected for a given request. -type ExpectedResponse struct { - // Request defines the request to make. - Request Request +type Assertion struct { + Meta AssertionMeta + Request AssertionRequest + Response AssertionResponse +} + +type AssertionMeta struct { + // TestCaseName is the User Given TestCase name + TestCaseName string + // TargetBackend defines the target backend service + TargetBackend string + // TargetNamespace defines the target backend namespace + TargetNamespace string +} + +type AssertionRequest struct { + // ActualRequest defines the request to make. + ActualRequest Request // ExpectedRequest defines the request that // is expected to arrive at the backend. If // not specified, the backend request will be // expected to match Request. ExpectedRequest *ExpectedRequest - RedirectRequest *roundtripper.RedirectRequest +} - // BackendSetResponseHeaders is a set of headers - // the echoserver should set in its response. - BackendSetResponseHeaders map[string]string - - // Response defines what response the test case +type AssertionResponse struct { + // ExpectedResponse defines what response the test case // should receive. - Response Response - - Backend string - Namespace string - - // User Given TestCase name - TestCaseName string + ExpectedResponse Response + // AdditionalResponseHeaders is a set of headers + // the echoserver should set in its response. + AdditionalResponseHeaders map[string]string } // Request can be used as both the request to make and a means to verify @@ -91,38 +99,38 @@ const requiredConsecutiveSuccesses = 3 // // Once the request succeeds consistently with the response having the expected status code, make // additional assertions on the response body using the provided ExpectedResponse. -func MakeRequestAndExpectEventuallyConsistentResponse(t *testing.T, r roundtripper.RoundTripper, timeoutConfig config.TimeoutConfig, gwAddr string, expected ExpectedResponse) { +func MakeRequestAndExpectEventuallyConsistentResponse(t *testing.T, r roundtripper.RoundTripper, timeoutConfig config.TimeoutConfig, gwAddr string, expected Assertion) { t.Helper() - if expected.Request.Method == "" { - expected.Request.Method = "GET" + if expected.Request.ActualRequest.Method == "" { + expected.Request.ActualRequest.Method = "GET" } - if expected.Response.StatusCode == 0 { - expected.Response.StatusCode = 200 + if expected.Response.ExpectedResponse.StatusCode == 0 { + expected.Response.ExpectedResponse.StatusCode = 200 } - t.Logf("Making %s request to http://%s%s", expected.Request.Method, gwAddr, expected.Request.Path) + t.Logf("Making %s request to http://%s%s", expected.Request.ActualRequest.Method, gwAddr, expected.Request.ActualRequest.Path) - path, query, _ := strings.Cut(expected.Request.Path, "?") + path, query, _ := strings.Cut(expected.Request.ActualRequest.Path, "?") req := roundtripper.Request{ - Method: expected.Request.Method, - Host: expected.Request.Host, + Method: expected.Request.ActualRequest.Method, + Host: expected.Request.ActualRequest.Host, URL: url.URL{Scheme: "http", Host: gwAddr, Path: path, RawQuery: query}, Protocol: "HTTP", Headers: map[string][]string{}, - UnfollowRedirect: expected.Request.UnfollowRedirect, + UnfollowRedirect: expected.Request.ActualRequest.UnfollowRedirect, } - if expected.Request.Headers != nil { - for name, value := range expected.Request.Headers { + if expected.Request.ActualRequest.Headers != nil { + for name, value := range expected.Request.ActualRequest.Headers { req.Headers[name] = []string{value} } } backendSetHeaders := []string{} - for name, val := range expected.BackendSetResponseHeaders { + for name, val := range expected.Response.AdditionalResponseHeaders { backendSetHeaders = append(backendSetHeaders, name+":"+val) } req.Headers["X-Echo-Set-Header"] = []string{strings.Join(backendSetHeaders, ",")} @@ -170,7 +178,7 @@ func awaitConvergence(t *testing.T, threshold int, maxTimeToConsistency time.Dur // WaitForConsistentResponse repeats the provided request until it completes with a response having // the expected response consistently. The provided threshold determines how many times in // a row this must occur to be considered "consistent". -func WaitForConsistentResponse(t *testing.T, r roundtripper.RoundTripper, req roundtripper.Request, expected ExpectedResponse, threshold int, maxTimeToConsistency time.Duration) { +func WaitForConsistentResponse(t *testing.T, r roundtripper.RoundTripper, req roundtripper.Request, expected Assertion, threshold int, maxTimeToConsistency time.Duration) { awaitConvergence(t, threshold, maxTimeToConsistency, func(elapsed time.Duration) bool { cReq, cRes, err := r.CaptureRoundTrip(req) if err != nil { @@ -188,43 +196,43 @@ func WaitForConsistentResponse(t *testing.T, r roundtripper.RoundTripper, req ro t.Logf("Request passed") } -func CompareRequest(req *roundtripper.Request, cReq *roundtripper.CapturedRequest, cRes *roundtripper.CapturedResponse, expected ExpectedResponse) error { - if expected.Response.StatusCode != cRes.StatusCode { - return fmt.Errorf("expected status code to be %d, got %d", expected.Response.StatusCode, cRes.StatusCode) +func CompareRequest(req *roundtripper.Request, cReq *roundtripper.CapturedRequest, cRes *roundtripper.CapturedResponse, expected Assertion) error { + if expected.Response.ExpectedResponse.StatusCode != cRes.StatusCode { + return fmt.Errorf("expected status code to be %d, got %d", expected.Response.ExpectedResponse.StatusCode, cRes.StatusCode) } if cRes.StatusCode == 200 { // The request expected to arrive at the backend is // the same as the request made, unless otherwise // specified. - if expected.ExpectedRequest == nil { - expected.ExpectedRequest = &ExpectedRequest{Request: expected.Request} + if expected.Request.ExpectedRequest == nil { + expected.Request.ExpectedRequest = &ExpectedRequest{Request: expected.Request.ActualRequest} } - if expected.ExpectedRequest.Method == "" { - expected.ExpectedRequest.Method = "GET" + if expected.Request.ExpectedRequest.Method == "" { + expected.Request.ExpectedRequest.Method = "GET" } - if expected.ExpectedRequest.Host != "" && expected.ExpectedRequest.Host != cReq.Host { - return fmt.Errorf("expected host to be %s, got %s", expected.ExpectedRequest.Host, cReq.Host) + if expected.Request.ExpectedRequest.Host != "" && expected.Request.ExpectedRequest.Host != cReq.Host { + return fmt.Errorf("expected host to be %s, got %s", expected.Request.ExpectedRequest.Host, cReq.Host) } - if expected.ExpectedRequest.Path != cReq.Path { - return fmt.Errorf("expected path to be %s, got %s", expected.ExpectedRequest.Path, cReq.Path) + if expected.Request.ExpectedRequest.Path != cReq.Path { + return fmt.Errorf("expected path to be %s, got %s", expected.Request.ExpectedRequest.Path, cReq.Path) } - if expected.ExpectedRequest.Method != cReq.Method { - return fmt.Errorf("expected method to be %s, got %s", expected.ExpectedRequest.Method, cReq.Method) + if expected.Request.ExpectedRequest.Method != cReq.Method { + return fmt.Errorf("expected method to be %s, got %s", expected.Request.ExpectedRequest.Method, cReq.Method) } - if expected.Namespace != cReq.Namespace { - return fmt.Errorf("expected namespace to be %s, got %s", expected.Namespace, cReq.Namespace) + if expected.Meta.TargetNamespace != cReq.Namespace { + return fmt.Errorf("expected namespace to be %s, got %s", expected.Meta.TargetNamespace, cReq.Namespace) } - if expected.ExpectedRequest.Headers != nil { + if expected.Request.ExpectedRequest.Headers != nil { if cReq.Headers == nil { - return fmt.Errorf("no headers captured, expected %v", len(expected.ExpectedRequest.Headers)) + return fmt.Errorf("no headers captured, expected %v", len(expected.Request.ExpectedRequest.Headers)) } for name, val := range cReq.Headers { cReq.Headers[strings.ToLower(name)] = val } - for name, expectedVal := range expected.ExpectedRequest.Headers { + for name, expectedVal := range expected.Request.ExpectedRequest.Headers { actualVal, ok := cReq.Headers[strings.ToLower(name)] if !ok { return fmt.Errorf("expected %s header to be set, actual headers: %v", name, cReq.Headers) @@ -235,15 +243,15 @@ func CompareRequest(req *roundtripper.Request, cReq *roundtripper.CapturedReques } } - if expected.Response.Headers != nil { + if expected.Response.ExpectedResponse.Headers != nil { if cRes.Headers == nil { - return fmt.Errorf("no headers captured, expected %v", len(expected.ExpectedRequest.Headers)) + return fmt.Errorf("no headers captured, expected %v", len(expected.Request.ExpectedRequest.Headers)) } for name, val := range cRes.Headers { cRes.Headers[strings.ToLower(name)] = val } - for name, expectedVal := range expected.Response.Headers { + for name, expectedVal := range expected.Response.ExpectedResponse.Headers { actualVal, ok := cRes.Headers[strings.ToLower(name)] if !ok { return fmt.Errorf("expected %s header to be set, actual headers: %v", name, cRes.Headers) @@ -253,12 +261,12 @@ func CompareRequest(req *roundtripper.Request, cReq *roundtripper.CapturedReques } } - if len(expected.Response.AbsentHeaders) > 0 { + if len(expected.Response.ExpectedResponse.AbsentHeaders) > 0 { for name, val := range cRes.Headers { cRes.Headers[strings.ToLower(name)] = val } - for _, name := range expected.Response.AbsentHeaders { + for _, name := range expected.Response.ExpectedResponse.AbsentHeaders { val, ok := cRes.Headers[strings.ToLower(name)] if ok { return fmt.Errorf("expected %s header to not be set, got %s", name, val) @@ -268,12 +276,12 @@ func CompareRequest(req *roundtripper.Request, cReq *roundtripper.CapturedReques // Verify that headers expected *not* to be present on the // request are actually not present. - if len(expected.ExpectedRequest.AbsentHeaders) > 0 { + if len(expected.Request.ExpectedRequest.AbsentHeaders) > 0 { for name, val := range cReq.Headers { cReq.Headers[strings.ToLower(name)] = val } - for _, name := range expected.ExpectedRequest.AbsentHeaders { + for _, name := range expected.Request.ExpectedRequest.AbsentHeaders { val, ok := cReq.Headers[strings.ToLower(name)] if ok { return fmt.Errorf("expected %s header to not be set, got %s", name, val) @@ -281,74 +289,74 @@ func CompareRequest(req *roundtripper.Request, cReq *roundtripper.CapturedReques } } - if !strings.HasPrefix(cReq.Pod, expected.Backend) { - return fmt.Errorf("expected pod name to start with %s, got %s", expected.Backend, cReq.Pod) + if !strings.HasPrefix(cReq.Pod, expected.Meta.TargetBackend) { + return fmt.Errorf("expected pod name to start with %s, got %s", expected.Meta.TargetBackend, cReq.Pod) } } else if roundtripper.IsRedirect(cRes.StatusCode) { - if expected.RedirectRequest == nil { + if expected.Request.RedirectRequest == nil { return nil } setRedirectRequestDefaults(req, cRes, &expected) - if expected.RedirectRequest.Host != cRes.RedirectRequest.Host { - return fmt.Errorf("expected redirected hostname to be %s, got %s", expected.RedirectRequest.Host, cRes.RedirectRequest.Host) + if expected.Request.RedirectRequest.Host != cRes.RedirectRequest.Host { + return fmt.Errorf("expected redirected hostname to be %s, got %s", expected.Request.RedirectRequest.Host, cRes.RedirectRequest.Host) } - if expected.RedirectRequest.Port != cRes.RedirectRequest.Port { - return fmt.Errorf("expected redirected port to be %s, got %s", expected.RedirectRequest.Port, cRes.RedirectRequest.Port) + if expected.Request.RedirectRequest.Port != cRes.RedirectRequest.Port { + return fmt.Errorf("expected redirected port to be %s, got %s", expected.Request.RedirectRequest.Port, cRes.RedirectRequest.Port) } - if expected.RedirectRequest.Scheme != cRes.RedirectRequest.Scheme { - return fmt.Errorf("expected redirected scheme to be %s, got %s", expected.RedirectRequest.Scheme, cRes.RedirectRequest.Scheme) + if expected.Request.RedirectRequest.Scheme != cRes.RedirectRequest.Scheme { + return fmt.Errorf("expected redirected scheme to be %s, got %s", expected.Request.RedirectRequest.Scheme, cRes.RedirectRequest.Scheme) } - if expected.RedirectRequest.Path != cRes.RedirectRequest.Path { - return fmt.Errorf("expected redirected path to be %s, got %s", expected.RedirectRequest.Path, cRes.RedirectRequest.Path) + if expected.Request.RedirectRequest.Path != cRes.RedirectRequest.Path { + return fmt.Errorf("expected redirected path to be %s, got %s", expected.Request.RedirectRequest.Path, cRes.RedirectRequest.Path) } } return nil } // Get User-defined test case name or generate from expected response to a given request. -func (er *ExpectedResponse) GetTestCaseName(i int) string { +func (er *Assertion) GetTestCaseName(i int) string { // If TestCase name is provided then use that or else generate one. - if er.TestCaseName != "" { - return er.TestCaseName + if er.Meta.TestCaseName != "" { + return er.Meta.TestCaseName } headerStr := "" reqStr := "" - if er.Request.Headers != nil { + if er.Request.ActualRequest.Headers != nil { headerStr = " with headers" } - reqStr = fmt.Sprintf("%d request to '%s%s'%s", i, er.Request.Host, er.Request.Path, headerStr) + reqStr = fmt.Sprintf("%d request to '%s%s'%s", i, er.Request.ActualRequest.Host, er.Request.ActualRequest.Path, headerStr) - if er.Backend != "" { - return fmt.Sprintf("%s should go to %s", reqStr, er.Backend) + if er.Meta.TargetBackend != "" { + return fmt.Sprintf("%s should go to %s", reqStr, er.Meta.TargetBackend) } - return fmt.Sprintf("%s should receive a %d", reqStr, er.Response.StatusCode) + return fmt.Sprintf("%s should receive a %d", reqStr, er.Response.ExpectedResponse.StatusCode) } -func setRedirectRequestDefaults(req *roundtripper.Request, cRes *roundtripper.CapturedResponse, expected *ExpectedResponse) { +func setRedirectRequestDefaults(req *roundtripper.Request, cRes *roundtripper.CapturedResponse, expected *Assertion) { // If the expected host is nil it means we do not test host redirect. // In that case we are setting it to the one we got from the response because we do not know the ip/host of the gateway. - if expected.RedirectRequest.Host == "" { - expected.RedirectRequest.Host = cRes.RedirectRequest.Host + if expected.Request.RedirectRequest.Host == "" { + expected.Request.RedirectRequest.Host = cRes.RedirectRequest.Host } - if expected.RedirectRequest.Port == "" { - expected.RedirectRequest.Port = req.URL.Port() + if expected.Request.RedirectRequest.Port == "" { + expected.Request.RedirectRequest.Port = req.URL.Port() } - if expected.RedirectRequest.Scheme == "" { - expected.RedirectRequest.Scheme = req.URL.Scheme + if expected.Request.RedirectRequest.Scheme == "" { + expected.Request.RedirectRequest.Scheme = req.URL.Scheme } - if expected.RedirectRequest.Path == "" { - expected.RedirectRequest.Path = req.URL.Path + if expected.Request.RedirectRequest.Path == "" { + expected.Request.RedirectRequest.Path = req.URL.Path } }