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