Closed dcramer closed 5 years ago
Are you running the tests with T.parallel by chance?
When you call httpmock.Activate()
a non thread safe operation occurs where the http package's DefaultTransport is overwritten by httpmock's MockTransport. Relevant code. This works fine unless you're running tests in parallel.
If you would like to avoid this, then you'll need to have some code pass a Transport to your HTTP clients (rather than letting them use DefaultTransport). This will let you give the clients the MockTransport instead when running tests.
@jarcoal not using T.parallel, just running with -race
I experienced something similar to this while trying to mock one or two calls at a great height where many calls i didn't so much care about were being made. My solution to this was to make MockTransport a wrapper around the original http.DefaultTransport, then ensuring that its RoundTrip() method got called instead of ConnectionFailure().
I think in some cases the ConnectionFailure() method can cause havoc, but i'd need to investigate further.
I didn't stash my code, sorry, but maybe it'll help you find a fix.
This project seems dead ... To fix the race condition add a sync.Mutex to the MockTransport struct ;-)
Yeah I'm not really writing Go anymore so haven't been working on this. If anyone wants to be added as a collaborator, let me know.
Pick me Pick me Pick me!
Hey @SchumacherFM I made you a collaborator, thanks for helping out.
I have an initial idea quick and dirty implemented https://github.com/SchumacherFM/httpmock/blob/try-to-remove-race/transport.go#L153
What is missing is to make a process wait while another process holds the http.DefaultTransport lock ...
Little bit late my answer, stuck on other projects :-(
I don't think this can be solved in the general case. Even if you use locks in Activate/Deactivate the client can still use the transport (in anothor goroutine) and there is a race. The best solution is to use custom client and pass transport as a param so you can mock it.
The fix in abbf1543e32e3d8a74ab639e37d9b097178a6924 breaks my use case. Basically my app works as a proxy and in my tests, we send a request to our web server then the app will make another request to a mocked address:
I have dig around and it looks like the 1st request is still holding the lock while the 2nd request reaches responderForKey
. As a workaround, we now use a new &http.Client{}
for requests made by test.go
. Is that recommended?
var tmpClient = &http.Client{
Transport: httpmock.InitialTransport,
}
I think this is ok.
This reminded me of: https://xkcd.com/1172/ :)
@daohoangson do you still encounter the problem? If yes, a simple change can work your problem around, executing runCancelable()
(here and here) after releasing the lock. Something like:
m.mu.Lock()
// if we found a responder, call it
if responder != nil {
m.callCountInfo[key]++
m.totalCallCount++
} else {
// we didn't find a responder, so fire the 'no responder' responder
if m.noResponder == nil {
m.mu.Unlock()
return ConnectionFailure(req)
}
m.callCountInfo["NO_RESPONDER"]++
m.totalCallCount++
responder = m.noResponder
}
m.mu.Unlock()
return runCancelable(responder, req)
Done in #62
@maxatome it has been quite a while, we used the workaround above to pass the tests. Let me drop it and see whether it still works 🙏
@daohoangson any news?
Let me start by saying, this might be entirely my fault, and I barely understand anything about Go.
I'm attempting to convert a test from using a pretty ugly chunk of httptest code to httpmock:
https://github.com/dropbox/changes-client/compare/handle-abortions...httpmock
However, upon doing this we hit a race which only presents itself with httpmock: