diff --git a/pkg/btree/bptree_test.go b/pkg/btree/bptree_test.go index 34772c8a..0af1dd4f 100644 --- a/pkg/btree/bptree_test.go +++ b/pkg/btree/bptree_test.go @@ -4,6 +4,8 @@ import ( "encoding/binary" "math/rand" "testing" + + "github.com/kevmo314/appendable/pkg/buftest" ) type testMetaPage struct { @@ -21,7 +23,7 @@ func (m *testMetaPage) Root() (MemoryPointer, error) { func TestBPTree(t *testing.T) { t.Run("empty tree", func(t *testing.T) { - b := newSeekableBuffer() + b := buftest.NewSeekableBuffer() p, err := NewPageFile(b) if err != nil { t.Fatal(err) @@ -38,7 +40,7 @@ func TestBPTree(t *testing.T) { }) t.Run("insert creates a root", func(t *testing.T) { - b := newSeekableBuffer() + b := buftest.NewSeekableBuffer() p, err := NewPageFile(b) if err != nil { t.Fatal(err) @@ -60,7 +62,7 @@ func TestBPTree(t *testing.T) { }) t.Run("insert into root", func(t *testing.T) { - b := newSeekableBuffer() + b := buftest.NewSeekableBuffer() p, err := NewPageFile(b) if err != nil { t.Fatal(err) @@ -95,7 +97,7 @@ func TestBPTree(t *testing.T) { }) t.Run("split root", func(t *testing.T) { - b := newSeekableBuffer() + b := buftest.NewSeekableBuffer() p, err := NewPageFile(b) if err != nil { t.Fatal(err) @@ -156,7 +158,7 @@ func TestBPTree(t *testing.T) { }) t.Run("split intermediate", func(t *testing.T) { - b := newSeekableBuffer() + b := buftest.NewSeekableBuffer() p, err := NewPageFile(b) if err != nil { t.Fatal(err) @@ -180,7 +182,7 @@ func TestBPTree(t *testing.T) { }) t.Run("insertion test", func(t *testing.T) { - b := newSeekableBuffer() + b := buftest.NewSeekableBuffer() p, err := NewPageFile(b) if err != nil { t.Fatal(err) @@ -210,7 +212,7 @@ func TestBPTree(t *testing.T) { }) t.Run("random insertion test", func(t *testing.T) { - b := newSeekableBuffer() + b := buftest.NewSeekableBuffer() p, err := NewPageFile(b) if err != nil { t.Fatal(err) @@ -246,7 +248,7 @@ func TestBPTree(t *testing.T) { }) // t.Run("bulk insert", func(t *testing.T) { - // b := newSeekableBuffer() + // b := buftest.NewSeekableBuffer() // tree :=NewBPTree(b, 2) // if err != nil { // t.Fatal(err) diff --git a/pkg/btree/buffer.go b/pkg/btree/buffer.go deleted file mode 100644 index 0fb80a9c..00000000 --- a/pkg/btree/buffer.go +++ /dev/null @@ -1,64 +0,0 @@ -package btree - -import "io" - -// seekableBuffer is a buffer that can be seeked into. -// this replicates the behavior of a file on disk without having to write to disk -// which is useful for testing. -type seekableBuffer struct { - buf []byte - pos int -} - -func newSeekableBuffer() *seekableBuffer { - return &seekableBuffer{} -} - -func (b *seekableBuffer) Write(p []byte) (int, error) { - n := copy(b.buf[b.pos:], p) - if n < len(p) { - b.buf = append(b.buf, p[n:]...) - } - b.pos += len(p) - return len(p), nil -} - -func (b *seekableBuffer) Seek(offset int64, whence int) (int64, error) { - switch whence { - case io.SeekStart: - b.pos = int(offset) - case io.SeekCurrent: - b.pos += int(offset) - case io.SeekEnd: - b.pos = len(b.buf) + int(offset) - } - if b.pos < 0 { - b.pos = 0 - } - if b.pos > len(b.buf) { - b.pos = len(b.buf) - } - return int64(b.pos), nil -} - -func (b *seekableBuffer) Read(p []byte) (int, error) { - if b.pos >= len(b.buf) { - return 0, io.EOF - } - n := copy(p, b.buf[b.pos:]) - b.pos += n - return n, nil -} - -func (b *seekableBuffer) Truncate(size int64) error { - if size < 0 { - return io.ErrShortBuffer - } - if size > int64(len(b.buf)) { - return io.ErrShortWrite - } - b.buf = b.buf[:size] - return nil -} - -var _ io.ReadWriteSeeker = &seekableBuffer{} diff --git a/pkg/btree/multi_test.go b/pkg/btree/multi_test.go index e656507e..f91cea78 100644 --- a/pkg/btree/multi_test.go +++ b/pkg/btree/multi_test.go @@ -3,11 +3,13 @@ package btree import ( "reflect" "testing" + + "github.com/kevmo314/appendable/pkg/buftest" ) func TestMultiBPTree(t *testing.T) { t.Run("empty tree", func(t *testing.T) { - b := newSeekableBuffer() + b := buftest.NewSeekableBuffer() p, err := NewPageFile(b) if err != nil { t.Fatal(err) @@ -23,7 +25,7 @@ func TestMultiBPTree(t *testing.T) { }) t.Run("reset tree", func(t *testing.T) { - b := newSeekableBuffer() + b := buftest.NewSeekableBuffer() p, err := NewPageFile(b) if err != nil { t.Fatal(err) @@ -46,7 +48,7 @@ func TestMultiBPTree(t *testing.T) { }) t.Run("insert a second page", func(t *testing.T) { - b := newSeekableBuffer() + b := buftest.NewSeekableBuffer() p, err := NewPageFile(b) if err != nil { t.Fatal(err) @@ -85,7 +87,7 @@ func TestMultiBPTree(t *testing.T) { }) t.Run("duplicate next pointer", func(t *testing.T) { - b := newSeekableBuffer() + b := buftest.NewSeekableBuffer() p, err := NewPageFile(b) if err != nil { t.Fatal(err) @@ -108,7 +110,7 @@ func TestMultiBPTree(t *testing.T) { }) t.Run("starts with empty metadata", func(t *testing.T) { - b := newSeekableBuffer() + b := buftest.NewSeekableBuffer() p, err := NewPageFile(b) if err != nil { t.Fatal(err) @@ -127,7 +129,7 @@ func TestMultiBPTree(t *testing.T) { }) t.Run("storing metadata works", func(t *testing.T) { - b := newSeekableBuffer() + b := buftest.NewSeekableBuffer() p, err := NewPageFile(b) if err != nil { t.Fatal(err) diff --git a/pkg/btree/pagefile_test.go b/pkg/btree/pagefile_test.go index 3a6cd78c..7d99704c 100644 --- a/pkg/btree/pagefile_test.go +++ b/pkg/btree/pagefile_test.go @@ -3,11 +3,13 @@ package btree import ( "io" "testing" + + "github.com/kevmo314/appendable/pkg/buftest" ) func TestPageFile(t *testing.T) { t.Run("allocates first page", func(t *testing.T) { - buf := newSeekableBuffer() + buf := buftest.NewSeekableBuffer() pf, err := NewPageFile(buf) if err != nil { t.Fatal(err) @@ -22,7 +24,7 @@ func TestPageFile(t *testing.T) { }) t.Run("page size reuses page without allocation", func(t *testing.T) { - buf := newSeekableBuffer() + buf := buftest.NewSeekableBuffer() pf, err := NewPageFile(buf) if err != nil { t.Fatal(err) @@ -45,7 +47,7 @@ func TestPageFile(t *testing.T) { }) t.Run("page size allocates second page", func(t *testing.T) { - buf := newSeekableBuffer() + buf := buftest.NewSeekableBuffer() pf, err := NewPageFile(buf) if err != nil { t.Fatal(err) @@ -71,7 +73,7 @@ func TestPageFile(t *testing.T) { }) t.Run("new page seeks to page", func(t *testing.T) { - buf := newSeekableBuffer() + buf := buftest.NewSeekableBuffer() pf, err := NewPageFile(buf) if err != nil { t.Fatal(err) @@ -90,7 +92,7 @@ func TestPageFile(t *testing.T) { }) t.Run("free page reuses page", func(t *testing.T) { - buf := newSeekableBuffer() + buf := buftest.NewSeekableBuffer() pf, err := NewPageFile(buf) if err != nil { t.Fatal(err) diff --git a/pkg/buftest/buffer.go b/pkg/buftest/buffer.go new file mode 100644 index 00000000..ed5515f2 --- /dev/null +++ b/pkg/buftest/buffer.go @@ -0,0 +1,91 @@ +package buftest + +import "io" + +// SeekableBuffer is a buffer that can be seeked into. +// this replicates the behavior of a file on disk without having to write to disk +// which is useful for testing. +type SeekableBuffer struct { + buf []byte + pos int +} + +func NewSeekableBuffer() *SeekableBuffer { + return &SeekableBuffer{} +} + +func (b *SeekableBuffer) Write(p []byte) (int, error) { + n := copy(b.buf[b.pos:], p) + if n < len(p) { + b.buf = append(b.buf, p[n:]...) + } + b.pos += len(p) + return len(p), nil +} + +func (b *SeekableBuffer) Seek(offset int64, whence int) (int64, error) { + switch whence { + case io.SeekStart: + b.pos = int(offset) + case io.SeekCurrent: + b.pos += int(offset) + case io.SeekEnd: + b.pos = len(b.buf) + int(offset) + } + if b.pos < 0 { + b.pos = 0 + } + if b.pos > len(b.buf) { + b.pos = len(b.buf) + } + return int64(b.pos), nil +} + +func (b *SeekableBuffer) Read(p []byte) (int, error) { + if b.pos >= len(b.buf) { + return 0, io.EOF + } + n := copy(p, b.buf[b.pos:]) + b.pos += n + return n, nil +} + +func (b *SeekableBuffer) Truncate(size int64) error { + if size < 0 { + return io.ErrShortBuffer + } + if size > int64(len(b.buf)) { + return io.ErrShortWrite + } + b.buf = b.buf[:size] + return nil +} + +func (b *SeekableBuffer) WriteAt(p []byte, off int64) (int, error) { + if off < 0 { + return 0, io.ErrShortBuffer + } + if off > int64(len(b.buf)) { + return 0, io.ErrShortWrite + } + n := copy(b.buf[off:], p) + if n < len(p) { + b.buf = append(b.buf, p[n:]...) + } + return len(p), nil +} + +func (b *SeekableBuffer) ReadAt(p []byte, off int64) (int, error) { + if off < 0 { + return 0, io.ErrShortBuffer + } + if off > int64(len(b.buf)) { + return 0, io.EOF + } + n := copy(p, b.buf[off:]) + return n, nil +} + +var _ io.ReadWriteSeeker = &SeekableBuffer{} +var _ io.ReaderAt = &SeekableBuffer{} +var _ io.WriterAt = &SeekableBuffer{} diff --git a/pkg/btree/buffer_test.go b/pkg/buftest/buffer_test.go similarity index 92% rename from pkg/btree/buffer_test.go rename to pkg/buftest/buffer_test.go index 7552c22b..0867ebf9 100644 --- a/pkg/btree/buffer_test.go +++ b/pkg/buftest/buffer_test.go @@ -1,4 +1,4 @@ -package btree +package buftest import ( "io" @@ -7,7 +7,7 @@ import ( func TestSeekableBuffer(t *testing.T) { t.Run("Write", func(t *testing.T) { - b := newSeekableBuffer() + b := NewSeekableBuffer() n, err := b.Write([]byte("hello")) if err != nil { t.Fatal(err) @@ -21,7 +21,7 @@ func TestSeekableBuffer(t *testing.T) { }) t.Run("write to end", func(t *testing.T) { - b := newSeekableBuffer() + b := NewSeekableBuffer() if _, err := b.Write([]byte("hello")); err != nil { t.Fatal(err) } @@ -37,7 +37,7 @@ func TestSeekableBuffer(t *testing.T) { }) t.Run("Seek", func(t *testing.T) { - b := newSeekableBuffer() + b := NewSeekableBuffer() if _, err := b.Write([]byte("helloo")); err != nil { t.Fatal(err) } @@ -53,7 +53,7 @@ func TestSeekableBuffer(t *testing.T) { }) t.Run("Read", func(t *testing.T) { - b := newSeekableBuffer() + b := NewSeekableBuffer() if _, err := b.Write([]byte("hello")); err != nil { t.Fatal(err) } @@ -74,7 +74,7 @@ func TestSeekableBuffer(t *testing.T) { }) t.Run("read from middle", func(t *testing.T) { - b := newSeekableBuffer() + b := NewSeekableBuffer() if _, err := b.Write([]byte("hello")); err != nil { t.Fatal(err) } @@ -95,7 +95,7 @@ func TestSeekableBuffer(t *testing.T) { }) t.Run("truncate", func(t *testing.T) { - b := newSeekableBuffer() + b := NewSeekableBuffer() if _, err := b.Write([]byte("hello")); err != nil { t.Fatal(err) }