@@ -9,6 +9,7 @@ package integration
9
9
import (
10
10
"context"
11
11
"testing"
12
+ "time"
12
13
13
14
"go.mongodb.org/mongo-driver/bson"
14
15
"go.mongodb.org/mongo-driver/internal/testutil/assert"
@@ -24,6 +25,7 @@ const (
24
25
func TestCursor (t * testing.T ) {
25
26
mt := mtest .New (t , mtest .NewOptions ().CreateClient (false ))
26
27
defer mt .Close ()
28
+ cappedCollectionOpts := bson.D {{"capped" , true }, {"size" , 64 * 1024 }}
27
29
28
30
// server versions 2.6 and 3.0 use OP_GET_MORE so this works on >= 3.2
29
31
mt .RunOpts ("cursor is killed on server" , mtest .NewOptions ().MinServerVersion ("3.2" ), func (mt * mtest.T ) {
@@ -53,8 +55,7 @@ func TestCursor(t *testing.T) {
53
55
defer cursor .Close (mtest .Background )
54
56
tryNextExistingBatchTest (mt , cursor )
55
57
})
56
- cappedOpts := bson.D {{"capped" , true }, {"size" , 64 * 1024 }}
57
- mt .RunOpts ("one getMore sent" , mtest .NewOptions ().CollectionCreateOptions (cappedOpts ), func (mt * mtest.T ) {
58
+ mt .RunOpts ("one getMore sent" , mtest .NewOptions ().CollectionCreateOptions (cappedCollectionOpts ), func (mt * mtest.T ) {
58
59
// If the current batch is empty, TryNext should send one getMore and return.
59
60
60
61
// insert a document because a tailable cursor will only have a non-zero ID if the initial Find matches
@@ -82,6 +83,88 @@ func TestCursor(t *testing.T) {
82
83
tryNextGetmoreError (mt , cursor )
83
84
})
84
85
})
86
+ mt .RunOpts ("RemainingBatchLength" , noClientOpts , func (mt * mtest.T ) {
87
+ cappedMtOpts := mtest .NewOptions ().CollectionCreateOptions (cappedCollectionOpts )
88
+ mt .RunOpts ("first batch is non empty" , cappedMtOpts , func (mt * mtest.T ) {
89
+ // Test that the cursor reports the correct value for RemainingBatchLength at various execution points if
90
+ // the first batch from the server is non-empty.
91
+
92
+ initCollection (mt , mt .Coll )
93
+
94
+ // Create a tailable await cursor with a low cursor timeout.
95
+ batchSize := 2
96
+ findOpts := options .Find ().
97
+ SetBatchSize (int32 (batchSize )).
98
+ SetCursorType (options .TailableAwait ).
99
+ SetMaxAwaitTime (100 * time .Millisecond )
100
+ cursor , err := mt .Coll .Find (mtest .Background , bson.D {}, findOpts )
101
+ assert .Nil (mt , err , "Find error: %v" , err )
102
+ defer cursor .Close (mtest .Background )
103
+
104
+ mt .ClearEvents ()
105
+
106
+ // The initial batch length should be equal to the batchSize. Do batchSize Next calls to exhaust the current
107
+ // batch and assert that no getMore was done.
108
+ assertCursorBatchLength (mt , cursor , batchSize )
109
+ for i := 0 ; i < batchSize ; i ++ {
110
+ prevLength := cursor .RemainingBatchLength ()
111
+ if ! cursor .Next (mtest .Background ) {
112
+ mt .Fatalf ("expected Next to return true on index %d; cursor err: %v" , i , cursor .Err ())
113
+ }
114
+
115
+ // Each successful Next call should decrement batch length by 1.
116
+ assertCursorBatchLength (mt , cursor , prevLength - 1 )
117
+ }
118
+ evt := mt .GetStartedEvent ()
119
+ assert .Nil (mt , evt , "expected no events, got %v" , evt )
120
+
121
+ // The batch is exhaused, so the batch length should be 0. Do one Next call, which should do a getMore and
122
+ // fetch batchSize more documents. The batch length after the call should be (batchSize-1) because Next consumes
123
+ // one document.
124
+ assertCursorBatchLength (mt , cursor , 0 )
125
+
126
+ assert .True (mt , cursor .Next (mtest .Background ), "expected Next to return true; cursor err: %v" , cursor .Err ())
127
+ evt = mt .GetStartedEvent ()
128
+ assert .NotNil (mt , evt , "expected CommandStartedEvent, got nil" )
129
+ assert .Equal (mt , "getMore" , evt .CommandName , "expected command %q, got %q" , "getMore" , evt .CommandName )
130
+
131
+ assertCursorBatchLength (mt , cursor , batchSize - 1 )
132
+ })
133
+ mt .RunOpts ("first batch is empty" , mtest .NewOptions ().ClientType (mtest .Mock ), func (mt * mtest.T ) {
134
+ // Test that the cursor reports the correct value for RemainingBatchLength if the first batch is empty.
135
+ // Using a mock deployment simplifies this test becuase the server won't create a valid cursor if the
136
+ // collection is empty when the find is run.
137
+
138
+ cursorID := int64 (50 )
139
+ ns := mt .DB .Name () + "." + mt .Coll .Name ()
140
+ getMoreBatch := []bson.D {
141
+ {{"x" , 1 }},
142
+ {{"x" , 2 }},
143
+ }
144
+
145
+ // Create mock responses.
146
+ find := mtest .CreateCursorResponse (cursorID , ns , mtest .FirstBatch )
147
+ getMore := mtest .CreateCursorResponse (cursorID , ns , mtest .NextBatch , getMoreBatch ... )
148
+ killCursors := mtest .CreateSuccessResponse ()
149
+ mt .AddMockResponses (find , getMore , killCursors )
150
+
151
+ cursor , err := mt .Coll .Find (mtest .Background , bson.D {})
152
+ assert .Nil (mt , err , "Find error: %v" , err )
153
+ defer cursor .Close (mtest .Background )
154
+ mt .ClearEvents ()
155
+
156
+ for {
157
+ if cursor .TryNext (mtest .Background ) {
158
+ break
159
+ }
160
+
161
+ assert .Nil (mt , cursor .Err (), "cursor error: %v" , err )
162
+ assertCursorBatchLength (mt , cursor , 0 )
163
+ }
164
+ // TryNext consumes one document so the remaining batch size should be len(getMoreBatch)-1.
165
+ assertCursorBatchLength (mt , cursor , len (getMoreBatch )- 1 )
166
+ })
167
+ })
85
168
}
86
169
87
170
type tryNextCursor interface {
@@ -133,3 +216,8 @@ func tryNextGetmoreError(mt *mtest.T, cursor tryNextCursor) {
133
216
err := cursor .Err ()
134
217
assert .NotNil (mt , err , "expected change stream error, got nil" )
135
218
}
219
+
220
+ func assertCursorBatchLength (mt * mtest.T , cursor * mongo.Cursor , expected int ) {
221
+ batchLen := cursor .RemainingBatchLength ()
222
+ assert .Equal (mt , expected , batchLen , "expected remaining batch length %d, got %d" , expected , batchLen )
223
+ }
0 commit comments