1073 lines
26 KiB
Go
1073 lines
26 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"
|
||
|
|
||
|
"github.com/google/go-cmp/cmp"
|
||
|
"go.mongodb.org/mongo-driver/bson/bsoncodec"
|
||
|
"go.mongodb.org/mongo-driver/bson/bsonrw"
|
||
|
"go.mongodb.org/mongo-driver/bson/bsonrw/bsonrwtest"
|
||
|
"go.mongodb.org/mongo-driver/bson/bsontype"
|
||
|
"go.mongodb.org/mongo-driver/bson/primitive"
|
||
|
"go.mongodb.org/mongo-driver/x/bsonx/bsoncore"
|
||
|
)
|
||
|
|
||
|
func bytesFromDoc(doc interface{}) []byte {
|
||
|
b, err := Marshal(doc)
|
||
|
if err != nil {
|
||
|
panic(fmt.Errorf("Couldn't marshal BSON document: %v", err))
|
||
|
}
|
||
|
return b
|
||
|
}
|
||
|
|
||
|
func compareDecimal128(d1, d2 primitive.Decimal128) bool {
|
||
|
d1H, d1L := d1.GetBytes()
|
||
|
d2H, d2L := d2.GetBytes()
|
||
|
|
||
|
if d1H != d2H {
|
||
|
return false
|
||
|
}
|
||
|
|
||
|
if d1L != d2L {
|
||
|
return false
|
||
|
}
|
||
|
|
||
|
return true
|
||
|
}
|
||
|
|
||
|
func compareErrors(err1, err2 error) bool {
|
||
|
if err1 == nil && err2 == nil {
|
||
|
return true
|
||
|
}
|
||
|
|
||
|
if err1 == nil || err2 == nil {
|
||
|
return false
|
||
|
}
|
||
|
|
||
|
if err1.Error() != err2.Error() {
|
||
|
return false
|
||
|
}
|
||
|
|
||
|
return true
|
||
|
}
|
||
|
|
||
|
func TestDefaultValueEncoders(t *testing.T) {
|
||
|
var pc PrimitiveCodecs
|
||
|
|
||
|
var wrong = func(string, string) string { return "wrong" }
|
||
|
|
||
|
type subtest struct {
|
||
|
name string
|
||
|
val interface{}
|
||
|
ectx *bsoncodec.EncodeContext
|
||
|
llvrw *bsonrwtest.ValueReaderWriter
|
||
|
invoke bsonrwtest.Invoked
|
||
|
err error
|
||
|
}
|
||
|
|
||
|
testCases := []struct {
|
||
|
name string
|
||
|
ve bsoncodec.ValueEncoder
|
||
|
subtests []subtest
|
||
|
}{
|
||
|
{
|
||
|
"RawValueEncodeValue",
|
||
|
bsoncodec.ValueEncoderFunc(pc.RawValueEncodeValue),
|
||
|
[]subtest{
|
||
|
{
|
||
|
"wrong type",
|
||
|
wrong,
|
||
|
nil,
|
||
|
nil,
|
||
|
bsonrwtest.Nothing,
|
||
|
bsoncodec.ValueEncoderError{
|
||
|
Name: "RawValueEncodeValue",
|
||
|
Types: []reflect.Type{tRawValue},
|
||
|
Received: reflect.ValueOf(wrong),
|
||
|
},
|
||
|
},
|
||
|
{
|
||
|
"RawValue/success",
|
||
|
RawValue{Type: bsontype.Double, Value: bsoncore.AppendDouble(nil, 3.14159)},
|
||
|
nil,
|
||
|
nil,
|
||
|
bsonrwtest.WriteDouble,
|
||
|
nil,
|
||
|
},
|
||
|
},
|
||
|
},
|
||
|
{
|
||
|
"RawEncodeValue",
|
||
|
bsoncodec.ValueEncoderFunc(pc.RawEncodeValue),
|
||
|
[]subtest{
|
||
|
{
|
||
|
"wrong type",
|
||
|
wrong,
|
||
|
nil,
|
||
|
nil,
|
||
|
bsonrwtest.Nothing,
|
||
|
bsoncodec.ValueEncoderError{Name: "RawEncodeValue", Types: []reflect.Type{tRaw}, Received: reflect.ValueOf(wrong)},
|
||
|
},
|
||
|
{
|
||
|
"WriteDocument Error",
|
||
|
Raw{},
|
||
|
nil,
|
||
|
&bsonrwtest.ValueReaderWriter{Err: errors.New("wd error"), ErrAfter: bsonrwtest.WriteDocument},
|
||
|
bsonrwtest.WriteDocument,
|
||
|
errors.New("wd error"),
|
||
|
},
|
||
|
{
|
||
|
"Raw.Elements Error",
|
||
|
Raw{0xFF, 0x00, 0x00, 0x00, 0x00},
|
||
|
nil,
|
||
|
&bsonrwtest.ValueReaderWriter{},
|
||
|
bsonrwtest.WriteDocument,
|
||
|
errors.New("length read exceeds number of bytes available. length=5 bytes=255"),
|
||
|
},
|
||
|
{
|
||
|
"WriteDocumentElement Error",
|
||
|
Raw(bytesFromDoc(D{{"foo", nil}})),
|
||
|
nil,
|
||
|
&bsonrwtest.ValueReaderWriter{Err: errors.New("wde error"), ErrAfter: bsonrwtest.WriteDocumentElement},
|
||
|
bsonrwtest.WriteDocumentElement,
|
||
|
errors.New("wde error"),
|
||
|
},
|
||
|
{
|
||
|
"encodeValue error",
|
||
|
Raw(bytesFromDoc(D{{"foo", nil}})),
|
||
|
nil,
|
||
|
&bsonrwtest.ValueReaderWriter{Err: errors.New("ev error"), ErrAfter: bsonrwtest.WriteNull},
|
||
|
bsonrwtest.WriteNull,
|
||
|
errors.New("ev error"),
|
||
|
},
|
||
|
{
|
||
|
"iterator error",
|
||
|
Raw{0x0C, 0x00, 0x00, 0x00, 0x01, 'f', 'o', 'o', 0x00, 0x01, 0x02, 0x03},
|
||
|
nil,
|
||
|
&bsonrwtest.ValueReaderWriter{},
|
||
|
bsonrwtest.WriteDocumentElement,
|
||
|
errors.New("not enough bytes available to read type. bytes=3 type=double"),
|
||
|
},
|
||
|
},
|
||
|
},
|
||
|
}
|
||
|
|
||
|
for _, tc := range testCases {
|
||
|
t.Run(tc.name, func(t *testing.T) {
|
||
|
for _, subtest := range tc.subtests {
|
||
|
t.Run(subtest.name, func(t *testing.T) {
|
||
|
var ec bsoncodec.EncodeContext
|
||
|
if subtest.ectx != nil {
|
||
|
ec = *subtest.ectx
|
||
|
}
|
||
|
llvrw := new(bsonrwtest.ValueReaderWriter)
|
||
|
if subtest.llvrw != nil {
|
||
|
llvrw = subtest.llvrw
|
||
|
}
|
||
|
llvrw.T = t
|
||
|
err := tc.ve.EncodeValue(ec, llvrw, reflect.ValueOf(subtest.val))
|
||
|
if !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) {
|
||
|
oid := primitive.NewObjectID()
|
||
|
oids := []primitive.ObjectID{primitive.NewObjectID(), primitive.NewObjectID(), primitive.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 := primitive.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", primitive.JavaScript(`function() { var hello = "world"; }`)}},
|
||
|
docToBytes(D{{"a", primitive.JavaScript(`function() { var hello = "world"; }`)}}),
|
||
|
nil,
|
||
|
},
|
||
|
{
|
||
|
"D to Symbol",
|
||
|
D{{"a", primitive.Symbol("foobarbaz")}},
|
||
|
docToBytes(D{{"a", primitive.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 primitive.ObjectID
|
||
|
T []struct{}
|
||
|
Y json.Number
|
||
|
Z time.Time
|
||
|
AA json.Number
|
||
|
AB *url.URL
|
||
|
AC primitive.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: bsontype.String, Value: bsoncore.AppendString(nil, "hello, raw value")},
|
||
|
AG: &RawValue{Type: bsontype.Double, 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", primitive.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", primitive.DateTime(now.UnixNano() / int64(time.Millisecond))},
|
||
|
{"aa", float64(10.1)},
|
||
|
{"ab", murl.String()},
|
||
|
{"ac", decimal128},
|
||
|
{"ad", primitive.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 []primitive.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 []primitive.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: []primitive.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{primitive.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{
|
||
|
primitive.DateTime(now.UnixNano() / int64(time.Millisecond)),
|
||
|
primitive.DateTime(now.UnixNano() / int64(time.Millisecond)),
|
||
|
}},
|
||
|
{"aa", A{int64(5), float64(10.10)}},
|
||
|
{"ab", A{murl.String()}},
|
||
|
{"ac", A{decimal128}},
|
||
|
{"ad", A{
|
||
|
primitive.DateTime(now.UnixNano() / int64(time.Millisecond)),
|
||
|
primitive.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 {
|
||
|
t.Run(tc.name, func(t *testing.T) {
|
||
|
b := make(bsonrw.SliceWriter, 0, 512)
|
||
|
vw, err := bsonrw.NewBSONValueWriter(&b)
|
||
|
noerr(t, err)
|
||
|
enc, err := NewEncoder(vw)
|
||
|
noerr(t, err)
|
||
|
err = enc.Encode(tc.value)
|
||
|
if 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 TestDefaultValueDecoders(t *testing.T) {
|
||
|
var pc PrimitiveCodecs
|
||
|
|
||
|
var wrong = func(string, string) string { return "wrong" }
|
||
|
|
||
|
const cansetreflectiontest = "cansetreflectiontest"
|
||
|
|
||
|
type subtest struct {
|
||
|
name string
|
||
|
val interface{}
|
||
|
dctx *bsoncodec.DecodeContext
|
||
|
llvrw *bsonrwtest.ValueReaderWriter
|
||
|
invoke bsonrwtest.Invoked
|
||
|
err error
|
||
|
}
|
||
|
|
||
|
testCases := []struct {
|
||
|
name string
|
||
|
vd bsoncodec.ValueDecoder
|
||
|
subtests []subtest
|
||
|
}{
|
||
|
{
|
||
|
"RawValueDecodeValue",
|
||
|
bsoncodec.ValueDecoderFunc(pc.RawValueDecodeValue),
|
||
|
[]subtest{
|
||
|
{
|
||
|
"wrong type",
|
||
|
wrong,
|
||
|
nil,
|
||
|
&bsonrwtest.ValueReaderWriter{},
|
||
|
bsonrwtest.Nothing,
|
||
|
bsoncodec.ValueDecoderError{
|
||
|
Name: "RawValueDecodeValue",
|
||
|
Types: []reflect.Type{tRawValue},
|
||
|
Received: reflect.ValueOf(wrong),
|
||
|
},
|
||
|
},
|
||
|
{
|
||
|
"ReadValue Error",
|
||
|
RawValue{},
|
||
|
nil,
|
||
|
&bsonrwtest.ValueReaderWriter{
|
||
|
BSONType: bsontype.Binary,
|
||
|
Err: errors.New("rb error"),
|
||
|
ErrAfter: bsonrwtest.ReadBinary,
|
||
|
},
|
||
|
bsonrwtest.ReadBinary,
|
||
|
errors.New("rb error"),
|
||
|
},
|
||
|
{
|
||
|
"RawValue/success",
|
||
|
RawValue{Type: bsontype.Binary, Value: bsoncore.AppendBinary(nil, 0xFF, []byte{0x01, 0x02, 0x03})},
|
||
|
nil,
|
||
|
&bsonrwtest.ValueReaderWriter{
|
||
|
BSONType: bsontype.Binary,
|
||
|
Return: bsoncore.Value{
|
||
|
Type: bsontype.Binary,
|
||
|
Data: bsoncore.AppendBinary(nil, 0xFF, []byte{0x01, 0x02, 0x03}),
|
||
|
},
|
||
|
},
|
||
|
bsonrwtest.ReadBinary,
|
||
|
nil,
|
||
|
},
|
||
|
},
|
||
|
},
|
||
|
{
|
||
|
"RawDecodeValue",
|
||
|
bsoncodec.ValueDecoderFunc(pc.RawDecodeValue),
|
||
|
[]subtest{
|
||
|
{
|
||
|
"wrong type",
|
||
|
wrong,
|
||
|
nil,
|
||
|
&bsonrwtest.ValueReaderWriter{},
|
||
|
bsonrwtest.Nothing,
|
||
|
bsoncodec.ValueDecoderError{Name: "RawDecodeValue", Types: []reflect.Type{tRaw}, Received: reflect.ValueOf(wrong)},
|
||
|
},
|
||
|
{
|
||
|
"*Raw is nil",
|
||
|
(*Raw)(nil),
|
||
|
nil,
|
||
|
nil,
|
||
|
bsonrwtest.Nothing,
|
||
|
bsoncodec.ValueDecoderError{
|
||
|
Name: "RawDecodeValue",
|
||
|
Types: []reflect.Type{tRaw},
|
||
|
Received: reflect.ValueOf((*Raw)(nil)),
|
||
|
},
|
||
|
},
|
||
|
{
|
||
|
"Copy error",
|
||
|
Raw{},
|
||
|
nil,
|
||
|
&bsonrwtest.ValueReaderWriter{Err: errors.New("copy error"), ErrAfter: bsonrwtest.ReadDocument},
|
||
|
bsonrwtest.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 bsoncodec.DecodeContext
|
||
|
if rc.dctx != nil {
|
||
|
dc = *rc.dctx
|
||
|
}
|
||
|
llvrw := new(bsonrwtest.ValueReaderWriter)
|
||
|
if rc.llvrw != nil {
|
||
|
llvrw = rc.llvrw
|
||
|
}
|
||
|
llvrw.T = t
|
||
|
// var got interface{}
|
||
|
if rc.val == cansetreflectiontest { // We're doing a CanSet reflection test
|
||
|
err := tc.vd.DecodeValue(dc, llvrw, reflect.Value{})
|
||
|
if !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 !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 !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 := primitive.NewObjectID()
|
||
|
oids := []primitive.ObjectID{primitive.NewObjectID(), primitive.NewObjectID(), primitive.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 := primitive.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]primitive.ObjectID",
|
||
|
map[string]primitive.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][]primitive.ObjectID",
|
||
|
map[string][]primitive.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][]primitive.Decimal128",
|
||
|
map[string][]primitive.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", primitive.JavaScript(`function() { var hello = "world"; }`)}},
|
||
|
docToBytes(D{{"a", primitive.JavaScript(`function() { var hello = "world"; }`)}}),
|
||
|
nil,
|
||
|
},
|
||
|
{
|
||
|
"Symbol to D",
|
||
|
D{{"a", primitive.Symbol("foobarbaz")}},
|
||
|
docToBytes(D{{"a", primitive.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 primitive.ObjectID
|
||
|
T []struct{}
|
||
|
Y json.Number
|
||
|
Z time.Time
|
||
|
AA json.Number
|
||
|
AB *url.URL
|
||
|
AC primitive.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: bsontype.String, val: bsoncore.AppendString(nil, "hello, world!")},
|
||
|
AF: RawValue{Type: bsontype.Double, Value: bsoncore.AppendDouble(nil, 3.14159)},
|
||
|
AG: &RawValue{Type: bsontype.Binary, 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", primitive.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", primitive.DateTime(now.UnixNano() / int64(time.Millisecond))},
|
||
|
{"aa", float64(10.1)},
|
||
|
{"ab", murl.String()},
|
||
|
{"ac", decimal128},
|
||
|
{"ad", primitive.DateTime(now.UnixNano() / int64(time.Millisecond))},
|
||
|
{"ae", "hello, world!"},
|
||
|
{"af", float64(3.14159)},
|
||
|
{"ag", primitive.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 []primitive.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 []primitive.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: []primitive.Decimal128{decimal128},
|
||
|
AD: []*time.Time{&now, &now},
|
||
|
AE: []*testValueUnmarshaler{
|
||
|
{t: bsontype.String, val: bsoncore.AppendString(nil, "hello")},
|
||
|
{t: bsontype.String, 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{primitive.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{
|
||
|
primitive.DateTime(now.UnixNano() / int64(time.Millisecond)),
|
||
|
primitive.DateTime(now.UnixNano() / int64(time.Millisecond)),
|
||
|
}},
|
||
|
{"aa", A{int64(5), float64(10.10)}},
|
||
|
{"ab", A{murl.String()}},
|
||
|
{"ac", A{decimal128}},
|
||
|
{"ad", A{
|
||
|
primitive.DateTime(now.UnixNano() / int64(time.Millisecond)),
|
||
|
primitive.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 := bsonrw.NewBSONDocumentReader(tc.b)
|
||
|
dec, err := NewDecoder(vr)
|
||
|
noerr(t, err)
|
||
|
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 bsontype.Type
|
||
|
buf []byte
|
||
|
err error
|
||
|
}
|
||
|
|
||
|
func (tvm testValueMarshaler) MarshalBSONValue() (bsontype.Type, []byte, error) {
|
||
|
return tvm.t, tvm.buf, tvm.err
|
||
|
}
|
||
|
|
||
|
type testValueUnmarshaler struct {
|
||
|
t bsontype.Type
|
||
|
val []byte
|
||
|
err error
|
||
|
}
|
||
|
|
||
|
func (tvu *testValueUnmarshaler) UnmarshalBSONValue(t bsontype.Type, val []byte) error {
|
||
|
tvu.t, tvu.val = 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 }
|