rubyrep 1.0.0
Sign up to get free protection for your applications and to get access to all the features.
- data/History.txt +4 -0
- data/License.txt +20 -0
- data/Manifest.txt +137 -0
- data/README.txt +37 -0
- data/Rakefile +30 -0
- data/bin/rubyrep +8 -0
- data/config/hoe.rb +72 -0
- data/config/mysql_config.rb +25 -0
- data/config/postgres_config.rb +21 -0
- data/config/proxied_test_config.rb +14 -0
- data/config/redmine_config.rb +17 -0
- data/config/rep_config.rb +20 -0
- data/config/requirements.rb +32 -0
- data/config/test_config.rb +20 -0
- data/lib/rubyrep/base_runner.rb +195 -0
- data/lib/rubyrep/command_runner.rb +144 -0
- data/lib/rubyrep/committers/buffered_committer.rb +140 -0
- data/lib/rubyrep/committers/committers.rb +146 -0
- data/lib/rubyrep/configuration.rb +240 -0
- data/lib/rubyrep/connection_extenders/connection_extenders.rb +133 -0
- data/lib/rubyrep/connection_extenders/jdbc_extender.rb +284 -0
- data/lib/rubyrep/connection_extenders/mysql_extender.rb +168 -0
- data/lib/rubyrep/connection_extenders/postgresql_extender.rb +261 -0
- data/lib/rubyrep/database_proxy.rb +52 -0
- data/lib/rubyrep/direct_table_scan.rb +75 -0
- data/lib/rubyrep/generate_runner.rb +105 -0
- data/lib/rubyrep/initializer.rb +39 -0
- data/lib/rubyrep/logged_change.rb +326 -0
- data/lib/rubyrep/proxied_table_scan.rb +171 -0
- data/lib/rubyrep/proxy_block_cursor.rb +145 -0
- data/lib/rubyrep/proxy_connection.rb +318 -0
- data/lib/rubyrep/proxy_cursor.rb +44 -0
- data/lib/rubyrep/proxy_row_cursor.rb +43 -0
- data/lib/rubyrep/proxy_runner.rb +89 -0
- data/lib/rubyrep/replication_difference.rb +91 -0
- data/lib/rubyrep/replication_extenders/mysql_replication.rb +271 -0
- data/lib/rubyrep/replication_extenders/postgresql_replication.rb +204 -0
- data/lib/rubyrep/replication_extenders/replication_extenders.rb +26 -0
- data/lib/rubyrep/replication_helper.rb +104 -0
- data/lib/rubyrep/replication_initializer.rb +307 -0
- data/lib/rubyrep/replication_run.rb +48 -0
- data/lib/rubyrep/replication_runner.rb +138 -0
- data/lib/rubyrep/replicators/replicators.rb +37 -0
- data/lib/rubyrep/replicators/two_way_replicator.rb +334 -0
- data/lib/rubyrep/scan_progress_printers/progress_bar.rb +65 -0
- data/lib/rubyrep/scan_progress_printers/scan_progress_printers.rb +65 -0
- data/lib/rubyrep/scan_report_printers/scan_detail_reporter.rb +111 -0
- data/lib/rubyrep/scan_report_printers/scan_report_printers.rb +67 -0
- data/lib/rubyrep/scan_report_printers/scan_summary_reporter.rb +75 -0
- data/lib/rubyrep/scan_runner.rb +25 -0
- data/lib/rubyrep/session.rb +177 -0
- data/lib/rubyrep/sync_helper.rb +111 -0
- data/lib/rubyrep/sync_runner.rb +31 -0
- data/lib/rubyrep/syncers/syncers.rb +112 -0
- data/lib/rubyrep/syncers/two_way_syncer.rb +174 -0
- data/lib/rubyrep/table_scan.rb +54 -0
- data/lib/rubyrep/table_scan_helper.rb +38 -0
- data/lib/rubyrep/table_sorter.rb +70 -0
- data/lib/rubyrep/table_spec_resolver.rb +136 -0
- data/lib/rubyrep/table_sync.rb +68 -0
- data/lib/rubyrep/trigger_mode_switcher.rb +63 -0
- data/lib/rubyrep/type_casting_cursor.rb +31 -0
- data/lib/rubyrep/uninstall_runner.rb +92 -0
- data/lib/rubyrep/version.rb +9 -0
- data/lib/rubyrep.rb +68 -0
- data/script/destroy +14 -0
- data/script/generate +14 -0
- data/script/txt2html +74 -0
- data/setup.rb +1585 -0
- data/sims/performance/big_rep_spec.rb +100 -0
- data/sims/performance/big_scan_spec.rb +57 -0
- data/sims/performance/big_sync_spec.rb +141 -0
- data/sims/performance/performance.rake +228 -0
- data/sims/sim_helper.rb +24 -0
- data/spec/base_runner_spec.rb +218 -0
- data/spec/buffered_committer_spec.rb +271 -0
- data/spec/command_runner_spec.rb +145 -0
- data/spec/committers_spec.rb +174 -0
- data/spec/configuration_spec.rb +198 -0
- data/spec/connection_extender_interface_spec.rb +138 -0
- data/spec/connection_extenders_registration_spec.rb +129 -0
- data/spec/database_proxy_spec.rb +48 -0
- data/spec/database_rake_spec.rb +40 -0
- data/spec/db_specific_connection_extenders_spec.rb +34 -0
- data/spec/db_specific_replication_extenders_spec.rb +38 -0
- data/spec/direct_table_scan_spec.rb +61 -0
- data/spec/generate_runner_spec.rb +84 -0
- data/spec/initializer_spec.rb +46 -0
- data/spec/logged_change_spec.rb +480 -0
- data/spec/postgresql_replication_spec.rb +48 -0
- data/spec/postgresql_support_spec.rb +57 -0
- data/spec/progress_bar_spec.rb +77 -0
- data/spec/proxied_table_scan_spec.rb +151 -0
- data/spec/proxy_block_cursor_spec.rb +197 -0
- data/spec/proxy_connection_spec.rb +399 -0
- data/spec/proxy_cursor_spec.rb +56 -0
- data/spec/proxy_row_cursor_spec.rb +66 -0
- data/spec/proxy_runner_spec.rb +70 -0
- data/spec/replication_difference_spec.rb +160 -0
- data/spec/replication_extender_interface_spec.rb +365 -0
- data/spec/replication_extenders_spec.rb +32 -0
- data/spec/replication_helper_spec.rb +121 -0
- data/spec/replication_initializer_spec.rb +477 -0
- data/spec/replication_run_spec.rb +166 -0
- data/spec/replication_runner_spec.rb +213 -0
- data/spec/replicators_spec.rb +31 -0
- data/spec/rubyrep_spec.rb +8 -0
- data/spec/scan_detail_reporter_spec.rb +119 -0
- data/spec/scan_progress_printers_spec.rb +68 -0
- data/spec/scan_report_printers_spec.rb +67 -0
- data/spec/scan_runner_spec.rb +50 -0
- data/spec/scan_summary_reporter_spec.rb +61 -0
- data/spec/session_spec.rb +212 -0
- data/spec/spec.opts +1 -0
- data/spec/spec_helper.rb +295 -0
- data/spec/sync_helper_spec.rb +157 -0
- data/spec/sync_runner_spec.rb +78 -0
- data/spec/syncers_spec.rb +171 -0
- data/spec/table_scan_helper_spec.rb +29 -0
- data/spec/table_scan_spec.rb +49 -0
- data/spec/table_sorter_spec.rb +31 -0
- data/spec/table_spec_resolver_spec.rb +102 -0
- data/spec/table_sync_spec.rb +84 -0
- data/spec/trigger_mode_switcher_spec.rb +83 -0
- data/spec/two_way_replicator_spec.rb +551 -0
- data/spec/two_way_syncer_spec.rb +256 -0
- data/spec/type_casting_cursor_spec.rb +50 -0
- data/spec/uninstall_runner_spec.rb +86 -0
- data/tasks/database.rake +439 -0
- data/tasks/deployment.rake +29 -0
- data/tasks/environment.rake +9 -0
- data/tasks/java.rake +37 -0
- data/tasks/redmine_test.rake +47 -0
- data/tasks/rspec.rake +68 -0
- data/tasks/rubyrep.tailor +18 -0
- data/tasks/stats.rake +19 -0
- data/tasks/task_helper.rb +20 -0
- data.tar.gz.sig +0 -0
- metadata +243 -0
- metadata.gz.sig +0 -0
@@ -0,0 +1,212 @@
|
|
1
|
+
require File.dirname(__FILE__) + '/spec_helper.rb'
|
2
|
+
require 'yaml'
|
3
|
+
|
4
|
+
include RR
|
5
|
+
|
6
|
+
describe Session do # database connection caching is disabled
|
7
|
+
before(:each) do
|
8
|
+
Initializer.configuration = standard_config
|
9
|
+
@@old_cache_status = ConnectionExtenders.use_db_connection_cache(false)
|
10
|
+
end
|
11
|
+
|
12
|
+
after(:each) do
|
13
|
+
ConnectionExtenders.use_db_connection_cache(@@old_cache_status)
|
14
|
+
end
|
15
|
+
|
16
|
+
it "initialize should keep a reference of the Configuration object" do
|
17
|
+
mock_active_record :twice
|
18
|
+
|
19
|
+
session = Session.new(Initializer.configuration)
|
20
|
+
session.configuration.should == Initializer.configuration
|
21
|
+
end
|
22
|
+
|
23
|
+
it "initialize should establish the database connections" do
|
24
|
+
mock_active_record :twice
|
25
|
+
|
26
|
+
session = Session.new
|
27
|
+
end
|
28
|
+
|
29
|
+
it "'left=' should store a Connection object and 'left' should return it" do
|
30
|
+
mock_active_record :twice
|
31
|
+
|
32
|
+
session = Session.new
|
33
|
+
|
34
|
+
session.left = :dummy
|
35
|
+
session.left.should == :dummy
|
36
|
+
end
|
37
|
+
|
38
|
+
it "'right=' should store a Connection object and 'right' should return it" do
|
39
|
+
mock_active_record :twice
|
40
|
+
|
41
|
+
session = Session.new
|
42
|
+
|
43
|
+
session.right = :dummy
|
44
|
+
session.right.should == :dummy
|
45
|
+
end
|
46
|
+
|
47
|
+
it "initialize shouldn't create the same database connection twice" do
|
48
|
+
mock_active_record :once
|
49
|
+
|
50
|
+
Initializer.configuration = deep_copy(Initializer.configuration)
|
51
|
+
Initializer.configuration.right = Initializer.configuration.left.clone
|
52
|
+
|
53
|
+
session = Session.new
|
54
|
+
end
|
55
|
+
end
|
56
|
+
|
57
|
+
describe Session do # here database connection caching is _not_ disabled
|
58
|
+
before(:each) do
|
59
|
+
Initializer.configuration = standard_config
|
60
|
+
end
|
61
|
+
|
62
|
+
after(:each) do
|
63
|
+
end
|
64
|
+
|
65
|
+
it "initialize should create (fake) proxy connections as per configuration" do
|
66
|
+
dummy_proxy = Object.new
|
67
|
+
dummy_connection = mock("dummy connection")
|
68
|
+
dummy_connection.stub!(:tables).and_return([])
|
69
|
+
dummy_connection.stub!(:manual_primary_keys=)
|
70
|
+
dummy_proxy.should_receive(:create_session).and_return(dummy_connection)
|
71
|
+
DRbObject.should_receive(:new).with(nil,"druby://localhost:9876").and_return(dummy_proxy)
|
72
|
+
|
73
|
+
session = Session.new proxied_config
|
74
|
+
|
75
|
+
session.proxies[:left].should == dummy_proxy
|
76
|
+
session.proxies[:right].should be_an_instance_of(DatabaseProxy)
|
77
|
+
|
78
|
+
session.left.should == dummy_connection
|
79
|
+
session.right.should be_an_instance_of(ProxyConnection)
|
80
|
+
end
|
81
|
+
|
82
|
+
it "initialize should assign manual primary keys to the proxy connections" do
|
83
|
+
config = deep_copy(standard_config)
|
84
|
+
config.included_table_specs.clear
|
85
|
+
config.include_tables "table_with_manual_key, extender_without_key", :primary_key_names => ['id']
|
86
|
+
session = Session.new config
|
87
|
+
session.left.manual_primary_keys.should == {'table_with_manual_key'=>['id']}
|
88
|
+
session.right.manual_primary_keys.should == {'extender_without_key'=>['id']}
|
89
|
+
end
|
90
|
+
|
91
|
+
it "refresh should reestablsih the database connections if no more active" do
|
92
|
+
session = Session.new
|
93
|
+
session.right.destroy
|
94
|
+
session.right.connection.should_not be_active
|
95
|
+
session.refresh
|
96
|
+
session.right.connection.should be_active
|
97
|
+
end
|
98
|
+
|
99
|
+
it "refresh should not do anyting if the connection is still active" do
|
100
|
+
session = Session.new
|
101
|
+
old_connection_id = session.right.connection.object_id
|
102
|
+
session.refresh
|
103
|
+
session.right.connection.object_id.should == old_connection_id
|
104
|
+
end
|
105
|
+
|
106
|
+
it "manual_primary_keys should return the specified manual primary keys" do
|
107
|
+
config = deep_copy(standard_config)
|
108
|
+
config.included_table_specs.clear
|
109
|
+
config.include_tables "table_with_manual_key, extender_without_key", :key => ['id']
|
110
|
+
session = Session.new config
|
111
|
+
session.manual_primary_keys(:left).should == {'table_with_manual_key'=>['id']}
|
112
|
+
session.manual_primary_keys(:right).should == {'extender_without_key'=>['id']}
|
113
|
+
end
|
114
|
+
|
115
|
+
it "manual_primary_keys should accept keys that are not packed into an array" do
|
116
|
+
config = deep_copy(standard_config)
|
117
|
+
config.included_table_specs.clear
|
118
|
+
config.include_tables "table_with_manual_key", :key => 'id'
|
119
|
+
session = Session.new config
|
120
|
+
session.manual_primary_keys(:left).should == {'table_with_manual_key'=>['id']}
|
121
|
+
end
|
122
|
+
|
123
|
+
it "manual_primary_keys should follow the :auto_key_limit option" do
|
124
|
+
config = deep_copy(standard_config)
|
125
|
+
config.included_table_specs.clear
|
126
|
+
config.include_tables "scanner_records"
|
127
|
+
config.include_tables "extender_without_key"
|
128
|
+
config.include_tables "table_with_manual_key", :key => 'id'
|
129
|
+
|
130
|
+
config.options[:auto_key_limit] = 2
|
131
|
+
session = Session.new config
|
132
|
+
session.manual_primary_keys(:left).should == {
|
133
|
+
'table_with_manual_key' => ['id'],
|
134
|
+
'extender_without_key' => ['first_id', 'second_id']
|
135
|
+
}
|
136
|
+
session.left.primary_key_names('extender_without_key').should == ['first_id', 'second_id']
|
137
|
+
|
138
|
+
config.options[:auto_key_limit] = 1
|
139
|
+
session = Session.new config
|
140
|
+
session.manual_primary_keys(:left).should == {
|
141
|
+
'table_with_manual_key' => ['id']
|
142
|
+
}
|
143
|
+
end
|
144
|
+
|
145
|
+
it "corresponding_table should return the correct corresponding table" do
|
146
|
+
config = deep_copy(standard_config)
|
147
|
+
config.included_table_specs.clear
|
148
|
+
config.include_tables "/scanner/"
|
149
|
+
config.include_tables "table_with_manual_key, extender_without_key"
|
150
|
+
session = Session.new config
|
151
|
+
|
152
|
+
session.corresponding_table(:left, 'scanner_records').should == 'scanner_records'
|
153
|
+
session.corresponding_table(:right, 'scanner_records').should == 'scanner_records'
|
154
|
+
session.corresponding_table(:left, 'table_with_manual_key').should == 'extender_without_key'
|
155
|
+
session.corresponding_table(:right, 'extender_without_key').should == 'table_with_manual_key'
|
156
|
+
end
|
157
|
+
|
158
|
+
it "corresponding_table should return the given table if no corresponding table can be found" do
|
159
|
+
session = Session.new
|
160
|
+
session.corresponding_table(:left, 'not_existing_table').should == 'not_existing_table'
|
161
|
+
end
|
162
|
+
|
163
|
+
it "configured_table_pairs should return the table pairs as per included_table_specs parameter" do
|
164
|
+
session = Session.new
|
165
|
+
session.configured_table_pairs(['scanner_records']).should == [
|
166
|
+
{:left => 'scanner_records', :right => 'scanner_records'},
|
167
|
+
]
|
168
|
+
end
|
169
|
+
|
170
|
+
it "configured_table_pairs should return the table pairs as per configuration if included_table_specs paramter is an empty array" do
|
171
|
+
session = Session.new
|
172
|
+
session.configured_table_pairs([]).should == [
|
173
|
+
{:left => 'scanner_left_records_only', :right => 'scanner_left_records_only'},
|
174
|
+
{:left => 'table_with_manual_key', :right => 'table_with_manual_key'}
|
175
|
+
]
|
176
|
+
end
|
177
|
+
|
178
|
+
def convert_table_array_to_table_pair_array(tables)
|
179
|
+
tables.map {|table| {:left => table, :right => table}}
|
180
|
+
end
|
181
|
+
|
182
|
+
it "sort_table_pairs should sort the tables correctly" do
|
183
|
+
table_pairs = convert_table_array_to_table_pair_array([
|
184
|
+
'scanner_records',
|
185
|
+
'referencing_table',
|
186
|
+
'referenced_table',
|
187
|
+
'scanner_text_key',
|
188
|
+
])
|
189
|
+
sorted_table_pairs = convert_table_array_to_table_pair_array([
|
190
|
+
'scanner_records',
|
191
|
+
'referenced_table',
|
192
|
+
'referencing_table',
|
193
|
+
'scanner_text_key',
|
194
|
+
])
|
195
|
+
|
196
|
+
Session.new.sort_table_pairs(table_pairs).should == sorted_table_pairs
|
197
|
+
end
|
198
|
+
|
199
|
+
it "sort_table_pairs should not sort the tables if table_ordering is not enabled in the configuration" do
|
200
|
+
table_pairs = convert_table_array_to_table_pair_array([
|
201
|
+
'scanner_records',
|
202
|
+
'referencing_table',
|
203
|
+
'referenced_table',
|
204
|
+
'scanner_text_key',
|
205
|
+
])
|
206
|
+
config = deep_copy(standard_config)
|
207
|
+
config.options[:table_ordering] = false
|
208
|
+
session = Session.new config
|
209
|
+
session.sort_table_pairs(table_pairs).should == table_pairs
|
210
|
+
end
|
211
|
+
end
|
212
|
+
|
data/spec/spec.opts
ADDED
@@ -0,0 +1 @@
|
|
1
|
+
--colour
|
data/spec/spec_helper.rb
ADDED
@@ -0,0 +1,295 @@
|
|
1
|
+
begin
|
2
|
+
require 'spec'
|
3
|
+
rescue LoadError
|
4
|
+
require 'rubygems'
|
5
|
+
gem 'rspec'
|
6
|
+
require 'spec'
|
7
|
+
end
|
8
|
+
|
9
|
+
require 'drb'
|
10
|
+
require 'digest/sha1'
|
11
|
+
|
12
|
+
$LOAD_PATH.unshift File.join(File.dirname(__FILE__), "..", "lib")
|
13
|
+
$LOAD_PATH.unshift File.dirname(__FILE__)
|
14
|
+
|
15
|
+
require 'rubyrep'
|
16
|
+
require 'connection_extender_interface_spec'
|
17
|
+
|
18
|
+
class Module
|
19
|
+
# Used to verify that an instance of the class / module receives a call of the
|
20
|
+
# specified method.
|
21
|
+
# This is for cases where a method call has to be mocked of an object that is
|
22
|
+
# not yet created.
|
23
|
+
# (Couldn't find out how to do that using existing rspec mocking features.)
|
24
|
+
def any_instance_should_receive(method, &blck)
|
25
|
+
tmp_method = "original_before_mocking_#{method}".to_sym
|
26
|
+
logger_key = "#{self.name}_#{method}"
|
27
|
+
$mock_method_marker ||= {}
|
28
|
+
$mock_method_marker[logger_key] = Spec::Mocks::Mock.new("#{name} Instance")
|
29
|
+
$mock_method_marker[logger_key].should_receive(method).at_least(:once)
|
30
|
+
self.send :alias_method, tmp_method, method
|
31
|
+
self.class_eval "def #{method}(*args); $mock_method_marker['#{logger_key}'].#{method}; end"
|
32
|
+
blck.call
|
33
|
+
ensure
|
34
|
+
$mock_method_marker.delete logger_key
|
35
|
+
self.send :alias_method, method, tmp_method rescue nil
|
36
|
+
end
|
37
|
+
|
38
|
+
# Used to verify that an instance of the class / module does not receive a
|
39
|
+
# call of the specified method.
|
40
|
+
# This is for cases where a method call has to be mocked of an object that is
|
41
|
+
# not yet created.
|
42
|
+
# (Couldn't find out how to do that using existing rspec mocking features.)
|
43
|
+
def any_instance_should_not_receive(method, &blck)
|
44
|
+
tmp_method = "original_before_mocking_#{method}".to_sym
|
45
|
+
logger_key = "#{self.name}_#{method}"
|
46
|
+
$mock_method_marker ||= {}
|
47
|
+
$mock_method_marker[logger_key] = Spec::Mocks::Mock.new("#{name} Instance")
|
48
|
+
$mock_method_marker[logger_key].should_not_receive(method)
|
49
|
+
self.send :alias_method, tmp_method, method
|
50
|
+
self.class_eval "def #{method}(*args); $mock_method_marker['#{logger_key}'].#{method}; end"
|
51
|
+
blck.call
|
52
|
+
ensure
|
53
|
+
$mock_method_marker.delete logger_key
|
54
|
+
self.send :alias_method, method, tmp_method rescue nil
|
55
|
+
end
|
56
|
+
end
|
57
|
+
|
58
|
+
class RR::Session
|
59
|
+
# To keep rspec output of failed tests managable
|
60
|
+
def inspect; 'session'; end
|
61
|
+
end
|
62
|
+
|
63
|
+
class ActiveRecord::Base
|
64
|
+
class << self
|
65
|
+
# Hack:
|
66
|
+
# The default inspect method (as per activerecord version 2.2.2) tries to
|
67
|
+
# send commands to the database.
|
68
|
+
# This leads to rcov failing.
|
69
|
+
# As workaround this is disabling the attempts to connect to the database.
|
70
|
+
def inspect
|
71
|
+
super
|
72
|
+
end
|
73
|
+
end
|
74
|
+
end
|
75
|
+
|
76
|
+
# If number_of_calls is :once, mock ActiveRecord for 1 call.
|
77
|
+
# If number_of_calls is :twice, mock ActiveRecord for 2 calls.
|
78
|
+
def mock_active_record(number_of_calls)
|
79
|
+
ConnectionExtenders::DummyActiveRecord.should_receive(:establish_connection).send(number_of_calls) \
|
80
|
+
.and_return {|config| $used_config = config}
|
81
|
+
|
82
|
+
dummy_connection = Object.new
|
83
|
+
# We have a spec testing behaviour for non-existing extenders.
|
84
|
+
# So extend might not be called in all cases
|
85
|
+
dummy_connection.stub!(:extend)
|
86
|
+
dummy_connection.stub!(:tables).and_return([])
|
87
|
+
dummy_connection.stub!(:initialize_search_path)
|
88
|
+
|
89
|
+
ConnectionExtenders::DummyActiveRecord.should_receive(:connection).send(number_of_calls) \
|
90
|
+
.and_return {dummy_connection}
|
91
|
+
end
|
92
|
+
|
93
|
+
# Creates a mock ProxyConnection with the given
|
94
|
+
# * mock_table: name of the mock table
|
95
|
+
# * primary_key_names: array of mock primary column names
|
96
|
+
# * column_names: array of mock column names, if nil: doesn't mock this function
|
97
|
+
def create_mock_proxy_connection(mock_table, primary_key_names, column_names = nil)
|
98
|
+
session = mock("ProxyConnection")
|
99
|
+
if primary_key_names
|
100
|
+
session.should_receive(:primary_key_names) \
|
101
|
+
.with(mock_table) \
|
102
|
+
.and_return(primary_key_names)
|
103
|
+
end
|
104
|
+
if column_names
|
105
|
+
session.should_receive(:column_names) \
|
106
|
+
.with(mock_table) \
|
107
|
+
.and_return(column_names)
|
108
|
+
end
|
109
|
+
session.should_receive(:quote_value) \
|
110
|
+
.any_number_of_times \
|
111
|
+
.with(an_instance_of(String), an_instance_of(String), anything) \
|
112
|
+
.and_return { |table, column, value| value}
|
113
|
+
|
114
|
+
session.should_receive(:connection) \
|
115
|
+
.any_number_of_times \
|
116
|
+
.and_return {dummy_connection}
|
117
|
+
|
118
|
+
session.should_receive(:quote_column_name) \
|
119
|
+
.any_number_of_times \
|
120
|
+
.with(an_instance_of(String)) \
|
121
|
+
.and_return { |column_name| "'#{column_name}'" }
|
122
|
+
|
123
|
+
session.should_receive(:quote_table_name) \
|
124
|
+
.any_number_of_times \
|
125
|
+
.with(an_instance_of(String)) \
|
126
|
+
.and_return { |table_name| "'#{table_name}'" }
|
127
|
+
|
128
|
+
session
|
129
|
+
end
|
130
|
+
|
131
|
+
# Turns an SQL query into a regular expression:
|
132
|
+
# * Handles quotes (differing depending on DBMS).
|
133
|
+
# * Handles round brackets (escaping with backslash to make them literals).
|
134
|
+
# * Removes line breaks and double spaces
|
135
|
+
# (allowing use of intendation and line continuation)
|
136
|
+
# Returns the regular expression created from the provided +sql+ string.
|
137
|
+
def sql_to_regexp(sql)
|
138
|
+
Regexp.new(sql.strip.squeeze(" ") \
|
139
|
+
.gsub("(", "\\(").gsub(")", "\\)") \
|
140
|
+
.gsub("'", 'E?.') \
|
141
|
+
.gsub('"', 'E?.'))
|
142
|
+
end
|
143
|
+
|
144
|
+
# Returns a deep copy of the provided object. Works also for Proc objects or
|
145
|
+
# objects referencing Proc objects.
|
146
|
+
def deep_copy(object)
|
147
|
+
Proc.send :define_method, :_dump, lambda { |depth|
|
148
|
+
@@proc_store ||= {}
|
149
|
+
@@proc_key ||= "000000000"
|
150
|
+
@@proc_key.succ!
|
151
|
+
@@proc_store[@@proc_key] = self
|
152
|
+
@@proc_key
|
153
|
+
}
|
154
|
+
Proc.class.send :define_method, :_load, lambda { |key|
|
155
|
+
proc = @@proc_store[key]
|
156
|
+
@@proc_store.delete key
|
157
|
+
proc
|
158
|
+
}
|
159
|
+
|
160
|
+
Marshal.restore(Marshal.dump(object))
|
161
|
+
ensure
|
162
|
+
Proc.send :remove_method, :_dump if Proc.method_defined? :_dump
|
163
|
+
Proc.class.send :remove_method, :_load if Proc.class.method_defined? :_load
|
164
|
+
end
|
165
|
+
|
166
|
+
# Allows the temporary faking of RUBY_PLATFORM to the given value
|
167
|
+
# Needs to be called with a block. While the block is executed, RUBY_PLATFORM
|
168
|
+
# is set to the given fake value
|
169
|
+
def fake_ruby_platform(fake_ruby_platform)
|
170
|
+
old_ruby_platform = RUBY_PLATFORM
|
171
|
+
old_verbose, $VERBOSE = $VERBOSE, nil
|
172
|
+
Object.const_set 'RUBY_PLATFORM', fake_ruby_platform
|
173
|
+
$VERBOSE = old_verbose
|
174
|
+
yield
|
175
|
+
ensure
|
176
|
+
$VERBOSE = nil
|
177
|
+
Object.const_set 'RUBY_PLATFORM', old_ruby_platform
|
178
|
+
$VERBOSE = old_verbose
|
179
|
+
end
|
180
|
+
|
181
|
+
# Reads the database configuration from the config folder for the specified config key
|
182
|
+
# E.g. if config is :postgres, tries to read the config from 'postgres_config.rb'
|
183
|
+
def read_config(config)
|
184
|
+
$config_cache ||= {}
|
185
|
+
cache_key = "#{config.to_s}_#{ENV['RR_TEST_DB']}"
|
186
|
+
unless $config_cache[cache_key]
|
187
|
+
# load the proxied config but ensure that the original configuration is restored
|
188
|
+
old_config = RR::Initializer.configuration
|
189
|
+
RR::Initializer.reset
|
190
|
+
begin
|
191
|
+
load File.dirname(__FILE__) + "/../config/#{config}_config.rb"
|
192
|
+
$config_cache[cache_key] = RR::Initializer.configuration
|
193
|
+
ensure
|
194
|
+
RR::Initializer.configuration = old_config
|
195
|
+
end
|
196
|
+
end
|
197
|
+
$config_cache[cache_key]
|
198
|
+
end
|
199
|
+
|
200
|
+
# Removes all cached database configurations
|
201
|
+
def clear_config_cache
|
202
|
+
$config_cache = {}
|
203
|
+
end
|
204
|
+
|
205
|
+
# Retrieves the proxied database config as specified in config/proxied_test_config.rb
|
206
|
+
def proxied_config
|
207
|
+
read_config :proxied_test
|
208
|
+
end
|
209
|
+
|
210
|
+
# Retrieves the standard (non-proxied) database config as specified in config/test_config.rb
|
211
|
+
def standard_config
|
212
|
+
read_config :test
|
213
|
+
end
|
214
|
+
|
215
|
+
# Inserts two records into 'sequence_test' and returns the generated id values
|
216
|
+
# * session: the active Session
|
217
|
+
# * table: name of the table which is to be tested
|
218
|
+
def get_example_sequence_values(session, table = 'sequence_test')
|
219
|
+
session.left.insert_record table, { 'name' => 'bla' }
|
220
|
+
id1 = session.left.select_one("select max(id) as id from #{table}")['id'].to_i
|
221
|
+
session.left.insert_record table, { 'name' => 'blub' }
|
222
|
+
id2 = session.left.select_one("select max(id) as id from #{table}")['id'].to_i
|
223
|
+
return id1, id2
|
224
|
+
end
|
225
|
+
|
226
|
+
# If true, start proxy as external process (more realistic test but also slower).
|
227
|
+
# Otherwise start in the current process as thread.
|
228
|
+
$start_proxy_as_external_process ||= false
|
229
|
+
|
230
|
+
# Starts a proxy under the given host and post
|
231
|
+
def start_proxy(host, port)
|
232
|
+
if $start_proxy_as_external_process
|
233
|
+
rrproxy_path = File.join(File.dirname(__FILE__), "..", "bin", "rrproxy.rb")
|
234
|
+
ruby = RUBY_PLATFORM =~ /java/ ? 'jruby' : 'ruby'
|
235
|
+
cmd = "#{ruby} #{rrproxy_path} -h #{host} -p #{port}"
|
236
|
+
Thread.new {system cmd}
|
237
|
+
else
|
238
|
+
url = "druby://#{host}:#{port}"
|
239
|
+
DRb.start_service(url, DatabaseProxy.new)
|
240
|
+
end
|
241
|
+
end
|
242
|
+
|
243
|
+
# Set to true if the proxy as per SPEC_PROXY_CONFIG is running
|
244
|
+
$proxy_confirmed_running = false
|
245
|
+
|
246
|
+
# Starts a proxy as per left proxy settings defined in config/proxied_test_config.rb.
|
247
|
+
# Only starts the proxy though if none is running yet at the according host / port.
|
248
|
+
# If it starts a proxy child process, it also prepares automatic termination
|
249
|
+
# after the spec run is finished.
|
250
|
+
def ensure_proxy
|
251
|
+
# only execute the network verification once per spec run
|
252
|
+
unless $proxy_confirmed_running
|
253
|
+
drb_url = "druby://#{proxied_config.left[:proxy_host]}:#{proxied_config.left[:proxy_port]}"
|
254
|
+
# try to connect to the proxy
|
255
|
+
begin
|
256
|
+
proxy = DRbObject.new nil, drb_url
|
257
|
+
proxy.ping
|
258
|
+
$proxy_confirmed_running = true
|
259
|
+
rescue DRb::DRbConnError => e
|
260
|
+
# Proxy not yet running ==> start it
|
261
|
+
start_proxy proxied_config.left[:proxy_host], proxied_config.left[:proxy_port]
|
262
|
+
|
263
|
+
maximum_startup_time = 5 # maximum time in seconds for the proxy to start
|
264
|
+
waiting_time = 0.1 # time to wait between connection attempts
|
265
|
+
|
266
|
+
time = 0.0
|
267
|
+
ping_response = ''
|
268
|
+
# wait for the proxy to start up and become operational
|
269
|
+
while ping_response != 'pong' and time < maximum_startup_time
|
270
|
+
begin
|
271
|
+
proxy = DRbObject.new nil, drb_url
|
272
|
+
ping_response = proxy.ping
|
273
|
+
break
|
274
|
+
rescue DRb::DRbConnError => e
|
275
|
+
# do nothing (just try again)
|
276
|
+
end
|
277
|
+
sleep waiting_time
|
278
|
+
time += waiting_time
|
279
|
+
end
|
280
|
+
if ping_response == 'pong'
|
281
|
+
#puts "Proxy started (took #{time} seconds)"
|
282
|
+
# Ensure that the started proxy is terminated with the completion of the spec run.
|
283
|
+
at_exit do
|
284
|
+
proxy = DRbObject.new nil, drb_url
|
285
|
+
proxy.terminate! rescue DRb::DRbConnError
|
286
|
+
end if $start_proxy_as_external_process
|
287
|
+
else
|
288
|
+
raise "Could not start proxy"
|
289
|
+
end
|
290
|
+
end
|
291
|
+
|
292
|
+
# if we got till here, then a proxy is running or was successfully started
|
293
|
+
$proxy_confirmed_running = true
|
294
|
+
end
|
295
|
+
end
|
@@ -0,0 +1,157 @@
|
|
1
|
+
require File.dirname(__FILE__) + '/spec_helper.rb'
|
2
|
+
|
3
|
+
include RR
|
4
|
+
|
5
|
+
describe SyncHelper do
|
6
|
+
before(:each) do
|
7
|
+
Initializer.configuration = standard_config
|
8
|
+
end
|
9
|
+
|
10
|
+
it "initialize should initialize the correct committer" do
|
11
|
+
sync = TableSync.new(Session.new, 'scanner_records')
|
12
|
+
helper = SyncHelper.new(sync)
|
13
|
+
c = helper.instance_eval {committer}
|
14
|
+
c.should be_an_instance_of(Committers::DefaultCommitter)
|
15
|
+
c.session.should == helper.session
|
16
|
+
end
|
17
|
+
|
18
|
+
it "session should return the session" do
|
19
|
+
sync = TableSync.new(Session.new, 'scanner_records')
|
20
|
+
helper = SyncHelper.new(sync)
|
21
|
+
helper.session.should == sync.session
|
22
|
+
end
|
23
|
+
|
24
|
+
it "ensure_event_log should ask the replication_initializer to ensure the event log" do
|
25
|
+
sync = TableSync.new(Session.new, 'scanner_records')
|
26
|
+
helper = SyncHelper.new(sync)
|
27
|
+
ReplicationInitializer.any_instance_should_receive(:ensure_event_log) do
|
28
|
+
helper.ensure_event_log
|
29
|
+
end
|
30
|
+
end
|
31
|
+
|
32
|
+
it "log_sync_outcome should log the replication outcome correctly" do
|
33
|
+
session = Session.new
|
34
|
+
session.left.begin_db_transaction
|
35
|
+
begin
|
36
|
+
sync = TableSync.new(Session.new, 'scanner_records')
|
37
|
+
helper = SyncHelper.new(sync)
|
38
|
+
|
39
|
+
helper.log_sync_outcome(
|
40
|
+
{'bla' => 'blub', 'id' => 1},
|
41
|
+
'my_sync_type',
|
42
|
+
'my_outcome',
|
43
|
+
'my_long_description'
|
44
|
+
)
|
45
|
+
|
46
|
+
row = session.left.select_one("select * from rr_logged_events order by id desc")
|
47
|
+
row['activity'].should == 'sync'
|
48
|
+
row['change_table'].should == 'scanner_records'
|
49
|
+
row['diff_type'].should == 'my_sync_type'
|
50
|
+
row['change_key'].should == '1'
|
51
|
+
row['left_change_type'].should be_nil
|
52
|
+
row['right_change_type'].should be_nil
|
53
|
+
row['description'].should == 'my_outcome'
|
54
|
+
row['long_description'].should == 'my_long_description'
|
55
|
+
Time.parse(row['event_time']).should >= 10.seconds.ago
|
56
|
+
row['diff_dump'].should == nil
|
57
|
+
ensure
|
58
|
+
session.left.rollback_db_transaction if session
|
59
|
+
end
|
60
|
+
end
|
61
|
+
|
62
|
+
it "log_sync_outcome should log events for combined primary key tables correctly" do
|
63
|
+
session = Session.new
|
64
|
+
session.left.begin_db_transaction
|
65
|
+
begin
|
66
|
+
sync = TableSync.new(Session.new, 'extender_combined_key')
|
67
|
+
helper = SyncHelper.new(sync)
|
68
|
+
|
69
|
+
helper.log_sync_outcome(
|
70
|
+
{'bla' => 'blub', 'first_id' => 1, 'second_id' => 2},
|
71
|
+
'my_sync_type',
|
72
|
+
'my_outcome',
|
73
|
+
'my_long_description'
|
74
|
+
)
|
75
|
+
|
76
|
+
row = session.left.select_one("select * from rr_logged_events order by id desc")
|
77
|
+
row['change_key'].should == '"first_id"=>"1", "second_id"=>"2"'
|
78
|
+
ensure
|
79
|
+
session.left.rollback_db_transaction if session
|
80
|
+
end
|
81
|
+
end
|
82
|
+
|
83
|
+
it "left_table and right_table should return the correct table names" do
|
84
|
+
sync = TableSync.new(Session.new, 'scanner_records')
|
85
|
+
helper = SyncHelper.new(sync)
|
86
|
+
helper.left_table.should == 'scanner_records'
|
87
|
+
helper.right_table.should == 'scanner_records'
|
88
|
+
|
89
|
+
sync = TableSync.new(Session.new, 'scanner_records', 'right_table')
|
90
|
+
helper = SyncHelper.new(sync)
|
91
|
+
helper.left_table.should == 'scanner_records'
|
92
|
+
helper.right_table.should == 'right_table'
|
93
|
+
end
|
94
|
+
|
95
|
+
it "tables should return the correct table name hash" do
|
96
|
+
sync = TableSync.new(Session.new, 'scanner_records', 'right_table')
|
97
|
+
helper = SyncHelper.new(sync)
|
98
|
+
helper.tables.should == {:left => 'scanner_records', :right => 'right_table'}
|
99
|
+
end
|
100
|
+
|
101
|
+
it "table_sync should return the current table sync instance" do
|
102
|
+
sync = TableSync.new(Session.new, 'scanner_records')
|
103
|
+
helper = SyncHelper.new(sync)
|
104
|
+
helper.table_sync.should == sync
|
105
|
+
end
|
106
|
+
|
107
|
+
it "sync_options should return the correct sync options" do
|
108
|
+
sync = TableSync.new(Session.new, 'scanner_records')
|
109
|
+
helper = SyncHelper.new(sync)
|
110
|
+
helper.sync_options.should == sync.sync_options
|
111
|
+
end
|
112
|
+
|
113
|
+
it "insert_record should insert the given record" do
|
114
|
+
sync = TableSync.new(Session.new, 'scanner_records')
|
115
|
+
helper = SyncHelper.new(sync)
|
116
|
+
c = helper.instance_eval {committer}
|
117
|
+
c.should_receive(:insert_record).with(:right, 'scanner_records', :dummy_record)
|
118
|
+
helper.insert_record :right, :dummy_record
|
119
|
+
end
|
120
|
+
|
121
|
+
it "update_record should update the given record" do
|
122
|
+
sync = TableSync.new(Session.new, 'scanner_records')
|
123
|
+
helper = SyncHelper.new(sync)
|
124
|
+
c = helper.instance_eval {committer}
|
125
|
+
c.should_receive(:update_record).with(:right, 'scanner_records', :dummy_record, nil)
|
126
|
+
helper.update_record :right, :dummy_record
|
127
|
+
end
|
128
|
+
|
129
|
+
it "update_record should update the given record with the provided old key" do
|
130
|
+
sync = TableSync.new(Session.new, 'scanner_records')
|
131
|
+
helper = SyncHelper.new(sync)
|
132
|
+
c = helper.instance_eval {committer}
|
133
|
+
c.should_receive(:update_record).with(:right, 'scanner_records', :dummy_record, :old_key)
|
134
|
+
helper.update_record :right, :dummy_record, :old_key
|
135
|
+
end
|
136
|
+
|
137
|
+
it "delete_record should delete the given record" do
|
138
|
+
sync = TableSync.new(Session.new, 'scanner_records')
|
139
|
+
helper = SyncHelper.new(sync)
|
140
|
+
c = helper.instance_eval {committer}
|
141
|
+
c.should_receive(:delete_record).with(:right, 'scanner_records', :dummy_record)
|
142
|
+
helper.delete_record :right, :dummy_record
|
143
|
+
end
|
144
|
+
|
145
|
+
it "finalize should be delegated to the committer" do
|
146
|
+
sync = TableSync.new(Session.new, 'scanner_records')
|
147
|
+
helper = SyncHelper.new(sync)
|
148
|
+
|
149
|
+
# finalize itself should not lead to creation of committer
|
150
|
+
helper.finalize
|
151
|
+
helper.instance_eval {@committer}.should be_nil
|
152
|
+
|
153
|
+
c = helper.instance_eval {committer}
|
154
|
+
c.should_receive(:finalize).with(false)
|
155
|
+
helper.finalize(false)
|
156
|
+
end
|
157
|
+
end
|