diff options
Diffstat (limited to 'tools/http2_interop')
-rw-r--r-- | tools/http2_interop/http2interop_test.go | 44 | ||||
-rw-r--r-- | tools/http2_interop/s6.5.go | 59 | ||||
-rw-r--r-- | tools/http2_interop/s6.5_test.go | 16 | ||||
-rw-r--r-- | tools/http2_interop/settings.go | 4 | ||||
-rw-r--r-- | tools/http2_interop/testsuite.go | 56 |
5 files changed, 168 insertions, 11 deletions
diff --git a/tools/http2_interop/http2interop_test.go b/tools/http2_interop/http2interop_test.go index b35d085569..fb314da196 100644 --- a/tools/http2_interop/http2interop_test.go +++ b/tools/http2_interop/http2interop_test.go @@ -3,6 +3,7 @@ package http2interop import ( "crypto/tls" "crypto/x509" + "encoding/json" "flag" "fmt" "io/ioutil" @@ -67,7 +68,8 @@ func (ctx *HTTP2InteropCtx) Close() error { return nil } -func TestClientShortSettings(t *testing.T) { +func TestSoonClientShortSettings(t *testing.T) { + defer Report(t) if *testCase != "framing" { t.SkipNow() } @@ -78,7 +80,8 @@ func TestClientShortSettings(t *testing.T) { } } -func TestShortPreface(t *testing.T) { +func TestSoonShortPreface(t *testing.T) { + defer Report(t) if *testCase != "framing" { t.SkipNow() } @@ -89,7 +92,8 @@ func TestShortPreface(t *testing.T) { } } -func TestUnknownFrameType(t *testing.T) { +func TestSoonUnknownFrameType(t *testing.T) { + defer Report(t) if *testCase != "framing" { t.SkipNow() } @@ -99,7 +103,8 @@ func TestUnknownFrameType(t *testing.T) { } } -func TestClientPrefaceWithStreamId(t *testing.T) { +func TestSoonClientPrefaceWithStreamId(t *testing.T) { + defer Report(t) if *testCase != "framing" { t.SkipNow() } @@ -108,7 +113,8 @@ func TestClientPrefaceWithStreamId(t *testing.T) { matchError(t, err, "EOF") } -func TestTLSApplicationProtocol(t *testing.T) { +func TestSoonTLSApplicationProtocol(t *testing.T) { + defer Report(t) if *testCase != "tls" { t.SkipNow() } @@ -117,7 +123,8 @@ func TestTLSApplicationProtocol(t *testing.T) { matchError(t, err, "EOF", "broken pipe") } -func TestTLSMaxVersion(t *testing.T) { +func TestSoonTLSMaxVersion(t *testing.T) { + defer Report(t) if *testCase != "tls" { t.SkipNow() } @@ -128,7 +135,8 @@ func TestTLSMaxVersion(t *testing.T) { matchError(t, err, "EOF", "server selected unsupported protocol") } -func TestTLSBadCipherSuites(t *testing.T) { +func TestSoonTLSBadCipherSuites(t *testing.T) { + defer Report(t) if *testCase != "tls" { t.SkipNow() } @@ -151,5 +159,25 @@ func matchError(t *testing.T, err error, matches ...string) { func TestMain(m *testing.M) { flag.Parse() - os.Exit(m.Run()) + m.Run() + var fatal bool + var any bool + for _, ci := range allCaseInfos.Cases { + if ci.Skipped { + continue + } + any = true + if !ci.Passed && ci.Fatal { + fatal = true + } + } + + if err := json.NewEncoder(os.Stderr).Encode(&allCaseInfos); err != nil { + fmt.Println("Failed to encode", err) + } + var code int + if !any || fatal { + code = 1 + } + os.Exit(code) } diff --git a/tools/http2_interop/s6.5.go b/tools/http2_interop/s6.5.go index 8145b6e031..4295c46f73 100644 --- a/tools/http2_interop/s6.5.go +++ b/tools/http2_interop/s6.5.go @@ -1,6 +1,7 @@ package http2interop import ( + "fmt" "time" ) @@ -11,7 +12,6 @@ func testSmallMaxFrameSize(ctx *HTTP2InteropCtx) error { return err } defer conn.Close() - conn.Log = ctx.T.Log conn.SetDeadline(time.Now().Add(defaultTimeout)) sf := &SettingsFrame{ @@ -31,3 +31,60 @@ func testSmallMaxFrameSize(ctx *HTTP2InteropCtx) error { return nil } + +// Section 6.5.3 says all settings frames must be acked. +func testAllSettingsFramesAcked(ctx *HTTP2InteropCtx) error { + conn, err := connect(ctx) + if err != nil { + return err + } + defer conn.Close() + conn.SetDeadline(time.Now().Add(defaultTimeout)) + + sf := &SettingsFrame{} + if err := http2Connect(conn, sf); err != nil { + return err + } + + // The spec says "The values in the SETTINGS frame MUST be processed in the order they + // appear. [...] Once all values have been processed, the recipient MUST immediately + // emit a SETTINGS frame with the ACK flag set." From my understanding, processing all + // of no values warrants an ack per frame. + for i := 0; i < 10; i++ { + if err := streamFrame(conn, sf); err != nil { + return err + } + } + + var settingsFramesReceived = 0 + // The server by default sends a settings frame as part of the handshake, and another + // after the receipt of the initial settings frame as part of our conneection preface. + // This means we expected 1 + 1 + 10 = 12 settings frames in return, with all but the + // first having the ack bit. + for settingsFramesReceived < 12 { + f, err := parseFrame(conn) + if err != nil { + return err + } + + // Other frames come down the wire too, including window update. Just ignore those. + if f, ok := f.(*SettingsFrame); ok { + settingsFramesReceived += 1 + if settingsFramesReceived == 1 { + if f.Header.Flags&SETTINGS_FLAG_ACK > 0 { + return fmt.Errorf("settings frame should not have used ack: %v") + } + continue + } + + if f.Header.Flags&SETTINGS_FLAG_ACK == 0 { + return fmt.Errorf("settings frame should have used ack: %v", f) + } + if len(f.Params) != 0 { + return fmt.Errorf("settings ack cannot have params: %v", f) + } + } + } + + return nil +} diff --git a/tools/http2_interop/s6.5_test.go b/tools/http2_interop/s6.5_test.go index 48e8ced576..063fd5664c 100644 --- a/tools/http2_interop/s6.5_test.go +++ b/tools/http2_interop/s6.5_test.go @@ -4,11 +4,23 @@ import ( "testing" ) -func TestSmallMaxFrameSize(t *testing.T) { - if *testCase != "experimental" { +func TestSoonSmallMaxFrameSize(t *testing.T) { + defer Report(t) + if *testCase != "framing" { t.SkipNow() } ctx := InteropCtx(t) err := testSmallMaxFrameSize(ctx) matchError(t, err, "Got goaway frame") } + +func TestSoonAllSettingsFramesAcked(t *testing.T) { + defer Report(t) + if *testCase != "framing" { + t.SkipNow() + } + ctx := InteropCtx(t) + if err := testAllSettingsFramesAcked(ctx); err != nil { + t.Fatal(err) + } +} diff --git a/tools/http2_interop/settings.go b/tools/http2_interop/settings.go index 97914d960f..544cec01ee 100644 --- a/tools/http2_interop/settings.go +++ b/tools/http2_interop/settings.go @@ -26,6 +26,10 @@ const ( SettingsMaxHeaderListSize SettingsIdentifier = 6 ) +const ( + SETTINGS_FLAG_ACK byte = 0x01 +) + func (si SettingsIdentifier) String() string { switch si { case SettingsHeaderTableSize: diff --git a/tools/http2_interop/testsuite.go b/tools/http2_interop/testsuite.go new file mode 100644 index 0000000000..51d36e217e --- /dev/null +++ b/tools/http2_interop/testsuite.go @@ -0,0 +1,56 @@ +package http2interop + +import ( + "path" + "runtime" + "strings" + "sync" + "testing" +) + +// When a test is skipped or fails, runtime.Goexit() is called which destroys the callstack. +// This means the name of the test case is lost, so we need to grab a copy of pc before. +func Report(t testing.TB) { + // If the goroutine panics, Fatal()s, or Skip()s, the function name is at the 3rd callstack + // layer. On success, its at 1st. Since it's hard to check which happened, just try both. + pcs := make([]uintptr, 10) + total := runtime.Callers(1, pcs) + var name string + for _, pc := range pcs[:total] { + fn := runtime.FuncForPC(pc) + fullName := fn.Name() + if strings.HasPrefix(path.Ext(fullName), ".Test") { + // Skip the leading . + name = string([]byte(path.Ext(fullName))[1:]) + break + } + } + if name == "" { + return + } + + allCaseInfos.lock.Lock() + defer allCaseInfos.lock.Unlock() + allCaseInfos.Cases = append(allCaseInfos.Cases, &caseInfo{ + Name: name, + Passed: !t.Failed() && !t.Skipped(), + Skipped: t.Skipped(), + Fatal: t.Failed() && !strings.HasPrefix(name, "TestSoon"), + }) +} + +type caseInfo struct { + Name string `json:"name"` + Passed bool `json:"passed"` + Skipped bool `json:"skipped,omitempty"` + Fatal bool `json:"fatal,omitempty"` +} + +type caseInfos struct { + lock sync.Mutex + Cases []*caseInfo `json:"cases"` +} + +var ( + allCaseInfos = caseInfos{} +) |