mongo 1.11.1 → 1.12.0.rc0

Sign up to get free protection for your applications and to get access to all the features.
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