From 6f90f009eb98eb6431f15e128b255f3467189d92 Mon Sep 17 00:00:00 2001 From: tcnksm Date: Tue, 25 Oct 2016 16:22:00 +0900 Subject: [PATCH] Add tests and travis tests --- .travis.yml | 16 ++++++ httpstat.go | 19 +++++++ httpstat_test.go | 143 +++++++++++++++++++++++++++++++++++++++++++++++ 3 files changed, 178 insertions(+) create mode 100644 .travis.yml create mode 100644 httpstat_test.go diff --git a/.travis.yml b/.travis.yml new file mode 100644 index 0000000..a3ed0bc --- /dev/null +++ b/.travis.yml @@ -0,0 +1,16 @@ +language: go +go: + - 1.7.3 + - tip + +os: + - linux + - osx + +sudo: false + +install: + - echo "skipping travis' default" + +script: + - go test -v diff --git a/httpstat.go b/httpstat.go index 25427e5..aa2ffb5 100644 --- a/httpstat.go +++ b/httpstat.go @@ -11,11 +11,13 @@ import ( // Result stores httpstat info. type Result struct { + // The following are duration for each phase DNSLookup time.Duration TCPConnection time.Duration TLSHandshake time.Duration ServerProcessing time.Duration + // The followings are timeline of reuqest NameLookup time.Duration Connect time.Duration Pretransfer time.Duration @@ -30,6 +32,22 @@ type Result struct { isTLS bool } +func (r *Result) durations(t time.Time) map[string]time.Duration { + return map[string]time.Duration{ + "DNSLookup": r.DNSLookup, + "TCPConnection": r.TCPConnection, + "TLSHandshake": r.TLSHandshake, + "ServerProcessing": r.ServerProcessing, + "ContentTransfer": r.ContentTransfer(t), + + "NameLookup": r.NameLookup, + "Connect": r.Connect, + "Pretransfer": r.Connect, + "StartTransfer": r.StartTransfer, + "Total": r.Total(t), + } +} + // ContentTransfer returns the duration of content transfer time. // It is from first response byte to the given time. The time must // be time after read body (go-httpstat can not detect that time). @@ -105,6 +123,7 @@ func WithHTTPStat(ctx context.Context, r *Result) context.Context { r.t0 = time.Now() r.t1 = r.t0 r.t2 = r.t0 + r.t3 = r.t0 } if r.isTLS { diff --git a/httpstat_test.go b/httpstat_test.go new file mode 100644 index 0000000..93c9e0a --- /dev/null +++ b/httpstat_test.go @@ -0,0 +1,143 @@ +package httpstat + +import ( + "io" + "io/ioutil" + "net" + "net/http" + "testing" + "time" +) + +const ( + TestDomainHTTP = "http://example.com" + TestDomainHTTPS = "https://example.com" +) + +func DefaultTransport() *http.Transport { + // It comes from std transport.go + return &http.Transport{ + Proxy: http.ProxyFromEnvironment, + DialContext: (&net.Dialer{ + Timeout: 30 * time.Second, + KeepAlive: 30 * time.Second, + }).DialContext, + MaxIdleConns: 100, + IdleConnTimeout: 90 * time.Second, + TLSHandshakeTimeout: 10 * time.Second, + ExpectContinueTimeout: 1 * time.Second, + } +} + +// To avoid shared transport +func DefaultClient() *http.Client { + return &http.Client{ + Transport: DefaultTransport(), + } +} + +func NewRequest(t *testing.T, urlStr string, result *Result) *http.Request { + req, err := http.NewRequest("GET", urlStr, nil) + if err != nil { + t.Fatal("NewRequest failed:", err) + } + + ctx := WithHTTPStat(req.Context(), result) + return req.WithContext(ctx) +} + +func TestHTTPStat_HTTPS(t *testing.T) { + var result Result + req := NewRequest(t, TestDomainHTTPS, &result) + + client := DefaultClient() + res, err := client.Do(req) + if err != nil { + t.Fatal("client.Do failed:", err) + } + + if _, err := io.Copy(ioutil.Discard, res.Body); err != nil { + t.Fatal("io.Copy failed:", err) + } + res.Body.Close() + end := time.Now() + + for k, d := range result.durations(end) { + if d <= 0*time.Millisecond { + t.Fatalf("expect %s to be non-zero", k) + } + } +} + +func TestHTTPStat_HTTP(t *testing.T) { + var result Result + req := NewRequest(t, TestDomainHTTP, &result) + + client := DefaultClient() + res, err := client.Do(req) + if err != nil { + t.Fatal("client.Do failed:", err) + } + + if _, err := io.Copy(ioutil.Discard, res.Body); err != nil { + t.Fatal("io.Copy failed:", err) + } + res.Body.Close() + end := time.Now() + + if got, want := result.TLSHandshake, 0*time.Millisecond; got != want { + t.Fatalf("TLSHandshake time of HTTP = %d, want %d", got, want) + } + + // Except TLS should be non zero + durations := result.durations(end) + delete(durations, "TLSHandshake") + + for k, d := range durations { + if d <= 0*time.Millisecond { + t.Fatalf("expect %s to be non-zero", k) + } + } +} + +func TestHTTPStat_beforeGO17(t *testing.T) { + var result Result + req := NewRequest(t, TestDomainHTTPS, &result) + + // Before go1.7, it uses non context based Dial function. + // It doesn't support httptrace. + oldTransport := &http.Transport{ + Dial: (&net.Dialer{ + Timeout: 5 * time.Second, + }).Dial, + TLSHandshakeTimeout: 5 * time.Second, + } + + client := &http.Client{ + Timeout: time.Second * 10, + Transport: oldTransport, + } + + res, err := client.Do(req) + if err != nil { + t.Fatal("client.Do failed:", err) + } + + if _, err := io.Copy(ioutil.Discard, res.Body); err != nil { + t.Fatal("io.Copy failed:", err) + } + res.Body.Close() + + // The following values are not mesured. + durations := []time.Duration{ + result.DNSLookup, + result.TCPConnection, + result.TLSHandshake, + } + + for i, d := range durations { + if got, want := d, 0*time.Millisecond; got != want { + t.Fatalf("#%d expect %d to be eq %d", i, got, want) + } + } +}