byteio/val_read_test.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)
}
}