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.
- data/LICENSE +674 -674
- data/README.rdoc +29 -29
- data/Rakefile +75 -75
- data/TODO +42 -42
- data/lib/app/models/offroad/group_state.rb +85 -85
- data/lib/app/models/offroad/mirror_info.rb +53 -53
- data/lib/app/models/offroad/model_state.rb +36 -36
- data/lib/app/models/offroad/received_record_state.rb +115 -115
- data/lib/app/models/offroad/sendable_record_state.rb +91 -91
- data/lib/app/models/offroad/system_state.rb +33 -33
- data/lib/cargo_streamer.rb +222 -222
- data/lib/controller_extensions.rb +74 -74
- data/lib/exceptions.rb +16 -16
- data/lib/migrate/20100512164608_create_offroad_tables.rb +72 -72
- data/lib/mirror_data.rb +376 -376
- data/lib/model_extensions.rb +378 -377
- data/lib/module_funcs.rb +94 -94
- data/lib/offroad.rb +41 -41
- data/lib/version.rb +3 -3
- data/lib/view_helper.rb +7 -7
- data/templates/offline.rb +36 -36
- data/templates/offline_database.yml +7 -7
- data/templates/offroad.yml +6 -6
- data/test/app_root/app/controllers/application_controller.rb +2 -2
- data/test/app_root/app/controllers/group_controller.rb +28 -28
- data/test/app_root/app/models/global_record.rb +10 -10
- data/test/app_root/app/models/group.rb +12 -12
- data/test/app_root/app/models/group_owned_record.rb +68 -68
- data/test/app_root/app/models/guest.rb +7 -7
- data/test/app_root/app/models/subrecord.rb +12 -12
- data/test/app_root/app/models/unmirrored_record.rb +4 -4
- data/test/app_root/app/views/group/download_down_mirror.html.erb +3 -3
- data/test/app_root/app/views/group/download_initial_down_mirror.html.erb +3 -3
- data/test/app_root/app/views/group/download_up_mirror.html.erb +5 -5
- data/test/app_root/app/views/layouts/mirror.html.erb +8 -8
- data/test/app_root/config/boot.rb +115 -115
- data/test/app_root/config/database-pg.yml +8 -8
- data/test/app_root/config/database.yml +5 -5
- data/test/app_root/config/environment.rb +24 -24
- data/test/app_root/config/environments/test.rb +17 -17
- data/test/app_root/config/offroad.yml +6 -6
- data/test/app_root/config/routes.rb +4 -4
- data/test/app_root/db/migrate/20100529235049_create_tables.rb +64 -64
- data/test/app_root/lib/common_hobo.rb +15 -15
- data/test/app_root/vendor/plugins/offroad/init.rb +2 -2
- data/test/functional/mirror_operations_test.rb +148 -148
- data/test/test_helper.rb +453 -453
- data/test/unit/app_state_tracking_test.rb +275 -275
- data/test/unit/cargo_streamer_test.rb +332 -332
- data/test/unit/global_data_test.rb +102 -102
- data/test/unit/group_controller_test.rb +152 -152
- data/test/unit/group_data_test.rb +442 -435
- data/test/unit/group_single_test.rb +136 -136
- data/test/unit/hobo_permissions_test.rb +57 -57
- data/test/unit/mirror_data_test.rb +1283 -1283
- data/test/unit/mirror_info_test.rb +31 -31
- data/test/unit/module_funcs_test.rb +37 -37
- data/test/unit/pathological_model_test.rb +62 -62
- data/test/unit/test_framework_test.rb +86 -86
- data/test/unit/unmirrored_data_test.rb +14 -14
- 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
|