mongo 2.14.0 → 2.15.0.alpha

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 (230) 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 +4 -1
  5. data/Rakefile +8 -15
  6. data/lib/mongo.rb +23 -0
  7. data/lib/mongo/auth/aws/conversation.rb +1 -4
  8. data/lib/mongo/auth/base.rb +13 -7
  9. data/lib/mongo/auth/conversation_base.rb +32 -0
  10. data/lib/mongo/auth/cr/conversation.rb +6 -29
  11. data/lib/mongo/auth/gssapi/conversation.rb +4 -15
  12. data/lib/mongo/auth/ldap/conversation.rb +3 -14
  13. data/lib/mongo/auth/sasl_conversation_base.rb +1 -13
  14. data/lib/mongo/auth/scram_conversation_base.rb +7 -34
  15. data/lib/mongo/auth/user/view.rb +16 -9
  16. data/lib/mongo/auth/x509/conversation.rb +4 -25
  17. data/lib/mongo/background_thread.rb +11 -0
  18. data/lib/mongo/bulk_write.rb +21 -18
  19. data/lib/mongo/client.rb +82 -6
  20. data/lib/mongo/cluster.rb +19 -28
  21. data/lib/mongo/cluster/reapers/cursor_reaper.rb +6 -2
  22. data/lib/mongo/cluster/sdam_flow.rb +14 -0
  23. data/lib/mongo/collection.rb +8 -6
  24. data/lib/mongo/collection/view/aggregation.rb +1 -1
  25. data/lib/mongo/collection/view/change_stream.rb +1 -1
  26. data/lib/mongo/collection/view/iterable.rb +1 -1
  27. data/lib/mongo/collection/view/map_reduce.rb +2 -2
  28. data/lib/mongo/collection/view/readable.rb +42 -20
  29. data/lib/mongo/collection/view/writable.rb +14 -14
  30. data/lib/mongo/cursor.rb +2 -2
  31. data/lib/mongo/database.rb +22 -5
  32. data/lib/mongo/database/view.rb +1 -1
  33. data/lib/mongo/error.rb +9 -1
  34. data/lib/mongo/error/bulk_write_error.rb +17 -3
  35. data/lib/mongo/error/internal_driver_error.rb +22 -0
  36. data/lib/mongo/error/operation_failure.rb +21 -2
  37. data/lib/mongo/error/parser.rb +65 -12
  38. data/lib/mongo/error/server_api_conflict.rb +23 -0
  39. data/lib/mongo/error/server_api_not_supported.rb +24 -0
  40. data/lib/mongo/error/unmet_dependency.rb +21 -0
  41. data/lib/mongo/grid/fs_bucket.rb +37 -37
  42. data/lib/mongo/index/view.rb +21 -11
  43. data/lib/mongo/monitoring.rb +13 -4
  44. data/lib/mongo/monitoring/event/server_heartbeat_failed.rb +27 -16
  45. data/lib/mongo/monitoring/event/server_heartbeat_succeeded.rb +26 -15
  46. data/lib/mongo/operation.rb +2 -2
  47. data/lib/mongo/operation/collections_info.rb +18 -1
  48. data/lib/mongo/operation/collections_info/command.rb +2 -2
  49. data/lib/mongo/operation/context.rb +99 -0
  50. data/lib/mongo/operation/indexes.rb +15 -1
  51. data/lib/mongo/operation/insert/command.rb +2 -2
  52. data/lib/mongo/operation/insert/legacy.rb +2 -2
  53. data/lib/mongo/operation/insert/op_msg.rb +2 -2
  54. data/lib/mongo/operation/list_collections/result.rb +4 -1
  55. data/lib/mongo/operation/result.rb +2 -0
  56. data/lib/mongo/operation/shared/executable.rb +24 -14
  57. data/lib/mongo/operation/shared/executable_no_validate.rb +2 -2
  58. data/lib/mongo/operation/shared/op_msg_or_command.rb +1 -7
  59. data/lib/mongo/operation/shared/op_msg_or_find_command.rb +1 -7
  60. data/lib/mongo/operation/shared/polymorphic_operation.rb +39 -0
  61. data/lib/mongo/operation/shared/response_handling.rb +23 -23
  62. data/lib/mongo/operation/shared/sessions_supported.rb +13 -2
  63. data/lib/mongo/operation/shared/write.rb +8 -18
  64. data/lib/mongo/protocol/compressed.rb +51 -5
  65. data/lib/mongo/protocol/message.rb +20 -2
  66. data/lib/mongo/protocol/msg.rb +36 -11
  67. data/lib/mongo/query_cache.rb +30 -0
  68. data/lib/mongo/retryable.rb +1 -1
  69. data/lib/mongo/server.rb +7 -15
  70. data/lib/mongo/server/app_metadata.rb +52 -18
  71. data/lib/mongo/server/connection.rb +5 -0
  72. data/lib/mongo/server/connection_base.rb +13 -10
  73. data/lib/mongo/server/connection_pool.rb +6 -4
  74. data/lib/mongo/server/description.rb +4 -0
  75. data/lib/mongo/server/description/features.rb +9 -8
  76. data/lib/mongo/server/monitor.rb +20 -1
  77. data/lib/mongo/server/monitor/app_metadata.rb +1 -1
  78. data/lib/mongo/server/monitor/connection.rb +9 -10
  79. data/lib/mongo/server/pending_connection.rb +24 -6
  80. data/lib/mongo/server/push_monitor.rb +11 -1
  81. data/lib/mongo/session.rb +2 -2
  82. data/lib/mongo/session/session_pool.rb +4 -2
  83. data/lib/mongo/socket.rb +29 -4
  84. data/lib/mongo/socket/ssl.rb +8 -0
  85. data/lib/mongo/srv/monitor.rb +0 -11
  86. data/lib/mongo/uri/options_mapper.rb +38 -0
  87. data/lib/mongo/utils.rb +15 -0
  88. data/lib/mongo/version.rb +1 -1
  89. data/spec/README.md +24 -1
  90. data/spec/integration/auth_spec.rb +25 -15
  91. data/spec/integration/bulk_write_error_message_spec.rb +41 -0
  92. data/spec/integration/change_stream_spec.rb +4 -4
  93. data/spec/integration/command_monitoring_spec.rb +2 -2
  94. data/spec/integration/connection_spec.rb +2 -0
  95. data/spec/integration/docs_examples_spec.rb +8 -1
  96. data/spec/integration/fork_reconnect_spec.rb +4 -1
  97. data/spec/integration/ocsp_verifier_spec.rb +13 -7
  98. data/spec/integration/operation_failure_code_spec.rb +1 -1
  99. data/spec/integration/operation_failure_message_spec.rb +90 -0
  100. data/spec/integration/reconnect_spec.rb +1 -1
  101. data/spec/integration/sdam_error_handling_spec.rb +1 -1
  102. data/spec/integration/sdam_events_spec.rb +3 -5
  103. data/spec/integration/snappy_compression_spec.rb +25 -0
  104. data/spec/integration/srv_monitoring_spec.rb +1 -1
  105. data/spec/integration/transactions_examples_spec.rb +6 -0
  106. data/spec/integration/zlib_compression_spec.rb +1 -1
  107. data/spec/integration/zstd_compression_spec.rb +26 -0
  108. data/spec/lite_spec_helper.rb +7 -1
  109. data/spec/mongo/address_spec.rb +15 -11
  110. data/spec/mongo/auth/ldap/conversation_spec.rb +1 -1
  111. data/spec/mongo/auth/ldap_spec.rb +5 -1
  112. data/spec/mongo/auth/scram_negotiation_spec.rb +1 -1
  113. data/spec/mongo/auth/scram_spec.rb +1 -1
  114. data/spec/mongo/auth/x509/conversation_spec.rb +3 -3
  115. data/spec/mongo/client_construction_spec.rb +207 -33
  116. data/spec/mongo/client_spec.rb +17 -0
  117. data/spec/mongo/cluster_spec.rb +3 -18
  118. data/spec/mongo/collection/view/explainable_spec.rb +1 -1
  119. data/spec/mongo/collection/view/readable_spec.rb +33 -19
  120. data/spec/mongo/collection_crud_spec.rb +4357 -0
  121. data/spec/mongo/collection_ddl_spec.rb +534 -0
  122. data/spec/mongo/collection_spec.rb +5 -4859
  123. data/spec/mongo/database_spec.rb +66 -4
  124. data/spec/mongo/error/bulk_write_error_spec.rb +3 -3
  125. data/spec/mongo/error/parser_spec.rb +37 -6
  126. data/spec/mongo/index/view_spec.rb +8 -2
  127. data/spec/mongo/monitoring/event/server_heartbeat_failed_spec.rb +1 -1
  128. data/spec/mongo/monitoring/event/server_heartbeat_succeeded_spec.rb +1 -1
  129. data/spec/mongo/operation/aggregate_spec.rb +2 -1
  130. data/spec/mongo/operation/collections_info_spec.rb +4 -1
  131. data/spec/mongo/operation/command_spec.rb +6 -3
  132. data/spec/mongo/operation/create_index_spec.rb +6 -3
  133. data/spec/mongo/operation/create_user_spec.rb +6 -3
  134. data/spec/mongo/operation/delete/bulk_spec.rb +9 -6
  135. data/spec/mongo/operation/delete_spec.rb +11 -7
  136. data/spec/mongo/operation/drop_index_spec.rb +6 -2
  137. data/spec/mongo/operation/find/legacy_spec.rb +3 -1
  138. data/spec/mongo/operation/get_more_spec.rb +3 -1
  139. data/spec/mongo/operation/indexes_spec.rb +5 -1
  140. data/spec/mongo/operation/insert/bulk_spec.rb +10 -7
  141. data/spec/mongo/operation/insert_spec.rb +15 -12
  142. data/spec/mongo/operation/map_reduce_spec.rb +5 -2
  143. data/spec/mongo/operation/remove_user_spec.rb +6 -3
  144. data/spec/mongo/operation/result_spec.rb +1 -1
  145. data/spec/mongo/operation/update/bulk_spec.rb +9 -6
  146. data/spec/mongo/operation/update_spec.rb +10 -7
  147. data/spec/mongo/operation/update_user_spec.rb +4 -1
  148. data/spec/mongo/protocol/compressed_spec.rb +26 -12
  149. data/spec/mongo/query_cache_middleware_spec.rb +55 -0
  150. data/spec/mongo/retryable_spec.rb +3 -2
  151. data/spec/mongo/server/app_metadata_spec.rb +2 -0
  152. data/spec/mongo/server/connection_pool/populator_spec.rb +3 -1
  153. data/spec/mongo/server/connection_pool_spec.rb +1 -1
  154. data/spec/mongo/server/connection_spec.rb +24 -17
  155. data/spec/mongo/server/monitor/connection_spec.rb +17 -7
  156. data/spec/mongo/server/monitor_spec.rb +9 -1
  157. data/spec/mongo/server_spec.rb +15 -2
  158. data/spec/mongo/socket/ssl_spec.rb +40 -0
  159. data/spec/mongo/socket_spec.rb +2 -2
  160. data/spec/mongo/tls_context_hooks_spec.rb +37 -0
  161. data/spec/runners/connection_string.rb +0 -4
  162. data/spec/runners/crud/requirement.rb +40 -3
  163. data/spec/runners/crud/verifier.rb +8 -0
  164. data/spec/runners/transactions/operation.rb +13 -2
  165. data/spec/runners/transactions/test.rb +1 -0
  166. data/spec/runners/unified.rb +96 -0
  167. data/spec/runners/unified/assertions.rb +249 -0
  168. data/spec/runners/unified/change_stream_operations.rb +26 -0
  169. data/spec/runners/unified/crud_operations.rb +199 -0
  170. data/spec/runners/unified/ddl_operations.rb +96 -0
  171. data/spec/runners/unified/entity_map.rb +39 -0
  172. data/spec/runners/unified/error.rb +25 -0
  173. data/spec/runners/unified/event_subscriber.rb +91 -0
  174. data/spec/runners/unified/exceptions.rb +21 -0
  175. data/spec/runners/unified/grid_fs_operations.rb +55 -0
  176. data/spec/runners/unified/support_operations.rb +250 -0
  177. data/spec/runners/unified/test.rb +393 -0
  178. data/spec/runners/unified/test_group.rb +28 -0
  179. data/spec/runners/unified/using_hash.rb +31 -0
  180. data/spec/shared/bin/get-mongodb-download-url +17 -0
  181. data/spec/shared/lib/mrss/cluster_config.rb +218 -0
  182. data/spec/shared/lib/mrss/constraints.rb +43 -0
  183. data/spec/shared/lib/mrss/docker_runner.rb +262 -0
  184. data/spec/shared/lib/mrss/server_version_registry.rb +112 -0
  185. data/spec/shared/lib/mrss/utils.rb +15 -0
  186. data/spec/shared/share/Dockerfile.erb +231 -0
  187. data/spec/shared/shlib/distro.sh +73 -0
  188. data/spec/shared/shlib/server.sh +290 -0
  189. data/spec/shared/shlib/set_env.sh +128 -0
  190. data/spec/solo/clean_exit_spec.rb +21 -0
  191. data/spec/spec_helper.rb +4 -1
  192. data/spec/spec_tests/crud_unified_spec.rb +10 -0
  193. data/spec/spec_tests/data/change_streams/change-streams.yml +0 -1
  194. data/spec/spec_tests/data/crud_unified/estimatedDocumentCount.yml +267 -0
  195. data/spec/spec_tests/data/retryable_reads/estimatedDocumentCount-4.9.yml +60 -0
  196. data/spec/spec_tests/data/retryable_reads/{estimatedDocumentCount.yml → estimatedDocumentCount-pre4.9.yml} +2 -0
  197. data/spec/spec_tests/data/retryable_reads/estimatedDocumentCount-serverErrors-4.9.yml +146 -0
  198. data/spec/spec_tests/data/retryable_reads/{estimatedDocumentCount-serverErrors.yml → estimatedDocumentCount-serverErrors-pre4.9.yml} +2 -0
  199. data/spec/spec_tests/data/retryable_reads/listIndexNames.yml +1 -1
  200. data/spec/spec_tests/data/unified/valid-fail/operation-failure.yml +31 -0
  201. data/spec/spec_tests/data/unified/valid-pass/poc-change-streams.yml +220 -0
  202. data/spec/spec_tests/data/unified/valid-pass/poc-command-monitoring.yml +102 -0
  203. data/spec/spec_tests/data/unified/valid-pass/poc-crud.yml +184 -0
  204. data/spec/spec_tests/data/unified/valid-pass/poc-gridfs.yml +155 -0
  205. data/spec/spec_tests/data/unified/valid-pass/poc-retryable-reads.yml +193 -0
  206. data/spec/spec_tests/data/unified/valid-pass/poc-retryable-writes.yml +210 -0
  207. data/spec/spec_tests/data/unified/valid-pass/poc-sessions.yml +215 -0
  208. data/spec/spec_tests/data/unified/valid-pass/poc-transactions-convenient-api.yml +235 -0
  209. data/spec/spec_tests/data/unified/valid-pass/poc-transactions-mongos-pin-auto.yml +169 -0
  210. data/spec/spec_tests/data/unified/valid-pass/poc-transactions.yml +170 -0
  211. data/spec/spec_tests/data/uri_options/compression-options.yml +1 -1
  212. data/spec/spec_tests/data/versioned_api/crud-api-version-1-strict.yml +416 -0
  213. data/spec/spec_tests/data/versioned_api/crud-api-version-1.yml +409 -0
  214. data/spec/spec_tests/data/versioned_api/runcommand-helper-no-api-version-declared.yml +67 -0
  215. data/spec/spec_tests/data/versioned_api/test-commands-deprecation-errors.yml +47 -0
  216. data/spec/spec_tests/data/versioned_api/test-commands-strict-mode.yml +44 -0
  217. data/spec/spec_tests/data/versioned_api/transaction-handling.yml +180 -0
  218. data/spec/spec_tests/unified_spec.rb +15 -0
  219. data/spec/spec_tests/uri_options_spec.rb +16 -0
  220. data/spec/spec_tests/versioned_api_spec.rb +10 -0
  221. data/spec/support/common_shortcuts.rb +15 -1
  222. data/spec/support/shared/session.rb +2 -2
  223. data/spec/support/spec_config.rb +46 -3
  224. data/spec/support/spec_setup.rb +48 -38
  225. data/spec/support/utils.rb +64 -3
  226. metadata +1104 -992
  227. metadata.gz.sig +0 -0
  228. data/lib/mongo/operation/shared/collections_info_or_list_collections.rb +0 -58
  229. data/lib/mongo/operation/shared/op_msg_or_list_indexes_command.rb +0 -47
  230. data/spec/support/cluster_config.rb +0 -207
@@ -17,6 +17,12 @@ describe Mongo::Server::Monitor do
17
17
  {}
18
18
  end
19
19
 
20
+ let(:monitor_app_metadata) do
21
+ Mongo::Server::Monitor::AppMetadata.new(
22
+ server_api: SpecConfig.instance.ruby_options[:server_api],
23
+ )
24
+ end
25
+
20
26
  let(:cluster) do
21
27
  double('cluster').tap do |cluster|
22
28
  allow(cluster).to receive(:run_sdam_flow)
@@ -32,7 +38,9 @@ describe Mongo::Server::Monitor do
32
38
  let(:monitor) do
33
39
  register_background_thread_object(
34
40
  described_class.new(server, listeners, Mongo::Monitoring.new,
35
- SpecConfig.instance.test_options.merge(cluster: cluster).merge(monitor_options))
41
+ SpecConfig.instance.test_options.merge(cluster: cluster).merge(monitor_options).update(
42
+ app_metadata: monitor_app_metadata,
43
+ push_monitor_app_metadata: monitor_app_metadata))
36
44
  )
37
45
  end
38
46
 
@@ -40,12 +40,20 @@ describe Mongo::Server do
40
40
  )
41
41
  end
42
42
 
43
+ let(:monitor_app_metadata) do
44
+ Mongo::Server::Monitor::AppMetadata.new(
45
+ server_api: SpecConfig.instance.ruby_options[:server_api],
46
+ )
47
+ end
48
+
43
49
  shared_context 'with monitoring io' do
44
50
  let(:server_options) do
45
51
  {monitoring_io: true}
46
52
  end
47
53
 
48
54
  before do
55
+ allow(cluster).to receive(:monitor_app_metadata).and_return(monitor_app_metadata)
56
+ allow(cluster).to receive(:push_monitor_app_metadata).and_return(monitor_app_metadata)
49
57
  allow(cluster).to receive(:heartbeat_interval).and_return(1000)
50
58
  end
51
59
  end
@@ -144,8 +152,13 @@ describe Mongo::Server do
144
152
  expect(server.options[:monitoring_io]).to be true
145
153
  end
146
154
 
147
- it 'creates monitor with monitoring app metadata' do
148
- expect(server.monitor.options[:app_metadata]).to be_a(Mongo::Server::Monitor::AppMetadata)
155
+ context 'with monitoring app metadata option' do
156
+ require_no_required_api_version
157
+
158
+ it 'creates monitor with monitoring app metadata' do
159
+ server.monitor.scan!
160
+ expect(server.monitor.connection.options[:app_metadata]).to be monitor_app_metadata
161
+ end
149
162
  end
150
163
 
151
164
  context 'monitoring_io: false' do
@@ -50,6 +50,46 @@ describe Mongo::Socket::SSL, retry: 3 do
50
50
  end
51
51
 
52
52
  describe '#connect!' do
53
+ context 'when TLS context hooks are provided' do
54
+ # https://github.com/jruby/jruby-openssl/issues/221
55
+ fails_on_jruby
56
+
57
+ let(:proc) do
58
+ Proc.new do |context|
59
+ if BSON::Environment.jruby?
60
+ context.ciphers = ["AES256-SHA256"]
61
+ else
62
+ context.ciphers = ["AES256-SHA"]
63
+ end
64
+ end
65
+ end
66
+
67
+ before do
68
+ Mongo.tls_context_hooks = [ proc ]
69
+ end
70
+
71
+ after do
72
+ Mongo.tls_context_hooks.clear
73
+ end
74
+
75
+ it 'runs the TLS context hook before connecting' do
76
+ if ENV['OCSP_ALGORITHM']
77
+ skip "OCSP configurations use different certificates which this test does not handle"
78
+ end
79
+
80
+ expect(proc).to receive(:call).and_call_original
81
+ socket
82
+ # Even though we are requesting a single cipher in the hook,
83
+ # there may be multiple ciphers available in the context.
84
+ # All of the ciphers should match the requested one (using
85
+ # OpenSSL's idea of what "match" means).
86
+ socket.context.ciphers.each do |cipher|
87
+ unless cipher.first =~ /SHA256/ || cipher.last == 256
88
+ raise "Unexpected cipher #{cipher} after requesting SHA-256"
89
+ end
90
+ end
91
+ end
92
+ end
53
93
 
54
94
  context 'when a certificate is provided' do
55
95
 
@@ -48,7 +48,7 @@ describe Mongo::Socket do
48
48
  socket.send(:map_exceptions) do
49
49
  raise OpenSSL::SSL::SSLError.new('Test error')
50
50
  end
51
- end.to raise_error(Mongo::Error::SocketError, 'OpenSSL::SSL::SSLError: Test error (for fake-address) (MongoDB may not be configured with TLS support)')
51
+ end.to raise_error(Mongo::Error::SocketError, 'OpenSSL::SSL::SSLError: Test error (for fake-address)')
52
52
  end
53
53
  end
54
54
 
@@ -86,7 +86,7 @@ describe Mongo::Socket do
86
86
 
87
87
  expect do
88
88
  socket.read(10)
89
- end.to raise_error(Mongo::Error::SocketTimeoutError, /Took more than .* seconds to receive data \(for /)
89
+ end.to raise_error(Mongo::Error::SocketTimeoutError, /Took more than .* seconds to receive data.*\(for /)
90
90
  end
91
91
  end
92
92
 
@@ -0,0 +1,37 @@
1
+ require 'spec_helper'
2
+
3
+ describe Mongo do
4
+ before do
5
+ Mongo.tls_context_hooks.clear
6
+ end
7
+
8
+ describe '#tls_context_hooks' do
9
+ it 'returns an array' do
10
+ expect(Mongo.tls_context_hooks).to eq([])
11
+ end
12
+ end
13
+
14
+ describe '#tls_context_hooks=' do
15
+ context 'when argument is not an array' do
16
+ it 'raises an ArgumentError' do
17
+ expect do
18
+ Mongo.tls_context_hooks = "Hello"
19
+ end.to raise_error(ArgumentError, /TLS context hooks must be an array of Procs/)
20
+ end
21
+ end
22
+
23
+ context 'when argument is an array not containing procs' do
24
+ it 'raises an ArgumentError' do
25
+ expect do
26
+ Mongo.tls_context_hooks = [1, 2, 3]
27
+ end.to raise_error(ArgumentError, /TLS context hooks must be an array of Procs/)
28
+ end
29
+ end
30
+
31
+ it 'saves the provided hooks' do
32
+ Mongo.tls_context_hooks = [ Proc.new { |x| x ** 2 } ]
33
+ expect(Mongo.tls_context_hooks.length).to eq(1)
34
+ expect(Mongo.tls_context_hooks.first).to be_a(Proc)
35
+ end
36
+ end
37
+ end
@@ -235,10 +235,6 @@ module Mongo
235
235
  if k.downcase == 'authmechanismproperties'
236
236
  expected[k] = ::Utils.downcase_keys(v)
237
237
  end
238
- # Ruby driver does not support snappy.
239
- if k == 'compressors'
240
- expected[k] = v.reject { |sub_v| sub_v == 'snappy' }
241
- end
242
238
  end
243
239
  # We omit retryReads/retryWrites=true because some tests do not
244
240
  # provide those.
@@ -1,18 +1,39 @@
1
1
  module Mongo
2
2
  module CRUD
3
3
  class Requirement
4
- YAML_KEYS = %w(minServerVersion maxServerVersion topology).freeze
4
+ YAML_KEYS = %w(minServerVersion maxServerVersion topology topologies serverParameters).freeze
5
5
 
6
6
  def initialize(spec)
7
+ spec = spec.dup
8
+ # Legacy tests have the requirements mixed with other test fields
9
+ spec.delete('data')
10
+ spec.delete('tests')
11
+
12
+ unless (unhandled_keys = spec.keys - YAML_KEYS).empty?
13
+ raise "Unhandled requirement specification keys: #{unhandled_keys}"
14
+ end
15
+
7
16
  @min_server_version = spec['minServerVersion']
8
17
  @max_server_version = spec['maxServerVersion']
9
- @topologies = if topologies = spec['topology']
18
+ # topologies is for unified test format.
19
+ # topology is for legacy tests.
20
+ @topologies = if topologies = spec['topology'] || spec['topologies']
10
21
  topologies.map do |topology|
11
- {'replicaset' => :replica_set, 'single' => :single, 'sharded' => :sharded}[topology]
22
+ {
23
+ 'replicaset' => :replica_set,
24
+ 'single' => :single,
25
+ 'sharded' => :sharded,
26
+ 'sharded-replicaset' => :sharded,
27
+ }[topology].tap do |v|
28
+ unless v
29
+ raise "Unknown topology #{topology}"
30
+ end
31
+ end
12
32
  end
13
33
  else
14
34
  nil
15
35
  end
36
+ @server_parameters = spec['serverParameters']
16
37
  end
17
38
 
18
39
  attr_reader :min_server_version
@@ -47,6 +68,22 @@ module Mongo
47
68
  if topologies
48
69
  ok &&= topologies.include?(cc.topology)
49
70
  end
71
+ if @server_parameters
72
+ @server_parameters.each do |k, required_v|
73
+ actual_v = cc.server_parameters[k]
74
+ if actual_v.nil? && !required_v.nil?
75
+ ok = false
76
+ elsif actual_v != required_v
77
+ if Numeric === actual_v && Numeric === required_v
78
+ if actual_v.to_f != required_v.to_f
79
+ ok = false
80
+ end
81
+ else
82
+ ok = false
83
+ end
84
+ end
85
+ end
86
+ end
50
87
  ok
51
88
  end
52
89
 
@@ -193,6 +193,14 @@ EOT
193
193
  return
194
194
  end
195
195
 
196
+ if k == 'updateDescription'
197
+ # Change stream result - verify subset, not exact match
198
+ expected.fetch(k).each do |sub_k, sub_v|
199
+ {sub_k => sub_v}.should == {sub_k => actual.fetch(k).fetch(sub_k)}
200
+ end
201
+ return
202
+ end
203
+
196
204
  if expected[k].is_a?(Time)
197
205
  expect(k => actual[k].utc.to_s).to eq(k => expected[k].utc.to_s)
198
206
  else
@@ -191,8 +191,19 @@ module Mongo
191
191
 
192
192
  def assert_event_count(client, context)
193
193
  events = _select_events(context)
194
- unless events.length == arguments['count']
195
- raise "Exppected #{arguments['count']} #{arguments['event']} events, but have #{events.length}"
194
+ if %w(ServerMarkedUnknownEvent PoolClearedEvent).include?(arguments['event'])
195
+ # We publish SDAM events from both regular and push monitors.
196
+ # This means sometimes there are two ServerMarkedUnknownEvent
197
+ # events published for the same server transition.
198
+ # Allow actual event count to be at least the expected event count
199
+ # in case there are multiple transitions in a single test.
200
+ unless events.length >= arguments['count']
201
+ raise "Expected #{arguments['count']} #{arguments['event']} events, but have #{events.length}"
202
+ end
203
+ else
204
+ unless events.length == arguments['count']
205
+ raise "Expected #{arguments['count']} #{arguments['event']} events, but have #{events.length}"
206
+ end
196
207
  end
197
208
  end
198
209
 
@@ -289,6 +289,7 @@ module Mongo
289
289
  client.command(configureFailPoint: fail_point_command['configureFailPoint'],
290
290
  mode: 'off')
291
291
  end
292
+ $disable_fail_points = nil
292
293
  end
293
294
 
294
295
  if @test_client
@@ -0,0 +1,96 @@
1
+ require 'runners/unified/error'
2
+ require 'runners/unified/entity_map'
3
+ require 'runners/unified/event_subscriber'
4
+ require 'runners/unified/test'
5
+ require 'runners/unified/test_group'
6
+ require 'runners/unified/using_hash'
7
+
8
+ def define_unified_spec_tests(base_path, paths, expect_failure: false)
9
+ paths.each do |path|
10
+ basename = path[base_path.length+1...path.length]
11
+ context basename do
12
+ group = Unified::TestGroup.new(path)
13
+
14
+ if basename =~ /retryable|transaction/
15
+ require_wired_tiger
16
+ end
17
+
18
+ group.tests.each do |test|
19
+ context test.description do
20
+
21
+ if test.skip?
22
+ before do
23
+ skip test.skip_reason
24
+ end
25
+ end
26
+
27
+ before(:all) do
28
+ if SpecConfig.instance.retry_reads == false
29
+ skip "Tests are not applicable when legacy read retries are used"
30
+ end
31
+ if SpecConfig.instance.retry_writes == false
32
+ skip "Tests are not applicable when legacy write retries are used"
33
+ end
34
+
35
+ if ClusterConfig.instance.topology == :sharded
36
+ if test.require_multiple_mongoses? && SpecConfig.instance.addresses.length == 1
37
+ skip "Test requires multiple mongoses"
38
+ elsif test.require_single_mongos? && SpecConfig.instance.addresses.length > 1
39
+ # Many transaction spec tests that do not specifically deal with
40
+ # sharded transactions fail when run against a multi-mongos cluster
41
+ skip "Test requires single mongos"
42
+ end
43
+ end
44
+ end
45
+
46
+ if expect_failure
47
+ it 'fails as expected' do
48
+ if test.group_reqs
49
+ unless test.group_reqs.any? { |r| r.satisfied? }
50
+ skip "Group requirements not satisfied"
51
+ end
52
+ end
53
+ if test.reqs
54
+ unless test.reqs.any? { |r| r.satisfied? }
55
+ skip "Requirements not satisfied"
56
+ end
57
+ end
58
+ begin
59
+ test.create_entities
60
+ test.set_initial_data
61
+ lambda do
62
+ test.run
63
+ test.assert_outcome
64
+ test.assert_events
65
+ # HACK: other errors are possible and likely will need to
66
+ # be added here later as the tests evolve.
67
+ end.should raise_error(Mongo::Error::OperationFailure)
68
+ ensure
69
+ test.cleanup
70
+ end
71
+ end
72
+ else
73
+ it 'passes' do
74
+ if test.group_reqs
75
+ unless test.group_reqs.any? { |r| r.satisfied? }
76
+ skip "Group requirements not satisfied"
77
+ end
78
+ end
79
+ if test.reqs
80
+ unless test.reqs.any? { |r| r.satisfied? }
81
+ skip "Requirements not satisfied"
82
+ end
83
+ end
84
+ test.create_entities
85
+ test.set_initial_data
86
+ test.run
87
+ test.assert_outcome
88
+ test.assert_events
89
+ test.cleanup
90
+ end
91
+ end
92
+ end
93
+ end
94
+ end
95
+ end
96
+ end
@@ -0,0 +1,249 @@
1
+ module Unified
2
+
3
+ module Assertions
4
+
5
+ def assert_result_matches(actual, expected)
6
+ if Hash === expected
7
+ use_all(expected, 'expected result', expected) do |expected|
8
+ %w(deleted inserted matched modified upserted).each do |k|
9
+ if count = expected.use("#{k}Count")
10
+ if Hash === count || count > 0
11
+ actual_count = case actual
12
+ when Mongo::BulkWrite::Result, Mongo::Operation::Delete::Result
13
+ actual.send("#{k}_count")
14
+ else
15
+ actual["n_#{k}"]
16
+ end
17
+ assert_value_matches(actual_count, count, "#{k} count")
18
+ end
19
+ end
20
+ end
21
+ %w(inserted upserted).each do |k|
22
+ expected_v = expected.use("#{k}Ids")
23
+ next unless expected_v
24
+ actual_v = case actual
25
+ when Mongo::BulkWrite::Result, Mongo::Operation::Update::Result
26
+ # Ruby driver returns inserted ids as an array of ids.
27
+ # The yaml file specifies them as a map from operation.
28
+ if Hash === expected_v && expected_v.keys == %w($$unsetOrMatches)
29
+ expected_v = expected_v.values.first.values
30
+ elsif Hash === expected_v
31
+ expected_v = expected_v.values
32
+ end
33
+ actual.send("#{k}_ids")
34
+ else
35
+ actual["#{k}_ids"]
36
+ end
37
+ if expected_v
38
+ if expected_v.empty?
39
+ if actual_v && !actual_v.empty?
40
+ raise Error::ResultMismatch, "Actual not empty"
41
+ end
42
+ else
43
+ if actual_v != expected_v
44
+ raise Error::ResultMismatch, "Mismatch: actual #{actual_v}, expected #{expected_v}"
45
+ end
46
+ end
47
+ end
48
+ end
49
+
50
+ assert_matches(actual, expected, 'result')
51
+ expected.clear
52
+ end
53
+ else
54
+ assert_matches(actual, expected, 'result')
55
+ end
56
+ end
57
+
58
+ def assert_outcome
59
+ return unless outcome
60
+
61
+ client = ClientRegistry.instance.global_client('authorized')
62
+ outcome.each do |spec|
63
+ spec = UsingHash[spec]
64
+ collection = client.use(spec.use!('databaseName'))[spec.use!('collectionName')]
65
+ expected_docs = spec.use!('documents')
66
+ actual_docs = collection.find({}, order: :_id).to_a
67
+ assert_documents_match(actual_docs, expected_docs)
68
+ unless spec.empty?
69
+ raise NotImplementedError, "Unhandled keys: #{spec}"
70
+ end
71
+ end
72
+ end
73
+
74
+ def assert_documents_match(actual, expected)
75
+ unless actual.length == expected.length
76
+ raise Error::ResultMismatch, "Unexpected number of documents: expected #{expected.length}, actual #{actual.length}"
77
+ end
78
+
79
+ actual.each_with_index do |document, index|
80
+ assert_matches(document, expected[index], "document ##{index}")
81
+ end
82
+ end
83
+
84
+ def assert_document_matches(actual, expected, msg)
85
+ unless actual == expected
86
+ p actual
87
+ p expected
88
+ raise Error::ResultMismatch, "#{msg} does not match"
89
+ end
90
+ end
91
+
92
+ def assert_events
93
+ return unless @expected_events
94
+ @expected_events.each do |spec|
95
+ spec = UsingHash[spec]
96
+ client_id = spec.use!('client')
97
+ client = entities.get(:client, client_id)
98
+ subscriber = @subscribers.fetch(client)
99
+ expected_events = spec.use!('events')
100
+ actual_events = subscriber.wanted_events
101
+ unless actual_events.length == expected_events.length
102
+ raise Error::ResultMismatch, "Event count mismatch: expected #{expected_events.length}, actual #{actual_events.length}\nExpected: #{expected_events}\nActual: #{actual_events}"
103
+ end
104
+ expected_events.each_with_index do |event, i|
105
+ assert_event_matches(actual_events[i], event)
106
+ end
107
+ end
108
+ end
109
+
110
+ def assert_event_matches(actual, expected)
111
+ assert_eq(expected.keys.length, 1, "Expected event must have one key: #{expected}")
112
+ expected_name, spec = expected.first
113
+ spec = UsingHash[spec]
114
+ expected_name = expected_name.sub(/Event$/, '').sub(/^(.)/) { $1.upcase }
115
+ assert_eq(actual.class.name.sub(/.*::/, ''), expected_name, 'Event name does not match')
116
+ if db_name = spec.use('databaseName')
117
+ assert_eq(actual.database_name, db_name, 'Database names differ')
118
+ end
119
+ if command_name = spec.use('commandName')
120
+ assert_eq(actual.command_name, command_name, 'Command names differ')
121
+ end
122
+ if command = spec.use('command')
123
+ assert_matches(actual.command, command, 'Commands differ')
124
+ end
125
+ end
126
+
127
+ def assert_eq(actual, expected, msg)
128
+ unless expected == actual
129
+ raise Error::ResultMismatch, "#{msg}: expected #{expected}, actual #{actual}"
130
+ end
131
+ end
132
+
133
+ def assert_matches(actual, expected, msg)
134
+ if actual.nil? && !expected.nil?
135
+ raise Error::ResultMismatch, "#{msg}: expected #{expected} but got nil"
136
+ end
137
+
138
+ case expected
139
+ when Array
140
+ unless Array === actual
141
+ raise Error::ResultMismatch, "Expected an array, found #{actual}"
142
+ end
143
+ unless actual.length == expected.length
144
+ raise Error::ResultMismatch, "Expected array of length #{expected.length}, found array of length #{actual.length}: #{actual}"
145
+ end
146
+ expected.each_with_index do |v, i|
147
+ assert_matches(actual[i], v, "#{msg}: index #{i}")
148
+ end
149
+ when Hash
150
+ if expected.keys == %w($$unsetOrMatches) && expected.values.first.keys == %w(insertedId)
151
+ actual_v = actual.inserted_id
152
+ expected_v = expected.values.first.values.first
153
+ assert_value_matches(actual_v, expected_v, 'inserted_id')
154
+ else
155
+ expected.each do |k, expected_v|
156
+ if k.start_with?('$$')
157
+ assert_value_matches(actual, expected, k)
158
+ else
159
+ actual_v = actual[k]
160
+ if Hash === expected_v && expected_v.length == 1 && expected_v.keys.first.start_with?('$$')
161
+ assert_value_matches(actual_v, expected_v, k)
162
+ else
163
+ assert_matches(actual_v, expected_v, "#{msg}: key #{k}")
164
+ end
165
+ end
166
+ end
167
+ end
168
+ else
169
+ if Integer === expected && BSON::Int64 === actual
170
+ actual = actual.value
171
+ end
172
+ unless actual == expected
173
+ raise Error::ResultMismatch, "#{msg}: expected #{expected}, actual #{actual}"
174
+ end
175
+ end
176
+ end
177
+
178
+ def assert_type(object, type)
179
+ ok = case type
180
+ when 'object'
181
+ Hash === object
182
+ when %w(int long)
183
+ Integer === object || BSON::Int32 === object || BSON::Int64 === object
184
+ when 'objectId'
185
+ BSON::ObjectId === object
186
+ when 'date'
187
+ Time === object
188
+ else
189
+ raise NotImplementedError, "Unhandled type #{type}"
190
+ end
191
+ unless ok
192
+ raise Error::ResultMismatch, "Object #{object} is not of type #{type}"
193
+ end
194
+ end
195
+
196
+ def assert_value_matches(actual, expected, msg)
197
+ if Hash === expected && expected.keys.length == 1 &&
198
+ (operator = expected.keys.first).start_with?('$$')
199
+ then
200
+ expected_v = expected.values.first
201
+ case operator
202
+ when '$$unsetOrMatches'
203
+ if actual
204
+ unless actual == expected_v
205
+ raise Error::ResultMismatch, "Mismatch for #{msg}: expected #{expected}, have #{actual}"
206
+ end
207
+ end
208
+ when '$$matchesHexBytes'
209
+ expected_data = decode_hex_bytes(expected_v)
210
+ unless actual == expected_data
211
+ raise Error::ResultMismatch, "Hex bytes do not match"
212
+ end
213
+ when '$$exists'
214
+ case expected_v
215
+ when true
216
+ if actual.nil?
217
+ raise Error::ResultMismatch, "#{msg}: wanted value to exist, but it did not"
218
+ end
219
+ when false
220
+ if actual
221
+ raise Error::ResultMismatch, "#{msg}: wanted value to not exist, but it did"
222
+ end
223
+ else
224
+ raise NotImplementedError, "Bogus value #{expected_v}"
225
+ end
226
+ when '$$sessionLsid'
227
+ expected_session = entities.get(:session, expected_v)
228
+ # TODO - sessions do not expose server sessions after being ended
229
+ #unless actual_v == {'id' => expected_session.server_session.session_id.to_bson}
230
+ # raise Error::ResultMismatch, "Session does not match: wanted #{expected_session}, have #{actual_v}"
231
+ #end
232
+ when '$$type'
233
+ assert_type(actual, expected_v)
234
+ when '$$matchesEntity'
235
+ result = entities.get(:result, expected_v)
236
+ unless actual == result
237
+ raise Error::ResultMismatch, "Actual value #{actual} does not match entity #{expected_v} with value #{result}"
238
+ end
239
+ else
240
+ raise NotImplementedError, "Unknown operator #{operator}"
241
+ end
242
+ else
243
+ if actual != expected
244
+ raise Error::ResultMismatch, "Mismatch for #{msg}: expected #{expected}, have #{actual}"
245
+ end
246
+ end
247
+ end
248
+ end
249
+ end