offroad 0.0.2 → 0.0.3

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 (61) hide show
  1. data/LICENSE +674 -674
  2. data/README.rdoc +29 -29
  3. data/Rakefile +75 -75
  4. data/TODO +42 -42
  5. data/lib/app/models/offroad/group_state.rb +85 -85
  6. data/lib/app/models/offroad/mirror_info.rb +53 -53
  7. data/lib/app/models/offroad/model_state.rb +36 -36
  8. data/lib/app/models/offroad/received_record_state.rb +115 -115
  9. data/lib/app/models/offroad/sendable_record_state.rb +91 -91
  10. data/lib/app/models/offroad/system_state.rb +33 -33
  11. data/lib/cargo_streamer.rb +222 -222
  12. data/lib/controller_extensions.rb +74 -74
  13. data/lib/exceptions.rb +16 -16
  14. data/lib/migrate/20100512164608_create_offroad_tables.rb +72 -72
  15. data/lib/mirror_data.rb +376 -376
  16. data/lib/model_extensions.rb +378 -377
  17. data/lib/module_funcs.rb +94 -94
  18. data/lib/offroad.rb +41 -41
  19. data/lib/version.rb +3 -3
  20. data/lib/view_helper.rb +7 -7
  21. data/templates/offline.rb +36 -36
  22. data/templates/offline_database.yml +7 -7
  23. data/templates/offroad.yml +6 -6
  24. data/test/app_root/app/controllers/application_controller.rb +2 -2
  25. data/test/app_root/app/controllers/group_controller.rb +28 -28
  26. data/test/app_root/app/models/global_record.rb +10 -10
  27. data/test/app_root/app/models/group.rb +12 -12
  28. data/test/app_root/app/models/group_owned_record.rb +68 -68
  29. data/test/app_root/app/models/guest.rb +7 -7
  30. data/test/app_root/app/models/subrecord.rb +12 -12
  31. data/test/app_root/app/models/unmirrored_record.rb +4 -4
  32. data/test/app_root/app/views/group/download_down_mirror.html.erb +3 -3
  33. data/test/app_root/app/views/group/download_initial_down_mirror.html.erb +3 -3
  34. data/test/app_root/app/views/group/download_up_mirror.html.erb +5 -5
  35. data/test/app_root/app/views/layouts/mirror.html.erb +8 -8
  36. data/test/app_root/config/boot.rb +115 -115
  37. data/test/app_root/config/database-pg.yml +8 -8
  38. data/test/app_root/config/database.yml +5 -5
  39. data/test/app_root/config/environment.rb +24 -24
  40. data/test/app_root/config/environments/test.rb +17 -17
  41. data/test/app_root/config/offroad.yml +6 -6
  42. data/test/app_root/config/routes.rb +4 -4
  43. data/test/app_root/db/migrate/20100529235049_create_tables.rb +64 -64
  44. data/test/app_root/lib/common_hobo.rb +15 -15
  45. data/test/app_root/vendor/plugins/offroad/init.rb +2 -2
  46. data/test/functional/mirror_operations_test.rb +148 -148
  47. data/test/test_helper.rb +453 -453
  48. data/test/unit/app_state_tracking_test.rb +275 -275
  49. data/test/unit/cargo_streamer_test.rb +332 -332
  50. data/test/unit/global_data_test.rb +102 -102
  51. data/test/unit/group_controller_test.rb +152 -152
  52. data/test/unit/group_data_test.rb +442 -435
  53. data/test/unit/group_single_test.rb +136 -136
  54. data/test/unit/hobo_permissions_test.rb +57 -57
  55. data/test/unit/mirror_data_test.rb +1283 -1283
  56. data/test/unit/mirror_info_test.rb +31 -31
  57. data/test/unit/module_funcs_test.rb +37 -37
  58. data/test/unit/pathological_model_test.rb +62 -62
  59. data/test/unit/test_framework_test.rb +86 -86
  60. data/test/unit/unmirrored_data_test.rb +14 -14
  61. metadata +6 -8
data/test/test_helper.rb CHANGED
@@ -1,453 +1,453 @@
1
- ENV['RAILS_ENV'] = 'test'
2
-
3
- prev_dir = Dir.getwd
4
- begin
5
- Dir.chdir("#{File.dirname(__FILE__)}/..")
6
-
7
- begin
8
- # Used when running test files directly
9
- $LOAD_PATH << "#{File.dirname(__FILE__)}/../lib"
10
- require "#{File.dirname(__FILE__)}/app_root/config/environment"
11
- rescue LoadError
12
- # This is needed for root-level rake task 'test'
13
- require "app_root/config/environment"
14
- end
15
- ensure
16
- Dir.chdir(prev_dir)
17
- end
18
-
19
- require 'rubygems'
20
- require 'test/unit/util/backtracefilter'
21
- require 'test_help'
22
-
23
- # Try to load the redgreen test console outputter, if it's available
24
- begin
25
- require 'redgreen'
26
- rescue LoadError
27
- end
28
-
29
- # Monkey patch the backtrace filter to include project source files
30
- module Test::Unit::Util::BacktraceFilter
31
- def filter_backtrace(backtrace, prefix = nil)
32
- backtrace = backtrace.select do |e|
33
- if ENV['FULL_BACKTRACE']
34
- true
35
- else
36
- e.include?("offroad") || !(e.include?("/ruby/") || e.include?("/gems/"))
37
- end
38
- end
39
-
40
- common_prefix = nil
41
- backtrace.each do |elem|
42
- next if elem.start_with? "./"
43
- if common_prefix
44
- until elem.start_with? common_prefix
45
- common_prefix.chop!
46
- end
47
- else
48
- common_prefix = String.new(elem)
49
- end
50
- end
51
-
52
- return backtrace.map do |element|
53
- if element.start_with? common_prefix && common_prefix.size < element.size
54
- element[common_prefix.size, element.size]
55
- elsif element.start_with? "./"
56
- element[2, element.size]
57
- elsif element.start_with?(Dir.getwd)
58
- element[Dir.getwd.size+1, element.size]
59
- else
60
- element
61
- end
62
- end
63
- end
64
- end
65
-
66
- def force_save_and_reload(*records)
67
- records.each do |record|
68
- record.class.delete(record.id) unless record.new_record?
69
- record.class.import([record], :validate => false, :timestamps => false)
70
- if record.new_record?
71
- record.id = record.class.last(:select => record.class.primary_key, :order => record.class.primary_key).id
72
- end
73
- record.reload
74
- end
75
- end
76
-
77
- class VirtualTestDatabase
78
- @@current_database = nil
79
- @@test_instance = nil
80
-
81
- def initialize(prefix, test_class)
82
- ActiveRecord::Base.connection.clear_query_cache
83
-
84
- @prefix = prefix
85
- @test_instance_vars = {}
86
- @test_instance_var_names = {}
87
-
88
- if @@current_database != nil
89
- @@current_database.send(:put_away)
90
- delete_all_rows
91
- end
92
-
93
- @@test_instance = test_class
94
- setup
95
- backup_as_fresh
96
- @@current_database = self
97
- end
98
-
99
- def bring_forward(test_class, fresh_flag = false)
100
- ActiveRecord::Base.connection.clear_query_cache
101
- @@current_database.send(:put_away)
102
- @@test_instance = test_class
103
- fresh_flag ? restore_fresh : restore
104
- @@current_database = self
105
- end
106
-
107
- def delete_all_rows
108
- tables = ActiveRecord::Base.connection.tables
109
- if ActiveRecord::Base.connection.adapter_name.downcase.include?("sqlite")
110
- tables << "sqlite_sequence"
111
- end
112
-
113
- tables.each do |table|
114
- next if table.downcase.start_with?("virtual_")
115
- next if table == "schema_migrations"
116
- ActiveRecord::Base.connection.execute "DELETE FROM #{table}"
117
- end
118
-
119
- if ActiveRecord::Base.connection.adapter_name.downcase.include?("postgres")
120
- # Reset all sequences so that autoincremented ids start from 1 again
121
- seqnames = ActiveRecord::Base.connection.select_values "SELECT c.relname FROM pg_class c WHERE c.relkind = 'S'"
122
- seqnames.each do |s|
123
- ActiveRecord::Base.connection.execute "SELECT setval('#{s}', 1, false)"
124
- end
125
- end
126
- end
127
-
128
- protected
129
-
130
- def setup
131
- delete_all_rows
132
- end
133
-
134
- private
135
-
136
- def normal_prefix
137
- "virtual_normal_#{@prefix}_"
138
- end
139
-
140
- def fresh_prefix
141
- "virtual_fresh_#{@prefix}_"
142
- end
143
-
144
- def put_away
145
- copy_tables("", normal_prefix)
146
- backup_instance_vars(normal_prefix)
147
- delete_instance_vars
148
- end
149
-
150
- def backup_as_fresh
151
- copy_tables("", fresh_prefix)
152
- backup_instance_vars(fresh_prefix)
153
- end
154
-
155
- def restore
156
- copy_tables(normal_prefix, "")
157
- restore_instance_vars(normal_prefix)
158
- end
159
-
160
- def restore_fresh
161
- copy_tables(fresh_prefix, "")
162
- restore_instance_vars(fresh_prefix)
163
- end
164
-
165
- def copy_tables(src_prefix, dst_prefix)
166
- tables = ActiveRecord::Base.connection.tables
167
- if ActiveRecord::Base.connection.adapter_name.downcase.include?("sqlite")
168
- tables << "sqlite_sequence"
169
- end
170
- tables.each do |src_table|
171
- next if src_table.end_with?("schema_migrations")
172
- next unless src_table.start_with?(src_prefix)
173
- next if dst_prefix != "" && src_table.start_with?(dst_prefix)
174
- dst_table = dst_prefix + src_table[(src_prefix.size)..(src_table.size)]
175
- next if src_table.downcase.start_with?("virtual_") && dst_table.downcase.start_with?("virtual_")
176
- if tables.include?(dst_table)
177
- ActiveRecord::Base.connection.execute "DELETE FROM #{dst_table}"
178
- ActiveRecord::Base.connection.execute "INSERT INTO #{dst_table} SELECT * FROM #{src_table}"
179
- else
180
- ActiveRecord::Base.connection.execute "CREATE TABLE #{dst_table} AS SELECT * FROM #{src_table}"
181
- end
182
- end
183
-
184
- if ActiveRecord::Base.connection.adapter_name.downcase.include?("postgres")
185
- # Manage postgresql sequences by keeping non-current sequence vals backed up in Ruby
186
- @pg_seq_vals ||= {}
187
- if src_prefix.blank?
188
- # PG Sequences -> Ruby
189
- @pg_seq_vals[dst_prefix] ||= {}
190
- seqnames = ActiveRecord::Base.connection.select_values "SELECT c.relname FROM pg_class c WHERE c.relkind = 'S'"
191
- seqnames.each do |s|
192
- @pg_seq_vals[dst_prefix][s] = ActiveRecord::Base.connection.select_value "SELECT last_value FROM \"#{s}\""
193
- end
194
- else
195
- # Ruby -> PG Sequences
196
- @pg_seq_vals[src_prefix].each do |s, v|
197
- ActiveRecord::Base.connection.execute "SELECT setval('#{s}', #{v}, true)"
198
- end
199
- end
200
- end
201
- end
202
-
203
- def backup_instance_vars(key)
204
- @test_instance_vars ||= {}
205
- @test_instance_vars[key] = {}
206
- @@test_instance.instance_variables.each do |varname|
207
- next unless @test_instance_var_names.has_key?(varname)
208
- value = @@test_instance.instance_variable_get(varname.to_sym)
209
- next unless value
210
- @test_instance_vars[key][varname] = value.dup
211
- end
212
- end
213
-
214
- def restore_instance_vars(key)
215
- delete_instance_vars
216
- @test_instance_vars[key].each_pair do |varname, value|
217
- restored_val = nil
218
- if value.is_a?(ActiveRecord::Base)
219
- if !value.destroyed? && (value.changed? || value.new_record?)
220
- restored_val = value.class.find(value.id)
221
- else
222
- restored_val = value
223
- end
224
- else
225
- restored_val = value.dup
226
- end
227
-
228
- # Using find_by_id so that if the record was destroyed earlier in the test, RecordNotFound isn't raised here
229
- restored_val = value.is_a?(ActiveRecord::Base) ? value.class.find_by_id(value.id) : value.dup
230
-
231
- @@test_instance.instance_variable_set(varname.to_sym, restored_val)
232
- end
233
- end
234
-
235
- def delete_instance_vars
236
- @@test_instance.instance_variables.each do |varname|
237
- next unless @test_instance_var_names.has_key?(varname)
238
- @@test_instance.instance_variable_set(varname.to_sym, nil)
239
- end
240
- end
241
-
242
- def setup_ivar(key, value)
243
- @test_instance_var_names[key.to_s] = true
244
- @@test_instance.instance_variable_set(key, value)
245
- end
246
- end
247
-
248
- class OnlineTestDatabase < VirtualTestDatabase
249
- def initialize(test_class)
250
- super("online", test_class)
251
- end
252
-
253
- def self.initial_mirror_data
254
- @@initial_mirror_data
255
- end
256
-
257
- protected
258
-
259
- def setup
260
- super
261
-
262
- unused_offline_group = Group.create(:name => "Unused Offline Group")
263
- unused_online_group = Group.create(:name => "Unused Online Group")
264
-
265
- offline_group = Group.create(:name => "An Offline Group")
266
- online_group = Group.create(:name => "An Online Group")
267
- setup_ivar(:@offline_group, offline_group)
268
- setup_ivar(:@online_group, online_group)
269
-
270
- offline_data = GroupOwnedRecord.create( :description => "Sam", :group => offline_group)
271
- online_data = GroupOwnedRecord.create(:description => "Max", :group => online_group)
272
- setup_ivar(:@offline_group_data, offline_data)
273
- setup_ivar(:@online_group_data, online_data)
274
-
275
- indirect_offline_data = SubRecord.create( :description => "Boris", :group_owned_record => offline_data)
276
- indirect_online_data = SubRecord.create( :description => "Natasha", :group_owned_record => online_data)
277
- setup_ivar(:@offline_indirect_data, indirect_offline_data)
278
- setup_ivar(:@online_indirect_data, indirect_online_data)
279
-
280
- setup_ivar(:@editable_group, online_group)
281
- setup_ivar(:@editable_group_data, online_data)
282
- setup_ivar(:@editable_indirect_data, indirect_online_data)
283
-
284
- unused_offline_group.group_offline = true
285
- offline_group.group_offline = true
286
-
287
- @@initial_mirror_data ||= Offroad::MirrorData.new(offline_group, :initial_mode => true).write_downwards_data
288
- end
289
- end
290
-
291
- class OfflineTestDatabase < VirtualTestDatabase
292
- def initialize(test_class)
293
- super("offline", test_class)
294
- end
295
-
296
- protected
297
-
298
- def setup
299
- super
300
-
301
- Offroad::MirrorData.new(nil, :initial_mode => true).load_downwards_data OnlineTestDatabase::initial_mirror_data
302
-
303
- offline_group = Group.first
304
- offline_data = GroupOwnedRecord.first
305
- offline_indirect_data = SubRecord.first
306
-
307
- setup_ivar(:@offline_group, offline_group)
308
- setup_ivar(:@offline_group_data, offline_data)
309
- setup_ivar(:@offline_indirect_data, offline_indirect_data)
310
-
311
- setup_ivar(:@editable_group, offline_group)
312
- setup_ivar(:@editable_group_data, offline_data)
313
- setup_ivar(:@editable_indirect_data, offline_indirect_data)
314
- end
315
- end
316
-
317
- class Test::Unit::TestCase
318
- @@online_database = nil
319
- @@offline_database = nil
320
- @@initial_setup = false
321
-
322
- include Test::Unit::Util::BacktraceFilter
323
-
324
- def setup
325
- begin
326
- unless @@initial_setup
327
- if ActiveRecord::Base.connection.adapter_name.downcase.include?("postgresql")
328
- puts "Dropping all postgresql tables"
329
- tables = ActiveRecord::Base.connection.tables.each do |table|
330
- ActiveRecord::Base.connection.execute "DROP TABLE #{table}"
331
- end
332
- end
333
- @@initial_setup = true
334
- end
335
-
336
- unless ActiveRecord::Base.connection.table_exists?("schema_migrations")
337
- # FIXME : Figure out why ActionController::TestCase keeps on deleting all the tables before each method
338
-
339
- # First time the setup method has ran, create our test databases
340
- ActiveRecord::Migration.verbose = false
341
- ActiveRecord::Migrator.migrate("#{Rails.root}/db/migrate") # Migrations for the testing pseudo-app
342
- ActiveRecord::Migrator.migrate("#{File.dirname(__FILE__)}/../lib/migrate/") # Offroad internal tables
343
-
344
- Offroad::config_app_online(true)
345
- @@online_database = OnlineTestDatabase.new(self)
346
-
347
- Offroad::config_app_online(false)
348
- @@offline_database = OfflineTestDatabase.new(self)
349
-
350
- Offroad::config_app_online(nil)
351
- end
352
- rescue Exception => e
353
- puts ""
354
- puts "!!!!! Test framework setup error: #{e.to_s}"
355
- puts " " + filter_backtrace(e.backtrace).join("\n ")
356
- puts ""
357
- raise SystemExit
358
- end
359
- end
360
-
361
- def in_online_app(fresh_flag = false, delete_all_rows = false, &block)
362
- begin
363
- Offroad::config_app_online(true)
364
- @@online_database.bring_forward(self, fresh_flag)
365
- if delete_all_rows
366
- @@online_database.delete_all_rows
367
- end
368
- instance_eval &block
369
- ensure
370
- Offroad::config_app_online(nil)
371
- end
372
- end
373
-
374
- def in_offline_app(fresh_flag = false, delete_all_rows = false, &block)
375
- begin
376
- Offroad::config_app_online(false)
377
- @@offline_database.bring_forward(self, fresh_flag)
378
- if delete_all_rows
379
- @@offline_database.delete_all_rows
380
- end
381
- instance_eval &block
382
- ensure
383
- Offroad::config_app_online(nil)
384
- end
385
- end
386
-
387
- def restore_all_from_fresh
388
- @@offline_database.bring_forward(self, true)
389
- @@online_database.bring_forward(self, true)
390
- end
391
- end
392
-
393
- def define_wrapped_test(name, wrapper_proc, block)
394
- method_name = "test_" + name.to_s.gsub(/[^\w ]/, '_').gsub(' ', '_')
395
- define_method method_name.to_sym, &block
396
- if wrapper_proc
397
- define_method "wrapped_#{method_name}".to_sym do
398
- wrapper_proc.call(self) { send "unwrapped_#{method_name}".to_sym }
399
- end
400
- alias_method "unwrapped_#{method_name}".to_sym, method_name.to_sym
401
- alias_method method_name.to_sym, "wrapped_#{method_name}".to_sym
402
- end
403
- end
404
-
405
- # Convenience methods to create tests that apply to particular environments or situations
406
-
407
- # Test that should be run in the online environment
408
- def online_test(name, &block)
409
- wrapper = Proc.new do |t|
410
- t.in_online_app(true, &block)
411
- end
412
-
413
- define_wrapped_test("ONLINE #{name}", wrapper, block)
414
- end
415
-
416
- # Test that is ran in the online environment, but with no preset records
417
- def empty_online_test(name, &block)
418
- wrapper = Proc.new do |t|
419
- t.in_online_app(false, true, &block)
420
- end
421
-
422
- define_wrapped_test("EMPTY ONLINE #{name}", wrapper, block)
423
- end
424
-
425
- # Test that should be run in the offline environment
426
- def offline_test(name, &block)
427
- wrapper = Proc.new do |t|
428
- t.in_offline_app(true, &block)
429
- end
430
-
431
- define_wrapped_test("OFFLINE #{name}", wrapper, block)
432
- end
433
-
434
- # Test that should be run twice, once online and once offline
435
- def double_test(name, &block)
436
- online_test(name, &block)
437
- offline_test(name, &block)
438
- end
439
-
440
- # Test that shouldn't care what environment it is started in
441
- def agnostic_test(name, &block)
442
- define_wrapped_test("AGNOSTIC #{name}", nil, block)
443
- end
444
-
445
- # Test that involves both environments (within test, use in_online_app and in_offline_app)
446
- def cross_test(name, &block)
447
- wrapper = Proc.new do |t|
448
- t.restore_all_from_fresh
449
- t.instance_eval &block
450
- end
451
-
452
- define_wrapped_test("CROSS #{name}", wrapper, block)
453
- end
1
+ ENV['RAILS_ENV'] = 'test'
2
+
3
+ prev_dir = Dir.getwd
4
+ begin
5
+ Dir.chdir("#{File.dirname(__FILE__)}/..")
6
+
7
+ begin
8
+ # Used when running test files directly
9
+ $LOAD_PATH << "#{File.dirname(__FILE__)}/../lib"
10
+ require "#{File.dirname(__FILE__)}/app_root/config/environment"
11
+ rescue LoadError
12
+ # This is needed for root-level rake task 'test'
13
+ require "app_root/config/environment"
14
+ end
15
+ ensure
16
+ Dir.chdir(prev_dir)
17
+ end
18
+
19
+ require 'rubygems'
20
+ require 'test/unit/util/backtracefilter'
21
+ require 'test_help'
22
+
23
+ # Try to load the redgreen test console outputter, if it's available
24
+ begin
25
+ require 'redgreen'
26
+ rescue LoadError
27
+ end
28
+
29
+ # Monkey patch the backtrace filter to include project source files
30
+ module Test::Unit::Util::BacktraceFilter
31
+ def filter_backtrace(backtrace, prefix = nil)
32
+ backtrace = backtrace.select do |e|
33
+ if ENV['FULL_BACKTRACE']
34
+ true
35
+ else
36
+ e.include?("offroad") || !(e.include?("/ruby/") || e.include?("/gems/"))
37
+ end
38
+ end
39
+
40
+ common_prefix = nil
41
+ backtrace.each do |elem|
42
+ next if elem.start_with? "./"
43
+ if common_prefix
44
+ until elem.start_with? common_prefix
45
+ common_prefix.chop!
46
+ end
47
+ else
48
+ common_prefix = String.new(elem)
49
+ end
50
+ end
51
+
52
+ return backtrace.map do |element|
53
+ if element.start_with? common_prefix && common_prefix.size < element.size
54
+ element[common_prefix.size, element.size]
55
+ elsif element.start_with? "./"
56
+ element[2, element.size]
57
+ elsif element.start_with?(Dir.getwd)
58
+ element[Dir.getwd.size+1, element.size]
59
+ else
60
+ element
61
+ end
62
+ end
63
+ end
64
+ end
65
+
66
+ def force_save_and_reload(*records)
67
+ records.each do |record|
68
+ record.class.delete(record.id) unless record.new_record?
69
+ record.class.import([record], :validate => false, :timestamps => false)
70
+ if record.new_record?
71
+ record.id = record.class.last(:select => record.class.primary_key, :order => record.class.primary_key).id
72
+ end
73
+ record.reload
74
+ end
75
+ end
76
+
77
+ class VirtualTestDatabase
78
+ @@current_database = nil
79
+ @@test_instance = nil
80
+
81
+ def initialize(prefix, test_class)
82
+ ActiveRecord::Base.connection.clear_query_cache
83
+
84
+ @prefix = prefix
85
+ @test_instance_vars = {}
86
+ @test_instance_var_names = {}
87
+
88
+ if @@current_database != nil
89
+ @@current_database.send(:put_away)
90
+ delete_all_rows
91
+ end
92
+
93
+ @@test_instance = test_class
94
+ setup
95
+ backup_as_fresh
96
+ @@current_database = self
97
+ end
98
+
99
+ def bring_forward(test_class, fresh_flag = false)
100
+ ActiveRecord::Base.connection.clear_query_cache
101
+ @@current_database.send(:put_away)
102
+ @@test_instance = test_class
103
+ fresh_flag ? restore_fresh : restore
104
+ @@current_database = self
105
+ end
106
+
107
+ def delete_all_rows
108
+ tables = ActiveRecord::Base.connection.tables
109
+ if ActiveRecord::Base.connection.adapter_name.downcase.include?("sqlite")
110
+ tables << "sqlite_sequence"
111
+ end
112
+
113
+ tables.each do |table|
114
+ next if table.downcase.start_with?("virtual_")
115
+ next if table == "schema_migrations"
116
+ ActiveRecord::Base.connection.execute "DELETE FROM #{table}"
117
+ end
118
+
119
+ if ActiveRecord::Base.connection.adapter_name.downcase.include?("postgres")
120
+ # Reset all sequences so that autoincremented ids start from 1 again
121
+ seqnames = ActiveRecord::Base.connection.select_values "SELECT c.relname FROM pg_class c WHERE c.relkind = 'S'"
122
+ seqnames.each do |s|
123
+ ActiveRecord::Base.connection.execute "SELECT setval('#{s}', 1, false)"
124
+ end
125
+ end
126
+ end
127
+
128
+ protected
129
+
130
+ def setup
131
+ delete_all_rows
132
+ end
133
+
134
+ private
135
+
136
+ def normal_prefix
137
+ "virtual_normal_#{@prefix}_"
138
+ end
139
+
140
+ def fresh_prefix
141
+ "virtual_fresh_#{@prefix}_"
142
+ end
143
+
144
+ def put_away
145
+ copy_tables("", normal_prefix)
146
+ backup_instance_vars(normal_prefix)
147
+ delete_instance_vars
148
+ end
149
+
150
+ def backup_as_fresh
151
+ copy_tables("", fresh_prefix)
152
+ backup_instance_vars(fresh_prefix)
153
+ end
154
+
155
+ def restore
156
+ copy_tables(normal_prefix, "")
157
+ restore_instance_vars(normal_prefix)
158
+ end
159
+
160
+ def restore_fresh
161
+ copy_tables(fresh_prefix, "")
162
+ restore_instance_vars(fresh_prefix)
163
+ end
164
+
165
+ def copy_tables(src_prefix, dst_prefix)
166
+ tables = ActiveRecord::Base.connection.tables
167
+ if ActiveRecord::Base.connection.adapter_name.downcase.include?("sqlite")
168
+ tables << "sqlite_sequence"
169
+ end
170
+ tables.each do |src_table|
171
+ next if src_table.end_with?("schema_migrations")
172
+ next unless src_table.start_with?(src_prefix)
173
+ next if dst_prefix != "" && src_table.start_with?(dst_prefix)
174
+ dst_table = dst_prefix + src_table[(src_prefix.size)..(src_table.size)]
175
+ next if src_table.downcase.start_with?("virtual_") && dst_table.downcase.start_with?("virtual_")
176
+ if tables.include?(dst_table)
177
+ ActiveRecord::Base.connection.execute "DELETE FROM #{dst_table}"
178
+ ActiveRecord::Base.connection.execute "INSERT INTO #{dst_table} SELECT * FROM #{src_table}"
179
+ else
180
+ ActiveRecord::Base.connection.execute "CREATE TABLE #{dst_table} AS SELECT * FROM #{src_table}"
181
+ end
182
+ end
183
+
184
+ if ActiveRecord::Base.connection.adapter_name.downcase.include?("postgres")
185
+ # Manage postgresql sequences by keeping non-current sequence vals backed up in Ruby
186
+ @pg_seq_vals ||= {}
187
+ if src_prefix.blank?
188
+ # PG Sequences -> Ruby
189
+ @pg_seq_vals[dst_prefix] ||= {}
190
+ seqnames = ActiveRecord::Base.connection.select_values "SELECT c.relname FROM pg_class c WHERE c.relkind = 'S'"
191
+ seqnames.each do |s|
192
+ @pg_seq_vals[dst_prefix][s] = ActiveRecord::Base.connection.select_value "SELECT last_value FROM \"#{s}\""
193
+ end
194
+ else
195
+ # Ruby -> PG Sequences
196
+ @pg_seq_vals[src_prefix].each do |s, v|
197
+ ActiveRecord::Base.connection.execute "SELECT setval('#{s}', #{v}, true)"
198
+ end
199
+ end
200
+ end
201
+ end
202
+
203
+ def backup_instance_vars(key)
204
+ @test_instance_vars ||= {}
205
+ @test_instance_vars[key] = {}
206
+ @@test_instance.instance_variables.each do |varname|
207
+ next unless @test_instance_var_names.has_key?(varname)
208
+ value = @@test_instance.instance_variable_get(varname.to_sym)
209
+ next unless value
210
+ @test_instance_vars[key][varname] = value.dup
211
+ end
212
+ end
213
+
214
+ def restore_instance_vars(key)
215
+ delete_instance_vars
216
+ @test_instance_vars[key].each_pair do |varname, value|
217
+ restored_val = nil
218
+ if value.is_a?(ActiveRecord::Base)
219
+ if !value.destroyed? && (value.changed? || value.new_record?)
220
+ restored_val = value.class.find(value.id)
221
+ else
222
+ restored_val = value
223
+ end
224
+ else
225
+ restored_val = value.dup
226
+ end
227
+
228
+ # Using find_by_id so that if the record was destroyed earlier in the test, RecordNotFound isn't raised here
229
+ restored_val = value.is_a?(ActiveRecord::Base) ? value.class.find_by_id(value.id) : value.dup
230
+
231
+ @@test_instance.instance_variable_set(varname.to_sym, restored_val)
232
+ end
233
+ end
234
+
235
+ def delete_instance_vars
236
+ @@test_instance.instance_variables.each do |varname|
237
+ next unless @test_instance_var_names.has_key?(varname)
238
+ @@test_instance.instance_variable_set(varname.to_sym, nil)
239
+ end
240
+ end
241
+
242
+ def setup_ivar(key, value)
243
+ @test_instance_var_names[key.to_s] = true
244
+ @@test_instance.instance_variable_set(key, value)
245
+ end
246
+ end
247
+
248
+ class OnlineTestDatabase < VirtualTestDatabase
249
+ def initialize(test_class)
250
+ super("online", test_class)
251
+ end
252
+
253
+ def self.initial_mirror_data
254
+ @@initial_mirror_data
255
+ end
256
+
257
+ protected
258
+
259
+ def setup
260
+ super
261
+
262
+ unused_offline_group = Group.create(:name => "Unused Offline Group")
263
+ unused_online_group = Group.create(:name => "Unused Online Group")
264
+
265
+ offline_group = Group.create(:name => "An Offline Group")
266
+ online_group = Group.create(:name => "An Online Group")
267
+ setup_ivar(:@offline_group, offline_group)
268
+ setup_ivar(:@online_group, online_group)
269
+
270
+ offline_data = GroupOwnedRecord.create( :description => "Sam", :group => offline_group)
271
+ online_data = GroupOwnedRecord.create(:description => "Max", :group => online_group)
272
+ setup_ivar(:@offline_group_data, offline_data)
273
+ setup_ivar(:@online_group_data, online_data)
274
+
275
+ indirect_offline_data = SubRecord.create( :description => "Boris", :group_owned_record => offline_data)
276
+ indirect_online_data = SubRecord.create( :description => "Natasha", :group_owned_record => online_data)
277
+ setup_ivar(:@offline_indirect_data, indirect_offline_data)
278
+ setup_ivar(:@online_indirect_data, indirect_online_data)
279
+
280
+ setup_ivar(:@editable_group, online_group)
281
+ setup_ivar(:@editable_group_data, online_data)
282
+ setup_ivar(:@editable_indirect_data, indirect_online_data)
283
+
284
+ unused_offline_group.group_offline = true
285
+ offline_group.group_offline = true
286
+
287
+ @@initial_mirror_data ||= Offroad::MirrorData.new(offline_group, :initial_mode => true).write_downwards_data
288
+ end
289
+ end
290
+
291
+ class OfflineTestDatabase < VirtualTestDatabase
292
+ def initialize(test_class)
293
+ super("offline", test_class)
294
+ end
295
+
296
+ protected
297
+
298
+ def setup
299
+ super
300
+
301
+ Offroad::MirrorData.new(nil, :initial_mode => true).load_downwards_data OnlineTestDatabase::initial_mirror_data
302
+
303
+ offline_group = Group.first
304
+ offline_data = GroupOwnedRecord.first
305
+ offline_indirect_data = SubRecord.first
306
+
307
+ setup_ivar(:@offline_group, offline_group)
308
+ setup_ivar(:@offline_group_data, offline_data)
309
+ setup_ivar(:@offline_indirect_data, offline_indirect_data)
310
+
311
+ setup_ivar(:@editable_group, offline_group)
312
+ setup_ivar(:@editable_group_data, offline_data)
313
+ setup_ivar(:@editable_indirect_data, offline_indirect_data)
314
+ end
315
+ end
316
+
317
+ class Test::Unit::TestCase
318
+ @@online_database = nil
319
+ @@offline_database = nil
320
+ @@initial_setup = false
321
+
322
+ include Test::Unit::Util::BacktraceFilter
323
+
324
+ def setup
325
+ begin
326
+ unless @@initial_setup
327
+ if ActiveRecord::Base.connection.adapter_name.downcase.include?("postgresql")
328
+ puts "Dropping all postgresql tables"
329
+ tables = ActiveRecord::Base.connection.tables.each do |table|
330
+ ActiveRecord::Base.connection.execute "DROP TABLE #{table}"
331
+ end
332
+ end
333
+ @@initial_setup = true
334
+ end
335
+
336
+ unless ActiveRecord::Base.connection.table_exists?("schema_migrations")
337
+ # FIXME : Figure out why ActionController::TestCase keeps on deleting all the tables before each method
338
+
339
+ # First time the setup method has ran, create our test databases
340
+ ActiveRecord::Migration.verbose = false
341
+ ActiveRecord::Migrator.migrate("#{Rails.root}/db/migrate") # Migrations for the testing pseudo-app
342
+ ActiveRecord::Migrator.migrate("#{File.dirname(__FILE__)}/../lib/migrate/") # Offroad internal tables
343
+
344
+ Offroad::config_app_online(true)
345
+ @@online_database = OnlineTestDatabase.new(self)
346
+
347
+ Offroad::config_app_online(false)
348
+ @@offline_database = OfflineTestDatabase.new(self)
349
+
350
+ Offroad::config_app_online(nil)
351
+ end
352
+ rescue Exception => e
353
+ puts ""
354
+ puts "!!!!! Test framework setup error: #{e.to_s}"
355
+ puts " " + filter_backtrace(e.backtrace).join("\n ")
356
+ puts ""
357
+ raise SystemExit
358
+ end
359
+ end
360
+
361
+ def in_online_app(fresh_flag = false, delete_all_rows = false, &block)
362
+ begin
363
+ Offroad::config_app_online(true)
364
+ @@online_database.bring_forward(self, fresh_flag)
365
+ if delete_all_rows
366
+ @@online_database.delete_all_rows
367
+ end
368
+ instance_eval &block
369
+ ensure
370
+ Offroad::config_app_online(nil)
371
+ end
372
+ end
373
+
374
+ def in_offline_app(fresh_flag = false, delete_all_rows = false, &block)
375
+ begin
376
+ Offroad::config_app_online(false)
377
+ @@offline_database.bring_forward(self, fresh_flag)
378
+ if delete_all_rows
379
+ @@offline_database.delete_all_rows
380
+ end
381
+ instance_eval &block
382
+ ensure
383
+ Offroad::config_app_online(nil)
384
+ end
385
+ end
386
+
387
+ def restore_all_from_fresh
388
+ @@offline_database.bring_forward(self, true)
389
+ @@online_database.bring_forward(self, true)
390
+ end
391
+ end
392
+
393
+ def define_wrapped_test(name, wrapper_proc, block)
394
+ method_name = "test_" + name.to_s.gsub(/[^\w ]/, '_').gsub(' ', '_')
395
+ define_method method_name.to_sym, &block
396
+ if wrapper_proc
397
+ define_method "wrapped_#{method_name}".to_sym do
398
+ wrapper_proc.call(self) { send "unwrapped_#{method_name}".to_sym }
399
+ end
400
+ alias_method "unwrapped_#{method_name}".to_sym, method_name.to_sym
401
+ alias_method method_name.to_sym, "wrapped_#{method_name}".to_sym
402
+ end
403
+ end
404
+
405
+ # Convenience methods to create tests that apply to particular environments or situations
406
+
407
+ # Test that should be run in the online environment
408
+ def online_test(name, &block)
409
+ wrapper = Proc.new do |t|
410
+ t.in_online_app(true, &block)
411
+ end
412
+
413
+ define_wrapped_test("ONLINE #{name}", wrapper, block)
414
+ end
415
+
416
+ # Test that is ran in the online environment, but with no preset records
417
+ def empty_online_test(name, &block)
418
+ wrapper = Proc.new do |t|
419
+ t.in_online_app(false, true, &block)
420
+ end
421
+
422
+ define_wrapped_test("EMPTY ONLINE #{name}", wrapper, block)
423
+ end
424
+
425
+ # Test that should be run in the offline environment
426
+ def offline_test(name, &block)
427
+ wrapper = Proc.new do |t|
428
+ t.in_offline_app(true, &block)
429
+ end
430
+
431
+ define_wrapped_test("OFFLINE #{name}", wrapper, block)
432
+ end
433
+
434
+ # Test that should be run twice, once online and once offline
435
+ def double_test(name, &block)
436
+ online_test(name, &block)
437
+ offline_test(name, &block)
438
+ end
439
+
440
+ # Test that shouldn't care what environment it is started in
441
+ def agnostic_test(name, &block)
442
+ define_wrapped_test("AGNOSTIC #{name}", nil, block)
443
+ end
444
+
445
+ # Test that involves both environments (within test, use in_online_app and in_offline_app)
446
+ def cross_test(name, &block)
447
+ wrapper = Proc.new do |t|
448
+ t.restore_all_from_fresh
449
+ t.instance_eval &block
450
+ end
451
+
452
+ define_wrapped_test("CROSS #{name}", wrapper, block)
453
+ end