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 }