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.
- checksums.yaml +4 -4
- checksums.yaml.gz.sig +0 -0
- data.tar.gz.sig +0 -0
- data/README.md +4 -1
- data/Rakefile +8 -15
- data/lib/mongo.rb +23 -0
- data/lib/mongo/auth/aws/conversation.rb +1 -4
- data/lib/mongo/auth/base.rb +13 -7
- data/lib/mongo/auth/conversation_base.rb +32 -0
- data/lib/mongo/auth/cr/conversation.rb +6 -29
- data/lib/mongo/auth/gssapi/conversation.rb +4 -15
- data/lib/mongo/auth/ldap/conversation.rb +3 -14
- data/lib/mongo/auth/sasl_conversation_base.rb +1 -13
- data/lib/mongo/auth/scram_conversation_base.rb +7 -34
- data/lib/mongo/auth/user/view.rb +16 -9
- data/lib/mongo/auth/x509/conversation.rb +4 -25
- data/lib/mongo/background_thread.rb +11 -0
- data/lib/mongo/bulk_write.rb +21 -18
- data/lib/mongo/client.rb +82 -6
- data/lib/mongo/cluster.rb +19 -28
- data/lib/mongo/cluster/reapers/cursor_reaper.rb +6 -2
- data/lib/mongo/cluster/sdam_flow.rb +14 -0
- data/lib/mongo/collection.rb +8 -6
- data/lib/mongo/collection/view/aggregation.rb +1 -1
- data/lib/mongo/collection/view/change_stream.rb +1 -1
- data/lib/mongo/collection/view/iterable.rb +1 -1
- data/lib/mongo/collection/view/map_reduce.rb +2 -2
- data/lib/mongo/collection/view/readable.rb +42 -20
- data/lib/mongo/collection/view/writable.rb +14 -14
- data/lib/mongo/cursor.rb +2 -2
- data/lib/mongo/database.rb +22 -5
- data/lib/mongo/database/view.rb +1 -1
- data/lib/mongo/error.rb +9 -1
- data/lib/mongo/error/bulk_write_error.rb +17 -3
- data/lib/mongo/error/internal_driver_error.rb +22 -0
- data/lib/mongo/error/operation_failure.rb +21 -2
- data/lib/mongo/error/parser.rb +65 -12
- data/lib/mongo/error/server_api_conflict.rb +23 -0
- data/lib/mongo/error/server_api_not_supported.rb +24 -0
- data/lib/mongo/error/unmet_dependency.rb +21 -0
- data/lib/mongo/grid/fs_bucket.rb +37 -37
- data/lib/mongo/index/view.rb +21 -11
- data/lib/mongo/monitoring.rb +13 -4
- data/lib/mongo/monitoring/event/server_heartbeat_failed.rb +27 -16
- data/lib/mongo/monitoring/event/server_heartbeat_succeeded.rb +26 -15
- data/lib/mongo/operation.rb +2 -2
- data/lib/mongo/operation/collections_info.rb +18 -1
- data/lib/mongo/operation/collections_info/command.rb +2 -2
- data/lib/mongo/operation/context.rb +99 -0
- data/lib/mongo/operation/indexes.rb +15 -1
- data/lib/mongo/operation/insert/command.rb +2 -2
- data/lib/mongo/operation/insert/legacy.rb +2 -2
- data/lib/mongo/operation/insert/op_msg.rb +2 -2
- data/lib/mongo/operation/list_collections/result.rb +4 -1
- data/lib/mongo/operation/result.rb +2 -0
- data/lib/mongo/operation/shared/executable.rb +24 -14
- data/lib/mongo/operation/shared/executable_no_validate.rb +2 -2
- data/lib/mongo/operation/shared/op_msg_or_command.rb +1 -7
- data/lib/mongo/operation/shared/op_msg_or_find_command.rb +1 -7
- data/lib/mongo/operation/shared/polymorphic_operation.rb +39 -0
- data/lib/mongo/operation/shared/response_handling.rb +23 -23
- data/lib/mongo/operation/shared/sessions_supported.rb +13 -2
- data/lib/mongo/operation/shared/write.rb +8 -18
- data/lib/mongo/protocol/compressed.rb +51 -5
- data/lib/mongo/protocol/message.rb +20 -2
- data/lib/mongo/protocol/msg.rb +36 -11
- data/lib/mongo/query_cache.rb +30 -0
- data/lib/mongo/retryable.rb +1 -1
- data/lib/mongo/server.rb +7 -15
- data/lib/mongo/server/app_metadata.rb +52 -18
- data/lib/mongo/server/connection.rb +5 -0
- data/lib/mongo/server/connection_base.rb +13 -10
- data/lib/mongo/server/connection_pool.rb +6 -4
- data/lib/mongo/server/description.rb +4 -0
- data/lib/mongo/server/description/features.rb +9 -8
- data/lib/mongo/server/monitor.rb +20 -1
- data/lib/mongo/server/monitor/app_metadata.rb +1 -1
- data/lib/mongo/server/monitor/connection.rb +9 -10
- data/lib/mongo/server/pending_connection.rb +24 -6
- data/lib/mongo/server/push_monitor.rb +11 -1
- data/lib/mongo/session.rb +2 -2
- data/lib/mongo/session/session_pool.rb +4 -2
- data/lib/mongo/socket.rb +29 -4
- data/lib/mongo/socket/ssl.rb +8 -0
- data/lib/mongo/srv/monitor.rb +0 -11
- data/lib/mongo/uri/options_mapper.rb +38 -0
- data/lib/mongo/utils.rb +15 -0
- data/lib/mongo/version.rb +1 -1
- data/spec/README.md +24 -1
- data/spec/integration/auth_spec.rb +25 -15
- data/spec/integration/bulk_write_error_message_spec.rb +41 -0
- data/spec/integration/change_stream_spec.rb +4 -4
- data/spec/integration/command_monitoring_spec.rb +2 -2
- data/spec/integration/connection_spec.rb +2 -0
- data/spec/integration/docs_examples_spec.rb +8 -1
- data/spec/integration/fork_reconnect_spec.rb +4 -1
- data/spec/integration/ocsp_verifier_spec.rb +13 -7
- data/spec/integration/operation_failure_code_spec.rb +1 -1
- data/spec/integration/operation_failure_message_spec.rb +90 -0
- data/spec/integration/reconnect_spec.rb +1 -1
- data/spec/integration/sdam_error_handling_spec.rb +1 -1
- data/spec/integration/sdam_events_spec.rb +3 -5
- data/spec/integration/snappy_compression_spec.rb +25 -0
- data/spec/integration/srv_monitoring_spec.rb +1 -1
- data/spec/integration/transactions_examples_spec.rb +6 -0
- data/spec/integration/zlib_compression_spec.rb +1 -1
- data/spec/integration/zstd_compression_spec.rb +26 -0
- data/spec/lite_spec_helper.rb +7 -1
- data/spec/mongo/address_spec.rb +15 -11
- data/spec/mongo/auth/ldap/conversation_spec.rb +1 -1
- data/spec/mongo/auth/ldap_spec.rb +5 -1
- data/spec/mongo/auth/scram_negotiation_spec.rb +1 -1
- data/spec/mongo/auth/scram_spec.rb +1 -1
- data/spec/mongo/auth/x509/conversation_spec.rb +3 -3
- data/spec/mongo/client_construction_spec.rb +207 -33
- data/spec/mongo/client_spec.rb +17 -0
- data/spec/mongo/cluster_spec.rb +3 -18
- data/spec/mongo/collection/view/explainable_spec.rb +1 -1
- data/spec/mongo/collection/view/readable_spec.rb +33 -19
- data/spec/mongo/collection_crud_spec.rb +4357 -0
- data/spec/mongo/collection_ddl_spec.rb +534 -0
- data/spec/mongo/collection_spec.rb +5 -4859
- data/spec/mongo/database_spec.rb +66 -4
- data/spec/mongo/error/bulk_write_error_spec.rb +3 -3
- data/spec/mongo/error/parser_spec.rb +37 -6
- data/spec/mongo/index/view_spec.rb +8 -2
- data/spec/mongo/monitoring/event/server_heartbeat_failed_spec.rb +1 -1
- data/spec/mongo/monitoring/event/server_heartbeat_succeeded_spec.rb +1 -1
- data/spec/mongo/operation/aggregate_spec.rb +2 -1
- data/spec/mongo/operation/collections_info_spec.rb +4 -1
- data/spec/mongo/operation/command_spec.rb +6 -3
- data/spec/mongo/operation/create_index_spec.rb +6 -3
- data/spec/mongo/operation/create_user_spec.rb +6 -3
- data/spec/mongo/operation/delete/bulk_spec.rb +9 -6
- data/spec/mongo/operation/delete_spec.rb +11 -7
- data/spec/mongo/operation/drop_index_spec.rb +6 -2
- data/spec/mongo/operation/find/legacy_spec.rb +3 -1
- data/spec/mongo/operation/get_more_spec.rb +3 -1
- data/spec/mongo/operation/indexes_spec.rb +5 -1
- data/spec/mongo/operation/insert/bulk_spec.rb +10 -7
- data/spec/mongo/operation/insert_spec.rb +15 -12
- data/spec/mongo/operation/map_reduce_spec.rb +5 -2
- data/spec/mongo/operation/remove_user_spec.rb +6 -3
- data/spec/mongo/operation/result_spec.rb +1 -1
- data/spec/mongo/operation/update/bulk_spec.rb +9 -6
- data/spec/mongo/operation/update_spec.rb +10 -7
- data/spec/mongo/operation/update_user_spec.rb +4 -1
- data/spec/mongo/protocol/compressed_spec.rb +26 -12
- data/spec/mongo/query_cache_middleware_spec.rb +55 -0
- data/spec/mongo/retryable_spec.rb +3 -2
- data/spec/mongo/server/app_metadata_spec.rb +2 -0
- data/spec/mongo/server/connection_pool/populator_spec.rb +3 -1
- data/spec/mongo/server/connection_pool_spec.rb +1 -1
- data/spec/mongo/server/connection_spec.rb +24 -17
- data/spec/mongo/server/monitor/connection_spec.rb +17 -7
- data/spec/mongo/server/monitor_spec.rb +9 -1
- data/spec/mongo/server_spec.rb +15 -2
- data/spec/mongo/socket/ssl_spec.rb +40 -0
- data/spec/mongo/socket_spec.rb +2 -2
- data/spec/mongo/tls_context_hooks_spec.rb +37 -0
- data/spec/runners/connection_string.rb +0 -4
- data/spec/runners/crud/requirement.rb +40 -3
- data/spec/runners/crud/verifier.rb +8 -0
- data/spec/runners/transactions/operation.rb +13 -2
- data/spec/runners/transactions/test.rb +1 -0
- data/spec/runners/unified.rb +96 -0
- data/spec/runners/unified/assertions.rb +249 -0
- data/spec/runners/unified/change_stream_operations.rb +26 -0
- data/spec/runners/unified/crud_operations.rb +199 -0
- data/spec/runners/unified/ddl_operations.rb +96 -0
- data/spec/runners/unified/entity_map.rb +39 -0
- data/spec/runners/unified/error.rb +25 -0
- data/spec/runners/unified/event_subscriber.rb +91 -0
- data/spec/runners/unified/exceptions.rb +21 -0
- data/spec/runners/unified/grid_fs_operations.rb +55 -0
- data/spec/runners/unified/support_operations.rb +250 -0
- data/spec/runners/unified/test.rb +393 -0
- data/spec/runners/unified/test_group.rb +28 -0
- data/spec/runners/unified/using_hash.rb +31 -0
- data/spec/shared/bin/get-mongodb-download-url +17 -0
- data/spec/shared/lib/mrss/cluster_config.rb +218 -0
- data/spec/shared/lib/mrss/constraints.rb +43 -0
- data/spec/shared/lib/mrss/docker_runner.rb +262 -0
- data/spec/shared/lib/mrss/server_version_registry.rb +112 -0
- data/spec/shared/lib/mrss/utils.rb +15 -0
- data/spec/shared/share/Dockerfile.erb +231 -0
- data/spec/shared/shlib/distro.sh +73 -0
- data/spec/shared/shlib/server.sh +290 -0
- data/spec/shared/shlib/set_env.sh +128 -0
- data/spec/solo/clean_exit_spec.rb +21 -0
- data/spec/spec_helper.rb +4 -1
- data/spec/spec_tests/crud_unified_spec.rb +10 -0
- data/spec/spec_tests/data/change_streams/change-streams.yml +0 -1
- data/spec/spec_tests/data/crud_unified/estimatedDocumentCount.yml +267 -0
- data/spec/spec_tests/data/retryable_reads/estimatedDocumentCount-4.9.yml +60 -0
- data/spec/spec_tests/data/retryable_reads/{estimatedDocumentCount.yml → estimatedDocumentCount-pre4.9.yml} +2 -0
- data/spec/spec_tests/data/retryable_reads/estimatedDocumentCount-serverErrors-4.9.yml +146 -0
- data/spec/spec_tests/data/retryable_reads/{estimatedDocumentCount-serverErrors.yml → estimatedDocumentCount-serverErrors-pre4.9.yml} +2 -0
- data/spec/spec_tests/data/retryable_reads/listIndexNames.yml +1 -1
- data/spec/spec_tests/data/unified/valid-fail/operation-failure.yml +31 -0
- data/spec/spec_tests/data/unified/valid-pass/poc-change-streams.yml +220 -0
- data/spec/spec_tests/data/unified/valid-pass/poc-command-monitoring.yml +102 -0
- data/spec/spec_tests/data/unified/valid-pass/poc-crud.yml +184 -0
- data/spec/spec_tests/data/unified/valid-pass/poc-gridfs.yml +155 -0
- data/spec/spec_tests/data/unified/valid-pass/poc-retryable-reads.yml +193 -0
- data/spec/spec_tests/data/unified/valid-pass/poc-retryable-writes.yml +210 -0
- data/spec/spec_tests/data/unified/valid-pass/poc-sessions.yml +215 -0
- data/spec/spec_tests/data/unified/valid-pass/poc-transactions-convenient-api.yml +235 -0
- data/spec/spec_tests/data/unified/valid-pass/poc-transactions-mongos-pin-auto.yml +169 -0
- data/spec/spec_tests/data/unified/valid-pass/poc-transactions.yml +170 -0
- data/spec/spec_tests/data/uri_options/compression-options.yml +1 -1
- data/spec/spec_tests/data/versioned_api/crud-api-version-1-strict.yml +416 -0
- data/spec/spec_tests/data/versioned_api/crud-api-version-1.yml +409 -0
- data/spec/spec_tests/data/versioned_api/runcommand-helper-no-api-version-declared.yml +67 -0
- data/spec/spec_tests/data/versioned_api/test-commands-deprecation-errors.yml +47 -0
- data/spec/spec_tests/data/versioned_api/test-commands-strict-mode.yml +44 -0
- data/spec/spec_tests/data/versioned_api/transaction-handling.yml +180 -0
- data/spec/spec_tests/unified_spec.rb +15 -0
- data/spec/spec_tests/uri_options_spec.rb +16 -0
- data/spec/spec_tests/versioned_api_spec.rb +10 -0
- data/spec/support/common_shortcuts.rb +15 -1
- data/spec/support/shared/session.rb +2 -2
- data/spec/support/spec_config.rb +46 -3
- data/spec/support/spec_setup.rb +48 -38
- data/spec/support/utils.rb +64 -3
- metadata +1104 -992
- metadata.gz.sig +0 -0
- data/lib/mongo/operation/shared/collections_info_or_list_collections.rb +0 -58
- data/lib/mongo/operation/shared/op_msg_or_list_indexes_command.rb +0 -47
- 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
|