|
7 | 7 | package driver |
8 | 8 |
|
9 | 9 | import ( |
| 10 | + "strconv" |
10 | 11 | "testing" |
11 | 12 |
|
12 | 13 | "go.mongodb.org/mongo-driver/v2/internal/assert" |
13 | 14 | "go.mongodb.org/mongo-driver/v2/x/bsonx/bsoncore" |
14 | 15 | "go.mongodb.org/mongo-driver/v2/x/mongo/driver/wiremessage" |
15 | 16 | ) |
16 | 17 |
|
17 | | -func newTestBatches(t *testing.T) *Batches { |
18 | | - t.Helper() |
19 | | - return &Batches{ |
20 | | - Identifier: "foobar", |
21 | | - Documents: []bsoncore.Document{ |
22 | | - []byte("Lorem ipsum dolor sit amet"), |
23 | | - []byte("consectetur adipiscing elit"), |
24 | | - }, |
| 18 | +const testIdentifier = "foobar" |
| 19 | + |
| 20 | +var testDocs = [][]byte{ |
| 21 | + []byte("Lorem ipsum dolor sit amet"), |
| 22 | + []byte("consectetur adipiscing elit"), |
| 23 | +} |
| 24 | + |
| 25 | +// newBatches builds a Batches with the test identifier from docs. |
| 26 | +func newBatches(docs ...[]byte) *Batches { |
| 27 | + b := &Batches{Identifier: testIdentifier} |
| 28 | + for _, doc := range docs { |
| 29 | + b.Documents = append(b.Documents, bsoncore.Document(doc)) |
| 30 | + } |
| 31 | + return b |
| 32 | +} |
| 33 | + |
| 34 | +// wantSequence builds the expected AppendBatchSequence output: prefix followed by |
| 35 | +// a document sequence section holding docs. |
| 36 | +func wantSequence(prefix []byte, docs [][]byte) []byte { |
| 37 | + dst := append([]byte(nil), prefix...) |
| 38 | + dst = wiremessage.AppendMsgSectionType(dst, wiremessage.DocumentSequence) |
| 39 | + idx, dst := bsoncore.ReserveLength(dst) |
| 40 | + dst = append(dst, testIdentifier...) |
| 41 | + dst = append(dst, 0x00) |
| 42 | + for _, doc := range docs { |
| 43 | + dst = append(dst, doc...) |
25 | 44 | } |
| 45 | + return bsoncore.UpdateLength(dst, idx, int32(len(dst[idx:]))) |
| 46 | +} |
| 47 | + |
| 48 | +// wantArray builds the expected AppendBatchArray output using the straightforward |
| 49 | +// bsoncore.AppendDocumentElement / strconv.Itoa construction. Comparing against it |
| 50 | +// also verifies AppendBatchArray's in-place integer-key encoding. |
| 51 | +func wantArray(prefix []byte, docs [][]byte) []byte { |
| 52 | + dst := append([]byte(nil), prefix...) |
| 53 | + idx, dst := bsoncore.AppendArrayElementStart(dst, testIdentifier) |
| 54 | + for i, doc := range docs { |
| 55 | + dst = bsoncore.AppendDocumentElement(dst, strconv.Itoa(i), doc) |
| 56 | + } |
| 57 | + dst, _ = bsoncore.AppendArrayEnd(dst, idx) |
| 58 | + return dst |
| 59 | +} |
| 60 | + |
| 61 | +// benchBatches builds a Batches of numDocs identical documents of docSize bytes. |
| 62 | +func benchBatches(numDocs, docSize int) *Batches { |
| 63 | + b := &Batches{Identifier: "documents"} |
| 64 | + doc := bsoncore.Document(make([]byte, docSize)) |
| 65 | + for i := 0; i < numDocs; i++ { |
| 66 | + b.Documents = append(b.Documents, doc) |
| 67 | + } |
| 68 | + return b |
26 | 69 | } |
27 | 70 |
|
28 | 71 | func TestAdvancing(t *testing.T) { |
29 | | - batches := newTestBatches(t) |
| 72 | + batches := newBatches(testDocs...) |
30 | 73 | batches.AdvanceBatches(3) |
31 | 74 | size := batches.Size() |
32 | | - assert.Equal(t, 0, size, "expected Size(): %d, got: %d", 1, size) |
| 75 | + assert.Equal(t, 0, size, "expected Size(): %d, got: %d", 0, size) |
33 | 76 | } |
34 | 77 |
|
35 | 78 | func TestAppendBatchSequence(t *testing.T) { |
36 | | - batches := newTestBatches(t) |
37 | | - |
38 | | - got := []byte{42} |
39 | | - sizeLimit := len(batches.Documents[0]) + len(batches.Documents[1]) |
40 | | - var n int |
41 | | - var err error |
42 | | - n, got, err = batches.AppendBatchSequence(got, 2, sizeLimit) |
43 | | - assert.NoError(t, err) |
44 | | - assert.Equal(t, 1, n) |
45 | | - |
46 | | - var idx int32 |
47 | | - dst := []byte{42} |
48 | | - dst = wiremessage.AppendMsgSectionType(dst, wiremessage.DocumentSequence) |
49 | | - idx, dst = bsoncore.ReserveLength(dst) |
50 | | - dst = append(dst, "foobar"...) |
51 | | - dst = append(dst, 0x00) |
52 | | - dst = append(dst, "Lorem ipsum dolor sit amet"...) |
53 | | - dst = bsoncore.UpdateLength(dst, idx, int32(len(dst[idx:]))) |
54 | | - assert.Equal(t, dst, got) |
| 79 | + tests := []struct { |
| 80 | + name string |
| 81 | + docs [][]byte |
| 82 | + advance int |
| 83 | + maxCount int |
| 84 | + sizeLimit int |
| 85 | + want [][]byte |
| 86 | + }{ |
| 87 | + { |
| 88 | + name: "size limit fits only the first document", |
| 89 | + docs: testDocs, |
| 90 | + maxCount: 2, |
| 91 | + sizeLimit: len(testDocs[0]) + len(testDocs[1]), |
| 92 | + want: testDocs[:1], |
| 93 | + }, |
| 94 | + { |
| 95 | + name: "all documents fit", |
| 96 | + docs: testDocs, |
| 97 | + maxCount: 10, |
| 98 | + sizeLimit: 16 * 1024 * 1024, |
| 99 | + want: testDocs, |
| 100 | + }, |
| 101 | + { |
| 102 | + name: "offset skips leading documents", |
| 103 | + docs: testDocs, |
| 104 | + advance: 1, |
| 105 | + maxCount: 10, |
| 106 | + sizeLimit: 16 * 1024 * 1024, |
| 107 | + want: testDocs[1:], |
| 108 | + }, |
| 109 | + } |
| 110 | + for _, tt := range tests { |
| 111 | + t.Run(tt.name, func(t *testing.T) { |
| 112 | + batches := newBatches(tt.docs...) |
| 113 | + batches.AdvanceBatches(tt.advance) |
| 114 | + |
| 115 | + n, got, err := batches.AppendBatchSequence([]byte{42}, tt.maxCount, tt.sizeLimit) |
| 116 | + assert.NoError(t, err) |
| 117 | + assert.Equal(t, len(tt.want), n) |
| 118 | + assert.Equal(t, wantSequence([]byte{42}, tt.want), got) |
| 119 | + }) |
| 120 | + } |
55 | 121 | } |
56 | 122 |
|
57 | 123 | func TestAppendBatchArray(t *testing.T) { |
58 | | - batches := newTestBatches(t) |
59 | | - |
60 | | - got := []byte{42} |
61 | | - sizeLimit := len(batches.Documents[0]) + len(batches.Documents[1]) |
62 | | - var n int |
63 | | - var err error |
64 | | - n, got, err = batches.AppendBatchArray(got, 2, sizeLimit) |
65 | | - assert.NoError(t, err) |
66 | | - assert.Equal(t, 1, n) |
67 | | - |
68 | | - var idx int32 |
69 | | - dst := []byte{42} |
70 | | - idx, dst = bsoncore.AppendArrayElementStart(dst, "foobar") |
71 | | - dst = bsoncore.AppendDocumentElement(dst, "0", []byte("Lorem ipsum dolor sit amet")) |
72 | | - dst, err = bsoncore.AppendArrayEnd(dst, idx) |
73 | | - assert.NoError(t, err) |
74 | | - assert.Equal(t, dst, got) |
| 124 | + manyDocs := make([][]byte, 150) // exercises multi-digit array keys |
| 125 | + for i := range manyDocs { |
| 126 | + manyDocs[i] = testDocs[0] |
| 127 | + } |
| 128 | + |
| 129 | + tests := []struct { |
| 130 | + name string |
| 131 | + docs [][]byte |
| 132 | + advance int |
| 133 | + maxCount int |
| 134 | + sizeLimit int |
| 135 | + want [][]byte |
| 136 | + }{ |
| 137 | + { |
| 138 | + name: "size limit fits only the first document", |
| 139 | + docs: testDocs, |
| 140 | + maxCount: 2, |
| 141 | + sizeLimit: len(testDocs[0]) + len(testDocs[1]), |
| 142 | + want: testDocs[:1], |
| 143 | + }, |
| 144 | + { |
| 145 | + name: "all documents fit", |
| 146 | + docs: testDocs, |
| 147 | + maxCount: 10, |
| 148 | + sizeLimit: 16 * 1024 * 1024, |
| 149 | + want: testDocs, |
| 150 | + }, |
| 151 | + { |
| 152 | + name: "offset skips leading documents", |
| 153 | + docs: testDocs, |
| 154 | + advance: 1, |
| 155 | + maxCount: 10, |
| 156 | + sizeLimit: 16 * 1024 * 1024, |
| 157 | + want: testDocs[1:], |
| 158 | + }, |
| 159 | + { |
| 160 | + name: "many elements with multi-digit keys", |
| 161 | + docs: manyDocs, |
| 162 | + maxCount: len(manyDocs), |
| 163 | + sizeLimit: 16 * 1024 * 1024, |
| 164 | + want: manyDocs, |
| 165 | + }, |
| 166 | + } |
| 167 | + for _, tt := range tests { |
| 168 | + t.Run(tt.name, func(t *testing.T) { |
| 169 | + batches := newBatches(tt.docs...) |
| 170 | + batches.AdvanceBatches(tt.advance) |
| 171 | + |
| 172 | + n, got, err := batches.AppendBatchArray([]byte{42}, tt.maxCount, tt.sizeLimit) |
| 173 | + assert.NoError(t, err) |
| 174 | + assert.Equal(t, len(tt.want), n) |
| 175 | + assert.Equal(t, wantArray([]byte{42}, tt.want), got) |
| 176 | + }) |
| 177 | + } |
| 178 | +} |
| 179 | + |
| 180 | +func BenchmarkAppendBatch(b *testing.B) { |
| 181 | + batches := benchBatches(1000, 256) |
| 182 | + |
| 183 | + benchmarks := []struct { |
| 184 | + name string |
| 185 | + fn func(*Batches, []byte, int, int) (int, []byte, error) |
| 186 | + }{ |
| 187 | + {"Sequence", (*Batches).AppendBatchSequence}, |
| 188 | + {"Array", (*Batches).AppendBatchArray}, |
| 189 | + } |
| 190 | + for _, bm := range benchmarks { |
| 191 | + b.Run(bm.name, func(b *testing.B) { |
| 192 | + b.ReportAllocs() |
| 193 | + for i := 0; i < b.N; i++ { |
| 194 | + batches.offset = 0 |
| 195 | + // A nil buffer measures the growth a BulkWrite pays per operation. |
| 196 | + if _, _, err := bm.fn(batches, nil, len(batches.Documents), 16*1024*1024); err != nil { |
| 197 | + b.Fatal(err) |
| 198 | + } |
| 199 | + } |
| 200 | + }) |
| 201 | + } |
75 | 202 | } |
0 commit comments