bson/value_reader_test.go
2025-03-17 20:58:26 +01:00

1490 lines
35 KiB
Go

// Copyright (C) MongoDB, Inc. 2017-present.
//
// Licensed under the Apache License, Version 2.0 (the "License"); you may
// not use this file except in compliance with the License. You may obtain
// a copy of the License at http://www.apache.org/licenses/LICENSE-2.0
package bson
import (
"bufio"
"bytes"
_ "embed"
"errors"
"fmt"
"io"
"math"
"testing"
"gitea.psichedelico.com/go/bson/internal/assert"
"gitea.psichedelico.com/go/bson/x/bsonx/bsoncore"
"github.com/google/go-cmp/cmp"
)
//go:embed testdata/lorem.txt
var lorem []byte
var testcstring = append(lorem, []byte{0x00}...)
func TestValueReader(t *testing.T) {
t.Run("ReadBinary", func(t *testing.T) {
testCases := []struct {
name string
data []byte
btype byte
b []byte
err error
vType Type
}{
{
"incorrect type",
[]byte{},
0,
nil,
(&valueReader{stack: []vrState{{vType: TypeEmbeddedDocument}}, frame: 0}).typeError(TypeBinary),
TypeEmbeddedDocument,
},
{
"length too short",
[]byte{},
0,
nil,
io.EOF,
TypeBinary,
},
{
"no byte available",
[]byte{0x00, 0x00, 0x00, 0x00},
0,
nil,
io.EOF,
TypeBinary,
},
{
"not enough bytes for binary",
[]byte{0x05, 0x00, 0x00, 0x00, 0x00},
0,
nil,
io.EOF,
TypeBinary,
},
{
"success",
[]byte{0x03, 0x00, 0x00, 0x00, 0xEA, 0x01, 0x02, 0x03},
0xEA,
[]byte{0x01, 0x02, 0x03},
nil,
TypeBinary,
},
}
for _, tc := range testCases {
t.Run(tc.name, func(t *testing.T) {
vr := &valueReader{
r: bufio.NewReader(bytes.NewReader(tc.data)),
stack: []vrState{
{mode: mTopLevel},
{
mode: mElement,
vType: tc.vType,
},
},
frame: 1,
}
b, btype, err := vr.ReadBinary()
if !errequal(t, err, tc.err) {
t.Errorf("Returned errors do not match. got %v; want %v", err, tc.err)
}
if btype != tc.btype {
t.Errorf("Incorrect binary type returned. got %v; want %v", btype, tc.btype)
}
if !bytes.Equal(b, tc.b) {
t.Errorf("Binary data does not match. got %v; want %v", b, tc.b)
}
})
}
})
t.Run("ReadBoolean", func(t *testing.T) {
testCases := []struct {
name string
data []byte
boolean bool
err error
vType Type
}{
{
"incorrect type",
[]byte{},
false,
(&valueReader{stack: []vrState{{vType: TypeEmbeddedDocument}}, frame: 0}).typeError(TypeBoolean),
TypeEmbeddedDocument,
},
{
"no byte available",
[]byte{},
false,
io.EOF,
TypeBoolean,
},
{
"invalid byte for boolean",
[]byte{0x03},
false,
fmt.Errorf("invalid byte for boolean, %b", 0x03),
TypeBoolean,
},
{
"success",
[]byte{0x01},
true,
nil,
TypeBoolean,
},
}
for _, tc := range testCases {
t.Run(tc.name, func(t *testing.T) {
vr := &valueReader{
r: bufio.NewReader(bytes.NewReader(tc.data)),
stack: []vrState{
{mode: mTopLevel},
{
mode: mElement,
vType: tc.vType,
},
},
frame: 1,
}
boolean, err := vr.ReadBoolean()
if !errequal(t, err, tc.err) {
t.Errorf("Returned errors do not match. got %v; want %v", err, tc.err)
}
if boolean != tc.boolean {
t.Errorf("Incorrect boolean returned. got %v; want %v", boolean, tc.boolean)
}
})
}
})
t.Run("ReadDocument", func(t *testing.T) {
t.Run("TopLevel", func(t *testing.T) {
doc := []byte{0x05, 0x00, 0x00, 0x00, 0x00}
vr := &valueReader{
stack: []vrState{{mode: mTopLevel}},
frame: 0,
}
// invalid length
vr.r = bufio.NewReader(bytes.NewReader([]byte{0x00, 0x00}))
_, err := vr.ReadDocument()
if !errors.Is(err, io.ErrUnexpectedEOF) {
t.Errorf("Expected io.ErrUnexpectedEOF with document length too small. got %v; want %v", err, io.EOF)
}
if vr.offset != 0 {
t.Errorf("Expected 0 offset. got %d", vr.offset)
}
vr.r = bufio.NewReader(bytes.NewReader(doc))
_, err = vr.ReadDocument()
noerr(t, err)
if vr.stack[vr.frame].end != 5 {
t.Errorf("Incorrect end for document. got %d; want %d", vr.stack[vr.frame].end, 5)
}
})
t.Run("EmbeddedDocument", func(t *testing.T) {
vr := &valueReader{
stack: []vrState{
{mode: mTopLevel},
{mode: mElement, vType: TypeBoolean},
},
frame: 1,
}
var wanterr = (&valueReader{stack: []vrState{{mode: mElement, vType: TypeBoolean}}}).typeError(TypeEmbeddedDocument)
_, err := vr.ReadDocument()
if err == nil || err.Error() != wanterr.Error() {
t.Errorf("Incorrect returned error. got %v; want %v", err, wanterr)
}
vr.stack[1].mode = mArray
wanterr = vr.invalidTransitionErr(mDocument, "ReadDocument", []mode{mTopLevel, mElement, mValue})
_, err = vr.ReadDocument()
if err == nil || err.Error() != wanterr.Error() {
t.Errorf("Incorrect returned error. got %v; want %v", err, wanterr)
}
vr.stack[1].mode, vr.stack[1].vType = mElement, TypeEmbeddedDocument
vr.offset = 4
vr.r = bufio.NewReader(bytes.NewReader([]byte{0x05, 0x00, 0x00, 0x00, 0x00, 0x00}))
_, err = vr.ReadDocument()
noerr(t, err)
if len(vr.stack) != 3 {
t.Errorf("Incorrect number of stack frames. got %d; want %d", len(vr.stack), 3)
}
if vr.stack[2].mode != mDocument {
t.Errorf("Incorrect mode set. got %v; want %v", vr.stack[2].mode, mDocument)
}
if vr.stack[2].end != 9 {
t.Errorf("End of embedded document is not correct. got %d; want %d", vr.stack[2].end, 9)
}
if vr.offset != 8 {
t.Errorf("Offset not incremented correctly. got %d; want %d", vr.offset, 8)
}
vr.frame--
_, err = vr.ReadDocument()
if !errors.Is(err, io.ErrUnexpectedEOF) {
t.Errorf("Should return error when attempting to read length with not enough bytes. got %v; want %v", err, io.EOF)
}
})
})
t.Run("ReadCodeWithScope", func(t *testing.T) {
codeWithScope := []byte{
0x11, 0x00, 0x00, 0x00, // total length
0x4, 0x00, 0x00, 0x00, // string length
'f', 'o', 'o', 0x00, // string
0x05, 0x00, 0x00, 0x00, 0x00, // document
}
mismatchCodeWithScope := []byte{
0x11, 0x00, 0x00, 0x00, // total length
0x4, 0x00, 0x00, 0x00, // string length
'f', 'o', 'o', 0x00, // string
0x07, 0x00, 0x00, 0x00, // document
0x0A, 0x00, // null element, empty key
0x00, // document end
}
invalidCodeWithScope := []byte{
0x7, 0x00, 0x00, 0x00, // total length
0x0, 0x00, 0x00, 0x00, // string length = 0
0x05, 0x00, 0x00, 0x00, 0x00, // document
}
testCases := []struct {
name string
data []byte
err error
vType Type
}{
{
"incorrect type",
[]byte{},
(&valueReader{stack: []vrState{{vType: TypeEmbeddedDocument}}, frame: 0}).typeError(TypeCodeWithScope),
TypeEmbeddedDocument,
},
{
"total length not enough bytes",
[]byte{},
io.EOF,
TypeCodeWithScope,
},
{
"string length not enough bytes",
codeWithScope[:4],
io.EOF,
TypeCodeWithScope,
},
{
"not enough string bytes",
codeWithScope[:8],
io.EOF,
TypeCodeWithScope,
},
{
"document length not enough bytes",
codeWithScope[:12],
io.EOF,
TypeCodeWithScope,
},
{
"length mismatch",
mismatchCodeWithScope,
fmt.Errorf("length of CodeWithScope does not match lengths of components; total: %d; components: %d", 17, 19),
TypeCodeWithScope,
},
{
"invalid strLength",
invalidCodeWithScope,
fmt.Errorf("invalid string length: %d", 0),
TypeCodeWithScope,
},
}
for _, tc := range testCases {
t.Run(tc.name, func(t *testing.T) {
vr := &valueReader{
r: bufio.NewReader(bytes.NewReader(tc.data)),
stack: []vrState{
{mode: mTopLevel},
{
mode: mElement,
vType: tc.vType,
},
},
frame: 1,
}
_, _, err := vr.ReadCodeWithScope()
if !errequal(t, err, tc.err) {
t.Errorf("Returned errors do not match. got %v; want %v", err, tc.err)
}
})
}
t.Run("success", func(t *testing.T) {
vr := &valueReader{
offset: 4,
r: bufio.NewReader(bytes.NewReader(codeWithScope)),
stack: []vrState{
{mode: mTopLevel},
{mode: mElement, vType: TypeCodeWithScope},
},
frame: 1,
}
code, _, err := vr.ReadCodeWithScope()
noerr(t, err)
if code != "foo" {
t.Errorf("Code does not match. got %s; want %s", code, "foo")
}
if len(vr.stack) != 3 {
t.Errorf("Incorrect number of stack frames. got %d; want %d", len(vr.stack), 3)
}
if vr.stack[2].mode != mCodeWithScope {
t.Errorf("Incorrect mode set. got %v; want %v", vr.stack[2].mode, mDocument)
}
if vr.stack[2].end != 21 {
t.Errorf("End of scope is not correct. got %d; want %d", vr.stack[2].end, 21)
}
if vr.offset != 20 {
t.Errorf("Offset not incremented correctly. got %d; want %d", vr.offset, 20)
}
})
})
t.Run("ReadDBPointer", func(t *testing.T) {
testCases := []struct {
name string
data []byte
ns string
oid ObjectID
err error
vType Type
}{
{
"incorrect type",
[]byte{},
"",
ObjectID{},
(&valueReader{stack: []vrState{{vType: TypeEmbeddedDocument}}, frame: 0}).typeError(TypeDBPointer),
TypeEmbeddedDocument,
},
{
"length too short",
[]byte{},
"",
ObjectID{},
io.EOF,
TypeDBPointer,
},
{
"not enough bytes for namespace",
[]byte{0x04, 0x00, 0x00, 0x00},
"",
ObjectID{},
io.EOF,
TypeDBPointer,
},
{
"not enough bytes for objectID",
[]byte{0x04, 0x00, 0x00, 0x00, 'f', 'o', 'o', 0x00},
"",
ObjectID{},
io.EOF,
TypeDBPointer,
},
{
"success",
[]byte{
0x04, 0x00, 0x00, 0x00, 'f', 'o', 'o', 0x00,
0x01, 0x02, 0x03, 0x04, 0x05, 0x06, 0x07, 0x08, 0x09, 0x0A, 0x0B, 0x0C,
},
"foo",
ObjectID{0x01, 0x02, 0x03, 0x04, 0x05, 0x06, 0x07, 0x08, 0x09, 0x0A, 0x0B, 0x0C},
nil,
TypeDBPointer,
},
}
for _, tc := range testCases {
t.Run(tc.name, func(t *testing.T) {
vr := &valueReader{
r: bufio.NewReader(bytes.NewReader(tc.data)),
stack: []vrState{
{mode: mTopLevel},
{
mode: mElement,
vType: tc.vType,
},
},
frame: 1,
}
ns, oid, err := vr.ReadDBPointer()
if !errequal(t, err, tc.err) {
t.Errorf("Returned errors do not match. got %v; want %v", err, tc.err)
}
if ns != tc.ns {
t.Errorf("Incorrect namespace returned. got %v; want %v", ns, tc.ns)
}
if oid != tc.oid {
t.Errorf("ObjectIDs did not match. got %v; want %v", oid, tc.oid)
}
})
}
})
t.Run("ReadDateTime", func(t *testing.T) {
testCases := []struct {
name string
data []byte
dt int64
err error
vType Type
}{
{
"incorrect type",
[]byte{},
0,
(&valueReader{stack: []vrState{{vType: TypeEmbeddedDocument}}, frame: 0}).typeError(TypeDateTime),
TypeEmbeddedDocument,
},
{
"length too short",
[]byte{},
0,
io.EOF,
TypeDateTime,
},
{
"success",
[]byte{0xFF, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00},
255,
nil,
TypeDateTime,
},
}
for _, tc := range testCases {
t.Run(tc.name, func(t *testing.T) {
vr := &valueReader{
r: bufio.NewReader(bytes.NewReader(tc.data)),
stack: []vrState{
{mode: mTopLevel},
{
mode: mElement,
vType: tc.vType,
},
},
frame: 1,
}
dt, err := vr.ReadDateTime()
if !errequal(t, err, tc.err) {
t.Errorf("Returned errors do not match. got %v; want %v", err, tc.err)
}
if dt != tc.dt {
t.Errorf("Incorrect datetime returned. got %d; want %d", dt, tc.dt)
}
})
}
})
t.Run("ReadDecimal128", func(t *testing.T) {
testCases := []struct {
name string
data []byte
dc128 Decimal128
err error
vType Type
}{
{
"incorrect type",
[]byte{},
Decimal128{},
(&valueReader{stack: []vrState{{vType: TypeEmbeddedDocument}}, frame: 0}).typeError(TypeDecimal128),
TypeEmbeddedDocument,
},
{
"length too short",
[]byte{},
Decimal128{},
io.EOF,
TypeDecimal128,
},
{
"success",
[]byte{
0xFF, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, // Low
0x00, 0xFF, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, // High
},
NewDecimal128(65280, 255),
nil,
TypeDecimal128,
},
}
for _, tc := range testCases {
t.Run(tc.name, func(t *testing.T) {
vr := &valueReader{
r: bufio.NewReader(bytes.NewReader(tc.data)),
stack: []vrState{
{mode: mTopLevel},
{
mode: mElement,
vType: tc.vType,
},
},
frame: 1,
}
dc128, err := vr.ReadDecimal128()
if !errequal(t, err, tc.err) {
t.Errorf("Returned errors do not match. got %v; want %v", err, tc.err)
}
gotHigh, gotLow := dc128.GetBytes()
wantHigh, wantLow := tc.dc128.GetBytes()
if gotHigh != wantHigh {
t.Errorf("Retuired high byte does not match. got %d; want %d", gotHigh, wantHigh)
}
if gotLow != wantLow {
t.Errorf("Returned low byte does not match. got %d; want %d", gotLow, wantLow)
}
})
}
})
t.Run("ReadDouble", func(t *testing.T) {
testCases := []struct {
name string
data []byte
double float64
err error
vType Type
}{
{
"incorrect type",
[]byte{},
0,
(&valueReader{stack: []vrState{{vType: TypeEmbeddedDocument}}, frame: 0}).typeError(TypeDouble),
TypeEmbeddedDocument,
},
{
"length too short",
[]byte{},
0,
io.EOF,
TypeDouble,
},
{
"success",
[]byte{0xFF, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00},
math.Float64frombits(255),
nil,
TypeDouble,
},
}
for _, tc := range testCases {
t.Run(tc.name, func(t *testing.T) {
vr := &valueReader{
r: bufio.NewReader(bytes.NewReader(tc.data)),
stack: []vrState{
{mode: mTopLevel},
{
mode: mElement,
vType: tc.vType,
},
},
frame: 1,
}
double, err := vr.ReadDouble()
if !errequal(t, err, tc.err) {
t.Errorf("Returned errors do not match. got %v; want %v", err, tc.err)
}
if double != tc.double {
t.Errorf("Incorrect double returned. got %f; want %f", double, tc.double)
}
})
}
})
t.Run("ReadInt32", func(t *testing.T) {
testCases := []struct {
name string
data []byte
i32 int32
err error
vType Type
}{
{
"incorrect type",
[]byte{},
0,
(&valueReader{stack: []vrState{{vType: TypeEmbeddedDocument}}, frame: 0}).typeError(TypeInt32),
TypeEmbeddedDocument,
},
{
"length too short",
[]byte{},
0,
io.EOF,
TypeInt32,
},
{
"success",
[]byte{0xFF, 0x00, 0x00, 0x00},
255,
nil,
TypeInt32,
},
}
for _, tc := range testCases {
t.Run(tc.name, func(t *testing.T) {
vr := &valueReader{
r: bufio.NewReader(bytes.NewReader(tc.data)),
stack: []vrState{
{mode: mTopLevel},
{
mode: mElement,
vType: tc.vType,
},
},
frame: 1,
}
i32, err := vr.ReadInt32()
if !errequal(t, err, tc.err) {
t.Errorf("Returned errors do not match. got %v; want %v", err, tc.err)
}
if i32 != tc.i32 {
t.Errorf("Incorrect int32 returned. got %d; want %d", i32, tc.i32)
}
})
}
})
t.Run("ReadInt64", func(t *testing.T) {
testCases := []struct {
name string
data []byte
i64 int64
err error
vType Type
}{
{
"incorrect type",
[]byte{},
0,
(&valueReader{stack: []vrState{{vType: TypeEmbeddedDocument}}, frame: 0}).typeError(TypeInt64),
TypeEmbeddedDocument,
},
{
"length too short",
[]byte{},
0,
io.EOF,
TypeInt64,
},
{
"success",
[]byte{0xFF, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00},
255,
nil,
TypeInt64,
},
}
for _, tc := range testCases {
t.Run(tc.name, func(t *testing.T) {
vr := &valueReader{
r: bufio.NewReader(bytes.NewReader(tc.data)),
stack: []vrState{
{mode: mTopLevel},
{
mode: mElement,
vType: tc.vType,
},
},
frame: 1,
}
i64, err := vr.ReadInt64()
if !errequal(t, err, tc.err) {
t.Errorf("Returned errors do not match. got %v; want %v", err, tc.err)
}
if i64 != tc.i64 {
t.Errorf("Incorrect int64 returned. got %d; want %d", i64, tc.i64)
}
})
}
})
t.Run("ReadJavascript/ReadString/ReadSymbol", func(t *testing.T) {
testCases := []struct {
name string
data []byte
fn func(*valueReader) (string, error)
css string // code, string, symbol :P
err error
vType Type
}{
{
"ReadJavascript/incorrect type",
[]byte{},
(*valueReader).ReadJavascript,
"",
(&valueReader{stack: []vrState{{vType: TypeEmbeddedDocument}}, frame: 0}).typeError(TypeJavaScript),
TypeEmbeddedDocument,
},
{
"ReadString/incorrect type",
[]byte{},
(*valueReader).ReadString,
"",
(&valueReader{stack: []vrState{{vType: TypeEmbeddedDocument}}, frame: 0}).typeError(TypeString),
TypeEmbeddedDocument,
},
{
"ReadSymbol/incorrect type",
[]byte{},
(*valueReader).ReadSymbol,
"",
(&valueReader{stack: []vrState{{vType: TypeEmbeddedDocument}}, frame: 0}).typeError(TypeSymbol),
TypeEmbeddedDocument,
},
{
"ReadJavascript/length too short",
[]byte{},
(*valueReader).ReadJavascript,
"",
io.EOF,
TypeJavaScript,
},
{
"ReadString/length too short",
[]byte{},
(*valueReader).ReadString,
"",
io.EOF,
TypeString,
},
{
"ReadString/long - length too short",
append([]byte{0x40, 0x27, 0x00, 0x00}, testcstring...),
(*valueReader).ReadString,
"",
io.ErrUnexpectedEOF,
TypeString,
},
{
"ReadSymbol/length too short",
[]byte{},
(*valueReader).ReadSymbol,
"",
io.EOF,
TypeSymbol,
},
{
"ReadJavascript/incorrect end byte",
[]byte{0x01, 0x00, 0x00, 0x00, 0x05},
(*valueReader).ReadJavascript,
"",
fmt.Errorf("string does not end with null byte, but with %v", 0x05),
TypeJavaScript,
},
{
"ReadString/incorrect end byte",
[]byte{0x01, 0x00, 0x00, 0x00, 0x05},
(*valueReader).ReadString,
"",
fmt.Errorf("string does not end with null byte, but with %v", 0x05),
TypeString,
},
{
"ReadString/long - incorrect end byte",
append([]byte{0x35, 0x27, 0x00, 0x00}, testcstring[:len(testcstring)-1]...),
(*valueReader).ReadString,
"",
fmt.Errorf("string does not end with null byte, but with %v", 0x20),
TypeString,
},
{
"ReadSymbol/incorrect end byte",
[]byte{0x01, 0x00, 0x00, 0x00, 0x05},
(*valueReader).ReadSymbol,
"",
fmt.Errorf("string does not end with null byte, but with %v", 0x05),
TypeSymbol,
},
{
"ReadJavascript/success",
[]byte{0x04, 0x00, 0x00, 0x00, 'f', 'o', 'o', 0x00},
(*valueReader).ReadJavascript,
"foo",
nil,
TypeJavaScript,
},
{
"ReadString/success",
[]byte{0x04, 0x00, 0x00, 0x00, 'f', 'o', 'o', 0x00},
(*valueReader).ReadString,
"foo",
nil,
TypeString,
},
{
"ReadString/long - success",
append([]byte{0x36, 0x27, 0x00, 0x00}, testcstring...),
(*valueReader).ReadString,
string(testcstring[:len(testcstring)-1]),
nil,
TypeString,
},
{
"ReadSymbol/success",
[]byte{0x04, 0x00, 0x00, 0x00, 'f', 'o', 'o', 0x00},
(*valueReader).ReadSymbol,
"foo",
nil,
TypeSymbol,
},
}
for _, tc := range testCases {
t.Run(tc.name, func(t *testing.T) {
vr := &valueReader{
r: bufio.NewReader(bytes.NewReader(tc.data)),
stack: []vrState{
{mode: mTopLevel},
{
mode: mElement,
vType: tc.vType,
},
},
frame: 1,
}
css, err := tc.fn(vr)
if !errequal(t, err, tc.err) {
t.Errorf("Returned errors do not match. got %v; want %v", err, tc.err)
}
if css != tc.css {
t.Errorf("Incorrect (JavaScript,String,Symbol) returned. got %s; want %s", css, tc.css)
}
})
}
})
t.Run("ReadMaxKey/ReadMinKey/ReadNull/ReadUndefined", func(t *testing.T) {
testCases := []struct {
name string
fn func(*valueReader) error
err error
vType Type
}{
{
"ReadMaxKey/incorrect type",
(*valueReader).ReadMaxKey,
(&valueReader{stack: []vrState{{vType: TypeEmbeddedDocument}}, frame: 0}).typeError(TypeMaxKey),
TypeEmbeddedDocument,
},
{
"ReadMaxKey/success",
(*valueReader).ReadMaxKey,
nil,
TypeMaxKey,
},
{
"ReadMinKey/incorrect type",
(*valueReader).ReadMinKey,
(&valueReader{stack: []vrState{{vType: TypeEmbeddedDocument}}, frame: 0}).typeError(TypeMinKey),
TypeEmbeddedDocument,
},
{
"ReadMinKey/success",
(*valueReader).ReadMinKey,
nil,
TypeMinKey,
},
{
"ReadNull/incorrect type",
(*valueReader).ReadNull,
(&valueReader{stack: []vrState{{vType: TypeEmbeddedDocument}}, frame: 0}).typeError(TypeNull),
TypeEmbeddedDocument,
},
{
"ReadNull/success",
(*valueReader).ReadNull,
nil,
TypeNull,
},
{
"ReadUndefined/incorrect type",
(*valueReader).ReadUndefined,
(&valueReader{stack: []vrState{{vType: TypeEmbeddedDocument}}, frame: 0}).typeError(TypeUndefined),
TypeEmbeddedDocument,
},
{
"ReadUndefined/success",
(*valueReader).ReadUndefined,
nil,
TypeUndefined,
},
}
for _, tc := range testCases {
t.Run(tc.name, func(t *testing.T) {
vr := &valueReader{
stack: []vrState{
{mode: mTopLevel},
{
mode: mElement,
vType: tc.vType,
},
},
frame: 1,
}
err := tc.fn(vr)
if !errequal(t, err, tc.err) {
t.Errorf("Returned errors do not match. got %v; want %v", err, tc.err)
}
})
}
})
t.Run("ReadObjectID", func(t *testing.T) {
testCases := []struct {
name string
data []byte
oid ObjectID
err error
vType Type
}{
{
"incorrect type",
[]byte{},
ObjectID{},
(&valueReader{stack: []vrState{{vType: TypeEmbeddedDocument}}, frame: 0}).typeError(TypeObjectID),
TypeEmbeddedDocument,
},
{
"not enough bytes for objectID",
[]byte{},
ObjectID{},
io.EOF,
TypeObjectID,
},
{
"success",
[]byte{0x01, 0x02, 0x03, 0x04, 0x05, 0x06, 0x07, 0x08, 0x09, 0x0A, 0x0B, 0x0C},
ObjectID{0x01, 0x02, 0x03, 0x04, 0x05, 0x06, 0x07, 0x08, 0x09, 0x0A, 0x0B, 0x0C},
nil,
TypeObjectID,
},
}
for _, tc := range testCases {
t.Run(tc.name, func(t *testing.T) {
vr := &valueReader{
r: bufio.NewReader(bytes.NewReader(tc.data)),
stack: []vrState{
{mode: mTopLevel},
{
mode: mElement,
vType: tc.vType,
},
},
frame: 1,
}
oid, err := vr.ReadObjectID()
if !errequal(t, err, tc.err) {
t.Errorf("Returned errors do not match. got %v; want %v", err, tc.err)
}
if oid != tc.oid {
t.Errorf("ObjectIDs did not match. got %v; want %v", oid, tc.oid)
}
})
}
})
t.Run("ReadRegex", func(t *testing.T) {
testCases := []struct {
name string
data []byte
pattern string
options string
err error
vType Type
}{
{
"incorrect type",
[]byte{},
"",
"",
(&valueReader{stack: []vrState{{vType: TypeEmbeddedDocument}}, frame: 0}).typeError(TypeRegex),
TypeEmbeddedDocument,
},
{
"length too short",
[]byte{},
"",
"",
io.EOF,
TypeRegex,
},
{
"not enough bytes for options",
[]byte{'f', 'o', 'o', 0x00},
"",
"",
io.EOF,
TypeRegex,
},
{
"success",
[]byte{'f', 'o', 'o', 0x00, 'b', 'a', 'r', 0x00},
"foo",
"bar",
nil,
TypeRegex,
},
}
for _, tc := range testCases {
t.Run(tc.name, func(t *testing.T) {
vr := &valueReader{
r: bufio.NewReader(bytes.NewReader(tc.data)),
stack: []vrState{
{mode: mTopLevel},
{
mode: mElement,
vType: tc.vType,
},
},
frame: 1,
}
pattern, options, err := vr.ReadRegex()
if !errequal(t, err, tc.err) {
t.Errorf("Returned errors do not match. got %v; want %v", err, tc.err)
}
if pattern != tc.pattern {
t.Errorf("Incorrect pattern returned. got %s; want %s", pattern, tc.pattern)
}
if options != tc.options {
t.Errorf("Incorrect options returned. got %s; want %s", options, tc.options)
}
})
}
})
t.Run("ReadTimestamp", func(t *testing.T) {
testCases := []struct {
name string
data []byte
ts uint32
incr uint32
err error
vType Type
}{
{
"incorrect type",
[]byte{},
0,
0,
(&valueReader{stack: []vrState{{vType: TypeEmbeddedDocument}}, frame: 0}).typeError(TypeTimestamp),
TypeEmbeddedDocument,
},
{
"not enough bytes for increment",
[]byte{},
0,
0,
io.EOF,
TypeTimestamp,
},
{
"not enough bytes for timestamp",
[]byte{0x01, 0x02, 0x03, 0x04},
0,
0,
io.EOF,
TypeTimestamp,
},
{
"success",
[]byte{0xFF, 0x00, 0x00, 0x00, 0x00, 0x01, 0x00, 0x00},
256,
255,
nil,
TypeTimestamp,
},
}
for _, tc := range testCases {
t.Run(tc.name, func(t *testing.T) {
vr := &valueReader{
r: bufio.NewReader(bytes.NewReader(tc.data)),
stack: []vrState{
{mode: mTopLevel},
{
mode: mElement,
vType: tc.vType,
},
},
frame: 1,
}
ts, incr, err := vr.ReadTimestamp()
if !errequal(t, err, tc.err) {
t.Errorf("Returned errors do not match. got %v; want %v", err, tc.err)
}
if ts != tc.ts {
t.Errorf("Incorrect timestamp returned. got %d; want %d", ts, tc.ts)
}
if incr != tc.incr {
t.Errorf("Incorrect increment returned. got %d; want %d", incr, tc.incr)
}
})
}
})
t.Run("ReadBytes & Skip", func(t *testing.T) {
index, docb := bsoncore.ReserveLength(nil)
docb = bsoncore.AppendNullElement(docb, "foobar")
docb = append(docb, 0x00)
docb = bsoncore.UpdateLength(docb, index, int32(len(docb)))
cwsbytes := bsoncore.AppendCodeWithScope(nil, "var hellow = world;", docb)
strbytes := []byte{0x04, 0x00, 0x00, 0x00, 'f', 'o', 'o', 0x00}
testCases := []struct {
name string
t Type
data []byte
err error
offset int64
startingOffset int64
}{
{
"Array/invalid length",
TypeArray,
[]byte{0x01, 0x02, 0x03},
io.EOF, 0, 0,
},
{
"Array/not enough bytes",
TypeArray,
[]byte{0x0F, 0x00, 0x00, 0x00, 0x01, 0x02, 0x03},
io.EOF, 0, 0,
},
{
"Array/success",
TypeArray,
[]byte{0x08, 0x00, 0x00, 0x00, 0x0A, '1', 0x00, 0x00},
nil, 8, 0,
},
{
"EmbeddedDocument/invalid length",
TypeEmbeddedDocument,
[]byte{0x01, 0x02, 0x03},
io.EOF, 0, 0,
},
{
"EmbeddedDocument/not enough bytes",
TypeEmbeddedDocument,
[]byte{0x0F, 0x00, 0x00, 0x00, 0x01, 0x02, 0x03},
io.EOF, 0, 0,
},
{
"EmbeddedDocument/success",
TypeEmbeddedDocument,
[]byte{0x08, 0x00, 0x00, 0x00, 0x0A, 'A', 0x00, 0x00},
nil, 8, 0,
},
{
"CodeWithScope/invalid length",
TypeCodeWithScope,
[]byte{0x01, 0x02, 0x03},
io.EOF, 0, 0,
},
{
"CodeWithScope/not enough bytes",
TypeCodeWithScope,
[]byte{0x0F, 0x00, 0x00, 0x00, 0x01, 0x02, 0x03},
io.EOF, 0, 0,
},
{
"CodeWithScope/success",
TypeCodeWithScope,
cwsbytes,
nil, 41, 0,
},
{
"Binary/invalid length",
TypeBinary,
[]byte{0x01, 0x02, 0x03},
io.EOF, 0, 0,
},
{
"Binary/not enough bytes",
TypeBinary,
[]byte{0x0F, 0x00, 0x00, 0x00, 0x01, 0x02, 0x03},
io.EOF, 0, 0,
},
{
"Binary/success",
TypeBinary,
[]byte{0x03, 0x00, 0x00, 0x00, 0x00, 0x01, 0x02, 0x03},
nil, 8, 0,
},
{
"Boolean/invalid length",
TypeBoolean,
[]byte{},
io.EOF, 0, 0,
},
{
"Boolean/success",
TypeBoolean,
[]byte{0x01},
nil, 1, 0,
},
{
"DBPointer/invalid length",
TypeDBPointer,
[]byte{0x01, 0x02, 0x03},
io.EOF, 0, 0,
},
{
"DBPointer/not enough bytes",
TypeDBPointer,
[]byte{0x0F, 0x00, 0x00, 0x00, 0x01, 0x02, 0x03},
io.EOF, 0, 0,
},
{
"DBPointer/success",
TypeDBPointer,
[]byte{0x01, 0x00, 0x00, 0x00, 0x00, 0x01, 0x02, 0x03, 0x04, 0x05, 0x06, 0x07, 0x08, 0x09, 0x0A, 0x0B, 0x0C},
nil, 17, 0,
},
{"DBPointer/not enough bytes", TypeDateTime, []byte{0x01, 0x02, 0x03, 0x04}, io.EOF, 0, 0},
{"DBPointer/success", TypeDateTime, []byte{0x01, 0x02, 0x03, 0x04, 0x05, 0x06, 0x07, 0x08}, nil, 8, 0},
{"Double/not enough bytes", TypeDouble, []byte{0x01, 0x02, 0x03, 0x04}, io.EOF, 0, 0},
{"Double/success", TypeDouble, []byte{0x01, 0x02, 0x03, 0x04, 0x05, 0x06, 0x07, 0x08}, nil, 8, 0},
{"Int64/not enough bytes", TypeInt64, []byte{0x01, 0x02, 0x03, 0x04}, io.EOF, 0, 0},
{"Int64/success", TypeInt64, []byte{0x01, 0x02, 0x03, 0x04, 0x05, 0x06, 0x07, 0x08}, nil, 8, 0},
{"Timestamp/not enough bytes", TypeTimestamp, []byte{0x01, 0x02, 0x03, 0x04}, io.EOF, 0, 0},
{"Timestamp/success", TypeTimestamp, []byte{0x01, 0x02, 0x03, 0x04, 0x05, 0x06, 0x07, 0x08}, nil, 8, 0},
{
"Decimal128/not enough bytes",
TypeDecimal128,
[]byte{0x01, 0x02, 0x03, 0x04},
io.EOF, 0, 0,
},
{
"Decimal128/success",
TypeDecimal128,
[]byte{0x01, 0x02, 0x03, 0x04, 0x05, 0x06, 0x07, 0x08, 0x09, 0x0A, 0x0B, 0x0C, 0x0D, 0x0E, 0x0F, 0x10},
nil, 16, 0,
},
{"Int32/not enough bytes", TypeInt32, []byte{0x01, 0x02}, io.EOF, 0, 0},
{"Int32/success", TypeInt32, []byte{0x01, 0x02, 0x03, 0x04}, nil, 4, 0},
{"Javascript/invalid length", TypeJavaScript, strbytes[:2], io.EOF, 0, 0},
{"Javascript/not enough bytes", TypeJavaScript, strbytes[:5], io.EOF, 0, 0},
{"Javascript/success", TypeJavaScript, strbytes, nil, 8, 0},
{"String/invalid length", TypeString, strbytes[:2], io.EOF, 0, 0},
{"String/not enough bytes", TypeString, strbytes[:5], io.EOF, 0, 0},
{"String/success", TypeString, strbytes, nil, 8, 0},
{"Symbol/invalid length", TypeSymbol, strbytes[:2], io.EOF, 0, 0},
{"Symbol/not enough bytes", TypeSymbol, strbytes[:5], io.EOF, 0, 0},
{"Symbol/success", TypeSymbol, strbytes, nil, 8, 0},
{"MaxKey/success", TypeMaxKey, []byte{}, nil, 0, 0},
{"MinKey/success", TypeMinKey, []byte{}, nil, 0, 0},
{"Null/success", TypeNull, []byte{}, nil, 0, 0},
{"Undefined/success", TypeUndefined, []byte{}, nil, 0, 0},
{
"ObjectID/not enough bytes",
TypeObjectID,
[]byte{0x01, 0x02, 0x03, 0x04},
io.EOF, 0, 0,
},
{
"ObjectID/success",
TypeObjectID,
[]byte{0x01, 0x02, 0x03, 0x04, 0x05, 0x06, 0x07, 0x08, 0x09, 0x0A, 0x0B, 0x0C},
nil, 12, 0,
},
{
"Regex/not enough bytes (first string)",
TypeRegex,
[]byte{'f', 'o', 'o'},
io.EOF, 0, 0,
},
{
"Regex/not enough bytes (second string)",
TypeRegex,
[]byte{'f', 'o', 'o', 0x00, 'b', 'a', 'r'},
io.EOF, 0, 0,
},
{
"Regex/success",
TypeRegex,
[]byte{0x00, 0x00, 0x00, 'f', 'o', 'o', 0x00, 'i', 0x00},
nil, 9, 3,
},
{
"Unknown Type",
Type(0),
nil,
fmt.Errorf("attempted to read bytes of unknown BSON type %v", Type(0)), 0, 0,
},
}
for _, tc := range testCases {
t.Run(tc.name, func(t *testing.T) {
const startingEnd = 64
t.Run("Skip", func(t *testing.T) {
vr := &valueReader{
r: bufio.NewReader(bytes.NewReader(tc.data[tc.startingOffset:tc.offset])),
stack: []vrState{
{mode: mTopLevel, end: startingEnd},
{mode: mElement, vType: tc.t},
},
frame: 1,
offset: tc.startingOffset,
}
err := vr.Skip()
if !errequal(t, err, tc.err) {
t.Errorf("Did not receive expected error; got %v; want %v", err, tc.err)
}
if tc.err == nil {
offset := startingEnd - vr.stack[0].end
if offset != tc.offset {
t.Errorf("Offset not set at correct position; got %d; want %d", offset, tc.offset)
}
}
})
t.Run("ReadBytes", func(t *testing.T) {
vr := &valueReader{
r: bufio.NewReader(bytes.NewReader(tc.data[tc.startingOffset:tc.offset])),
stack: []vrState{
{mode: mTopLevel, end: startingEnd},
{mode: mElement, vType: tc.t},
},
frame: 1,
offset: tc.startingOffset,
}
_, got, err := vr.readValueBytes(nil)
if !errequal(t, err, tc.err) {
t.Errorf("Did not receive expected error; got %v; want %v", err, tc.err)
}
if tc.err == nil {
offset := startingEnd - vr.stack[0].end
if offset != tc.offset {
t.Errorf("Offset not set at correct position; got %d; want %d", vr.offset, tc.offset)
}
}
if tc.err == nil && !bytes.Equal(got, tc.data[tc.startingOffset:]) {
t.Errorf("Did not receive expected bytes. got %v; want %v", got, tc.data[tc.startingOffset:])
}
})
})
}
t.Run("ReadValueBytes/Top Level Doc", func(t *testing.T) {
testCases := []struct {
name string
want []byte
wantType Type
wantErr error
}{
{
"success",
bsoncore.BuildDocument(nil, bsoncore.AppendDoubleElement(nil, "pi", 3.14159)),
Type(0),
nil,
},
{
"wrong length",
[]byte{0x01, 0x02, 0x03},
Type(0),
io.EOF,
},
{
"append bytes",
[]byte{0x01, 0x02, 0x03, 0x04},
Type(0),
io.ErrUnexpectedEOF,
},
}
for _, tc := range testCases {
tc := tc
t.Run(tc.name, func(t *testing.T) {
t.Parallel()
vr := &valueReader{
r: bufio.NewReader(bytes.NewReader(tc.want)),
stack: []vrState{
{mode: mTopLevel},
},
frame: 0,
}
gotType, got, gotErr := vr.readValueBytes(nil)
if !errors.Is(gotErr, tc.wantErr) {
t.Errorf("Did not receive expected error. got %v; want %v", gotErr, tc.wantErr)
}
if tc.wantErr == nil && gotType != tc.wantType {
t.Errorf("Did not receive expected type. got %v; want %v", gotType, tc.wantType)
}
if tc.wantErr == nil && !bytes.Equal(got, tc.want) {
t.Errorf("Did not receive expected bytes. got %v; want %v", got, tc.want)
}
})
}
})
})
t.Run("invalid transition", func(t *testing.T) {
t.Run("Skip", func(t *testing.T) {
vr := &valueReader{stack: []vrState{{mode: mTopLevel}}}
wanterr := (&valueReader{stack: []vrState{{mode: mTopLevel}}}).invalidTransitionErr(0, "Skip", []mode{mElement, mValue})
goterr := vr.Skip()
if !cmp.Equal(goterr, wanterr, cmp.Comparer(assert.CompareErrors)) {
t.Errorf("Expected correct invalid transition error. got %v; want %v", goterr, wanterr)
}
})
})
t.Run("ReadBytes", func(t *testing.T) {
vr := &valueReader{stack: []vrState{{mode: mTopLevel}, {mode: mDocument}}, frame: 1}
wanterr := (&valueReader{stack: []vrState{{mode: mTopLevel}, {mode: mDocument}}, frame: 1}).
invalidTransitionErr(0, "ReadValueBytes", []mode{mElement, mValue})
_, _, goterr := vr.readValueBytes(nil)
if !cmp.Equal(goterr, wanterr, cmp.Comparer(assert.CompareErrors)) {
t.Errorf("Expected correct invalid transition error. got %v; want %v", goterr, wanterr)
}
})
}
func errequal(t *testing.T, err1, err2 error) bool {
t.Helper()
if err1 == nil && err2 == nil { // If they are both nil, they are equal
return true
}
if err1 == nil || err2 == nil { // If only one is nil, they are not equal
return false
}
if errors.Is(err1, err2) { // They are the same error, they are equal
return true
}
if err1.Error() == err2.Error() { // They string formats match, they are equal
return true
}
return false
}