mongo 2.9.2 → 2.10.0.rc0

Sign up to get free protection for your applications and to get access to all the features.
Files changed (227) hide show
  1. checksums.yaml +4 -4
  2. checksums.yaml.gz.sig +0 -0
  3. data.tar.gz.sig +0 -0
  4. data/lib/mongo.rb +1 -0
  5. data/lib/mongo/auth/user/view.rb +4 -4
  6. data/lib/mongo/bulk_write.rb +14 -8
  7. data/lib/mongo/bulk_write/result.rb +1 -1
  8. data/lib/mongo/bulk_write/result_combiner.rb +2 -2
  9. data/lib/mongo/bulk_write/transformable.rb +17 -9
  10. data/lib/mongo/client.rb +107 -16
  11. data/lib/mongo/cluster.rb +47 -25
  12. data/lib/mongo/cluster/topology/replica_set_no_primary.rb +1 -1
  13. data/lib/mongo/cluster_time.rb +139 -0
  14. data/lib/mongo/collection.rb +84 -25
  15. data/lib/mongo/collection/view.rb +7 -3
  16. data/lib/mongo/collection/view/aggregation.rb +4 -4
  17. data/lib/mongo/collection/view/builder/aggregation.rb +31 -6
  18. data/lib/mongo/collection/view/builder/find_command.rb +4 -1
  19. data/lib/mongo/collection/view/builder/map_reduce.rb +4 -1
  20. data/lib/mongo/collection/view/change_stream.rb +54 -66
  21. data/lib/mongo/collection/view/iterable.rb +2 -2
  22. data/lib/mongo/collection/view/map_reduce.rb +6 -4
  23. data/lib/mongo/collection/view/readable.rb +36 -16
  24. data/lib/mongo/collection/view/writable.rb +68 -22
  25. data/lib/mongo/cursor.rb +87 -20
  26. data/lib/mongo/database.rb +47 -43
  27. data/lib/mongo/database/view.rb +54 -11
  28. data/lib/mongo/error.rb +13 -4
  29. data/lib/mongo/error/invalid_write_concern.rb +2 -2
  30. data/lib/mongo/error/operation_failure.rb +65 -11
  31. data/lib/mongo/error/parser.rb +41 -8
  32. data/lib/mongo/grid/fs_bucket.rb +26 -6
  33. data/lib/mongo/grid/stream/read.rb +9 -2
  34. data/lib/mongo/grid/stream/write.rb +21 -5
  35. data/lib/mongo/index/view.rb +3 -3
  36. data/lib/mongo/lint.rb +10 -3
  37. data/lib/mongo/operation.rb +2 -0
  38. data/lib/mongo/operation/aggregate/result.rb +19 -6
  39. data/lib/mongo/operation/collections_info.rb +1 -1
  40. data/lib/mongo/operation/get_more/result.rb +9 -0
  41. data/lib/mongo/operation/list_collections/command.rb +1 -3
  42. data/lib/mongo/operation/list_collections/op_msg.rb +1 -2
  43. data/lib/mongo/operation/parallel_scan/command.rb +4 -1
  44. data/lib/mongo/operation/parallel_scan/op_msg.rb +4 -1
  45. data/lib/mongo/operation/result.rb +27 -4
  46. data/lib/mongo/operation/shared/executable.rb +19 -5
  47. data/lib/mongo/operation/shared/executable_no_validate.rb +1 -2
  48. data/lib/mongo/operation/shared/executable_transaction_label.rb +0 -9
  49. data/lib/mongo/operation/shared/polymorphic_result.rb +9 -1
  50. data/lib/mongo/operation/shared/result/aggregatable.rb +2 -2
  51. data/lib/mongo/operation/shared/sessions_supported.rb +42 -32
  52. data/lib/mongo/operation/shared/specifiable.rb +40 -0
  53. data/lib/mongo/operation/shared/unpinnable.rb +39 -0
  54. data/lib/mongo/operation/shared/write.rb +1 -1
  55. data/lib/mongo/protocol/update.rb +6 -2
  56. data/lib/mongo/retryable.rb +79 -39
  57. data/lib/mongo/server/connection.rb +10 -3
  58. data/lib/mongo/server/description.rb +25 -1
  59. data/lib/mongo/server/monitor/connection.rb +1 -1
  60. data/lib/mongo/server_selector.rb +10 -0
  61. data/lib/mongo/server_selector/selectable.rb +172 -32
  62. data/lib/mongo/session.rb +654 -581
  63. data/lib/mongo/session/session_pool.rb +1 -1
  64. data/lib/mongo/socket.rb +7 -28
  65. data/lib/mongo/socket/ssl.rb +26 -1
  66. data/lib/mongo/socket/tcp.rb +3 -0
  67. data/lib/mongo/socket/unix.rb +3 -0
  68. data/lib/mongo/uri.rb +112 -265
  69. data/lib/mongo/uri/srv_protocol.rb +4 -1
  70. data/lib/mongo/version.rb +1 -1
  71. data/lib/mongo/write_concern.rb +10 -29
  72. data/lib/mongo/write_concern/acknowledged.rb +12 -0
  73. data/lib/mongo/write_concern/base.rb +17 -13
  74. data/lib/mongo/write_concern/unacknowledged.rb +12 -0
  75. data/spec/atlas/atlas_connectivity_spec.rb +7 -37
  76. data/spec/atlas/operations_spec.rb +25 -0
  77. data/spec/integration/change_stream_examples_spec.rb +45 -31
  78. data/spec/integration/change_stream_spec.rb +305 -5
  79. data/spec/integration/client_spec.rb +44 -0
  80. data/spec/integration/command_monitoring_spec.rb +1 -0
  81. data/spec/integration/command_spec.rb +7 -1
  82. data/spec/integration/mmapv1_spec.rb +28 -0
  83. data/spec/integration/mongos_pinning_spec.rb +34 -0
  84. data/spec/integration/operation_failure_code_spec.rb +2 -2
  85. data/spec/integration/{read_concern.rb → read_concern_spec.rb} +7 -1
  86. data/spec/integration/read_preference_spec.rb +485 -0
  87. data/spec/integration/retryable_writes_spec.rb +8 -19
  88. data/spec/integration/sdam_error_handling_spec.rb +1 -1
  89. data/spec/integration/sdam_events_spec.rb +2 -2
  90. data/spec/integration/server_description_spec.rb +14 -17
  91. data/spec/integration/server_selector_spec.rb +7 -3
  92. data/spec/integration/server_spec.rb +48 -0
  93. data/spec/integration/ssl_uri_options_spec.rb +1 -1
  94. data/spec/integration/step_down_spec.rb +10 -4
  95. data/spec/integration/transactions_examples_spec.rb +11 -10
  96. data/spec/lite_spec_helper.rb +19 -16
  97. data/spec/mongo/auth/scram/negotiation_spec.rb +11 -8
  98. data/spec/mongo/bulk_write/ordered_combiner_spec.rb +6 -6
  99. data/spec/mongo/bulk_write/unordered_combiner_spec.rb +4 -4
  100. data/spec/mongo/bulk_write_spec.rb +12 -2
  101. data/spec/mongo/client_construction_spec.rb +160 -8
  102. data/spec/mongo/client_spec.rb +5 -4
  103. data/spec/mongo/cluster_spec.rb +6 -6
  104. data/spec/mongo/cluster_time_spec.rb +148 -0
  105. data/spec/mongo/collection/view/aggregation_spec.rb +34 -15
  106. data/spec/mongo/collection/view/change_stream_spec.rb +62 -3
  107. data/spec/mongo/collection/view/map_reduce_spec.rb +7 -5
  108. data/spec/mongo/collection/view/readable_spec.rb +4 -4
  109. data/spec/mongo/collection_spec.rb +331 -14
  110. data/spec/mongo/cursor_spec.rb +117 -5
  111. data/spec/mongo/database_spec.rb +240 -8
  112. data/spec/mongo/error/operation_failure_spec.rb +47 -1
  113. data/spec/mongo/error/parser_spec.rb +160 -23
  114. data/spec/mongo/operation/insert/bulk_spec.rb +2 -1
  115. data/spec/mongo/operation/result_spec.rb +27 -0
  116. data/spec/mongo/operation/update/bulk_spec.rb +1 -0
  117. data/spec/mongo/retryable_spec.rb +2 -0
  118. data/spec/mongo/server/app_metadata_spec.rb +2 -2
  119. data/spec/mongo/server/connection_spec.rb +13 -17
  120. data/spec/mongo/server/monitor/connection_spec.rb +13 -10
  121. data/spec/mongo/server_selector_spec.rb +34 -2
  122. data/spec/mongo/session/session_pool_spec.rb +14 -3
  123. data/spec/mongo/session_spec.rb +3 -3
  124. data/spec/mongo/session_transaction_spec.rb +4 -3
  125. data/spec/mongo/socket/ssl_spec.rb +19 -5
  126. data/spec/mongo/socket_spec.rb +1 -62
  127. data/spec/mongo/uri/srv_protocol_spec.rb +14 -20
  128. data/spec/mongo/uri_option_parsing_spec.rb +94 -8
  129. data/spec/mongo/uri_spec.rb +23 -10
  130. data/spec/mongo/write_concern_spec.rb +56 -3
  131. data/spec/spec_tests/change_streams_spec.rb +2 -1
  132. data/spec/spec_tests/cmap_spec.rb +1 -1
  133. data/spec/spec_tests/crud_spec.rb +12 -2
  134. data/spec/spec_tests/data/change_streams/change-streams-errors.yml +24 -1
  135. data/spec/spec_tests/data/change_streams/change-streams.yml +172 -3
  136. data/spec/spec_tests/data/command_monitoring/bulkWrite.yml +1 -1
  137. data/spec/spec_tests/data/command_monitoring/updateMany.yml +0 -2
  138. data/spec/spec_tests/data/command_monitoring/updateOne.yml +0 -5
  139. data/spec/spec_tests/data/crud/read/aggregate-out.yml +0 -6
  140. data/spec/spec_tests/data/crud/read/count-empty.yml +29 -0
  141. data/spec/spec_tests/data/crud/write/bulkWrite-arrayFilters.yml +1 -0
  142. data/spec/spec_tests/data/crud/write/bulkWrite-collation.yml +101 -0
  143. data/spec/spec_tests/data/crud/write/bulkWrite.yml +401 -0
  144. data/spec/spec_tests/data/crud/write/insertMany.yml +58 -2
  145. data/spec/spec_tests/data/crud/write/updateMany-arrayFilters.yml +3 -0
  146. data/spec/spec_tests/data/crud/write/updateOne-arrayFilters.yml +6 -1
  147. data/spec/spec_tests/data/crud_v2/aggregate-merge.yml +103 -0
  148. data/spec/spec_tests/data/crud_v2/aggregate-out-readConcern.yml +110 -0
  149. data/spec/spec_tests/data/crud_v2/bulkWrite-arrayFilters.yml +81 -0
  150. data/spec/spec_tests/data/crud_v2/db-aggregate.yml +38 -0
  151. data/spec/spec_tests/data/crud_v2/updateWithPipelines.yml +92 -0
  152. data/spec/spec_tests/data/retryable_writes/insertOne-serverErrors.yml +2 -2
  153. data/spec/spec_tests/data/transactions/abort.yml +3 -0
  154. data/spec/spec_tests/data/transactions/bulk.yml +3 -8
  155. data/spec/spec_tests/data/transactions/causal-consistency.yml +3 -8
  156. data/spec/spec_tests/data/transactions/commit.yml +3 -1
  157. data/spec/spec_tests/data/transactions/count.yml +3 -0
  158. data/spec/spec_tests/data/transactions/delete.yml +3 -0
  159. data/spec/spec_tests/data/transactions/error-labels.yml +4 -1
  160. data/spec/spec_tests/data/transactions/errors-client.yml +56 -0
  161. data/spec/spec_tests/data/transactions/errors.yml +3 -0
  162. data/spec/spec_tests/data/transactions/findOneAndDelete.yml +3 -0
  163. data/spec/spec_tests/data/transactions/findOneAndReplace.yml +3 -0
  164. data/spec/spec_tests/data/transactions/findOneAndUpdate.yml +3 -0
  165. data/spec/spec_tests/data/transactions/insert.yml +3 -0
  166. data/spec/spec_tests/data/transactions/isolation.yml +3 -0
  167. data/spec/spec_tests/data/transactions/mongos-pin-auto.yml +1671 -0
  168. data/spec/spec_tests/data/transactions/mongos-recovery-token.yml +347 -0
  169. data/spec/spec_tests/data/transactions/pin-mongos.yml +557 -0
  170. data/spec/spec_tests/data/transactions/read-concern.yml +3 -0
  171. data/spec/spec_tests/data/transactions/read-pref.yml +3 -0
  172. data/spec/spec_tests/data/transactions/reads.yml +3 -0
  173. data/spec/spec_tests/data/transactions/retryable-abort.yml +5 -2
  174. data/spec/spec_tests/data/transactions/retryable-commit.yml +4 -1
  175. data/spec/spec_tests/data/transactions/retryable-writes.yml +3 -0
  176. data/spec/spec_tests/data/transactions/run-command.yml +3 -0
  177. data/spec/spec_tests/data/transactions/transaction-options.yml +6 -0
  178. data/spec/spec_tests/data/transactions/update.yml +3 -8
  179. data/spec/spec_tests/data/transactions/write-concern.yml +348 -38
  180. data/spec/spec_tests/data/transactions_api/callback-aborts.yml +6 -0
  181. data/spec/spec_tests/data/transactions_api/callback-commits.yml +5 -0
  182. data/spec/spec_tests/data/transactions_api/callback-retry.yml +7 -2
  183. data/spec/spec_tests/data/transactions_api/commit-retry.yml +70 -15
  184. data/spec/spec_tests/data/transactions_api/commit-transienttransactionerror-4.2.yml +3 -0
  185. data/spec/spec_tests/data/transactions_api/commit-transienttransactionerror.yml +3 -0
  186. data/spec/spec_tests/data/transactions_api/commit-writeconcernerror.yml +59 -109
  187. data/spec/spec_tests/data/transactions_api/commit.yml +5 -0
  188. data/spec/spec_tests/data/transactions_api/transaction-options.yml +10 -0
  189. data/spec/spec_tests/retryable_reads_spec.rb +5 -2
  190. data/spec/spec_tests/retryable_writes_spec.rb +5 -2
  191. data/spec/spec_tests/sdam_monitoring_spec.rb +3 -3
  192. data/spec/spec_tests/sdam_spec.rb +2 -2
  193. data/spec/spec_tests/transactions_api_spec.rb +1 -67
  194. data/spec/spec_tests/transactions_spec.rb +2 -66
  195. data/spec/support/authorization.rb +4 -0
  196. data/spec/support/change_streams.rb +30 -10
  197. data/spec/support/change_streams/operation.rb +27 -0
  198. data/spec/support/client_registry.rb +44 -25
  199. data/spec/support/cluster_config.rb +25 -14
  200. data/spec/support/cluster_tools.rb +32 -10
  201. data/spec/support/command_monitoring.rb +1 -1
  202. data/spec/support/common_shortcuts.rb +30 -0
  203. data/spec/support/connection_string.rb +8 -3
  204. data/spec/support/constraints.rb +34 -0
  205. data/spec/support/crud.rb +31 -16
  206. data/spec/support/crud/context.rb +23 -0
  207. data/spec/support/crud/operation.rb +311 -14
  208. data/spec/support/crud/spec.rb +2 -1
  209. data/spec/support/crud/test.rb +24 -27
  210. data/spec/support/crud/test_base.rb +22 -0
  211. data/spec/support/crud/verifier.rb +15 -1
  212. data/spec/support/event_subscriber.rb +12 -0
  213. data/spec/support/sdam_formatter_integration.rb +12 -6
  214. data/spec/support/shared/server_selector.rb +10 -0
  215. data/spec/support/shared/session.rb +13 -12
  216. data/spec/support/spec_config.rb +32 -22
  217. data/spec/support/spec_setup.rb +2 -2
  218. data/spec/support/transactions.rb +87 -0
  219. data/spec/support/transactions/context.rb +33 -0
  220. data/spec/support/transactions/operation.rb +99 -349
  221. data/spec/support/transactions/spec.rb +1 -3
  222. data/spec/support/transactions/test.rb +110 -49
  223. data/spec/support/utils.rb +74 -1
  224. metadata +52 -10
  225. metadata.gz.sig +0 -0
  226. data/spec/support/crud/read.rb +0 -265
  227. data/spec/support/crud/write.rb +0 -284
@@ -74,20 +74,6 @@ class ClusterConfig
74
74
  end
75
75
  end
76
76
 
77
- def primary_address_str
78
- primary_address
79
- end
80
-
81
- def primary_address_host
82
- both = primary_address_str
83
- both.split(':').first
84
- end
85
-
86
- def primary_address_port
87
- both = primary_address_str
88
- both.split(':')[1] || 27017
89
- end
90
-
91
77
  # Try running a command on the admin database to see if the mongod was
92
78
  # started with auth.
93
79
  def auth_enabled?
@@ -111,4 +97,29 @@ class ClusterConfig
111
97
  topology.to_sym
112
98
  end
113
99
  end
100
+
101
+ def storage_engine
102
+ @storage_engine ||= begin
103
+ # 2.6 does not have wired tiger
104
+ if short_server_version == '2.6'
105
+ :mmapv1
106
+ else
107
+ client = ClientRegistry.instance.global_client('root_authorized')
108
+ if topology == :sharded
109
+ shards = client.use(:admin).command(listShards: 1).first
110
+ shard = shards['shards'].first
111
+ address_str = shard['host'].sub(/^.*\//, '').sub(/,.*/, '')
112
+ client = ClusterTools.instance.direct_client(address_str,
113
+ SpecConfig.instance.test_options.merge(SpecConfig.instance.auth_options).merge(connect: :direct))
114
+ end
115
+ rv = client.use(:admin).command(serverStatus: 1).first
116
+ rv = rv['storageEngine']['name']
117
+ rv_map = {
118
+ 'wiredTiger' => :wired_tiger,
119
+ 'mmapv1' => :mmapv1,
120
+ }
121
+ rv_map[rv] || rv
122
+ end
123
+ end
124
+ end
114
125
  end
@@ -309,25 +309,47 @@ class ClusterTools
309
309
  end
310
310
  end
311
311
 
312
- private
313
-
314
312
  def admin_client
315
313
  # Since we are triggering elections, we need to have a higher server
316
314
  # selection timeout applied. The default timeout for tests assumes a
317
315
  # stable deployment.
318
- @admin_client ||= ClientRegistry.instance.global_client('root_authorized_admin').
319
- with(server_selection_timeout: 15)
316
+ (
317
+ @admin_client ||= ClientRegistry.instance.global_client('root_authorized').
318
+ with(server_selection_timeout: 15).use(:admin)
319
+ ).tap do |client|
320
+ ClientRegistry.reconnect_client_if_perished(client)
321
+ end
320
322
  end
321
323
 
322
- def direct_client(address)
324
+ def direct_client(address, options = {})
323
325
  @direct_clients ||= {}
324
- @direct_clients[address] ||= ClientRegistry.instance.new_local_client(
325
- [address.to_s],
326
- SpecConfig.instance.test_options.merge(
327
- SpecConfig.instance.auth_options).merge(
328
- connect: :direct, server_selection_timeout: 10))
326
+ cache_key = {address: address}.update(options)
327
+ (
328
+ @direct_clients[cache_key] ||= ClientRegistry.instance.new_local_client(
329
+ [address.to_s],
330
+ SpecConfig.instance.test_options.merge(
331
+ SpecConfig.instance.auth_options).merge(
332
+ connect: :direct, server_selection_timeout: 10).merge(options))
333
+ ).tap do |client|
334
+ ClientRegistry.reconnect_client_if_perished(client)
335
+ end
329
336
  end
330
337
 
338
+ def close_clients
339
+ if @admin_client
340
+ @admin_client.close
341
+ @admin_client = nil
342
+ end
343
+ if @direct_clients
344
+ @direct_clients.each do |cache_key, client|
345
+ client.close
346
+ end
347
+ @direct_clients = nil
348
+ end
349
+ end
350
+
351
+ private
352
+
331
353
  def each_server(&block)
332
354
  admin_client.cluster.servers_list.each(&block)
333
355
  end
@@ -248,7 +248,7 @@ module Mongo
248
248
  @description = test['description']
249
249
  @max_server_version = test['ignore_if_server_version_greater_than']
250
250
  @min_server_fcv = test['ignore_if_server_version_less_than']
251
- @operation = Mongo::CRUD::Operation.get(test['operation'])
251
+ @operation = Mongo::CRUD::Operation.new(self, test['operation'])
252
252
  @expectations = test['expectations'].map{ |e| Expectation.new(e) }
253
253
  end
254
254
 
@@ -9,6 +9,36 @@ module CommonShortcuts
9
9
  end
10
10
  end
11
11
  end
12
+
13
+ # For tests which require clients to connect, clean slate asks all
14
+ # existing clients to be closed prior to the test execution.
15
+ # Note that clean_slate closes all clients for each test in the scope.
16
+ def clean_slate
17
+ before do
18
+ ClientRegistry.instance.close_all_clients
19
+ end
20
+ end
21
+
22
+ # Similar to clean slate but closes clients once before all tests in
23
+ # the scope. Use when the tests do not create new clients but do not
24
+ # want any background output from previously existing clients.
25
+ def clean_slate_for_all
26
+ before(:all) do
27
+ ClientRegistry.instance.close_all_clients
28
+ end
29
+ end
30
+
31
+ # For some reason, there are tests which fail on evergreen either
32
+ # intermittently or reliably that always succeed locally.
33
+ # Debugging of tests in evergreen is difficult/impossible,
34
+ # thus this workaround.
35
+ def clean_slate_on_evergreen
36
+ before(:all) do
37
+ if SpecConfig.instance.ci?
38
+ ClientRegistry.instance.close_all_clients
39
+ end
40
+ end
41
+ end
12
42
  end
13
43
 
14
44
  module InstanceMethods
@@ -123,6 +123,7 @@ module Mongo
123
123
  end
124
124
 
125
125
  class Test
126
+ include RSpec::Core::Pending
126
127
 
127
128
  attr_reader :description
128
129
  attr_reader :uri_string
@@ -153,6 +154,10 @@ module Mongo
153
154
 
154
155
  def client
155
156
  @client ||= ClientRegistry.instance.new_local_client(@spec['uri'], monitoring_io: false)
157
+ rescue Mongo::Error::LintError => e
158
+ if e.message =~ /arbitraryButStillValid/
159
+ skip 'Test uses a read concern that fails linter'
160
+ end
156
161
  end
157
162
 
158
163
  def uri
@@ -223,9 +228,9 @@ module Mongo
223
228
  'maxidletimems' => :max_idle_time,
224
229
 
225
230
  # Write Options
226
- 'journal' => [:write, 'j'],
227
- 'w' => [:write, 'w'],
228
- 'wtimeoutms' => [:write, 'wtimeout'],
231
+ 'journal' => [:write_concern, 'j'],
232
+ 'w' => [:write_concern, 'w'],
233
+ 'wtimeoutms' => [:write_concern, 'wtimeout'],
229
234
 
230
235
  # Read Options
231
236
  'readpreference' => ['read', 'mode'],
@@ -157,4 +157,38 @@ module Constraints
157
157
  end
158
158
  end
159
159
  end
160
+
161
+ def require_no_multi_shard
162
+ before do
163
+ if ClusterConfig.instance.topology == :sharded && SpecConfig.instance.addresses.length > 1
164
+ skip 'Test requires a single shard if run in sharded topology'
165
+ end
166
+ end
167
+ end
168
+
169
+ def require_wired_tiger
170
+ before(:all) do
171
+ if ClusterConfig.instance.storage_engine != :wired_tiger
172
+ skip 'Test requires WiredTiger storage engine'
173
+ end
174
+ end
175
+ end
176
+
177
+ def require_wired_tiger_on_36
178
+ before(:all) do
179
+ if ClusterConfig.instance.short_server_version >= '3.6'
180
+ if ClusterConfig.instance.storage_engine != :wired_tiger
181
+ skip 'Test requires WiredTiger storage engine on 3.6+ servers'
182
+ end
183
+ end
184
+ end
185
+ end
186
+
187
+ def require_mmapv1
188
+ before do
189
+ if ClusterConfig.instance.storage_engine != :mmapv1
190
+ skip 'Test requires MMAPv1 storage engine'
191
+ end
192
+ end
193
+ end
160
194
  end
@@ -15,13 +15,17 @@
15
15
  require 'support/gridfs'
16
16
  require 'support/crud/requirement'
17
17
  require 'support/crud/spec'
18
+ require 'support/crud/test_base'
18
19
  require 'support/crud/test'
19
20
  require 'support/crud/outcome'
21
+ require 'support/crud/context'
20
22
  require 'support/crud/operation'
21
- require 'support/crud/read'
22
- require 'support/crud/write'
23
23
  require 'support/crud/verifier'
24
24
 
25
+ def collection_data(collection)
26
+ collection.find.to_a
27
+ end
28
+
25
29
  def crud_execute_operations(spec, test, num_ops, event_subscriber, expect_error,
26
30
  client
27
31
  )
@@ -42,7 +46,7 @@ def crud_execute_operations(spec, test, num_ops, event_subscriber, expect_error,
42
46
  result = if expect_error.nil?
43
47
  res = nil
44
48
  begin
45
- res = test.run(spec, client, num_ops)
49
+ res = test.run(client, num_ops)
46
50
  rescue => e
47
51
  res = e
48
52
  end
@@ -50,13 +54,13 @@ def crud_execute_operations(spec, test, num_ops, event_subscriber, expect_error,
50
54
  elsif expect_error
51
55
  error = nil
52
56
  begin
53
- test.run(spec, client, num_ops)
57
+ test.run(client, num_ops)
54
58
  rescue => e
55
59
  error = e
56
60
  end
57
61
  error
58
62
  else
59
- test.run(spec, client, num_ops)
63
+ test.run(client, num_ops)
60
64
  end
61
65
 
62
66
  $crud_event_cache ||= {}
@@ -68,7 +72,7 @@ def crud_execute_operations(spec, test, num_ops, event_subscriber, expect_error,
68
72
  if last_op.outcome && last_op.outcome.collection_data?
69
73
  verify_collection = client[last_op.verify_collection_name]
70
74
  $crud_collection_data_cache ||= {}
71
- $crud_collection_data_cache[cache_key] = verify_collection.find.to_a
75
+ $crud_collection_data_cache[cache_key] = collection_data(verify_collection)
72
76
  end
73
77
 
74
78
  result
@@ -136,7 +140,7 @@ def define_crud_spec_test_examples(spec, req = nil, &block)
136
140
  result
137
141
  verifier.verify_collection_data(
138
142
  operation.outcome.collection_data,
139
- verify_collection.find.to_a)
143
+ collection_data(verify_collection))
140
144
  end
141
145
  end
142
146
 
@@ -173,6 +177,20 @@ def define_crud_spec_test_examples(spec, req = nil, &block)
173
177
  end
174
178
  end
175
179
  end
180
+
181
+ if test.outcome && test.outcome.collection_data?
182
+ let(:result) do
183
+ crud_execute_operations(spec, test, test.operations.length,
184
+ event_subscriber, nil, client)
185
+ end
186
+
187
+ it 'has the correct data in the collection' do
188
+ result
189
+ verifier.verify_collection_data(
190
+ test.outcome.collection_data,
191
+ collection_data(client[test.outcome.collection_name || spec.collection_name]))
192
+ end
193
+ end
176
194
  end
177
195
  end
178
196
  end
@@ -203,17 +221,14 @@ def define_spec_tests_with_requirements(spec, &block)
203
221
  end
204
222
  end
205
223
 
206
- def define_crud_spec_tests(description, test_paths, &block)
207
- describe(description) do
208
-
209
- test_paths.each do |path|
224
+ def define_crud_spec_tests(test_paths, spec_cls = Mongo::CRUD::Spec, &block)
225
+ test_paths.each do |path|
210
226
 
211
- spec = Mongo::CRUD::Spec.new(path)
227
+ spec = spec_cls.new(path)
212
228
 
213
- context(spec.description) do
214
- define_spec_tests_with_requirements(spec) do |req|
215
- define_crud_spec_test_examples(spec, req, &block)
216
- end
229
+ context(spec.description) do
230
+ define_spec_tests_with_requirements(spec) do |req|
231
+ define_crud_spec_test_examples(spec, req, &block)
217
232
  end
218
233
  end
219
234
  end
@@ -0,0 +1,23 @@
1
+ # Copyright (C) 2019 MongoDB, Inc.
2
+ #
3
+ # Licensed under the Apache License, Version 2.0 (the "License");
4
+ # you may not use this file except in compliance with the License.
5
+ # You may obtain a copy of the License at
6
+ #
7
+ # http://www.apache.org/licenses/LICENSE-2.0
8
+ #
9
+ # Unless required by applicable law or agreed to in writing, software
10
+ # distributed under the License is distributed on an "AS IS" BASIS,
11
+ # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
12
+ # See the License for the specific language governing permissions and
13
+ # limitations under the License.
14
+
15
+ module Mongo
16
+ module CRUD
17
+ class Context
18
+ def transform_arguments(arguments)
19
+ arguments
20
+ end
21
+ end
22
+ end
23
+ end
@@ -15,29 +15,326 @@
15
15
  module Mongo
16
16
  module CRUD
17
17
 
18
- # Helper module for instantiating either a Read or Write test operation.
19
- #
20
- # @since 2.0.0
21
- module Operation
22
- extend self
18
+ class Operation
23
19
 
24
- # Get a new Operation.
25
- #
26
- # @example Get the operation.
27
- # Operation.get(spec)
20
+ # Instantiate the operation.
28
21
  #
29
22
  # @param [ Hash ] spec The operation specification.
23
+ # @param [ Hash ] outcome_spec The outcome specification.
24
+ # If not provided, outcome is taken out of operation specification.
25
+ #
26
+ # @since 2.0.0
27
+ def initialize(crud_test, spec, outcome_spec = nil)
28
+ @crud_test = crud_test
29
+ @spec = IceNine.deep_freeze(spec)
30
+ @name = spec['name']
31
+ @arguments = spec['arguments'] || {}
32
+ @outcome = Outcome.new(outcome_spec || spec)
33
+ end
34
+
35
+ # The operation name.
30
36
  #
31
- # @return [ Operation::Write, Operation::Read ] The Operation object.
37
+ # @return [ String ] name The operation name.
32
38
  #
33
39
  # @since 2.0.0
34
- def get(spec, outcome_spec = nil)
35
- if Write::OPERATIONS.keys.include?(spec['name'])
36
- Write.new(spec, outcome_spec)
40
+ attr_reader :name
41
+
42
+ attr_reader :arguments
43
+
44
+ attr_reader :outcome
45
+
46
+ def object
47
+ @spec['object'] || 'collection'
48
+ end
49
+
50
+ # Which collection to verify results from.
51
+ # Returns the collection name specified on the operation, or
52
+ # the collection name for the entire spec file.
53
+ def verify_collection_name
54
+ if outcome && outcome.collection_name
55
+ outcome.collection_name
37
56
  else
38
- Read.new(spec, outcome_spec)
57
+ @spec['collection_name'] || 'crud_spec_test'
58
+ end
59
+ end
60
+
61
+ # Whether the operation is expected to have results.
62
+ #
63
+ # @example Whether the operation is expected to have results.
64
+ # operation.has_results?
65
+ #
66
+ # @return [ true, false ] If the operation is expected to have results.
67
+ #
68
+ # @since 2.0.0
69
+ def has_results?
70
+ !(name == 'aggregate' &&
71
+ pipeline.find {|op| op.keys.include?('$out') })
72
+ end
73
+
74
+ # Execute the operation.
75
+ #
76
+ # @example Execute the operation.
77
+ # operation.execute
78
+ #
79
+ # @param [ Collection ] collection The collection to execute the operation on.
80
+ #
81
+ # @return [ Result, Array<Hash> ] The result of executing the operation.
82
+ #
83
+ # @since 2.0.0
84
+ def execute(target)
85
+ op_name = Utils.underscore(name)
86
+ if target.is_a?(Mongo::Database)
87
+ op_name = "db_#{op_name}"
88
+ elsif target.is_a?(Mongo::Client)
89
+ op_name= "client_#{op_name}"
90
+ end
91
+ send(op_name, target, Context.new)
92
+ end
93
+
94
+ def collection_options
95
+ Utils.convert_operation_options(@spec['collectionOptions'])
96
+ end
97
+
98
+ private
99
+
100
+ # read operations
101
+
102
+ def aggregate(collection, context)
103
+ collection.aggregate(arguments['pipeline'], context.transform_arguments(options)).to_a
104
+ end
105
+
106
+ def db_aggregate(database, context)
107
+ database.aggregate(arguments['pipeline'], context.transform_arguments(options)).to_a
108
+ end
109
+
110
+ def count(collection, context)
111
+ collection.count(arguments['filter'], context.transform_arguments(options))
112
+ end
113
+
114
+ def count_documents(collection, context)
115
+ collection.count_documents(arguments['filter'], context.transform_arguments(options))
116
+ end
117
+
118
+ def distinct(collection, context)
119
+ collection.distinct(arguments['fieldName'], arguments['filter'], context.transform_arguments(options))
120
+ end
121
+
122
+ def estimated_document_count(collection, context)
123
+ collection.estimated_document_count(context.transform_arguments(options))
124
+ end
125
+
126
+ def find(collection, context)
127
+ opts = context.transform_arguments(options)
128
+ if arguments['modifiers']
129
+ opts = opts.merge(modifiers: BSON::Document.new(arguments['modifiers']))
130
+ end
131
+ if read_preference
132
+ collection = collection.with(read: read_preference)
133
+ end
134
+ collection.find(arguments['filter'], opts).to_a
135
+ end
136
+
137
+ def find_one(collection, context)
138
+ find(collection, context).first
139
+ end
140
+
141
+ def client_list_databases(client, context)
142
+ client.list_databases
143
+ end
144
+
145
+ def client_list_database_names(client, context)
146
+ client.list_databases({}, true)
147
+ end
148
+
149
+ def client_list_database_objects(client, context)
150
+ client.list_mongo_databases
151
+ end
152
+
153
+ def db_list_collections(database, context)
154
+ database.list_collections
155
+ end
156
+
157
+ def db_list_collection_names(database, context)
158
+ database.collection_names
159
+ end
160
+
161
+ def db_list_collection_objects(database, context)
162
+ database.collections
163
+ end
164
+
165
+ def list_indexes(collection, context)
166
+ collection.indexes.to_a
167
+ end
168
+
169
+ def watch(collection, context)
170
+ collection.watch
171
+ end
172
+
173
+ def db_watch(database, context)
174
+ database.watch
175
+ end
176
+
177
+ def client_watch(client, context)
178
+ client.watch
179
+ end
180
+
181
+ def download(fs_bucket, context)
182
+ stream = fs_bucket.open_download_stream(BSON::ObjectId.from_string(arguments['id']['$oid']))
183
+ stream.read
184
+ end
185
+
186
+ def download_by_name(fs_bucket, context)
187
+ stream = fs_bucket.open_download_stream_by_name(arguments['filename'])
188
+ stream.read
189
+ end
190
+
191
+ def map_reduce(collection, context)
192
+ view = Mongo::Collection::View.new(collection)
193
+ mr = Mongo::Collection::View::MapReduce.new(view, arguments['map']['$code'], arguments['reduce']['$code'])
194
+ mr.to_a
195
+ end
196
+
197
+ # write operations
198
+
199
+ def bulk_write(collection, context)
200
+ result = collection.bulk_write(requests, context.transform_arguments(options))
201
+ return_doc = {}
202
+ return_doc['deletedCount'] = result.deleted_count || 0
203
+ return_doc['insertedIds'] = result.inserted_ids if result.inserted_ids
204
+ return_doc['insertedCount'] = result.inserted_count || 0
205
+ return_doc['upsertedId'] = result.upserted_id if arguments['upsert']
206
+ return_doc['upsertedIds'] = result.upserted_ids if result.upserted_ids
207
+ return_doc['upsertedCount'] = result.upserted_count || 0
208
+ return_doc['matchedCount'] = result.matched_count || 0
209
+ return_doc['modifiedCount'] = result.modified_count || 0
210
+ return_doc
211
+ end
212
+
213
+ def delete_many(collection, context)
214
+ result = collection.delete_many(arguments['filter'], context.transform_arguments(options))
215
+ { 'deletedCount' => result.deleted_count }
216
+ end
217
+
218
+ def delete_one(collection, context)
219
+ result = collection.delete_one(arguments['filter'], context.transform_arguments(options))
220
+ { 'deletedCount' => result.deleted_count }
221
+ end
222
+
223
+ def insert_many(collection, context)
224
+ result = collection.insert_many(arguments['documents'], context.transform_arguments(options))
225
+ { 'insertedIds' => result.inserted_ids }
226
+ end
227
+
228
+ def insert_one(collection, context)
229
+ result = collection.insert_one(arguments['document'], context.transform_arguments(options))
230
+ { 'insertedId' => result.inserted_id }
231
+ end
232
+
233
+ def replace_one(collection, context)
234
+ result = collection.replace_one(arguments['filter'], arguments['replacement'], context.transform_arguments(options))
235
+ update_return_doc(result)
236
+ end
237
+
238
+ def update_many(collection, context)
239
+ result = collection.update_many(arguments['filter'], arguments['update'], context.transform_arguments(options))
240
+ update_return_doc(result)
241
+ end
242
+
243
+ def update_one(collection, context)
244
+ result = collection.update_one(arguments['filter'], arguments['update'], context.transform_arguments(options))
245
+ update_return_doc(result)
246
+ end
247
+
248
+ def find_one_and_delete(collection, context)
249
+ collection.find_one_and_delete(arguments['filter'], context.transform_arguments(options))
250
+ end
251
+
252
+ def find_one_and_replace(collection, context)
253
+ collection.find_one_and_replace(arguments['filter'], arguments['replacement'], context.transform_arguments(options))
254
+ end
255
+
256
+ def find_one_and_update(collection, context)
257
+ collection.find_one_and_update(arguments['filter'], arguments['update'], context.transform_arguments(options))
258
+ end
259
+
260
+ # options & arguments
261
+
262
+ def options
263
+ out = {}
264
+ # Most tests have an "arguments" key which is a hash of options to
265
+ # be provided to the operation. The command monitoring unacknowledged
266
+ # bulk write test is an exception in that it has an "options" key
267
+ # with the options.
268
+ arguments.merge(arguments['options'] || {}).each do |spec_k, v|
269
+ ruby_k = Utils.underscore(spec_k).to_sym
270
+
271
+ if v.is_a?(Hash) && v['$numberLong']
272
+ v = v['$numberLong'].to_i
273
+ end
274
+
275
+ if respond_to?("transform_#{ruby_k}", true)
276
+ v = send("transform_#{ruby_k}", v)
277
+ end
278
+
279
+ out[ruby_k] = v
280
+ end
281
+ out
282
+ end
283
+
284
+ def requests
285
+ arguments['requests'].map do |request|
286
+ case request.keys.first
287
+ when 'insertOne' then
288
+ { insert_one: request['insertOne']['document'] }
289
+ when 'updateOne' then
290
+ update = request['updateOne']
291
+ { update_one: { filter: update['filter'], update: update['update'] } }
292
+ when 'name' then
293
+ bulk_request(request)
294
+ end
39
295
  end
40
296
  end
297
+
298
+ def bulk_request(request)
299
+ op_name = Utils.underscore(request['name'])
300
+ args = Utils.shallow_snakeize_hash(request['arguments'])
301
+ if args[:document]
302
+ unless args.keys == [:document]
303
+ raise "If :document is given, it must be the only key"
304
+ end
305
+ args = args[:document]
306
+ end
307
+ { op_name => args }
308
+ end
309
+
310
+ def upsert
311
+ arguments['upsert']
312
+ end
313
+
314
+ def transform_return_document(v)
315
+ Utils.underscore(v).to_sym
316
+ end
317
+
318
+ def update
319
+ arguments['update']
320
+ end
321
+
322
+ def transform_read_preference(v)
323
+ Utils.snakeize_hash(v)
324
+ end
325
+
326
+ def read_preference
327
+ transform_read_preference(@spec['read_preference'])
328
+ end
329
+
330
+ def update_return_doc(result)
331
+ return_doc = {}
332
+ return_doc['upsertedId'] = result.upserted_id if arguments['upsert']
333
+ return_doc['upsertedCount'] = result.upserted_count
334
+ return_doc['matchedCount'] = result.matched_count
335
+ return_doc['modifiedCount'] = result.modified_count if result.modified_count
336
+ return_doc
337
+ end
41
338
  end
42
339
  end
43
340
  end