diff options
Diffstat (limited to 'internal/buffer')
-rw-r--r-- | internal/buffer/buffer.go | 95 | ||||
-rw-r--r-- | internal/buffer/buffer_test.go | 88 |
2 files changed, 183 insertions, 0 deletions
diff --git a/internal/buffer/buffer.go b/internal/buffer/buffer.go new file mode 100644 index 0000000..055bf7f --- /dev/null +++ b/internal/buffer/buffer.go @@ -0,0 +1,95 @@ +package buffer + +import ( + "io" +) + +type Buffer struct { + buf []byte + pos int + len int +} + +func NewBuffer(buf []byte) *Buffer { + return &Buffer{ + buf: buf, + pos: 0, + len: len(buf), + } +} + +// Read implements io.Reader's Read method +func (b *Buffer) Read(p []byte) (int, error) { + if b.pos >= b.len { + return 0, io.EOF + } + + n := len(p) + if n > b.len-b.pos { + n = b.len - b.pos + } + + copy(p[:n], b.buf[b.pos:b.pos+n]) + b.pos += n + + return n, nil +} + +// Write appends the contents of p to the buffer's data. +func (b *Buffer) Write(p []byte) (int, error) { + if len(b.buf) < b.len+len(p) { + newLen := b.len + len(p) + if cap(b.buf) >= newLen { + b.buf = b.buf[:newLen] + } else { + newBuf := make([]byte, newLen*2) + copy(newBuf, b.buf[:b.len]) + b.buf = newBuf + } + } + + copy(b.buf[b.len:], p) + b.len += len(p) + + return len(p), nil +} + +func (b *Buffer) Len() int { + return b.len +} + +// Reset resets the buffer to be empty. The underlying array is reused if possible. +func (b *Buffer) Reset() { + b.len = 0 + b.pos = 0 +} + +// Seek moves the read position by offset bytes relative to whence (Start, Current, End) +func (b *Buffer) Seek(offset int64, whence int) (int64, error) { + var newpos int + + switch whence { + case io.SeekStart: + newpos = int(offset) + case io.SeekCurrent: + newpos = b.pos + int(offset) + case io.SeekEnd: + newpos = b.len + int(offset) + default: + return 0, io.EOF + } + + if newpos < 0 { + newpos = 0 + } else if newpos > b.len { + newpos = b.len + } + + b.pos = newpos + + return int64(newpos), nil +} + +func (b *Buffer) Bytes() []byte { + return b.buf[:b.len] +} diff --git a/internal/buffer/buffer_test.go b/internal/buffer/buffer_test.go new file mode 100644 index 0000000..9cddeca --- /dev/null +++ b/internal/buffer/buffer_test.go @@ -0,0 +1,88 @@ +package buffer + +import ( + "io" + "testing" +) + +func TestWrite(t *testing.T) { + b := Buffer{} + data := []byte("test") + + n, err := b.Write(data) + if n != len(data) || err != nil { + t.Errorf("Write failed: expected %d bytes, got %d, error: %v", len(data), n, err) + } + + if b.Len() != len(data) { + t.Errorf("Len is incorrect after write: expected %d, got %d", len(data), b.Len()) + } + + if string(b.Bytes()) != "test" { + t.Error("Bytes returned after write do not match written data") + } +} + +func TestRead(t *testing.T) { + b := NewBuffer([]byte("testdata")) + p := make([]byte, 3) + n, err := b.Read(p) + + if n != 3 || string(p[:n]) != "tes" { + t.Errorf("Read returned incorrect data: expected 'tes', got '%s'", p[:n]) + } + + b.Reset() + b.Write([]byte("abc")) + p = make([]byte, 2) + n, err = b.Read(p) + + if n != 2 || string(p) != "ab" { + t.Errorf("Read after reset failed: expected 'ab', got '%s'", p) + } + + b.pos = b.len + n, err = b.Read(p) + if n != 0 || err != io.EOF { + t.Errorf("Reading beyond buffer did not return EOF: n=%d, err=%v", n, err) + } +} + +func TestReset(t *testing.T) { + b := NewBuffer([]byte("test")) + b.Write([]byte("data")) + + if b.Len() != 8 || b.pos != 0 { + t.Errorf("Initial buffer state incorrect: len=%d, pos=%d", b.Len(), b.pos) + } + + b.Reset() + if b.Len() != 0 || b.pos != 0 { + t.Errorf("Reset did not clear buffer correctly: len=%d, pos=%d", b.Len(), b.pos) + } +} + +func TestSeek(t *testing.T) { + b := NewBuffer([]byte("test")) + tests := []struct { + offset int64 + whence int + expect int64 + err error + }{ + {2, io.SeekStart, 2, nil}, + {-1, io.SeekCurrent, 1, nil}, + {-2, io.SeekEnd, int64(len("test") - 2), nil}, + {5, io.SeekStart, int64(len("test")), nil}, + {-10, io.SeekEnd, 0, nil}, + {0, 999, 0, io.EOF}, // Invalid whence test + } + + for _, tt := range tests { + pos, err := b.Seek(tt.offset, tt.whence) + if pos != tt.expect || (err != tt.err && !((err == nil) && (tt.err == nil))) { + t.Errorf("Seek(%d, %d): expected %d with error %v, got %d and %v", + tt.offset, tt.whence, tt.expect, tt.err, pos, err) + } + } +} |