mirror of
https://github.com/soypat/lneto.git
synced 2026-08-09 09:23:40 +00:00
363 lines
9.3 KiB
Go
363 lines
9.3 KiB
Go
package internal
|
|
|
|
import (
|
|
"bytes"
|
|
"io"
|
|
"math/rand"
|
|
"testing"
|
|
)
|
|
|
|
func TestRing(t *testing.T) {
|
|
rng := rand.New(rand.NewSource(0))
|
|
const bufSize = 10
|
|
r := &Ring{
|
|
Buf: make([]byte, bufSize),
|
|
}
|
|
const data = "hello"
|
|
_, err := r.WriteString(data)
|
|
if err != nil {
|
|
t.Error(err)
|
|
}
|
|
// Case where data is contiguous and at start of buffer.
|
|
var buf [bufSize]byte
|
|
n, err := fragmentReadInto(r, buf[:])
|
|
if err != nil {
|
|
t.Fatal(err)
|
|
}
|
|
if string(buf[:n]) != data {
|
|
t.Fatalf("got %q; want %q", buf[:n], data)
|
|
}
|
|
|
|
// Case where data overwrites end of buffer.
|
|
const overdata = "hello world"
|
|
n, err = r.Write([]byte(overdata))
|
|
if err == nil || n > 0 {
|
|
t.Fatal(err, n)
|
|
}
|
|
|
|
// Set Random data in ring buffer and read it back.
|
|
for i := 0; i < 32; i++ {
|
|
n := rng.Intn(bufSize)
|
|
copy(buf[:], overdata[:n])
|
|
offset := rng.Intn(bufSize - 1)
|
|
setRingData(t, r, offset, buf[:n])
|
|
|
|
// Case where data wraps around end of buffer.
|
|
n, err = r.Read(buf[:])
|
|
if err != nil {
|
|
break
|
|
}
|
|
if string(buf[:n]) != overdata[:n] {
|
|
t.Error("got", buf[:n], "want", overdata[:n])
|
|
}
|
|
}
|
|
|
|
// Set random data and write some more and read it back.
|
|
for i := 0; i < 32; i++ {
|
|
nfirst := rng.Intn(bufSize) / 2
|
|
nsecond := rng.Intn(bufSize) / 2
|
|
if nfirst+nsecond > bufSize {
|
|
nfirst = bufSize - nsecond
|
|
}
|
|
offset := rng.Intn(bufSize - 1)
|
|
|
|
copy(buf[:], overdata[:nfirst])
|
|
setRingData(t, r, offset, buf[:nfirst])
|
|
// println("test", r.end, r.off, offset, r)
|
|
ngot, err := r.WriteString(overdata[nfirst : nfirst+nsecond])
|
|
if err != nil {
|
|
t.Fatal(err)
|
|
}
|
|
if ngot != nsecond {
|
|
t.Errorf("%d did not write data correctly: got %d; want %d", i, ngot, nsecond)
|
|
}
|
|
buf = [bufSize]byte{}
|
|
// Case where data wraps around end of buffer.
|
|
n, err = r.Read(buf[:])
|
|
if err != nil {
|
|
break
|
|
}
|
|
|
|
if n != nfirst+nsecond {
|
|
t.Errorf("got %d; want %d (%d+%d)", n, nfirst+nsecond, nfirst, nsecond)
|
|
}
|
|
if string(buf[:n]) != overdata[:n] {
|
|
t.Errorf("got %q; want %q", buf[:n], overdata[:n])
|
|
}
|
|
}
|
|
|
|
var readback [bufSize]byte
|
|
var zeros [bufSize]byte
|
|
|
|
// Set random data and write some more and read it back with ReadAt and ReadPeek and ReadDiscard.
|
|
for i := 0; i < 32; i++ {
|
|
nfirst := rng.Intn(len(data))/2 + 1 // write garbage data first.
|
|
nsecond := rng.Intn(len(data))/2 + 1
|
|
if nfirst+nsecond > bufSize {
|
|
nfirst = bufSize - nsecond
|
|
}
|
|
r.Reset()
|
|
|
|
randOff := rng.Intn(bufSize)
|
|
content := append([]byte{}, zeros[:nfirst]...)
|
|
content = append(content, data[:nsecond]...)
|
|
setRingData(t, r, randOff, content)
|
|
// Two-tap ReadPeek to make sure pointer not advanced.
|
|
for i := 0; i < 2; i++ {
|
|
n, err = r.ReadPeek(readback[:])
|
|
if err != nil && err != io.EOF {
|
|
t.Fatal("read failed", err)
|
|
} else if n != nfirst+nsecond {
|
|
t.Errorf("want!=got bytes read %d, %d", nfirst+nsecond, n)
|
|
} else if !bytes.Equal(readback[:nfirst], zeros[:nfirst]) {
|
|
t.Error("first section not match")
|
|
} else if !bytes.Equal(readback[nfirst:nfirst+nsecond], []byte(data[:nsecond])) {
|
|
t.Error("second section not match")
|
|
}
|
|
}
|
|
|
|
// Two-tap ReadAt to make sure pointer not advanced.
|
|
for i := 0; i < 2; i++ {
|
|
off := rng.Intn(nfirst + nsecond)
|
|
n, err = r.ReadAt(readback[:nfirst+nsecond-off], int64(off))
|
|
|
|
readat := readback[:n]
|
|
first := zeros[min(off, nfirst):nfirst]
|
|
secondOff := max(0, off-nfirst)
|
|
second := []byte(data[secondOff:nsecond])
|
|
gotSecond := readat[len(first):]
|
|
if err != nil && err != io.EOF {
|
|
t.Fatal("read failed", off, err)
|
|
} else if n != nfirst+nsecond-off {
|
|
t.Errorf("want!=got bytes read %d, %d", nfirst+nsecond-off, n)
|
|
} else if len(first) > 0 && !bytes.Equal(readat[:nfirst-off], first) {
|
|
t.Error("first section not match")
|
|
} else if len(second) > 0 && !bytes.Equal(gotSecond, second) {
|
|
t.Errorf("second section not match got=%q want=%q", gotSecond, second)
|
|
}
|
|
}
|
|
|
|
// ReadDiscard test.
|
|
discard := rng.Intn(nfirst+nsecond) + 1
|
|
r.ReadDiscard(discard)
|
|
n, err := r.Read(readback[:])
|
|
if err != nil && err != io.EOF {
|
|
t.Fatal(err)
|
|
}
|
|
wantN := nfirst + nsecond - discard
|
|
if wantN != n {
|
|
t.Errorf("Want %d bytes read, got %d", wantN, n)
|
|
}
|
|
if !bytes.Equal(readback[:n], content[discard:]) {
|
|
t.Errorf("want data read %q, got %q", content[discard:], readback[:n])
|
|
}
|
|
}
|
|
|
|
_ = r._string(0)
|
|
}
|
|
|
|
func TestRing2(t *testing.T) {
|
|
const maxsize = 6
|
|
const ntests = 80000
|
|
rng := rand.New(rand.NewSource(0))
|
|
data := make([]byte, maxsize)
|
|
ringbuf := make([]byte, maxsize)
|
|
auxbuf := make([]byte, maxsize)
|
|
rng.Read(data)
|
|
for i := 0; i < ntests; i++ {
|
|
dsize := max(rng.Intn(len(data)), 1)
|
|
if !testRing1_loopback(t, rng, ringbuf, data[:dsize], auxbuf) {
|
|
t.Fatalf("failed test %d", i)
|
|
}
|
|
}
|
|
}
|
|
|
|
func TestRingWriteLimited(t *testing.T) {
|
|
rng := rand.New(rand.NewSource(2))
|
|
const bufSize = 8
|
|
r := &Ring{
|
|
Buf: make([]byte, bufSize),
|
|
}
|
|
var data [bufSize]byte
|
|
var wdata [bufSize]byte
|
|
for i := 0; i < 10000; i++ {
|
|
for i := range r.Buf {
|
|
r.Buf[i] = 0
|
|
}
|
|
buffered, _ := rng.Read(data[:rng.Intn(bufSize-2)+1])
|
|
off := rng.Intn(bufSize)
|
|
setRingData(t, r, off, data[:buffered])
|
|
if r.Buffered() != buffered {
|
|
t.Fatalf("failed to set buffered amount of data")
|
|
} else if r.Off != off {
|
|
t.Fatal("bad offset")
|
|
}
|
|
free := r.Free()
|
|
|
|
toWrite := rng.Intn(free-1) + 1
|
|
rng.Read(wdata[:toWrite])
|
|
limOff := rng.Intn(bufSize) + 1
|
|
var wantN int
|
|
isContiguous := limOff > r.End
|
|
if isContiguous {
|
|
wantN = min(toWrite, limOff-r.End)
|
|
} else {
|
|
wantN = min(toWrite, len(r.Buf)-r.End+limOff)
|
|
}
|
|
overwrite := toWrite > wantN
|
|
n, err := r.WriteLimited(wdata[:toWrite], limOff)
|
|
if !overwrite && err != nil {
|
|
t.Errorf("limited write: %s", err)
|
|
} else if !overwrite && n != wantN {
|
|
t.Errorf("nwant=%d ngot=%d off=%d lim=%d towrite=%d buffered=%d/%d wantremain=%d gotremain=%d endOff=%d", wantN, n, off, limOff, toWrite, buffered, r.Size(), free-wantN, free-n, r.Off)
|
|
} else if overwrite && (err == nil || n != 0) {
|
|
t.Errorf("expected full buffer error and no data written on limit overwrite, got %d", n)
|
|
}
|
|
for i := r.End % r.Size(); i != limOff && i != r.Off; i = (i + 1) % r.Size() {
|
|
if r.Buf[i] != 0 {
|
|
t.Fatalf("OVERWRITE pos=%d end=%d lim=%d", i, r.End, limOff)
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
func TestRing_findcrash(t *testing.T) {
|
|
const maxsize = 33
|
|
const ntests = 800000
|
|
r := Ring{
|
|
Buf: make([]byte, maxsize*6),
|
|
}
|
|
rng := rand.New(rand.NewSource(0))
|
|
data := make([]byte, maxsize)
|
|
|
|
for i := 0; i < ntests; i++ {
|
|
free := r.Free()
|
|
if free < 0 {
|
|
t.Fatal("free < 0")
|
|
}
|
|
if rng.Intn(2) == 0 {
|
|
l := max(rng.Intn(len(data)), 1)
|
|
if l > free {
|
|
continue // Buffer full.
|
|
}
|
|
n, err := r.Write(data[:l])
|
|
expectFree := free - n
|
|
free = r.Free()
|
|
if n != l {
|
|
t.Fatal(i, "write failed", n, l, err)
|
|
} else if expectFree != free {
|
|
t.Fatal(i, "free not updated correctly", expectFree, free)
|
|
}
|
|
}
|
|
buffered := r.Buffered()
|
|
if buffered < 0 {
|
|
t.Fatal("buffered < 0")
|
|
}
|
|
if rng.Intn(2) == 0 {
|
|
l := max(rng.Intn(len(data)), 1)
|
|
n, err := r.Read(data[:l])
|
|
expectRead := min(buffered, l)
|
|
expectBuffered := buffered - n
|
|
buffered = r.Buffered()
|
|
if n != expectRead {
|
|
t.Fatal(i, "read failed", n, l, expectRead, err)
|
|
} else if buffered != expectBuffered {
|
|
t.Fatal(i, "buffered not updated correctly", expectBuffered, buffered)
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
func testRing1_loopback(t *testing.T, rng *rand.Rand, ringbuf, data, auxbuf []byte) bool {
|
|
if len(data) > len(ringbuf) || len(data) > len(auxbuf) {
|
|
panic("invalid ringbuf or data")
|
|
}
|
|
dsize := len(data)
|
|
var r Ring
|
|
r.Buf = ringbuf
|
|
|
|
nfirst := rng.Intn(dsize) / 2
|
|
nsecond := rng.Intn(dsize) / 2
|
|
if nfirst == 0 || nsecond == 0 {
|
|
return true
|
|
}
|
|
offset := rng.Intn(dsize - 1)
|
|
|
|
setRingData(t, &r, offset, data[:nfirst])
|
|
ngot, err := r.Write(data[nfirst : nfirst+nsecond])
|
|
if err != nil {
|
|
t.Error(err)
|
|
return false
|
|
}
|
|
if ngot != nsecond {
|
|
t.Errorf("did not write data correctly: got %d; want %d", ngot, nsecond)
|
|
}
|
|
// Case where data wraps around end of buffer.
|
|
n, err := r.Read(auxbuf[:])
|
|
if err != nil {
|
|
t.Error(err)
|
|
return false
|
|
}
|
|
|
|
if n != nfirst+nsecond {
|
|
t.Errorf("got %d; want %d (%d+%d)", n, nfirst+nsecond, nfirst, nsecond)
|
|
}
|
|
if !bytes.Equal(auxbuf[:n], data[:n]) {
|
|
t.Errorf("got %q; want %q", auxbuf[:n], data[:n])
|
|
}
|
|
return !t.Failed()
|
|
}
|
|
|
|
func fragmentReadInto(r io.Reader, buf []byte) (n int, _ error) {
|
|
maxSize := len(buf) / 4
|
|
for {
|
|
ntop := min(n+rand.Intn(maxSize)+1, len(buf))
|
|
ngot, err := r.Read(buf[n:ntop])
|
|
n += ngot
|
|
if err != nil {
|
|
if err == io.EOF {
|
|
return n, nil
|
|
}
|
|
return n, err
|
|
}
|
|
if n == len(buf) {
|
|
return n, nil
|
|
}
|
|
}
|
|
}
|
|
|
|
func setRingData(t *testing.T, r *Ring, offset int, data []byte) {
|
|
t.Helper()
|
|
if len(data) > len(r.Buf) {
|
|
panic("data too large")
|
|
}
|
|
n := copy(r.Buf[offset:], data)
|
|
r.End = offset + n
|
|
if len(data)+offset > len(r.Buf) {
|
|
// End of buffer not enough to hold data, wrap around.
|
|
n = copy(r.Buf, data[n:])
|
|
r.End = n
|
|
}
|
|
r.Off = offset
|
|
r.onReadEnd()
|
|
// println("buf:", len(r.buf), "end:", r.end, "off:", r.off, offset, "data:", len(data))
|
|
free := r.Free()
|
|
wantFree := len(r.Buf) - len(data)
|
|
if free != wantFree {
|
|
t.Fatalf("free got %d; want %d", free, wantFree)
|
|
}
|
|
buffered := r.Buffered()
|
|
wantBuffered := len(data)
|
|
if buffered != wantBuffered {
|
|
t.Fatalf("buffered got %d; want %d", buffered, wantBuffered)
|
|
}
|
|
end := r.End
|
|
off := r.Off
|
|
sdata := r.string()
|
|
if sdata != string(data) {
|
|
t.Fatalf("data got %q; want %q", sdata, data)
|
|
}
|
|
r.End = end
|
|
r.Off = off
|
|
}
|