560 lines
16 KiB
Ruby
Executable file
560 lines
16 KiB
Ruby
Executable file
#!/usr/local/bin/ruby -w
|
|
#
|
|
# Copyright(c) 2003-2004 Stephen Sykes
|
|
# Copyright(c) 2003-2004 Anders Bengtsson
|
|
#
|
|
|
|
$LOAD_PATH.unshift("lib")
|
|
|
|
require 'madeleine'
|
|
require 'madeleine/automatic'
|
|
require 'test/unit'
|
|
#require 'contrib/batched.rb' # uncomment if testing batched
|
|
|
|
class A
|
|
include Madeleine::Automatic::Interceptor
|
|
attr_accessor :z,:k
|
|
def initialize
|
|
@k=1
|
|
end
|
|
end
|
|
|
|
class B
|
|
include Madeleine::Automatic::Interceptor
|
|
attr_accessor :yy, :s
|
|
def initialize(a)
|
|
@yy = C.new(a)
|
|
end
|
|
end
|
|
|
|
class C
|
|
include Madeleine::Automatic::Interceptor
|
|
attr_accessor :x, :a
|
|
def initialize(x)
|
|
@x = x
|
|
@a ||= D.new
|
|
end
|
|
end
|
|
|
|
# direct changes in this class are not saved, except at snapshot
|
|
class D
|
|
attr_accessor :w
|
|
end
|
|
|
|
class F
|
|
include Madeleine::Automatic::Interceptor
|
|
attr_accessor :z,:a
|
|
def plus1
|
|
@z += 1
|
|
end
|
|
end
|
|
|
|
class G
|
|
include Madeleine::Automatic::Interceptor
|
|
attr_accessor :yy,:a
|
|
def initialize
|
|
@yy = H.new
|
|
end
|
|
end
|
|
|
|
class H
|
|
include Madeleine::Automatic::Interceptor
|
|
attr_accessor :w
|
|
def minus1
|
|
@w -= 1
|
|
end
|
|
end
|
|
|
|
class I
|
|
include Madeleine::Automatic::Interceptor
|
|
def initialize
|
|
@x = J.new
|
|
end
|
|
def testyield
|
|
r = false
|
|
@x.yielder {|c| r = true if c == 1}
|
|
r
|
|
end
|
|
end
|
|
|
|
class J
|
|
include Madeleine::Automatic::Interceptor
|
|
def yielder
|
|
yield 1
|
|
end
|
|
end
|
|
|
|
class K
|
|
include Madeleine::Automatic::Interceptor
|
|
attr_accessor :k
|
|
def initialize
|
|
@k=1
|
|
end
|
|
def seven
|
|
@k=7
|
|
end
|
|
def fourteen
|
|
@k=14
|
|
end
|
|
automatic_read_only :fourteen
|
|
automatic_read_only
|
|
def twentyone
|
|
@k=21
|
|
end
|
|
end
|
|
|
|
class L
|
|
include Madeleine::Automatic::Interceptor
|
|
attr_reader :x
|
|
def initialize
|
|
@x = M.new(self)
|
|
end
|
|
end
|
|
|
|
class M
|
|
include Madeleine::Automatic::Interceptor
|
|
attr_reader :yy
|
|
def initialize(yy)
|
|
@yy = yy
|
|
end
|
|
end
|
|
|
|
class AutoTest < Test::Unit::TestCase
|
|
|
|
def persister
|
|
SnapshotMadeleine
|
|
end
|
|
|
|
def delete_directory(directory_name)
|
|
return unless File.exist?(directory_name)
|
|
Dir.foreach(directory_name) do |file|
|
|
next if file == "."
|
|
next if file == ".."
|
|
assert(File.delete(directory_name + File::SEPARATOR + file) == 1,
|
|
"Unable to delete #{file}")
|
|
end
|
|
Dir.delete(directory_name)
|
|
end
|
|
|
|
def create_new_system(klass, dir, *arg)
|
|
delete_directory(dir)
|
|
Thread.critical = true
|
|
@system_bases << dir
|
|
Thread.critical = false
|
|
make_system(dir) { klass.new(*arg) }
|
|
end
|
|
|
|
def make_system(dir, marshaller=Marshal, &block)
|
|
AutomaticSnapshotMadeleine.new(dir, marshaller, persister, &block)
|
|
end
|
|
|
|
def prevalence_base
|
|
"AutoPrevalenceTestBase" + self.class.to_s
|
|
end
|
|
|
|
def setup
|
|
@system_bases = []
|
|
end
|
|
|
|
def teardown
|
|
@system_bases.each {|dir|
|
|
delete_directory(dir)
|
|
}
|
|
end
|
|
|
|
def simpletest(n)
|
|
pb = prevalence_base + n.to_s
|
|
mad_a = create_new_system(A, pb)
|
|
mad_a.close
|
|
mad_a1 = make_system(pb) { A.new }
|
|
assert_equal(1, mad_a1.system.k, "No commands or snapshot")
|
|
mad_a1.system.z = 0
|
|
mad_a1.system.z += 1
|
|
assert_equal(1, mad_a1.system.z, "Object changes")
|
|
mad_a1.system.z -= 10
|
|
assert_equal(-9, mad_a1.system.z, "Object changes")
|
|
mad_a1.close
|
|
mad_a2 = make_system(pb) { A.new }
|
|
assert_equal(-9, mad_a2.system.z, "Commands but no snapshot")
|
|
mad_a2.take_snapshot
|
|
mad_a2.close
|
|
mad_a3 = make_system(pb) { A.new }
|
|
assert_equal(-9, mad_a3.system.z, "Snapshot but no commands")
|
|
mad_a3.system.z -= 6
|
|
mad_a3.system.z -= 3
|
|
mad_a3.close
|
|
mad_a4 = make_system(pb) { A.new }
|
|
assert_equal(-18, mad_a4.system.z, "Snapshot and commands")
|
|
mad_a4.close
|
|
end
|
|
end
|
|
|
|
# Basic test, and that system works in SAFE level 1
|
|
class BasicTest < AutoTest
|
|
def test_main
|
|
simpletest(1)
|
|
end
|
|
|
|
def test_main_in_safe_level_one
|
|
thread = Thread.new {
|
|
$SAFE = 1
|
|
test_main
|
|
}
|
|
thread.join
|
|
end
|
|
end
|
|
|
|
class ObjectOutsideTest < AutoTest
|
|
def test_main
|
|
mad = create_new_system(A, prevalence_base)
|
|
assert_raises(RuntimeError) {
|
|
mad.system.z = A.new # app object created outside system
|
|
}
|
|
mad.close
|
|
end
|
|
end
|
|
|
|
# Passing a block when it would generate a command is not allowed because blocks cannot
|
|
# be serialised. However, block passing/yielding inside the application is ok.
|
|
class BlockGivenTest < AutoTest
|
|
def test_main
|
|
mad = create_new_system(J, prevalence_base)
|
|
assert_raises(RuntimeError) {
|
|
mad.system.yielder {|a| a}
|
|
}
|
|
mad.close
|
|
mad2 = create_new_system(I, prevalence_base+"2")
|
|
assert(mad2.system.testyield, "Internal block passing")
|
|
mad2.close
|
|
end
|
|
end
|
|
|
|
class NonPersistedObjectTest < AutoTest
|
|
def test_main
|
|
mad_b = create_new_system(B, prevalence_base, 0)
|
|
mad_b.system.yy.x -= 1
|
|
assert_equal(-1, mad_b.system.yy.x, "Direct change of object inside main object")
|
|
|
|
mad_b.system.yy.a.w ||= "hello" # not saved
|
|
mad_b.system.yy.a.w += " again" # not saved
|
|
|
|
assert_equal("hello again", mad_b.system.yy.a.w, "Non persisted object before close")
|
|
|
|
mad_b.close
|
|
mad_b2 = make_system(prevalence_base) { B.new(0) }
|
|
assert_equal(nil, mad_b2.system.yy.a.w, "Non persisted object after restart, no snapshot")
|
|
mad_b2.system.yy.a.w ||= "hello" # not saved
|
|
mad_b2.system.yy.a.w += " again" # not saved
|
|
mad_b2.take_snapshot # NOW saved
|
|
mad_b2.system.yy.a.w += " again" # not saved
|
|
assert_equal("hello again again", mad_b2.system.yy.a.w, "Non persisted object after take_snapshot and 1 change")
|
|
|
|
mad_b2.close
|
|
mad_b3 = make_system(prevalence_base) { B.new(0) }
|
|
assert_equal("hello again", mad_b3.system.yy.a.w, "Non persisted object after restore (back to snapshotted state)")
|
|
mad_b3.close
|
|
end
|
|
end
|
|
|
|
class RefInExternalObjTest < AutoTest
|
|
def test_main
|
|
mad_c = create_new_system(B, prevalence_base, 0)
|
|
x = D.new
|
|
x.w = mad_c.system.yy
|
|
mad_c.system.s = x # pass in an external object that contains a ref to obj in ths system
|
|
|
|
mad_c.system.s.w.x += 1 # Increment counter via external obj
|
|
assert_equal(1, mad_c.system.yy.x, "Change via external object")
|
|
mad_c.system.yy.x += 1 # Increment counter directly
|
|
assert_equal(2, mad_c.system.s.w.x, "Direct change")
|
|
mad_c.close
|
|
|
|
mad_c2 = make_system(prevalence_base) { B.new(0) }
|
|
assert_equal(2, mad_c2.system.s.w.x, "Value via external object after commands/restore")
|
|
assert_equal(2, mad_c2.system.yy.x, "Direct value after restore")
|
|
mad_c2.take_snapshot
|
|
mad_c2.close
|
|
|
|
mad_c3 = make_system(prevalence_base) { B.new(0) }
|
|
assert_equal(2, mad_c3.system.s.w.x, "Value via external object after snapshot/restore")
|
|
assert_equal(2, mad_c3.system.yy.x, "Direct value after snapshot/restore")
|
|
|
|
mad_c3.system.s.w.x += 1 # Increment counter via external obj
|
|
mad_c3.system.yy.x += 1 # Increment counter directly
|
|
mad_c3.close
|
|
|
|
mad_c4 = make_system(prevalence_base) { B.new(0) }
|
|
assert_equal(4, mad_c4.system.s.w.x, "Value via external object after snapshot+commands/restore")
|
|
assert_equal(4, mad_c4.system.yy.x, "Direct value after snapshot+commands/restore")
|
|
mad_c4.close
|
|
end
|
|
end
|
|
|
|
class BasicThreadSafetyTest < AutoTest
|
|
def test_main
|
|
x = Thread.new {
|
|
simpletest(1)
|
|
}
|
|
y = Thread.new {
|
|
simpletest(2)
|
|
}
|
|
x.join
|
|
y.join
|
|
end
|
|
end
|
|
|
|
class ThreadConfidenceTest < AutoTest
|
|
def test_main
|
|
mad_d = create_new_system(F, prevalence_base)
|
|
mad_d.system.z = 0
|
|
mad_e = create_new_system(G, prevalence_base+"2")
|
|
mad_e.system.yy.w = 0
|
|
|
|
x = []
|
|
25.times {|n|
|
|
x[n] = Thread.new {
|
|
5.times {
|
|
sleep(rand/10)
|
|
mad_d.system.plus1
|
|
mad_e.system.yy.minus1
|
|
}
|
|
}
|
|
}
|
|
25.times {|n|
|
|
x[n].join
|
|
}
|
|
assert_equal(125, mad_d.system.z, "125 commands")
|
|
assert_equal(-125, mad_e.system.yy.w, "125 commands")
|
|
|
|
mad_e.close
|
|
mad_e2 = make_system(prevalence_base+"2") { G.new }
|
|
|
|
25.times {|n|
|
|
x[n] = Thread.new {
|
|
5.times {
|
|
sleep(rand/10)
|
|
mad_d.system.plus1
|
|
mad_e2.system.yy.minus1
|
|
}
|
|
}
|
|
}
|
|
25.times {|n|
|
|
x[n].join
|
|
}
|
|
assert_equal(250, mad_d.system.z, "restore/125 commands")
|
|
assert_equal(-250, mad_e2.system.yy.w, "restore/125 commands")
|
|
mad_d.close
|
|
mad_e2.close
|
|
end
|
|
end
|
|
|
|
class InvalidMethodTest < AutoTest
|
|
def test_main
|
|
mad_f = create_new_system(A, prevalence_base)
|
|
mad_f.system.z = -1
|
|
assert_raises(NoMethodError) {
|
|
mad_f.system.not_a_method
|
|
}
|
|
assert_equal(-1, mad_f.system.z, "System functions after NoMethodError")
|
|
mad_f.close
|
|
end
|
|
end
|
|
|
|
class CircularReferenceTest < AutoTest
|
|
def test_main
|
|
mad_g = create_new_system(G, prevalence_base)
|
|
mad_g.system.yy.w = mad_g.system
|
|
mad_g.close
|
|
mad_g2 = make_system(prevalence_base) { G.new }
|
|
assert(mad_g2.system == mad_g2.system.yy.w.yy.w.yy.w, "Circular reference after command/restore")
|
|
mad_g2.take_snapshot
|
|
mad_g2.close
|
|
mad_g3 = make_system(prevalence_base) { G.new }
|
|
assert(mad_g3.system == mad_g3.system.yy.w.yy.w.yy.w, "Circular reference after snapshot/restore")
|
|
mad_g3.system.yy.w.yy.w.yy.w.a = 1
|
|
assert_equal(1, mad_g3.system.a, "Circular reference change")
|
|
mad_g3.close
|
|
mad_g4 = make_system(prevalence_base) { G.new }
|
|
assert_equal(1, mad_g4.system.yy.w.yy.w.yy.w.a, "Circular reference after snapshot+commands/restore")
|
|
mad_g4.close
|
|
# The following tests would fail, cannot pass self (from class L to class M during init)
|
|
# self is the proxied object itself, not the Prox object it needs to be
|
|
mad_l = create_new_system(L, prevalence_base)
|
|
# assert_equal(mad_l.system, mad_l.system.x.yy, "Circular ref before snapshot/restore, passed self")
|
|
mad_l.take_snapshot
|
|
mad_l.close
|
|
mad_l = make_system(prevalence_base) { L.new }
|
|
# assert_equal(mad_l.system, mad_l.system.x.yy, "Circular ref after snapshot/restore, passed self")
|
|
mad_l.close
|
|
end
|
|
end
|
|
|
|
class AutomaticCustomMarshallerTest < AutoTest
|
|
def test_main
|
|
custom_m(YAML)
|
|
custom_m(SOAP::Marshal)
|
|
custom_m(Madeleine::ZMarshal.new)
|
|
custom_m(Madeleine::ZMarshal.new(YAML))
|
|
custom_m(Madeleine::ZMarshal.new(SOAP::Marshal))
|
|
end
|
|
|
|
def custom_m(marshaller)
|
|
dir = prevalence_base
|
|
delete_directory(dir)
|
|
@system_bases << dir
|
|
mad_h = make_system(dir) { G.new }
|
|
mad_h.system.yy.w = "abc"
|
|
mad_h.take_snapshot
|
|
mad_h.system.yy.w += "d"
|
|
assert_equal("abcd", mad_h.system.yy.w, "Custom marshalling after snapshot+commands with normal marshaller")
|
|
mad_h.close
|
|
mad_h = make_system(dir, marshaller) { G.new }
|
|
assert_equal("abcd", mad_h.system.yy.w, "Custom marshalling after snapshot+commands with normal marshaller, read with custom as marshaller")
|
|
mad_h.close
|
|
mad_h = make_system(dir) { G.new }
|
|
mad_h.marshaller = marshaller
|
|
mad_h.system.yy.w += "e"
|
|
assert_equal("abcde", mad_h.system.yy.w, "Custom marshalling after snapshot+commands+change marshaller+commands")
|
|
mad_h.take_snapshot
|
|
mad_h.close
|
|
if (marshaller == YAML)
|
|
File.open(dir + "/000000000000000000002.snapshot", "r") {|f|
|
|
assert_equal(f.gets, "--- !ruby/object:Madeleine::Automatic::Prox \n", "Custom marshalling marshaller change check")
|
|
}
|
|
end
|
|
mad_h = make_system(dir, marshaller) { G.new }
|
|
assert_equal("abcde", mad_h.system.yy.w,
|
|
"Custom marshalling after snapshot+commands+change marshaller+commands+snapshot+restore with normal marshaller")
|
|
mad_h.system.yy.w += "f"
|
|
mad_h.close
|
|
mad_h = make_system(dir) { G.new }
|
|
assert_equal("abcdef", mad_h.system.yy.w, "Custom marshalling snapshot custom+commands+restore normal")
|
|
mad_h.take_snapshot
|
|
mad_h.close
|
|
mad_h = make_system(dir, marshaller) { G.new }
|
|
assert_equal("abcdef", mad_h.system.yy.w, "Custom marshalling snapshot+restore custom")
|
|
mad_h.take_snapshot
|
|
mad_h.system.yy.w += "g"
|
|
mad_h.close
|
|
mad_h = make_system(dir, marshaller) { G.new }
|
|
assert_equal("abcdefg", mad_h.system.yy.w, "Custom marshalling after restore normal snapshot custom+commands+restore custom")
|
|
mad_h.system.yy.w = "abc"
|
|
mad_h.close
|
|
mad_h2 = make_system(dir, marshaller) { G.new }
|
|
assert_equal("abc", mad_h2.system.yy.w, "Custom marshalling after commands/restore")
|
|
mad_h2.take_snapshot
|
|
mad_h2.close
|
|
mad_h3 = make_system(dir, marshaller) { G.new }
|
|
assert_equal("abc", mad_h3.system.yy.w, "Custom marshalling after snapshot/restore")
|
|
mad_h3.system.yy.w += "d"
|
|
mad_h3.close
|
|
mad_h4 = make_system(dir, marshaller) { G.new }
|
|
assert_equal("abcd", mad_h4.system.yy.w, "Custom marshalling after snapshot+commands/restore")
|
|
mad_h4.close
|
|
mad_h = make_system(dir, marshaller) { G.new }
|
|
mad_h.system.yy.w = mad_h.system
|
|
mad_h.close
|
|
mad_h2 = make_system(dir, marshaller) { G.new }
|
|
assert_equal(mad_h2.system, mad_h2.system.yy.w, "Custom marshalling after commands/restore, circular ref")
|
|
mad_h2.take_snapshot
|
|
mad_h2.close
|
|
mad_h3 = make_system(dir, marshaller) { G.new }
|
|
assert_equal(mad_h3.system, mad_h3.system.yy.w, "Custom marshalling after snapshot/restore, circular ref")
|
|
mad_h3.system.yy.w = "sss"
|
|
mad_h3.system.yy.w = mad_h3.system
|
|
mad_h3.close
|
|
mad_h4 = make_system(dir, marshaller) { G.new }
|
|
assert_equal(mad_h4.system, mad_h4.system.yy.w, "Custom marshalling after snapshot+commands/restore, circular ref")
|
|
mad_h4.close
|
|
end
|
|
end
|
|
|
|
# tests thread safety during system creation, particularly that different system ids are generated
|
|
class ThreadedStartupTest < AutoTest
|
|
def test_main
|
|
x,mad = [],[]
|
|
20.times {|n|
|
|
x[n] = Thread.new {
|
|
sleep(rand/100)
|
|
mad[n] = create_new_system(F, prevalence_base+n.to_s)
|
|
mad[n].system.z = n
|
|
assert_equal(n, mad[n].system.z, "object change mad[#{n}].z")
|
|
mad[n].close
|
|
}
|
|
}
|
|
20.times {|n|
|
|
x[n].join
|
|
mad_i = make_system(prevalence_base+n.to_s) { F.new }
|
|
assert_equal(n, mad_i.system.z, "restored mad[#{n}].z")
|
|
mad_i.close
|
|
}
|
|
end
|
|
end
|
|
|
|
# tests restoring when objects get unreferenced and GC'd during restore
|
|
class FinalisedTest < AutoTest
|
|
def test_main
|
|
mad = create_new_system(B, prevalence_base, 0)
|
|
mad.system.yy = Array.new(200000) # make ruby run GC
|
|
mad.system.yy = Array.new(200000) # must be a better way, but running GC.start from inside
|
|
mad.system.yy = Array.new(50000) # class B didn't work for me
|
|
mad.close
|
|
mad2 = make_system(prevalence_base) { B.new(0) }
|
|
mad2.close
|
|
end
|
|
end
|
|
|
|
class DontInterceptTest < AutoTest
|
|
def test_main
|
|
mad = create_new_system(K, prevalence_base)
|
|
mad.system.seven
|
|
assert_equal(7, mad.system.k, "Object changes")
|
|
mad.system.fourteen
|
|
assert_equal(14, mad.system.k, "Object changes, not intercepted")
|
|
mad.system.twentyone
|
|
assert_equal(21, mad.system.k, "Object changes, not intercepted")
|
|
mad.close
|
|
mad_1 = make_system(prevalence_base) { K.new }
|
|
assert_equal(7, mad_1.system.k, "Commands but no snapshot")
|
|
mad_1.take_snapshot
|
|
mad_1.close
|
|
mad_2 = make_system(prevalence_base) { K.new }
|
|
assert_equal(7, mad_2.system.k, "Snapshot but no commands")
|
|
mad_2.system.k -= 6
|
|
mad_2.system.k -= 3
|
|
mad_2.system.fourteen
|
|
mad_2.close
|
|
mad_3 = make_system(prevalence_base) { K.new }
|
|
assert_equal(-2, mad_3.system.k, "Snapshot and commands")
|
|
mad_3.close
|
|
end
|
|
end
|
|
|
|
def add_automatic_tests(suite)
|
|
suite << BasicTest.suite
|
|
suite << ObjectOutsideTest.suite
|
|
suite << BlockGivenTest.suite
|
|
suite << NonPersistedObjectTest.suite
|
|
suite << RefInExternalObjTest.suite
|
|
suite << InvalidMethodTest.suite
|
|
suite << CircularReferenceTest.suite
|
|
suite << BasicThreadSafetyTest.suite
|
|
suite << FinalisedTest.suite
|
|
suite << DontInterceptTest.suite
|
|
suite << AutomaticCustomMarshallerTest.suite
|
|
end
|
|
|
|
def add_slow_automatic_tests(suite)
|
|
suite << ThreadConfidenceTest.suite
|
|
suite << ThreadedStartupTest.suite
|
|
end
|
|
|
|
if __FILE__ == $0
|
|
slowsuite = Test::Unit::TestSuite.new("AutomaticMadeleine (including slow tests)")
|
|
add_automatic_tests(slowsuite)
|
|
add_slow_automatic_tests(slowsuite)
|
|
|
|
require 'test/unit/ui/console/testrunner'
|
|
Test::Unit::UI::Console::TestRunner.run(slowsuite)
|
|
end
|