diff options
Diffstat (limited to 'src/internal/coverage/slicewriter')
-rw-r--r-- | src/internal/coverage/slicewriter/slicewriter.go | 80 | ||||
-rw-r--r-- | src/internal/coverage/slicewriter/slw_test.go | 134 |
2 files changed, 214 insertions, 0 deletions
diff --git a/src/internal/coverage/slicewriter/slicewriter.go b/src/internal/coverage/slicewriter/slicewriter.go new file mode 100644 index 0000000..460e9dc --- /dev/null +++ b/src/internal/coverage/slicewriter/slicewriter.go @@ -0,0 +1,80 @@ +// Copyright 2022 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 slicewriter + +import ( + "fmt" + "io" +) + +// WriteSeeker is a helper object that implements the io.WriteSeeker +// interface. Clients can create a WriteSeeker, make a series of Write +// calls to add data to it (and possibly Seek calls to update +// previously written portions), then finally invoke BytesWritten() to +// get a pointer to the constructed byte slice. +type WriteSeeker struct { + payload []byte + off int64 +} + +func (sws *WriteSeeker) Write(p []byte) (n int, err error) { + amt := len(p) + towrite := sws.payload[sws.off:] + if len(towrite) < amt { + sws.payload = append(sws.payload, make([]byte, amt-len(towrite))...) + towrite = sws.payload[sws.off:] + } + copy(towrite, p) + sws.off += int64(amt) + return amt, nil +} + +// Seek repositions the read/write position of the WriteSeeker within +// its internally maintained slice. Note that it is not possible to +// expand the size of the slice using SEEK_SET; trying to seek outside +// the slice will result in an error. +func (sws *WriteSeeker) Seek(offset int64, whence int) (int64, error) { + switch whence { + case io.SeekStart: + if sws.off != offset && (offset < 0 || offset > int64(len(sws.payload))) { + return 0, fmt.Errorf("invalid seek: new offset %d (out of range [0 %d]", offset, len(sws.payload)) + } + sws.off = offset + return offset, nil + case io.SeekCurrent: + newoff := sws.off + offset + if newoff != sws.off && (newoff < 0 || newoff > int64(len(sws.payload))) { + return 0, fmt.Errorf("invalid seek: new offset %d (out of range [0 %d]", newoff, len(sws.payload)) + } + sws.off += offset + return sws.off, nil + case io.SeekEnd: + newoff := int64(len(sws.payload)) + offset + if newoff != sws.off && (newoff < 0 || newoff > int64(len(sws.payload))) { + return 0, fmt.Errorf("invalid seek: new offset %d (out of range [0 %d]", newoff, len(sws.payload)) + } + sws.off = newoff + return sws.off, nil + } + // other modes not supported + return 0, fmt.Errorf("unsupported seek mode %d", whence) +} + +// BytesWritten returns the underlying byte slice for the WriteSeeker, +// containing the data written to it via Write/Seek calls. +func (sws *WriteSeeker) BytesWritten() []byte { + return sws.payload +} + +func (sws *WriteSeeker) Read(p []byte) (n int, err error) { + amt := len(p) + toread := sws.payload[sws.off:] + if len(toread) < amt { + amt = len(toread) + } + copy(p, toread) + sws.off += int64(amt) + return amt, nil +} diff --git a/src/internal/coverage/slicewriter/slw_test.go b/src/internal/coverage/slicewriter/slw_test.go new file mode 100644 index 0000000..9e26767 --- /dev/null +++ b/src/internal/coverage/slicewriter/slw_test.go @@ -0,0 +1,134 @@ +// Copyright 2022 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 slicewriter + +import ( + "io" + "testing" +) + +func TestSliceWriter(t *testing.T) { + + sleq := func(t *testing.T, got []byte, want []byte) { + t.Helper() + if len(got) != len(want) { + t.Fatalf("bad length got %d want %d", len(got), len(want)) + } + for i := range got { + if got[i] != want[i] { + t.Fatalf("bad read at %d got %d want %d", i, got[i], want[i]) + } + } + } + + wf := func(t *testing.T, ws *WriteSeeker, p []byte) { + t.Helper() + nw, werr := ws.Write(p) + if werr != nil { + t.Fatalf("unexpected write error: %v", werr) + } + if nw != len(p) { + t.Fatalf("wrong amount written want %d got %d", len(p), nw) + } + } + + rf := func(t *testing.T, ws *WriteSeeker, p []byte) { + t.Helper() + b := make([]byte, len(p)) + nr, rerr := ws.Read(b) + if rerr != nil { + t.Fatalf("unexpected read error: %v", rerr) + } + if nr != len(p) { + t.Fatalf("wrong amount read want %d got %d", len(p), nr) + } + sleq(t, b, p) + } + + sk := func(t *testing.T, ws *WriteSeeker, offset int64, whence int) int64 { + t.Helper() + off, err := ws.Seek(offset, whence) + if err != nil { + t.Fatalf("unexpected seek error: %v", err) + } + return off + } + + wp1 := []byte{1, 2} + ws := &WriteSeeker{} + + // write some stuff + wf(t, ws, wp1) + // check that BytesWritten returns what we wrote. + sleq(t, ws.BytesWritten(), wp1) + // offset is at end of slice, so reading should return zero bytes. + rf(t, ws, []byte{}) + + // write some more stuff + wp2 := []byte{7, 8, 9} + wf(t, ws, wp2) + // check that BytesWritten returns what we expect. + wpex := []byte{1, 2, 7, 8, 9} + sleq(t, ws.BytesWritten(), wpex) + rf(t, ws, []byte{}) + + // seeks and reads. + sk(t, ws, 1, io.SeekStart) + rf(t, ws, []byte{2, 7}) + sk(t, ws, -2, io.SeekCurrent) + rf(t, ws, []byte{2, 7}) + sk(t, ws, -4, io.SeekEnd) + rf(t, ws, []byte{2, 7}) + off := sk(t, ws, 0, io.SeekEnd) + sk(t, ws, off, io.SeekStart) + + // seek back and overwrite + sk(t, ws, 1, io.SeekStart) + wf(t, ws, []byte{9, 11}) + wpex = []byte{1, 9, 11, 8, 9} + sleq(t, ws.BytesWritten(), wpex) + + // seeks on empty writer. + ws2 := &WriteSeeker{} + sk(t, ws2, 0, io.SeekStart) + sk(t, ws2, 0, io.SeekCurrent) + sk(t, ws2, 0, io.SeekEnd) + + // check for seek errors. + _, err := ws.Seek(-1, io.SeekStart) + if err == nil { + t.Fatalf("expected error on invalid -1 seek") + } + _, err = ws.Seek(int64(len(ws.BytesWritten())+1), io.SeekStart) + if err == nil { + t.Fatalf("expected error on invalid %d seek", len(ws.BytesWritten())) + } + + ws.Seek(0, io.SeekStart) + _, err = ws.Seek(-1, io.SeekCurrent) + if err == nil { + t.Fatalf("expected error on invalid -1 seek") + } + _, err = ws.Seek(int64(len(ws.BytesWritten())+1), io.SeekCurrent) + if err == nil { + t.Fatalf("expected error on invalid %d seek", len(ws.BytesWritten())) + } + + _, err = ws.Seek(1, io.SeekEnd) + if err == nil { + t.Fatalf("expected error on invalid 1 seek") + } + bsamt := int64(-1*len(ws.BytesWritten()) - 1) + _, err = ws.Seek(bsamt, io.SeekEnd) + if err == nil { + t.Fatalf("expected error on invalid %d seek", bsamt) + } + + // bad seek mode + _, err = ws.Seek(-1, io.SeekStart+9) + if err == nil { + t.Fatalf("expected error on invalid seek mode") + } +} |