testing - How to test for leaking connections in a Go test? -
after i've found leaks in program, i've solved problem. however, now i'm trying find out how " test " leaking connections in go test? question.
i've tried change number of requests in test, didn't matter. no matter do, current number of tcp connections in test stay same.
func testleakingconnections(t *testing.t) { getter := myhttp.new() s := newserver(ok) defer s.close() cur := tcps(t) := 0; < 1000; i++ { r, _ := getter.getwithtimeout(s.url, time.millisecond*10) r.body.close() } tries := 10; tries >= 0; tries-- { growth := tcps(t) - cur if growth > 5 { t.error("leaked") return } } } // find tcp connections func tcps(t *testing.t) (conns int) { lsof, err := exec.command("lsof", "-n", "-p", strconv.itoa(os.getpid())).output() if err != nil { t.skip("skipping test; error finding or running lsof") } _, ls := range strings.split(string(lsof), "\n") { if strings.contains(ls, "tcp") { conns++ } } return } func newserver(f http.handlerfunc) *httptest.server { return httptest.newserver(http.handlerfunc(f)) } func ok(w http.responsewriter, r *http.request) { w.header().add("content-type", "application/xml") io.writestring(w, "<xml></xml>") } // myhttp package // ...other code omitted clarification func (g *getter) getwithtimeout( url string, timeout time.duration, ) ( *http.response, error, ) { // leaking part // moving out of here stop leaks transport := http.transport{ dialcontext: (&net.dialer{ timeout: timeout, }).dialcontext, tlshandshaketimeout: timeout, responseheadertimeout: timeout, expectcontinuetimeout: timeout, } client := http.client{ timeout: timeout, transport: &transport, } return client.get(url) } // fixture worker package // outside code injects getter fixture_worker this: getter := myhttp.new() // newwithtimeout creates new fetcher timeout threshold func newwithtimeout( getter myhttp.httpgetter, fetchurl string, timeout time.duration, ) *fetcher { return &fetcher{getter, fetchurl, timeout} } // fetch fetches fixture xml func (f *fetcher) fetch() (*parser.fixturexml, error) { res, err := f.getter.getwithtimeout(f.fetchurl, f.timeout) if err != nil { if res != nil && res.body != nil { res.body.close() } return nil, errors.wrap(err, errfetch.error()) } defer res.body.close() ioutil.readall(res.body) return &parser.fixturexml{}, nil }
output of fixture worker lsof
: https://pastebin.com/fdukpyse
output of test: https://pastebin.com/ugpk0czn
test never leaks whereas fixture worker leaks.
fixture worker using same code test, request http gets using myhttp package.
if want test represent reality, need use in same manner outside of tests. in case you're not reading of responses, , transport happens preventing lost connections because it's discarding them possible since can't reused.
reading response use connection, , state it's "leaked". need handle errors in cases, , need close()
response body. pattern handle http response , make sure it's closed simple, , doesn't require testing (see what happen if don't close response.body in golang?)
resp, err := getwithtimeout(s.url) if err != nil { t.fatal(err) } ioutil.readall(resp.body) resp.body.close()
this arguably of limited usefulness, since common bugs result improper error , response handling, , you're not testing because test needs correctly in first place.
the remaining problem here getwithtimeout
method returns error value and valid http response, contradicts http package documentation user's expectations. if you're going insert error, better handle response @ same point ensure body closed , discarded.
finally, of getwithtimeout
superfluous, since not creating transports every time incorrect, creating http.client every request unnecessary meant reused , safe concurrent use.
Comments
Post a Comment