Repository: quic-go/quic-go
Pull request #5073 (2025-04-22 11:24:10)

http3: migrate the capsule tests away from Ginkgo
Part of #3652.


Patch:
diff --git a/http3/request_writer.go b/http3/request_writer.go
index 2dbacb15..6d473aa6 100644
--- a/http3/request_writer.go
+++ b/http3/request_writer.go
@@ -17,7 +17,6 @@ import (
 	"golang.org/x/net/idna"
 
 	"github.com/quic-go/qpack"
-	"github.com/quic-go/quic-go"
 )
 
 const bodyCopyBufferSize = 8 * 1024
@@ -37,13 +36,13 @@ func newRequestWriter() *requestWriter {
 	}
 }
 
-func (w *requestWriter) WriteRequestHeader(str quic.Stream, req *http.Request, gzip bool) error {
+func (w *requestWriter) WriteRequestHeader(wr io.Writer, req *http.Request, gzip bool) error {
 	// TODO: figure out how to add support for trailers
 	buf := &bytes.Buffer{}
 	if err := w.writeHeaders(buf, req, gzip); err != nil {
 		return err
 	}
-	if _, err := str.Write(buf.Bytes()); err != nil {
+	if _, err := wr.Write(buf.Bytes()); err != nil {
 		return err
 	}
 	trace := httptrace.ContextClientTrace(req.Context())
diff --git a/http3/response_writer.go b/http3/response_writer.go
index b8b68120..ab0282e4 100644
--- a/http3/response_writer.go
+++ b/http3/response_writer.go
@@ -57,6 +57,13 @@ var (
 	_ http.Flusher        = &responseWriter{}
 	_ Hijacker            = &responseWriter{}
 	_ HTTPStreamer        = &responseWriter{}
+	// make sure that we implement (some of the) methods used by the http.ResponseController
+	_ interface {
+		SetReadDeadline(time.Time) error
+		SetWriteDeadline(time.Time) error
+		Flush()
+		FlushError() error
+	} = &responseWriter{}
 )
 
 func newResponseWriter(str *stream, conn Connection, isHead bool, logger *slog.Logger) *responseWriter {


Test patch:
diff --git a/http3/capsule_test.go b/http3/capsule_test.go
index 758d1be2..34408190 100644
--- a/http3/capsule_test.go
+++ b/http3/capsule_test.go
@@ -3,62 +3,55 @@ package http3
 import (
 	"bytes"
 	"io"
+	"testing"
 
 	"github.com/quic-go/quic-go/quicvarint"
 
-	. "github.com/onsi/ginkgo/v2"
-	. "github.com/onsi/gomega"
+	"github.com/stretchr/testify/require"
 )
 
-var _ = Describe("Capsule", func() {
-	It("parses Capsules", func() {
-		b := quicvarint.Append(nil, 1337)
-		b = quicvarint.Append(b, 6)
-		b = append(b, []byte("foobar")...)
-
-		ct, r, err := ParseCapsule(bytes.NewReader(b))
-		Expect(err).ToNot(HaveOccurred())
-		Expect(ct).To(BeEquivalentTo(1337))
-		buf := make([]byte, 3)
-		n, err := r.Read(buf)
-		Expect(err).ToNot(HaveOccurred())
-		Expect(n).To(Equal(3))
-		Expect(buf).To(Equal([]byte("foo")))
-		data, err := io.ReadAll(r)
-		Expect(err).ToNot(HaveOccurred())
-		Expect(data).To(Equal([]byte("bar")))
-	})
-
-	It("writes capsules", func() {
-		var buf bytes.Buffer
-		Expect(WriteCapsule(&buf, 1337, []byte("foobar"))).To(Succeed())
-
-		ct, r, err := ParseCapsule(&buf)
-		Expect(err).ToNot(HaveOccurred())
-		Expect(ct).To(BeEquivalentTo(1337))
-		val, err := io.ReadAll(r)
-		Expect(err).ToNot(HaveOccurred())
-		Expect(string(val)).To(Equal("foobar"))
-	})
-
-	It("errors on EOF", func() {
-		b := quicvarint.Append(nil, 1337)
-		b = quicvarint.Append(b, 6)
-		b = append(b, []byte("foobar")...)
-
-		for i := range b {
-			ct, r, err := ParseCapsule(bytes.NewReader(b[:i]))
-			if err != nil {
-				if i == 0 {
-					Expect(err).To(MatchError(io.EOF))
-				} else {
-					Expect(err).To(MatchError(io.ErrUnexpectedEOF))
-				}
-				continue
+func TestCapsuleParsing(t *testing.T) {
+	b := quicvarint.Append(nil, 1337)
+	b = quicvarint.Append(b, 6)
+	b = append(b, []byte("foobar")...)
+
+	ct, r, err := ParseCapsule(bytes.NewReader(b))
+	require.NoError(t, err)
+	require.Equal(t, CapsuleType(1337), ct)
+	buf := make([]byte, 3)
+	n, err := r.Read(buf)
+	require.NoError(t, err)
+	require.Equal(t, 3, n)
+	require.Equal(t, []byte("foo"), buf)
+	data, err := io.ReadAll(r) // reads until EOF
+	require.NoError(t, err)
+	require.Equal(t, []byte("bar"), data)
+
+	// test EOF vs ErrUnexpectedEOF
+	for i := range b {
+		ct, r, err := ParseCapsule(bytes.NewReader(b[:i]))
+		if err != nil {
+			if i == 0 {
+				require.ErrorIs(t, err, io.EOF)
+			} else {
+				require.ErrorIs(t, err, io.ErrUnexpectedEOF)
 			}
-			Expect(ct).To(BeEquivalentTo(1337))
-			_, err = io.ReadAll(r)
-			Expect(err).To(Equal(io.ErrUnexpectedEOF))
+			continue
 		}
-	})
-})
+		require.Equal(t, CapsuleType(1337), ct)
+		_, err = io.ReadAll(r)
+		require.ErrorIs(t, err, io.ErrUnexpectedEOF)
+	}
+}
+
+func TestCapsuleWriting(t *testing.T) {
+	var buf bytes.Buffer
+	require.NoError(t, WriteCapsule(&buf, 1337, []byte("foobar")))
+
+	ct, r, err := ParseCapsule(&buf)
+	require.NoError(t, err)
+	require.Equal(t, CapsuleType(1337), ct)
+	val, err := io.ReadAll(r)
+	require.NoError(t, err)
+	require.Equal(t, "foobar", string(val))
+}
diff --git a/http3/request_writer_test.go b/http3/request_writer_test.go
index 5e5d7bd4..85ee512b 100644
--- a/http3/request_writer_test.go
+++ b/http3/request_writer_test.go
@@ -7,10 +7,8 @@ import (
 	"testing"
 
 	"github.com/quic-go/qpack"
-	mockquic "github.com/quic-go/quic-go/internal/mocks/quic"
 
 	"github.com/stretchr/testify/require"
-	"go.uber.org/mock/gomock"
 )
 
 func decodeRequest(t *testing.T, str io.Reader) map[string]string {
@@ -49,12 +47,9 @@ func testRequestWriterGzip(t *testing.T, gzip bool) {
 	req.AddCookie(&http.Cookie{Name: "baz", Value: "lorem ipsum"})
 
 	rw := newRequestWriter()
-	strBuf := &bytes.Buffer{}
-	mockCtrl := gomock.NewController(t)
-	str := mockquic.NewMockStream(mockCtrl)
-	str.EXPECT().Write(gomock.Any()).DoAndReturn(strBuf.Write).AnyTimes()
-	require.NoError(t, rw.WriteRequestHeader(str, req, gzip))
-	headerFields := decodeRequest(t, strBuf)
+	buf := &bytes.Buffer{}
+	require.NoError(t, rw.WriteRequestHeader(buf, req, gzip))
+	headerFields := decodeRequest(t, buf)
 	require.Equal(t, "quic-go.net", headerFields[":authority"])
 	require.Equal(t, http.MethodGet, headerFields[":method"])
 	require.Equal(t, "/index.html?foo=bar", headerFields[":path"])
@@ -73,12 +68,8 @@ func TestRequestWriterInvalidHostHeader(t *testing.T) {
 	require.NoError(t, err)
 	req.Host = "foo@bar" // @ is invalid
 	rw := newRequestWriter()
-	strBuf := &bytes.Buffer{}
-	mockCtrl := gomock.NewController(t)
-	str := mockquic.NewMockStream(mockCtrl)
-	str.EXPECT().Write(gomock.Any()).DoAndReturn(strBuf.Write).AnyTimes()
 	require.EqualError(t,
-		rw.WriteRequestHeader(str, req, false),
+		rw.WriteRequestHeader(&bytes.Buffer{}, req, false),
 		"http3: invalid Host header",
 	)
 }
@@ -87,12 +78,9 @@ func TestRequestWriterConnect(t *testing.T) {
 	req, err := http.NewRequest(http.MethodConnect, "https://quic-go.net/", nil)
 	require.NoError(t, err)
 	rw := newRequestWriter()
-	strBuf := &bytes.Buffer{}
-	mockCtrl := gomock.NewController(t)
-	str := mockquic.NewMockStream(mockCtrl)
-	str.EXPECT().Write(gomock.Any()).DoAndReturn(strBuf.Write).AnyTimes()
-	require.NoError(t, rw.WriteRequestHeader(str, req, false))
-	headerFields := decodeRequest(t, strBuf)
+	buf := &bytes.Buffer{}
+	require.NoError(t, rw.WriteRequestHeader(buf, req, false))
+	headerFields := decodeRequest(t, buf)
 	require.Equal(t, http.MethodConnect, headerFields[":method"])
 	require.Equal(t, "quic-go.net", headerFields[":authority"])
 	require.NotContains(t, headerFields, ":path")
@@ -105,12 +93,9 @@ func TestRequestWriterExtendedConnect(t *testing.T) {
 	require.NoError(t, err)
 	req.Proto = "webtransport"
 	rw := newRequestWriter()
-	strBuf := &bytes.Buffer{}
-	mockCtrl := gomock.NewController(t)
-	str := mockquic.NewMockStream(mockCtrl)
-	str.EXPECT().Write(gomock.Any()).DoAndReturn(strBuf.Write).AnyTimes()
-	require.NoError(t, rw.WriteRequestHeader(str, req, false))
-	headerFields := decodeRequest(t, strBuf)
+	buf := &bytes.Buffer{}
+	require.NoError(t, rw.WriteRequestHeader(buf, req, false))
+	headerFields := decodeRequest(t, buf)
 	require.Equal(t, "quic-go.net", headerFields[":authority"])
 	require.Equal(t, http.MethodConnect, headerFields[":method"])
 	require.Equal(t, "/", headerFields[":path"])
diff --git a/http3/response_writer_test.go b/http3/response_writer_test.go
index 94437c77..91b09034 100644
--- a/http3/response_writer_test.go
+++ b/http3/response_writer_test.go
@@ -4,7 +4,6 @@ import (
 	"bytes"
 	"io"
 	"net/http"
-	"time"
 
 	"github.com/quic-go/qpack"
 	mockquic "github.com/quic-go/quic-go/internal/mocks/quic"
@@ -165,11 +164,6 @@ var _ = Describe("Response Writer", func() {
 		Expect(fields).To(HaveKeyWithValue("content-type", []string{"text/html; charset=utf-8"}))
 	})
 
-	It(`is compatible with "net/http".ResponseController`, func() {
-		Expect(rw.SetReadDeadline(time.Now().Add(1 * time.Second))).To(BeNil())
-		Expect(rw.SetWriteDeadline(time.Now().Add(1 * time.Second))).To(BeNil())
-	})
-
 	It(`checks Content-Length header`, func() {
 		rw.Header().Set("Content-Length", "6")
 		n, err := rw.Write([]byte("foobar"))