330 lines
10 KiB
Go
330 lines
10 KiB
Go
package byteio_test
|
|
|
|
import (
|
|
"bytes"
|
|
"encoding/binary"
|
|
"errors"
|
|
"io"
|
|
"math"
|
|
"testing"
|
|
|
|
"src.lwithers.me.uk/go/byteio"
|
|
)
|
|
|
|
// TestReadUint ensures correct operation of the ReadUint variants by verifying
|
|
// read data integrity against encoding/binary.
|
|
func TestReadUint(t *testing.T) {
|
|
checkErr := func(err error) {
|
|
if err != nil {
|
|
t.Fatalf("unexpected I/O error: %v", err)
|
|
}
|
|
}
|
|
|
|
// prepare a buffer of test data
|
|
vals := []uint64{0, 1, 0x8000, 0xFFFF, 0x81020304050607}
|
|
buf := bytes.NewBuffer(nil)
|
|
for _, val := range vals {
|
|
checkErr(binary.Write(buf, binary.BigEndian, uint16(val)))
|
|
checkErr(binary.Write(buf, binary.BigEndian, uint32(val)))
|
|
checkErr(binary.Write(buf, binary.BigEndian, uint64(val)))
|
|
checkErr(binary.Write(buf, binary.LittleEndian, uint16(val)))
|
|
checkErr(binary.Write(buf, binary.LittleEndian, uint32(val)))
|
|
checkErr(binary.Write(buf, binary.LittleEndian, uint64(val)))
|
|
}
|
|
|
|
// validate that we read back the expected values
|
|
for _, exp64 := range vals {
|
|
var (
|
|
act16, exp16 uint16 = 0, uint16(exp64)
|
|
act32, exp32 uint32 = 0, uint32(exp64)
|
|
act64 uint64
|
|
err error
|
|
)
|
|
|
|
act16, err = byteio.ReadUint16BE(buf)
|
|
checkErr(err)
|
|
if act16 != exp16 {
|
|
t.Errorf("ReadUint16BE: act %X ≠ exp %X", act16, exp16)
|
|
}
|
|
|
|
act32, err = byteio.ReadUint32BE(buf)
|
|
checkErr(err)
|
|
if act32 != exp32 {
|
|
t.Errorf("ReadUint32BE: act %X ≠ exp %X", act32, exp32)
|
|
}
|
|
|
|
act64, err = byteio.ReadUint64BE(buf)
|
|
checkErr(err)
|
|
if act64 != exp64 {
|
|
t.Errorf("ReadUint64BE: act %X ≠ exp %X", act64, exp64)
|
|
}
|
|
|
|
act16, err = byteio.ReadUint16LE(buf)
|
|
checkErr(err)
|
|
if act16 != exp16 {
|
|
t.Errorf("ReadUint16LE: act %X ≠ exp %X", act16, exp16)
|
|
}
|
|
|
|
act32, err = byteio.ReadUint32LE(buf)
|
|
checkErr(err)
|
|
if act32 != exp32 {
|
|
t.Errorf("ReadUint32LE: act %X ≠ exp %X", act32, exp32)
|
|
}
|
|
|
|
act64, err = byteio.ReadUint64LE(buf)
|
|
checkErr(err)
|
|
if act64 != exp64 {
|
|
t.Errorf("ReadUint64LE: act %X ≠ exp %X", act64, exp64)
|
|
}
|
|
}
|
|
}
|
|
|
|
// TestReadInt ensures correct operation of the ReadInt variants by comparing
|
|
// operation against encoding/binary.
|
|
func TestReadInt(t *testing.T) {
|
|
checkErr := func(err error) {
|
|
if err != nil {
|
|
t.Fatalf("read error: %v", err)
|
|
}
|
|
}
|
|
|
|
// prepare a buffer of test data using encoding/binary
|
|
vals := []int64{-1, 0, 1, -0x8000, 0xFFFF, 0x01020304050607}
|
|
buf := bytes.NewBuffer(nil)
|
|
for _, val := range vals {
|
|
checkErr(binary.Write(buf, binary.BigEndian, int16(val)))
|
|
checkErr(binary.Write(buf, binary.BigEndian, int32(val)))
|
|
checkErr(binary.Write(buf, binary.BigEndian, int64(val)))
|
|
checkErr(binary.Write(buf, binary.LittleEndian, int16(val)))
|
|
checkErr(binary.Write(buf, binary.LittleEndian, int32(val)))
|
|
checkErr(binary.Write(buf, binary.LittleEndian, int64(val)))
|
|
}
|
|
|
|
// validate that we read back the expected values
|
|
for _, exp64 := range vals {
|
|
var (
|
|
v16, exp16 int16 = 0, int16(exp64)
|
|
v32, exp32 int32 = 0, int32(exp64)
|
|
v64 int64
|
|
err error
|
|
)
|
|
|
|
v16, err = byteio.ReadInt16BE(buf)
|
|
checkErr(err)
|
|
if v16 != exp16 {
|
|
t.Errorf("ReadInt16BE: act %X ≠ exp %X", v16, exp16)
|
|
}
|
|
|
|
v32, err = byteio.ReadInt32BE(buf)
|
|
checkErr(err)
|
|
if v32 != exp32 {
|
|
t.Errorf("ReadInt32BE: act %X ≠ exp %X", v32, exp32)
|
|
}
|
|
|
|
v64, err = byteio.ReadInt64BE(buf)
|
|
checkErr(err)
|
|
if v64 != exp64 {
|
|
t.Errorf("ReadInt64BE: act %X ≠ exp %X", v64, exp64)
|
|
}
|
|
|
|
v16, err = byteio.ReadInt16LE(buf)
|
|
checkErr(err)
|
|
if v16 != exp16 {
|
|
t.Errorf("ReadInt16LE: act %X ≠ exp %X", v16, exp16)
|
|
}
|
|
|
|
v32, err = byteio.ReadInt32LE(buf)
|
|
checkErr(err)
|
|
if v32 != exp32 {
|
|
t.Errorf("ReadInt32LE: act %X ≠ exp %X", v32, exp32)
|
|
}
|
|
|
|
v64, err = byteio.ReadInt64LE(buf)
|
|
checkErr(err)
|
|
if v64 != exp64 {
|
|
t.Errorf("ReadInt64LE: act %X ≠ exp %X", v64, exp64)
|
|
}
|
|
}
|
|
}
|
|
|
|
// TestReadFloat validates operation of the byteio.ReadFloat variants against
|
|
// values written by encoding/binary.
|
|
func TestReadFloat(t *testing.T) {
|
|
vals := []float64{
|
|
0, 1, -1,
|
|
math.MaxFloat32, math.SmallestNonzeroFloat32,
|
|
math.Inf(1), math.Inf(-1), math.NaN(),
|
|
}
|
|
|
|
buf := bytes.NewBuffer(nil)
|
|
|
|
for _, val := range vals {
|
|
binary.Write(buf, binary.BigEndian, val)
|
|
binary.Write(buf, binary.BigEndian, float32(val))
|
|
binary.Write(buf, binary.LittleEndian, val)
|
|
binary.Write(buf, binary.LittleEndian, float32(val))
|
|
}
|
|
|
|
for _, exp64 := range vals {
|
|
exp32 := float32(exp64)
|
|
|
|
if act64, err := byteio.ReadFloat64BE(buf); err != nil {
|
|
t.Fatalf("read error: %v", err)
|
|
} else if act64 != exp64 && !math.IsNaN(act64) && !math.IsNaN(exp64) {
|
|
t.Errorf("act %f (0x%X) ≠ exp %f (0x%X)",
|
|
act64, math.Float64bits(act64),
|
|
exp64, math.Float64bits(exp64))
|
|
}
|
|
if act32, err := byteio.ReadFloat32BE(buf); err != nil {
|
|
t.Fatalf("read error: %v", err)
|
|
} else if act32 != exp32 && !math.IsNaN(float64(act32)) && !math.IsNaN(exp64) {
|
|
t.Errorf("act %f (0x%X) ≠ exp %f (0x%X)",
|
|
act32, math.Float32bits(act32),
|
|
exp32, math.Float32bits(exp32))
|
|
}
|
|
|
|
if act64, err := byteio.ReadFloat64LE(buf); err != nil {
|
|
t.Fatalf("read error: %v", err)
|
|
} else if act64 != exp64 && !math.IsNaN(act64) && !math.IsNaN(exp64) {
|
|
t.Errorf("act %f (0x%X) ≠ exp %f (0x%X)",
|
|
act64, math.Float64bits(act64),
|
|
exp64, math.Float64bits(exp64))
|
|
}
|
|
if act32, err := byteio.ReadFloat32LE(buf); err != nil {
|
|
t.Fatalf("read error: %v", err)
|
|
} else if act32 != exp32 && !math.IsNaN(float64(act32)) && !math.IsNaN(exp64) {
|
|
t.Errorf("act %f (0x%X) ≠ exp %f (0x%X)",
|
|
act32, math.Float32bits(act32),
|
|
exp32, math.Float32bits(exp32))
|
|
}
|
|
}
|
|
}
|
|
|
|
// TestReadIntEOF ensures that io.EOF is propagated correctly when a ReadInt
|
|
// variant is called at the end of a buffer (i.e. 0 bytes to read). It only
|
|
// tests the Uint variants since the Int and Float variants are wrappers over
|
|
// Uint.
|
|
func TestReadIntEOF(t *testing.T) {
|
|
bin := bytes.NewBuffer(nil)
|
|
check := func(fn string, f func() error) {
|
|
err := f()
|
|
switch err {
|
|
case nil:
|
|
t.Errorf("%s: did not return expected error", fn)
|
|
case io.EOF:
|
|
// OK
|
|
default:
|
|
t.Errorf("%s: returned unexpected error %v", fn, err)
|
|
}
|
|
}
|
|
|
|
check("ReadUint16LE", func() error { _, err := byteio.ReadUint16LE(bin); return err })
|
|
check("ReadUint16BE", func() error { _, err := byteio.ReadUint16BE(bin); return err })
|
|
check("ReadUint32LE", func() error { _, err := byteio.ReadUint32LE(bin); return err })
|
|
check("ReadUint32BE", func() error { _, err := byteio.ReadUint32BE(bin); return err })
|
|
check("ReadUint64LE", func() error { _, err := byteio.ReadUint64LE(bin); return err })
|
|
check("ReadUint64BE", func() error { _, err := byteio.ReadUint64BE(bin); return err })
|
|
|
|
}
|
|
|
|
// TestReadIntShort ensures that io.ErrUnexpectedEOF is returned when a ReadInt
|
|
// variant is called without sufficient data, but not immediately at the end of
|
|
// the buffer (i.e. > 0 bytes to read, but not sufficient for data type).
|
|
func TestReadIntShort(t *testing.T) {
|
|
check := func(fn string, sz int, f func(bin byteio.Reader) error) {
|
|
for i := 1; i < sz; i++ {
|
|
bin := bytes.NewBuffer(make([]byte, i))
|
|
err := f(bin)
|
|
switch err {
|
|
case nil:
|
|
t.Errorf("%s/%d: did not return expected error",
|
|
fn, i)
|
|
case io.ErrUnexpectedEOF:
|
|
// OK
|
|
case io.EOF:
|
|
t.Errorf("%s/%d: returned incorrect io.EOF",
|
|
fn, i)
|
|
default:
|
|
t.Errorf("%s/%d: returned unexpected error %v",
|
|
fn, i, err)
|
|
}
|
|
}
|
|
}
|
|
|
|
check("ReadUint16LE", 2, func(bin byteio.Reader) error { _, err := byteio.ReadUint16LE(bin); return err })
|
|
check("ReadUint16BE", 2, func(bin byteio.Reader) error { _, err := byteio.ReadUint16BE(bin); return err })
|
|
check("ReadUint32LE", 4, func(bin byteio.Reader) error { _, err := byteio.ReadUint32LE(bin); return err })
|
|
check("ReadUint32BE", 4, func(bin byteio.Reader) error { _, err := byteio.ReadUint32BE(bin); return err })
|
|
check("ReadUint64LE", 8, func(bin byteio.Reader) error { _, err := byteio.ReadUint64LE(bin); return err })
|
|
check("ReadUint64BE", 8, func(bin byteio.Reader) error { _, err := byteio.ReadUint64BE(bin); return err })
|
|
}
|
|
|
|
// ErrAbortReader is returned by AbortReader when the limit is reached.
|
|
var ErrAbortReader = errors.New("aborted read")
|
|
|
|
// AbortReader will abort after a certain number of bytes have been read with a
|
|
// non-io.EOF error. This can be used to test error handling on partial reads.
|
|
// It does not implement byteio.Reader, to ensure that errors propagate
|
|
// correctly through the implicit bufio wrapper.
|
|
type AbortReader struct {
|
|
when, cur int
|
|
}
|
|
|
|
func (ar *AbortReader) Read(buf []byte) (n int, err error) {
|
|
for i := range buf {
|
|
if ar.when == ar.cur {
|
|
return i, ErrAbortReader
|
|
}
|
|
buf[i] = 0x55
|
|
ar.cur++
|
|
}
|
|
return len(buf), nil
|
|
}
|
|
|
|
// TestReadIntErr ensures that errors encountered during reads are propagated
|
|
// correctly.
|
|
func TestReadIntErr(t *testing.T) {
|
|
check := func(name string, size int, fn func(bin byteio.Reader) error) {
|
|
for i := 0; i < size; i++ {
|
|
err := fn(byteio.NewReader(&AbortReader{when: i}))
|
|
switch err {
|
|
case nil:
|
|
if i < size {
|
|
t.Errorf("%s: expected err after %d "+
|
|
"bytes", name, i)
|
|
}
|
|
case ErrAbortReader:
|
|
if i == size {
|
|
t.Errorf("%s: unexpected err after %d "+
|
|
"bytes", name, i)
|
|
}
|
|
default:
|
|
t.Errorf("%s: unexpected err %v", name, err)
|
|
}
|
|
}
|
|
}
|
|
|
|
check("ReadUint16BE", 2, func(bin byteio.Reader) error { _, err := byteio.ReadUint16BE(bin); return err })
|
|
check("ReadUint32BE", 4, func(bin byteio.Reader) error { _, err := byteio.ReadUint32BE(bin); return err })
|
|
check("ReadUint64BE", 8, func(bin byteio.Reader) error { _, err := byteio.ReadUint64BE(bin); return err })
|
|
check("ReadUint16LE", 2, func(bin byteio.Reader) error { _, err := byteio.ReadUint16LE(bin); return err })
|
|
check("ReadUint32LE", 4, func(bin byteio.Reader) error { _, err := byteio.ReadUint32LE(bin); return err })
|
|
check("ReadUint64LE", 8, func(bin byteio.Reader) error { _, err := byteio.ReadUint64LE(bin); return err })
|
|
}
|
|
|
|
// BenchmarkReadUint32BE is a simple benchmark for reading 32-bit integers.
|
|
func BenchmarkReadUint32BE(b *testing.B) {
|
|
bin := byteio.NewReader(new(MockReader))
|
|
for i := 0; i < b.N; i++ {
|
|
_, _ = byteio.ReadUint32BE(bin)
|
|
}
|
|
}
|
|
|
|
// BenchmarkReadUint32BE is a simple benchmark for reading 64-bit integers.
|
|
func BenchmarkReadUint64BE(b *testing.B) {
|
|
bin := byteio.NewReader(new(MockReader))
|
|
for i := 0; i < b.N; i++ {
|
|
_, _ = byteio.ReadUint64BE(bin)
|
|
}
|
|
}
|