bson/bson_unmarshal_test.go

820 lines
19 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"
"math/rand"
"reflect"
"sync"
"testing"
"gitea.psichedelico.com/go/bson/internal/assert"
"gitea.psichedelico.com/go/bson/x/bsonx/bsoncore"
)
func TestUnmarshal(t *testing.T) {
for _, tc := range unmarshalingTestCases() {
t.Run(tc.name, func(t *testing.T) {
// Make a copy of the test data so we can modify it later.
data := make([]byte, len(tc.data))
copy(data, tc.data)
// Assert that unmarshaling the input data results in the expected value.
got := reflect.New(tc.sType).Interface()
err := Unmarshal(data, got)
noerr(t, err)
assert.Equal(t, tc.want, got, "Did not unmarshal as expected.")
// Fill the input data slice with random bytes and then assert that the result still
// matches the expected value.
_, err = rand.Read(data)
noerr(t, err)
assert.Equal(t, tc.want, got, "unmarshaled value does not match expected after modifying the input bytes")
})
}
}
func TestUnmarshalWithRegistry(t *testing.T) {
t.Parallel()
testCases := []struct {
name string
val interface{}
bsontype Type
bytes []byte
}{
{
name: "SliceCodec binary",
val: []byte("hello world"),
bsontype: TypeBinary,
bytes: bsoncore.AppendBinary(nil, TypeBinaryGeneric, []byte("hello world")),
},
{
name: "SliceCodec string",
val: []byte("hello world"),
bsontype: TypeString,
bytes: bsoncore.AppendString(nil, "hello world"),
},
}
reg := NewRegistry()
reg.RegisterTypeDecoder(reflect.TypeOf([]byte{}), &sliceCodec{})
for _, tc := range testCases {
tc := tc
t.Run(tc.name, func(t *testing.T) {
t.Parallel()
// Assert that unmarshaling the input data results in the expected value.
gotValue := reflect.New(reflect.TypeOf(tc.val))
dec := NewDecoder(newValueReader(tc.bsontype, bytes.NewReader(tc.bytes)))
dec.SetRegistry(reg)
err := dec.Decode(gotValue.Interface())
noerr(t, err)
assert.Equal(t, tc.val, gotValue.Elem().Interface(), "value mismatch; expected %s, got %s", tc.val, gotValue.Elem())
// Fill the input data slice with random bytes and then assert that the result still
// matches the expected value.
_, err = rand.Read(tc.bytes)
noerr(t, err)
assert.Equal(t, tc.val, gotValue.Elem().Interface(), "unmarshaled value does not match expected after modifying the input bytes")
})
}
}
func TestCachingDecodersNotSharedAcrossRegistries(t *testing.T) {
// Decoders that have caches for recursive decoder lookup should not be shared across Registry instances. Otherwise,
// the first DecodeValue call would cache an decoder and a subsequent call would see that decoder even if a
// different Registry is used.
// Create a custom Registry that negates BSON int32 values when decoding.
var decodeInt32 ValueDecoderFunc = func(_ DecodeContext, vr ValueReader, val reflect.Value) error {
i32, err := vr.ReadInt32()
if err != nil {
return err
}
val.SetInt(int64(-1 * i32))
return nil
}
customReg := NewRegistry()
customReg.RegisterTypeDecoder(tInt32, decodeInt32)
docBytes := bsoncore.BuildDocumentFromElements(
nil,
bsoncore.AppendInt32Element(nil, "x", 1),
)
// For all sub-tests, unmarshal docBytes into a struct and assert that value for "x" is 1 when using the default
// registry and -1 when using the custom registry.
t.Run("struct", func(t *testing.T) {
type Struct struct {
X int32
}
var first Struct
err := Unmarshal(docBytes, &first)
assert.Nil(t, err, "Unmarshal error: %v", err)
assert.Equal(t, int32(1), first.X, "expected X value to be 1, got %v", first.X)
var second Struct
dec := NewDecoder(NewDocumentReader(bytes.NewReader(docBytes)))
dec.SetRegistry(customReg)
err = dec.Decode(&second)
assert.Nil(t, err, "Unmarshal error: %v", err)
assert.Equal(t, int32(-1), second.X, "expected X value to be -1, got %v", second.X)
})
t.Run("pointer", func(t *testing.T) {
type Struct struct {
X *int32
}
var first Struct
err := Unmarshal(docBytes, &first)
assert.Nil(t, err, "Unmarshal error: %v", err)
assert.Equal(t, int32(1), *first.X, "expected X value to be 1, got %v", *first.X)
var second Struct
dec := NewDecoder(NewDocumentReader(bytes.NewReader(docBytes)))
dec.SetRegistry(customReg)
err = dec.Decode(&second)
assert.Nil(t, err, "Unmarshal error: %v", err)
assert.Equal(t, int32(-1), *second.X, "expected X value to be -1, got %v", *second.X)
})
}
func TestUnmarshalExtJSONWithUndefinedField(t *testing.T) {
// When unmarshalling extJSON, fields that are undefined in the destination struct are skipped.
// This process must not skip other, defined fields and must not raise errors.
type expectedResponse struct {
DefinedField interface{}
}
unmarshalExpectedResponse := func(t *testing.T, extJSON string) *expectedResponse {
t.Helper()
responseDoc := expectedResponse{}
err := UnmarshalExtJSON([]byte(extJSON), false, &responseDoc)
assert.Nil(t, err, "UnmarshalExtJSON error: %v", err)
return &responseDoc
}
testCases := []struct {
name string
testJSON string
expectedValue interface{}
}{
{
"no array",
`{
"UndefinedField": {"key": 1},
"DefinedField": "value"
}`,
"value",
},
{
"outer array",
`{
"UndefinedField": [{"key": 1}],
"DefinedField": "value"
}`,
"value",
},
{
"embedded array",
`{
"UndefinedField": {"keys": [2]},
"DefinedField": "value"
}`,
"value",
},
{
"outer array and embedded array",
`{
"UndefinedField": [{"keys": [2]}],
"DefinedField": "value"
}`,
"value",
},
{
"embedded document",
`{
"UndefinedField": {"key": {"one": "two"}},
"DefinedField": "value"
}`,
"value",
},
{
"doubly embedded document",
`{
"UndefinedField": {"key": {"one": {"two": "three"}}},
"DefinedField": "value"
}`,
"value",
},
{
"embedded document and embedded array",
`{
"UndefinedField": {"key": {"one": {"two": [3]}}},
"DefinedField": "value"
}`,
"value",
},
{
"embedded document and embedded array in outer array",
`{
"UndefinedField": [{"key": {"one": [3]}}],
"DefinedField": "value"
}`,
"value",
},
{
"code with scope",
`{
"UndefinedField": {"logic": {"$code": "foo", "$scope": {"bar": 1}}},
"DefinedField": "value"
}`,
"value",
},
{
"embedded array of code with scope",
`{
"UndefinedField": {"logic": [{"$code": "foo", "$scope": {"bar": 1}}]},
"DefinedField": "value"
}`,
"value",
},
{
"type definition embedded document",
`{
"UndefinedField": {"myDouble": {"$numberDouble": "1.24"}},
"DefinedField": "value"
}`,
"value",
},
{
"empty embedded document",
`{
"UndefinedField": {"empty": {}, "key": 1},
"DefinedField": "value"
}`,
"value",
},
{
"empty object before",
`{
"UndefinedField": {},
"DefinedField": {"value": "a"}
}`,
D{{"value", "a"}},
},
{
"empty object after",
`{
"DefinedField": {"value": "a"},
"UndefinedField": {}
}`,
D{{"value", "a"}},
},
}
for _, tc := range testCases {
t.Run(tc.name, func(t *testing.T) {
responseDoc := unmarshalExpectedResponse(t, tc.testJSON)
assert.Equal(t, tc.expectedValue, responseDoc.DefinedField, "expected DefinedField to be %v, got %q",
tc.expectedValue, responseDoc.DefinedField)
})
}
}
func TestUnmarshalInterface(t *testing.T) {
t.Parallel()
type testCase struct {
name string
stub func() ([]byte, interface{}, func(*testing.T))
}
testCases := []testCase{
{
name: "struct with interface containing a concrete value",
stub: func() ([]byte, interface{}, func(*testing.T)) {
type testStruct struct {
Value interface{}
}
var value string
data := docToBytes(struct {
Value string
}{
Value: "foo",
})
receiver := testStruct{&value}
check := func(t *testing.T) {
t.Helper()
assert.Equal(t, "foo", value)
}
return data, &receiver, check
},
},
{
name: "struct with interface containing a struct",
stub: func() ([]byte, interface{}, func(*testing.T)) {
type demo struct {
Data string
}
type testStruct struct {
Value interface{}
}
var value demo
data := docToBytes(struct {
Value demo
}{
Value: demo{"foo"},
})
receiver := testStruct{&value}
check := func(t *testing.T) {
t.Helper()
assert.Equal(t, "foo", value.Data)
}
return data, &receiver, check
},
},
{
name: "struct with interface containing a slice",
stub: func() ([]byte, interface{}, func(*testing.T)) {
type testStruct struct {
Values interface{}
}
var values []string
data := docToBytes(struct {
Values []string
}{
Values: []string{"foo", "bar"},
})
receiver := testStruct{&values}
check := func(t *testing.T) {
t.Helper()
assert.Equal(t, []string{"foo", "bar"}, values)
}
return data, &receiver, check
},
},
{
name: "struct with interface containing an array",
stub: func() ([]byte, interface{}, func(*testing.T)) {
type testStruct struct {
Values interface{}
}
var values [2]string
data := docToBytes(struct {
Values []string
}{
Values: []string{"foo", "bar"},
})
receiver := testStruct{&values}
check := func(t *testing.T) {
t.Helper()
assert.Equal(t, [2]string{"foo", "bar"}, values)
}
return data, &receiver, check
},
},
{
name: "struct with interface array containing concrete values",
stub: func() ([]byte, interface{}, func(*testing.T)) {
type testStruct struct {
Values [3]interface{}
}
var str string
var i, j int
data := docToBytes(struct {
Values []interface{}
}{
Values: []interface{}{"foo", 42, nil},
})
receiver := testStruct{[3]interface{}{&str, &i, &j}}
check := func(t *testing.T) {
t.Helper()
assert.Equal(t, "foo", str)
assert.Equal(t, 42, i)
assert.Equal(t, 0, j)
assert.Equal(t, testStruct{[3]interface{}{&str, &i, nil}}, receiver)
}
return data, &receiver, check
},
},
}
for _, tc := range testCases {
tc := tc
t.Run(tc.name, func(t *testing.T) {
t.Parallel()
data, receiver, check := tc.stub()
err := Unmarshal(data, receiver)
noerr(t, err)
check(t)
})
}
}
func TestUnmarshalBSONWithUndefinedField(t *testing.T) {
// When unmarshalling BSON, fields that are undefined in the destination struct are skipped.
// This process must not skip other, defined fields and must not raise errors.
type expectedResponse struct {
DefinedField string `bson:"DefinedField"`
}
createExpectedResponse := func(t *testing.T, doc D) *expectedResponse {
t.Helper()
marshalledBSON, err := Marshal(doc)
assert.Nil(t, err, "error marshalling BSON: %v", err)
responseDoc := expectedResponse{}
err = Unmarshal(marshalledBSON, &responseDoc)
assert.Nil(t, err, "error unmarshalling BSON: %v", err)
return &responseDoc
}
testCases := []struct {
name string
testBSON D
}{
{
"no array",
D{
{"UndefinedField", D{
{"key", 1},
}},
{"DefinedField", "value"},
},
},
{
"outer array",
D{
{"UndefinedField", A{D{
{"key", 1},
}}},
{"DefinedField", "value"},
},
},
{
"embedded array",
D{
{"UndefinedField", D{
{"key", A{1}},
}},
{"DefinedField", "value"},
},
},
{
"outer array and embedded array",
D{
{"UndefinedField", A{D{
{"key", A{1}},
}}},
{"DefinedField", "value"},
},
},
{
"embedded document",
D{
{"UndefinedField", D{
{"key", D{
{"one", "two"},
}},
}},
{"DefinedField", "value"},
},
},
{
"doubly embedded document",
D{
{"UndefinedField", D{
{"key", D{
{"one", D{
{"two", "three"},
}},
}},
}},
{"DefinedField", "value"},
},
},
{
"embedded document and embedded array",
D{
{"UndefinedField", D{
{"key", D{
{"one", D{
{"two", A{3}},
}},
}},
}},
{"DefinedField", "value"},
},
},
{
"embedded document and embedded array in outer array",
D{
{"UndefinedField", A{D{
{"key", D{
{"one", A{3}},
}},
}}},
{"DefinedField", "value"},
},
},
{
"code with scope",
D{
{"UndefinedField", D{
{"logic", D{
{"$code", "foo"},
{"$scope", D{
{"bar", 1},
}},
}},
}},
{"DefinedField", "value"},
},
},
{
"embedded array of code with scope",
D{
{"UndefinedField", D{
{"logic", A{D{
{"$code", "foo"},
{"$scope", D{
{"bar", 1},
}},
}}},
}},
{"DefinedField", "value"},
},
},
{
"empty embedded document",
D{
{"UndefinedField", D{}},
{"DefinedField", "value"},
},
},
}
for _, tc := range testCases {
t.Run(tc.name, func(t *testing.T) {
responseDoc := createExpectedResponse(t, tc.testBSON)
assert.Equal(t, "value", responseDoc.DefinedField, "expected DefinedField to be 'value', got %q", responseDoc.DefinedField)
})
}
}
// GODRIVER-2311
// Assert that unmarshaled values containing byte slices do not reference the same underlying byte
// array as the BSON input data byte slice.
func TestUnmarshalByteSlicesUseDistinctArrays(t *testing.T) {
type fooBytes struct {
Foo []byte
}
type myBytes []byte
type fooMyBytes struct {
Foo myBytes
}
type fooBinary struct {
Foo Binary
}
type fooObjectID struct {
Foo ObjectID
}
type fooDBPointer struct {
Foo DBPointer
}
testCases := []struct {
description string
data []byte
sType reflect.Type
want interface{}
// getByteSlice returns the byte slice from the unmarshaled value, allowing the test to
// inspect the addresses of the underlying byte array.
getByteSlice func(interface{}) []byte
}{
{
description: "struct with byte slice",
data: docToBytes(fooBytes{
Foo: []byte{0, 1, 2, 3, 4, 5},
}),
sType: reflect.TypeOf(fooBytes{}),
want: &fooBytes{
Foo: []byte{0, 1, 2, 3, 4, 5},
},
getByteSlice: func(val interface{}) []byte {
return (val.(*fooBytes)).Foo
},
},
{
description: "bson.D with byte slice",
data: docToBytes(D{
{"foo", []byte{0, 1, 2, 3, 4, 5}},
}),
sType: reflect.TypeOf(D{}),
want: &D{
{"foo", Binary{Subtype: 0, Data: []byte{0, 1, 2, 3, 4, 5}}},
},
getByteSlice: func(val interface{}) []byte {
return (*(val.(*D)))[0].Value.(Binary).Data
},
},
{
description: "struct with custom byte slice type",
data: docToBytes(fooMyBytes{
Foo: myBytes{0, 1, 2, 3, 4, 5},
}),
sType: reflect.TypeOf(fooMyBytes{}),
want: &fooMyBytes{
Foo: myBytes{0, 1, 2, 3, 4, 5},
},
getByteSlice: func(val interface{}) []byte {
return (val.(*fooMyBytes)).Foo
},
},
{
description: "bson.D with custom byte slice type",
data: docToBytes(D{
{"foo", myBytes{0, 1, 2, 3, 4, 5}},
}),
sType: reflect.TypeOf(D{}),
want: &D{
{"foo", Binary{Subtype: 0, Data: myBytes{0, 1, 2, 3, 4, 5}}},
},
getByteSlice: func(val interface{}) []byte {
return (*(val.(*D)))[0].Value.(Binary).Data
},
},
{
description: "struct with Binary",
data: docToBytes(fooBinary{
Foo: Binary{Subtype: 0, Data: []byte{0, 1, 2, 3, 4, 5}},
}),
sType: reflect.TypeOf(fooBinary{}),
want: &fooBinary{
Foo: Binary{Subtype: 0, Data: []byte{0, 1, 2, 3, 4, 5}},
},
getByteSlice: func(val interface{}) []byte {
return (val.(*fooBinary)).Foo.Data
},
},
{
description: "bson.D with Binary",
data: docToBytes(D{
{"foo", Binary{Subtype: 0, Data: []byte{0, 1, 2, 3, 4, 5}}},
}),
sType: reflect.TypeOf(D{}),
want: &D{
{"foo", Binary{Subtype: 0, Data: []byte{0, 1, 2, 3, 4, 5}}},
},
getByteSlice: func(val interface{}) []byte {
return (*(val.(*D)))[0].Value.(Binary).Data
},
},
{
description: "struct with ObjectID",
data: docToBytes(fooObjectID{
Foo: ObjectID{0, 1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11},
}),
sType: reflect.TypeOf(fooObjectID{}),
want: &fooObjectID{
Foo: ObjectID{0, 1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11},
},
getByteSlice: func(val interface{}) []byte {
return (val.(*fooObjectID)).Foo[:]
},
},
{
description: "bson.D with ObjectID",
data: docToBytes(D{
{"foo", ObjectID{0, 1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11}},
}),
sType: reflect.TypeOf(D{}),
want: &D{
{"foo", ObjectID{0, 1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11}},
},
getByteSlice: func(val interface{}) []byte {
oid := (*(val.(*D)))[0].Value.(ObjectID)
return oid[:]
},
},
{
description: "struct with DBPointer",
data: docToBytes(fooDBPointer{
Foo: DBPointer{
DB: "test",
Pointer: ObjectID{0, 1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11},
},
}),
sType: reflect.TypeOf(fooDBPointer{}),
want: &fooDBPointer{
Foo: DBPointer{
DB: "test",
Pointer: ObjectID{0, 1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11},
},
},
getByteSlice: func(val interface{}) []byte {
return (val.(*fooDBPointer)).Foo.Pointer[:]
},
},
{
description: "bson.D with DBPointer",
data: docToBytes(D{
{"foo", DBPointer{
DB: "test",
Pointer: ObjectID{0, 1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11},
}},
}),
sType: reflect.TypeOf(D{}),
want: &D{
{"foo", DBPointer{
DB: "test",
Pointer: ObjectID{0, 1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11},
}},
},
getByteSlice: func(val interface{}) []byte {
oid := (*(val.(*D)))[0].Value.(DBPointer).Pointer
return oid[:]
},
},
}
for _, tc := range testCases {
tc := tc // Capture range variable.
t.Run(tc.description, func(t *testing.T) {
t.Parallel()
// Make a copy of the test data so we can modify it later.
data := make([]byte, len(tc.data))
copy(data, tc.data)
// Assert that unmarshaling the input data results in the expected value.
got := reflect.New(tc.sType).Interface()
err := Unmarshal(data, got)
noerr(t, err)
assert.Equal(t, tc.want, got, "unmarshaled value does not match the expected value")
// Fill the input data slice with random bytes and then assert that the result still
// matches the expected value.
_, err = rand.Read(data)
noerr(t, err)
assert.Equal(t, tc.want, got, "unmarshaled value does not match expected after modifying the input bytes")
// Assert that the byte slice in the unmarshaled value does not share any memory
// addresses with the input byte slice.
assert.DifferentAddressRanges(t, data, tc.getByteSlice(got))
})
}
}
func TestUnmarshalConcurrently(t *testing.T) {
t.Parallel()
const size = 10_000
data := []byte{16, 0, 0, 0, 10, 108, 97, 115, 116, 101, 114, 114, 111, 114, 0, 0}
wg := sync.WaitGroup{}
wg.Add(size)
for i := 0; i < size; i++ {
go func() {
defer wg.Done()
var res struct{ LastError error }
_ = Unmarshal(data, &res)
}()
}
wg.Wait()
}