spec/support/transactions.rb in mongo-2.8.0 vs spec/support/transactions.rb in mongo-2.9.0.rc0

- old
+ new

@@ -11,311 +11,7 @@ # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. # See the License for the specific language governing permissions and # limitations under the License. require 'support/transactions/operation' -require 'support/transactions/verifier' - -module Mongo - module Transactions - - # Represents a Transactions specification test. - # - # @since 2.6.0 - class Spec - - # The name of the collection to run the tests against. - # - # @since 2.6.0 - COLLECTION_NAME = 'transactions-tests'.freeze - - # @return [ String ] description The spec description. - # - # @since 2.6.0 - attr_reader :description - - # Instantiate the new spec. - # - # @example Create the spec. - # Spec.new(file) - # - # @param [ String ] file The name of the file. - # - # @since 2.6.0 - def initialize(file) - file = File.new(file) - contents = ERB.new(file.read).result - @spec = YAML.load(contents) - file.close - @description = File.basename(file) - @data = @spec['data'] - @transaction_tests = @spec['tests'] - end - - # Get a list of TransactionTests for each test definition. - # - # @example Get the list of TransactionTests. - # spec.tests - # - # @return [ Array<TransactionsTest> ] The list of TransactionTests. - # - # @since 2.6.0 - def tests - @transaction_tests.map do |test| - Proc.new { Mongo::Transactions::TransactionsTest.new(@data, test, self) } - end.compact - end - - def database_name - @spec['database_name'] - end - - def collection_name - @spec['collection_name'] - end - - def min_server_version - @spec['minServerVersion'] - end - end - - # Represents a single transaction test. - # - # @since 2.6.0 - class TransactionsTest - - # The test description. - # - # @return [ String ] description The test description. - # - # @since 2.6.0 - attr_reader :description - - # The expected command monitoring events - # - # @since 2.6.0 - attr_reader :expectations - - attr_reader :expected_results - attr_reader :skip_reason - - # Instantiate the new CRUDTest. - # - # @example Create the test. - # TransactionTest.new(data, test) - # - # @param [ Array<Hash> ] data The documents the collection - # must have before the test runs. - # @param [ Hash ] test The test specification. - # @param [ Hash ] spec The top level YAML specification. - # - # @since 2.6.0 - def initialize(data, test, spec) - test = IceNine.deep_freeze(test) - @spec = spec - @data = data - @description = test['description'] - @client_options = convert_client_options(test['clientOptions'] || {}) - @session_options = snakeize_hash(test['sessionOptions'] || {}) - @fail_point = test['failPoint'] - @operations = test['operations'] - @expectations = test['expectations'] - @skip_reason = test['skipReason'] - @outcome = test['outcome'] - @expected_results = @operations.map do |o| - result = o['result'] - next result unless result.class == Hash - - # Change maps of result ids to arrays of ids - result.dup.tap do |r| - r.each do |k, v| - next unless ['insertedIds', 'upsertedIds'].include?(k) - r[k] = v.to_a.sort_by(&:first).map(&:last) - end - end - end - end - - def support_client - @support_client ||= ClientRegistry.instance.global_client('root_authorized').use(@spec.database_name) - end - - def admin_support_client - @admin_support_client ||= support_client.use('admin') - end - - def test_client - @test_client ||= ClientRegistry.instance.global_client('authorized_without_retry_writes').with( - @client_options.merge( - database: @spec.database_name, - app_name: 'this is used solely to force the new client to create its own cluster')) - end - - def event_subscriber - @event_subscriber ||= EventSubscriber.new - end - - # Run the test. - # - # @example Run the test. - # test.run - # - # @return [ Result ] The result of running the test. - # - # @since 2.6.0 - def run - test_client.subscribe(Mongo::Monitoring::COMMAND, event_subscriber) - - results = @ops.map do |op| - op.execute(@collection) - end - - session0_id = @session0.session_id - session1_id = @session1.session_id - - @session0.end_session - @session1.end_session - - events = event_subscriber.started_events.map do |e| - - # Convert txnNumber field from a BSON integer to an extended JSON int64 - if e.command['txnNumber'] - e.command['txnNumber'] = { - '$numberLong' => e.command['txnNumber'].instance_variable_get(:@integer).to_s - } - end - - # Replace the session id placeholders with the actual session ids. - e.command['lsid'] = 'session0' if e.command['lsid'] == session0_id - e.command['lsid'] = 'session1' if e.command['lsid'] == session1_id - - - # The spec files don't include these fields, so we delete them. - e.command.delete('$readPreference') - e.command.delete('bypassDocumentValidation') - e.command.delete('$clusterTime') - - - if e.command['readConcern'] - # The spec test use an afterClusterTime value of 42 to indicate that we need to assert - # that the field exists in the actual read concern rather than comparing the value, so - # we replace any afterClusterTime value with 42. - if e.command['readConcern']['afterClusterTime'] - e.command['readConcern']['afterClusterTime'] = 42 - end - - # Convert the readConcern level from a symbol to a string. - if e.command['readConcern']['level'] - e.command['readConcern']['level'] = e.command['readConcern']['level'].to_s - end - end - - # This write concern is sent for some server topologies/configurations, but not all, so it - # doesn't appear in the expected events. - e.command.delete('writeConcern') if e.command['writeConcern'] == { 'w' => 2 } - - # The spec tests use 42 as a placeholder value for any getMore cursorId. - e.command['getMore'] = { '$numberLong' => '42' } if e.command['getMore'] - - # Remove fields if empty - e.command.delete('cursor') if e.command['cursor'] && e.command['cursor'].empty? - e.command.delete('filter') if e.command['filter'] && e.command['filter'].empty? - e.command.delete('query') if e.command['query'] && e.command['query'].empty? - - { - 'command_started_event' => { - 'command' => order_hash(e.command), - 'command_name' => e.command_name.to_s, - 'database_name' => e.database_name - } - } - end - - # Remove any events from authentication commands. - events.reject! { |c| c['command_started_event']['command_name'].start_with?('sasl') } - - if @fail_point - admin_support_client.command(configureFailPoint: 'failCommand', mode: 'off') - end - - events.map! do |event| - event['command_started_event'] = order_hash(event['command_started_event']) - end - - { - results: results, - contents: @collection.find.to_a, - events: events, - } - end - - def setup_test - begin - admin_support_client.command(killAllSessions: []) - rescue Mongo::Error - end - - coll = support_client[@spec.collection_name] - coll.database.drop - coll.with(write: { w: :majority }).drop - support_client.command(create: @spec.collection_name, writeConcern: { w: :majority }) - - coll.with(write: { w: :majority }).insert_many(@data) unless @data.empty? - admin_support_client.command(@fail_point) if @fail_point - - @collection = test_client[@spec.collection_name] - - @session0 = test_client.start_session(@session_options[:session0] || {}) - @session1 = test_client.start_session(@session_options[:session1] || {}) - - @ops = @operations.map do |op| - Operation.new(op, @session0, @session1) - end - end - - def teardown_test - if @admin_support_client - @admin_support_client.close - end - if @test_client - @test_client.close - end - end - - # The expected data in the collection as an outcome after running this test. - # - # @example Get the outcome collection data - # test.outcome_collection_data - # - # @return [ Array<Hash> ] The list of documents expected to be in the collection - # after running this test. - # - # @since 2.6.0 - def outcome_collection_data - @outcome['collection']['data'] if @outcome && @outcome['collection'] - end - - def order_hash(hash) - Hash[hash.to_a.sort] - end - - private - - def convert_client_options(client_options) - client_options.reduce({}) do |opts, kv| - case kv.first - when 'readConcernLevel' - kv = [:read_concern, { 'level' => kv.last }] - when 'readPreference' - kv = [:read, { 'mode' => kv.last }] - when 'w' - kv = [:write, { w: kv.last }] - else - kv[0] = camel_to_snake(kv[0]) - end - - opts.tap { |o| o[kv.first] = kv.last } - end - end - end - end -end +require 'support/transactions/spec' +require 'support/transactions/test'