diff --git a/http3/datagram_test.go b/http3/datagram_test.go index 647d3dcd7..1a79599af 100644 --- a/http3/datagram_test.go +++ b/http3/datagram_test.go @@ -2,75 +2,105 @@ package http3 import ( "context" - "errors" + "net" + "testing" "time" - . "github.com/onsi/ginkgo/v2" - . "github.com/onsi/gomega" + "github.com/stretchr/testify/assert" + "github.com/stretchr/testify/require" ) -var _ = Describe("Datagrams", func() { - It("receives a datagram", func() { - dg := newDatagrammer(nil) - dg.enqueue([]byte("foobar")) +func TestDatagramReceiving(t *testing.T) { + dg := newDatagrammer(nil) + + type result struct { + data []byte + err error + } + + // Receive blocks until a datagram is received + resultChan := make(chan result) + go func() { + defer close(resultChan) data, err := dg.Receive(context.Background()) - Expect(err).ToNot(HaveOccurred()) - Expect(data).To(Equal([]byte("foobar"))) - }) + resultChan <- result{data: data, err: err} + }() - It("queues up to 32 datagrams", func() { - dg := newDatagrammer(nil) - for i := 0; i < streamDatagramQueueLen+1; i++ { - dg.enqueue([]byte{uint8(i)}) - } - for i := 0; i < streamDatagramQueueLen; i++ { - data, err := dg.Receive(context.Background()) - Expect(err).ToNot(HaveOccurred()) - Expect(data[0]).To(BeEquivalentTo(i)) - } - ctx, cancel := context.WithCancel(context.Background()) - cancel() - _, err := dg.Receive(ctx) - Expect(err).To(MatchError(context.Canceled)) - }) + select { + case <-time.After(scaleDuration(10 * time.Millisecond)): + case <-resultChan: + t.Fatal("should not have received a datagram") + } + dg.enqueue([]byte("foobar")) - It("blocks until a new datagram is received", func() { - dg := newDatagrammer(nil) - done := make(chan struct{}) - go func() { - defer GinkgoRecover() - defer close(done) - data, err := dg.Receive(context.Background()) - Expect(err).ToNot(HaveOccurred()) - Expect(data).To(Equal([]byte("foobar"))) - }() + select { + case res := <-resultChan: + require.NoError(t, res.err) + require.Equal(t, []byte("foobar"), res.data) + case <-time.After(time.Second): + t.Fatal("should have received a datagram") + } - Consistently(done, 50*time.Millisecond).ShouldNot(BeClosed()) - dg.enqueue([]byte("foobar")) - Eventually(done).Should(BeClosed()) - }) - - It("drops datagrams when the stream's receive side is closed", func() { - dg := newDatagrammer(nil) - dg.enqueue([]byte("foo")) - testErr := errors.New("test error") - dg.SetReceiveError(testErr) - dg.enqueue([]byte("bar")) + // up to 32 datagrams can be queued + for i := range streamDatagramQueueLen + 1 { + dg.enqueue([]byte{uint8(i)}) + } + for i := range streamDatagramQueueLen { data, err := dg.Receive(context.Background()) - Expect(err).ToNot(HaveOccurred()) - Expect(data).To(Equal([]byte("foo"))) - _, err = dg.Receive(context.Background()) - Expect(err).To(MatchError(testErr)) - }) + require.NoError(t, err) + require.Equal(t, []byte{uint8(i)}, data) + } - It("sends datagrams", func() { - var sent []byte - testErr := errors.New("test error") - dg := newDatagrammer(func(b []byte) error { - sent = b - return testErr - }) - Expect(dg.Send([]byte("foobar"))).To(MatchError(testErr)) - Expect(sent).To(Equal([]byte("foobar"))) + // Receive respects the context + ctx, cancel := context.WithCancel(context.Background()) + cancel() + _, err := dg.Receive(ctx) + require.ErrorIs(t, err, context.Canceled) +} + +func TestDatagramReceiveError(t *testing.T) { + dg := newDatagrammer(nil) + + errChan := make(chan error) + go func() { + _, err := dg.Receive(context.Background()) + errChan <- err + }() + + select { + case <-time.After(scaleDuration(10 * time.Millisecond)): + case err := <-errChan: + t.Fatalf("should not have received an error: %v", err) + } + + dg.SetReceiveError(assert.AnError) + select { + case err := <-errChan: + require.ErrorIs(t, err, assert.AnError) + case <-time.After(time.Second): + t.Fatal("timeout") + } + + ctx, cancel := context.WithTimeout(context.Background(), time.Second) + defer cancel() + _, err := dg.Receive(ctx) + require.ErrorIs(t, err, assert.AnError) +} + +func TestDatagramSending(t *testing.T) { + var sendQueue [][]byte + errors := []error{nil, nil, assert.AnError} + dg := newDatagrammer(func(b []byte) error { + sendQueue = append(sendQueue, b) + err := errors[0] + errors = errors[1:] + return err }) -}) + require.NoError(t, dg.Send([]byte("foo"))) + require.NoError(t, dg.Send([]byte("bar"))) + require.ErrorIs(t, dg.Send([]byte("baz")), assert.AnError) + require.Equal(t, [][]byte{[]byte("foo"), []byte("bar"), []byte("baz")}, sendQueue) + + dg.SetSendError(net.ErrClosed) + require.ErrorIs(t, dg.Send([]byte("foobar")), net.ErrClosed) +}