spec/support/shared/session.rb in mongo-2.5.0.beta vs spec/support/shared/session.rb in mongo-2.5.0
- old
+ new
@@ -1,8 +1,8 @@
shared_examples 'an operation using a session' do
- describe 'operation execution', if: sessions_enabled? do
+ describe 'operation execution', if: test_sessions? do
context 'when the session is created from the same client used for the operation' do
let(:session) do
client.start_session
@@ -15,11 +15,11 @@
let!(:before_last_use) do
server_session.last_use
end
let!(:before_operation_time) do
- (session.instance_variable_get(:@operation_time) || 0)
+ (session.operation_time || 0)
end
let!(:operation_result) do
operation
end
@@ -31,28 +31,24 @@
it 'updates the last use value' do
expect(server_session.last_use).not_to eq(before_last_use)
end
it 'updates the operation time value' do
- expect(session.instance_variable_get(:@operation_time)).not_to eq(before_operation_time)
+ expect(session.operation_time).not_to eq(before_operation_time)
end
it 'does not close the session when the operation completes' do
expect(session.ended?).to be(false)
end
end
context 'when a session from another client is provided' do
let(:session) do
- client.start_session
+ authorized_client.with(read: { mode: :secondary }).start_session
end
- let(:client) do
- authorized_client.with(read: { mode: :secondary })
- end
-
let(:operation_result) do
operation
end
it 'raises an exception' do
@@ -85,21 +81,22 @@
end
end
shared_examples 'a failed operation using a session' do
- context 'when the operation fails', if: sessions_enabled? do
+ context 'when the operation fails', if: test_sessions? do
let!(:before_last_use) do
session.instance_variable_get(:@server_session).last_use
end
let!(:before_operation_time) do
- (session.instance_variable_get(:@operation_time) || 0)
+ (session.operation_time || 0)
end
let!(:operation_result) do
+ sleep 0.2
begin; failed_operation; rescue => e; e; end
end
let(:session) do
client.start_session
@@ -113,21 +110,469 @@
it 'updates the last use value' do
expect(session.instance_variable_get(:@server_session).last_use).not_to eq(before_last_use)
end
it 'updates the operation time value' do
- expect(session.instance_variable_get(:@operation_time)).not_to eq(before_operation_time)
+ expect(session.operation_time).not_to eq(before_operation_time)
end
end
end
+shared_examples 'a causally consistent client session with an unacknowledged write' do
+
+ context 'when an unacknowledged write is executed in the context of a causally consistent session', if: sessions_enabled? do
+
+ let(:session) do
+ client.start_session(causal_consistency: true)
+ end
+
+ it 'does not update the operation time of the session' do
+ operation
+ expect(session.operation_time).to be_nil
+ end
+ end
+end
+
+shared_examples 'an operation supporting causally consistent reads' do
+
+ let(:client) do
+ authorized_client.with(heartbeat_frequency: 100).tap do |cl|
+ cl.subscribe(Mongo::Monitoring::COMMAND, subscriber)
+ end
+ end
+
+ let(:subscriber) do
+ EventSubscriber.new
+ end
+
+ after do
+ client.close
+ end
+
+ context 'when connected to a standalone', if: sessions_enabled? && standalone? do
+
+ context 'when the collection specifies a read concern' do
+
+ let(:collection) do
+ client[TEST_COLL, read_concern: { level: 'majority' }]
+ end
+
+ context 'when the session has causal_consistency set to true' do
+
+ let(:session) do
+ client.start_session(causal_consistency: true)
+ end
+
+ it 'does not add the afterClusterTime to the read concern in the command' do
+ expect(command['readConcern']['afterClusterTime']).to be_nil
+ end
+ end
+
+ context 'when the session has causal_consistency set to false' do
+
+ let(:session) do
+ client.start_session(causal_consistency: false)
+ end
+
+ it 'does not add the afterClusterTime to the read concern in the command' do
+ expect(command['readConcern']['afterClusterTime']).to be_nil
+ end
+ end
+
+ context 'when the session has causal_consistency not set' do
+
+ let(:session) do
+ client.start_session
+ end
+
+ it 'does not add the afterClusterTime to the read concern in the command' do
+ expect(command['readConcern']['afterClusterTime']).to be_nil
+ end
+ end
+ end
+
+ context 'when the collection does not specify a read concern' do
+
+ let(:collection) do
+ client[TEST_COLL]
+ end
+
+ context 'when the session has causal_consistency set to true' do
+
+ let(:session) do
+ client.start_session(causal_consistency: true)
+ end
+
+ it 'does not include the read concern in the command' do
+ expect(command['readConcern']).to be_nil
+ end
+ end
+
+ context 'when the session has causal_consistency set to false' do
+
+ let(:session) do
+ client.start_session(causal_consistency: false)
+ end
+
+ it 'does not include the read concern in the command' do
+ expect(command['readConcern']).to be_nil
+ end
+ end
+
+ context 'when the session has causal_consistency not set' do
+
+ let(:session) do
+ client.start_session
+ end
+
+ it 'does not include the read concern in the command' do
+ expect(command['readConcern']).to be_nil
+ end
+ end
+ end
+ end
+
+ context 'when connected to replica set or sharded cluster', if: test_sessions? do
+
+ context 'when the collection specifies a read concern' do
+
+ let(:collection) do
+ client[TEST_COLL, read_concern: { level: 'majority' }]
+ end
+
+ context 'when the session has causal_consistency set to true' do
+
+ let(:session) do
+ client.start_session(causal_consistency: true)
+ end
+
+ context 'when the session has an operation time' do
+
+ before do
+ client.database.command({ ping: 1 }, session: session)
+ end
+
+ let!(:operation_time) do
+ session.operation_time
+ end
+
+ let(:expected_read_concern) do
+ BSON::Document.new(level: 'majority', afterClusterTime: operation_time)
+ end
+
+ it 'merges the afterClusterTime with the read concern in the command' do
+ expect(command['readConcern']).to eq(expected_read_concern)
+ end
+ end
+
+ context 'when the session does not have an operation time' do
+
+ let(:expected_read_concern) do
+ BSON::Document.new(level: 'majority')
+ end
+
+ it 'leaves the read concern document unchanged' do
+ expect(command['readConcern']).to eq(expected_read_concern)
+ end
+ end
+
+ context 'when the operation time is advanced' do
+
+ before do
+ session.advance_operation_time(operation_time)
+ end
+
+ let(:operation_time) do
+ BSON::Timestamp.new(0, 1)
+ end
+
+ let(:expected_read_concern) do
+ BSON::Document.new(level: 'majority', afterClusterTime: operation_time)
+ end
+
+ it 'merges the afterClusterTime with the new operation time and read concern in the command' do
+ expect(command['readConcern']).to eq(expected_read_concern)
+ end
+ end
+ end
+
+ context 'when the session has causal_consistency set to false' do
+
+ let(:session) do
+ client.start_session(causal_consistency: false)
+ end
+
+ context 'when the session does not have an operation time' do
+
+ let(:expected_read_concern) do
+ BSON::Document.new(level: 'majority')
+ end
+
+ it 'leaves the read concern document unchanged' do
+ expect(command['readConcern']).to eq(expected_read_concern)
+ end
+ end
+
+ context 'when the session has an operation time' do
+
+ before do
+ client.database.command({ ping: 1 }, session: session)
+ end
+
+ let(:expected_read_concern) do
+ BSON::Document.new(level: 'majority')
+ end
+
+ it 'leaves the read concern document unchanged' do
+ expect(command['readConcern']).to eq(expected_read_concern)
+ end
+ end
+
+ context 'when the operation time is advanced' do
+
+ before do
+ session.advance_operation_time(operation_time)
+ end
+
+ let(:operation_time) do
+ BSON::Timestamp.new(0, 1)
+ end
+
+ let(:expected_read_concern) do
+ BSON::Document.new(level: 'majority')
+ end
+
+ it 'leaves the read concern document unchanged' do
+ expect(command['readConcern']).to eq(expected_read_concern)
+ end
+ end
+ end
+
+ context 'when the session has causal_consistency not set' do
+
+ let(:session) do
+ client.start_session
+ end
+
+ context 'when the session does not have an operation time' do
+
+ let(:expected_read_concern) do
+ BSON::Document.new(level: 'majority')
+ end
+
+ it 'leaves the read concern document unchanged' do
+ expect(command['readConcern']).to eq(expected_read_concern)
+ end
+ end
+
+ context 'when the session has an operation time' do
+
+ before do
+ client.database.command({ ping: 1 }, session: session)
+ end
+
+ let!(:operation_time) do
+ session.operation_time
+ end
+
+ let(:expected_read_concern) do
+ BSON::Document.new(level: 'majority', afterClusterTime: operation_time)
+ end
+
+ it 'merges the afterClusterTime with the new operation time and read concern in the command' do
+ expect(command['readConcern']).to eq(expected_read_concern)
+ end
+ end
+
+ context 'when the operation time is advanced' do
+
+ before do
+ session.advance_operation_time(operation_time)
+ end
+
+ let(:operation_time) do
+ BSON::Timestamp.new(0, 1)
+ end
+
+ let(:expected_read_concern) do
+ BSON::Document.new(level: 'majority', afterClusterTime: operation_time)
+ end
+
+ it 'merges the afterClusterTime with the new operation time and read concern in the command' do
+ expect(command['readConcern']).to eq(expected_read_concern)
+ end
+ end
+ end
+ end
+
+ context 'when the collection does not specify a read concern' do
+
+ let(:collection) do
+ client[TEST_COLL]
+ end
+
+ context 'when the session has causal_consistency set to true' do
+
+ let(:session) do
+ client.start_session(causal_consistency: true)
+ end
+
+ context 'when the session does not have an operation time' do
+
+ it 'does not include the read concern in the command' do
+ expect(command['readConcern']).to be_nil
+ end
+ end
+
+ context 'when the session has an operation time' do
+
+ before do
+ client.database.command({ ping: 1 }, session: session)
+ end
+
+ let!(:operation_time) do
+ session.operation_time
+ end
+
+ let(:expected_read_concern) do
+ BSON::Document.new(afterClusterTime: operation_time)
+ end
+
+ it 'merges the afterClusterTime with the read concern in the command' do
+ expect(command['readConcern']).to eq(expected_read_concern)
+ end
+ end
+
+ context 'when the operation time is advanced' do
+
+ before do
+ session.advance_operation_time(operation_time)
+ end
+
+ let(:operation_time) do
+ BSON::Timestamp.new(0, 1)
+ end
+
+ let(:expected_read_concern) do
+ BSON::Document.new(afterClusterTime: operation_time)
+ end
+
+ it 'merges the afterClusterTime with the new operation time in the command' do
+ expect(command['readConcern']).to eq(expected_read_concern)
+ end
+ end
+ end
+
+ context 'when the session has causal_consistency set to false' do
+
+ let(:session) do
+ client.start_session(causal_consistency: false)
+ end
+
+ context 'when the session does not have an operation time' do
+
+ it 'does not include the read concern in the command' do
+ expect(command['readConcern']).to be_nil
+ end
+ end
+
+ context 'when the session has an operation time' do
+
+ before do
+ client.database.command({ ping: 1 }, session: session)
+ end
+
+ it 'does not include the read concern in the command' do
+ expect(command['readConcern']).to be_nil
+ end
+ end
+
+ context 'when the operation time is advanced' do
+
+ before do
+ session.advance_operation_time(operation_time)
+ end
+
+ let(:operation_time) do
+ BSON::Timestamp.new(0, 1)
+ end
+
+ let(:expected_read_concern) do
+ BSON::Document.new(afterClusterTime: operation_time)
+ end
+
+ it 'does not include the read concern in the command' do
+ expect(command['readConcern']).to be_nil
+ end
+ end
+ end
+
+ context 'when the session has causal_consistency not set' do
+
+ let(:session) do
+ client.start_session
+ end
+
+ context 'when the session does not have an operation time' do
+
+ it 'does not include the read concern in the command' do
+ expect(command['readConcern']).to be_nil
+ end
+ end
+
+ context 'when the session has an operation time' do
+
+ before do
+ client.database.command({ ping: 1 }, session: session)
+ end
+
+ let!(:operation_time) do
+ session.operation_time
+ end
+
+ let(:expected_read_concern) do
+ BSON::Document.new(afterClusterTime: operation_time)
+ end
+
+ it 'merges the afterClusterTime with the read concern in the command' do
+ expect(command['readConcern']).to eq(expected_read_concern)
+ end
+ end
+
+ context 'when the operation time is advanced' do
+
+ before do
+ session.advance_operation_time(operation_time)
+ end
+
+ let(:operation_time) do
+ BSON::Timestamp.new(0, 1)
+ end
+
+ let(:expected_read_concern) do
+ BSON::Document.new(afterClusterTime: operation_time)
+ end
+
+ it 'merges the afterClusterTime with the new operation time in the command' do
+ expect(command['readConcern']).to eq(expected_read_concern)
+ end
+ end
+ end
+ end
+ end
+end
+
shared_examples 'an operation updating cluster time' do
let(:cluster) do
client.cluster
end
+ let(:session) do
+ client.start_session
+ end
+
let(:client) do
authorized_client.with(heartbeat_frequency: 100).tap do |cl|
cl.subscribe(Mongo::Monitoring::COMMAND, subscriber)
end
end
@@ -142,36 +587,44 @@
context 'when the command is run once' do
context 'when the server is version 3.6' do
- context 'when the server is a mongos', if: (sharded? && sessions_enabled?) do
+ context 'when the cluster is sharded or a replica set', if: test_sessions? do
let!(:reply_cluster_time) do
- operation
+ operation_with_session
subscriber.succeeded_events[-1].reply['$clusterTime']
end
it 'updates the cluster time of the cluster' do
expect(cluster.cluster_time).to eq(reply_cluster_time)
end
+
+ it 'updates the cluster time of the session' do
+ expect(session.cluster_time).to eq(reply_cluster_time)
+ end
end
- context 'when the server is not a mongos', if: (!sharded? && sessions_enabled?) do
+ context 'when the server is a standalone', if: (standalone? && sessions_enabled?) do
let(:before_cluster_time) do
client.cluster.cluster_time
end
let!(:reply_cluster_time) do
- operation
+ operation_with_session
subscriber.succeeded_events[-1].reply['$clusterTime']
end
it 'does not update the cluster time of the cluster' do
expect(before_cluster_time).to eq(before_cluster_time)
end
+
+ it 'does not update the cluster time of the session' do
+ expect(session.cluster_time).to be_nil
+ end
end
end
context 'when the server is less than version 3.6', if: !sessions_enabled? do
@@ -191,29 +644,77 @@
end
context 'when the command is run twice' do
let!(:reply_cluster_time) do
- operation
+ operation_with_session
subscriber.succeeded_events[-1].reply['$clusterTime']
end
- let(:second_command_cluster_time) do
- second_operation
- subscriber.started_events[-1].command['$clusterTime']
- end
+ context 'when the cluster is sharded or a replica set', if: test_sessions? do
- context 'when the server is a mongos', if: (sharded? && sessions_enabled?) do
+ context 'when the session cluster time is advanced' do
- it 'includes the received cluster time in the second command' do
- expect(second_command_cluster_time).to eq(reply_cluster_time)
+ before do
+ session.advance_cluster_time(advanced_cluster_time)
+ end
+
+ let(:second_command_cluster_time) do
+ second_operation
+ subscriber.started_events[-1].command['$clusterTime']
+ end
+
+ context 'when the advanced cluster time is greater than the existing cluster time' do
+
+ let(:advanced_cluster_time) do
+ new_timestamp = BSON::Timestamp.new(reply_cluster_time[Mongo::Cluster::CLUSTER_TIME].seconds,
+ reply_cluster_time[Mongo::Cluster::CLUSTER_TIME].increment + 1)
+ new_cluster_time = reply_cluster_time.dup
+ new_cluster_time.merge(Mongo::Cluster::CLUSTER_TIME => new_timestamp)
+ end
+
+ it 'includes the advanced cluster time in the second command' do
+ expect(second_command_cluster_time).to eq(advanced_cluster_time)
+ end
+ end
+
+ context 'when the advanced cluster time is not greater than the existing cluster time' do
+
+ let(:advanced_cluster_time) do
+ new_timestamp = BSON::Timestamp.new(reply_cluster_time[Mongo::Cluster::CLUSTER_TIME].seconds,
+ reply_cluster_time[Mongo::Cluster::CLUSTER_TIME].increment - 1)
+ new_cluster_time = reply_cluster_time.dup
+ new_cluster_time.merge(Mongo::Cluster::CLUSTER_TIME => new_timestamp)
+ end
+
+ it 'does not advance the cluster time' do
+ expect(second_command_cluster_time).to eq(reply_cluster_time)
+ end
+ end
end
+
+ context 'when the session cluster time is not advanced' do
+
+ let(:second_command_cluster_time) do
+ second_operation
+ subscriber.started_events[-1].command['$clusterTime']
+ end
+
+ it 'includes the received cluster time in the second command' do
+ expect(second_command_cluster_time).to eq(reply_cluster_time)
+ end
+ end
end
- context 'when the server is not a mongos', if: (!sharded? && sessions_enabled?) do
+ context 'when the server is a standalone', if: (standalone? && sessions_enabled?) do
let(:before_cluster_time) do
client.cluster.cluster_time
+ end
+
+ let(:second_command_cluster_time) do
+ second_operation
+ subscriber.started_events[-1].command['$clusterTime']
end
it 'does not update the cluster time of the cluster' do
second_command_cluster_time
expect(before_cluster_time).to eq(before_cluster_time)