testing - 如何在 Go 测试中测试泄漏连接?

标签 testing go memory-leaks

在我发现我的程序存在漏洞之后,我就解决了这个问题。但是,现在我正试图找出如何在 Go 测试中“测试 ”泄漏连接?这是我的问题。

我曾尝试更改测试中的请求数量,但没关系。无论我做什么,我测试中的当前 TCP 连接数都保持不变。

func TestLeakingConnections(t *testing.T) {
    getter := myhttp.New()

    s := newServer(ok)
    defer s.Close()

    cur := tcps(t)
    for i := 0; i < 1000; i++ {
        r, _ := getter.GetWithTimeout(s.URL, time.Millisecond*10)
        r.Body.Close()
    }

    for 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")
    }

    for _, 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 for clarification

func (g *Getter) GetWithTimeout(
    url string,
    timeout time.Duration,
) (
    *http.Response, error,
) {
    // this is the leaking part
    // moving this out of here will 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

// some outside code injects getter into fixture_worker like this:
getter := myhttp.New()

// NewWithTimeout creates a new fetcher with 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
}

leaking conns gif


fixture worker lsof 的输出:https://pastebin.com/fDUkpYsE

测试输出:https://pastebin.com/uGpK0czn

测试永远不会泄漏,而 fixture worker 会泄漏。

Fixture worker 使用与测试相同的代码,使用 myhttp 包请求 http 获取。

最佳答案

如果您希望您的测试代表现实,您需要以与测试之外相同的方式使用它。在这种情况下,您没有阅读任何响应,并且传输恰好在防止丢失连接,因为它会尽快丢弃它们,因为它们无法重复使用。

读取响应将使用连接,并使其进入“泄漏”状态。您还需要在所有情况下正确处理错误,并且始终需要 Close() 响应主体。处理 http 响应并确保其关闭的模式非常简单,并且不一定需要测试(请参阅 What could happen if I 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()

这可以说是有限的用处,因为最常见的错误将由不正确的错误和响应处理引起,而您没有测试它,因为测试首先需要正确地进行。

这里剩下的问题是您的 GetWithTimeout 方法返回一个错误值一个有效的 http 响应,这与 http 包文档以及大多数用户的期望相矛盾。如果您要插入错误,最好在同一点处理响应以确保正文被关闭和丢弃。

最后,GetWithTimeout 的大部分内容都是多余的,因为不仅每次都创建 Transports 不正确,而且每个请求都创建一个 http.Client 通常是不必要的,因为它们应该被重用并且对同时使用。

关于testing - 如何在 Go 测试中测试泄漏连接?,我们在Stack Overflow上找到一个类似的问题: https://stackoverflow.com/questions/45746753/

相关文章:

ruby - 使用 headless Chrome 将 URL 列入黑名单

ruby-on-rails - 如何配置 Guard 来运行 Rails 5 系统测试?

go - 在具有非常规目录结构的同一页面上提供 HTML 模板和 CSS?

ios - imageWithCG图像根据仪器泄漏

c++ - 程序的内存使用量随着时间的推移而增加,但内存泄漏工具没有报告任何问题

java - 内存泄漏在 MAT 中显示为 GC root : Native Stack

reactjs - 如何使用 msw 有条件地模拟错误响应

wpf行为单元测试

arrays - 返回一个映射并将结果存储在不同的变量中

golang FileServer 设置目录