rubyrep 1.0.0
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.
- 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
|