rubyrep 1.0.0

Sign up to get free protection for your applications and to get access to all the features.
Files changed (140) hide show
  1. data/History.txt +4 -0
  2. data/License.txt +20 -0
  3. data/Manifest.txt +137 -0
  4. data/README.txt +37 -0
  5. data/Rakefile +30 -0
  6. data/bin/rubyrep +8 -0
  7. data/config/hoe.rb +72 -0
  8. data/config/mysql_config.rb +25 -0
  9. data/config/postgres_config.rb +21 -0
  10. data/config/proxied_test_config.rb +14 -0
  11. data/config/redmine_config.rb +17 -0
  12. data/config/rep_config.rb +20 -0
  13. data/config/requirements.rb +32 -0
  14. data/config/test_config.rb +20 -0
  15. data/lib/rubyrep/base_runner.rb +195 -0
  16. data/lib/rubyrep/command_runner.rb +144 -0
  17. data/lib/rubyrep/committers/buffered_committer.rb +140 -0
  18. data/lib/rubyrep/committers/committers.rb +146 -0
  19. data/lib/rubyrep/configuration.rb +240 -0
  20. data/lib/rubyrep/connection_extenders/connection_extenders.rb +133 -0
  21. data/lib/rubyrep/connection_extenders/jdbc_extender.rb +284 -0
  22. data/lib/rubyrep/connection_extenders/mysql_extender.rb +168 -0
  23. data/lib/rubyrep/connection_extenders/postgresql_extender.rb +261 -0
  24. data/lib/rubyrep/database_proxy.rb +52 -0
  25. data/lib/rubyrep/direct_table_scan.rb +75 -0
  26. data/lib/rubyrep/generate_runner.rb +105 -0
  27. data/lib/rubyrep/initializer.rb +39 -0
  28. data/lib/rubyrep/logged_change.rb +326 -0
  29. data/lib/rubyrep/proxied_table_scan.rb +171 -0
  30. data/lib/rubyrep/proxy_block_cursor.rb +145 -0
  31. data/lib/rubyrep/proxy_connection.rb +318 -0
  32. data/lib/rubyrep/proxy_cursor.rb +44 -0
  33. data/lib/rubyrep/proxy_row_cursor.rb +43 -0
  34. data/lib/rubyrep/proxy_runner.rb +89 -0
  35. data/lib/rubyrep/replication_difference.rb +91 -0
  36. data/lib/rubyrep/replication_extenders/mysql_replication.rb +271 -0
  37. data/lib/rubyrep/replication_extenders/postgresql_replication.rb +204 -0
  38. data/lib/rubyrep/replication_extenders/replication_extenders.rb +26 -0
  39. data/lib/rubyrep/replication_helper.rb +104 -0
  40. data/lib/rubyrep/replication_initializer.rb +307 -0
  41. data/lib/rubyrep/replication_run.rb +48 -0
  42. data/lib/rubyrep/replication_runner.rb +138 -0
  43. data/lib/rubyrep/replicators/replicators.rb +37 -0
  44. data/lib/rubyrep/replicators/two_way_replicator.rb +334 -0
  45. data/lib/rubyrep/scan_progress_printers/progress_bar.rb +65 -0
  46. data/lib/rubyrep/scan_progress_printers/scan_progress_printers.rb +65 -0
  47. data/lib/rubyrep/scan_report_printers/scan_detail_reporter.rb +111 -0
  48. data/lib/rubyrep/scan_report_printers/scan_report_printers.rb +67 -0
  49. data/lib/rubyrep/scan_report_printers/scan_summary_reporter.rb +75 -0
  50. data/lib/rubyrep/scan_runner.rb +25 -0
  51. data/lib/rubyrep/session.rb +177 -0
  52. data/lib/rubyrep/sync_helper.rb +111 -0
  53. data/lib/rubyrep/sync_runner.rb +31 -0
  54. data/lib/rubyrep/syncers/syncers.rb +112 -0
  55. data/lib/rubyrep/syncers/two_way_syncer.rb +174 -0
  56. data/lib/rubyrep/table_scan.rb +54 -0
  57. data/lib/rubyrep/table_scan_helper.rb +38 -0
  58. data/lib/rubyrep/table_sorter.rb +70 -0
  59. data/lib/rubyrep/table_spec_resolver.rb +136 -0
  60. data/lib/rubyrep/table_sync.rb +68 -0
  61. data/lib/rubyrep/trigger_mode_switcher.rb +63 -0
  62. data/lib/rubyrep/type_casting_cursor.rb +31 -0
  63. data/lib/rubyrep/uninstall_runner.rb +92 -0
  64. data/lib/rubyrep/version.rb +9 -0
  65. data/lib/rubyrep.rb +68 -0
  66. data/script/destroy +14 -0
  67. data/script/generate +14 -0
  68. data/script/txt2html +74 -0
  69. data/setup.rb +1585 -0
  70. data/sims/performance/big_rep_spec.rb +100 -0
  71. data/sims/performance/big_scan_spec.rb +57 -0
  72. data/sims/performance/big_sync_spec.rb +141 -0
  73. data/sims/performance/performance.rake +228 -0
  74. data/sims/sim_helper.rb +24 -0
  75. data/spec/base_runner_spec.rb +218 -0
  76. data/spec/buffered_committer_spec.rb +271 -0
  77. data/spec/command_runner_spec.rb +145 -0
  78. data/spec/committers_spec.rb +174 -0
  79. data/spec/configuration_spec.rb +198 -0
  80. data/spec/connection_extender_interface_spec.rb +138 -0
  81. data/spec/connection_extenders_registration_spec.rb +129 -0
  82. data/spec/database_proxy_spec.rb +48 -0
  83. data/spec/database_rake_spec.rb +40 -0
  84. data/spec/db_specific_connection_extenders_spec.rb +34 -0
  85. data/spec/db_specific_replication_extenders_spec.rb +38 -0
  86. data/spec/direct_table_scan_spec.rb +61 -0
  87. data/spec/generate_runner_spec.rb +84 -0
  88. data/spec/initializer_spec.rb +46 -0
  89. data/spec/logged_change_spec.rb +480 -0
  90. data/spec/postgresql_replication_spec.rb +48 -0
  91. data/spec/postgresql_support_spec.rb +57 -0
  92. data/spec/progress_bar_spec.rb +77 -0
  93. data/spec/proxied_table_scan_spec.rb +151 -0
  94. data/spec/proxy_block_cursor_spec.rb +197 -0
  95. data/spec/proxy_connection_spec.rb +399 -0
  96. data/spec/proxy_cursor_spec.rb +56 -0
  97. data/spec/proxy_row_cursor_spec.rb +66 -0
  98. data/spec/proxy_runner_spec.rb +70 -0
  99. data/spec/replication_difference_spec.rb +160 -0
  100. data/spec/replication_extender_interface_spec.rb +365 -0
  101. data/spec/replication_extenders_spec.rb +32 -0
  102. data/spec/replication_helper_spec.rb +121 -0
  103. data/spec/replication_initializer_spec.rb +477 -0
  104. data/spec/replication_run_spec.rb +166 -0
  105. data/spec/replication_runner_spec.rb +213 -0
  106. data/spec/replicators_spec.rb +31 -0
  107. data/spec/rubyrep_spec.rb +8 -0
  108. data/spec/scan_detail_reporter_spec.rb +119 -0
  109. data/spec/scan_progress_printers_spec.rb +68 -0
  110. data/spec/scan_report_printers_spec.rb +67 -0
  111. data/spec/scan_runner_spec.rb +50 -0
  112. data/spec/scan_summary_reporter_spec.rb +61 -0
  113. data/spec/session_spec.rb +212 -0
  114. data/spec/spec.opts +1 -0
  115. data/spec/spec_helper.rb +295 -0
  116. data/spec/sync_helper_spec.rb +157 -0
  117. data/spec/sync_runner_spec.rb +78 -0
  118. data/spec/syncers_spec.rb +171 -0
  119. data/spec/table_scan_helper_spec.rb +29 -0
  120. data/spec/table_scan_spec.rb +49 -0
  121. data/spec/table_sorter_spec.rb +31 -0
  122. data/spec/table_spec_resolver_spec.rb +102 -0
  123. data/spec/table_sync_spec.rb +84 -0
  124. data/spec/trigger_mode_switcher_spec.rb +83 -0
  125. data/spec/two_way_replicator_spec.rb +551 -0
  126. data/spec/two_way_syncer_spec.rb +256 -0
  127. data/spec/type_casting_cursor_spec.rb +50 -0
  128. data/spec/uninstall_runner_spec.rb +86 -0
  129. data/tasks/database.rake +439 -0
  130. data/tasks/deployment.rake +29 -0
  131. data/tasks/environment.rake +9 -0
  132. data/tasks/java.rake +37 -0
  133. data/tasks/redmine_test.rake +47 -0
  134. data/tasks/rspec.rake +68 -0
  135. data/tasks/rubyrep.tailor +18 -0
  136. data/tasks/stats.rake +19 -0
  137. data/tasks/task_helper.rb +20 -0
  138. data.tar.gz.sig +0 -0
  139. metadata +243 -0
  140. 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
@@ -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