mongo 2.4.1 → 2.4.2

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 (62) hide show
  1. checksums.yaml +4 -4
  2. checksums.yaml.gz.sig +0 -0
  3. data.tar.gz.sig +0 -0
  4. data/README.md +3 -3
  5. data/lib/mongo/client.rb +16 -7
  6. data/lib/mongo/cluster.rb +9 -4
  7. data/lib/mongo/cluster/cursor_reaper.rb +1 -0
  8. data/lib/mongo/cluster/topology.rb +1 -1
  9. data/lib/mongo/collection.rb +0 -3
  10. data/lib/mongo/collection/view.rb +12 -5
  11. data/lib/mongo/collection/view/builder/find_command.rb +2 -2
  12. data/lib/mongo/collection/view/readable.rb +4 -4
  13. data/lib/mongo/collection/view/writable.rb +12 -10
  14. data/lib/mongo/cursor.rb +1 -0
  15. data/lib/mongo/error.rb +1 -0
  16. data/lib/mongo/error/invalid_min_pool_size.rb +35 -0
  17. data/lib/mongo/error/operation_failure.rb +24 -5
  18. data/lib/mongo/operation/write/bulk/update/result.rb +1 -10
  19. data/lib/mongo/operation/write/update/result.rb +26 -7
  20. data/lib/mongo/retryable.rb +30 -35
  21. data/lib/mongo/socket.rb +1 -1
  22. data/lib/mongo/socket/tcp.rb +1 -0
  23. data/lib/mongo/version.rb +1 -1
  24. data/spec/mongo/bulk_write_spec.rb +113 -43
  25. data/spec/mongo/client_spec.rb +253 -0
  26. data/spec/mongo/cluster/topology_spec.rb +37 -0
  27. data/spec/mongo/cluster_spec.rb +1 -1
  28. data/spec/mongo/collection/view/aggregation_spec.rb +2 -2
  29. data/spec/mongo/collection/view/map_reduce_spec.rb +1 -1
  30. data/spec/mongo/collection_spec.rb +55 -19
  31. data/spec/mongo/command_monitoring_spec.rb +1 -1
  32. data/spec/mongo/database_spec.rb +5 -5
  33. data/spec/mongo/grid/stream/write_spec.rb +2 -2
  34. data/spec/mongo/index/view_spec.rb +5 -5
  35. data/spec/mongo/max_staleness_spec.rb +0 -4
  36. data/spec/mongo/operation/write/update_spec.rb +15 -3
  37. data/spec/mongo/retryable_spec.rb +26 -22
  38. data/spec/mongo/server/connection_spec.rb +26 -0
  39. data/spec/mongo/shell_examples_spec.rb +981 -0
  40. data/spec/mongo/socket/ssl_spec.rb +51 -18
  41. data/spec/spec_helper.rb +26 -16
  42. data/spec/support/authorization.rb +38 -16
  43. data/spec/support/connection_string.rb +3 -3
  44. data/spec/support/crud.rb +38 -10
  45. data/spec/support/crud/write.rb +6 -3
  46. data/spec/support/crud_tests/write/findOneAndReplace-upsert.yml +48 -4
  47. data/spec/support/crud_tests/write/findOneAndReplace-upsert_pre_2.6.yml +88 -0
  48. data/spec/support/crud_tests/write/findOneAndReplace.yml +0 -29
  49. data/spec/support/crud_tests/write/findOneAndUpdate.yml +4 -1
  50. data/spec/support/crud_tests/write/replaceOne-collation.yml +1 -0
  51. data/spec/support/crud_tests/write/replaceOne-pre_2.6.yml +98 -0
  52. data/spec/support/crud_tests/write/replaceOne.yml +21 -5
  53. data/spec/support/crud_tests/write/updateMany-collation.yml +1 -0
  54. data/spec/support/crud_tests/write/updateMany-pre_2.6.yml +86 -0
  55. data/spec/support/crud_tests/write/updateMany.yml +5 -0
  56. data/spec/support/crud_tests/write/updateOne-collation.yml +1 -0
  57. data/spec/support/crud_tests/write/updateOne-pre_2.6.yml +83 -0
  58. data/spec/support/crud_tests/write/updateOne.yml +7 -2
  59. data/spec/support/gridfs.rb +1 -0
  60. metadata +13 -4
  61. metadata.gz.sig +2 -1
  62. data/spec/support/helpers.rb +0 -140
@@ -2,8 +2,13 @@ require 'spec_helper'
2
2
 
3
3
  describe Mongo::Socket::SSL, if: running_ssl? do
4
4
 
5
+ let(:family) do
6
+ resolver = default_address.instance_variable_get(:@resolver)
7
+ Mongo::Address::FAMILY_MAP.key(resolver.class)
8
+ end
9
+
5
10
  let(:socket) do
6
- described_class.new(*default_address.to_s.split(":"), default_address.host, 5, Socket::PF_INET, options)
11
+ described_class.new(*default_address.to_s.split(":"), default_address.host, 5, family, options)
7
12
  end
8
13
 
9
14
  let(:options) do
@@ -95,7 +100,7 @@ describe Mongo::Socket::SSL, if: running_ssl? do
95
100
  end
96
101
  end
97
102
 
98
- context 'when certificate and an encrypted key are provided as strings' do
103
+ context 'when certificate and an encrypted key are provided as strings', if: testing_ssl_locally? do
99
104
 
100
105
  let(:options) do
101
106
  {
@@ -271,21 +276,49 @@ describe Mongo::Socket::SSL, if: running_ssl? do
271
276
  end
272
277
  end
273
278
 
274
- context 'when a key is passed, but it is not of the right type' do
275
- let(:options) do
276
- key = "This is a string not a key"
277
- {
278
- :ssl => true,
279
- :ssl_key_object => key,
280
- :ssl_cert => CLIENT_CERT_PEM,
281
- :ssl_verify => false
282
- }
279
+ context 'when ruby version is < 2.4.1', if: RUBY_VERSION < '2.4.1' do
280
+
281
+ context 'when a key is passed, but it is not of the right type' do
282
+
283
+ let(:options) do
284
+ key = "This is a string not a key"
285
+ {
286
+ :ssl => true,
287
+ :ssl_key_object => key,
288
+ :ssl_cert => CLIENT_CERT_PEM,
289
+ :ssl_verify => false
290
+ }
291
+ end
292
+
293
+ it 'raises a TypeError' do
294
+ expect{
295
+ socket.connect!
296
+ }.to raise_exception(TypeError)
297
+ end
283
298
  end
299
+ end
284
300
 
285
- it 'raises a TypeError' do
286
- expect{
287
- socket.connect!
288
- }.to raise_exception(TypeError)
301
+ # Note that as of MRI 2.4, Creating a socket with the wrong key type raises
302
+ # a NoMethodError because #private? is attempted to be called on the key.
303
+ context 'when ruby version is >= 2.4.1', if: RUBY_VERSION >= '2.4.1' do
304
+
305
+ context 'when a key is passed, but it is not of the right type' do
306
+
307
+ let(:options) do
308
+ key = "This is a string not a key"
309
+ {
310
+ :ssl => true,
311
+ :ssl_key_object => key,
312
+ :ssl_cert => CLIENT_CERT_PEM,
313
+ :ssl_verify => false
314
+ }
315
+ end
316
+
317
+ it 'raises a NoMethodError' do
318
+ expect{
319
+ socket.connect!
320
+ }.to raise_exception(NoMethodError)
321
+ end
289
322
  end
290
323
  end
291
324
 
@@ -293,14 +326,14 @@ describe Mongo::Socket::SSL, if: running_ssl? do
293
326
 
294
327
  let(:options) do
295
328
  super().merge(
296
- :ssl_key => CRL_PEM
329
+ :ssl_key => COMMAND_MONITORING_TESTS.first
297
330
  )
298
331
  end
299
332
 
300
333
  it 'raises an exception' do
301
334
  expect {
302
335
  socket.connect!
303
- }.to raise_exception(ArgumentError)
336
+ }.to raise_exception
304
337
  end
305
338
  end
306
339
 
@@ -324,7 +357,7 @@ describe Mongo::Socket::SSL, if: running_ssl? do
324
357
  end
325
358
  end
326
359
 
327
- context 'as a string containg the PEM-encoded certificate' do
360
+ context 'as a string containing the PEM-encoded certificate' do
328
361
 
329
362
  let (:options) do
330
363
  super().merge(
@@ -11,20 +11,28 @@ COMMAND_MONITORING_TESTS = Dir.glob("#{CURRENT_PATH}/support/command_monitoring/
11
11
  CONNECTION_STRING_TESTS = Dir.glob("#{CURRENT_PATH}/support/connection_string_tests/*.yml")
12
12
  GRIDFS_TESTS = Dir.glob("#{CURRENT_PATH}/support/gridfs_tests/*.yml")
13
13
 
14
- SSL_CERTS_DIR = "#{CURRENT_PATH}/support/certificates"
15
- CLIENT_PEM = "#{SSL_CERTS_DIR}/client.pem"
16
- CLIENT_PASSWORD_PEM = "#{SSL_CERTS_DIR}/password_protected.pem"
17
- CA_PEM = "#{SSL_CERTS_DIR}/ca.pem"
18
- CRL_PEM = "#{SSL_CERTS_DIR}/crl.pem"
19
- CLIENT_KEY_PEM = "#{SSL_CERTS_DIR}/client_key.pem"
20
- CLIENT_CERT_PEM = "#{SSL_CERTS_DIR}/client_cert.pem"
21
- CLIENT_KEY_ENCRYPTED_PEM = "#{SSL_CERTS_DIR}/client_key_encrypted.pem"
22
- CLIENT_KEY_PASSPHRASE = "passphrase"
14
+ if ENV['DRIVERS_TOOLS']
15
+ CLIENT_CERT_PEM = ENV['DRIVER_TOOLS_CLIENT_CERT_PEM']
16
+ CLIENT_KEY_PEM = ENV['DRIVER_TOOLS_CLIENT_KEY_PEM']
17
+ CA_PEM = ENV['DRIVER_TOOLS_CA_PEM']
18
+ CLIENT_KEY_ENCRYPTED_PEM = ENV['DRIVER_TOOLS_CLIENT_KEY_ENCRYPTED_PEM']
19
+ else
20
+ SSL_CERTS_DIR = "#{CURRENT_PATH}/support/certificates"
21
+ CLIENT_PEM = "#{SSL_CERTS_DIR}/client.pem"
22
+ CLIENT_PASSWORD_PEM = "#{SSL_CERTS_DIR}/password_protected.pem"
23
+ CA_PEM = "#{SSL_CERTS_DIR}/ca.pem"
24
+ CRL_PEM = "#{SSL_CERTS_DIR}/crl.pem"
25
+ CLIENT_KEY_PEM = "#{SSL_CERTS_DIR}/client_key.pem"
26
+ CLIENT_CERT_PEM = "#{SSL_CERTS_DIR}/client_cert.pem"
27
+ CLIENT_KEY_ENCRYPTED_PEM = "#{SSL_CERTS_DIR}/client_key_encrypted.pem"
28
+ CLIENT_KEY_PASSPHRASE = "passphrase"
29
+ end
23
30
 
24
31
  require 'mongo'
25
32
 
26
33
  Mongo::Logger.logger = Logger.new($stdout)
27
34
  Mongo::Logger.logger.level = Logger::INFO
35
+ Encoding.default_external = Encoding::UTF_8
28
36
 
29
37
  require 'support/travis'
30
38
  require 'support/matchers'
@@ -45,7 +53,6 @@ RSpec.configure do |config|
45
53
  config.include(Authorization)
46
54
 
47
55
  config.before(:suite) do
48
-
49
56
  begin
50
57
  # Create the root user administrator as the first user to be added to the
51
58
  # database. This user will need to be authenticated in order to add any
@@ -191,13 +198,16 @@ end
191
198
  #
192
199
  # @since 2.2.0
193
200
  def auth_enabled?
194
- $mongo_client ||= initialize_scanned_client!
195
- begin
196
- $mongo_client.use(:admin).command(getCmdLineOpts: 1).first["argv"].include?("--auth")
197
- rescue
198
- return true
201
+ if auth = ENV['AUTH']
202
+ auth == 'auth'
203
+ else
204
+ $mongo_client ||= initialize_scanned_client!
205
+ begin
206
+ $mongo_client.use(:admin).command(getCmdLineOpts: 1).first["argv"].include?("--auth")
207
+ rescue => e
208
+ e.message =~ /(not authorized)|(unauthorized)/
209
+ end
199
210
  end
200
- false
201
211
  end
202
212
 
203
213
  # Initializes a basic scanned client to do an ismaster check.
@@ -22,28 +22,45 @@ TEST_DB = 'ruby-driver'.freeze
22
22
  # @since 2.0.0
23
23
  TEST_COLL = 'test'.freeze
24
24
 
25
- # The seed addresses to be used when creating a client.
26
- #
27
- # @since 2.0.0
28
- ADDRESSES = ENV['MONGODB_ADDRESSES'] ? ENV['MONGODB_ADDRESSES'].split(',').freeze :
29
- [ '127.0.0.1:27017' ].freeze
25
+ # For Evergreen
26
+ if ENV['MONGODB_URI']
27
+ MONGODB_URI = Mongo::URI.new(ENV['MONGODB_URI'])
28
+ URI_OPTIONS = Mongo::Options::Mapper.transform_keys_to_symbols(MONGODB_URI.uri_options)
29
+ if URI_OPTIONS[:replica_set]
30
+ ADDRESSES = MONGODB_URI.servers
31
+ CONNECT = { connect: :replica_set, replica_set: URI_OPTIONS[:replica_set] }
32
+ elsif ENV['TOPOLOGY'] == 'sharded_cluster'
33
+ ADDRESSES = [ MONGODB_URI.servers.first ] # See SERVER-16836 for why we can only use one host:port
34
+ CONNECT = { connect: :sharded }
35
+ else
36
+ ADDRESSES = MONGODB_URI.servers
37
+ CONNECT = { connect: :direct }
38
+ end
39
+ else # For Jenkins
40
+ ADDRESSES = ENV['MONGODB_ADDRESSES'] ? ENV['MONGODB_ADDRESSES'].split(',').freeze : [ '127.0.0.1:27017' ].freeze
41
+ if ENV['RS_ENABLED']
42
+ CONNECT = { connect: :replica_set, replica_set: ENV['RS_NAME'] }
43
+ elsif ENV['SHARDED_ENABLED']
44
+ CONNECT = { connect: :sharded }
45
+ else
46
+ CONNECT = { connect: :direct }
47
+ end
48
+ end
30
49
 
31
- # The topology type.
50
+ # The write concern to use in the tests.
32
51
  #
33
52
  # @since 2.0.0
34
- CONNECT = ENV['RS_ENABLED'] == 'true' ? { connect: :replica_set, replica_set: ENV['RS_NAME'] } :
35
- ENV['SHARDED_ENABLED'] == 'true' ? { connect: :sharded } :
36
- { connect: :direct }
53
+ WRITE_CONCERN = CONNECT[:connect] == :replica_set ? { w: 2 } : { w: 1 }
37
54
 
38
- # The write concern to use in the tests.
55
+ # An invalid write concern.
39
56
  #
40
- # @since 2.0.0
41
- WRITE_CONCERN = CONNECT[:connect] == :replica_set ? { w: ADDRESSES.size } : { w: 1 }
57
+ # @since 2.4.2
58
+ INVALID_WRITE_CONCERN = { w: 4 }
42
59
 
43
60
  # Whether to use SSL.
44
61
  #
45
62
  # @since 2.0.3
46
- SSL = ENV['SSL_ENABLED'] == 'true'
63
+ SSL = (ENV['SSL'] == 'ssl') || (ENV['SSL_ENABLED'] == 'true')
47
64
 
48
65
  # SSL options.
49
66
  #
@@ -73,12 +90,17 @@ TEST_OPTIONS = BASE_OPTIONS.merge(CONNECT).merge(SSL_OPTIONS)
73
90
  # The root user name.
74
91
  #
75
92
  # @since 2.0.0
76
- ROOT_USER_NAME = ENV['ROOT_USER_NAME'] || 'root-user'
93
+ ROOT_USER_NAME = (defined?(MONGODB_URI) && MONGODB_URI.credentials[:user]) || 'root-user'
77
94
 
78
95
  # The root user password.
79
96
  #
80
97
  # @since 2.0.0
81
- ROOT_USER_PWD = ENV['ROOT_USER_PWD'] || 'password'
98
+ ROOT_USER_PWD = (defined?(MONGODB_URI) && MONGODB_URI.credentials[:password]) || 'password'
99
+
100
+ # The root user auth source.
101
+ #
102
+ # @since 2.4.2
103
+ ROOT_USER_AUTH_SOURCE = (defined?(URI_OPTIONS) && URI_OPTIONS[:auth_source]) || Mongo::Database::ADMIN
82
104
 
83
105
  # Gets the root system administrator user.
84
106
  #
@@ -161,7 +183,7 @@ ADMIN_AUTHORIZED_TEST_CLIENT = ADMIN_UNAUTHORIZED_CLIENT.with(
161
183
  user: ROOT_USER.name,
162
184
  password: ROOT_USER.password,
163
185
  database: TEST_DB,
164
- auth_source: Mongo::Database::ADMIN,
186
+ auth_source: ROOT_USER_AUTH_SOURCE,
165
187
  monitoring: false
166
188
  )
167
189
 
@@ -14,7 +14,7 @@
14
14
 
15
15
  RSpec::Matchers.define :have_hosts do |test|
16
16
 
17
- match do |client|
17
+ match do |cl|
18
18
 
19
19
  def find_server(client, host)
20
20
  client.cluster.instance_variable_get(:@servers).detect do |s|
@@ -40,7 +40,7 @@ RSpec::Matchers.define :have_hosts do |test|
40
40
  end
41
41
 
42
42
  test.hosts.all? do |host|
43
- server = find_server(client, host)
43
+ server = find_server(cl, host)
44
44
  match_host?(server, host) &&
45
45
  match_port?(server, host) if server #&&
46
46
  #match_address_family?(server, host) if server
@@ -49,7 +49,7 @@ RSpec::Matchers.define :have_hosts do |test|
49
49
  failure_message do |client|
50
50
  "With URI: #{test.uri_string}\n" +
51
51
  "Expected that test hosts: #{test.hosts} would match " +
52
- "client hosts: #{client.cluster.instance_variable_get(:@servers)}"
52
+ "client hosts: #{cl.cluster.instance_variable_get(:@servers)}"
53
53
  end
54
54
  end
55
55
  end
@@ -83,14 +83,7 @@ module Mongo
83
83
  #
84
84
  # @since 2.4.0
85
85
  def server_version_satisfied?(client)
86
- case @min_server_version
87
- when '2.6'
88
- client.cluster.servers.first.features.write_command_enabled?
89
- when '3.4'
90
- client.cluster.servers.first.features.collation_enabled?
91
- else
92
- true
93
- end
86
+ lower_bound_satisfied?(client) && upper_bound_satisfied?(client)
94
87
  end
95
88
 
96
89
  # Get a list of CRUDTests for each test definition.
@@ -106,6 +99,32 @@ module Mongo
106
99
  Mongo::CRUD::CRUDTest.new(@data, test)
107
100
  end
108
101
  end
102
+
103
+ private
104
+
105
+ def upper_bound_satisfied?(client)
106
+ if @max_server_version
107
+ if @max_server_version < '2.6'
108
+ !client.cluster.next_primary.features.write_command_enabled?
109
+ end
110
+ else
111
+ true
112
+ end
113
+ end
114
+
115
+ def lower_bound_satisfied?(client)
116
+ if @min_server_version
117
+ if @min_server_version >= '3.4'
118
+ client.cluster.next_primary.features.collation_enabled?
119
+ elsif @min_server_version >= '2.6'
120
+ client.cluster.next_primary.features.write_command_enabled?
121
+ else
122
+ true
123
+ end
124
+ else
125
+ true
126
+ end
127
+ end
109
128
  end
110
129
 
111
130
  # Represents a single CRUD test.
@@ -227,14 +246,23 @@ module Mongo
227
246
  when nil
228
247
  actual.nil?
229
248
  when Hash
230
- actual.each do |k, v|
231
- expected[k] == v
249
+ actual.all? do |k, v|
250
+ expected[k] == v || handle_upserted_id(k, expected[k], v)
232
251
  end
233
252
  when Integer
234
253
  expected == actual
235
254
  end
236
255
  end
237
256
 
257
+ def handle_upserted_id(field, expected_id, actual_id)
258
+ return true if expected_id.nil?
259
+ if field == 'upsertedId'
260
+ if expected_id.is_a?(Integer)
261
+ actual_id.is_a?(BSON::ObjectId) || actual_id.nil?
262
+ end
263
+ end
264
+ end
265
+
238
266
  def actual_collection_data
239
267
  if @outcome['collection']
240
268
  collection_name = @outcome['collection']['name'] || @collection.name
@@ -117,9 +117,12 @@ module Mongo
117
117
  end
118
118
 
119
119
  def update_return_doc(result)
120
- return_doc = { 'upsertedId' => result.upserted_id } if upsert
121
- (return_doc || {}).merge!({ 'matchedCount' => result.matched_count,
122
- 'modifiedCount' => result.modified_count })
120
+ return_doc = {}
121
+ return_doc['upsertedId'] = result.upserted_id if upsert
122
+ return_doc['upsertedCount'] = result.upserted_count
123
+ return_doc['matchedCount'] = result.matched_count
124
+ return_doc['modifiedCount'] = result.modified_count if result.modified_count
125
+ return_doc
123
126
  end
124
127
 
125
128
  def replace_one(collection)
@@ -3,18 +3,20 @@ data:
3
3
  - {_id: 2, x: 22}
4
4
  - {_id: 3, x: 33}
5
5
  minServerVersion: '2.6'
6
- # See SERVER-5289 for why the collection data is only checked for server versions >= 2.6
7
6
 
8
7
  tests:
9
8
  -
10
- description: "FindOneAndReplace when no documents match with upsert returning the document before modification"
9
+ description: "FindOneAndReplace when no documents match without id specified with upsert returning the document before modification"
11
10
  operation:
12
11
  name: findOneAndReplace
13
12
  arguments:
14
13
  filter: {_id: 4}
15
14
  replacement: {x: 44}
16
15
  projection: {x: 1, _id: 0}
17
- sort: {x: 1}
16
+ # Omit the sort option as it has no effect when no documents
17
+ # match and would only cause an inconsistent return value on
18
+ # pre-3.0 servers when combined with returnDocument "before"
19
+ # (see: SERVER-17650).
18
20
  upsert: true
19
21
 
20
22
  outcome:
@@ -26,7 +28,7 @@ tests:
26
28
  - {_id: 3, x: 33}
27
29
  - {_id: 4, x: 44}
28
30
  -
29
- description: "FindOneAndReplace when no documents match with upsert returning the document after modification"
31
+ description: "FindOneAndReplace when no documents match without id specified with upsert returning the document after modification"
30
32
  operation:
31
33
  name: findOneAndReplace
32
34
  arguments:
@@ -45,3 +47,45 @@ tests:
45
47
  - {_id: 2, x: 22}
46
48
  - {_id: 3, x: 33}
47
49
  - {_id: 4, x: 44}
50
+ -
51
+ description: "FindOneAndReplace when no documents match with id specified with upsert returning the document before modification"
52
+ operation:
53
+ name: findOneAndReplace
54
+ arguments:
55
+ filter: {_id: 4}
56
+ replacement: {_id: 4, x: 44}
57
+ projection: {x: 1, _id: 0}
58
+ # Omit the sort option as it has no effect when no documents
59
+ # match and would only cause an inconsistent return value on
60
+ # pre-3.0 servers when combined with returnDocument "before"
61
+ # (see: SERVER-17650).
62
+ upsert: true
63
+
64
+ outcome:
65
+ result: null
66
+ collection:
67
+ data:
68
+ - {_id: 1, x: 11}
69
+ - {_id: 2, x: 22}
70
+ - {_id: 3, x: 33}
71
+ - {_id: 4, x: 44}
72
+ -
73
+ description: "FindOneAndReplace when no documents match with id specified with upsert returning the document after modification"
74
+ operation:
75
+ name: findOneAndReplace
76
+ arguments:
77
+ filter: {_id: 4}
78
+ replacement: {_id: 4, x: 44}
79
+ projection: {x: 1, _id: 0}
80
+ returnDocument: After
81
+ sort: {x: 1}
82
+ upsert: true
83
+
84
+ outcome:
85
+ result: {x: 44}
86
+ collection:
87
+ data:
88
+ - {_id: 1, x: 11}
89
+ - {_id: 2, x: 22}
90
+ - {_id: 3, x: 33}
91
+ - {_id: 4, x: 44}