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

1113 lines
25 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 (
"bytes"
"encoding/json"
"errors"
"fmt"
"net/url"
"reflect"
"testing"
"time"
"gitea.psichedelico.com/go/bson/internal/assert"
"gitea.psichedelico.com/go/bson/x/bsonx/bsoncore"
"github.com/google/go-cmp/cmp"
)
func bytesFromDoc(doc interface{}) []byte {
b, err := Marshal(doc)
if err != nil {
panic(fmt.Errorf("Couldn't marshal BSON document: %w", err))
}
return b
}
func TestPrimitiveValueEncoders(t *testing.T) {
t.Parallel()
var wrong = func(string, string) string { return "wrong" }
type subtest struct {
name string
val interface{}
ectx *EncodeContext
llvrw *valueReaderWriter
invoke invoked
err error
}
testCases := []struct {
name string
ve ValueEncoder
subtests []subtest
}{
{
"RawValueEncodeValue",
ValueEncoderFunc(rawValueEncodeValue),
[]subtest{
{
"wrong type",
wrong,
nil,
nil,
nothing,
ValueEncoderError{
Name: "RawValueEncodeValue",
Types: []reflect.Type{tRawValue},
Received: reflect.ValueOf(wrong),
},
},
{
"RawValue/success",
RawValue{Type: TypeDouble, Value: bsoncore.AppendDouble(nil, 3.14159)},
nil,
nil,
writeDouble,
nil,
},
{
"RawValue Type is zero with non-zero value",
RawValue{
Type: 0x00,
Value: bsoncore.AppendDouble(nil, 3.14159),
},
nil,
nil,
nothing,
fmt.Errorf("the RawValue Type specifies an invalid BSON type: 0x0"),
},
{
"RawValue Type is invalid",
RawValue{
Type: 0x8F,
Value: bsoncore.AppendDouble(nil, 3.14159),
},
nil,
nil,
nothing,
fmt.Errorf("the RawValue Type specifies an invalid BSON type: 0x8f"),
},
},
},
{
"RawEncodeValue",
ValueEncoderFunc(rawEncodeValue),
[]subtest{
{
"wrong type",
wrong,
nil,
nil,
nothing,
ValueEncoderError{Name: "RawEncodeValue", Types: []reflect.Type{tRaw}, Received: reflect.ValueOf(wrong)},
},
{
"WriteDocument Error",
Raw{},
nil,
&valueReaderWriter{Err: errors.New("wd error"), ErrAfter: writeDocument},
writeDocument,
errors.New("wd error"),
},
{
"Raw.Elements Error",
Raw{0xFF, 0x00, 0x00, 0x00, 0x00},
nil,
&valueReaderWriter{},
writeDocument,
errors.New("length read exceeds number of bytes available. length=5 bytes=255"),
},
{
"WriteDocumentElement Error",
Raw(bytesFromDoc(D{{"foo", nil}})),
nil,
&valueReaderWriter{Err: errors.New("wde error"), ErrAfter: writeDocumentElement},
writeDocumentElement,
errors.New("wde error"),
},
{
"encodeValue error",
Raw(bytesFromDoc(D{{"foo", nil}})),
nil,
&valueReaderWriter{Err: errors.New("ev error"), ErrAfter: writeNull},
writeNull,
errors.New("ev error"),
},
{
"iterator error",
Raw{0x0C, 0x00, 0x00, 0x00, 0x01, 'f', 'o', 'o', 0x00, 0x01, 0x02, 0x03},
nil,
&valueReaderWriter{},
writeDocumentElement,
errors.New("not enough bytes available to read type. bytes=3 type=double"),
},
},
},
}
for _, tc := range testCases {
tc := tc // Capture the range variable
t.Run(tc.name, func(t *testing.T) {
t.Parallel()
for _, subtest := range tc.subtests {
subtest := subtest // Capture the range variable
t.Run(subtest.name, func(t *testing.T) {
t.Parallel()
var ec EncodeContext
if subtest.ectx != nil {
ec = *subtest.ectx
}
llvrw := new(valueReaderWriter)
if subtest.llvrw != nil {
llvrw = subtest.llvrw
}
llvrw.T = t
err := tc.ve.EncodeValue(ec, llvrw, reflect.ValueOf(subtest.val))
if !assert.CompareErrors(err, subtest.err) {
t.Errorf("Errors do not match. got %v; want %v", err, subtest.err)
}
invoked := llvrw.invoked
if !cmp.Equal(invoked, subtest.invoke) {
t.Errorf("Incorrect method invoked. got %v; want %v", invoked, subtest.invoke)
}
})
}
})
}
t.Run("success path", func(t *testing.T) {
t.Parallel()
oid := NewObjectID()
oids := []ObjectID{NewObjectID(), NewObjectID(), NewObjectID()}
var str = new(string)
*str = "bar"
now := time.Now().Truncate(time.Millisecond)
murl, err := url.Parse("https://mongodb.com/random-url?hello=world")
if err != nil {
t.Errorf("Error parsing URL: %v", err)
t.FailNow()
}
decimal128, err := ParseDecimal128("1.5e10")
if err != nil {
t.Errorf("Error parsing decimal128: %v", err)
t.FailNow()
}
testCases := []struct {
name string
value interface{}
b []byte
err error
}{
{
"D to JavaScript",
D{{"a", JavaScript(`function() { var hello = "world"; }`)}},
docToBytes(D{{"a", JavaScript(`function() { var hello = "world"; }`)}}),
nil,
},
{
"D to Symbol",
D{{"a", Symbol("foobarbaz")}},
docToBytes(D{{"a", Symbol("foobarbaz")}}),
nil,
},
{
"omitempty map",
struct {
T map[string]string `bson:",omitempty"`
}{
T: map[string]string{},
},
docToBytes(D{}),
nil,
},
{
"omitempty slice",
struct {
T []struct{} `bson:",omitempty"`
}{
T: []struct{}{},
},
docToBytes(D{}),
nil,
},
{
"omitempty string",
struct {
T string `bson:",omitempty"`
}{
T: "",
},
docToBytes(D{}),
nil,
},
{
"struct{}",
struct {
A bool
B int32
C int64
D uint16
E uint64
F float64
G string
H map[string]string
I []byte
K [2]string
L struct {
M string
}
P Raw
Q ObjectID
T []struct{}
Y json.Number
Z time.Time
AA json.Number
AB *url.URL
AC Decimal128
AD *time.Time
AE testValueMarshaler
AF RawValue
AG *RawValue
AH D
AI *D
AJ *D
}{
A: true,
B: 123,
C: 456,
D: 789,
E: 101112,
F: 3.14159,
G: "Hello, world",
H: map[string]string{"foo": "bar"},
I: []byte{0x01, 0x02, 0x03},
K: [2]string{"baz", "qux"},
L: struct {
M string
}{
M: "foobar",
},
P: Raw{0x05, 0x00, 0x00, 0x00, 0x00},
Q: oid,
T: nil,
Y: json.Number("5"),
Z: now,
AA: json.Number("10.1"),
AB: murl,
AC: decimal128,
AD: &now,
AE: testValueMarshaler{t: TypeString, buf: bsoncore.AppendString(nil, "hello, world")},
AF: RawValue{Type: TypeString, Value: bsoncore.AppendString(nil, "hello, raw value")},
AG: &RawValue{Type: TypeDouble, Value: bsoncore.AppendDouble(nil, 3.14159)},
AH: D{{"foo", "bar"}},
AI: &D{{"pi", 3.14159}},
AJ: nil,
},
docToBytes(D{
{"a", true},
{"b", int32(123)},
{"c", int64(456)},
{"d", int32(789)},
{"e", int64(101112)},
{"f", float64(3.14159)},
{"g", "Hello, world"},
{"h", D{{"foo", "bar"}}},
{"i", Binary{Subtype: 0x00, Data: []byte{0x01, 0x02, 0x03}}},
{"k", A{"baz", "qux"}},
{"l", D{{"m", "foobar"}}},
{"p", D{}},
{"q", oid},
{"t", nil},
{"y", int64(5)},
{"z", DateTime(now.UnixNano() / int64(time.Millisecond))},
{"aa", float64(10.1)},
{"ab", murl.String()},
{"ac", decimal128},
{"ad", DateTime(now.UnixNano() / int64(time.Millisecond))},
{"ae", "hello, world"},
{"af", "hello, raw value"},
{"ag", 3.14159},
{"ah", D{{"foo", "bar"}}},
{"ai", D{{"pi", float64(3.14159)}}},
{"aj", nil},
}),
nil,
},
{
"struct{[]interface{}}",
struct {
A []bool
B []int32
C []int64
D []uint16
E []uint64
F []float64
G []string
H []map[string]string
I [][]byte
K [1][2]string
L []struct {
M string
}
N [][]string
Q []Raw
R []ObjectID
T []struct{}
W []map[string]struct{}
X []map[string]struct{}
Y []map[string]struct{}
Z []time.Time
AA []json.Number
AB []*url.URL
AC []Decimal128
AD []*time.Time
AE []testValueMarshaler
AF []D
AG []*D
}{
A: []bool{true},
B: []int32{123},
C: []int64{456},
D: []uint16{789},
E: []uint64{101112},
F: []float64{3.14159},
G: []string{"Hello, world"},
H: []map[string]string{{"foo": "bar"}},
I: [][]byte{{0x01, 0x02, 0x03}},
K: [1][2]string{{"baz", "qux"}},
L: []struct {
M string
}{
{
M: "foobar",
},
},
N: [][]string{{"foo", "bar"}},
Q: []Raw{{0x05, 0x00, 0x00, 0x00, 0x00}},
R: oids,
T: nil,
W: nil,
X: []map[string]struct{}{}, // Should be empty BSON Array
Y: []map[string]struct{}{{}}, // Should be BSON array with one element, an empty BSON SubDocument
Z: []time.Time{now, now},
AA: []json.Number{json.Number("5"), json.Number("10.1")},
AB: []*url.URL{murl},
AC: []Decimal128{decimal128},
AD: []*time.Time{&now, &now},
AE: []testValueMarshaler{
{t: TypeString, buf: bsoncore.AppendString(nil, "hello")},
{t: TypeString, buf: bsoncore.AppendString(nil, "world")},
},
AF: []D{{{"foo", "bar"}}, {{"hello", "world"}, {"number", 12345}}},
AG: []*D{{{"pi", 3.14159}}, nil},
},
docToBytes(D{
{"a", A{true}},
{"b", A{int32(123)}},
{"c", A{int64(456)}},
{"d", A{int32(789)}},
{"e", A{int64(101112)}},
{"f", A{float64(3.14159)}},
{"g", A{"Hello, world"}},
{"h", A{D{{"foo", "bar"}}}},
{"i", A{Binary{Subtype: 0x00, Data: []byte{0x01, 0x02, 0x03}}}},
{"k", A{A{"baz", "qux"}}},
{"l", A{D{{"m", "foobar"}}}},
{"n", A{A{"foo", "bar"}}},
{"q", A{D{}}},
{"r", A{oids[0], oids[1], oids[2]}},
{"t", nil},
{"w", nil},
{"x", A{}},
{"y", A{D{}}},
{"z", A{
DateTime(now.UnixNano() / int64(time.Millisecond)),
DateTime(now.UnixNano() / int64(time.Millisecond)),
}},
{"aa", A{int64(5), float64(10.10)}},
{"ab", A{murl.String()}},
{"ac", A{decimal128}},
{"ad", A{
DateTime(now.UnixNano() / int64(time.Millisecond)),
DateTime(now.UnixNano() / int64(time.Millisecond)),
}},
{"ae", A{"hello", "world"}},
{"af", A{D{{"foo", "bar"}}, D{{"hello", "world"}, {"number", int32(12345)}}}},
{"ag", A{D{{"pi", float64(3.14159)}}, nil}},
}),
nil,
},
}
for _, tc := range testCases {
tc := tc // Capture the range variable
t.Run(tc.name, func(t *testing.T) {
t.Parallel()
b := make(sliceWriter, 0, 512)
vw := NewDocumentWriter(&b)
enc := NewEncoder(vw)
err := enc.Encode(tc.value)
if !errors.Is(err, tc.err) {
t.Errorf("Did not receive expected error. got %v; want %v", err, tc.err)
}
if diff := cmp.Diff([]byte(b), tc.b); diff != "" {
t.Errorf("Bytes written differ: (-got +want)\n%s", diff)
t.Errorf("Bytes\ngot: %v\nwant:%v\n", b, tc.b)
t.Errorf("Readers\ngot: %v\nwant:%v\n", Raw(b), Raw(tc.b))
}
})
}
})
}
func TestPrimitiveValueDecoders(t *testing.T) {
var wrong = func(string, string) string { return "wrong" }
const cansetreflectiontest = "cansetreflectiontest"
type subtest struct {
name string
val interface{}
dctx *DecodeContext
llvrw *valueReaderWriter
invoke invoked
err error
}
testCases := []struct {
name string
vd ValueDecoder
subtests []subtest
}{
{
"RawValueDecodeValue",
ValueDecoderFunc(rawValueDecodeValue),
[]subtest{
{
"wrong type",
wrong,
nil,
&valueReaderWriter{},
nothing,
ValueDecoderError{
Name: "RawValueDecodeValue",
Types: []reflect.Type{tRawValue},
Received: reflect.ValueOf(wrong),
},
},
{
"ReadValue Error",
RawValue{},
nil,
&valueReaderWriter{
BSONType: TypeBinary,
Err: errors.New("rb error"),
ErrAfter: readBinary,
},
readBinary,
errors.New("rb error"),
},
{
"RawValue/success",
RawValue{Type: TypeBinary, Value: bsoncore.AppendBinary(nil, 0xFF, []byte{0x01, 0x02, 0x03})},
nil,
&valueReaderWriter{
BSONType: TypeBinary,
Return: bsoncore.Value{
Type: bsoncore.TypeBinary,
Data: bsoncore.AppendBinary(nil, 0xFF, []byte{0x01, 0x02, 0x03}),
},
},
readBinary,
nil,
},
},
},
{
"RawDecodeValue",
ValueDecoderFunc(rawDecodeValue),
[]subtest{
{
"wrong type",
wrong,
nil,
&valueReaderWriter{},
nothing,
ValueDecoderError{Name: "RawDecodeValue", Types: []reflect.Type{tRaw}, Received: reflect.ValueOf(wrong)},
},
{
"*Raw is nil",
(*Raw)(nil),
nil,
nil,
nothing,
ValueDecoderError{
Name: "RawDecodeValue",
Types: []reflect.Type{tRaw},
Received: reflect.ValueOf((*Raw)(nil)),
},
},
{
"Copy error",
Raw{},
nil,
&valueReaderWriter{Err: errors.New("copy error"), ErrAfter: readDocument},
readDocument,
errors.New("copy error"),
},
},
},
}
for _, tc := range testCases {
t.Run(tc.name, func(t *testing.T) {
for _, rc := range tc.subtests {
t.Run(rc.name, func(t *testing.T) {
var dc DecodeContext
if rc.dctx != nil {
dc = *rc.dctx
}
llvrw := new(valueReaderWriter)
if rc.llvrw != nil {
llvrw = rc.llvrw
}
llvrw.T = t
if rc.val == cansetreflectiontest { // We're doing a CanSet reflection test
err := tc.vd.DecodeValue(dc, llvrw, reflect.Value{})
if !assert.CompareErrors(err, rc.err) {
t.Errorf("Errors do not match. got %v; want %v", err, rc.err)
}
val := reflect.New(reflect.TypeOf(rc.val)).Elem()
err = tc.vd.DecodeValue(dc, llvrw, val)
if !assert.CompareErrors(err, rc.err) {
t.Errorf("Errors do not match. got %v; want %v", err, rc.err)
}
return
}
var val reflect.Value
if rtype := reflect.TypeOf(rc.val); rtype != nil {
val = reflect.New(rtype).Elem()
}
want := rc.val
defer func() {
if err := recover(); err != nil {
fmt.Println(t.Name())
panic(err)
}
}()
err := tc.vd.DecodeValue(dc, llvrw, val)
if !assert.CompareErrors(err, rc.err) {
t.Errorf("Errors do not match. got %v; want %v", err, rc.err)
}
invoked := llvrw.invoked
if !cmp.Equal(invoked, rc.invoke) {
t.Errorf("Incorrect method invoked. got %v; want %v", invoked, rc.invoke)
}
var got interface{}
if val.IsValid() && val.CanInterface() {
got = val.Interface()
}
if rc.err == nil && !cmp.Equal(got, want) {
t.Errorf("Values do not match. got (%T)%v; want (%T)%v", got, got, want, want)
}
})
}
})
}
t.Run("success path", func(t *testing.T) {
oid := NewObjectID()
oids := []ObjectID{NewObjectID(), NewObjectID(), NewObjectID()}
var str = new(string)
*str = "bar"
now := time.Now().Truncate(time.Millisecond)
murl, err := url.Parse("https://mongodb.com/random-url?hello=world")
if err != nil {
t.Errorf("Error parsing URL: %v", err)
t.FailNow()
}
decimal128, err := ParseDecimal128("1.5e10")
if err != nil {
t.Errorf("Error parsing decimal128: %v", err)
t.FailNow()
}
testCases := []struct {
name string
value interface{}
b []byte
err error
}{
{
"map[string]int",
map[string]int32{"foo": 1},
[]byte{
0x0E, 0x00, 0x00, 0x00,
0x10, 'f', 'o', 'o', 0x00,
0x01, 0x00, 0x00, 0x00,
0x00,
},
nil,
},
{
"map[string]ObjectID",
map[string]ObjectID{"foo": oid},
docToBytes(D{{"foo", oid}}),
nil,
},
{
"map[string]Reader",
map[string]Raw{"Z": {0x05, 0x00, 0x00, 0x00, 0x00}},
docToBytes(D{{"Z", Raw{0x05, 0x00, 0x00, 0x00, 0x00}}}),
nil,
},
{
"map[string][]int32",
map[string][]int32{"Z": {1, 2, 3}},
docToBytes(D{{"Z", A{int32(1), int32(2), int32(3)}}}),
nil,
},
{
"map[string][]ObjectID",
map[string][]ObjectID{"Z": oids},
docToBytes(D{{"Z", A{oids[0], oids[1], oids[2]}}}),
nil,
},
{
"map[string][]json.Number(int64)",
map[string][]json.Number{"Z": {json.Number("5"), json.Number("10")}},
docToBytes(D{{"Z", A{int64(5), int64(10)}}}),
nil,
},
{
"map[string][]json.Number(float64)",
map[string][]json.Number{"Z": {json.Number("5"), json.Number("10.1")}},
docToBytes(D{{"Z", A{int64(5), float64(10.1)}}}),
nil,
},
{
"map[string][]*url.URL",
map[string][]*url.URL{"Z": {murl}},
docToBytes(D{{"Z", A{murl.String()}}}),
nil,
},
{
"map[string][]Decimal128",
map[string][]Decimal128{"Z": {decimal128}},
docToBytes(D{{"Z", A{decimal128}}}),
nil,
},
{
"-",
struct {
A string `bson:"-"`
}{
A: "",
},
docToBytes(D{}),
nil,
},
{
"omitempty",
struct {
A string `bson:",omitempty"`
}{
A: "",
},
docToBytes(D{}),
nil,
},
{
"omitempty, empty time",
struct {
A time.Time `bson:",omitempty"`
}{
A: time.Time{},
},
docToBytes(D{}),
nil,
},
{
"no private fields",
noPrivateFields{a: "should be empty"},
docToBytes(D{}),
nil,
},
{
"minsize",
struct {
A int64 `bson:",minsize"`
}{
A: 12345,
},
docToBytes(D{{"a", int32(12345)}}),
nil,
},
{
"inline",
struct {
Foo struct {
A int64 `bson:",minsize"`
} `bson:",inline"`
}{
Foo: struct {
A int64 `bson:",minsize"`
}{
A: 12345,
},
},
docToBytes(D{{"a", int32(12345)}}),
nil,
},
{
"inline map",
struct {
Foo map[string]string `bson:",inline"`
}{
Foo: map[string]string{"foo": "bar"},
},
docToBytes(D{{"foo", "bar"}}),
nil,
},
{
"alternate name bson:name",
struct {
A string `bson:"foo"`
}{
A: "bar",
},
docToBytes(D{{"foo", "bar"}}),
nil,
},
{
"alternate name",
struct {
A string `bson:"foo"`
}{
A: "bar",
},
docToBytes(D{{"foo", "bar"}}),
nil,
},
{
"inline, omitempty",
struct {
A string
Foo zeroTest `bson:"omitempty,inline"`
}{
A: "bar",
Foo: zeroTest{true},
},
docToBytes(D{{"a", "bar"}}),
nil,
},
{
"JavaScript to D",
D{{"a", JavaScript(`function() { var hello = "world"; }`)}},
docToBytes(D{{"a", JavaScript(`function() { var hello = "world"; }`)}}),
nil,
},
{
"Symbol to D",
D{{"a", Symbol("foobarbaz")}},
docToBytes(D{{"a", Symbol("foobarbaz")}}),
nil,
},
{
"struct{}",
struct {
A bool
B int32
C int64
D uint16
E uint64
F float64
G string
H map[string]string
I []byte
K [2]string
L struct {
M string
}
P Raw
Q ObjectID
T []struct{}
Y json.Number
Z time.Time
AA json.Number
AB *url.URL
AC Decimal128
AD *time.Time
AE *testValueUnmarshaler
AF RawValue
AG *RawValue
AH D
AI *D
AJ *D
}{
A: true,
B: 123,
C: 456,
D: 789,
E: 101112,
F: 3.14159,
G: "Hello, world",
H: map[string]string{"foo": "bar"},
I: []byte{0x01, 0x02, 0x03},
K: [2]string{"baz", "qux"},
L: struct {
M string
}{
M: "foobar",
},
P: Raw{0x05, 0x00, 0x00, 0x00, 0x00},
Q: oid,
T: nil,
Y: json.Number("5"),
Z: now,
AA: json.Number("10.1"),
AB: murl,
AC: decimal128,
AD: &now,
AE: &testValueUnmarshaler{t: TypeString, val: bsoncore.AppendString(nil, "hello, world!")},
AF: RawValue{Type: TypeDouble, Value: bsoncore.AppendDouble(nil, 3.14159)},
AG: &RawValue{Type: TypeBinary, Value: bsoncore.AppendBinary(nil, 0xFF, []byte{0x01, 0x02, 0x03})},
AH: D{{"foo", "bar"}},
AI: &D{{"pi", 3.14159}},
AJ: nil,
},
docToBytes(D{
{"a", true},
{"b", int32(123)},
{"c", int64(456)},
{"d", int32(789)},
{"e", int64(101112)},
{"f", float64(3.14159)},
{"g", "Hello, world"},
{"h", D{{"foo", "bar"}}},
{"i", Binary{Subtype: 0x00, Data: []byte{0x01, 0x02, 0x03}}},
{"k", A{"baz", "qux"}},
{"l", D{{"m", "foobar"}}},
{"p", D{}},
{"q", oid},
{"t", nil},
{"y", int64(5)},
{"z", DateTime(now.UnixNano() / int64(time.Millisecond))},
{"aa", float64(10.1)},
{"ab", murl.String()},
{"ac", decimal128},
{"ad", DateTime(now.UnixNano() / int64(time.Millisecond))},
{"ae", "hello, world!"},
{"af", float64(3.14159)},
{"ag", Binary{Subtype: 0xFF, Data: []byte{0x01, 0x02, 0x03}}},
{"ah", D{{"foo", "bar"}}},
{"ai", D{{"pi", float64(3.14159)}}},
{"aj", nil},
}),
nil,
},
{
"struct{[]interface{}}",
struct {
A []bool
B []int32
C []int64
D []uint16
E []uint64
F []float64
G []string
H []map[string]string
I [][]byte
K [1][2]string
L []struct {
M string
}
N [][]string
Q []Raw
R []ObjectID
T []struct{}
W []map[string]struct{}
X []map[string]struct{}
Y []map[string]struct{}
Z []time.Time
AA []json.Number
AB []*url.URL
AC []Decimal128
AD []*time.Time
AE []*testValueUnmarshaler
AF []D
AG []*D
}{
A: []bool{true},
B: []int32{123},
C: []int64{456},
D: []uint16{789},
E: []uint64{101112},
F: []float64{3.14159},
G: []string{"Hello, world"},
H: []map[string]string{{"foo": "bar"}},
I: [][]byte{{0x01, 0x02, 0x03}},
K: [1][2]string{{"baz", "qux"}},
L: []struct {
M string
}{
{
M: "foobar",
},
},
N: [][]string{{"foo", "bar"}},
Q: []Raw{{0x05, 0x00, 0x00, 0x00, 0x00}},
R: oids,
T: nil,
W: nil,
X: []map[string]struct{}{}, // Should be empty BSON Array
Y: []map[string]struct{}{{}}, // Should be BSON array with one element, an empty BSON SubDocument
Z: []time.Time{now, now},
AA: []json.Number{json.Number("5"), json.Number("10.1")},
AB: []*url.URL{murl},
AC: []Decimal128{decimal128},
AD: []*time.Time{&now, &now},
AE: []*testValueUnmarshaler{
{t: TypeString, val: bsoncore.AppendString(nil, "hello")},
{t: TypeString, val: bsoncore.AppendString(nil, "world")},
},
AF: []D{{{"foo", "bar"}}, {{"hello", "world"}, {"number", int64(12345)}}},
AG: []*D{{{"pi", 3.14159}}, nil},
},
docToBytes(D{
{"a", A{true}},
{"b", A{int32(123)}},
{"c", A{int64(456)}},
{"d", A{int32(789)}},
{"e", A{int64(101112)}},
{"f", A{float64(3.14159)}},
{"g", A{"Hello, world"}},
{"h", A{D{{"foo", "bar"}}}},
{"i", A{Binary{Subtype: 0x00, Data: []byte{0x01, 0x02, 0x03}}}},
{"k", A{A{"baz", "qux"}}},
{"l", A{D{{"m", "foobar"}}}},
{"n", A{A{"foo", "bar"}}},
{"q", A{D{}}},
{"r", A{oids[0], oids[1], oids[2]}},
{"t", nil},
{"w", nil},
{"x", A{}},
{"y", A{D{}}},
{"z", A{
DateTime(now.UnixNano() / int64(time.Millisecond)),
DateTime(now.UnixNano() / int64(time.Millisecond)),
}},
{"aa", A{int64(5), float64(10.10)}},
{"ab", A{murl.String()}},
{"ac", A{decimal128}},
{"ad", A{
DateTime(now.UnixNano() / int64(time.Millisecond)),
DateTime(now.UnixNano() / int64(time.Millisecond)),
}},
{"ae", A{"hello", "world"}},
{"af", A{
D{{"foo", "bar"}},
D{{"hello", "world"}, {"number", int64(12345)}},
}},
{"ag", A{D{{"pi", float64(3.14159)}}, nil}},
}),
nil,
},
}
t.Run("Decode", func(t *testing.T) {
for _, tc := range testCases {
t.Run(tc.name, func(t *testing.T) {
vr := NewDocumentReader(bytes.NewReader(tc.b))
dec := NewDecoder(vr)
gotVal := reflect.New(reflect.TypeOf(tc.value))
err := dec.Decode(gotVal.Interface())
noerr(t, err)
got := gotVal.Elem().Interface()
want := tc.value
if diff := cmp.Diff(
got, want,
cmp.Comparer(compareDecimal128),
cmp.Comparer(compareNoPrivateFields),
cmp.Comparer(compareZeroTest),
); diff != "" {
t.Errorf("difference:\n%s", diff)
t.Errorf("Values are not equal.\ngot: %#v\nwant:%#v", got, want)
}
})
}
})
})
}
type testValueMarshaler struct {
t Type
buf []byte
err error
}
func (tvm testValueMarshaler) MarshalBSONValue() (byte, []byte, error) {
return byte(tvm.t), tvm.buf, tvm.err
}
type testValueUnmarshaler struct {
t Type
val []byte
err error
}
func (tvu *testValueUnmarshaler) UnmarshalBSONValue(t byte, val []byte) error {
tvu.t, tvu.val = Type(t), val
return tvu.err
}
func (tvu testValueUnmarshaler) Equal(tvu2 testValueUnmarshaler) bool {
return tvu.t == tvu2.t && bytes.Equal(tvu.val, tvu2.val)
}
type noPrivateFields struct {
a string
}
func compareNoPrivateFields(npf1, npf2 noPrivateFields) bool {
return npf1.a != npf2.a // We don't want these to be equal
}
type zeroTest struct {
reportZero bool
}
func (z zeroTest) IsZero() bool { return z.reportZero }
func compareZeroTest(_, _ zeroTest) bool { return true }
func compareDecimal128(d1, d2 Decimal128) bool {
d1H, d1L := d1.GetBytes()
d2H, d2L := d2.GetBytes()
if d1H != d2H {
return false
}
if d1L != d2L {
return false
}
return true
}