819 lines
21 KiB
Go
819 lines
21 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 (
|
||
"math/rand"
|
||
"reflect"
|
||
"testing"
|
||
"unsafe"
|
||
|
||
"github.com/stretchr/testify/assert"
|
||
"go.mongodb.org/mongo-driver/bson/bsoncodec"
|
||
"go.mongodb.org/mongo-driver/bson/bsonrw"
|
||
"go.mongodb.org/mongo-driver/bson/primitive"
|
||
"go.mongodb.org/mongo-driver/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) {
|
||
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 := UnmarshalWithRegistry(DefaultRegistry, 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 TestUnmarshalWithContext(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.
|
||
dc := bsoncodec.DecodeContext{Registry: DefaultRegistry}
|
||
got := reflect.New(tc.sType).Interface()
|
||
err := UnmarshalWithContext(dc, 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 TestUnmarshalExtJSONWithRegistry(t *testing.T) {
|
||
t.Run("UnmarshalExtJSONWithContext", func(t *testing.T) {
|
||
type teststruct struct{ Foo int }
|
||
var got teststruct
|
||
data := []byte("{\"foo\":1}")
|
||
err := UnmarshalExtJSONWithRegistry(DefaultRegistry, data, true, &got)
|
||
noerr(t, err)
|
||
want := teststruct{1}
|
||
assert.Equal(t, want, got, "Did not unmarshal as expected.")
|
||
})
|
||
|
||
t.Run("UnmarshalExtJSONInvalidInput", func(t *testing.T) {
|
||
data := []byte("invalid")
|
||
err := UnmarshalExtJSONWithRegistry(DefaultRegistry, data, true, &M{})
|
||
if err != bsonrw.ErrInvalidJSON {
|
||
t.Fatalf("wanted ErrInvalidJSON, got %v", err)
|
||
}
|
||
})
|
||
}
|
||
|
||
func TestUnmarshalExtJSONWithContext(t *testing.T) {
|
||
type fooInt struct {
|
||
Foo int
|
||
}
|
||
|
||
type fooString struct {
|
||
Foo string
|
||
}
|
||
|
||
type fooBytes struct {
|
||
Foo []byte
|
||
}
|
||
|
||
var cases = []struct {
|
||
name string
|
||
sType reflect.Type
|
||
want interface{}
|
||
data []byte
|
||
}{
|
||
{
|
||
name: "Small struct",
|
||
sType: reflect.TypeOf(fooInt{}),
|
||
data: []byte(`{"foo":1}`),
|
||
want: &fooInt{Foo: 1},
|
||
},
|
||
{
|
||
name: "Valid surrogate pair",
|
||
sType: reflect.TypeOf(fooString{}),
|
||
data: []byte(`{"foo":"\uD834\uDd1e"}`),
|
||
want: &fooString{Foo: "𝄞"},
|
||
},
|
||
{
|
||
name: "Valid surrogate pair with other values",
|
||
sType: reflect.TypeOf(fooString{}),
|
||
data: []byte(`{"foo":"abc \uD834\uDd1e 123"}`),
|
||
want: &fooString{Foo: "abc 𝄞 123"},
|
||
},
|
||
{
|
||
name: "High surrogate value with no following low surrogate value",
|
||
sType: reflect.TypeOf(fooString{}),
|
||
data: []byte(`{"foo":"abc \uD834 123"}`),
|
||
want: &fooString{Foo: "abc <20> 123"},
|
||
},
|
||
{
|
||
name: "High surrogate value at end of string",
|
||
sType: reflect.TypeOf(fooString{}),
|
||
data: []byte(`{"foo":"\uD834"}`),
|
||
want: &fooString{Foo: "<22>"},
|
||
},
|
||
{
|
||
name: "Low surrogate value with no preceding high surrogate value",
|
||
sType: reflect.TypeOf(fooString{}),
|
||
data: []byte(`{"foo":"abc \uDd1e 123"}`),
|
||
want: &fooString{Foo: "abc <20> 123"},
|
||
},
|
||
{
|
||
name: "Low surrogate value at end of string",
|
||
sType: reflect.TypeOf(fooString{}),
|
||
data: []byte(`{"foo":"\uDd1e"}`),
|
||
want: &fooString{Foo: "<22>"},
|
||
},
|
||
{
|
||
name: "High surrogate value with non-surrogate unicode value",
|
||
sType: reflect.TypeOf(fooString{}),
|
||
data: []byte(`{"foo":"\uD834\u00BF"}`),
|
||
want: &fooString{Foo: "<22>¿"},
|
||
},
|
||
// GODRIVER-2311
|
||
// Test that ExtJSON-encoded binary unmarshals correctly to a bson.D and that the
|
||
// unmarshaled value does not reference the same underlying byte array as the input.
|
||
{
|
||
name: "bson.D with binary",
|
||
sType: reflect.TypeOf(D{}),
|
||
data: []byte(`{"foo": {"$binary": {"subType": "0", "base64": "AAECAwQF"}}}`),
|
||
want: &D{{"foo", primitive.Binary{Subtype: 0, Data: []byte{0, 1, 2, 3, 4, 5}}}},
|
||
},
|
||
// GODRIVER-2311
|
||
// Test that ExtJSON-encoded binary unmarshals correctly to a struct and that the
|
||
// unmarshaled value does not reference thesame underlying byte array as the input.
|
||
{
|
||
name: "struct with binary",
|
||
sType: reflect.TypeOf(fooBytes{}),
|
||
data: []byte(`{"foo": {"$binary": {"subType": "0", "base64": "AAECAwQF"}}}`),
|
||
want: &fooBytes{Foo: []byte{0, 1, 2, 3, 4, 5}},
|
||
},
|
||
}
|
||
|
||
for _, tc := range cases {
|
||
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()
|
||
dc := bsoncodec.DecodeContext{Registry: DefaultRegistry}
|
||
err := UnmarshalExtJSONWithContext(dc, data, true, 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 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 bsoncodec.ValueDecoderFunc = func(_ bsoncodec.DecodeContext, vr bsonrw.ValueReader, val reflect.Value) error {
|
||
i32, err := vr.ReadInt32()
|
||
if err != nil {
|
||
return err
|
||
}
|
||
|
||
val.SetInt(int64(-1 * i32))
|
||
return nil
|
||
}
|
||
customReg := NewRegistryBuilder().
|
||
RegisterTypeDecoder(tInt32, decodeInt32).
|
||
Build()
|
||
|
||
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
|
||
err = UnmarshalWithRegistry(customReg, docBytes, &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
|
||
err = UnmarshalWithRegistry(customReg, docBytes, &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 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 primitive.Binary
|
||
}
|
||
|
||
type fooObjectID struct {
|
||
Foo primitive.ObjectID
|
||
}
|
||
|
||
type fooDBPointer struct {
|
||
Foo primitive.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", primitive.Binary{Subtype: 0, Data: []byte{0, 1, 2, 3, 4, 5}}},
|
||
},
|
||
getByteSlice: func(val interface{}) []byte {
|
||
return (*(val.(*D)))[0].Value.(primitive.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", primitive.Binary{Subtype: 0, Data: myBytes{0, 1, 2, 3, 4, 5}}},
|
||
},
|
||
getByteSlice: func(val interface{}) []byte {
|
||
return (*(val.(*D)))[0].Value.(primitive.Binary).Data
|
||
},
|
||
},
|
||
{
|
||
description: "struct with primitive.Binary",
|
||
data: docToBytes(fooBinary{
|
||
Foo: primitive.Binary{Subtype: 0, Data: []byte{0, 1, 2, 3, 4, 5}},
|
||
}),
|
||
sType: reflect.TypeOf(fooBinary{}),
|
||
want: &fooBinary{
|
||
Foo: primitive.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 primitive.Binary",
|
||
data: docToBytes(D{
|
||
{"foo", primitive.Binary{Subtype: 0, Data: []byte{0, 1, 2, 3, 4, 5}}},
|
||
}),
|
||
sType: reflect.TypeOf(D{}),
|
||
want: &D{
|
||
{"foo", primitive.Binary{Subtype: 0, Data: []byte{0, 1, 2, 3, 4, 5}}},
|
||
},
|
||
getByteSlice: func(val interface{}) []byte {
|
||
return (*(val.(*D)))[0].Value.(primitive.Binary).Data
|
||
},
|
||
},
|
||
{
|
||
description: "struct with primitive.ObjectID",
|
||
data: docToBytes(fooObjectID{
|
||
Foo: primitive.ObjectID{0, 1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11},
|
||
}),
|
||
sType: reflect.TypeOf(fooObjectID{}),
|
||
want: &fooObjectID{
|
||
Foo: primitive.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 primitive.ObjectID",
|
||
data: docToBytes(D{
|
||
{"foo", primitive.ObjectID{0, 1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11}},
|
||
}),
|
||
sType: reflect.TypeOf(D{}),
|
||
want: &D{
|
||
{"foo", primitive.ObjectID{0, 1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11}},
|
||
},
|
||
getByteSlice: func(val interface{}) []byte {
|
||
oid := (*(val.(*D)))[0].Value.(primitive.ObjectID)
|
||
return oid[:]
|
||
},
|
||
},
|
||
{
|
||
description: "struct with primitive.DBPointer",
|
||
data: docToBytes(fooDBPointer{
|
||
Foo: primitive.DBPointer{
|
||
DB: "test",
|
||
Pointer: primitive.ObjectID{0, 1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11},
|
||
},
|
||
}),
|
||
sType: reflect.TypeOf(fooDBPointer{}),
|
||
want: &fooDBPointer{
|
||
Foo: primitive.DBPointer{
|
||
DB: "test",
|
||
Pointer: primitive.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 primitive.DBPointer",
|
||
data: docToBytes(D{
|
||
{"foo", primitive.DBPointer{
|
||
DB: "test",
|
||
Pointer: primitive.ObjectID{0, 1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11},
|
||
}},
|
||
}),
|
||
sType: reflect.TypeOf(D{}),
|
||
want: &D{
|
||
{"foo", primitive.DBPointer{
|
||
DB: "test",
|
||
Pointer: primitive.ObjectID{0, 1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11},
|
||
}},
|
||
},
|
||
getByteSlice: func(val interface{}) []byte {
|
||
oid := (*(val.(*D)))[0].Value.(primitive.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.
|
||
assertDifferentArrays(t, data, tc.getByteSlice(got))
|
||
})
|
||
}
|
||
}
|
||
|
||
// assertDifferentArrays asserts that two byte slices reference distinct memory ranges, meaning
|
||
// they reference different underlying byte arrays.
|
||
func assertDifferentArrays(t *testing.T, a, b []byte) {
|
||
// Find the start and end memory addresses for the underlying byte array for each input byte
|
||
// slice.
|
||
sliceAddrRange := func(b []byte) (uintptr, uintptr) {
|
||
sh := (*reflect.SliceHeader)(unsafe.Pointer(&b))
|
||
return sh.Data, sh.Data + uintptr(sh.Cap-1)
|
||
}
|
||
aStart, aEnd := sliceAddrRange(a)
|
||
bStart, bEnd := sliceAddrRange(b)
|
||
|
||
// If "b" starts after "a" ends or "a" starts after "b" ends, there is no overlap.
|
||
if bStart > aEnd || aStart > bEnd {
|
||
return
|
||
}
|
||
|
||
// Otherwise, calculate the overlap start and end and print the memory overlap error message.
|
||
min := func(a, b uintptr) uintptr {
|
||
if a < b {
|
||
return a
|
||
}
|
||
return b
|
||
}
|
||
max := func(a, b uintptr) uintptr {
|
||
if a > b {
|
||
return a
|
||
}
|
||
return b
|
||
}
|
||
overlapLow := max(aStart, bStart)
|
||
overlapHigh := min(aEnd, bEnd)
|
||
|
||
t.Errorf("Byte slices point to the same the same underlying byte array:\n"+
|
||
"\ta addresses:\t%d ... %d\n"+
|
||
"\tb addresses:\t%d ... %d\n"+
|
||
"\toverlap:\t%d ... %d",
|
||
aStart, aEnd,
|
||
bStart, bEnd,
|
||
overlapLow, overlapHigh)
|
||
}
|