mirror of
https://github.com/golang/go
synced 2024-11-02 11:50:30 +00:00
io: add OffsetWriter, NewOffsetWriter
Offsetwriter refers to the design of SectionReader and removes the section parameter n. Since the size of the written data is determined by the user, we cannot know where the end offset of the original data is. The offset of SeekEnd is not valid in Seek method. Fixes #45899. Change-Id: I9d9445aecfa0dd4fc5168f2f65e1e3055c201b45 Reviewed-on: https://go-review.googlesource.com/c/go/+/406776 Run-TryBot: Ian Lance Taylor <iant@google.com> Reviewed-by: Joedian Reid <joedian@golang.org> Reviewed-by: Ian Lance Taylor <iant@google.com> TryBot-Result: Gopher Robot <gobot@golang.org> Run-TryBot: Ian Lance Taylor <iant@golang.org> Auto-Submit: Ian Lance Taylor <iant@google.com>
This commit is contained in:
parent
7dad1d24b2
commit
dc8e2a6a8e
4 changed files with 224 additions and 0 deletions
5
api/next/45899.txt
Normal file
5
api/next/45899.txt
Normal file
|
@ -0,0 +1,5 @@
|
|||
pkg io, type OffsetWriter struct #45899
|
||||
pkg io, func NewOffsetWriter(WriterAt, int64) *OffsetWriter #45899
|
||||
pkg io, method (*OffsetWriter) Write([]uint8) (int, error) #45899
|
||||
pkg io, method (*OffsetWriter) WriteAt([]uint8, int64) (int, error) #45899
|
||||
pkg io, method (*OffsetWriter) Seek(int64, int) (int64, error) #45899
|
|
@ -6,3 +6,5 @@ package io
|
|||
|
||||
// exported for test
|
||||
var ErrInvalidWrite = errInvalidWrite
|
||||
var ErrWhence = errWhence
|
||||
var ErrOffset = errOffset
|
||||
|
|
40
src/io/io.go
40
src/io/io.go
|
@ -555,6 +555,46 @@ func (s *SectionReader) ReadAt(p []byte, off int64) (n int, err error) {
|
|||
// Size returns the size of the section in bytes.
|
||||
func (s *SectionReader) Size() int64 { return s.limit - s.base }
|
||||
|
||||
// An OffsetWriter maps writes at offset base to offset base+off in the underlying writer.
|
||||
type OffsetWriter struct {
|
||||
w WriterAt
|
||||
base int64 // the original offset
|
||||
off int64 // the current offset
|
||||
}
|
||||
|
||||
// NewOffsetWriter returns an OffsetWriter that writes to w
|
||||
// starting at offset off.
|
||||
func NewOffsetWriter(w WriterAt, off int64) *OffsetWriter {
|
||||
return &OffsetWriter{w, off, off}
|
||||
}
|
||||
|
||||
func (o *OffsetWriter) Write(p []byte) (n int, err error) {
|
||||
n, err = o.w.WriteAt(p, o.off)
|
||||
o.off += int64(n)
|
||||
return
|
||||
}
|
||||
|
||||
func (o *OffsetWriter) WriteAt(p []byte, off int64) (n int, err error) {
|
||||
off += o.base
|
||||
return o.w.WriteAt(p, off)
|
||||
}
|
||||
|
||||
func (o *OffsetWriter) Seek(offset int64, whence int) (int64, error) {
|
||||
switch whence {
|
||||
default:
|
||||
return 0, errWhence
|
||||
case SeekStart:
|
||||
offset += o.base
|
||||
case SeekCurrent:
|
||||
offset += o.off
|
||||
}
|
||||
if offset < o.base {
|
||||
return 0, errOffset
|
||||
}
|
||||
o.off = offset
|
||||
return offset - o.base, nil
|
||||
}
|
||||
|
||||
// TeeReader returns a Reader that writes to w what it reads from r.
|
||||
// All reads from r performed through it are matched with
|
||||
// corresponding writes to w. There is no internal buffering -
|
||||
|
|
|
@ -9,7 +9,10 @@ import (
|
|||
"errors"
|
||||
"fmt"
|
||||
. "io"
|
||||
"os"
|
||||
"strings"
|
||||
"sync"
|
||||
"sync/atomic"
|
||||
"testing"
|
||||
)
|
||||
|
||||
|
@ -492,3 +495,177 @@ func TestNopCloserWriterToForwarding(t *testing.T) {
|
|||
}
|
||||
}
|
||||
}
|
||||
|
||||
func TestOffsetWriter_Seek(t *testing.T) {
|
||||
tmpfilename := "TestOffsetWriter_Seek"
|
||||
tmpfile, err := os.CreateTemp(t.TempDir(), tmpfilename)
|
||||
if err != nil || tmpfile == nil {
|
||||
t.Fatalf("CreateTemp(%s) failed: %v", tmpfilename, err)
|
||||
}
|
||||
defer tmpfile.Close()
|
||||
w := NewOffsetWriter(tmpfile, 0)
|
||||
|
||||
// Should throw error errWhence if whence is not valid
|
||||
t.Run("errWhence", func(t *testing.T) {
|
||||
for _, whence := range []int{-3, -2, -1, 3, 4, 5} {
|
||||
var offset int64 = 0
|
||||
gotOff, gotErr := w.Seek(offset, whence)
|
||||
if gotOff != 0 || gotErr != ErrWhence {
|
||||
t.Errorf("For whence %d, offset %d, OffsetWriter.Seek got: (%d, %v), want: (%d, %v)",
|
||||
whence, offset, gotOff, gotErr, 0, ErrWhence)
|
||||
}
|
||||
}
|
||||
})
|
||||
|
||||
// Should throw error errOffset if offset is negative
|
||||
t.Run("errOffset", func(t *testing.T) {
|
||||
for _, whence := range []int{SeekStart, SeekCurrent} {
|
||||
for offset := int64(-3); offset < 0; offset++ {
|
||||
gotOff, gotErr := w.Seek(offset, whence)
|
||||
if gotOff != 0 || gotErr != ErrOffset {
|
||||
t.Errorf("For whence %d, offset %d, OffsetWriter.Seek got: (%d, %v), want: (%d, %v)",
|
||||
whence, offset, gotOff, gotErr, 0, ErrOffset)
|
||||
}
|
||||
}
|
||||
}
|
||||
})
|
||||
|
||||
// Normal tests
|
||||
t.Run("normal", func(t *testing.T) {
|
||||
tests := []struct {
|
||||
offset int64
|
||||
whence int
|
||||
returnOff int64
|
||||
}{
|
||||
// keep in order
|
||||
{whence: SeekStart, offset: 1, returnOff: 1},
|
||||
{whence: SeekStart, offset: 2, returnOff: 2},
|
||||
{whence: SeekStart, offset: 3, returnOff: 3},
|
||||
{whence: SeekCurrent, offset: 1, returnOff: 4},
|
||||
{whence: SeekCurrent, offset: 2, returnOff: 6},
|
||||
{whence: SeekCurrent, offset: 3, returnOff: 9},
|
||||
}
|
||||
for idx, tt := range tests {
|
||||
gotOff, gotErr := w.Seek(tt.offset, tt.whence)
|
||||
if gotOff != tt.returnOff || gotErr != nil {
|
||||
t.Errorf("%d:: For whence %d, offset %d, OffsetWriter.Seek got: (%d, %v), want: (%d, <nil>)",
|
||||
idx+1, tt.whence, tt.offset, gotOff, gotErr, tt.returnOff)
|
||||
}
|
||||
}
|
||||
})
|
||||
}
|
||||
|
||||
func TestOffsetWriter_WriteAt(t *testing.T) {
|
||||
const content = "0123456789ABCDEF"
|
||||
contentSize := int64(len(content))
|
||||
tmpdir, err := os.MkdirTemp(t.TempDir(), "TestOffsetWriter_WriteAt")
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
|
||||
work := func(off, at int64) {
|
||||
position := fmt.Sprintf("off_%d_at_%d", off, at)
|
||||
tmpfile, err := os.CreateTemp(tmpdir, position)
|
||||
if err != nil || tmpfile == nil {
|
||||
t.Fatalf("CreateTemp(%s) failed: %v", position, err)
|
||||
}
|
||||
defer tmpfile.Close()
|
||||
|
||||
var writeN int64
|
||||
var wg sync.WaitGroup
|
||||
// Concurrent writes, one byte at a time
|
||||
for step, value := range []byte(content) {
|
||||
wg.Add(1)
|
||||
go func(wg *sync.WaitGroup, tmpfile *os.File, value byte, off, at int64, step int) {
|
||||
defer wg.Done()
|
||||
|
||||
w := NewOffsetWriter(tmpfile, off)
|
||||
n, e := w.WriteAt([]byte{value}, at+int64(step))
|
||||
if e != nil {
|
||||
t.Errorf("WriteAt failed. off: %d, at: %d, step: %d\n error: %v", off, at, step, e)
|
||||
}
|
||||
atomic.AddInt64(&writeN, int64(n))
|
||||
}(&wg, tmpfile, value, off, at, step)
|
||||
}
|
||||
wg.Wait()
|
||||
|
||||
// Read one more byte to reach EOF
|
||||
buf := make([]byte, contentSize+1)
|
||||
readN, err := tmpfile.ReadAt(buf, off+at)
|
||||
if err != EOF {
|
||||
t.Fatalf("ReadAt failed: %v", err)
|
||||
}
|
||||
readContent := string(buf[:contentSize])
|
||||
if writeN != int64(readN) || writeN != contentSize || readContent != content {
|
||||
t.Fatalf("%s:: WriteAt(%s, %d) error. \ngot n: %v, content: %s \nexpected n: %v, content: %v",
|
||||
position, content, at, readN, readContent, contentSize, content)
|
||||
}
|
||||
}
|
||||
for off := int64(0); off < 2; off++ {
|
||||
for at := int64(0); at < 2; at++ {
|
||||
work(off, at)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
func TestOffsetWriter_Write(t *testing.T) {
|
||||
const content = "0123456789ABCDEF"
|
||||
contentSize := len(content)
|
||||
tmpdir := t.TempDir()
|
||||
|
||||
makeOffsetWriter := func(name string) (*OffsetWriter, *os.File) {
|
||||
tmpfilename := "TestOffsetWriter_Write_" + name
|
||||
tmpfile, err := os.CreateTemp(tmpdir, tmpfilename)
|
||||
if err != nil || tmpfile == nil {
|
||||
t.Fatalf("CreateTemp(%s) failed: %v", tmpfilename, err)
|
||||
}
|
||||
return NewOffsetWriter(tmpfile, 0), tmpfile
|
||||
}
|
||||
checkContent := func(name string, f *os.File) {
|
||||
// Read one more byte to reach EOF
|
||||
buf := make([]byte, contentSize+1)
|
||||
readN, err := f.ReadAt(buf, 0)
|
||||
if err != EOF {
|
||||
t.Fatalf("ReadAt failed, err: %v", err)
|
||||
}
|
||||
readContent := string(buf[:contentSize])
|
||||
if readN != contentSize || readContent != content {
|
||||
t.Fatalf("%s error. \ngot n: %v, content: %s \nexpected n: %v, content: %v",
|
||||
name, readN, readContent, contentSize, content)
|
||||
}
|
||||
}
|
||||
|
||||
var name string
|
||||
name = "Write"
|
||||
t.Run(name, func(t *testing.T) {
|
||||
// Write directly (off: 0, at: 0)
|
||||
// Write content to file
|
||||
w, f := makeOffsetWriter(name)
|
||||
defer f.Close()
|
||||
for _, value := range []byte(content) {
|
||||
n, err := w.Write([]byte{value})
|
||||
if err != nil {
|
||||
t.Fatalf("Write failed, n: %d, err: %v", n, err)
|
||||
}
|
||||
}
|
||||
checkContent(name, f)
|
||||
|
||||
// Copy -> Write
|
||||
// Copy file f to file f2
|
||||
name = "Copy"
|
||||
w2, f2 := makeOffsetWriter(name)
|
||||
defer f2.Close()
|
||||
Copy(w2, f)
|
||||
checkContent(name, f2)
|
||||
})
|
||||
|
||||
// Copy -> WriteTo -> Write
|
||||
// Note: strings.Reader implements the io.WriterTo interface.
|
||||
name = "Write_Of_Copy_WriteTo"
|
||||
t.Run(name, func(t *testing.T) {
|
||||
w, f := makeOffsetWriter(name)
|
||||
defer f.Close()
|
||||
Copy(w, strings.NewReader(content))
|
||||
checkContent(name, f)
|
||||
})
|
||||
}
|
||||
|
|
Loading…
Reference in a new issue