mongo 1.11.1 → 1.12.0.rc0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (39) hide show
  1. checksums.yaml +4 -4
  2. checksums.yaml.gz.sig +0 -0
  3. data.tar.gz.sig +0 -0
  4. data/VERSION +1 -1
  5. data/lib/mongo/collection.rb +8 -3
  6. data/lib/mongo/collection_writer.rb +1 -1
  7. data/lib/mongo/connection/socket/unix_socket.rb +1 -1
  8. data/lib/mongo/cursor.rb +8 -1
  9. data/lib/mongo/db.rb +61 -33
  10. data/lib/mongo/exception.rb +57 -0
  11. data/lib/mongo/functional.rb +1 -0
  12. data/lib/mongo/functional/authentication.rb +138 -11
  13. data/lib/mongo/functional/read_preference.rb +31 -22
  14. data/lib/mongo/functional/scram.rb +555 -0
  15. data/lib/mongo/functional/uri_parser.rb +107 -79
  16. data/lib/mongo/mongo_client.rb +19 -24
  17. data/lib/mongo/mongo_replica_set_client.rb +2 -1
  18. data/test/functional/authentication_test.rb +3 -0
  19. data/test/functional/client_test.rb +33 -0
  20. data/test/functional/collection_test.rb +29 -19
  21. data/test/functional/db_api_test.rb +16 -1
  22. data/test/functional/pool_test.rb +7 -6
  23. data/test/functional/uri_test.rb +111 -7
  24. data/test/helpers/test_unit.rb +17 -3
  25. data/test/replica_set/client_test.rb +31 -0
  26. data/test/replica_set/insert_test.rb +49 -32
  27. data/test/replica_set/pinning_test.rb +50 -0
  28. data/test/replica_set/query_test.rb +1 -1
  29. data/test/replica_set/replication_ack_test.rb +3 -3
  30. data/test/shared/authentication/basic_auth_shared.rb +14 -1
  31. data/test/shared/authentication/gssapi_shared.rb +13 -8
  32. data/test/shared/authentication/scram_shared.rb +92 -0
  33. data/test/tools/mongo_config.rb +18 -6
  34. data/test/unit/client_test.rb +40 -6
  35. data/test/unit/connection_test.rb +15 -5
  36. data/test/unit/db_test.rb +1 -1
  37. data/test/unit/read_pref_test.rb +291 -0
  38. metadata +9 -6
  39. metadata.gz.sig +0 -0
@@ -315,10 +315,25 @@ class DBAPITest < Test::Unit::TestCase
315
315
  cursor = @db.collections_info
316
316
  rows = cursor.to_a
317
317
  assert rows.length >= 1
318
- row = rows.detect { |r| r['name'] == @coll_full_name }
318
+ if @client.server_version < '2.7.6'
319
+ row = rows.detect { |r| r['name'] == @coll_full_name }
320
+ else
321
+ row = rows.detect { |r| r['name'] == @coll.name }
322
+ end
319
323
  assert_not_nil row
320
324
  end
321
325
 
326
+ def test_collections_info_with_name
327
+ cursor = @db.collections_info(@coll.name)
328
+ info = cursor.to_a
329
+ assert_equal 1, info.length
330
+ if @client.server_version < '2.7.6'
331
+ assert_equal "#{@db.name}.#{@coll.name}", info.first['name']
332
+ else
333
+ assert_equal @coll.name, info.first['name']
334
+ end
335
+ end
336
+
322
337
  def test_collection_options
323
338
  @db.drop_collection('foobar')
324
339
  @db.strict = true
@@ -43,19 +43,22 @@ class PoolTest < Test::Unit::TestCase
43
43
  end
44
44
 
45
45
  def test_pool_affinity_max_size
46
+ client = standard_connection({:pool_size => 15, :pool_timeout => 5,
47
+ :op_timeout => nil})
48
+ coll = client[TEST_DB]['pool_test']
46
49
  docs = []
47
50
  8000.times {|x| docs << {:value => x}}
48
- @collection.insert(docs)
51
+ coll.insert(docs)
49
52
 
50
53
  threads = []
51
54
  threads << Thread.new do
52
- @collection.find({"value" => {"$lt" => 100}}).each {|e| e}
55
+ coll.find({"value" => {"$lt" => 100}}).each {|e| e}
53
56
  Thread.pass
54
57
  sleep(0.125)
55
- @collection.find({"value" => {"$gt" => 100}}).each {|e| e}
58
+ coll.find({"value" => {"$gt" => 100}}).each {|e| e}
56
59
  end
57
60
  threads << Thread.new do
58
- @collection.find({'$where' => "function() {for(i=0;i<1000;i++) {this.value};}"}).each {|e| e}
61
+ coll.find({'$where' => "function() {for(i=0;i<500;i++) {this.value};}"}).each {|e| e}
59
62
  end
60
63
  threads.each(&:join)
61
64
  end
@@ -83,7 +86,6 @@ class PoolTest < Test::Unit::TestCase
83
86
  :username => TEST_USER,
84
87
  :password => TEST_USER_PWD,
85
88
  :source => TEST_DB,
86
- :mechanism => Mongo::Authentication::DEFAULT_MECHANISM,
87
89
  :extra => {}
88
90
  }
89
91
  client.auths << creds
@@ -111,7 +113,6 @@ class PoolTest < Test::Unit::TestCase
111
113
  :username => TEST_USER,
112
114
  :password => TEST_USER_PWD,
113
115
  :source => TEST_DB,
114
- :mechanism => Mongo::Authentication::DEFAULT_MECHANISM,
115
116
  :extra => {}
116
117
  }
117
118
  client.auths << creds
@@ -31,6 +31,30 @@ class URITest < Test::Unit::TestCase
31
31
  assert_equal 27018, parser.nodes[0][1]
32
32
  end
33
33
 
34
+ def test_unix_socket
35
+ parser = Mongo::URIParser.new('mongodb:///tmp/mongod.sock')
36
+ assert_equal 1, parser.nodes.length
37
+ assert_equal '/tmp/mongod.sock', parser.nodes[0][0]
38
+ end
39
+
40
+ def test_unix_socket_with_user
41
+ parser = Mongo::URIParser.new('mongodb://bob:secret.word@/tmp/mongod.sock')
42
+ assert_equal 1, parser.nodes.length
43
+ assert_equal '/tmp/mongod.sock', parser.nodes[0][0]
44
+ assert_equal "bob", parser.auths.first[:username]
45
+ assert_equal "secret.word", parser.auths.first[:password]
46
+ assert_equal 'admin', parser.auths.first[:source]
47
+ end
48
+
49
+ def test_unix_socket_with_db
50
+ parser = Mongo::URIParser.new('mongodb://bob:secret.word@/tmp/mongod.sock/some_db')
51
+ assert_equal 1, parser.nodes.length
52
+ assert_equal '/tmp/mongod.sock', parser.nodes[0][0]
53
+ assert_equal 'bob', parser.auths.first[:username]
54
+ assert_equal 'secret.word', parser.auths.first[:password]
55
+ assert_equal 'some_db', parser.auths.first[:source]
56
+ end
57
+
34
58
  def test_ipv6_format
35
59
  parser = Mongo::URIParser.new('mongodb://[::1]:27018')
36
60
  assert_equal 1, parser.nodes.length
@@ -89,6 +113,23 @@ class URITest < Test::Unit::TestCase
89
113
  end
90
114
  end
91
115
 
116
+ def test_username_without_password_unix_socket
117
+ parser = Mongo::URIParser.new('mongodb://bob:@/tmp/mongod.sock?authMechanism=GSSAPI')
118
+ assert_equal "bob", parser.auths.first[:username]
119
+ assert_equal nil, parser.auths.first[:password]
120
+
121
+ parser = Mongo::URIParser.new('mongodb://bob@/tmp/mongod.sock?authMechanism=GSSAPI')
122
+ assert_equal nil, parser.auths.first[:password]
123
+
124
+ assert_raise_error MongoArgumentError do
125
+ Mongo::URIParser.new('mongodb://bob:@/tmp/mongod.sock')
126
+ end
127
+
128
+ assert_raise_error MongoArgumentError do
129
+ Mongo::URIParser.new('mongodb://bob@/tmp/mongod.sock')
130
+ end
131
+ end
132
+
92
133
  def test_complex_passwords
93
134
  parser = Mongo::URIParser.new('mongodb://bob:secret.word@a.example.com:27018/test')
94
135
  assert_equal "bob", parser.auths.first[:username]
@@ -215,6 +256,11 @@ class URITest < Test::Unit::TestCase
215
256
  assert_equal :primary, parser.readpreference
216
257
  end
217
258
 
259
+ def test_read_preference_option_primary_unix_sock
260
+ parser = Mongo::URIParser.new("mongodb:///tmp/mongod.sock?readPreference=primary")
261
+ assert_equal :primary, parser.readpreference
262
+ end
263
+
218
264
  def test_read_preference_option_primary_preferred
219
265
  parser = Mongo::URIParser.new("mongodb://localhost:27018?readPreference=primaryPreferred")
220
266
  assert_equal :primary_preferred, parser.readpreference
@@ -256,6 +302,34 @@ class URITest < Test::Unit::TestCase
256
302
  assert_equal :nearest, parser.connection_options[:read]
257
303
  end
258
304
 
305
+ def test_read_preference_tags
306
+ parser = Mongo::URIParser.new("mongodb://localhost:27017?replicaset=test&" +
307
+ "readPreferenceTags=dc:ny,rack:1")
308
+ expected_tags = [{ 'dc' => 'ny', 'rack' => '1' }]
309
+ assert_equal expected_tags, parser.connection_options[:tag_sets]
310
+ end
311
+
312
+ def test_read_preference_tags_multiple
313
+ parser = Mongo::URIParser.new("mongodb://localhost:27017?replicaset=test&" +
314
+ "readPreferenceTags=dc:ny,rack:1&readPreferenceTags=dc:bos")
315
+ expected_tags = [{'dc' => 'ny', 'rack' => '1'}, { 'dc' => 'bos' }]
316
+ assert_equal expected_tags, parser.connection_options[:tag_sets]
317
+ end
318
+
319
+ def test_invalid_read_preference_tags
320
+ assert_raise_error MongoArgumentError do
321
+ Mongo::URIParser.new("mongodb://localhost:27017?replicaset=test&" +
322
+ "readPreferenceTags=dc")
323
+ end
324
+ end
325
+
326
+ def test_invalid_read_preference_tags_multiple
327
+ assert_raise_error MongoArgumentError do
328
+ Mongo::URIParser.new("mongodb://localhost:27017?replicaset=test&" +
329
+ "readPreferenceTags=dc:nyc&readPreferenceTags=dc")
330
+ end
331
+ end
332
+
259
333
  def test_connection_when_sharded_with_no_options
260
334
  parser = Mongo::URIParser.new("mongodb://localhost:27017,localhost:27018")
261
335
  client = parser.connection({}, false, true)
@@ -288,11 +362,41 @@ class URITest < Test::Unit::TestCase
288
362
  parser = Mongo::URIParser.new("mongodb://user@localhost?authMechanism=MONGODB-X509")
289
363
  assert_equal 'MONGODB-X509', parser.authmechanism
290
364
 
291
- assert_raise_error MongoArgumentError do
365
+ assert_raise_error MongoArgumentError do
292
366
  Mongo::URIParser.new("mongodb://user@localhost?authMechanism=INVALID")
293
367
  end
294
368
  end
295
369
 
370
+ def test_auth_mechanism_properties
371
+ uri = "mongodb://user@localhost?authMechanism=GSSAPI&authMechanismProperties=SERVICE_NAME" +
372
+ ":mongodb,CANONICALIZE_HOST_NAME:true"
373
+ parser = Mongo::URIParser.new(uri)
374
+ properties = {:service_name => "mongodb", :canonicalize_host_name => true}
375
+ assert_equal properties, parser.authmechanismproperties
376
+ assert_equal 'GSSAPI', parser.authmechanism
377
+
378
+ uri = "mongodb://user@localhost?authMechanism=GSSAPI&authMechanismProperties=SERVICE_NAME" +
379
+ ":MongoDB,CANONICALIZE_HOST_NAME:false,SERVICE_REALM:test"
380
+ parser = Mongo::URIParser.new(uri)
381
+ properties = {:service_name => "MongoDB", :canonicalize_host_name => false, :service_realm => "test"}
382
+ assert_equal properties, parser.authmechanismproperties
383
+ assert_equal 'GSSAPI', parser.authmechanism
384
+ end
385
+
386
+ def test_invalid_auth_mechanism_properties
387
+ uri = "mongodb://user@localhost?authMechanism=GSSAPI&authMechanismProperties=SERVICE_NAME" +
388
+ ":mongodb,INVALID_PROPERTY:true"
389
+ assert_raise_error MongoArgumentError do
390
+ parser = Mongo::URIParser.new(uri)
391
+ end
392
+
393
+ uri = "mongodb://user@localhost?authMechanism=PLAIN&authMechanismProperties="+
394
+ "SERVICE_NAME:mongodb"
395
+ assert_raise_error MongoArgumentError do
396
+ parser = Mongo::URIParser.new(uri)
397
+ end
398
+ end
399
+
296
400
  def test_sasl_plain
297
401
  parser = Mongo::URIParser.new("mongodb://user:pass@localhost?authMechanism=PLAIN")
298
402
  assert_equal 'PLAIN', parser.auths.first[:mechanism]
@@ -319,22 +423,22 @@ class URITest < Test::Unit::TestCase
319
423
 
320
424
 
321
425
  uri = "mongodb://foo%2Fbar%40example.net@localhost?authMechanism=GSSAPI;" +
322
- "gssapiServiceName=mongodb;canonicalizeHostName=true"
426
+ "authMechanismProperties=SERVICE_NAME:mongodb,SERVICE_REALM:example," +
427
+ "CANONICALIZE_HOST_NAME:true"
323
428
  parser = Mongo::URIParser.new(uri)
324
429
  assert_equal 'GSSAPI', parser.auths.first[:mechanism]
325
430
  assert_equal 'foo/bar@example.net', parser.auths.first[:username]
326
- assert_equal 'mongodb', parser.auths.first[:extra][:gssapi_service_name]
431
+ assert_equal 'mongodb', parser.auths.first[:extra][:service_name]
327
432
  assert_equal true, parser.auths.first[:extra][:canonicalize_host_name]
433
+ assert_equal 'example', parser.auths.first[:extra][:service_realm]
328
434
  end
329
435
 
330
436
  def test_opts_case_sensitivity
331
- # options gssapiservicename, authsource, replicaset, w should be case sensitive
332
- uri = "mongodb://localhost?gssapiServiceName=MongoDB;" +
333
- "authSource=FooBar;" +
437
+ # options authsource, replicaset, w should be case sensitive
438
+ uri = "mongodb://localhost?authSource=FooBar;" +
334
439
  "replicaSet=Foo;" +
335
440
  "w=Majority"
336
441
  parser = Mongo::URIParser.new(uri)
337
- assert_equal 'MongoDB', parser.gssapiservicename
338
442
  assert_equal 'FooBar', parser.authsource
339
443
  assert_equal 'Foo', parser.replicaset
340
444
  assert_equal :Majority, parser.w
@@ -61,7 +61,7 @@ class Test::Unit::TestCase
61
61
 
62
62
  uri = "mongodb://#{TEST_USER}:#{TEST_USER_PWD}@" +
63
63
  "#{cluster_instance.members_uri}"
64
- uri += "?replicaset=#{@rs.repl_set_name}" if cluster_instance.replica_set?
64
+ uri += "?replicaset=#{@rs.repl_set_name}&sockettimeoutms=60000" if cluster_instance.replica_set?
65
65
  instance_variable_set("@uri", uri)
66
66
  end
67
67
 
@@ -147,6 +147,7 @@ class Test::Unit::TestCase
147
147
  begin
148
148
  step_down_command = BSON::OrderedHash.new
149
149
  step_down_command[:replSetStepDown] = 30
150
+ step_down_command[:force] = true
150
151
  member['admin'].command(step_down_command)
151
152
  rescue Mongo::OperationFailure => e
152
153
  retry unless (Time.now - start) > timeout
@@ -176,6 +177,13 @@ class Test::Unit::TestCase
176
177
  Object.new
177
178
  end
178
179
 
180
+ def mock_pool(tags={}, ping_time=15)
181
+ mock('pool').tap do |pool|
182
+ pool.stubs(:tags).returns(tags)
183
+ pool.stubs(:ping_time).returns(ping_time)
184
+ end
185
+ end
186
+
179
187
  def assert_raise_error(klass, message=nil)
180
188
  begin
181
189
  yield
@@ -247,11 +255,17 @@ class Test::Unit::TestCase
247
255
  def with_default_journaling(client, &block)
248
256
  authenticate_client(client)
249
257
  cmd_line_args = client['admin'].command({ :getCmdLineOpts => 1 })['parsed']
250
- unless client.server_version < "2.0" || cmd_line_args.include?('nojournal')
258
+ unless client.server_version < "2.0" || cmd_line_args.include?('nojournal') ||
259
+ using_heap1_storage_engine?(cmd_line_args)
251
260
  yield
252
261
  end
253
262
  end
254
263
 
264
+ def using_heap1_storage_engine?(cmd_line_args)
265
+ cmd_line_args.include?('storage') &&
266
+ cmd_line_args['storage']['engine'] == 'heap1'
267
+ end
268
+
255
269
  def with_no_replication(client, &block)
256
270
  if client.class == MongoClient
257
271
  yield
@@ -384,7 +398,7 @@ class Test::Unit::TestCase
384
398
  client = Mongo::MongoClient.new(TEST_HOST, TEST_PORT)
385
399
  db = client[TEST_DB]
386
400
  begin
387
- db.authenticate(TEST_USER, TEST_USER_PWD)
401
+ db.authenticate(TEST_USER, TEST_USER_PWD, nil, 'admin')
388
402
  rescue Mongo::AuthenticationError => ex
389
403
  roles = [ 'dbAdminAnyDatabase',
390
404
  'userAdminAnyDatabase',
@@ -216,6 +216,24 @@ class ReplicaSetClientTest < Test::Unit::TestCase
216
216
  end
217
217
  end
218
218
 
219
+ def test_read_pref_primary_with_tags
220
+ parser = Mongo::URIParser.new("mongodb://#{@rs.replicas[0].host_port},#{@rs.replicas[1].host_port}" +
221
+ "?replicaset=#{@rs.repl_set_name}&readPreference=primary&" +
222
+ "readPreferenceTags=dc:ny,rack:1")
223
+ assert_raise_error Mongo::MongoArgumentError do
224
+ parser.connection.read_pool
225
+ end
226
+ end
227
+
228
+ def test_read_pref_with_tags
229
+ parser = Mongo::URIParser.new("mongodb://#{@rs.replicas[0].host_port},#{@rs.replicas[1].host_port}" +
230
+ "?replicaset=#{@rs.repl_set_name}&" +
231
+ "readPreferenceTags=dc:ny,rack:1")
232
+ assert_raise_error Mongo::MongoArgumentError do
233
+ parser.connection.read_pool
234
+ end
235
+ end
236
+
219
237
  def test_connect_with_connection_string
220
238
  @client = MongoClient.from_uri("mongodb://#{@rs.replicas[0].host_port},#{@rs.replicas[1].host_port}?replicaset=#{@rs.repl_set_name}")
221
239
  assert !@client.nil?
@@ -344,4 +362,17 @@ class ReplicaSetClientTest < Test::Unit::TestCase
344
362
  )
345
363
  assert_equal true, collection.find_one({ 'a' => id }, :read => :primary)['processed']
346
364
  end
365
+
366
+ def test_op_timeout_option
367
+ client = MongoReplicaSetClient.new(@rs.repl_set_seeds, :connect => false,
368
+ :op_timeout => nil)
369
+ assert_equal nil, client.op_timeout
370
+
371
+ client = MongoReplicaSetClient.new(@rs.repl_set_seeds, :connect => false,
372
+ :op_timeout => 50)
373
+ assert_equal 50, client.op_timeout
374
+
375
+ client = MongoReplicaSetClient.new(@rs.repl_set_seeds, :connect => false)
376
+ assert_equal Mongo::MongoClient::DEFAULT_OP_TIMEOUT, client.op_timeout
377
+ end
347
378
  end
@@ -68,47 +68,64 @@ class ReplicaSetInsertTest < Test::Unit::TestCase
68
68
  end
69
69
 
70
70
  should "handle error with deferred write concern error - spec Merging Results" do
71
- with_write_commands_and_operations(@db.connection) do |wire_version|
71
+ if @client.wire_version_feature?(MongoClient::MONGODB_2_8)
72
72
  @coll.remove
73
73
  @coll.ensure_index(BSON::OrderedHash[:a, Mongo::ASCENDING], {:unique => true})
74
74
  bulk = @coll.initialize_ordered_bulk_op
75
75
  bulk.insert({:a => 1})
76
76
  bulk.find({:a => 2}).upsert.update({'$set' => {:a => 2}})
77
77
  bulk.insert({:a => 1})
78
+ secondary = MongoClient.new(@rs.secondaries.first.host, @rs.secondaries.first.port)
79
+ cmd = BSON::OrderedHash[:configureFailPoint, 'rsSyncApplyStop', :mode, 'alwaysOn']
80
+ secondary['admin'].command(cmd)
78
81
  ex = assert_raise BulkWriteError do
79
- bulk.execute({:w => 5, :wtimeout => 1})
82
+ bulk.execute({:w => @rs.servers.size, :wtimeout => 1})
83
+ end
84
+ cmd = BSON::OrderedHash[:configureFailPoint, 'rsSyncApplyStop', :mode, 'off']
85
+ secondary['admin'].command(cmd)
86
+ else
87
+ with_write_commands_and_operations(@db.connection) do |wire_version|
88
+ @coll.remove
89
+ @coll.ensure_index(BSON::OrderedHash[:a, Mongo::ASCENDING], {:unique => true})
90
+ bulk = @coll.initialize_ordered_bulk_op
91
+ bulk.insert({:a => 1})
92
+ bulk.find({:a => 2}).upsert.update({'$set' => {:a => 2}})
93
+ bulk.insert({:a => 1})
94
+ ex = assert_raise BulkWriteError do
95
+ bulk.execute({:w => 5, :wtimeout => 1})
96
+ end
80
97
  end
81
- result = ex.result
82
- assert_match_document(
83
- {
84
- "ok" => 1,
85
- "n" => 2,
86
- "writeErrors" => [
87
- {
88
- "index" => 2,
89
- "code" => 11000,
90
- "errmsg" => /duplicate key error/,
91
- }
92
- ],
93
- "writeConcernError" => [
94
- {
95
- "errmsg" => /waiting for replication timed out|timed out waiting for slaves|timeout/,
96
- "code" => 64,
97
- "errInfo" => {"wtimeout" => true},
98
- "index" => 0
99
- },
100
- {
101
- "errmsg" => /waiting for replication timed out|timed out waiting for slaves|timeout/,
102
- "code" => 64,
103
- "errInfo" => {"wtimeout" => true},
104
- "index" => 1
105
- }
106
- ],
107
- "code" => 65,
108
- "errmsg" => "batch item errors occurred",
109
- "nInserted" => 1
110
- }, result, "wire_version:#{wire_version}")
111
98
  end
99
+ result = ex.result
100
+ assert_match_document(
101
+ {
102
+ "ok" => 1,
103
+ "n" => 2,
104
+ "writeErrors" => [
105
+ {
106
+ "index" => 2,
107
+ "code" => 11000,
108
+ "errmsg" => /duplicate key error/,
109
+ }
110
+ ],
111
+ "writeConcernError" => [
112
+ {
113
+ "errmsg" => /waiting for replication timed out|timed out waiting for slaves|timeout/,
114
+ "code" => 64,
115
+ "errInfo" => {"wtimeout" => true},
116
+ "index" => 0
117
+ },
118
+ {
119
+ "errmsg" => /waiting for replication timed out|timed out waiting for slaves|timeout/,
120
+ "code" => 64,
121
+ "errInfo" => {"wtimeout" => true},
122
+ "index" => 1
123
+ }
124
+ ],
125
+ "code" => 65,
126
+ "errmsg" => "batch item errors occurred",
127
+ "nInserted" => 1
128
+ }, result)
112
129
  assert_equal 2, @coll.find.to_a.size
113
130
  end
114
131
 
@@ -52,4 +52,54 @@ class ReplicaSetPinningTest < Test::Unit::TestCase
52
52
  end
53
53
  threads.each(&:join)
54
54
  end
55
+
56
+ def test_aggregation_cursor_pinning
57
+ return unless @client.server_version >= '2.5.1'
58
+ @coll.drop
59
+
60
+ [10, 1000].each do |size|
61
+ @coll.drop
62
+ size.times {|i| @coll.insert({ :_id => i }) }
63
+ expected_sum = size.times.reduce(:+)
64
+
65
+ cursor = @coll.aggregate(
66
+ [{ :$project => {:_id => '$_id'}} ],
67
+ :cursor => { :batchSize => 1 }
68
+ )
69
+
70
+ assert_equal Mongo::Cursor, cursor.class
71
+
72
+ cursor_sum = cursor.reduce(0) do |sum, doc|
73
+ sum += doc['_id']
74
+ end
75
+
76
+ assert_equal expected_sum, cursor_sum
77
+ end
78
+ @coll.drop
79
+ end
80
+
81
+ def test_parallel_scan_pinning
82
+ return unless @client.server_version >= '2.5.5'
83
+ @coll.drop
84
+
85
+ 8000.times { |i| @coll.insert({ :_id => i }) }
86
+
87
+ lock = Mutex.new
88
+ doc_ids = Set.new
89
+ threads = []
90
+ cursors = @coll.parallel_scan(3)
91
+ cursors.each_with_index do |cursor, i|
92
+ threads << Thread.new do
93
+ docs = cursor.to_a
94
+ lock.synchronize do
95
+ docs.each do |doc|
96
+ doc_ids << doc['_id']
97
+ end
98
+ end
99
+ end
100
+ end
101
+ threads.each(&:join)
102
+ assert_equal 8000, doc_ids.count
103
+ @coll.drop
104
+ end
55
105
  end