1// Copyright 2014 The Go Authors. All rights reserved.
2// Use of this source code is governed by a BSD-style
3// license that can be found in the LICENSE file.
4
5package http2
6
7import (
8	"bytes"
9	"errors"
10	"flag"
11	"fmt"
12	"net/http"
13	"os/exec"
14	"strconv"
15	"strings"
16	"testing"
17	"time"
18
19	"golang.org/x/net/http2/hpack"
20)
21
22var knownFailing = flag.Bool("known_failing", false, "Run known-failing tests.")
23
24func condSkipFailingTest(t *testing.T) {
25	if !*knownFailing {
26		t.Skip("Skipping known-failing test without --known_failing")
27	}
28}
29
30func init() {
31	inTests = true
32	DebugGoroutines = true
33	flag.BoolVar(&VerboseLogs, "verboseh2", VerboseLogs, "Verbose HTTP/2 debug logging")
34}
35
36func TestSettingString(t *testing.T) {
37	tests := []struct {
38		s    Setting
39		want string
40	}{
41		{Setting{SettingMaxFrameSize, 123}, "[MAX_FRAME_SIZE = 123]"},
42		{Setting{1<<16 - 1, 123}, "[UNKNOWN_SETTING_65535 = 123]"},
43	}
44	for i, tt := range tests {
45		got := fmt.Sprint(tt.s)
46		if got != tt.want {
47			t.Errorf("%d. for %#v, string = %q; want %q", i, tt.s, got, tt.want)
48		}
49	}
50}
51
52type twriter struct {
53	t  testing.TB
54	st *serverTester // optional
55}
56
57func (w twriter) Write(p []byte) (n int, err error) {
58	if w.st != nil {
59		ps := string(p)
60		for _, phrase := range w.st.logFilter {
61			if strings.Contains(ps, phrase) {
62				return len(p), nil // no logging
63			}
64		}
65	}
66	w.t.Logf("%s", p)
67	return len(p), nil
68}
69
70// like encodeHeader, but don't add implicit pseudo headers.
71func encodeHeaderNoImplicit(t *testing.T, headers ...string) []byte {
72	var buf bytes.Buffer
73	enc := hpack.NewEncoder(&buf)
74	for len(headers) > 0 {
75		k, v := headers[0], headers[1]
76		headers = headers[2:]
77		if err := enc.WriteField(hpack.HeaderField{Name: k, Value: v}); err != nil {
78			t.Fatalf("HPACK encoding error for %q/%q: %v", k, v, err)
79		}
80	}
81	return buf.Bytes()
82}
83
84// Verify that curl has http2.
85func requireCurl(t *testing.T) {
86	out, err := dockerLogs(curl(t, "--version"))
87	if err != nil {
88		t.Skipf("failed to determine curl features; skipping test")
89	}
90	if !strings.Contains(string(out), "HTTP2") {
91		t.Skip("curl doesn't support HTTP2; skipping test")
92	}
93}
94
95func curl(t *testing.T, args ...string) (container string) {
96	out, err := exec.Command("docker", append([]string{"run", "-d", "--net=host", "gohttp2/curl"}, args...)...).Output()
97	if err != nil {
98		t.Skipf("Failed to run curl in docker: %v, %s", err, out)
99	}
100	return strings.TrimSpace(string(out))
101}
102
103// Verify that h2load exists.
104func requireH2load(t *testing.T) {
105	out, err := dockerLogs(h2load(t, "--version"))
106	if err != nil {
107		t.Skipf("failed to probe h2load; skipping test: %s", out)
108	}
109	if !strings.Contains(string(out), "h2load nghttp2/") {
110		t.Skipf("h2load not present; skipping test. (Output=%q)", out)
111	}
112}
113
114func h2load(t *testing.T, args ...string) (container string) {
115	out, err := exec.Command("docker", append([]string{"run", "-d", "--net=host", "--entrypoint=/usr/local/bin/h2load", "gohttp2/curl"}, args...)...).Output()
116	if err != nil {
117		t.Skipf("Failed to run h2load in docker: %v, %s", err, out)
118	}
119	return strings.TrimSpace(string(out))
120}
121
122type puppetCommand struct {
123	fn   func(w http.ResponseWriter, r *http.Request)
124	done chan<- bool
125}
126
127type handlerPuppet struct {
128	ch chan puppetCommand
129}
130
131func newHandlerPuppet() *handlerPuppet {
132	return &handlerPuppet{
133		ch: make(chan puppetCommand),
134	}
135}
136
137func (p *handlerPuppet) act(w http.ResponseWriter, r *http.Request) {
138	for cmd := range p.ch {
139		cmd.fn(w, r)
140		cmd.done <- true
141	}
142}
143
144func (p *handlerPuppet) done() { close(p.ch) }
145func (p *handlerPuppet) do(fn func(http.ResponseWriter, *http.Request)) {
146	done := make(chan bool)
147	p.ch <- puppetCommand{fn, done}
148	<-done
149}
150func dockerLogs(container string) ([]byte, error) {
151	out, err := exec.Command("docker", "wait", container).CombinedOutput()
152	if err != nil {
153		return out, err
154	}
155	exitStatus, err := strconv.Atoi(strings.TrimSpace(string(out)))
156	if err != nil {
157		return out, errors.New("unexpected exit status from docker wait")
158	}
159	out, err = exec.Command("docker", "logs", container).CombinedOutput()
160	exec.Command("docker", "rm", container).Run()
161	if err == nil && exitStatus != 0 {
162		err = fmt.Errorf("exit status %d: %s", exitStatus, out)
163	}
164	return out, err
165}
166
167func kill(container string) {
168	exec.Command("docker", "kill", container).Run()
169	exec.Command("docker", "rm", container).Run()
170}
171
172func cleanDate(res *http.Response) {
173	if d := res.Header["Date"]; len(d) == 1 {
174		d[0] = "XXX"
175	}
176}
177
178func TestSorterPoolAllocs(t *testing.T) {
179	ss := []string{"a", "b", "c"}
180	h := http.Header{
181		"a": nil,
182		"b": nil,
183		"c": nil,
184	}
185	sorter := new(sorter)
186
187	if allocs := testing.AllocsPerRun(100, func() {
188		sorter.SortStrings(ss)
189	}); allocs >= 1 {
190		t.Logf("SortStrings allocs = %v; want <1", allocs)
191	}
192
193	if allocs := testing.AllocsPerRun(5, func() {
194		if len(sorter.Keys(h)) != 3 {
195			t.Fatal("wrong result")
196		}
197	}); allocs > 0 {
198		t.Logf("Keys allocs = %v; want <1", allocs)
199	}
200}
201
202// waitCondition reports whether fn eventually returned true,
203// checking immediately and then every checkEvery amount,
204// until waitFor has elapsed, at which point it returns false.
205func waitCondition(waitFor, checkEvery time.Duration, fn func() bool) bool {
206	deadline := time.Now().Add(waitFor)
207	for time.Now().Before(deadline) {
208		if fn() {
209			return true
210		}
211		time.Sleep(checkEvery)
212	}
213	return false
214}
215
216// waitErrCondition is like waitCondition but with errors instead of bools.
217func waitErrCondition(waitFor, checkEvery time.Duration, fn func() error) error {
218	deadline := time.Now().Add(waitFor)
219	var err error
220	for time.Now().Before(deadline) {
221		if err = fn(); err == nil {
222			return nil
223		}
224		time.Sleep(checkEvery)
225	}
226	return err
227}
228
229func equalError(a, b error) bool {
230	if a == nil {
231		return b == nil
232	}
233	if b == nil {
234		return a == nil
235	}
236	return a.Error() == b.Error()
237}
238
239// Tests that http2.Server.IdleTimeout is initialized from
240// http.Server.{Idle,Read}Timeout. http.Server.IdleTimeout was
241// added in Go 1.8.
242func TestConfigureServerIdleTimeout_Go18(t *testing.T) {
243	const timeout = 5 * time.Second
244	const notThisOne = 1 * time.Second
245
246	// With a zero http2.Server, verify that it copies IdleTimeout:
247	{
248		s1 := &http.Server{
249			IdleTimeout: timeout,
250			ReadTimeout: notThisOne,
251		}
252		s2 := &Server{}
253		if err := ConfigureServer(s1, s2); err != nil {
254			t.Fatal(err)
255		}
256		if s2.IdleTimeout != timeout {
257			t.Errorf("s2.IdleTimeout = %v; want %v", s2.IdleTimeout, timeout)
258		}
259	}
260
261	// And that it falls back to ReadTimeout:
262	{
263		s1 := &http.Server{
264			ReadTimeout: timeout,
265		}
266		s2 := &Server{}
267		if err := ConfigureServer(s1, s2); err != nil {
268			t.Fatal(err)
269		}
270		if s2.IdleTimeout != timeout {
271			t.Errorf("s2.IdleTimeout = %v; want %v", s2.IdleTimeout, timeout)
272		}
273	}
274
275	// Verify that s1's IdleTimeout doesn't overwrite an existing setting:
276	{
277		s1 := &http.Server{
278			IdleTimeout: notThisOne,
279		}
280		s2 := &Server{
281			IdleTimeout: timeout,
282		}
283		if err := ConfigureServer(s1, s2); err != nil {
284			t.Fatal(err)
285		}
286		if s2.IdleTimeout != timeout {
287			t.Errorf("s2.IdleTimeout = %v; want %v", s2.IdleTimeout, timeout)
288		}
289	}
290}
291