1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 122 123 124 125 126 127 128 129 130 131 132 133 134 135 136 137 138
|
//go:build linux
package streamcache
import (
"bytes"
"io"
"math/rand"
"os"
"path/filepath"
"testing"
"testing/iotest"
"github.com/stretchr/testify/require"
"gitlab.com/gitlab-org/gitaly/v16/internal/testhelper"
)
type wrappedFile struct{ f *os.File }
func (wf *wrappedFile) Write(p []byte) (int, error) { return wf.f.Write(p) }
func (wf *wrappedFile) Close() error { return wf.f.Close() }
func (wf *wrappedFile) Name() string { return wf.f.Name() }
func TestPipe_WriteTo(t *testing.T) {
data := make([]byte, 10*1024*1024)
_, err := rand.Read(data)
require.NoError(t, err)
testCases := []struct {
desc string
create func(t *testing.T) namedWriteCloser
sendfile bool
}{
{
desc: "os.File",
create: func(t *testing.T) namedWriteCloser {
f, err := os.Create(filepath.Join(testhelper.TempDir(t), "pipe write to"))
require.NoError(t, err)
t.Cleanup(func() { require.NoError(t, os.Remove(f.Name())) })
return f
},
sendfile: true,
},
{
desc: "non-file writer",
create: func(t *testing.T) namedWriteCloser {
f, err := os.Create(filepath.Join(testhelper.TempDir(t), "pipe write to"))
require.NoError(t, err)
t.Cleanup(func() { require.NoError(t, os.Remove(f.Name())) })
return &wrappedFile{f}
},
},
}
for _, tc := range testCases {
t.Run(tc.desc, func(t *testing.T) {
pr, p := createPipe(t)
defer pr.Close()
errC := make(chan error, 1)
go func() {
errC <- func() error {
defer p.Close()
// To exercise pipe blocking logic, we want to prevent writing all of
// data at once.
r := iotest.HalfReader(bytes.NewReader(data))
if _, err := io.Copy(p, r); err != nil {
return err
}
return p.Close()
}()
}()
outW := tc.create(t)
require.NoError(t, err)
defer outW.Close()
n, err := pr.WriteTo(outW)
require.NoError(t, err)
require.Equal(t, int64(len(data)), n)
require.NoError(t, outW.Close())
require.NoError(t, <-errC)
outBytes, err := os.ReadFile(outW.Name())
require.NoError(t, err)
// Don't use require.Equal because we don't want a 10MB error message.
require.True(t, bytes.Equal(data, outBytes))
require.Equal(t, tc.sendfile, pr.sendfileCalledSuccessfully)
})
}
}
func TestPipe_WriteTo_EAGAIN(t *testing.T) {
data := make([]byte, 10*1024*1024)
_, err := rand.Read(data)
require.NoError(t, err)
pr, p := createPipe(t)
defer pr.Close()
defer p.Close()
_, err = p.Write(data)
require.NoError(t, err)
require.NoError(t, p.Close())
fr, fw, err := os.Pipe()
require.NoError(t, err)
defer fr.Close()
defer fw.Close()
errC := make(chan error, 1)
go func() {
errC <- func() error {
defer fw.Close()
// This will try to write 10MB into fw at once, which will fail because
// the pipe buffer is too small. Then sendfile will return EAGAIN. Doing
// this tests our ability to handle EAGAIN correctly.
_, err := pr.WriteTo(fw)
if err != nil {
return err
}
return fw.Close()
}()
}()
out, err := io.ReadAll(fr)
require.NoError(t, err)
// Don't use require.Equal because we don't want a 10MB error message.
require.True(t, bytes.Equal(data, out))
require.NoError(t, <-errC)
require.True(t, pr.sendfileCalledSuccessfully)
}
|