mongo 2.13.0.beta1 → 2.13.0.rc1

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 (170) hide show
  1. checksums.yaml +4 -4
  2. checksums.yaml.gz.sig +0 -0
  3. data.tar.gz.sig +1 -5
  4. data/Rakefile +15 -9
  5. data/lib/mongo.rb +4 -2
  6. data/lib/mongo/auth/aws/request.rb +4 -2
  7. data/lib/mongo/bulk_write.rb +1 -0
  8. data/lib/mongo/client.rb +143 -21
  9. data/lib/mongo/cluster.rb +53 -17
  10. data/lib/mongo/cluster/sdam_flow.rb +13 -10
  11. data/lib/mongo/cluster/topology/replica_set_no_primary.rb +3 -2
  12. data/lib/mongo/cluster/topology/sharded.rb +1 -1
  13. data/lib/mongo/cluster/topology/single.rb +1 -1
  14. data/lib/mongo/collection.rb +17 -13
  15. data/lib/mongo/collection/view/readable.rb +3 -1
  16. data/lib/mongo/collection/view/writable.rb +41 -5
  17. data/lib/mongo/database.rb +31 -4
  18. data/lib/mongo/database/view.rb +19 -4
  19. data/lib/mongo/distinguishing_semaphore.rb +55 -0
  20. data/lib/mongo/error.rb +1 -0
  21. data/lib/mongo/error/invalid_session.rb +2 -1
  22. data/lib/mongo/error/operation_failure.rb +6 -0
  23. data/lib/mongo/error/sessions_not_supported.rb +35 -0
  24. data/lib/mongo/event/base.rb +6 -0
  25. data/lib/mongo/grid/file.rb +5 -0
  26. data/lib/mongo/grid/file/chunk.rb +2 -0
  27. data/lib/mongo/grid/fs_bucket.rb +15 -13
  28. data/lib/mongo/grid/stream/write.rb +9 -3
  29. data/lib/mongo/monitoring.rb +38 -0
  30. data/lib/mongo/monitoring/command_log_subscriber.rb +10 -2
  31. data/lib/mongo/monitoring/event/command_failed.rb +11 -0
  32. data/lib/mongo/monitoring/event/command_started.rb +37 -2
  33. data/lib/mongo/monitoring/event/command_succeeded.rb +11 -0
  34. data/lib/mongo/monitoring/event/server_closed.rb +1 -1
  35. data/lib/mongo/monitoring/event/server_description_changed.rb +27 -4
  36. data/lib/mongo/monitoring/event/server_heartbeat_failed.rb +9 -2
  37. data/lib/mongo/monitoring/event/server_heartbeat_started.rb +9 -2
  38. data/lib/mongo/monitoring/event/server_heartbeat_succeeded.rb +9 -2
  39. data/lib/mongo/monitoring/event/server_opening.rb +1 -1
  40. data/lib/mongo/monitoring/event/topology_changed.rb +1 -1
  41. data/lib/mongo/monitoring/event/topology_closed.rb +1 -1
  42. data/lib/mongo/monitoring/event/topology_opening.rb +1 -1
  43. data/lib/mongo/monitoring/publishable.rb +6 -3
  44. data/lib/mongo/monitoring/server_description_changed_log_subscriber.rb +9 -1
  45. data/lib/mongo/monitoring/topology_changed_log_subscriber.rb +1 -1
  46. data/lib/mongo/protocol/message.rb +36 -8
  47. data/lib/mongo/protocol/msg.rb +14 -0
  48. data/lib/mongo/protocol/serializers.rb +5 -2
  49. data/lib/mongo/server.rb +10 -3
  50. data/lib/mongo/server/connection.rb +4 -4
  51. data/lib/mongo/server/connection_base.rb +3 -1
  52. data/lib/mongo/server/description.rb +5 -0
  53. data/lib/mongo/server/monitor.rb +76 -44
  54. data/lib/mongo/server/monitor/connection.rb +55 -7
  55. data/lib/mongo/server/pending_connection.rb +14 -4
  56. data/lib/mongo/server/push_monitor.rb +173 -0
  57. data/{spec/runners/transactions/context.rb → lib/mongo/server/push_monitor/connection.rb} +9 -14
  58. data/lib/mongo/server_selector.rb +0 -1
  59. data/lib/mongo/server_selector/base.rb +579 -1
  60. data/lib/mongo/server_selector/nearest.rb +1 -6
  61. data/lib/mongo/server_selector/primary.rb +1 -6
  62. data/lib/mongo/server_selector/primary_preferred.rb +7 -10
  63. data/lib/mongo/server_selector/secondary.rb +1 -6
  64. data/lib/mongo/server_selector/secondary_preferred.rb +1 -7
  65. data/lib/mongo/session.rb +2 -0
  66. data/lib/mongo/socket.rb +20 -8
  67. data/lib/mongo/socket/ssl.rb +1 -1
  68. data/lib/mongo/socket/tcp.rb +1 -1
  69. data/lib/mongo/topology_version.rb +9 -0
  70. data/lib/mongo/utils.rb +62 -0
  71. data/lib/mongo/version.rb +1 -1
  72. data/spec/README.aws-auth.md +2 -2
  73. data/spec/integration/awaited_ismaster_spec.rb +28 -0
  74. data/spec/integration/change_stream_examples_spec.rb +6 -2
  75. data/spec/integration/check_clean_slate_spec.rb +16 -0
  76. data/spec/integration/client_construction_spec.rb +1 -0
  77. data/spec/integration/connect_single_rs_name_spec.rb +5 -2
  78. data/spec/integration/connection_spec.rb +7 -4
  79. data/spec/integration/crud_spec.rb +4 -4
  80. data/spec/integration/docs_examples_spec.rb +6 -0
  81. data/spec/integration/grid_fs_bucket_spec.rb +48 -0
  82. data/spec/integration/heartbeat_events_spec.rb +4 -23
  83. data/spec/integration/read_concern_spec.rb +1 -1
  84. data/spec/integration/retryable_errors_spec.rb +1 -1
  85. data/spec/integration/retryable_writes/shared/performs_legacy_retries.rb +2 -2
  86. data/spec/integration/retryable_writes/shared/performs_modern_retries.rb +3 -3
  87. data/spec/integration/retryable_writes/shared/performs_no_retries.rb +2 -2
  88. data/spec/integration/sdam_error_handling_spec.rb +37 -15
  89. data/spec/integration/sdam_events_spec.rb +77 -6
  90. data/spec/integration/sdam_prose_spec.rb +64 -0
  91. data/spec/integration/server_monitor_spec.rb +25 -1
  92. data/spec/integration/size_limit_spec.rb +7 -3
  93. data/spec/integration/size_limit_spec.rb~12e1e9c4f... RUBY-2242 Fix zlib compression (#2021) +98 -0
  94. data/spec/integration/ssl_uri_options_spec.rb +2 -2
  95. data/spec/integration/zlib_compression_spec.rb +25 -0
  96. data/spec/lite_spec_helper.rb +12 -5
  97. data/spec/mongo/auth/aws/request_spec.rb +76 -0
  98. data/spec/mongo/auth/scram_spec.rb +1 -1
  99. data/spec/mongo/client_construction_spec.rb +207 -0
  100. data/spec/mongo/client_spec.rb +38 -3
  101. data/spec/mongo/cluster/topology/replica_set_spec.rb +52 -9
  102. data/spec/mongo/cluster/topology/single_spec.rb +4 -2
  103. data/spec/mongo/cluster_spec.rb +34 -35
  104. data/spec/mongo/collection/view/change_stream_resume_spec.rb +6 -6
  105. data/spec/mongo/collection_spec.rb +500 -0
  106. data/spec/mongo/database_spec.rb +245 -8
  107. data/spec/mongo/distinguishing_semaphore_spec.rb +63 -0
  108. data/spec/mongo/error/operation_failure_spec.rb +40 -0
  109. data/spec/mongo/index/view_spec.rb +2 -2
  110. data/spec/mongo/monitoring/event/server_description_changed_spec.rb +1 -4
  111. data/spec/mongo/protocol/msg_spec.rb +10 -0
  112. data/spec/mongo/semaphore_spec.rb +51 -0
  113. data/spec/mongo/server/connection_auth_spec.rb +2 -2
  114. data/spec/mongo/server_selector/nearest_spec.rb +23 -23
  115. data/spec/mongo/server_selector/primary_preferred_spec.rb +26 -26
  116. data/spec/mongo/server_selector/primary_spec.rb +9 -9
  117. data/spec/mongo/server_selector/secondary_preferred_spec.rb +22 -22
  118. data/spec/mongo/server_selector/secondary_spec.rb +18 -18
  119. data/spec/mongo/server_selector_spec.rb +4 -4
  120. data/spec/mongo/session_spec.rb +35 -0
  121. data/spec/runners/change_streams/test.rb +2 -2
  122. data/spec/runners/cmap.rb +1 -1
  123. data/spec/runners/command_monitoring.rb +3 -34
  124. data/spec/runners/crud/context.rb +9 -5
  125. data/spec/runners/crud/operation.rb +59 -27
  126. data/spec/runners/crud/spec.rb +0 -8
  127. data/spec/runners/crud/test.rb +1 -1
  128. data/spec/runners/sdam.rb +2 -2
  129. data/spec/runners/server_selection.rb +242 -28
  130. data/spec/runners/transactions.rb +12 -12
  131. data/spec/runners/transactions/operation.rb +151 -25
  132. data/spec/runners/transactions/test.rb +60 -16
  133. data/spec/spec_tests/command_monitoring_spec.rb +22 -12
  134. data/spec/spec_tests/crud_spec.rb +1 -1
  135. data/spec/spec_tests/data/change_streams/change-streams-errors.yml +4 -8
  136. data/spec/spec_tests/data/change_streams/change-streams-resume-whitelist.yml +66 -0
  137. data/spec/spec_tests/data/max_staleness/ReplicaSetNoPrimary/MaxStalenessTooSmall.yml +15 -0
  138. data/spec/spec_tests/data/max_staleness/ReplicaSetNoPrimary/NoKnownServers.yml +4 -3
  139. data/spec/spec_tests/data/max_staleness/Unknown/SmallMaxStaleness.yml +1 -0
  140. data/spec/spec_tests/data/sdam_integration/cancel-server-check.yml +96 -0
  141. data/spec/spec_tests/data/sdam_integration/connectTimeoutMS.yml +88 -0
  142. data/spec/spec_tests/data/sdam_integration/find-network-error.yml +83 -0
  143. data/spec/spec_tests/data/sdam_integration/find-shutdown-error.yml +116 -0
  144. data/spec/spec_tests/data/sdam_integration/insert-network-error.yml +86 -0
  145. data/spec/spec_tests/data/sdam_integration/insert-shutdown-error.yml +115 -0
  146. data/spec/spec_tests/data/sdam_integration/isMaster-command-error.yml +168 -0
  147. data/spec/spec_tests/data/sdam_integration/isMaster-network-error.yml +162 -0
  148. data/spec/spec_tests/data/sdam_integration/isMaster-timeout.yml +229 -0
  149. data/spec/spec_tests/data/sdam_integration/rediscover-quickly-after-step-down.yml +87 -0
  150. data/spec/spec_tests/max_staleness_spec.rb +4 -142
  151. data/spec/spec_tests/retryable_reads_spec.rb +2 -2
  152. data/spec/spec_tests/sdam_integration_spec.rb +13 -0
  153. data/spec/spec_tests/sdam_monitoring_spec.rb +1 -2
  154. data/spec/spec_tests/server_selection_spec.rb +4 -116
  155. data/spec/stress/cleanup_spec.rb +17 -2
  156. data/spec/stress/connection_pool_stress_spec.rb +10 -8
  157. data/spec/support/child_process_helper.rb +78 -0
  158. data/spec/support/client_registry.rb +1 -0
  159. data/spec/support/cluster_config.rb +4 -0
  160. data/spec/support/event_subscriber.rb +123 -33
  161. data/spec/support/keyword_struct.rb +26 -0
  162. data/spec/support/shared/server_selector.rb +13 -1
  163. data/spec/support/spec_config.rb +38 -13
  164. data/spec/support/spec_organizer.rb +129 -0
  165. data/spec/support/spec_setup.rb +1 -1
  166. data/spec/support/utils.rb +46 -0
  167. metadata +992 -942
  168. metadata.gz.sig +0 -0
  169. data/lib/mongo/server_selector/selectable.rb +0 -560
  170. data/spec/runners/sdam_monitoring.rb +0 -89
@@ -67,7 +67,7 @@ module Mongo
67
67
  end
68
68
 
69
69
  # Work around https://jira.mongodb.org/browse/SERVER-17397
70
- if ClusterConfig.instance.server_version < '4.3' &&
70
+ if ClusterConfig.instance.server_version < '4.4' &&
71
71
  global_client.cluster.servers.length > 1
72
72
  then
73
73
  mongos_each_direct_client do |client|
@@ -107,7 +107,7 @@ module Mongo
107
107
 
108
108
  def run
109
109
  change_stream = begin
110
- @target.watch(@pipeline, Utils.snakeize_hash(@options))
110
+ @target.watch(@pipeline, ::Utils.snakeize_hash(@options))
111
111
  rescue Mongo::Error::OperationFailure => e
112
112
  return {
113
113
  result: {
@@ -40,7 +40,7 @@ module Mongo
40
40
  @test = YAML.load(File.read(test_path))
41
41
 
42
42
  @description = @test['description']
43
- @pool_options = Utils.snakeize_hash(process_options(@test['poolOptions']))
43
+ @pool_options = ::Utils.snakeize_hash(process_options(@test['poolOptions']))
44
44
  @spec_ops = @test['operations'].map { |o| Operation.new(self, o) }
45
45
  @processed_ops = []
46
46
  @expected_error = @test['error']
@@ -135,7 +135,7 @@ module Mongo
135
135
  if expected.keys.first == '$numberLong'
136
136
  converted = expected.values.first.to_i
137
137
  if actual.is_a?(BSON::Int64)
138
- actual = Utils.int64_value(actual)
138
+ actual = ::Utils.int64_value(actual)
139
139
  elsif actual.is_a?(BSON::Int32)
140
140
  return false
141
141
  end
@@ -260,8 +260,9 @@ module Mongo
260
260
  # @param [ Mongo::Collection ] collection The collection.
261
261
  #
262
262
  # @since 2.1.0
263
- def run(collection)
263
+ def run(collection, subscriber)
264
264
  collection.insert_many(@data)
265
+ subscriber.clear_events!
265
266
  @operation.execute(collection)
266
267
  end
267
268
  end
@@ -338,37 +339,5 @@ module Mongo
338
339
  "match_#{event_type}"
339
340
  end
340
341
  end
341
-
342
- # The test subscriber to track the events.
343
- #
344
- # @since 2.1.0
345
- class TestSubscriber
346
-
347
- def started(event)
348
- command_started_event[event.command_name] = event
349
- end
350
-
351
- def succeeded(event)
352
- command_succeeded_event[event.command_name] = event
353
- end
354
-
355
- def failed(event)
356
- command_failed_event[event.command_name] = event
357
- end
358
-
359
- private
360
-
361
- def command_started_event
362
- @started_events ||= BSON::Document.new
363
- end
364
-
365
- def command_succeeded_event
366
- @succeeded_events ||= BSON::Document.new
367
- end
368
-
369
- def command_failed_event
370
- @failed_events ||= BSON::Document.new
371
- end
372
- end
373
342
  end
374
343
  end
@@ -12,12 +12,16 @@
12
12
  # See the License for the specific language governing permissions and
13
13
  # limitations under the License.
14
14
 
15
+ require 'support/keyword_struct'
16
+
15
17
  module Mongo
16
18
  module CRUD
17
- class Context
18
- def transform_arguments(arguments)
19
- arguments
20
- end
21
- end
19
+ Context = KeywordStruct.new(
20
+ :session0,
21
+ :session1,
22
+ :sdam_subscriber,
23
+ :threads,
24
+ :primary_address,
25
+ )
22
26
  end
23
27
  end
@@ -88,7 +88,7 @@ module Mongo
88
88
  #
89
89
  # @since 2.0.0
90
90
  def execute(target)
91
- op_name = Utils.underscore(name)
91
+ op_name = ::Utils.underscore(name)
92
92
  if target.is_a?(Mongo::Database)
93
93
  op_name = "db_#{op_name}"
94
94
  elsif target.is_a?(Mongo::Client)
@@ -99,14 +99,14 @@ module Mongo
99
99
 
100
100
  def database_options
101
101
  if opts = @spec['databaseOptions']
102
- Utils.convert_operation_options(opts)
102
+ ::Utils.convert_operation_options(opts)
103
103
  else
104
104
  nil
105
105
  end
106
106
  end
107
107
 
108
108
  def collection_options
109
- Utils.convert_operation_options(@spec['collectionOptions'])
109
+ ::Utils.convert_operation_options(@spec['collectionOptions'])
110
110
  end
111
111
 
112
112
  private
@@ -114,31 +114,31 @@ module Mongo
114
114
  # read operations
115
115
 
116
116
  def aggregate(collection, context)
117
- collection.aggregate(arguments['pipeline'], context.transform_arguments(options)).to_a
117
+ collection.aggregate(arguments['pipeline'], transformed_options(context)).to_a
118
118
  end
119
119
 
120
120
  def db_aggregate(database, context)
121
- database.aggregate(arguments['pipeline'], context.transform_arguments(options)).to_a
121
+ database.aggregate(arguments['pipeline'], transformed_options(context)).to_a
122
122
  end
123
123
 
124
124
  def count(collection, context)
125
- collection.count(arguments['filter'], context.transform_arguments(options))
125
+ collection.count(arguments['filter'], transformed_options(context))
126
126
  end
127
127
 
128
128
  def count_documents(collection, context)
129
- collection.count_documents(arguments['filter'], context.transform_arguments(options))
129
+ collection.count_documents(arguments['filter'], transformed_options(context))
130
130
  end
131
131
 
132
132
  def distinct(collection, context)
133
- collection.distinct(arguments['fieldName'], arguments['filter'], context.transform_arguments(options))
133
+ collection.distinct(arguments['fieldName'], arguments['filter'], transformed_options(context))
134
134
  end
135
135
 
136
136
  def estimated_document_count(collection, context)
137
- collection.estimated_document_count(context.transform_arguments(options))
137
+ collection.estimated_document_count(transformed_options(context))
138
138
  end
139
139
 
140
140
  def find(collection, context)
141
- opts = context.transform_arguments(options)
141
+ opts = transformed_options(context)
142
142
  if arguments['modifiers']
143
143
  opts = opts.merge(modifiers: BSON::Document.new(arguments['modifiers']))
144
144
  end
@@ -183,7 +183,7 @@ module Mongo
183
183
  # write operations
184
184
 
185
185
  def bulk_write(collection, context)
186
- result = collection.bulk_write(requests, context.transform_arguments(options))
186
+ result = collection.bulk_write(requests, transformed_options(context))
187
187
  return_doc = {}
188
188
  return_doc['deletedCount'] = result.deleted_count || 0
189
189
  return_doc['insertedIds'] = result.inserted_ids if result.inserted_ids
@@ -197,50 +197,50 @@ module Mongo
197
197
  end
198
198
 
199
199
  def delete_many(collection, context)
200
- result = collection.delete_many(arguments['filter'], context.transform_arguments(options))
200
+ result = collection.delete_many(arguments['filter'], transformed_options(context))
201
201
  { 'deletedCount' => result.deleted_count }
202
202
  end
203
203
 
204
204
  def delete_one(collection, context)
205
- result = collection.delete_one(arguments['filter'], context.transform_arguments(options))
205
+ result = collection.delete_one(arguments['filter'], transformed_options(context))
206
206
  { 'deletedCount' => result.deleted_count }
207
207
  end
208
208
 
209
209
  def insert_many(collection, context)
210
- result = collection.insert_many(arguments['documents'], context.transform_arguments(options))
210
+ result = collection.insert_many(arguments['documents'], transformed_options(context))
211
211
  { 'insertedIds' => result.inserted_ids }
212
212
  end
213
213
 
214
214
  def insert_one(collection, context)
215
- result = collection.insert_one(arguments['document'], context.transform_arguments(options))
215
+ result = collection.insert_one(arguments['document'], transformed_options(context))
216
216
  { 'insertedId' => result.inserted_id }
217
217
  end
218
218
 
219
219
  def replace_one(collection, context)
220
- result = collection.replace_one(arguments['filter'], arguments['replacement'], context.transform_arguments(options))
220
+ result = collection.replace_one(arguments['filter'], arguments['replacement'], transformed_options(context))
221
221
  update_return_doc(result)
222
222
  end
223
223
 
224
224
  def update_many(collection, context)
225
- result = collection.update_many(arguments['filter'], arguments['update'], context.transform_arguments(options))
225
+ result = collection.update_many(arguments['filter'], arguments['update'], transformed_options(context))
226
226
  update_return_doc(result)
227
227
  end
228
228
 
229
229
  def update_one(collection, context)
230
- result = collection.update_one(arguments['filter'], arguments['update'], context.transform_arguments(options))
230
+ result = collection.update_one(arguments['filter'], arguments['update'], transformed_options(context))
231
231
  update_return_doc(result)
232
232
  end
233
233
 
234
234
  def find_one_and_delete(collection, context)
235
- collection.find_one_and_delete(arguments['filter'], context.transform_arguments(options))
235
+ collection.find_one_and_delete(arguments['filter'], transformed_options(context))
236
236
  end
237
237
 
238
238
  def find_one_and_replace(collection, context)
239
- collection.find_one_and_replace(arguments['filter'], arguments['replacement'], context.transform_arguments(options))
239
+ collection.find_one_and_replace(arguments['filter'], arguments['replacement'], transformed_options(context))
240
240
  end
241
241
 
242
242
  def find_one_and_update(collection, context)
243
- collection.find_one_and_update(arguments['filter'], arguments['update'], context.transform_arguments(options))
243
+ collection.find_one_and_update(arguments['filter'], arguments['update'], transformed_options(context))
244
244
  end
245
245
 
246
246
  # ddl
@@ -270,7 +270,8 @@ module Mongo
270
270
  end
271
271
 
272
272
  def create_collection(database, context)
273
- database[arguments.fetch('collection')].create(session: context.session)
273
+ opts = transformed_options(context)
274
+ database[arguments.fetch('collection')].create(session: opts[:session])
274
275
  end
275
276
 
276
277
  def rename(collection, context)
@@ -351,6 +352,16 @@ module Mongo
351
352
  end
352
353
  end
353
354
 
355
+ def configure_fail_point(client, context)
356
+ fp = arguments.fetch('failPoint')
357
+ $disable_fail_points ||= []
358
+ $disable_fail_points << [
359
+ fp,
360
+ ClusterConfig.instance.primary_address,
361
+ ]
362
+ client.use('admin').database.command(fp)
363
+ end
364
+
354
365
  # options & arguments
355
366
 
356
367
  def options
@@ -360,7 +371,7 @@ module Mongo
360
371
  # bulk write test is an exception in that it has an "options" key
361
372
  # with the options.
362
373
  arguments.merge(arguments['options'] || {}).each do |spec_k, v|
363
- ruby_k = Utils.underscore(spec_k).to_sym
374
+ ruby_k = ::Utils.underscore(spec_k).to_sym
364
375
 
365
376
  if v.is_a?(Hash) && v['$numberLong']
366
377
  v = v['$numberLong'].to_i
@@ -390,8 +401,8 @@ module Mongo
390
401
  end
391
402
 
392
403
  def bulk_request(request)
393
- op_name = Utils.underscore(request['name'])
394
- args = Utils.shallow_snakeize_hash(request['arguments'])
404
+ op_name = ::Utils.underscore(request['name'])
405
+ args = ::Utils.shallow_snakeize_hash(request['arguments'])
395
406
  if args[:document]
396
407
  unless args.keys == [:document]
397
408
  raise "If :document is given, it must be the only key"
@@ -406,7 +417,7 @@ module Mongo
406
417
  end
407
418
 
408
419
  def transform_return_document(v)
409
- Utils.underscore(v).to_sym
420
+ ::Utils.underscore(v).to_sym
410
421
  end
411
422
 
412
423
  def update
@@ -414,7 +425,7 @@ module Mongo
414
425
  end
415
426
 
416
427
  def transform_read_preference(v)
417
- Utils.snakeize_hash(v)
428
+ ::Utils.snakeize_hash(v)
418
429
  end
419
430
 
420
431
  def read_preference
@@ -429,6 +440,27 @@ module Mongo
429
440
  return_doc['modifiedCount'] = result.modified_count if result.modified_count
430
441
  return_doc
431
442
  end
443
+
444
+ def transformed_options(context)
445
+ opts = options.dup
446
+ if opts[:session]
447
+ opts[:session] = case opts[:session]
448
+ when 'session0'
449
+ unless context.session0
450
+ raise "Trying to use session0 but it is not in context"
451
+ end
452
+ context.session0
453
+ when 'session1'
454
+ unless context.session1
455
+ raise "Trying to use session1 but it is not in context"
456
+ end
457
+ context.session1
458
+ else
459
+ raise "Invalid session name '#{opts[:session]}'"
460
+ end
461
+ end
462
+ opts
463
+ end
432
464
  end
433
465
  end
434
466
  end
@@ -11,14 +11,6 @@ module Mongo
11
11
  def initialize(test_path)
12
12
  contents = File.read(test_path)
13
13
 
14
- # Since Ruby driver binds a client to a database, change the
15
- # database name in the spec to the one we are using
16
- contents.sub!(/"crud-tests"/, '"ruby-driver"')
17
- contents.sub!(/"retryable-reads-tests"/, '"ruby-driver"')
18
- contents.sub!(/"transaction-tests"/, '"ruby-driver"')
19
- contents.sub!(/"withTransaction-tests"/, '"ruby-driver"')
20
- contents.sub!(/ default_write_concern_db/, ' ruby-driver')
21
-
22
14
  @spec = YAML.load(contents)
23
15
  @description = File.basename(test_path)
24
16
  @data = BSON::ExtJSON.parse_obj(@spec['data'])
@@ -28,7 +28,7 @@ module Mongo
28
28
  @spec = crud_spec
29
29
  @data = data
30
30
  @description = test['description']
31
- @client_options = Utils.convert_client_options(test['clientOptions'] || {})
31
+ @client_options = ::Utils.convert_client_options(test['clientOptions'] || {})
32
32
 
33
33
  if test['failPoint']
34
34
  @fail_point_command = FAIL_POINT_BASE_COMMAND.merge(test['failPoint'])
@@ -149,7 +149,7 @@ module Mongo
149
149
  end
150
150
 
151
151
  def when
152
- Utils.underscore(@spec.fetch('when'))
152
+ ::Utils.underscore(@spec.fetch('when'))
153
153
  end
154
154
 
155
155
  def max_wire_version
@@ -161,7 +161,7 @@ module Mongo
161
161
  end
162
162
 
163
163
  def type
164
- Utils.underscore(@spec.fetch('type'))
164
+ ::Utils.underscore(@spec.fetch('type'))
165
165
  end
166
166
 
167
167
  def result
@@ -73,19 +73,6 @@ module Mongo
73
73
  @type = Mongo::Cluster::Topology.const_get(@test['topology_description']['type'])
74
74
  end
75
75
 
76
- # Whether this spec describes a replica set.
77
- #
78
- # @example Determine if the spec describes a replica set.
79
- # spec.replica_set?
80
- #
81
- # @return [true, false] If the spec describes a replica set.
82
- #
83
- # @since 2.0.0
84
- def replica_set?
85
- type == Mongo::Cluster::Topology::ReplicaSetNoPrimary ||
86
- type == Mongo::Cluster::Topology::ReplicaSetWithPrimary
87
- end
88
-
89
76
  # Does this spec expect a server to be found.
90
77
  #
91
78
  # @example Will a server be found with this spec.
@@ -98,15 +85,10 @@ module Mongo
98
85
  !in_latency_window.empty?
99
86
  end
100
87
 
101
- # Is the max staleness setting invalid.
88
+ # Whether the test requires an error to be raised during server selection.
102
89
  #
103
- # @example Will the max staleness setting be valid with other options.
104
- # spec.invalid_max_staleness?
105
- #
106
- # @return [ true, false ] If an error will be raised by the max staleness setting.
107
- #
108
- # @since 2.4.0
109
- def invalid_max_staleness?
90
+ # @return [ true, false ] Whether the test expects an error.
91
+ def error?
110
92
  @test['error']
111
93
  end
112
94
 
@@ -122,9 +104,6 @@ module Mongo
122
104
  #
123
105
  # @since 2.0.0
124
106
  def in_latency_window
125
- if read_preference['mode'] == :secondary_preferred && primary
126
- return @in_latency_window.push(primary).uniq
127
- end
128
107
  @in_latency_window
129
108
  end
130
109
 
@@ -134,13 +113,248 @@ module Mongo
134
113
  #
135
114
  # @since 2.0.0
136
115
  def candidate_servers
137
- @candidate_servers.select { |s| !['Unknown', 'PossiblePrimary'].include?(s['type']) }
116
+ @candidate_servers
117
+ end
118
+ end
119
+ end
120
+ end
121
+ end
122
+
123
+ def define_server_selection_spec_tests(test_paths)
124
+ # Linter insists that a server selection semaphore is present when
125
+ # performing server selection.
126
+ skip_if_linting
127
+
128
+ test_paths.each do |file|
129
+
130
+ spec = Mongo::ServerSelection::Read::Spec.new(file)
131
+
132
+ context(spec.description) do
133
+ # Cluster needs a topology and topology needs a cluster...
134
+ # This temporary cluster is used for topology construction.
135
+ let(:temp_cluster) do
136
+ double('temp cluster').tap do |cluster|
137
+ allow(cluster).to receive(:servers_list).and_return([])
138
+ end
139
+ end
140
+
141
+ let(:topology) do
142
+ options = if spec.type <= Mongo::Cluster::Topology::ReplicaSetNoPrimary
143
+ {replica_set_name: 'foo'}
144
+ else
145
+ {}
146
+ end
147
+ spec.type.new(options, monitoring, temp_cluster)
148
+ end
149
+
150
+ let(:monitoring) do
151
+ Mongo::Monitoring.new(monitoring: false)
152
+ end
153
+
154
+ let(:listeners) do
155
+ Mongo::Event::Listeners.new
156
+ end
157
+
158
+ let(:options) do
159
+ if spec.heartbeat_frequency
160
+ {server_selection_timeout: 0.1, heartbeat_frequency: spec.heartbeat_frequency}
161
+ else
162
+ {server_selection_timeout: 0.1}
163
+ end
164
+ end
165
+
166
+ let(:cluster) do
167
+ double('cluster').tap do |c|
168
+ allow(c).to receive(:server_selection_semaphore)
169
+ allow(c).to receive(:connected?).and_return(true)
170
+ allow(c).to receive(:summary)
171
+ allow(c).to receive(:topology).and_return(topology)
172
+ allow(c).to receive(:single?).and_return(topology.single?)
173
+ allow(c).to receive(:sharded?).and_return(topology.sharded?)
174
+ allow(c).to receive(:replica_set?).and_return(topology.replica_set?)
175
+ allow(c).to receive(:unknown?).and_return(topology.unknown?)
176
+ allow(c).to receive(:options).and_return(options)
177
+ allow(c).to receive(:scan!).and_return(true)
178
+ allow(c).to receive(:app_metadata).and_return(app_metadata)
179
+ allow(c).to receive(:heartbeat_interval).and_return(
180
+ spec.heartbeat_frequency || Mongo::Server::Monitor::DEFAULT_HEARTBEAT_INTERVAL)
138
181
  end
182
+ end
183
+
184
+ # One of the spec test assertions is on the set of servers that are
185
+ # eligible for selection without taking latency into account.
186
+ # In the driver, latency is taken into account at various points during
187
+ # server selection, hence there isn't a method that can be called to
188
+ # retrieve the list of servers without accounting for latency.
189
+ # Work around this by executing server selection with all servers set
190
+ # to zero latency, when evaluating the candidate server set.
191
+ let(:ignore_latency) { false }
192
+
193
+ let(:candidate_servers) do
194
+ spec.candidate_servers.collect do |server|
195
+ features = double('features').tap do |feat|
196
+ allow(feat).to receive(:max_staleness_enabled?).and_return(server['maxWireVersion'] && server['maxWireVersion'] >= 5)
197
+ allow(feat).to receive(:check_driver_support!).and_return(true)
198
+ end
199
+ address = Mongo::Address.new(server['address'])
200
+ Mongo::Server.new(address, cluster, monitoring, listeners,
201
+ {monitoring_io: false}.update(options)
202
+ ).tap do |s|
203
+ allow(s).to receive(:average_round_trip_time) do
204
+ if ignore_latency
205
+ 0
206
+ elsif server['avg_rtt_ms']
207
+ server['avg_rtt_ms'] / 1000.0
208
+ end
209
+ end
210
+ allow(s).to receive(:tags).and_return(server['tags'])
211
+ allow(s).to receive(:secondary?).and_return(server['type'] == 'RSSecondary')
212
+ allow(s).to receive(:primary?).and_return(server['type'] == 'RSPrimary')
213
+ allow(s).to receive(:mongos?).and_return(server['type'] == 'Mongos')
214
+ allow(s).to receive(:standalone?).and_return(server['type'] == 'Standalone')
215
+ allow(s).to receive(:unknown?).and_return(server['type'] == 'Unknown')
216
+ allow(s).to receive(:connectable?).and_return(true)
217
+ allow(s).to receive(:last_write_date).and_return(
218
+ Time.at(server['lastWrite']['lastWriteDate']['$numberLong'].to_f / 1000)) if server['lastWrite']
219
+ allow(s).to receive(:last_scan).and_return(
220
+ Time.at(server['lastUpdateTime'].to_f / 1000))
221
+ allow(s).to receive(:features).and_return(features)
222
+ allow(s).to receive(:replica_set_name).and_return('foo')
223
+ end
224
+ end
225
+ end
226
+
227
+ let(:suitable_servers) do
228
+ spec.suitable_servers.collect do |server|
229
+ Mongo::Server.new(Mongo::Address.new(server['address']), cluster, monitoring, listeners,
230
+ options.merge(monitoring_io: false))
231
+ end
232
+ end
139
233
 
140
- private
234
+ let(:in_latency_window) do
235
+ spec.in_latency_window.collect do |server|
236
+ Mongo::Server.new(Mongo::Address.new(server['address']), cluster, monitoring, listeners,
237
+ options.merge(monitoring_io: false))
238
+ end
239
+ end
240
+
241
+ let(:server_selector_definition) do
242
+ { mode: spec.read_preference['mode'] }.tap do |definition|
243
+ definition[:tag_sets] = spec.read_preference['tag_sets']
244
+ definition[:max_staleness] = spec.max_staleness if spec.max_staleness
245
+ end
246
+ end
247
+
248
+ let(:server_selector) do
249
+ Mongo::ServerSelector.get(server_selector_definition)
250
+ end
251
+
252
+ let(:app_metadata) do
253
+ Mongo::Server::AppMetadata.new({})
254
+ end
255
+
256
+ before do
257
+ allow(cluster).to receive(:servers_list).and_return(candidate_servers)
258
+ allow(cluster).to receive(:servers) do
259
+ # Copy Cluster#servers definition because clusters is a double
260
+ cluster.topology.servers(cluster.servers_list)
261
+ end
262
+ allow(cluster).to receive(:addresses).and_return(candidate_servers.map(&:address))
263
+ end
264
+
265
+ if spec.error?
266
+
267
+ it 'Raises an InvalidServerPreference exception' do
268
+
269
+ expect do
270
+ server_selector.select_server(cluster)
271
+ end.to raise_exception(Mongo::Error::InvalidServerPreference)
272
+ end
273
+
274
+ else
275
+
276
+ if spec.server_available?
277
+
278
+ it 'has non-empty suitable servers' do
279
+ spec.suitable_servers.should be_a(Array)
280
+ spec.suitable_servers.should_not be_empty
281
+ end
282
+
283
+ if spec.in_latency_window.length == 1
284
+
285
+ it 'selects the expected server' do
286
+ [server_selector.select_server(cluster)].should == in_latency_window
287
+ end
288
+
289
+ else
290
+
291
+ it 'selects a server in the suitable list' do
292
+ in_latency_window.should include(server_selector.select_server(cluster))
293
+ end
294
+
295
+ let(:expected_addresses) do
296
+ in_latency_window.map(&:address).map(&:seed).sort
297
+ end
298
+
299
+ let(:actual_addresses) do
300
+ server_selector.suitable_servers(cluster).map(&:address).map(&:seed).sort
301
+ end
302
+
303
+ it 'identifies expected suitable servers' do
304
+ actual_addresses.should == expected_addresses
305
+ end
306
+
307
+ end
308
+
309
+ context 'candidate servers without taking latency into account' do
310
+ let(:ignore_latency) { true }
311
+
312
+ let(:expected_addresses) do
313
+ suitable_servers.map(&:address).map(&:seed).sort
314
+ end
315
+
316
+ let(:actual_addresses) do
317
+ servers = server_selector.send(:suitable_servers, cluster)
318
+
319
+ # The tests expect that only secondaries are "suitable" for
320
+ # server selection with secondary preferred read preference.
321
+ # In actuality, primaries are also suitable, and the driver
322
+ # returns the primaries also. Remove primaries from the
323
+ # actual set when read preference is secondary preferred.
324
+ # HOWEVER, if a test ends up selecting a primary, then it
325
+ # includes that primary into its suitable servers. Therefore
326
+ # only remove primaries when the number of suitable servers
327
+ # is greater than 1.
328
+ servers.delete_if do |server|
329
+ server_selector.is_a?(Mongo::ServerSelector::SecondaryPreferred) &&
330
+ server.primary? &&
331
+ servers.length > 1
332
+ end
333
+
334
+ # Since we remove the latency requirement, the servers
335
+ # may be returned in arbitrary order.
336
+ servers.map(&:address).map(&:seed).sort
337
+ end
338
+
339
+ it 'identifies expected suitable servers' do
340
+ actual_addresses.should == expected_addresses
341
+ end
342
+ end
343
+
344
+ else
345
+
346
+ # Runner does not handle non-empty suitable servers with
347
+ # no servers in latency window.
348
+ it 'has empty suitable servers' do
349
+ expect(spec.suitable_servers).to eq([])
350
+ end
351
+
352
+ it 'Raises a NoServerAvailable Exception' do
353
+ expect do
354
+ server_selector.select_server(cluster)
355
+ end.to raise_exception(Mongo::Error::NoServerAvailable)
356
+ end
141
357
 
142
- def primary
143
- @candidate_servers.find { |s| s['type'] == 'RSPrimary' }
144
358
  end
145
359
  end
146
360
  end