2017-08-18 39 views
1

在我的程序中發現泄漏之後,我已經解決了這個問題。但是,現在我試圖找出如何「測試」在Go測試中泄漏連接?這是我的問題。如何在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


夾具工人lsof的輸出:https://pastebin.com/fDUkpYsE

測試的輸出:https://pastebin.com/uGpK0czn

測試從來沒有泄漏,而它泄漏燈具的工人。

Fixture Worker正在使用與測試相同的代碼來請求http獲取使用myhttp包。

+0

你根本沒有在讀身體,http客戶端通過完全拋棄連接來保存你的連接(這不是保證行爲,也不能被依賴)。 'GetWithTimeout'也打破了關閉身體的記錄模式,在有合法的響應時添加一個錯誤值。這意味着調用者無法知道機構是否需要關閉或不出錯,而不會以某種方式檢查「ErrStatus」,可能導致更多的泄漏。我不明白'dialFunc'的原因,你現在應該使用'DialContext'。 – JimB

+0

謝謝你的提示,我會研究這些。但是,這並不能回答我的問題:我如何在測試中測試泄漏? –

回答

1

如果你想讓你的測試代表現實,你需要以與測試之外相同的方式使用它。在這種情況下,您不會讀取任何響應,並且運輸恰好可以防止丟失連接,因爲它們會盡快丟棄它們,因爲它們不能被重用。

讀取響應將使用連接,並使其進入「泄漏」狀態。您還需要在所有情況下妥善處理錯誤,並且您始終需要響應正文。該模式來處理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是多餘的,因爲不僅每次創建傳輸都不正確,而且創建一個http.Client通常是不必要的,因爲它們意味着被重用並且對於併發使用是安全的。

+0

確實很棒的提示,非常感謝你的所有。我正在考慮所有這些。但是,我仍然無法找到在我的集成測試中測試泄漏連接的方法。我很好奇如何測試這種行爲。 –

+0

@InancGumus:你正在做的'lsof'檢查工作正常(儘管你可能想過濾TIME_WAIT之類的東西,並且只查找客戶端連接),並且在你的測試中修復代碼將顯示預期的結果。你想要測試什麼其他行爲? – JimB

+0

是的,我明白了。但是,正如在我的問題中的代碼,目前,測試無法檢測到泄漏。然而,當我自己在CLI中運行'lsof'時,我可以看到泄漏(_你可以在我的問題中看到附加的gif)。 –