aboutsummaryrefslogtreecommitdiffhomepage
path: root/tools/http2_interop
diff options
context:
space:
mode:
Diffstat (limited to 'tools/http2_interop')
-rw-r--r--tools/http2_interop/http2interop_test.go44
-rw-r--r--tools/http2_interop/s6.5.go59
-rw-r--r--tools/http2_interop/s6.5_test.go16
-rw-r--r--tools/http2_interop/settings.go4
-rw-r--r--tools/http2_interop/testsuite.go56
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{}
+)