-
Notifications
You must be signed in to change notification settings - Fork 742
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
Test for data race conditions in adapters #1756
Changes from 10 commits
7074f58
cbcc293
a4c6c4a
dde3442
8dc32b2
e81eceb
015fc30
e22ffd7
4ce81b9
eb75ee4
87d8461
4e81fcf
77d9a54
e8d3a6a
6be78a1
f96f9e7
1023f30
5f81f1d
4138379
d0e5e70
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
Original file line number | Diff line number | Diff line change |
---|---|---|
|
@@ -7,8 +7,10 @@ import ( | |
"regexp" | ||
"testing" | ||
|
||
"github.com/jinzhu/copier" | ||
"github.com/mxmCherry/openrtb/v14/openrtb2" | ||
"github.com/prebid/prebid-server/adapters" | ||
"github.com/stretchr/testify/assert" | ||
"github.com/yudai/gojsondiff" | ||
"github.com/yudai/gojsondiff/formatter" | ||
|
||
|
@@ -83,7 +85,14 @@ func loadFile(filename string) (*testSpec, error) { | |
return nil, fmt.Errorf("Failed to read file %s: %v", filename, err) | ||
} | ||
|
||
//var buf bytes.Buffer | ||
|
||
//if err := json.Compact(&buf, specData); err != nil { | ||
// return nil, fmt.Errorf("Unable to compact JSON string found in file %s: %v", filename, err) | ||
//} | ||
|
||
var spec testSpec | ||
//if err := json.Unmarshal(buf.Bytes(), &spec); err != nil { | ||
if err := json.Unmarshal(specData, &spec); err != nil { | ||
return nil, fmt.Errorf("Failed to unmarshal JSON from file: %v", err) | ||
} | ||
|
@@ -107,24 +116,10 @@ func runSpec(t *testing.T, filename string, spec *testSpec, bidder adapters.Bidd | |
} else if isVideoTest { | ||
reqInfo.PbsEntryPoint = "video" | ||
} | ||
actualReqs, errs := bidder.MakeRequests(&spec.BidRequest, &reqInfo) | ||
diffErrorLists(t, fmt.Sprintf("%s: MakeRequests", filename), errs, spec.MakeRequestErrors) | ||
diffHttpRequestLists(t, filename, actualReqs, spec.HttpCalls) | ||
|
||
bidResponses := make([]*adapters.BidderResponse, 0) | ||
actualReqs := testMakeRequestsImpl(t, filename, spec, bidder, &reqInfo) | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. The There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. done |
||
|
||
var bidsErrs = make([]error, 0, len(spec.MakeBidsErrors)) | ||
for i := 0; i < len(actualReqs); i++ { | ||
thisBidResponse, theseErrs := bidder.MakeBids(&spec.BidRequest, spec.HttpCalls[i].Request.ToRequestData(t), spec.HttpCalls[i].Response.ToResponseData(t)) | ||
bidsErrs = append(bidsErrs, theseErrs...) | ||
bidResponses = append(bidResponses, thisBidResponse) | ||
} | ||
|
||
diffErrorLists(t, fmt.Sprintf("%s: MakeBids", filename), bidsErrs, spec.MakeBidsErrors) | ||
|
||
for i := 0; i < len(spec.BidResponses); i++ { | ||
diffBidLists(t, filename, bidResponses[i], spec.BidResponses[i].Bids) | ||
} | ||
testMakeBidsImpl(t, filename, spec, bidder, actualReqs) | ||
} | ||
|
||
type testSpec struct { | ||
|
@@ -194,8 +189,8 @@ type expectedBid struct { | |
// | ||
// Marshalling the structs and then using a JSON-diff library isn't great either, since | ||
|
||
// diffHttpRequests compares the actual http requests to the expected ones. | ||
func diffHttpRequestLists(t *testing.T, filename string, actual []*adapters.RequestData, expected []httpCall) { | ||
// assertMakeRequestsOutput compares the actual http requests to the expected ones. | ||
func assertMakeRequestsOutput(t *testing.T, filename string, actual []*adapters.RequestData, expected []httpCall) { | ||
t.Helper() | ||
|
||
if len(expected) != len(actual) { | ||
|
@@ -206,7 +201,7 @@ func diffHttpRequestLists(t *testing.T, filename string, actual []*adapters.Requ | |
} | ||
} | ||
|
||
func diffErrorLists(t *testing.T, description string, actual []error, expected []testSpecExpectedError) { | ||
func assertErrorList(t *testing.T, description string, actual []error, expected []testSpecExpectedError) { | ||
t.Helper() | ||
|
||
if len(expected) != len(actual) { | ||
|
@@ -227,10 +222,10 @@ func diffErrorLists(t *testing.T, description string, actual []error, expected [ | |
} | ||
} | ||
|
||
func diffBidLists(t *testing.T, filename string, response *adapters.BidderResponse, expected []expectedBid) { | ||
func assertMakeBidsOutput(t *testing.T, filename string, actualBidderResp *adapters.BidderResponse, expected []expectedBid) { | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Could this just be There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. I thought the |
||
t.Helper() | ||
|
||
if (response == nil || len(response.Bids) == 0) != (len(expected) == 0) { | ||
if (actualBidderResp == nil || len(actualBidderResp.Bids) == 0) != (len(expected) == 0) { | ||
if len(expected) == 0 { | ||
t.Fatalf("%s: expectedBidResponses indicated a nil response, but mockResponses supplied a non-nil response", filename) | ||
} | ||
|
@@ -239,17 +234,15 @@ func diffBidLists(t *testing.T, filename string, response *adapters.BidderRespon | |
} | ||
|
||
// Expected nil response - give diffBids something to work with. | ||
if response == nil { | ||
response = new(adapters.BidderResponse) | ||
if actualBidderResp == nil { | ||
actualBidderResp = new(adapters.BidderResponse) | ||
} | ||
|
||
actual := response.Bids | ||
|
||
if len(actual) != len(expected) { | ||
t.Fatalf("%s: MakeBids returned wrong bid count. Expected %d, got %d", filename, len(expected), len(actual)) | ||
if len(actualBidderResp.Bids) != len(expected) { | ||
t.Fatalf("%s: MakeBids returned wrong bid count. Expected %d, got %d", filename, len(expected), len(actualBidderResp.Bids)) | ||
} | ||
for i := 0; i < len(actual); i++ { | ||
diffBids(t, fmt.Sprintf("%s: typedBid[%d]", filename, i), actual[i], &(expected[i])) | ||
for i := 0; i < len(actualBidderResp.Bids); i++ { | ||
diffBids(t, fmt.Sprintf("%s: typedBid[%d]", filename, i), actualBidderResp.Bids[i], &(expected[i])) | ||
} | ||
} | ||
|
||
|
@@ -331,3 +324,110 @@ func diffJson(t *testing.T, description string, actual []byte, expected []byte) | |
} | ||
} | ||
} | ||
|
||
// testMakeBidsImpl asserts the results of the bidder MakeRequests implementation against the expected JSON-defined results | ||
// and makes sure no data races occur | ||
func testMakeRequestsImpl(t *testing.T, filename string, spec *testSpec, bidder adapters.Bidder, reqInfo *adapters.ExtraRequestInfo) []*adapters.RequestData { | ||
t.Helper() | ||
|
||
deepBidReqCopy, shallowBidReqCopy := getDataRaceTestCopies(&spec.BidRequest) | ||
|
||
// Run MakeRequests | ||
actualReqs, errs := bidder.MakeRequests(&spec.BidRequest, reqInfo) | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Same comment of regarding |
||
|
||
// Compare MakeRequests actual output versus expected values found in JSON file | ||
assertErrorList(t, fmt.Sprintf("%s: MakeRequests", filename), errs, spec.MakeRequestErrors) | ||
assertMakeRequestsOutput(t, filename, actualReqs, spec.HttpCalls) | ||
|
||
// Assert no data races occur using original bidRequest copies of references and values | ||
assertNoDataRace(t, deepBidReqCopy, shallowBidReqCopy, filename) | ||
|
||
return actualReqs | ||
} | ||
|
||
func getDataRaceTestCopies(original *openrtb2.BidRequest) (*openrtb2.BidRequest, *openrtb2.BidRequest) { | ||
|
||
// Save original bidRequest values to assert no data races occur inside MakeRequests latter | ||
deepReqCopy := openrtb2.BidRequest{} | ||
copier.Copy(&deepReqCopy, original) | ||
|
||
// Shallow copy PBS core provides to adapters | ||
shallowReqCopy := *original | ||
|
||
// Save original []Imp elements to assert no data races occur inside MakeRequests latter | ||
deepReqCopy.Imp = make([]openrtb2.Imp, len(original.Imp)) | ||
shallowReqCopy.Imp = make([]openrtb2.Imp, len(original.Imp)) | ||
for i := 0; i < len(original.Imp); i++ { | ||
deepImpCopy := openrtb2.Imp{} | ||
copier.Copy(&deepImpCopy, original.Imp[i]) | ||
deepReqCopy.Imp = append(deepReqCopy.Imp, deepImpCopy) | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Sorry, I think you explained this before. Why do we need to do make deep copies of the impression objects? Does There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Because the deep copy version of this library was throwing false positives when comparing the |
||
|
||
shallowImpCopy := original.Imp[i] | ||
shallowReqCopy.Imp = append(shallowReqCopy.Imp, shallowImpCopy) | ||
} | ||
|
||
return &deepReqCopy, &shallowReqCopy | ||
} | ||
|
||
// assertNoDataRace compares the contents of the reference fields found in the original openrtb2.BidRequest to their | ||
// original values to make sure they were not modified and we are not incurring indata races. In order to assert | ||
// no data races occur in the []Imp array, we call assertNoImpsDataRace() | ||
func assertNoDataRace(t *testing.T, bidRequestBefore *openrtb2.BidRequest, bidRequestAfter *openrtb2.BidRequest, filename string) { | ||
t.Helper() | ||
|
||
// Assert reference fields were not modified by bidder adapter MakeRequests implementation | ||
assert.Equal(t, bidRequestBefore.Site, bidRequestAfter.Site, "Data race in BidRequest.Site field in file %s", filename) | ||
assert.Equal(t, bidRequestBefore.App, bidRequestAfter.App, "Data race in BidRequest.App field in file %s", filename) | ||
assert.Equal(t, bidRequestBefore.Device, bidRequestAfter.Device, "Data race in BidRequest.Device field in file %s", filename) | ||
assert.Equal(t, bidRequestBefore.User, bidRequestAfter.User, "Data race in BidRequest.User field in file %s", filename) | ||
assert.Equal(t, bidRequestBefore.Source, bidRequestAfter.Source, "Data race in BidRequest.Source field in file %s", filename) | ||
assert.Equal(t, bidRequestBefore.Regs, bidRequestAfter.Regs, "Data race in BidRequest.Regs field in file %s", filename) | ||
|
||
// Assert Imps separately | ||
assertNoImpsDataRace(t, bidRequestBefore.Imp, bidRequestAfter.Imp, filename) | ||
} | ||
|
||
// assertNoImpsDataRace compares the contents of the reference fields found in the original openrtb2.Imp objects to | ||
// their original values to make sure they were not modified and we are not incurring in data races. | ||
func assertNoImpsDataRace(t *testing.T, impsBefore []openrtb2.Imp, impsAfter []openrtb2.Imp, filename string) { | ||
t.Helper() | ||
|
||
if assert.Len(t, impsAfter, len(impsBefore), "Original []Imp array was modified and length is not equal to original after MakeRequests was called. File:%s", filename) { | ||
// Assert no data races occured in individual Imp elements | ||
for i := 0; i < len(impsBefore); i++ { | ||
assert.Equal(t, impsBefore[i].Banner, impsAfter[i].Banner, "Data race in bidRequest.Imp[%d].Banner field. File:%s", i, filename) | ||
assert.Equal(t, impsBefore[i].Video, impsAfter[i].Video, "Data race in bidRequest.Imp[%d].Video field. File:%s", i, filename) | ||
assert.Equal(t, impsBefore[i].Audio, impsAfter[i].Audio, "Data race in bidRequest.Imp[%d].Audio field. File:%s", i, filename) | ||
assert.Equal(t, impsBefore[i].Native, impsAfter[i].Native, "Data race in bidRequest.Imp[%d].Native field. File:%s", i, filename) | ||
assert.Equal(t, impsBefore[i].PMP, impsAfter[i].PMP, "Data race in bidRequest.Imp[%d].PMP field. File:%s", i, filename) | ||
assert.Equal(t, impsBefore[i].Secure, impsAfter[i].Secure, "Data race in bidRequest.Imp[%d].Secure field. File:%s", i, filename) | ||
assert.ElementsMatch(t, impsBefore[i].Metric, impsAfter[i].Metric, "Data race in bidRequest.Imp[%d].[]Metric array. File:%s", i) | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Should we include the other slices in the data race tests too? There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. We decided to add all slices from the BidRequest and the Imp using an assert.Equal. Slices are shared memory which also may not be mutated. |
||
} | ||
} | ||
} | ||
|
||
// testMakeBidsImpl asserts the results of the bidder MakeBids implementation against the expected JSON-defined results | ||
func testMakeBidsImpl(t *testing.T, filename string, spec *testSpec, bidder adapters.Bidder, makeRequestsOut []*adapters.RequestData) { | ||
t.Helper() | ||
|
||
bidResponses := make([]*adapters.BidderResponse, 0) | ||
var bidsErrs = make([]error, 0, len(spec.MakeBidsErrors)) | ||
|
||
// We should have as many bids as number of adapters.RequestData found in MakeRequests output | ||
for i := 0; i < len(makeRequestsOut); i++ { | ||
// Run MakeBids with JSON refined spec.HttpCalls info that was asserted to match MakeRequests | ||
// output inside testMakeRequestsImpl | ||
thisBidResponse, theseErrs := bidder.MakeBids(&spec.BidRequest, spec.HttpCalls[i].Request.ToRequestData(t), spec.HttpCalls[i].Response.ToResponseData(t)) | ||
|
||
bidsErrs = append(bidsErrs, theseErrs...) | ||
bidResponses = append(bidResponses, thisBidResponse) | ||
} | ||
|
||
// Assert actual errors thrown by MakeBids implementation versus expected JSON-defined spec.MakeBidsErrors | ||
assertErrorList(t, fmt.Sprintf("%s: MakeBids", filename), bidsErrs, spec.MakeBidsErrors) | ||
|
||
// Assert MakeBids implementation BidResponses with expected JSON-defined spec.BidResponses[i].Bids | ||
for i := 0; i < len(spec.BidResponses); i++ { | ||
assertMakeBidsOutput(t, filename, bidResponses[i], spec.BidResponses[i].Bids) | ||
} | ||
} | ||
SyntaxNode marked this conversation as resolved.
Show resolved
Hide resolved
|
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Please remove the commented out code.