blob: 9caa47c3d52d57b39bffdeafaaa8bf1d4f6940fd [file] [edit]
// Copyright 2026 The Go Authors. All rights reserved.
// Use of this source code is governed by a BSD-style
// license that can be found in the LICENSE file.
package httpcommon
import (
"bytes"
"compress/gzip"
"fmt"
"io"
"io/fs"
"strings"
"testing"
"testing/synctest"
)
// Tests that GzipReader doesn't crash on a second Read call following
// the first Read call's gzip.NewReader returning an error.
func TestGzipReaderDoubleReadCrash(t *testing.T) {
gz := &GzipReader{Body: io.NopCloser(strings.NewReader("0123456789"))}
var buf [1]byte
n, err1 := gz.Read(buf[:])
if n != 0 || !strings.Contains(fmt.Sprint(err1), "invalid header") {
t.Fatalf("Read = %v, %v; want 0, invalid header", n, err1)
}
n, err2 := gz.Read(buf[:])
if n != 0 || err2 != err1 {
t.Fatalf("second Read = %v, %v; want 0, %v", n, err2, err1)
}
}
func TestGzipReaderReadAfterClose(t *testing.T) {
var body bytes.Buffer
w := gzip.NewWriter(&body)
w.Write([]byte("012345679"))
w.Close()
gz := &GzipReader{Body: io.NopCloser(&body)}
var buf [1]byte
n, err := gz.Read(buf[:])
if n != 1 || err != nil {
t.Fatalf("first Read = %v, %v; want 1, nil", n, err)
}
if err := gz.Close(); err != nil {
t.Fatalf("gz Close error: %v", err)
}
n, err = gz.Read(buf[:])
if n != 0 || err != fs.ErrClosed {
t.Fatalf("Read after close = %v, %v; want 0, fs.ErrClosed", n, err)
}
}
// blockingReadCloser blocks in Read until it is closed.
type blockingReadCloser struct {
closed chan struct{}
}
func (r *blockingReadCloser) Read([]byte) (int, error) {
<-r.closed
return 0, fs.ErrClosed
}
func (r *blockingReadCloser) Close() error {
close(r.closed)
return nil
}
// Tests that closing a GzipReader unblocks a Read that is waiting for the
// gzip header, rather than deadlocking on the GzipReader's mutex.
func TestGzipReaderConcurrentCloseAndRead(t *testing.T) {
synctest.Test(t, func(t *testing.T) {
gz := &GzipReader{Body: &blockingReadCloser{closed: make(chan struct{})}}
readErr := make(chan error, 1)
go func() {
var buf [1]byte
_, err := gz.Read(buf[:])
readErr <- err
}()
synctest.Wait()
if err := gz.Close(); err != nil {
t.Fatalf("Close = %v, want nil", err)
}
synctest.Wait()
select {
case err := <-readErr:
if err == nil {
t.Error("Read returned nil error, want error")
}
default:
t.Fatal("Read did not unblock on Close")
}
})
}