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
@@ -0,0 +1,21 @@
1
+ module Unified
2
+
3
+ class Error < StandardError
4
+ end
5
+
6
+ class ResultMismatch < Error
7
+ end
8
+
9
+ class ErrorMismatch < Error
10
+ end
11
+
12
+ class EntityMapOverwriteAttempt < Error
13
+ end
14
+
15
+ class EntityMissing < Error
16
+ end
17
+
18
+ class InvalidTest < Error
19
+ end
20
+
21
+ end
@@ -0,0 +1,55 @@
1
+ module Unified
2
+
3
+ module GridFsOperations
4
+
5
+ def delete(op)
6
+ bucket = entities.get(:bucket, op.use!('object'))
7
+ use_arguments(op) do |args|
8
+ bucket.delete(args.use!('id'))
9
+ end
10
+ end
11
+
12
+ def download(op)
13
+ bucket = entities.get(:bucket, op.use!('object'))
14
+ use_arguments(op) do |args|
15
+ stream = bucket.open_download_stream(args.use!('id'))
16
+ stream.read
17
+ end
18
+ end
19
+
20
+ def upload(op)
21
+ bucket = entities.get(:bucket, op.use!('object'))
22
+ use_arguments(op) do |args|
23
+ opts = {}
24
+ if chunk_size = args.use('chunkSizeBytes')
25
+ opts[:chunk_size] = chunk_size
26
+ end
27
+ contents = transform_contents(args.use!('source'))
28
+ file_id = nil
29
+ bucket.open_upload_stream(args.use!('filename'), **opts) do |stream|
30
+ stream.write(contents)
31
+ file_id = stream.file_id
32
+ end
33
+ file_id
34
+ end
35
+ end
36
+
37
+ private
38
+
39
+ def transform_contents(contents)
40
+ if Hash === contents
41
+ if contents.length != 1
42
+ raise NotImplementedError, "Wanted hash with one element"
43
+ end
44
+ if contents.keys.first != '$$hexBytes'
45
+ raise NotImplementedError, "$$hexBytes is the only key supported"
46
+ end
47
+
48
+ decode_hex_bytes(contents.values.first)
49
+ else
50
+ contents
51
+ end
52
+ end
53
+
54
+ end
55
+ end
@@ -0,0 +1,250 @@
1
+ module Unified
2
+
3
+ module SupportOperations
4
+
5
+ def run_command(op)
6
+ database = entities.get(:database, op.use!('object'))
7
+
8
+ use_arguments(op) do |args|
9
+ args.use!('commandName')
10
+
11
+ cmd = args.use!('command')
12
+
13
+ database.command(cmd)
14
+ end
15
+ end
16
+
17
+ def fail_point(op)
18
+ consume_test_runner(op)
19
+ use_arguments(op) do |args|
20
+ client = entities.get(:client, args.use!('client'))
21
+ client.command(args.use('failPoint'))
22
+ end
23
+ end
24
+
25
+ def targeted_fail_point(op)
26
+ consume_test_runner(op)
27
+ use_arguments(op) do |args|
28
+ session = args.use!('session')
29
+ session = entities.get(:session, session)
30
+ unless session.pinned_server
31
+ raise ArgumentError, 'Targeted fail point requires session to be pinned to a server'
32
+ end
33
+
34
+ client = ClusterTools.instance.direct_client(session.pinned_server.address,
35
+ database: 'admin')
36
+ client.command(fp = args.use!('failPoint'))
37
+ args.clear
38
+
39
+ $disable_fail_points ||= []
40
+ $disable_fail_points << [
41
+ fp,
42
+ session.pinned_server.address,
43
+ ]
44
+ end
45
+ end
46
+
47
+ def end_session(op)
48
+ session = entities.get(:session, op.use!('object'))
49
+ session.end_session
50
+ end
51
+
52
+ def assert_session_dirty(op)
53
+ consume_test_runner(op)
54
+ # https://jira.mongodb.org/browse/RUBY-1813
55
+ true
56
+ end
57
+
58
+ def assert_session_not_dirty(op)
59
+ consume_test_runner(op)
60
+ use_arguments(op) do |args|
61
+ session = entities.get(:session, args.use!('session'))
62
+ # https://jira.mongodb.org/browse/RUBY-1813
63
+ true
64
+ end
65
+ end
66
+
67
+ def assert_same_lsid_on_last_two_commands(op, expected: true)
68
+ consume_test_runner(op)
69
+ use_arguments(op) do |args|
70
+ client = entities.get(:client, args.use!('client'))
71
+ subscriber = @subscribers.fetch(client)
72
+ unless subscriber.started_events.length >= 2
73
+ raise Error::ResultMismatch, "Must have at least 2 events, have #{subscriber.started_events.length}"
74
+ end
75
+ lsids = subscriber.started_events[-2...-1].map do |cmd|
76
+ cmd.command.fetch('lsid')
77
+ end
78
+ if expected
79
+ unless lsids.first == lsids.last
80
+ raise Error::ResultMismatch, "lsids differ but they were expected to be the same"
81
+ end
82
+ else
83
+ if lsids.first == lsids.last
84
+ raise Error::ResultMismatch, "lsids are the same but they were expected to be different"
85
+ end
86
+ end
87
+ end
88
+ end
89
+
90
+ def assert_different_lsid_on_last_two_commands(op)
91
+ assert_same_lsid_on_last_two_commands(op, expected: false)
92
+ end
93
+
94
+ def start_transaction(op)
95
+ $klil_transactions = true
96
+ session = entities.get(:session, op.use!('object'))
97
+ assert_no_arguments(op)
98
+ session.start_transaction
99
+ end
100
+
101
+ def assert_session_transaction_state(op)
102
+ consume_test_runner(op)
103
+ use_arguments(op) do |args|
104
+ session = entities.get(:session, args.use!('session'))
105
+ state = args.use!('state')
106
+ unless session.send("#{state}_transaction?")
107
+ raise Error::ResultMismatch, "Expected session to have state #{state}"
108
+ end
109
+ end
110
+ end
111
+
112
+ def commit_transaction(op)
113
+ session = entities.get(:session, op.use!('object'))
114
+ assert_no_arguments(op)
115
+ session.commit_transaction
116
+ end
117
+
118
+ def abort_transaction(op)
119
+ session = entities.get(:session, op.use!('object'))
120
+ assert_no_arguments(op)
121
+ session.abort_transaction
122
+ end
123
+
124
+ def with_transaction(op)
125
+ $kill_transactions = true
126
+ session = entities.get(:session, op.use!('object'))
127
+ use_arguments(op) do |args|
128
+ ops = args.use!('callback')
129
+
130
+ if args.empty?
131
+ opts = {}
132
+ else
133
+ opts = ::Utils.underscore_hash(args)
134
+ if value = opts[:read_concern]&.[](:level)
135
+ opts[:read_concern][:level] = value.to_sym
136
+ end
137
+ args.clear
138
+ end
139
+
140
+ session.with_transaction(**opts) do
141
+ execute_operations(ops)
142
+ end
143
+ end
144
+ end
145
+
146
+ def assert_session_pinned(op, state = true)
147
+ consume_test_runner(op)
148
+ use_arguments(op) do |args|
149
+ session = entities.get(:session, args.use!('session'))
150
+
151
+ if state
152
+ unless session.pinned_server
153
+ raise Error::ResultMismatch, 'Expected session to be pinned but it is not'
154
+ end
155
+ else
156
+ if session.pinned_server
157
+ raise Error::ResultMismatch, 'Expected session to be not pinned but it is'
158
+ end
159
+ end
160
+ end
161
+ end
162
+
163
+ def assert_session_unpinned(op)
164
+ assert_session_pinned(op, false)
165
+ end
166
+
167
+ def _loop(op)
168
+ consume_test_runner(op)
169
+ use_arguments(op) do |args|
170
+ ops = args.use!('operations')
171
+
172
+ if store_errors = args.use('storeErrorsAsEntity')
173
+ entities.set(:error_list, store_errors, [])
174
+ end
175
+
176
+ if store_failures = args.use('storeFailuresAsEntity')
177
+ entities.set(:failure_list, store_failures, [])
178
+ end
179
+
180
+ store_iterations = args.use('storeIterationsAsEntity')
181
+ iterations = 0
182
+ store_successes = args.use('storeSuccessesAsEntity')
183
+ successes = 0
184
+
185
+ loop do
186
+ break if stop?
187
+ begin
188
+ ops.map(&:dup).each do |op|
189
+ execute_operation(op)
190
+ successes += 1
191
+ end
192
+ rescue Unified::Error::ResultMismatch => e
193
+ if store_failures
194
+ STDERR.puts "Failure: #{e.class}: #{e}"
195
+ entities.get(:failure_list, store_failures) << {
196
+ error: "#{e.class}: #{e}",
197
+ time: Time.now.to_f,
198
+ }
199
+ elsif store_errors
200
+ STDERR.puts "Failure: #{e.class}: #{e} (reporting as error)"
201
+ entities.get(:error_list, store_errors) << {
202
+ error: "#{e.class}: #{e}",
203
+ time: Time.now.to_f,
204
+ }
205
+ else
206
+ raise
207
+ end
208
+ rescue => e
209
+ if store_errors
210
+ STDERR.puts "Error: #{e.class}: #{e}"
211
+ entities.get(:error_list, store_errors) << {
212
+ error: "#{e.class}: #{e}",
213
+ observedAt: Time.now.to_f,
214
+ }
215
+ else
216
+ raise
217
+ end
218
+ end
219
+ iterations += 1
220
+ end
221
+
222
+ if store_iterations
223
+ entities.set(:iteration_count, store_iterations, iterations)
224
+ end
225
+ if store_successes
226
+ entities.set(:success_count, store_successes, successes)
227
+ end
228
+ end
229
+ end
230
+
231
+ private
232
+
233
+ def assert_no_arguments(op)
234
+ if op.key?('arguments')
235
+ raise NotimplementedError, "Arguments are not allowed"
236
+ end
237
+ end
238
+
239
+ def consume_test_runner(op)
240
+ v = op.use!('object')
241
+ unless v == 'testRunner'
242
+ raise NotImplementedError, 'Expected object to be testRunner'
243
+ end
244
+ end
245
+
246
+ def decode_hex_bytes(value)
247
+ value.scan(/../).map { |hex| hex.to_i(16).chr }.join
248
+ end
249
+ end
250
+ end
@@ -0,0 +1,393 @@
1
+ require 'runners/crud/requirement'
2
+ require 'runners/unified/crud_operations'
3
+ require 'runners/unified/grid_fs_operations'
4
+ require 'runners/unified/ddl_operations'
5
+ require 'runners/unified/change_stream_operations'
6
+ require 'runners/unified/support_operations'
7
+ require 'runners/unified/assertions'
8
+ require 'support/utils'
9
+
10
+ module Unified
11
+
12
+ class Test
13
+ include CrudOperations
14
+ include GridFsOperations
15
+ include DdlOperations
16
+ include ChangeStreamOperations
17
+ include SupportOperations
18
+ include Assertions
19
+
20
+ def initialize(spec, **opts)
21
+ @spec = spec
22
+ @entities = EntityMap.new
23
+ @test_spec = UsingHash[@spec.fetch('test')]
24
+ @description = @test_spec.use('description')
25
+ @outcome = @test_spec.use('outcome')
26
+ @expected_events = @test_spec.use('expectEvents')
27
+ @skip_reason = @test_spec.use('skipReason')
28
+ if req = @test_spec.use('runOnRequirements')
29
+ @reqs = req.map { |r| Mongo::CRUD::Requirement.new(r) }
30
+ end
31
+ if req = @spec['group_runOnRequirements']
32
+ @group_reqs = req.map { |r| Mongo::CRUD::Requirement.new(r) }
33
+ end
34
+ mongoses = @spec['createEntities'].select do |spec|
35
+ spec['client']
36
+ end.map do |spec|
37
+ spec['client']['useMultipleMongoses']
38
+ end.compact.uniq
39
+ if mongoses.length > 1
40
+ raise Error::InvalidTest, "Conflicting useMultipleMongoses values"
41
+ end
42
+ @multiple_mongoses = mongoses.first
43
+ @test_spec.freeze
44
+ @subscribers = {}
45
+ @options = opts
46
+ end
47
+
48
+ attr_reader :test_spec
49
+ attr_reader :description
50
+ attr_reader :outcome
51
+ attr_reader :skip_reason
52
+ attr_reader :reqs, :group_reqs
53
+ attr_reader :options
54
+
55
+ def skip?
56
+ !!@skip_reason
57
+ end
58
+
59
+ def require_multiple_mongoses?
60
+ @multiple_mongoses == true
61
+ end
62
+
63
+ def require_single_mongos?
64
+ @multiple_mongoses == false
65
+ end
66
+
67
+ attr_reader :entities
68
+
69
+ def create_entities
70
+ @spec['createEntities'].each do |entity_spec|
71
+ unless entity_spec.keys.length == 1
72
+ raise NotImplementedError, "Entity must have exactly one key"
73
+ end
74
+
75
+ type, spec = entity_spec.first
76
+ spec = UsingHash[spec]
77
+ id = spec.use!('id')
78
+
79
+ entity = case type
80
+ when 'client'
81
+ # Handled earlier
82
+ spec.delete('useMultipleMongoses')
83
+
84
+ if smc_opts = spec.use('uriOptions')
85
+ opts = Mongo::URI::OptionsMapper.new.smc_to_ruby(smc_opts)
86
+ else
87
+ opts = {}
88
+ end
89
+
90
+ if store_events = spec.use('storeEventsAsEntities')
91
+ store_event_names = {}
92
+ store_events.each do |spec|
93
+ entity_name = spec['id']
94
+ event_names = spec['events']
95
+ #event_name = event_name.gsub(/Event$/, '').gsub(/[A-Z]/) { |m| "_#{m}" }.upcase
96
+ #event_name = event_name.gsub(/Event$/, '').sub(/./) { |m| m.upcase }
97
+ event_names.each do |event_name|
98
+ store_event_names[event_name] = entity_name
99
+ end
100
+ end
101
+ store_event_names.values.uniq.each do |entity_name|
102
+ entities.set(:event_list, entity_name, [])
103
+ end
104
+ subscriber = StoringEventSubscriber.new do |payload|
105
+ if entity_name = store_event_names[payload['name']]
106
+ entities.get(:event_list, entity_name) << payload
107
+ end
108
+ end
109
+ opts[:sdam_proc] = lambda do |client|
110
+ client.subscribe(Mongo::Monitoring::COMMAND, subscriber)
111
+ client.subscribe(Mongo::Monitoring::CONNECTION_POOL, subscriber)
112
+ end
113
+ end
114
+
115
+ if server_api = spec.use('serverApi')
116
+ server_api = ::Utils.underscore_hash(server_api)
117
+ opts[:server_api] = server_api
118
+ end
119
+
120
+ create_client(**opts).tap do |client|
121
+ if oe = spec.use('observeEvents')
122
+ oe.each do |event|
123
+ case event
124
+ when 'commandStartedEvent', 'commandSucceededEvent', 'commandFailedEvent'
125
+ subscriber = (@subscribers[client] ||= EventSubscriber.new)
126
+ unless client.send(:monitoring).subscribers[Mongo::Monitoring::COMMAND].include?(subscriber)
127
+ client.subscribe(Mongo::Monitoring::COMMAND, subscriber)
128
+ end
129
+ kind = event.sub('command', '').sub('Event', '').downcase.to_sym
130
+ subscriber.add_wanted_events(kind)
131
+ if ignore_events = spec.use('ignoreCommandMonitoringEvents')
132
+ subscriber.ignore_commands(ignore_events)
133
+ end
134
+ else
135
+ raise NotImplementedError, "Unknown event #{event}"
136
+ end
137
+ end
138
+ end
139
+ end
140
+ when 'database'
141
+ client = entities.get(:client, spec.use!('client'))
142
+ client.use(spec.use!('databaseName')).database
143
+ when 'collection'
144
+ database = entities.get(:database, spec.use!('database'))
145
+ # TODO verify
146
+ opts = Utils.snakeize_hash(spec.use('collectionOptions') || {})
147
+ database[spec.use!('collectionName'), opts]
148
+ when 'bucket'
149
+ database = entities.get(:database, spec.use!('database'))
150
+ database.fs
151
+ when 'session'
152
+ client = entities.get(:client, spec.use!('client'))
153
+
154
+ if smc_opts = spec.use('sessionOptions')
155
+ opts = ::Utils.underscore_hash(smc_opts)
156
+ else
157
+ opts = {}
158
+ end
159
+
160
+ client.start_session(**opts)
161
+ else
162
+ raise NotImplementedError, "Unknown type #{type}"
163
+ end
164
+ unless spec.empty?
165
+ raise NotImplementedError, "Unhandled spec keys: #{spec}"
166
+ end
167
+ entities.set(type.to_sym, id, entity)
168
+ end
169
+ end
170
+
171
+ def set_initial_data
172
+ @spec['initialData']&.each do |entity_spec|
173
+ spec = UsingHash[entity_spec]
174
+ collection = root_authorized_client.use(spec.use!('databaseName'))[spec.use!('collectionName')]
175
+ collection.drop
176
+ docs = spec.use!('documents')
177
+ if docs.any?
178
+ collection.insert_many(docs)
179
+ else
180
+ begin
181
+ collection.create
182
+ rescue Mongo::Error => e
183
+ if Mongo::Error::OperationFailure === e && (
184
+ e.code == 48 || e.message =~ /collection already exists/
185
+ )
186
+ # Already exists
187
+ else
188
+ raise
189
+ end
190
+ end
191
+ end
192
+ unless spec.empty?
193
+ raise NotImplementedError, "Unhandled spec keys: #{spec}"
194
+ end
195
+ end
196
+ end
197
+
198
+ def run
199
+ kill_sessions
200
+
201
+ test_spec = UsingHash[self.test_spec]
202
+ ops = test_spec.use!('operations')
203
+ execute_operations(ops)
204
+ unless test_spec.empty?
205
+ raise NotImplementedError, "Unhandled spec keys: #{test_spec}"
206
+ end
207
+ ensure
208
+ disable_fail_points
209
+ end
210
+
211
+ def stop!
212
+ @stop = true
213
+ end
214
+
215
+ def stop?
216
+ !!@stop
217
+ end
218
+
219
+ def cleanup
220
+ if $kill_transactions || true
221
+ kill_sessions
222
+ $kill_transactions = nil
223
+ end
224
+
225
+ entities[:client]&.each do |id, client|
226
+ client.close
227
+ end
228
+ end
229
+
230
+ private
231
+
232
+ def execute_operations(ops)
233
+ ops.each do |op|
234
+ execute_operation(op)
235
+ end
236
+ end
237
+
238
+ def execute_operation(op)
239
+ use_all(op, 'operation', op) do |op|
240
+ name = Utils.underscore(op.use!('name'))
241
+ method_name = name
242
+ if name.to_s == 'loop'
243
+ method_name = "_#{name}"
244
+ end
245
+ if expected_error = op.use('expectError')
246
+ begin
247
+ send(method_name, op)
248
+ rescue Mongo::Error, BSON::String::IllegalKey => e
249
+ if expected_error.use('isClientError')
250
+ unless BSON::String::IllegalKey === e
251
+ raise Error::ErrorMismatch, "Expected client error but got #{e}"
252
+ end
253
+ end
254
+ if code = expected_error.use('errorCode')
255
+ unless e.code == code
256
+ raise Error::ErrorMismatch, "Expected #{code} code but had #{e.code}"
257
+ end
258
+ end
259
+ if code_name = expected_error.use('errorCodeName')
260
+ unless e.code_name == code_name
261
+ raise Error::ErrorMismatch, "Expected #{code_name} code name but had #{e.code_name}"
262
+ end
263
+ end
264
+ if text = expected_error.use('errorContains')
265
+ unless e.to_s.include?(text)
266
+ raise Error::ErrorMismatch, "Expected #{text} in the message but had #{e}"
267
+ end
268
+ end
269
+ if labels = expected_error.use('errorLabelsContain')
270
+ labels.each do |label|
271
+ unless e.label?(label)
272
+ raise Error::ErrorMismatch, "Expected error to contain label #{label} but it did not"
273
+ end
274
+ end
275
+ end
276
+ if omit_labels = expected_error.use('errorLabelsOmit')
277
+ omit_labels.each do |label|
278
+ if e.label?(label)
279
+ raise Error::ErrorMismatch, "Expected error to not contain label #{label} but it did"
280
+ end
281
+ end
282
+ end
283
+ if expected_result = expected_error.use('expectResult')
284
+ assert_result_matches(e.result, expected_result)
285
+ #expected_result.clear
286
+ # Important: this must be the last branch.
287
+ elsif expected_error.use('isError')
288
+ # Nothing but we consume the key.
289
+ end
290
+ unless expected_error.empty?
291
+ raise NotImplementedError, "Unhandled keys: #{expected_error}"
292
+ end
293
+ else
294
+ raise Error::ErrorMismatch, "Expected exception but none was raised"
295
+ end
296
+ else
297
+ result = send(method_name, op)
298
+ if expected_result = op.use('expectResult')
299
+ if result.nil? && !expected_result.empty?
300
+ raise Error::ResultMismatch, "Actual result nil but expected result #{expected_result}"
301
+ elsif Array === expected_result
302
+ assert_documents_match(result, expected_result)
303
+ else
304
+ assert_result_matches(result, expected_result)
305
+ end
306
+ #expected_result.clear
307
+ end
308
+ if save_entity = op.use('saveResultAsEntity')
309
+ entities.set(:result, save_entity, result)
310
+ end
311
+ end
312
+ end
313
+ end
314
+
315
+ def use_sub(hash, key, &block)
316
+ v = hash.use!(key)
317
+ use_all(hash, key, v, &block)
318
+ end
319
+
320
+ def use_all(hash, key, v)
321
+ orig_v = v.dup
322
+ (yield v).tap do
323
+ unless v.empty?
324
+ raise NotImplementedError, "Unconsumed items for #{key}: #{v}\nOriginal hash: #{orig_v}"
325
+ end
326
+ end
327
+ end
328
+
329
+ def use_arguments(op, &block)
330
+ if op.key?('arguments')
331
+ use_sub(op, 'arguments', &block)
332
+ else
333
+ yield UsingHash.new
334
+ end
335
+ end
336
+
337
+ def disable_fail_points
338
+ if $disable_fail_points
339
+ $disable_fail_points.each do |(fail_point_command, address)|
340
+ client = ClusterTools.instance.direct_client(address,
341
+ database: 'admin')
342
+ client.command(configureFailPoint: fail_point_command['configureFailPoint'],
343
+ mode: 'off')
344
+ end
345
+ $disable_fail_points = nil
346
+ end
347
+ end
348
+
349
+ def kill_sessions
350
+ if options[:kill_sessions] != false
351
+ begin
352
+ root_authorized_client.command(
353
+ killAllSessions: [],
354
+ )
355
+ rescue Mongo::Error::OperationFailure => e
356
+ if e.code == 11601
357
+ # operation was interrupted, ignore
358
+ elsif e.code == 59
359
+ # no such command (old server), ignore
360
+ else
361
+ raise
362
+ end
363
+ end
364
+ end
365
+ end
366
+
367
+ def root_authorized_client
368
+ @root_authorized_client ||= ClientRegistry.instance.global_client('root_authorized')
369
+ end
370
+
371
+ def create_client(**opts)
372
+ args = case v = options[:client_args]
373
+ when Array
374
+ unless v.length == 2
375
+ raise NotImplementedError, 'Client args array must have two elements'
376
+ end
377
+ [v.first, v.last.dup]
378
+ when String
379
+ [v, {}]
380
+ else
381
+ [
382
+ SpecConfig.instance.addresses,
383
+ SpecConfig.instance.all_test_options,
384
+ ]
385
+ end
386
+ args.last.update(
387
+ max_read_retries: 0,
388
+ max_write_retries: 0,
389
+ ).update(opts)
390
+ Mongo::Client.new(*args)
391
+ end
392
+ end
393
+ end