-
Notifications
You must be signed in to change notification settings - Fork 204
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
Merge pull request #1492 from Azure/better-failure-messages
Improve record/replay tests
- Loading branch information
Showing
10 changed files
with
1,753 additions
and
1,263 deletions.
There are no files selected for viewing
1,500 changes: 827 additions & 673 deletions
1,500
hack/generated/controllers/recordings/Test_CosmosDB_CRUD.yaml
Large diffs are not rendered by default.
Oops, something went wrong.
150 changes: 62 additions & 88 deletions
150
hack/generated/controllers/recordings/Test_ResourceGroup_CRUD.yaml
Large diffs are not rendered by default.
Oops, something went wrong.
492 changes: 291 additions & 201 deletions
492
hack/generated/controllers/recordings/Test_ServiceBus_Namespace_CRUD.yaml
Large diffs are not rendered by default.
Oops, something went wrong.
518 changes: 340 additions & 178 deletions
518
hack/generated/controllers/recordings/Test_StorageAccount_CRUD.yaml
Large diffs are not rendered by default.
Oops, something went wrong.
113 changes: 46 additions & 67 deletions
113
hack/generated/pkg/armclient/recordings/Test_NewResourceGroupDeployment.yaml
Large diffs are not rendered by default.
Oops, something went wrong.
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,41 @@ | ||
/* | ||
Copyright (c) Microsoft Corporation. | ||
Licensed under the MIT license. | ||
*/ | ||
|
||
package testcommon | ||
|
||
import ( | ||
"fmt" | ||
"net/http" | ||
) | ||
|
||
// Wraps an inner HTTP roundtripper to add a | ||
// counter for duplicated request URIs. This | ||
// is then used to match up requests in the recorder | ||
// - it is needed as we have multiple requests with | ||
// the same Request URL and it will return the first | ||
// one that matches. | ||
type requestCounter struct { | ||
inner http.RoundTripper | ||
counts map[string]uint32 | ||
} | ||
|
||
func addCountHeader(inner http.RoundTripper) *requestCounter { | ||
return &requestCounter{ | ||
inner: inner, | ||
counts: make(map[string]uint32), | ||
} | ||
} | ||
|
||
var COUNT_HEADER string = "TEST-REQUEST-ATTEMPT" | ||
|
||
func (rt *requestCounter) RoundTrip(req *http.Request) (*http.Response, error) { | ||
key := req.Method + ":" + req.URL.String() | ||
count := rt.counts[key] | ||
req.Header.Add(COUNT_HEADER, fmt.Sprintf("%v", count)) | ||
rt.counts[key] = count + 1 | ||
return rt.inner.RoundTrip(req) | ||
} | ||
|
||
var _ http.RoundTripper = &requestCounter{} |
106 changes: 106 additions & 0 deletions
106
hack/generated/pkg/testcommon/error_translating_roundtripper.go
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,106 @@ | ||
/* | ||
Copyright (c) Microsoft Corporation. | ||
Licensed under the MIT license. | ||
*/ | ||
|
||
package testcommon | ||
|
||
import ( | ||
"fmt" | ||
"io" | ||
"net/http" | ||
"strings" | ||
|
||
"github.com/dnaeon/go-vcr/cassette" | ||
"github.com/dnaeon/go-vcr/recorder" | ||
"github.com/google/go-cmp/cmp" | ||
) | ||
|
||
// translateErrors wraps the given Recorder to handle any "Requested interaction not found" | ||
// and log better information about what the expected request was. | ||
// | ||
// By default the error will be returned to the controller which might ignore/retry it | ||
// and not log any useful information. So instead here we find the recorded request with | ||
// the body that most closely matches what was sent and report the "expected" body. | ||
// | ||
// Ideally we would panic on this error but we don't have a good way to deal with the following | ||
// problem at the moment: | ||
// - during record the controller does GET (404), PUT, … GET (OK) | ||
// - during playback the controller does GET (which now returns OK), DELETE, PUT, … | ||
// and fails due to a missing DELETE recording | ||
func translateErrors(r *recorder.Recorder, cassetteName string) http.RoundTripper { | ||
return errorTranslation{r, cassetteName, nil} | ||
} | ||
|
||
type errorTranslation struct { | ||
recorder *recorder.Recorder | ||
cassetteName string | ||
|
||
cassette *cassette.Cassette | ||
} | ||
|
||
func (w errorTranslation) ensureCassette() *cassette.Cassette { | ||
if w.cassette == nil { | ||
cassette, err := cassette.Load(w.cassetteName) | ||
if err != nil { | ||
panic(fmt.Sprintf("unable to load casette %q", w.cassetteName)) | ||
} | ||
|
||
w.cassette = cassette | ||
} | ||
|
||
return w.cassette | ||
} | ||
|
||
func (w errorTranslation) RoundTrip(req *http.Request) (*http.Response, error) { | ||
resp, originalErr := w.recorder.RoundTrip(req) | ||
// sorry, go-vcr doesn't expose the error type or message | ||
if originalErr == nil || !strings.Contains(originalErr.Error(), "interaction not found") { | ||
return resp, originalErr | ||
} | ||
|
||
sentBodyString := "<nil>" | ||
if req.Body != nil { | ||
bodyBytes, bodyErr := io.ReadAll(req.Body) | ||
if bodyErr != nil { | ||
// see invocation of SetMatcher in the createRecorder, which does this | ||
panic("io.ReadAll(req.Body) failed, this should always succeed because req.Body has been replaced by a buffer") | ||
} | ||
|
||
sentBodyString = string(bodyBytes) | ||
} | ||
|
||
// find all request bodies for the specified method/URL combination | ||
matchingBodies := w.findMatchingBodies(req) | ||
|
||
if len(matchingBodies) == 0 { | ||
fmt.Printf("\n*** Cannot find go-vcr recording for request (no responses recorded for this method/URL): %s %s (attempt: %s)\n\n", req.Method, req.URL.String(), req.Header.Get(COUNT_HEADER)) | ||
return nil, originalErr | ||
} | ||
|
||
// locate the request body with the shortest diff from the sent body | ||
shortestDiff := "" | ||
for i, bodyString := range matchingBodies { | ||
diff := cmp.Diff(sentBodyString, bodyString) | ||
if i == 0 || len(diff) < len(shortestDiff) { | ||
shortestDiff = diff | ||
} | ||
} | ||
|
||
fmt.Printf("\n*** Cannot find go-vcr recording for request (body mismatch): %s %s\nShortest body diff: %s\n\n", req.Method, req.URL.String(), shortestDiff) | ||
return nil, originalErr | ||
} | ||
|
||
// finds bodies for interactions where request method, URL, and COUNT_HEADER match | ||
func (w errorTranslation) findMatchingBodies(r *http.Request) []string { | ||
urlString := r.URL.String() | ||
var result []string | ||
for _, interaction := range w.ensureCassette().Interactions { | ||
if urlString == interaction.URL && r.Method == interaction.Request.Method && | ||
r.Header.Get(COUNT_HEADER) == interaction.Request.Headers.Get(COUNT_HEADER) { | ||
result = append(result, interaction.Request.Body) | ||
} | ||
} | ||
|
||
return result | ||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters