...

Source file src/go.mongodb.org/mongo-driver/bson/bsonrw/value_writer_test.go

Documentation: go.mongodb.org/mongo-driver/bson/bsonrw

     1  // Copyright (C) MongoDB, Inc. 2017-present.
     2  //
     3  // Licensed under the Apache License, Version 2.0 (the "License"); you may
     4  // not use this file except in compliance with the License. You may obtain
     5  // a copy of the License at http://www.apache.org/licenses/LICENSE-2.0
     6  
     7  package bsonrw
     8  
     9  import (
    10  	"bytes"
    11  	"errors"
    12  	"fmt"
    13  	"io/ioutil"
    14  	"math"
    15  	"reflect"
    16  	"strings"
    17  	"testing"
    18  
    19  	"go.mongodb.org/mongo-driver/bson/bsontype"
    20  	"go.mongodb.org/mongo-driver/bson/primitive"
    21  	"go.mongodb.org/mongo-driver/x/bsonx/bsoncore"
    22  )
    23  
    24  func TestNewBSONValueWriter(t *testing.T) {
    25  	_, got := NewBSONValueWriter(nil)
    26  	want := errNilWriter
    27  	if !compareErrors(got, want) {
    28  		t.Errorf("Returned error did not match what was expected. got %v; want %v", got, want)
    29  	}
    30  
    31  	vw, got := NewBSONValueWriter(errWriter{})
    32  	want = nil
    33  	if !compareErrors(got, want) {
    34  		t.Errorf("Returned error did not match what was expected. got %v; want %v", got, want)
    35  	}
    36  	if vw == nil {
    37  		t.Errorf("Expected non-nil ValueWriter to be returned from NewBSONValueWriter")
    38  	}
    39  }
    40  
    41  func TestValueWriter(t *testing.T) {
    42  	header := []byte{0x00, 0x00, 0x00, 0x00}
    43  	oid := primitive.ObjectID{0x01, 0x02, 0x03, 0x04, 0x05, 0x06, 0x07, 0x08, 0x09, 0x0A, 0x0B, 0x0C}
    44  	testCases := []struct {
    45  		name   string
    46  		fn     interface{}
    47  		params []interface{}
    48  		want   []byte
    49  	}{
    50  		{
    51  			"WriteBinary",
    52  			(*valueWriter).WriteBinary,
    53  			[]interface{}{[]byte{0x01, 0x02, 0x03}},
    54  			bsoncore.AppendBinaryElement(header, "foo", 0x00, []byte{0x01, 0x02, 0x03}),
    55  		},
    56  		{
    57  			"WriteBinaryWithSubtype (not 0x02)",
    58  			(*valueWriter).WriteBinaryWithSubtype,
    59  			[]interface{}{[]byte{0x01, 0x02, 0x03}, byte(0xFF)},
    60  			bsoncore.AppendBinaryElement(header, "foo", 0xFF, []byte{0x01, 0x02, 0x03}),
    61  		},
    62  		{
    63  			"WriteBinaryWithSubtype (0x02)",
    64  			(*valueWriter).WriteBinaryWithSubtype,
    65  			[]interface{}{[]byte{0x01, 0x02, 0x03}, byte(0x02)},
    66  			bsoncore.AppendBinaryElement(header, "foo", 0x02, []byte{0x01, 0x02, 0x03}),
    67  		},
    68  		{
    69  			"WriteBoolean",
    70  			(*valueWriter).WriteBoolean,
    71  			[]interface{}{true},
    72  			bsoncore.AppendBooleanElement(header, "foo", true),
    73  		},
    74  		{
    75  			"WriteDBPointer",
    76  			(*valueWriter).WriteDBPointer,
    77  			[]interface{}{"bar", oid},
    78  			bsoncore.AppendDBPointerElement(header, "foo", "bar", oid),
    79  		},
    80  		{
    81  			"WriteDateTime",
    82  			(*valueWriter).WriteDateTime,
    83  			[]interface{}{int64(12345678)},
    84  			bsoncore.AppendDateTimeElement(header, "foo", 12345678),
    85  		},
    86  		{
    87  			"WriteDecimal128",
    88  			(*valueWriter).WriteDecimal128,
    89  			[]interface{}{primitive.NewDecimal128(10, 20)},
    90  			bsoncore.AppendDecimal128Element(header, "foo", primitive.NewDecimal128(10, 20)),
    91  		},
    92  		{
    93  			"WriteDouble",
    94  			(*valueWriter).WriteDouble,
    95  			[]interface{}{float64(3.14159)},
    96  			bsoncore.AppendDoubleElement(header, "foo", 3.14159),
    97  		},
    98  		{
    99  			"WriteInt32",
   100  			(*valueWriter).WriteInt32,
   101  			[]interface{}{int32(123456)},
   102  			bsoncore.AppendInt32Element(header, "foo", 123456),
   103  		},
   104  		{
   105  			"WriteInt64",
   106  			(*valueWriter).WriteInt64,
   107  			[]interface{}{int64(1234567890)},
   108  			bsoncore.AppendInt64Element(header, "foo", 1234567890),
   109  		},
   110  		{
   111  			"WriteJavascript",
   112  			(*valueWriter).WriteJavascript,
   113  			[]interface{}{"var foo = 'bar';"},
   114  			bsoncore.AppendJavaScriptElement(header, "foo", "var foo = 'bar';"),
   115  		},
   116  		{
   117  			"WriteMaxKey",
   118  			(*valueWriter).WriteMaxKey,
   119  			[]interface{}{},
   120  			bsoncore.AppendMaxKeyElement(header, "foo"),
   121  		},
   122  		{
   123  			"WriteMinKey",
   124  			(*valueWriter).WriteMinKey,
   125  			[]interface{}{},
   126  			bsoncore.AppendMinKeyElement(header, "foo"),
   127  		},
   128  		{
   129  			"WriteNull",
   130  			(*valueWriter).WriteNull,
   131  			[]interface{}{},
   132  			bsoncore.AppendNullElement(header, "foo"),
   133  		},
   134  		{
   135  			"WriteObjectID",
   136  			(*valueWriter).WriteObjectID,
   137  			[]interface{}{oid},
   138  			bsoncore.AppendObjectIDElement(header, "foo", oid),
   139  		},
   140  		{
   141  			"WriteRegex",
   142  			(*valueWriter).WriteRegex,
   143  			[]interface{}{"bar", "baz"},
   144  			bsoncore.AppendRegexElement(header, "foo", "bar", "abz"),
   145  		},
   146  		{
   147  			"WriteString",
   148  			(*valueWriter).WriteString,
   149  			[]interface{}{"hello, world!"},
   150  			bsoncore.AppendStringElement(header, "foo", "hello, world!"),
   151  		},
   152  		{
   153  			"WriteSymbol",
   154  			(*valueWriter).WriteSymbol,
   155  			[]interface{}{"symbollolz"},
   156  			bsoncore.AppendSymbolElement(header, "foo", "symbollolz"),
   157  		},
   158  		{
   159  			"WriteTimestamp",
   160  			(*valueWriter).WriteTimestamp,
   161  			[]interface{}{uint32(10), uint32(20)},
   162  			bsoncore.AppendTimestampElement(header, "foo", 10, 20),
   163  		},
   164  		{
   165  			"WriteUndefined",
   166  			(*valueWriter).WriteUndefined,
   167  			[]interface{}{},
   168  			bsoncore.AppendUndefinedElement(header, "foo"),
   169  		},
   170  	}
   171  
   172  	for _, tc := range testCases {
   173  		t.Run(tc.name, func(t *testing.T) {
   174  			fn := reflect.ValueOf(tc.fn)
   175  			if fn.Kind() != reflect.Func {
   176  				t.Fatalf("fn must be of kind Func but it is a %v", fn.Kind())
   177  			}
   178  			if fn.Type().NumIn() != len(tc.params)+1 || fn.Type().In(0) != reflect.TypeOf((*valueWriter)(nil)) {
   179  				t.Fatalf("fn must have at least one parameter and the first parameter must be a *valueWriter")
   180  			}
   181  			if fn.Type().NumOut() != 1 || fn.Type().Out(0) != reflect.TypeOf((*error)(nil)).Elem() {
   182  				t.Fatalf("fn must have one return value and it must be an error.")
   183  			}
   184  			params := make([]reflect.Value, 1, len(tc.params)+1)
   185  			vw := newValueWriter(ioutil.Discard)
   186  			params[0] = reflect.ValueOf(vw)
   187  			for _, param := range tc.params {
   188  				params = append(params, reflect.ValueOf(param))
   189  			}
   190  			_, err := vw.WriteDocument()
   191  			noerr(t, err)
   192  			_, err = vw.WriteDocumentElement("foo")
   193  			noerr(t, err)
   194  
   195  			results := fn.Call(params)
   196  			if !results[0].IsValid() {
   197  				err = results[0].Interface().(error)
   198  			} else {
   199  				err = nil
   200  			}
   201  			noerr(t, err)
   202  			got := vw.buf
   203  			want := tc.want
   204  			if !bytes.Equal(got, want) {
   205  				t.Errorf("Bytes are not equal.\n\tgot %v\n\twant %v", got, want)
   206  			}
   207  
   208  			t.Run("incorrect transition", func(t *testing.T) {
   209  				vw = newValueWriter(ioutil.Discard)
   210  				results := fn.Call(params)
   211  				got := results[0].Interface().(error)
   212  				fnName := tc.name
   213  				if strings.Contains(fnName, "WriteBinary") {
   214  					fnName = "WriteBinaryWithSubtype"
   215  				}
   216  				want := TransitionError{current: mTopLevel, name: fnName, modes: []mode{mElement, mValue},
   217  					action: "write"}
   218  				if !compareErrors(got, want) {
   219  					t.Errorf("Errors do not match. got %v; want %v", got, want)
   220  				}
   221  			})
   222  		})
   223  	}
   224  
   225  	t.Run("WriteArray", func(t *testing.T) {
   226  		vw := newValueWriter(ioutil.Discard)
   227  		vw.push(mArray)
   228  		want := TransitionError{current: mArray, destination: mArray, parent: mTopLevel,
   229  			name: "WriteArray", modes: []mode{mElement, mValue}, action: "write"}
   230  		_, got := vw.WriteArray()
   231  		if !compareErrors(got, want) {
   232  			t.Errorf("Did not get expected error. got %v; want %v", got, want)
   233  		}
   234  	})
   235  	t.Run("WriteCodeWithScope", func(t *testing.T) {
   236  		vw := newValueWriter(ioutil.Discard)
   237  		vw.push(mArray)
   238  		want := TransitionError{current: mArray, destination: mCodeWithScope, parent: mTopLevel,
   239  			name: "WriteCodeWithScope", modes: []mode{mElement, mValue}, action: "write"}
   240  		_, got := vw.WriteCodeWithScope("")
   241  		if !compareErrors(got, want) {
   242  			t.Errorf("Did not get expected error. got %v; want %v", got, want)
   243  		}
   244  	})
   245  	t.Run("WriteDocument", func(t *testing.T) {
   246  		vw := newValueWriter(ioutil.Discard)
   247  		vw.push(mArray)
   248  		want := TransitionError{current: mArray, destination: mDocument, parent: mTopLevel,
   249  			name: "WriteDocument", modes: []mode{mElement, mValue, mTopLevel}, action: "write"}
   250  		_, got := vw.WriteDocument()
   251  		if !compareErrors(got, want) {
   252  			t.Errorf("Did not get expected error. got %v; want %v", got, want)
   253  		}
   254  	})
   255  	t.Run("WriteDocumentElement", func(t *testing.T) {
   256  		vw := newValueWriter(ioutil.Discard)
   257  		vw.push(mElement)
   258  		want := TransitionError{current: mElement,
   259  			destination: mElement,
   260  			parent:      mTopLevel,
   261  			name:        "WriteDocumentElement",
   262  			modes:       []mode{mTopLevel, mDocument},
   263  			action:      "write"}
   264  		_, got := vw.WriteDocumentElement("")
   265  		if !compareErrors(got, want) {
   266  			t.Errorf("Did not get expected error. got %v; want %v", got, want)
   267  		}
   268  	})
   269  	t.Run("WriteDocumentEnd", func(t *testing.T) {
   270  		vw := newValueWriter(ioutil.Discard)
   271  		vw.push(mElement)
   272  		want := fmt.Errorf("incorrect mode to end document: %s", mElement)
   273  		got := vw.WriteDocumentEnd()
   274  		if !compareErrors(got, want) {
   275  			t.Errorf("Did not get expected error. got %v; want %v", got, want)
   276  		}
   277  		vw.pop()
   278  		vw.buf = append(vw.buf, make([]byte, 1023)...)
   279  		maxSize = 512
   280  		want = errMaxDocumentSizeExceeded{size: 1024}
   281  		got = vw.WriteDocumentEnd()
   282  		if !compareErrors(got, want) {
   283  			t.Errorf("Did not get expected error. got %v; want %v", got, want)
   284  		}
   285  		maxSize = math.MaxInt32
   286  		want = errors.New("what a nice fake error we have here")
   287  		vw.w = errWriter{err: want}
   288  		got = vw.WriteDocumentEnd()
   289  		if !compareErrors(got, want) {
   290  			t.Errorf("Did not get expected error. got %v; want %v", got, want)
   291  		}
   292  	})
   293  	t.Run("WriteArrayElement", func(t *testing.T) {
   294  		vw := newValueWriter(ioutil.Discard)
   295  		vw.push(mElement)
   296  		want := TransitionError{current: mElement,
   297  			destination: mValue,
   298  			parent:      mTopLevel,
   299  			name:        "WriteArrayElement",
   300  			modes:       []mode{mArray},
   301  			action:      "write"}
   302  		_, got := vw.WriteArrayElement()
   303  		if !compareErrors(got, want) {
   304  			t.Errorf("Did not get expected error. got %v; want %v", got, want)
   305  		}
   306  	})
   307  	t.Run("WriteArrayEnd", func(t *testing.T) {
   308  		vw := newValueWriter(ioutil.Discard)
   309  		vw.push(mElement)
   310  		want := fmt.Errorf("incorrect mode to end array: %s", mElement)
   311  		got := vw.WriteArrayEnd()
   312  		if !compareErrors(got, want) {
   313  			t.Errorf("Did not get expected error. got %v; want %v", got, want)
   314  		}
   315  		vw.push(mArray)
   316  		vw.buf = append(vw.buf, make([]byte, 1019)...)
   317  		maxSize = 512
   318  		want = errMaxDocumentSizeExceeded{size: 1024}
   319  		got = vw.WriteArrayEnd()
   320  		if !compareErrors(got, want) {
   321  			t.Errorf("Did not get expected error. got %v; want %v", got, want)
   322  		}
   323  		maxSize = math.MaxInt32
   324  	})
   325  
   326  	t.Run("WriteBytes", func(t *testing.T) {
   327  		t.Run("writeElementHeader error", func(t *testing.T) {
   328  			vw := newValueWriterFromSlice(nil)
   329  			want := TransitionError{current: mTopLevel, destination: mode(0),
   330  				name: "WriteValueBytes", modes: []mode{mElement, mValue}, action: "write"}
   331  			got := vw.WriteValueBytes(bsontype.EmbeddedDocument, nil)
   332  			if !compareErrors(got, want) {
   333  				t.Errorf("Did not received expected error. got %v; want %v", got, want)
   334  			}
   335  		})
   336  		t.Run("success", func(t *testing.T) {
   337  			index, doc := bsoncore.ReserveLength(nil)
   338  			doc = bsoncore.AppendStringElement(doc, "hello", "world")
   339  			doc = append(doc, 0x00)
   340  			doc = bsoncore.UpdateLength(doc, index, int32(len(doc)))
   341  
   342  			index, want := bsoncore.ReserveLength(nil)
   343  			want = bsoncore.AppendDocumentElement(want, "foo", doc)
   344  			want = append(want, 0x00)
   345  			want = bsoncore.UpdateLength(want, index, int32(len(want)))
   346  
   347  			vw := newValueWriterFromSlice(make([]byte, 0, 512))
   348  			_, err := vw.WriteDocument()
   349  			noerr(t, err)
   350  			_, err = vw.WriteDocumentElement("foo")
   351  			noerr(t, err)
   352  			err = vw.WriteValueBytes(bsontype.EmbeddedDocument, doc)
   353  			noerr(t, err)
   354  			err = vw.WriteDocumentEnd()
   355  			noerr(t, err)
   356  			got := vw.buf
   357  			if !bytes.Equal(got, want) {
   358  				t.Errorf("Bytes are not equal. got %v; want %v", got, want)
   359  			}
   360  		})
   361  	})
   362  }
   363  
   364  type errWriter struct {
   365  	err error
   366  }
   367  
   368  func (ew errWriter) Write([]byte) (int, error) { return 0, ew.err }
   369  

View as plain text