github.com/useflyent/fhttp@v0.0.0-20211004035111-333f430cfbbf/http2/http2_test.go (about)

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