Skip to content
Snippets Groups Projects
Commit f6cde8d9 authored by Will McCutchen's avatar Will McCutchen
Browse files

Fix and test synthetic byte stream

parent 7ec04703
No related branches found
No related tags found
No related merge requests found
......@@ -559,10 +559,9 @@ func (h *HTTPBin) Range(w http.ResponseWriter, r *http.Request) {
return
}
content := &syntheticReadSeeker{
numBytes: numBytes,
byteFactory: func(offset int64) byte { return byte(97 + (offset % 26)) },
}
content := newSyntheticByteStream(numBytes, func(offset int64) byte {
return byte(97 + (offset % 26))
})
var modtime time.Time
http.ServeContent(w, r, "", modtime, content)
}
......
......@@ -11,6 +11,7 @@ import (
"net/url"
"strconv"
"strings"
"sync"
"time"
)
......@@ -140,41 +141,60 @@ func parseBoundedDuration(input string, min, max time.Duration) (time.Duration,
return d, err
}
// syntheticReadSeeker implements the ReadSeeker interface to allow reading
// syntheticByteStream implements the ReadSeeker interface to allow reading
// arbitrary subsets of bytes up to a maximum size given a function for
// generating the byte at a given offset.
type syntheticReadSeeker struct {
numBytes int64
type syntheticByteStream struct {
mu sync.Mutex
size int64
offset int64
byteFactory func(int64) byte
factory func(int64) byte
}
// newSyntheticByteStream returns a new stream of bytes of a specific size,
// given a factory function for generating the byte at a given offset.
func newSyntheticByteStream(size int64, factory func(int64) byte) io.ReadSeeker {
return &syntheticByteStream{
size: size,
factory: factory,
}
}
// Read implements the Reader interface for syntheticReadSeeker
func (s *syntheticReadSeeker) Read(p []byte) (int, error) {
// Read implements the Reader interface for syntheticByteStream
func (s *syntheticByteStream) Read(p []byte) (int, error) {
s.mu.Lock()
defer s.mu.Unlock()
start := s.offset
end := start + int64(len(p))
var err error
if end > s.numBytes {
if end >= s.size {
err = io.EOF
end = s.numBytes - start
end = s.size
}
for idx := start; idx < end; idx++ {
p[idx-start] = s.byteFactory(idx)
p[idx-start] = s.factory(idx)
}
s.offset = end
return int(end - start), err
}
// Seek implements the Seeker interface for syntheticReadSeeker
func (s *syntheticReadSeeker) Seek(offset int64, whence int) (int64, error) {
// Seek implements the Seeker interface for syntheticByteStream
func (s *syntheticByteStream) Seek(offset int64, whence int) (int64, error) {
s.mu.Lock()
defer s.mu.Unlock()
switch whence {
case io.SeekStart:
s.offset = offset
case io.SeekCurrent:
s.offset += offset
case io.SeekEnd:
s.offset = s.numBytes - offset
s.offset = s.size - offset
default:
return 0, errors.New("Seek: invalid whence")
}
......
......@@ -2,10 +2,36 @@ package httpbin
import (
"fmt"
"io"
"reflect"
"testing"
"time"
)
func assertNil(t *testing.T, v interface{}) {
if v != nil {
t.Errorf("expected nil, got %#v", v)
}
}
func assertIntEqual(t *testing.T, a, b int) {
if a != b {
t.Errorf("expected %v == %v", a, b)
}
}
func assertBytesEqual(t *testing.T, a, b []byte) {
if !reflect.DeepEqual(a, b) {
t.Errorf("expected %v == %v", a, b)
}
}
func assertError(t *testing.T, got, expected error) {
if got != expected {
t.Errorf("expected error %v, got %v", expected, got)
}
}
func TestParseDuration(t *testing.T) {
var okTests = []struct {
input string
......@@ -53,3 +79,66 @@ func TestParseDuration(t *testing.T) {
})
}
}
func TestSyntheticByteStream(t *testing.T) {
factory := func(offset int64) byte {
return byte(offset)
}
t.Run("read", func(t *testing.T) {
s := newSyntheticByteStream(10, factory)
// read first half
p := make([]byte, 5)
count, err := s.Read(p)
assertNil(t, err)
assertIntEqual(t, count, 5)
assertBytesEqual(t, p, []byte{0, 1, 2, 3, 4})
// read second half
p = make([]byte, 5)
count, err = s.Read(p)
assertError(t, err, io.EOF)
assertIntEqual(t, count, 5)
assertBytesEqual(t, p, []byte{5, 6, 7, 8, 9})
// can't read any more
p = make([]byte, 5)
count, err = s.Read(p)
assertError(t, err, io.EOF)
assertIntEqual(t, count, 0)
assertBytesEqual(t, p, []byte{0, 0, 0, 0, 0})
})
t.Run("read into too-large buffer", func(t *testing.T) {
s := newSyntheticByteStream(5, factory)
p := make([]byte, 10)
count, err := s.Read(p)
assertError(t, err, io.EOF)
assertIntEqual(t, count, 5)
assertBytesEqual(t, p, []byte{0, 1, 2, 3, 4, 0, 0, 0, 0, 0})
})
t.Run("seek", func(t *testing.T) {
s := newSyntheticByteStream(100, factory)
p := make([]byte, 5)
s.Seek(10, io.SeekStart)
count, err := s.Read(p)
assertNil(t, err)
assertIntEqual(t, count, 5)
assertBytesEqual(t, p, []byte{10, 11, 12, 13, 14})
s.Seek(10, io.SeekCurrent)
count, err = s.Read(p)
assertNil(t, err)
assertIntEqual(t, count, 5)
assertBytesEqual(t, p, []byte{25, 26, 27, 28, 29})
s.Seek(10, io.SeekEnd)
count, err = s.Read(p)
assertNil(t, err)
assertIntEqual(t, count, 5)
assertBytesEqual(t, p, []byte{90, 91, 92, 93, 94})
})
}
0% Loading or .
You are about to add 0 people to the discussion. Proceed with caution.
Please register or to comment