Deleting Madeleine... with pleasure (it is cool, but not for wiki data)
This commit is contained in:
parent
b94559bc4c
commit
2c7a2779c7
|
@ -7,7 +7,7 @@ spec = Gem::Specification.new do |s|
|
|||
s.platform = Gem::Platform::RUBY
|
||||
s.name = 'instiki'
|
||||
s.version = "0.10.2"
|
||||
s.summary = 'Easy to install WikiClone running on WEBrick and Madeleine'
|
||||
s.summary = 'Easy to install WikiClone running on WEBrick and SQLite'
|
||||
s.description = <<-EOF
|
||||
Instiki is a Wiki Clone written in Ruby that ships with an embedded
|
||||
webserver. You can setup up an Instiki in just a few steps.
|
||||
|
@ -24,10 +24,10 @@ spec = Gem::Specification.new do |s|
|
|||
|
||||
s.has_rdoc = false
|
||||
|
||||
s.add_dependency('madeleine', '= 0.7.1')
|
||||
s.add_dependency('RedCloth', '= 3.0.3')
|
||||
s.add_dependency('rubyzip', '= 0.5.8')
|
||||
s.add_dependency('rails', '= 0.13.1')
|
||||
s.add_dependency('sqlite3-ruby', '= 1.1.0')
|
||||
s.requirements << 'none'
|
||||
s.require_path = 'lib'
|
||||
|
||||
|
|
2
vendor/madeleine-0.7.1/.cvsignore
vendored
2
vendor/madeleine-0.7.1/.cvsignore
vendored
|
@ -1,2 +0,0 @@
|
|||
PrevalenceBase
|
||||
*.gem
|
31
vendor/madeleine-0.7.1/COPYING
vendored
31
vendor/madeleine-0.7.1/COPYING
vendored
|
@ -1,31 +0,0 @@
|
|||
|
||||
Copyright (c) 2003-2004, Anders Bengtsson
|
||||
|
||||
Redistribution and use in source and binary forms, with or without
|
||||
modification, are permitted provided that the following conditions
|
||||
are met:
|
||||
|
||||
1. Redistributions of source code must retain the above copyright
|
||||
notice, this list of conditions and the following disclaimer.
|
||||
|
||||
2. Redistributions in binary form must reproduce the above copyright
|
||||
notice, this list of conditions and the following disclaimer in the
|
||||
documentation and/or other materials provided with the distribution.
|
||||
|
||||
3. The names of its contributors may not be used to endorse or promote
|
||||
products derived from this software without specific prior written
|
||||
permission.
|
||||
|
||||
THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND
|
||||
CONTRIBUTORS "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES,
|
||||
INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF
|
||||
MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
|
||||
DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS
|
||||
BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL,
|
||||
EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED
|
||||
TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
|
||||
DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON
|
||||
ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR
|
||||
TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF
|
||||
THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF
|
||||
SUCH DAMAGE.
|
55
vendor/madeleine-0.7.1/NEWS
vendored
55
vendor/madeleine-0.7.1/NEWS
vendored
|
@ -1,55 +0,0 @@
|
|||
|
||||
Madeleine 0.7.1 (August 22, 2004):
|
||||
|
||||
* ZMarshal changed to work around Zlib bug.
|
||||
* automatic_read_only fixed when intercepted class is inherited from
|
||||
|
||||
Madeleine 0.7 (July 23, 2004):
|
||||
|
||||
* Broken clock unit test on win32 fixed.
|
||||
* AutomaticSnapshotMadeleine detects snapshot format on recovery
|
||||
* Snapshot compression with Madeleine::ZMarshal
|
||||
* YAML snapshots supported for automatic commands
|
||||
* SOAP snapshots supported for automatic commands
|
||||
* Read-only methods for automatic commands
|
||||
|
||||
Madeleine 0.6.1 (March 30, 2004):
|
||||
|
||||
* Bug fix: Use binary mode for I/O, fixes log replay
|
||||
on mswin32 port of Ruby (Patch from Martin Tampe)
|
||||
|
||||
Madeleine 0.6 (March 28, 2004):
|
||||
|
||||
* Changed license to BSD
|
||||
* Added a RubyGem specification
|
||||
* Re-designed initialization (but still backward-compatible)
|
||||
* Bug fix: Fixed use of finalized object's id in AutomaticSnapshotMadeleine
|
||||
|
||||
Madeleine 0.5 (August 31, 2003):
|
||||
|
||||
* Bug fix: Log order on recovery was wrong on some platforms
|
||||
(Reported by IIMA Susumu)
|
||||
* No longer requires the system clock to always increase
|
||||
* Shared locks for queries
|
||||
|
||||
Madeleine 0.4 (July 4, 2003):
|
||||
|
||||
* Deprecated ClockedSnapshotMadeleine
|
||||
* Added execute_query()
|
||||
* API documentation in RDoc format
|
||||
|
||||
Madeleine 0.3 (May 15, 2003):
|
||||
|
||||
* Automatic commands
|
||||
* Some classes exported to the default module
|
||||
* Clock support not loaded by default (require 'madeleine/clock')
|
||||
* Bug fix: Error handling when replaying logged commands.
|
||||
* New system through block instead of argument (API change)
|
||||
* Works in $SAFE = 1
|
||||
|
||||
Madeleine 0.2:
|
||||
|
||||
* Supports custom marshalling implementations.
|
||||
* Changed interface for ClockedSystem and Clock.
|
||||
* Some documentation added, including API docs.
|
||||
|
78
vendor/madeleine-0.7.1/README
vendored
78
vendor/madeleine-0.7.1/README
vendored
|
@ -1,78 +0,0 @@
|
|||
|
||||
Madeleine is a Ruby implementation of Object Prevalence: Transparent
|
||||
persistence of business objects using command logging and complete
|
||||
system snapshots.
|
||||
|
||||
<http://madeleine.sourceforge.net/>
|
||||
|
||||
Madeleine's design is based on Prevayler, the original Java
|
||||
prevalence layer.
|
||||
|
||||
Learn more about object prevalence at <http://www.prevayler.org/>.
|
||||
|
||||
|
||||
Installation:
|
||||
|
||||
Typical installation procedure is:
|
||||
$ ruby install.rb config
|
||||
$ ruby install.rb setup
|
||||
# ruby install.rb install (may require root privilege)
|
||||
Try 'ruby install.rb --help' for detailed usage.
|
||||
|
||||
[From the documentation of Minero Aoki's 'install.rb']
|
||||
|
||||
Usage:
|
||||
|
||||
require 'madeleine'
|
||||
|
||||
# Create an application as a prevalent system
|
||||
|
||||
madeleine = SnapshotMadeleine.new("my_example_storage") {
|
||||
SomeExampleApplication.new()
|
||||
}
|
||||
|
||||
# Do modifications of the system by sending commands through
|
||||
# the Madeleine instance. A command is an object with a suitable
|
||||
# "execute(system)" method.
|
||||
|
||||
madeleine.execute_command(command)
|
||||
|
||||
|
||||
Requirements:
|
||||
|
||||
* Ruby 1.8.1 or later
|
||||
|
||||
Additionaly, some of the sample code also uses ruby/tk.
|
||||
|
||||
|
||||
Known problems:
|
||||
|
||||
* Won't run in some Windows-ports of Ruby due to missing
|
||||
fsync() call.
|
||||
|
||||
Contact:
|
||||
|
||||
Homepage:
|
||||
<http://madeleine.sourceforge.net/>
|
||||
|
||||
Questions, bug reports, patches, complaints? Use the mailing list:
|
||||
<http://lists.sourceforge.net/lists/listinfo/madeleine-devel>
|
||||
|
||||
License:
|
||||
|
||||
BSD (see the file COPYING)
|
||||
|
||||
Credits:
|
||||
|
||||
Anders Bengtsson - Prevalence core impl.
|
||||
Stephen Sykes - Automatic commands impl.
|
||||
|
||||
With the help of patches, testing and feedback from:
|
||||
|
||||
Steve Conover, David Heinemeier Hansson, Johan Lind, Håkan Råberg,
|
||||
IIMA Susumu, Martin Tampe and Jon Tirsén
|
||||
|
||||
Thanks to Klaus Wuestefeld and the Prevayler developers for the
|
||||
model of this software; to Minero Aoki for the installer; to Matz and
|
||||
the core developers for the Ruby language!
|
||||
|
23
vendor/madeleine-0.7.1/TODO
vendored
23
vendor/madeleine-0.7.1/TODO
vendored
|
@ -1,23 +0,0 @@
|
|||
|
||||
|
||||
- Fix broken time-dependent unit test
|
||||
* Rolling snapshots, with age limit
|
||||
- Compressed snapshots
|
||||
- Full support for YAML snapshots
|
||||
- SOAP marshalling
|
||||
* Configurable log marshaller (or use the snapshot marshaller?)
|
||||
* Write a document about the different marshallers, for app. developers.
|
||||
|
||||
* Move all default implementations into a "Default" module
|
||||
* Introduce an object representing a log directory
|
||||
* Move recovery out of DefaultSnapshotMadeleine entirely
|
||||
* Write an example with a web server
|
||||
|
||||
* Replace filesystem with mock objects for unit testing.
|
||||
* ClockCommand
|
||||
* Integrate batched-writes in SnapshotMadeleine
|
||||
* More sample code
|
||||
* More documentation
|
||||
* DRb integration
|
||||
* Rollback
|
||||
* Handle broken logs?
|
298
vendor/madeleine-0.7.1/contrib/batched.rb
vendored
298
vendor/madeleine-0.7.1/contrib/batched.rb
vendored
|
@ -1,298 +0,0 @@
|
|||
# Batched writes for Madeleine
|
||||
#
|
||||
# Copyright(c) Håkan Råberg 2003
|
||||
#
|
||||
#
|
||||
# This is an experimental implementation of batched log writes to mininize
|
||||
# calls to fsync. It uses a Shared/Exclusive-Lock, implemented in sync.rb,
|
||||
# which is included in Ruby 1.8.
|
||||
#
|
||||
# Writes are batched for a specified amount of time, before written to disk and
|
||||
# then executed.
|
||||
#
|
||||
# For a detailed discussion about the problem, see
|
||||
# http://www.prevayler.org/wiki.jsp?topic=OvercomingTheWriteBottleneck
|
||||
#
|
||||
#
|
||||
# Usage is identical to normal SnapshotMadeleine, and it can also be used as
|
||||
# persister for AutomaticSnapshotMadeleine. (One difference: the log isn't
|
||||
# visible on disk until any commands are executed.)
|
||||
#
|
||||
# You can also use the execute_query method for shared synchronzied queries,
|
||||
# for eaay coarse-grained locking of the system.
|
||||
#
|
||||
# The exclusive lock is only locked during the actual execution of commands and
|
||||
# while closing.
|
||||
#
|
||||
# Keeping both log writes and executes of commands in the originating thread
|
||||
# is needed by AutomaticSnapshotPrevayler. Hence the strange SimplisticPipe
|
||||
# class.
|
||||
#
|
||||
# Todo:
|
||||
# - It seems like Sync (sync.rb) prefers shared locks. This should probably
|
||||
# be changed.
|
||||
#
|
||||
#
|
||||
# Madeleine - Ruby Object Prevalence
|
||||
#
|
||||
# Copyright(c) Anders Bengtsson 2003
|
||||
#
|
||||
|
||||
require 'madeleine'
|
||||
require 'madeleine/clock'
|
||||
|
||||
include Madeleine::Clock
|
||||
|
||||
module Madeleine
|
||||
module Batch
|
||||
class BatchedSnapshotMadeleine < SnapshotMadeleine
|
||||
|
||||
def initialize(directory_name, marshaller=Marshal, &new_system_block)
|
||||
super(directory_name, marshaller, &new_system_block)
|
||||
@log_actor = LogActor.launch(self)
|
||||
end
|
||||
|
||||
def execute_command(command)
|
||||
verify_command_sane(command)
|
||||
queued_command = QueuedCommand.new(command)
|
||||
@lock.synchronize(:SH) do
|
||||
raise "closed" if @closed
|
||||
@logger.store(queued_command)
|
||||
end
|
||||
queued_command.wait_for
|
||||
end
|
||||
|
||||
def execute_query(query)
|
||||
verify_command_sane(query)
|
||||
@lock.synchronize(:SH) do
|
||||
execute_without_storing(query)
|
||||
end
|
||||
end
|
||||
|
||||
def close
|
||||
@log_actor.destroy
|
||||
@lock.synchronize do
|
||||
@logger.close
|
||||
@closed = true
|
||||
end
|
||||
end
|
||||
|
||||
def flush
|
||||
@lock.synchronize do
|
||||
@logger.flush
|
||||
end
|
||||
end
|
||||
|
||||
def take_snapshot
|
||||
@lock.synchronize(:SH) do
|
||||
@lock.synchronize do
|
||||
@logger.close
|
||||
end
|
||||
Snapshot.new(@directory_name, system, @marshaller).take
|
||||
@logger.reset
|
||||
end
|
||||
end
|
||||
|
||||
private
|
||||
|
||||
def create_lock
|
||||
Sync.new
|
||||
end
|
||||
|
||||
def create_logger(directory_name, log_factory)
|
||||
BatchedLogger.new(directory_name, log_factory, self.system)
|
||||
end
|
||||
|
||||
def log_factory
|
||||
BatchedLogFactory.new
|
||||
end
|
||||
end
|
||||
|
||||
private
|
||||
|
||||
class LogActor
|
||||
def self.launch(madeleine, delay=0.01)
|
||||
result = new(madeleine, delay)
|
||||
result
|
||||
end
|
||||
|
||||
def destroy
|
||||
@is_destroyed = true
|
||||
if @thread.alive?
|
||||
@thread.wakeup
|
||||
@thread.join
|
||||
end
|
||||
end
|
||||
|
||||
private
|
||||
|
||||
def initialize(madeleine, delay)
|
||||
@is_destroyed = false
|
||||
|
||||
madeleine.flush
|
||||
@thread = Thread.new {
|
||||
until @is_destroyed
|
||||
sleep(delay)
|
||||
madeleine.flush
|
||||
end
|
||||
}
|
||||
end
|
||||
end
|
||||
|
||||
class BatchedLogFactory
|
||||
def create_log(directory_name)
|
||||
BatchedLog.new(directory_name)
|
||||
end
|
||||
end
|
||||
|
||||
class BatchedLogger < Logger
|
||||
def initialize(directory_name, log_factory, system)
|
||||
super(directory_name, log_factory)
|
||||
@buffer = []
|
||||
@system = system
|
||||
end
|
||||
|
||||
def store(queued_command)
|
||||
@buffer << queued_command
|
||||
end
|
||||
|
||||
def close
|
||||
return if @log.nil?
|
||||
flush
|
||||
@log.close
|
||||
@log = nil
|
||||
end
|
||||
|
||||
def flush
|
||||
return if @buffer.empty?
|
||||
|
||||
open_new_log if @log.nil?
|
||||
|
||||
if @system.kind_of?(ClockedSystem)
|
||||
@buffer.unshift(QueuedTick.new)
|
||||
end
|
||||
|
||||
@buffer.each do |queued_command|
|
||||
queued_command.store(@log)
|
||||
end
|
||||
|
||||
@log.flush
|
||||
|
||||
@buffer.each do |queued_command|
|
||||
queued_command.execute(@system)
|
||||
end
|
||||
|
||||
@buffer.clear
|
||||
end
|
||||
end
|
||||
|
||||
class BatchedLog < CommandLog
|
||||
def store(command)
|
||||
Marshal.dump(command, @file)
|
||||
end
|
||||
|
||||
def flush
|
||||
@file.flush
|
||||
@file.fsync
|
||||
end
|
||||
end
|
||||
|
||||
class QueuedCommand
|
||||
def initialize(command)
|
||||
@command = command
|
||||
@pipe = SimplisticPipe.new
|
||||
end
|
||||
|
||||
def store(log)
|
||||
@pipe.write(log)
|
||||
end
|
||||
|
||||
def execute(system)
|
||||
@pipe.write(system)
|
||||
end
|
||||
|
||||
def wait_for
|
||||
@pipe.read do |log|
|
||||
log.store(@command)
|
||||
end
|
||||
|
||||
@pipe.read do |system|
|
||||
return @command.execute(system)
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
class QueuedTick
|
||||
def initialize
|
||||
@tick = Tick.new(Time.now)
|
||||
end
|
||||
|
||||
def store(log)
|
||||
log.store(@tick)
|
||||
end
|
||||
|
||||
def execute(system)
|
||||
@tick.execute(system)
|
||||
end
|
||||
end
|
||||
|
||||
class SimplisticPipe
|
||||
def initialize
|
||||
@receive_lock = Mutex.new.lock
|
||||
@consume_lock = Mutex.new.lock
|
||||
@message = nil
|
||||
end
|
||||
|
||||
def read
|
||||
begin
|
||||
wait_for_message_received
|
||||
|
||||
if block_given?
|
||||
yield @message
|
||||
else
|
||||
return @message
|
||||
end
|
||||
|
||||
ensure
|
||||
message_consumed
|
||||
end
|
||||
end
|
||||
|
||||
def write(message)
|
||||
raise WriteBlockedException unless can_write?
|
||||
|
||||
@message = message
|
||||
message_received
|
||||
wait_for_message_consumed
|
||||
@message = nil
|
||||
end
|
||||
|
||||
def can_write?
|
||||
@message.nil?
|
||||
end
|
||||
|
||||
private
|
||||
|
||||
def message_received
|
||||
@receive_lock.unlock
|
||||
end
|
||||
|
||||
def wait_for_message_received
|
||||
@receive_lock.lock
|
||||
end
|
||||
|
||||
def message_consumed
|
||||
@consume_lock.unlock
|
||||
end
|
||||
|
||||
def wait_for_message_consumed
|
||||
@consume_lock.lock
|
||||
end
|
||||
end
|
||||
|
||||
class WriteBlockedException < Exception
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
BatchedSnapshotMadeleine = Madeleine::Batch::BatchedSnapshotMadeleine
|
35
vendor/madeleine-0.7.1/contrib/benchmark.rb
vendored
35
vendor/madeleine-0.7.1/contrib/benchmark.rb
vendored
|
@ -1,35 +0,0 @@
|
|||
#!/usr/local/bin/ruby -w
|
||||
|
||||
$LOAD_PATH.unshift("../lib")
|
||||
|
||||
require 'madeleine'
|
||||
require 'batched'
|
||||
|
||||
class BenchmarkCommand
|
||||
def initialize(value)
|
||||
@value = value
|
||||
end
|
||||
|
||||
def execute(system)
|
||||
# do nothing
|
||||
end
|
||||
end
|
||||
|
||||
madeleine = BatchedSnapshotMadeleine.new("benchmark-base") { :the_system }
|
||||
|
||||
RUNS = 2000
|
||||
|
||||
GC.start
|
||||
GC.disable
|
||||
|
||||
t0 = Time.now
|
||||
RUNS.times {
|
||||
madeleine.execute_command(BenchmarkCommand.new(1234))
|
||||
}
|
||||
t1 = Time.now
|
||||
|
||||
GC.enable
|
||||
|
||||
tps = RUNS/(t1 - t0)
|
||||
|
||||
puts "#{tps.to_i} transactions/s"
|
245
vendor/madeleine-0.7.1/contrib/test_batched.rb
vendored
245
vendor/madeleine-0.7.1/contrib/test_batched.rb
vendored
|
@ -1,245 +0,0 @@
|
|||
#!/usr/local/bin/ruby -w
|
||||
#
|
||||
# Copyright(c) 2003 Håkan Råberg
|
||||
#
|
||||
# Some components taken from test_persistence.rb
|
||||
# Copyright(c) 2003 Anders Bengtsson
|
||||
#
|
||||
|
||||
$LOAD_PATH.unshift("../lib")
|
||||
|
||||
require 'batched'
|
||||
require 'test/unit'
|
||||
require 'madeleine/clock'
|
||||
|
||||
|
||||
module Madeleine::Batch
|
||||
class BatchedSnapshotMadeleineTest < Test::Unit::TestCase
|
||||
|
||||
class ArraySystem < Array
|
||||
include Madeleine::Clock::ClockedSystem
|
||||
end
|
||||
|
||||
class PushCommand
|
||||
def initialize(value)
|
||||
@value = value
|
||||
end
|
||||
|
||||
def execute(system)
|
||||
system << @value
|
||||
end
|
||||
end
|
||||
|
||||
class ArrayQuery
|
||||
def initialize
|
||||
@time = []
|
||||
end
|
||||
|
||||
def execute(system)
|
||||
length = system.length
|
||||
@time << system.clock.time
|
||||
|
||||
a = 1
|
||||
system.each do |n|
|
||||
a *= n
|
||||
end
|
||||
|
||||
raise "inconsistent read" unless length == system.length
|
||||
raise "inconsistent read" unless @time.last == system.clock.time
|
||||
end
|
||||
end
|
||||
|
||||
def test_live_snapshot
|
||||
system = ArraySystem.new
|
||||
w, r = [], []
|
||||
going = true
|
||||
|
||||
madeleine = BatchedSnapshotMadeleine.new(prevalence_base) { system }
|
||||
|
||||
i = 0
|
||||
10.times do |n|
|
||||
w[n] = Thread.new {
|
||||
while going
|
||||
madeleine.execute_command(PushCommand.new(i))
|
||||
i += 1
|
||||
sleep(0.1)
|
||||
end
|
||||
}
|
||||
end
|
||||
|
||||
q = 0
|
||||
query = ArrayQuery.new
|
||||
100.times do |n|
|
||||
r[n] = Thread.new {
|
||||
while going
|
||||
begin
|
||||
madeleine.execute_query(query)
|
||||
q += 1
|
||||
rescue
|
||||
fail("Query blocks writing")
|
||||
end
|
||||
sleep(0.1)
|
||||
end
|
||||
}
|
||||
end
|
||||
|
||||
s = 0
|
||||
snapshot = Thread.new {
|
||||
while going
|
||||
madeleine.take_snapshot
|
||||
s += 1
|
||||
sleep(0.01)
|
||||
end
|
||||
}
|
||||
|
||||
sleep(1)
|
||||
|
||||
going = false
|
||||
|
||||
r.each do |t|
|
||||
t.join
|
||||
end
|
||||
|
||||
w.each do |t|
|
||||
t.join
|
||||
end
|
||||
|
||||
snapshot.join
|
||||
|
||||
madeleine.close
|
||||
|
||||
madeleine2 = SnapshotMadeleine.new(prevalence_base)
|
||||
assert_equal(madeleine.system, madeleine2.system, "Take system snapshots while accessing")
|
||||
end
|
||||
|
||||
def prevalence_base
|
||||
"BatchedSnapshot"
|
||||
end
|
||||
|
||||
def teardown
|
||||
delete_directory(prevalence_base)
|
||||
end
|
||||
end
|
||||
|
||||
class BatchedLogTest < Test::Unit::TestCase
|
||||
|
||||
class MockMadeleine
|
||||
def initialize(logger)
|
||||
@logger = logger
|
||||
end
|
||||
|
||||
def flush
|
||||
@logger.flush
|
||||
end
|
||||
end
|
||||
|
||||
class MockCommand
|
||||
attr_reader :text
|
||||
|
||||
def initialize(text)
|
||||
@text = text
|
||||
end
|
||||
|
||||
def execute(system)
|
||||
end
|
||||
|
||||
def ==(o)
|
||||
o.text == @text
|
||||
end
|
||||
end
|
||||
|
||||
module BufferInspector
|
||||
def buffer_size
|
||||
@buffer.size
|
||||
end
|
||||
end
|
||||
|
||||
def setup
|
||||
@target = BatchedLogger.new(".", BatchedLogFactory.new, nil)
|
||||
@target.extend(BufferInspector)
|
||||
@madeleine = MockMadeleine.new(@target)
|
||||
@messages = []
|
||||
end
|
||||
|
||||
def test_logging
|
||||
actor = LogActor.launch(@madeleine, 0.1)
|
||||
|
||||
append("Hello")
|
||||
sleep(0.01)
|
||||
append("World")
|
||||
sleep(0.01)
|
||||
|
||||
assert_equal(2, @target.buffer_size, "Batched command queue")
|
||||
assert(!File.exist?(expected_file_name), "Batched commands not on disk")
|
||||
|
||||
sleep(0.2)
|
||||
|
||||
assert_equal(0, @target.buffer_size, "Queue emptied by batched write")
|
||||
file_size = File.size(expected_file_name)
|
||||
assert(file_size > 0, "Queue written to disk")
|
||||
|
||||
append("Again")
|
||||
sleep(0.2)
|
||||
|
||||
assert(File.size(expected_file_name) > file_size, "Command written to disk")
|
||||
|
||||
f = File.new(expected_file_name)
|
||||
|
||||
@messages.each do |message|
|
||||
assert_equal(message, Marshal.load(f), "Commands logged in order")
|
||||
end
|
||||
|
||||
f.close
|
||||
|
||||
actor.destroy
|
||||
@target.flush
|
||||
@target.close
|
||||
|
||||
end
|
||||
|
||||
def append(text)
|
||||
Thread.new {
|
||||
message = MockCommand.new(text)
|
||||
@messages << message
|
||||
queued_command = QueuedCommand.new(message)
|
||||
@target.store(queued_command)
|
||||
queued_command.wait_for
|
||||
}
|
||||
end
|
||||
|
||||
def expected_file_name
|
||||
"000000000000000000001.command_log"
|
||||
end
|
||||
|
||||
def teardown
|
||||
assert(File.delete(expected_file_name) == 1)
|
||||
end
|
||||
|
||||
end
|
||||
|
||||
def delete_directory(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
|
||||
end
|
||||
|
||||
include Madeleine::Batch
|
||||
|
||||
def add_batched_tests(suite)
|
||||
suite << BatchedSnapshotMadeleineTest.suite
|
||||
suite << BatchedLogTest.suite
|
||||
end
|
||||
|
||||
if __FILE__ == $0
|
||||
suite = Test::Unit::TestSuite.new("BatchedLogTest")
|
||||
add_batched_tests(suite)
|
||||
|
||||
require 'test/unit/ui/console/testrunner'
|
||||
Thread.abort_on_exception = true
|
||||
Test::Unit::UI::Console::TestRunner.run(suite)
|
||||
end
|
248
vendor/madeleine-0.7.1/contrib/test_scalability.rb
vendored
248
vendor/madeleine-0.7.1/contrib/test_scalability.rb
vendored
|
@ -1,248 +0,0 @@
|
|||
#!/usr/local/bin/ruby -w
|
||||
#
|
||||
# Copyright(c) 2003 Håkan Råberg
|
||||
#
|
||||
# This test is based on Prevaylers TransactionTestRun,
|
||||
# Copyright(c) 2001-2003 Klaus Wuestefeld.
|
||||
#
|
||||
|
||||
$LOAD_PATH.unshift("../lib")
|
||||
|
||||
require 'madeleine'
|
||||
require 'madeleine/clock'
|
||||
require 'batched'
|
||||
|
||||
module ScalabilityTest
|
||||
|
||||
class TransactionTestRun
|
||||
MIN_THREADS = 20
|
||||
MAX_THREADS = 20
|
||||
NUMBER_OF_OBJECTS = 100000
|
||||
ROUND_DURATION = 20
|
||||
DIR = "ScalabilityBase"
|
||||
|
||||
def initialize
|
||||
@system = TransactionSystem.new
|
||||
@madeleine = BatchedSnapshotMadeleine.new(DIR) { @system }
|
||||
|
||||
@system.replace_all_records(create_records(NUMBER_OF_OBJECTS))
|
||||
|
||||
@is_round_finished = false
|
||||
|
||||
@best_round_ops_per_s = 0
|
||||
@best_round_threads = 0
|
||||
@operation_count = 0
|
||||
@last_operation = 0
|
||||
@active_round_threads = 0
|
||||
|
||||
@half_of_the_objects = NUMBER_OF_OBJECTS / 2
|
||||
|
||||
@connection_cache = []
|
||||
@connection_cache_lock = Mutex.new
|
||||
|
||||
ObjectSpace.garbage_collect
|
||||
|
||||
puts "========= Running " + name + " (" + (MAX_THREADS - MIN_THREADS + 1).to_s + " rounds). Subject: " + subject_name + "..."
|
||||
puts "Each round will take approx. " + ROUND_DURATION.to_s + " seconds to run..."
|
||||
perform_test
|
||||
puts "----------- BEST ROUND: " + result_string(@best_round_ops_per_s, @best_round_threads)
|
||||
|
||||
@madeleine.close
|
||||
delete_directory(DIR)
|
||||
end
|
||||
|
||||
def name
|
||||
"Transaction Test"
|
||||
end
|
||||
|
||||
def subject_name
|
||||
"Madeleine"
|
||||
end
|
||||
|
||||
def result_string(ops_per_s, threads)
|
||||
ops_per_s.to_s + " operations/second (" + threads.to_s + " threads)"
|
||||
end
|
||||
|
||||
def perform_test
|
||||
for threads in MIN_THREADS..MAX_THREADS
|
||||
ops_per_s = perform_round(threads)
|
||||
if ops_per_s > @best_round_ops_per_s
|
||||
@best_round_ops_per_s = ops_per_s
|
||||
@best_round_threads = threads
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
def perform_round(threads)
|
||||
initial_operation_count = @operation_count
|
||||
start_time = Time.now.to_f
|
||||
|
||||
start_threads(threads)
|
||||
sleep(ROUND_DURATION)
|
||||
stop_threads
|
||||
|
||||
seconds_ellapsed = Time.now.to_f - start_time
|
||||
ops_per_second = (@operation_count - initial_operation_count) / seconds_ellapsed
|
||||
|
||||
puts
|
||||
puts "Seconds ellapsed: " + seconds_ellapsed.to_s
|
||||
puts "--------- Round Result: " + result_string(ops_per_second, threads)
|
||||
|
||||
ops_per_second
|
||||
end
|
||||
|
||||
def start_threads(threads)
|
||||
@is_round_finished = false
|
||||
for i in 1..threads
|
||||
start_thread(@last_operation + i, threads)
|
||||
end
|
||||
end
|
||||
|
||||
def start_thread(starting_operation, operation_increment)
|
||||
Thread.new {
|
||||
connection = accquire_connection
|
||||
|
||||
operation = starting_operation
|
||||
while not @is_round_finished
|
||||
# puts "Operation " + operation.to_s
|
||||
execute_operation(connection, operation)
|
||||
operation += operation_increment
|
||||
end
|
||||
|
||||
@connection_cache_lock.synchronize do
|
||||
@connection_cache << connection
|
||||
@operation_count += (operation - starting_operation) / operation_increment
|
||||
@last_operation = operation if @last_operation < operation
|
||||
@active_round_threads -= 1
|
||||
end
|
||||
}
|
||||
@active_round_threads += 1
|
||||
end
|
||||
|
||||
def execute_operation(connection, operation)
|
||||
record_to_insert = Record.new(NUMBER_OF_OBJECTS + operation)
|
||||
id_to_delete = spread_id(operation)
|
||||
record_to_update = Record.new(@half_of_the_objects + id_to_delete)
|
||||
|
||||
connection.perform_transaction(record_to_insert, record_to_update, id_to_delete)
|
||||
end
|
||||
|
||||
def spread_id(id)
|
||||
(id / @half_of_the_objects) * @half_of_the_objects + ((id * 16807) % @half_of_the_objects)
|
||||
end
|
||||
|
||||
def create_test_connection
|
||||
TransactionConnection.new(@madeleine)
|
||||
end
|
||||
|
||||
def accquire_connection
|
||||
@connection_cache_lock.synchronize do
|
||||
return @connection_cache.empty? ? create_test_connection : @connection_cache.shift
|
||||
end
|
||||
end
|
||||
|
||||
def stop_threads
|
||||
@is_round_finished = true
|
||||
while @active_round_threads != 0
|
||||
sleep(0.001)
|
||||
end
|
||||
end
|
||||
|
||||
def create_records(number_of_objects)
|
||||
result = []
|
||||
for i in 0..number_of_objects
|
||||
result << Record.new(i)
|
||||
end
|
||||
result
|
||||
end
|
||||
|
||||
|
||||
def delete_directory(directory_name)
|
||||
Dir.foreach(directory_name) do |file|
|
||||
next if file == "."
|
||||
next if file == ".."
|
||||
File.delete(directory_name + File::SEPARATOR + file)
|
||||
end
|
||||
Dir.delete(directory_name)
|
||||
end
|
||||
end
|
||||
|
||||
class TransactionSystem
|
||||
include Madeleine::Clock::ClockedSystem
|
||||
|
||||
def initialize
|
||||
@records_by_id = Hash.new
|
||||
@transaction_lock = Mutex.new
|
||||
end
|
||||
|
||||
def perform_transaction(record_to_insert, record_to_update, id_to_delete)
|
||||
@transaction_lock.synchronize do
|
||||
put(record_to_insert)
|
||||
put(record_to_update)
|
||||
@records_by_id.delete(id_to_delete)
|
||||
end
|
||||
end
|
||||
|
||||
def put(new_record)
|
||||
@records_by_id[new_record.id] = new_record
|
||||
end
|
||||
|
||||
def replace_all_records(new_records)
|
||||
@records_by_id.clear
|
||||
new_records.each do |record|
|
||||
put(record)
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
class TransactionConnection
|
||||
def initialize(madeleine)
|
||||
@madeleine = madeleine
|
||||
end
|
||||
|
||||
def perform_transaction(record_to_insert, record_to_update, id_to_delete)
|
||||
@madeleine.execute_command(TestTransaction.new(record_to_insert, record_to_update, id_to_delete))
|
||||
end
|
||||
end
|
||||
|
||||
class TestTransaction
|
||||
def initialize(record_to_insert, record_to_update, id_to_delete)
|
||||
@record_to_insert = record_to_insert
|
||||
@record_to_update = record_to_update
|
||||
@id_to_delete = id_to_delete
|
||||
end
|
||||
|
||||
def execute(system)
|
||||
system.perform_transaction(@record_to_insert, @record_to_update, @id_to_delete)
|
||||
end
|
||||
end
|
||||
|
||||
class Record
|
||||
attr_reader :id, :name, :string_1, :date_1, :date_2
|
||||
|
||||
def initialize(id)
|
||||
@id = id
|
||||
@name = "NAME" + (id % 10000).to_s
|
||||
@string_1 = (id % 10000).to_s == 0 ? Record.large_string + id : nil;
|
||||
@date_1 = Record.random_date
|
||||
@date_2 = Record.random_date
|
||||
end
|
||||
|
||||
def self.large_string
|
||||
[].fill("A", 1..980).to_s
|
||||
end
|
||||
|
||||
def self.random_date
|
||||
rand(10000000)
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
if __FILE__ == $0
|
||||
puts "Madeleine Scalability Test"
|
||||
puts "Based on Prevaylers Scalability Test"
|
||||
puts
|
||||
|
||||
Thread.abort_on_exception = true
|
||||
ScalabilityTest::TransactionTestRun.new
|
||||
end
|
|
@ -1,44 +0,0 @@
|
|||
#!/usr/local/bin/ruby -w
|
||||
|
||||
$LOAD_PATH.unshift("../lib")
|
||||
|
||||
require 'madeleine'
|
||||
require 'batched'
|
||||
|
||||
class BenchmarkCommand
|
||||
def initialize(value)
|
||||
@value = value
|
||||
end
|
||||
|
||||
def execute(system)
|
||||
# do nothing
|
||||
end
|
||||
end
|
||||
|
||||
madeleine = BattchedSnapshotMadeleine.new("benchmark-base") { :the_system }
|
||||
|
||||
RUNS = 200
|
||||
THREADS = 10
|
||||
|
||||
GC.start
|
||||
GC.disable
|
||||
|
||||
t0 = Time.now
|
||||
|
||||
threads = []
|
||||
THREADS.times {
|
||||
threads << Thread.new {
|
||||
RUNS.times {
|
||||
madeleine.execute_command(BenchmarkCommand.new(1234))
|
||||
}
|
||||
}
|
||||
}
|
||||
threads.each {|t| t.join }
|
||||
t1 = Time.now
|
||||
|
||||
GC.enable
|
||||
|
||||
tps = (THREADS * RUNS)/(t1 - t0)
|
||||
|
||||
puts "#{tps.to_i} transactions/s"
|
||||
|
1
vendor/madeleine-0.7.1/docs/.cvsignore
vendored
1
vendor/madeleine-0.7.1/docs/.cvsignore
vendored
|
@ -1 +0,0 @@
|
|||
api
|
87
vendor/madeleine-0.7.1/docs/designRules.html
vendored
87
vendor/madeleine-0.7.1/docs/designRules.html
vendored
|
@ -1,87 +0,0 @@
|
|||
<!DOCTYPE html PUBLIC "-//W3C//DTD HTML 4.01//EN">
|
||||
<html>
|
||||
<head>
|
||||
<title>Design rules - Madeleine</title>
|
||||
<link type="text/css" href="docs.css" rel="stylesheet">
|
||||
</head>
|
||||
|
||||
<body>
|
||||
|
||||
<h1>Design rules</h1>
|
||||
|
||||
<p>This is a summary of the design rules your application has to
|
||||
follow to work with Madeleine.
|
||||
|
||||
|
||||
<h2>The Prevalent System</h2>
|
||||
|
||||
<h3>Your objects have to fit into memory</h3>
|
||||
|
||||
<p>All of them. At the same time.
|
||||
|
||||
<h3>Your objects have to be marshallable</h3>
|
||||
|
||||
<p>Snapshots are taken of the system by marshalling the whole system to a
|
||||
file. If your classes can't be marshalled/unmarshalled then Madeleine
|
||||
won't be able to store/restore the system.
|
||||
|
||||
<h3>Your objects have to be deterministic</h3>
|
||||
|
||||
<p><em>Deterministic</em> means that, given the same commands, they have
|
||||
to always give the same results.
|
||||
|
||||
<p>For the much of your code this won't
|
||||
be a problem, but there are a few common issues:
|
||||
|
||||
<h4>The system clock</h4>
|
||||
<p>You can't use the system clock (see instead ClockedSystem and TimeActor).
|
||||
|
||||
<h4>Random numbers</h4>
|
||||
<p><code>Kernel.rand()</code> uses the system clock internally by
|
||||
default. Use <code>Kernel.srand()</code> to seed the random number
|
||||
generator before using <code>rand()</code>.
|
||||
|
||||
<h4>Files, network and other IO</h4>
|
||||
<p>You generally can't access the outside world from within your
|
||||
prevalent system. Instead do IO outside of the prevalent system and
|
||||
call into the system when needed.
|
||||
|
||||
<h3>Changes to the system have to be done through command
|
||||
objects</h3>
|
||||
|
||||
<p>Everything that modifies the prevalent system must be done through a
|
||||
<em>command object</em> sent to the Madeleine instance, using
|
||||
<code>execute_command(aCommand)</code>. Queries that don't modify the
|
||||
system can be done either through direct method calls or through
|
||||
command objects.
|
||||
|
||||
<h2>Command Objects</h2>
|
||||
|
||||
<p>A command object is an object that implements the method
|
||||
<code>execute(system)</code>. They are an example of the "Command"
|
||||
design pattern.
|
||||
|
||||
<h3>The command objects also have to be marshallable</h3>
|
||||
|
||||
<p>Madeleine keeps track of changes between snapshots by logging
|
||||
marshalled commands.
|
||||
|
||||
<h3>The command must raise errors before modifying the system</h3>
|
||||
|
||||
<p>Unlike a RDBMS, Madeleine can't roll back a command (yet). This means
|
||||
that your commands will have to do their error checking and raise any
|
||||
errors before modifying the system. Failing to do this will cause an
|
||||
inconsistent command log.
|
||||
|
||||
<h3>Command objects can't hold references to the system's objects</h3>
|
||||
|
||||
<p>Unmarshalling such a command would create clones of the original
|
||||
objects, which would then be modified instead of the real
|
||||
objects. The commands must <i>find</i> the objects to modify.
|
||||
|
||||
<hr>
|
||||
|
||||
<tt>$Id: designRules.html,v 1.1 2005/01/07 23:03:27 alexeyv Exp $</tt>
|
||||
|
||||
</body>
|
||||
</html>
|
28
vendor/madeleine-0.7.1/docs/docs.css
vendored
28
vendor/madeleine-0.7.1/docs/docs.css
vendored
|
@ -1,28 +0,0 @@
|
|||
body {
|
||||
background-color: #FFFFF0;
|
||||
}
|
||||
p {
|
||||
width: 70ex
|
||||
}
|
||||
h1 {
|
||||
font-family: verdana,arial,helvetica,sans-serif;
|
||||
}
|
||||
h2 {
|
||||
font-family: verdana,arial,helvetica,sans-serif;
|
||||
background: #EEEEE0;
|
||||
}
|
||||
h3 {
|
||||
font-family: verdana,arial,helvetica,sans-serif;
|
||||
}
|
||||
h4 {
|
||||
font-family: verdana,arial,helvetica,sans-serif;
|
||||
}
|
||||
.classMethod {
|
||||
font-family: courier,monospace;
|
||||
font-weight: bold;
|
||||
background: #EEEEE0;
|
||||
}
|
||||
.instanceMethod {
|
||||
font-family: courier,sans-serif;
|
||||
background: #EEEEE0;
|
||||
}
|
3
vendor/madeleine-0.7.1/generate_rdoc.rb
vendored
3
vendor/madeleine-0.7.1/generate_rdoc.rb
vendored
|
@ -1,3 +0,0 @@
|
|||
#!/usr/local/bin/ruby
|
||||
|
||||
`rdoc lib --op docs/api`
|
1098
vendor/madeleine-0.7.1/install.rb
vendored
1098
vendor/madeleine-0.7.1/install.rb
vendored
File diff suppressed because it is too large
Load diff
420
vendor/madeleine-0.7.1/lib/madeleine.rb
vendored
420
vendor/madeleine-0.7.1/lib/madeleine.rb
vendored
|
@ -1,420 +0,0 @@
|
|||
#
|
||||
# Madeleine - Ruby Object Prevalence
|
||||
#
|
||||
# Author:: Anders Bengtsson <ndrsbngtssn@yahoo.se>
|
||||
# Copyright:: Copyright (c) 2003-2004
|
||||
#
|
||||
# Usage:
|
||||
#
|
||||
# require 'madeleine'
|
||||
#
|
||||
# madeleine = SnapshotMadeleine.new("my_example_storage") {
|
||||
# SomeExampleApplication.new()
|
||||
# }
|
||||
#
|
||||
# madeleine.execute_command(command)
|
||||
#
|
||||
|
||||
module Madeleine
|
||||
|
||||
require 'thread'
|
||||
require 'sync'
|
||||
require 'madeleine/files'
|
||||
|
||||
MADELEINE_VERSION = "0.7.1"
|
||||
|
||||
class SnapshotMadeleine
|
||||
|
||||
# Builds a new Madeleine instance. If there is a snapshot available
|
||||
# then the system will be created from that, otherwise
|
||||
# <tt>new_system</tt> will be used. The state of the system will
|
||||
# then be restored from the command logs.
|
||||
#
|
||||
# You can provide your own snapshot marshaller, for instance using
|
||||
# YAML or SOAP, instead of Ruby's built-in marshaller. The
|
||||
# <tt>snapshot_marshaller</tt> must respond to
|
||||
# <tt>load(stream)</tt> and <tt>dump(object, stream)</tt>. You
|
||||
# must use the same marshaller every time for a system.
|
||||
#
|
||||
# See: DefaultSnapshotMadeleine
|
||||
#
|
||||
# * <tt>directory_name</tt> - Storage directory to use. Will be created if needed.
|
||||
# * <tt>snapshot_marshaller</tt> - Marshaller to use for system snapshots. (Optional)
|
||||
# * <tt>new_system_block</tt> - Block to create a new system (if no stored system was found).
|
||||
def self.new(directory_name, snapshot_marshaller=Marshal, &new_system_block)
|
||||
log_factory = DefaultLogFactory.new
|
||||
logger = Logger.new(directory_name,
|
||||
log_factory)
|
||||
snapshotter = Snapshotter.new(directory_name,
|
||||
snapshot_marshaller)
|
||||
lock = DefaultLock.new
|
||||
recoverer = Recoverer.new(directory_name,
|
||||
snapshot_marshaller)
|
||||
system = recoverer.recover_snapshot(new_system_block)
|
||||
|
||||
executer = Executer.new(system)
|
||||
recoverer.recover_logs(executer)
|
||||
DefaultSnapshotMadeleine.new(system, logger, snapshotter, lock, executer)
|
||||
end
|
||||
end
|
||||
|
||||
class DefaultSnapshotMadeleine
|
||||
|
||||
# The prevalent system
|
||||
attr_reader :system
|
||||
|
||||
def initialize(system, logger, snapshotter, lock, executer)
|
||||
@system = system
|
||||
@logger = logger
|
||||
@snapshotter = snapshotter
|
||||
@lock = lock
|
||||
@executer = executer
|
||||
|
||||
@closed = false
|
||||
end
|
||||
|
||||
# Execute a command on the prevalent system.
|
||||
#
|
||||
# Commands must have a method <tt>execute(aSystem)</tt>.
|
||||
# Otherwise an error, <tt>Madeleine::InvalidCommandException</tt>,
|
||||
# will be raised.
|
||||
#
|
||||
# The return value from the command's <tt>execute()</tt> method is returned.
|
||||
#
|
||||
# * <tt>command</tt> - The command to execute on the system.
|
||||
def execute_command(command)
|
||||
verify_command_sane(command)
|
||||
@lock.synchronize {
|
||||
raise "closed" if @closed
|
||||
@logger.store(command)
|
||||
@executer.execute(command)
|
||||
}
|
||||
end
|
||||
|
||||
# Execute a query on the prevalent system.
|
||||
#
|
||||
# Only differs from <tt>execute_command</tt> in that the command/query isn't logged, and
|
||||
# therefore isn't allowed to modify the system. A shared lock is held, preventing others
|
||||
# from modifying the system while the query is running.
|
||||
#
|
||||
# * <tt>query</tt> - The query command to execute
|
||||
def execute_query(query)
|
||||
@lock.synchronize_shared {
|
||||
@executer.execute(query)
|
||||
}
|
||||
end
|
||||
|
||||
# Take a snapshot of the current system.
|
||||
#
|
||||
# You need to regularly take a snapshot of a running system,
|
||||
# otherwise the logs will grow big and restarting the system will take a
|
||||
# long time. Your backups must also be done from the snapshot files,
|
||||
# since you can't make a consistent backup of a live log.
|
||||
#
|
||||
# A practical way of doing snapshots is a timer thread:
|
||||
#
|
||||
# Thread.new(madeleine) {|madeleine|
|
||||
# while true
|
||||
# sleep(60 * 60 * 24) # 24 hours
|
||||
# madeleine.take_snapshot
|
||||
# end
|
||||
# }
|
||||
def take_snapshot
|
||||
@lock.synchronize {
|
||||
@logger.close
|
||||
@snapshotter.take(@system)
|
||||
@logger.reset
|
||||
}
|
||||
end
|
||||
|
||||
# Close the system.
|
||||
#
|
||||
# The log file is closed and no new commands can be received
|
||||
# by this Madeleine.
|
||||
def close
|
||||
@lock.synchronize {
|
||||
@logger.close
|
||||
@closed = true
|
||||
}
|
||||
end
|
||||
|
||||
private
|
||||
|
||||
def verify_command_sane(command)
|
||||
unless command.respond_to?(:execute)
|
||||
raise InvalidCommandException.new("Commands must have an 'execute' method")
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
class InvalidCommandException < Exception
|
||||
end
|
||||
|
||||
#
|
||||
# Internal classes below
|
||||
#
|
||||
|
||||
FILE_COUNTER_SIZE = 21 #:nodoc:
|
||||
|
||||
class DefaultLock #:nodoc:
|
||||
|
||||
def initialize
|
||||
@lock = Sync.new
|
||||
end
|
||||
|
||||
def synchronize(&block)
|
||||
@lock.synchronize(&block)
|
||||
end
|
||||
|
||||
def synchronize_shared(&block)
|
||||
@lock.synchronize(:SH, &block)
|
||||
end
|
||||
end
|
||||
|
||||
class Executer #:nodoc:
|
||||
|
||||
def initialize(system)
|
||||
@system = system
|
||||
@in_recovery = false
|
||||
end
|
||||
|
||||
def execute(command)
|
||||
begin
|
||||
command.execute(@system)
|
||||
rescue
|
||||
raise unless @in_recovery
|
||||
end
|
||||
end
|
||||
|
||||
def recovery
|
||||
begin
|
||||
@in_recovery = true
|
||||
yield
|
||||
ensure
|
||||
@in_recovery = false
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
class Recoverer #:nodoc:
|
||||
|
||||
def initialize(directory_name, marshaller)
|
||||
@directory_name, @marshaller = directory_name, marshaller
|
||||
end
|
||||
|
||||
def recover_snapshot(new_system_block)
|
||||
system = nil
|
||||
id = SnapshotFile.highest_id(@directory_name)
|
||||
if id > 0
|
||||
snapshot_file = SnapshotFile.new(@directory_name, id).name
|
||||
open(snapshot_file, "rb") {|snapshot|
|
||||
system = @marshaller.load(snapshot)
|
||||
}
|
||||
else
|
||||
system = new_system_block.call
|
||||
end
|
||||
system
|
||||
end
|
||||
|
||||
def recover_logs(executer)
|
||||
executer.recovery {
|
||||
CommandLog.log_file_names(@directory_name, FileService.new).each {|file_name|
|
||||
open(@directory_name + File::SEPARATOR + file_name, "rb") {|log|
|
||||
recover_log(executer, log)
|
||||
}
|
||||
}
|
||||
}
|
||||
end
|
||||
|
||||
private
|
||||
|
||||
def recover_log(executer, log)
|
||||
while ! log.eof?
|
||||
command = Marshal.load(log)
|
||||
executer.execute(command)
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
class NumberedFile #:nodoc:
|
||||
|
||||
def initialize(path, name, id)
|
||||
@path, @name, @id = path, name, id
|
||||
end
|
||||
|
||||
def name
|
||||
result = @path
|
||||
result += File::SEPARATOR
|
||||
result += sprintf("%0#{FILE_COUNTER_SIZE}d", @id)
|
||||
result += '.'
|
||||
result += @name
|
||||
end
|
||||
end
|
||||
|
||||
class CommandLog #:nodoc:
|
||||
|
||||
def self.log_file_names(directory_name, file_service)
|
||||
return [] unless file_service.exist?(directory_name)
|
||||
result = file_service.dir_entries(directory_name).select {|name|
|
||||
name =~ /^\d{#{FILE_COUNTER_SIZE}}\.command_log$/
|
||||
}
|
||||
result.each {|name| name.untaint }
|
||||
result.sort!
|
||||
result
|
||||
end
|
||||
|
||||
def initialize(path, file_service)
|
||||
id = self.class.highest_log(path, file_service) + 1
|
||||
numbered_file = NumberedFile.new(path, "command_log", id)
|
||||
@file = file_service.open(numbered_file.name, 'wb')
|
||||
end
|
||||
|
||||
def close
|
||||
@file.close
|
||||
end
|
||||
|
||||
def store(command)
|
||||
Marshal.dump(command, @file)
|
||||
@file.flush
|
||||
@file.fsync
|
||||
end
|
||||
|
||||
def self.highest_log(directory_name, file_service)
|
||||
highest = 0
|
||||
log_file_names(directory_name, file_service).each {|file_name|
|
||||
match = /^(\d{#{FILE_COUNTER_SIZE}})/.match(file_name)
|
||||
n = match[1].to_i
|
||||
if n > highest
|
||||
highest = n
|
||||
end
|
||||
}
|
||||
highest
|
||||
end
|
||||
end
|
||||
|
||||
class DefaultLogFactory #:nodoc:
|
||||
def create_log(directory_name)
|
||||
CommandLog.new(directory_name, FileService.new)
|
||||
end
|
||||
end
|
||||
|
||||
class Logger #:nodoc:
|
||||
|
||||
def initialize(directory_name, log_factory)
|
||||
@directory_name = directory_name
|
||||
@log_factory = log_factory
|
||||
@log = nil
|
||||
@pending_tick = nil
|
||||
ensure_directory_exists
|
||||
end
|
||||
|
||||
def ensure_directory_exists
|
||||
if ! File.exist?(@directory_name)
|
||||
Dir.mkdir(@directory_name)
|
||||
end
|
||||
end
|
||||
|
||||
def reset
|
||||
close
|
||||
delete_log_files
|
||||
end
|
||||
|
||||
def store(command)
|
||||
if command.kind_of?(Madeleine::Clock::Tick)
|
||||
@pending_tick = command
|
||||
else
|
||||
if @pending_tick
|
||||
internal_store(@pending_tick)
|
||||
@pending_tick = nil
|
||||
end
|
||||
internal_store(command)
|
||||
end
|
||||
end
|
||||
|
||||
def internal_store(command)
|
||||
if @log.nil?
|
||||
open_new_log
|
||||
end
|
||||
@log.store(command)
|
||||
end
|
||||
|
||||
def close
|
||||
return if @log.nil?
|
||||
@log.close
|
||||
@log = nil
|
||||
end
|
||||
|
||||
private
|
||||
|
||||
def delete_log_files
|
||||
Dir.glob(@directory_name + File::SEPARATOR + "*.command_log").each {|name|
|
||||
name.untaint
|
||||
File.delete(name)
|
||||
}
|
||||
end
|
||||
|
||||
def open_new_log
|
||||
@log = @log_factory.create_log(@directory_name)
|
||||
end
|
||||
end
|
||||
|
||||
class SnapshotFile < NumberedFile #:nodoc:
|
||||
|
||||
def self.highest_id(directory_name)
|
||||
return 0 unless File.exist?(directory_name)
|
||||
suffix = "snapshot"
|
||||
highest = 0
|
||||
Dir.foreach(directory_name) {|file_name|
|
||||
match = /^(\d{#{FILE_COUNTER_SIZE}}\.#{suffix}$)/.match(file_name)
|
||||
next unless match
|
||||
n = match[1].to_i
|
||||
if n > highest
|
||||
highest = n
|
||||
end
|
||||
}
|
||||
highest
|
||||
end
|
||||
|
||||
def self.next(directory_name)
|
||||
new(directory_name, highest_id(directory_name) + 1)
|
||||
end
|
||||
|
||||
def initialize(directory_name, id)
|
||||
super(directory_name, "snapshot", id)
|
||||
end
|
||||
end
|
||||
|
||||
class Snapshotter #:nodoc:
|
||||
|
||||
def initialize(directory_name, marshaller)
|
||||
@directory_name, @marshaller = directory_name, marshaller
|
||||
end
|
||||
|
||||
def take(system)
|
||||
numbered_file = SnapshotFile.next(@directory_name)
|
||||
name = numbered_file.name
|
||||
open(name + '.tmp', 'wb') {|snapshot|
|
||||
@marshaller.dump(system, snapshot)
|
||||
snapshot.flush
|
||||
snapshot.fsync
|
||||
}
|
||||
File.rename(name + '.tmp', name)
|
||||
end
|
||||
end
|
||||
|
||||
module Clock #:nodoc:
|
||||
class Tick #:nodoc:
|
||||
|
||||
def initialize(time)
|
||||
@time = time
|
||||
end
|
||||
|
||||
def execute(system)
|
||||
system.clock.forward_to(@time)
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
SnapshotMadeleine = Madeleine::SnapshotMadeleine
|
||||
|
418
vendor/madeleine-0.7.1/lib/madeleine/automatic.rb
vendored
418
vendor/madeleine-0.7.1/lib/madeleine/automatic.rb
vendored
|
@ -1,418 +0,0 @@
|
|||
require 'yaml'
|
||||
require 'madeleine/zmarshal'
|
||||
require 'soap/marshal'
|
||||
|
||||
module Madeleine
|
||||
|
||||
# Automatic commands for Madeleine
|
||||
#
|
||||
# Author:: Stephen Sykes <sds@stephensykes.com>
|
||||
# Copyright:: Copyright (C) 2003-2004
|
||||
# Version:: 0.41
|
||||
#
|
||||
# This module provides a way of automatically generating command objects for madeleine to
|
||||
# store. It works by making a proxy object for all objects of any classes in which it is included.
|
||||
# Method calls to these objects are intercepted, and stored as a command before being
|
||||
# passed on to the real receiver. The command objects remember which object the command was
|
||||
# destined for by using a pair of internal ids that are contained in each of the proxy objects.
|
||||
#
|
||||
# There is also a mechanism for specifying which methods not to intercept calls to by using
|
||||
# automatic_read_only, and its opposite automatic_read_write.
|
||||
#
|
||||
# Should you require it, the snapshots can be stored as yaml, and can be compressed. Just pass
|
||||
# the marshaller you want to use as the second argument to AutomaticSnapshotMadeleine.new.
|
||||
# If the passed marshaller did not successfully deserialize the latest snapshot, the system
|
||||
# will try to automatically detect and read either Marshal, YAML, SOAP, or their corresponding
|
||||
# compressed versions.
|
||||
#
|
||||
# This module is designed to work correctly in the case there are multiple madeleine systems in use by
|
||||
# a single program, and is also safe to use with threads.
|
||||
#
|
||||
# Usage:
|
||||
#
|
||||
# require 'madeleine'
|
||||
# require 'madeleine/automatic'
|
||||
#
|
||||
# class A
|
||||
# include Madeleine::Automatic::Interceptor
|
||||
# attr_reader :foo
|
||||
# automatic_read_only :foo
|
||||
# def initialize(param1, ...)
|
||||
# ...
|
||||
# end
|
||||
# def some_method(paramA, ...)
|
||||
# ...
|
||||
# end
|
||||
# automatic_read_only
|
||||
# def bigfoo
|
||||
# foo.upcase
|
||||
# end
|
||||
# end
|
||||
#
|
||||
# mad = AutomaticSnapshotMadeleine.new("storage_directory") { A.new(param1, ...) }
|
||||
#
|
||||
# mad.system.some_method(paramA, ...) # logged as a command by madeleine
|
||||
# print mad.foo # not logged
|
||||
# print mad.bigfoo # not logged
|
||||
# mad.take_snapshot
|
||||
#
|
||||
|
||||
module Automatic
|
||||
#
|
||||
# This module should be included (at the top) in any classes that are to be persisted.
|
||||
# It will intercept method calls and make sure they are converted into commands that are logged by Madeleine.
|
||||
# It does this by returning a Prox object that is a proxy for the real object.
|
||||
#
|
||||
# It also handles automatic_read_only and automatic_read_write, allowing user specification of which methods
|
||||
# should be made into commands
|
||||
#
|
||||
module Interceptor
|
||||
#
|
||||
# When included, redefine new so that we can return a Prox object instead, and define methods to handle
|
||||
# keeping track of which methods are read only
|
||||
#
|
||||
def self.included(klass)
|
||||
class <<klass
|
||||
alias_method :_old_new, :new
|
||||
|
||||
def new(*args, &block)
|
||||
Prox.new(_old_new(*args, &block))
|
||||
end
|
||||
#
|
||||
# Called when a method added - remember symbol if read only
|
||||
# This is a good place to add in any superclass's read only methods also
|
||||
#
|
||||
def method_added(symbol)
|
||||
self.instance_eval {
|
||||
@read_only_methods ||= []
|
||||
@auto_read_only_flag ||= false
|
||||
@read_only_methods << symbol if @auto_read_only_flag
|
||||
c = self
|
||||
while (c = c.superclass)
|
||||
if (c.instance_eval {instance_variables.include? "@read_only_methods"})
|
||||
@read_only_methods |= c.instance_eval {@read_only_methods}
|
||||
end
|
||||
end
|
||||
}
|
||||
end
|
||||
#
|
||||
# Set the read only flag, or add read only methods
|
||||
#
|
||||
def automatic_read_only(*list)
|
||||
if (list == [])
|
||||
self.instance_eval {@auto_read_only_flag = true}
|
||||
else
|
||||
list.each {|s| self.instance_eval {@read_only_methods ||= []; @read_only_methods << s}}
|
||||
end
|
||||
end
|
||||
#
|
||||
# Clear the read only flag, or remove read only methods
|
||||
#
|
||||
def automatic_read_write(*list)
|
||||
if (list == [])
|
||||
self.instance_eval {@auto_read_only_flag = false}
|
||||
else
|
||||
list.each {|s| self.instance_eval {@read_only_methods ||= []; @read_only_methods.delete(s)}}
|
||||
end
|
||||
end
|
||||
|
||||
end
|
||||
end
|
||||
#
|
||||
# Return the list of read only methods so Automatic_proxy#method_missing can find what to and what not to make into a command
|
||||
#
|
||||
def read_only_methods
|
||||
self.class.instance_eval {@read_only_methods}
|
||||
end
|
||||
end
|
||||
|
||||
#
|
||||
# A Command object is automatically created for each method call to an object within the system that comes from without.
|
||||
# These objects are recorded in the log by Madeleine.
|
||||
#
|
||||
class Command
|
||||
def initialize(symbol, myid, *args)
|
||||
@symbol = symbol
|
||||
@myid = myid
|
||||
@args = args
|
||||
end
|
||||
#
|
||||
# Called by madeleine when the command is done either first time, or when restoring the log
|
||||
#
|
||||
def execute(system)
|
||||
Thread.current[:system].myid2ref(@myid).thing.send(@symbol, *@args)
|
||||
end
|
||||
end
|
||||
#
|
||||
# This is a little class to pass to SnapshotMadeleine. This is used for snapshots only.
|
||||
# It acts as the marshaller, and just passes marshalling requests on to the user specified
|
||||
# marshaller. This defaults to Marshal, but could be YAML or another.
|
||||
# After we have done a restore, the ObjectSpace is searched for instances of Prox to
|
||||
# add new objects to the list in AutomaticSnapshotMadeleine
|
||||
#
|
||||
class Automatic_marshaller #:nodoc:
|
||||
def Automatic_marshaller.load(io)
|
||||
restored_obj = Deserialize.load(io, Thread.current[:system].marshaller)
|
||||
ObjectSpace.each_object(Prox) {|o| Thread.current[:system].restore(o) if (o.sysid == restored_obj.sysid)}
|
||||
restored_obj
|
||||
end
|
||||
def Automatic_marshaller.dump(obj, io = nil)
|
||||
Thread.current[:system].marshaller.dump(obj, io)
|
||||
end
|
||||
end
|
||||
#
|
||||
# A Prox object is generated and returned by Interceptor each time a system object is created.
|
||||
#
|
||||
class Prox #:nodoc:
|
||||
attr_accessor :thing, :myid, :sysid
|
||||
|
||||
def initialize(thing)
|
||||
if (thing)
|
||||
raise "App object created outside of app" unless Thread.current[:system]
|
||||
@sysid = Thread.current[:system].sysid
|
||||
@myid = Thread.current[:system].add(self)
|
||||
@thing = thing
|
||||
end
|
||||
end
|
||||
#
|
||||
# This automatically makes and executes a new Command if a method is called from
|
||||
# outside the system.
|
||||
#
|
||||
def method_missing(symbol, *args, &block)
|
||||
# print "Sending #{symbol} to #{@thing.to_s}, myid=#{@myid}, sysid=#{@sysid}\n"
|
||||
raise NoMethodError, "Undefined method" unless @thing.respond_to?(symbol)
|
||||
if (Thread.current[:system])
|
||||
@thing.send(symbol, *args, &block)
|
||||
else
|
||||
raise "Cannot make command with block" if block_given?
|
||||
Thread.current[:system] = AutomaticSnapshotMadeleine.systems[@sysid]
|
||||
begin
|
||||
if (@thing.read_only_methods.include?(symbol))
|
||||
result = Thread.current[:system].execute_query(Command.new(symbol, @myid, *args))
|
||||
else
|
||||
result = Thread.current[:system].execute_command(Command.new(symbol, @myid, *args))
|
||||
end
|
||||
ensure
|
||||
Thread.current[:system] = false
|
||||
end
|
||||
result
|
||||
end
|
||||
end
|
||||
#
|
||||
# Custom marshalling - this adds the internal id (myid) and the system id to a marshal
|
||||
# of the object we are the proxy for.
|
||||
# We take care to not marshal the same object twice, so circular references will work.
|
||||
# We ignore Thread.current[:system].marshaller here - this is only called by Marshal, and
|
||||
# marshal is always used for Command objects
|
||||
#
|
||||
def _dump(depth)
|
||||
if (Thread.current[:snapshot_memory])
|
||||
if (Thread.current[:snapshot_memory][self])
|
||||
[@myid.to_s, @sysid].pack("A8A30")
|
||||
else
|
||||
Thread.current[:snapshot_memory][self] = true
|
||||
[@myid.to_s, @sysid].pack("A8A30") + Marshal.dump(@thing, depth)
|
||||
end
|
||||
else
|
||||
[@myid.to_s, @sysid].pack("A8A30") # never marshal a prox object in a command, just ref
|
||||
end
|
||||
end
|
||||
|
||||
#
|
||||
# Custom marshalling for Marshal - restore a Prox object.
|
||||
#
|
||||
def Prox._load(str)
|
||||
x = Prox.new(nil)
|
||||
a = str.unpack("A8A30a*")
|
||||
x.myid = a[0].to_i
|
||||
x.sysid = a[1]
|
||||
x = Thread.current[:system].restore(x)
|
||||
x.thing = Marshal.load(a[2]) if (a[2] > "")
|
||||
x
|
||||
end
|
||||
|
||||
end
|
||||
|
||||
#
|
||||
# The AutomaticSnapshotMadeleine class contains an instance of the persister
|
||||
# (default is SnapshotMadeleine) and provides additional automatic functionality.
|
||||
#
|
||||
# The class is instantiated the same way as SnapshotMadeleine:
|
||||
# madeleine_sys = AutomaticSnapshotMadeleine.new("storage_directory") { A.new(param1, ...) }
|
||||
# The second initialisation parameter is the persister. Supported persisters are:
|
||||
#
|
||||
# * Marshal (default)
|
||||
# * YAML
|
||||
# * SOAP::Marshal
|
||||
# * Madeleine::ZMarshal.new(Marshal)
|
||||
# * Madeleine::ZMarshal.new(YAML)
|
||||
# * Madeleine::ZMarshal.new(SOAP::Marshal)
|
||||
#
|
||||
# The class keeps a record of all the systems that currently exist.
|
||||
# Each instance of the class keeps a record of Prox objects in that system by internal id (myid).
|
||||
#
|
||||
# We also add functionality to take_snapshot in order to set things up so that the custom Prox object
|
||||
# marshalling will work correctly.
|
||||
#
|
||||
class AutomaticSnapshotMadeleine
|
||||
attr_accessor :marshaller
|
||||
attr_reader :list, :sysid
|
||||
|
||||
def initialize(directory_name, marshaller=Marshal, persister=SnapshotMadeleine, &new_system_block)
|
||||
@sysid ||= Time.now.to_f.to_s + Thread.current.object_id.to_s # Gererate a new sysid
|
||||
@myid_count = 0
|
||||
@list = {}
|
||||
Thread.current[:system] = self # during system startup system should not create commands
|
||||
Thread.critical = true
|
||||
@@systems ||= {} # holds systems by sysid
|
||||
@@systems[@sysid] = self
|
||||
Thread.critical = false
|
||||
@marshaller = marshaller # until attrb
|
||||
begin
|
||||
@persister = persister.new(directory_name, Automatic_marshaller, &new_system_block)
|
||||
@list.delete_if {|k,v| # set all the prox objects that now exist to have the right sysid
|
||||
begin
|
||||
ObjectSpace._id2ref(v).sysid = @sysid
|
||||
false
|
||||
rescue RangeError
|
||||
true # Id was to a GC'd object, delete it
|
||||
end
|
||||
}
|
||||
ensure
|
||||
Thread.current[:system] = false
|
||||
end
|
||||
end
|
||||
#
|
||||
# Add a proxy object to the list, return the myid for that object
|
||||
#
|
||||
def add(proxo)
|
||||
@list[@myid_count += 1] = proxo.object_id
|
||||
@myid_count
|
||||
end
|
||||
#
|
||||
# Restore a marshalled proxy object to list - myid_count is increased as required.
|
||||
# If the object already exists in the system then the existing object must be used.
|
||||
#
|
||||
def restore(proxo)
|
||||
if (@list[proxo.myid])
|
||||
proxo = myid2ref(proxo.myid)
|
||||
else
|
||||
@list[proxo.myid] = proxo.object_id
|
||||
@myid_count = proxo.myid if (@myid_count < proxo.myid)
|
||||
end
|
||||
proxo
|
||||
end
|
||||
#
|
||||
# Returns a reference to the object indicated by the internal id supplied.
|
||||
#
|
||||
def myid2ref(myid)
|
||||
raise "Internal id #{myid} not found" unless objid = @list[myid]
|
||||
ObjectSpace._id2ref(objid)
|
||||
end
|
||||
#
|
||||
# Take a snapshot of the system.
|
||||
#
|
||||
def take_snapshot
|
||||
begin
|
||||
Thread.current[:system] = self
|
||||
Thread.current[:snapshot_memory] = {}
|
||||
@persister.take_snapshot
|
||||
ensure
|
||||
Thread.current[:snapshot_memory] = nil
|
||||
Thread.current[:system] = false
|
||||
end
|
||||
end
|
||||
#
|
||||
# Returns the hash containing the systems.
|
||||
#
|
||||
def AutomaticSnapshotMadeleine.systems
|
||||
@@systems
|
||||
end
|
||||
#
|
||||
# Close method changes the sysid for Prox objects so they can't be mistaken for real ones in a new
|
||||
# system before GC gets them
|
||||
#
|
||||
def close
|
||||
begin
|
||||
@list.each_key {|k| myid2ref(k).sysid = nil}
|
||||
rescue RangeError
|
||||
# do nothing
|
||||
end
|
||||
@persister.close
|
||||
end
|
||||
|
||||
#
|
||||
# Pass on any other calls to the persister
|
||||
#
|
||||
def method_missing(symbol, *args, &block)
|
||||
@persister.send(symbol, *args, &block)
|
||||
end
|
||||
end
|
||||
|
||||
|
||||
module Deserialize #:nodoc:
|
||||
#
|
||||
# Detect format of an io stream. Leave it rewound.
|
||||
#
|
||||
def Deserialize.detect(io)
|
||||
c = io.getc
|
||||
c1 = io.getc
|
||||
io.rewind
|
||||
if (c == Marshal::MAJOR_VERSION && c1 <= Marshal::MINOR_VERSION)
|
||||
Marshal
|
||||
elsif (c == 31 && c1 == 139) # gzip magic numbers
|
||||
ZMarshal
|
||||
else
|
||||
while (s = io.gets)
|
||||
break if (s !~ /^\s*$/) # ignore blank lines
|
||||
end
|
||||
io.rewind
|
||||
if (s && s =~ /^\s*<\?[xX][mM][lL]/) # "<?xml" begins an xml serialization
|
||||
SOAP::Marshal
|
||||
else
|
||||
while (s = io.gets)
|
||||
break if (s !~ /^\s*#/ && s !~ /^\s*$/) # ignore blank and comment lines
|
||||
end
|
||||
io.rewind
|
||||
if (s && s =~ /^\s*---/) # "---" is the yaml header
|
||||
YAML
|
||||
else
|
||||
nil # failed to detect
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
||||
#
|
||||
# Try to deserialize object. If there was an error, try to detect marshal format,
|
||||
# and return deserialized object using the right marshaller
|
||||
# If detection didn't work, raise up the exception
|
||||
#
|
||||
def Deserialize.load(io, marshaller=Marshal)
|
||||
begin
|
||||
marshaller.load(io)
|
||||
rescue Exception => e
|
||||
io.rewind
|
||||
detected_marshaller = detect(io)
|
||||
if (detected_marshaller == ZMarshal)
|
||||
zio = Zlib::GzipReader.new(io)
|
||||
detected_zmarshaller = detect(zio)
|
||||
zio.finish
|
||||
io.rewind
|
||||
if (detected_zmarshaller)
|
||||
ZMarshal.new(detected_zmarshaller).load(io)
|
||||
else
|
||||
raise e
|
||||
end
|
||||
elsif (detected_marshaller)
|
||||
detected_marshaller.load(io)
|
||||
else
|
||||
raise e
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
end
|
||||
end
|
||||
|
||||
AutomaticSnapshotMadeleine = Madeleine::Automatic::AutomaticSnapshotMadeleine
|
94
vendor/madeleine-0.7.1/lib/madeleine/clock.rb
vendored
94
vendor/madeleine-0.7.1/lib/madeleine/clock.rb
vendored
|
@ -1,94 +0,0 @@
|
|||
#
|
||||
# Copyright(c) Anders Bengtsson 2003
|
||||
#
|
||||
|
||||
require 'madeleine'
|
||||
|
||||
module Madeleine
|
||||
module Clock
|
||||
|
||||
# Deprecated. Use SnapshotMadeleine instead.
|
||||
class ClockedSnapshotMadeleine < ::Madeleine::SnapshotMadeleine # :nodoc:
|
||||
end
|
||||
|
||||
# Let your system extend this module if you need to access the
|
||||
# machine time. Used together with a TimeActor that keeps
|
||||
# the clock current.
|
||||
module ClockedSystem
|
||||
|
||||
# Returns this system's Clock.
|
||||
def clock
|
||||
unless defined? @clock
|
||||
@clock = Clock.new
|
||||
end
|
||||
@clock
|
||||
end
|
||||
end
|
||||
|
||||
# Sends clock ticks to update a ClockedSystem, so that time can be
|
||||
# dealt with in a deterministic way.
|
||||
class TimeActor
|
||||
|
||||
# Create and launch a new TimeActor
|
||||
#
|
||||
# * <tt>madeleine</tt> - The SnapshotMadeleine instance to work on.
|
||||
# * <tt>delay</tt> - Delay between ticks in seconds (Optional).
|
||||
def self.launch(madeleine, delay=0.1)
|
||||
result = new(madeleine, delay)
|
||||
result
|
||||
end
|
||||
|
||||
# Stops the TimeActor.
|
||||
def destroy
|
||||
@is_destroyed = true
|
||||
@thread.wakeup
|
||||
@thread.join
|
||||
end
|
||||
|
||||
private_class_method :new
|
||||
|
||||
private
|
||||
|
||||
def initialize(madeleine, delay) #:nodoc:
|
||||
@madeleine = madeleine
|
||||
@is_destroyed = false
|
||||
send_tick
|
||||
@thread = Thread.new {
|
||||
until @is_destroyed
|
||||
sleep(delay)
|
||||
send_tick
|
||||
end
|
||||
}
|
||||
end
|
||||
|
||||
def send_tick
|
||||
@madeleine.execute_command(Tick.new(Time.now))
|
||||
end
|
||||
end
|
||||
|
||||
# Keeps track of time in a ClockedSystem.
|
||||
class Clock
|
||||
# Returns the system's time as a Ruby <tt>Time</tt>.
|
||||
attr_reader :time
|
||||
|
||||
def initialize
|
||||
@time = Time.at(0)
|
||||
end
|
||||
|
||||
def forward_to(newTime)
|
||||
@time = newTime
|
||||
end
|
||||
end
|
||||
|
||||
#
|
||||
# Internal classes below
|
||||
#
|
||||
|
||||
# Deprecated. Merged into default implementation.
|
||||
class TimeOptimizingLogger < ::Madeleine::Logger # :nodoc:
|
||||
end
|
||||
|
||||
end
|
||||
end
|
||||
|
||||
ClockedSnapshotMadeleine = Madeleine::Clock::ClockedSnapshotMadeleine
|
19
vendor/madeleine-0.7.1/lib/madeleine/files.rb
vendored
19
vendor/madeleine-0.7.1/lib/madeleine/files.rb
vendored
|
@ -1,19 +0,0 @@
|
|||
#
|
||||
# Wrapper for Ruby's file services, replaced during testing
|
||||
# so we can run tests without touching a real filesystem.
|
||||
#
|
||||
|
||||
class FileService
|
||||
|
||||
def open(*args)
|
||||
super(*args)
|
||||
end
|
||||
|
||||
def exist?(name)
|
||||
File.exist?(name)
|
||||
end
|
||||
|
||||
def dir_entries(name)
|
||||
Dir.entries(name)
|
||||
end
|
||||
end
|
60
vendor/madeleine-0.7.1/lib/madeleine/zmarshal.rb
vendored
60
vendor/madeleine-0.7.1/lib/madeleine/zmarshal.rb
vendored
|
@ -1,60 +0,0 @@
|
|||
#
|
||||
# Author:: Anders Bengtsson <ndrsbngtssn@yahoo.se>
|
||||
# Copyright:: Copyright (c) 2004
|
||||
#
|
||||
|
||||
require 'zlib'
|
||||
|
||||
module Madeleine
|
||||
#
|
||||
# Snapshot marshaller for compressed snapshots.
|
||||
#
|
||||
# Compresses the snapshots created by another marshaller. Uses either
|
||||
# Marshal (the default) or another supplied marshaller.
|
||||
#
|
||||
# Uses <tt>zlib</tt> to do on-the-fly compression/decompression.
|
||||
#
|
||||
# ZMarshal works with Ruby's own Marshal and YAML, but not with SOAP
|
||||
# marshalling.
|
||||
#
|
||||
# Usage:
|
||||
#
|
||||
# require 'madeleine'
|
||||
# require 'madeleine/zmarshal'
|
||||
#
|
||||
# marshaller = Madeleine::ZMarshal.new(YAML)
|
||||
# madeleine = SnapshotMadeleine.new("my_example_storage", marshaller) {
|
||||
# SomeExampleApplication.new()
|
||||
# }
|
||||
#
|
||||
class ZMarshal
|
||||
|
||||
def initialize(marshaller=Marshal)
|
||||
@marshaller = marshaller
|
||||
end
|
||||
|
||||
def load(stream)
|
||||
zstream = Zlib::GzipReader.new(stream)
|
||||
begin
|
||||
# Buffer into a string first, since GzipReader can't handle
|
||||
# Marshal's 0-sized reads and SOAP can't handle streams at all.
|
||||
# In a bright future we can revert to reading directly from the
|
||||
# stream again.
|
||||
buffer = zstream.read
|
||||
return @marshaller.load(buffer)
|
||||
ensure
|
||||
zstream.finish
|
||||
end
|
||||
end
|
||||
|
||||
def dump(system, stream)
|
||||
zstream = Zlib::GzipWriter.new(stream)
|
||||
begin
|
||||
@marshaller.dump(system, zstream)
|
||||
ensure
|
||||
zstream.finish
|
||||
end
|
||||
nil
|
||||
end
|
||||
end
|
||||
end
|
23
vendor/madeleine-0.7.1/madeleine.gemspec
vendored
23
vendor/madeleine-0.7.1/madeleine.gemspec
vendored
|
@ -1,23 +0,0 @@
|
|||
|
||||
require 'rubygems'
|
||||
|
||||
spec = Gem::Specification.new do |s|
|
||||
s.name = 'madeleine'
|
||||
s.version = '0.7.1'
|
||||
s.platform = Gem::Platform::RUBY
|
||||
s.required_ruby_version = ">= 1.8.1"
|
||||
s.summary = "Madeleine is a Ruby implementation of Object Prevalence"
|
||||
s.require_path = 'lib'
|
||||
s.autorequire = 'madeleine'
|
||||
s.author = "Anders Bengtsson"
|
||||
s.email = "ndrsbngtssn@yahoo.se"
|
||||
s.homepage = "http://madeleine.sourceforge.net"
|
||||
s.files = Dir.glob("lib/**/*.rb")
|
||||
s.files += Dir.glob("samples/**/*.rb")
|
||||
s.files += Dir.glob("contrib/**/*.rb")
|
||||
s.files += ['README', 'NEWS', 'COPYING']
|
||||
end
|
||||
|
||||
if $0 == __FILE__
|
||||
Gem::Builder.new(spec).build
|
||||
end
|
3
vendor/madeleine-0.7.1/samples/.cvsignore
vendored
3
vendor/madeleine-0.7.1/samples/.cvsignore
vendored
|
@ -1,3 +0,0 @@
|
|||
dictionary-base
|
||||
painter-demo
|
||||
clock-demo
|
73
vendor/madeleine-0.7.1/samples/clock_click.rb
vendored
73
vendor/madeleine-0.7.1/samples/clock_click.rb
vendored
|
@ -1,73 +0,0 @@
|
|||
#
|
||||
# Simple example of using time with Madeleine.
|
||||
#
|
||||
|
||||
$LOAD_PATH.unshift(".." + File::SEPARATOR + "lib")
|
||||
|
||||
require 'madeleine/clock'
|
||||
require 'tk'
|
||||
|
||||
# The Clicker keeps track of when it was last clicked.
|
||||
#
|
||||
# To access the time it extends ClockedSystem, which provides
|
||||
# it with the 'clock' attribute.
|
||||
#
|
||||
class Clicker
|
||||
include Madeleine::Clock::ClockedSystem
|
||||
|
||||
def initialize
|
||||
@last_clicked = nil
|
||||
end
|
||||
|
||||
def click
|
||||
@last_clicked = clock.time
|
||||
end
|
||||
|
||||
def last_clicked
|
||||
return '-' if @last_clicked.nil?
|
||||
@last_clicked.to_s
|
||||
end
|
||||
end
|
||||
|
||||
# A command to update the Clicker with.
|
||||
#
|
||||
class Click
|
||||
def execute(system)
|
||||
system.click
|
||||
end
|
||||
end
|
||||
|
||||
# Launch a ClockedSnapshotMadeleine.
|
||||
#
|
||||
# ClockedSnapshotMadeleine works like the regular SnapshotMadeleine, but
|
||||
# optimizes away redundant commands from TimeActor.
|
||||
#
|
||||
madeleine = ClockedSnapshotMadeleine.new("clock-demo") { Clicker.new }
|
||||
|
||||
# Launch the TimeActor.
|
||||
#
|
||||
# This provides time commands, without which the system's time would stand still.
|
||||
#
|
||||
Madeleine::Clock::TimeActor.launch(madeleine)
|
||||
|
||||
clicker = madeleine.system
|
||||
|
||||
# The GUI
|
||||
|
||||
root = TkRoot.new() { title "Madeleine Clock Example" }
|
||||
label = TkLabel.new(root) {
|
||||
text "Last clicked " + clicker.last_clicked
|
||||
width 40
|
||||
pack
|
||||
}
|
||||
button = TkButton.new(root) {
|
||||
text 'Click'
|
||||
command proc {
|
||||
madeleine.execute_command(Click.new)
|
||||
label.text("Last clicked " + clicker.last_clicked)
|
||||
}
|
||||
pack
|
||||
}
|
||||
|
||||
Tk.mainloop
|
||||
|
|
@ -1,23 +0,0 @@
|
|||
#
|
||||
# Dictionary client
|
||||
#
|
||||
# See dictionary_server.rb for details
|
||||
#
|
||||
|
||||
require 'drb'
|
||||
|
||||
DRb.start_service
|
||||
dictionary = DRbObject.new(nil, "druby://localhost:1234")
|
||||
|
||||
if ARGV.length == 1
|
||||
puts dictionary.lookup(ARGV[0])
|
||||
elsif ARGV.length == 2
|
||||
dictionary.add(ARGV[0], ARGV[1])
|
||||
puts "Stored"
|
||||
else
|
||||
puts "Usage: dictionary_client <key> [<value>]"
|
||||
end
|
||||
|
||||
|
||||
|
||||
|
|
@ -1,94 +0,0 @@
|
|||
#
|
||||
# A dictionary server using Distributed Ruby (DRb).
|
||||
#
|
||||
# All modifications to the dictionary are done as commands,
|
||||
# while read-only queries (i.e 'lookup') are done directly.
|
||||
#
|
||||
# First launch this server in the background, then use
|
||||
# dictionary_client.rb to look up and add items to the
|
||||
# dictionary.
|
||||
# You can kill the server at any time. The contents of the
|
||||
# dictionary will still be there when you restart it.
|
||||
#
|
||||
# DRb is available at http://raa.ruby-lang.org/list.rhtml?name=druby
|
||||
#
|
||||
|
||||
$LOAD_PATH.unshift(".." + File::SEPARATOR + "lib")
|
||||
require 'madeleine'
|
||||
|
||||
require 'drb'
|
||||
|
||||
|
||||
class Dictionary
|
||||
def initialize
|
||||
@data = {}
|
||||
end
|
||||
|
||||
def add(key, value)
|
||||
@data[key] = value
|
||||
end
|
||||
|
||||
def lookup(key)
|
||||
@data[key]
|
||||
end
|
||||
end
|
||||
|
||||
|
||||
class Addition
|
||||
def initialize(key, value)
|
||||
@key, @value = key, value
|
||||
end
|
||||
|
||||
def execute(system)
|
||||
system.add(@key, @value)
|
||||
end
|
||||
end
|
||||
|
||||
|
||||
class Lookup
|
||||
def initialize(key)
|
||||
@key = key
|
||||
end
|
||||
|
||||
def execute(system)
|
||||
system.lookup(@key)
|
||||
end
|
||||
end
|
||||
|
||||
|
||||
class DictionaryServer
|
||||
|
||||
def initialize(madeleine)
|
||||
@madeleine = madeleine
|
||||
@dictionary = madeleine.system
|
||||
end
|
||||
|
||||
def add(key, value)
|
||||
# When adding a new key-value pair we modify the system, so
|
||||
# this operation has to be done through a command.
|
||||
@madeleine.execute_command(Addition.new(key, value))
|
||||
end
|
||||
|
||||
def lookup(key)
|
||||
# A lookup is a read-only operation, so we can do it as a non-logged
|
||||
# query. If we weren't worried about concurrency problems we could
|
||||
# have just called @dictionary.lookup(key) directly instead.
|
||||
@madeleine.execute_query(Lookup.new(key))
|
||||
end
|
||||
end
|
||||
|
||||
|
||||
madeleine = SnapshotMadeleine.new("dictionary-base") { Dictionary.new }
|
||||
|
||||
Thread.new(madeleine) {
|
||||
puts "Taking snapshot every 30 seconds."
|
||||
while true
|
||||
sleep(30)
|
||||
madeleine.take_snapshot
|
||||
end
|
||||
}
|
||||
|
||||
DRb.start_service("druby://localhost:1234",
|
||||
DictionaryServer.new(madeleine))
|
||||
DRb.thread.join
|
||||
|
60
vendor/madeleine-0.7.1/samples/painter.rb
vendored
60
vendor/madeleine-0.7.1/samples/painter.rb
vendored
|
@ -1,60 +0,0 @@
|
|||
#
|
||||
# Simple drawing program to show Madeleine's logging feature.
|
||||
#
|
||||
# When you restart the program, your old artwork is still there.
|
||||
#
|
||||
# (Note: The GUI components used here aren't marshal-able,
|
||||
# so in a real app you would have to do custom marshaling for
|
||||
# the Painter class to get working snapshots. Then again, in a real
|
||||
# app you wouldn't use the GUI components to hold the app's data,
|
||||
# would you?)
|
||||
#
|
||||
|
||||
$LOAD_PATH.unshift(".." + File::SEPARATOR + "lib")
|
||||
|
||||
require 'madeleine'
|
||||
|
||||
require 'tkclass'
|
||||
|
||||
class Painter
|
||||
|
||||
def initialize(canvas)
|
||||
@canvas = canvas
|
||||
end
|
||||
|
||||
def draw(x, y)
|
||||
line = Line.new(@canvas, x, y, x + 1, y + 1)
|
||||
line.fill('black')
|
||||
end
|
||||
end
|
||||
|
||||
class PaintCommand
|
||||
|
||||
def initialize(x, y)
|
||||
@x, @y = x, y
|
||||
end
|
||||
|
||||
def execute(system)
|
||||
system.draw(@x, @y)
|
||||
end
|
||||
end
|
||||
|
||||
root = TkRoot.new() { title "Madeleine Painter" }
|
||||
canvas = Canvas.new(root)
|
||||
canvas.pack
|
||||
|
||||
$madeleine = Madeleine::SnapshotMadeleine.new("painter-demo") { Painter.new(canvas) }
|
||||
|
||||
canvas.bind("1",
|
||||
proc {|x, y|
|
||||
$madeleine.execute_command(PaintCommand.new(x, y))
|
||||
},
|
||||
"%x %y")
|
||||
canvas.bind("B1-Motion",
|
||||
proc {|x, y|
|
||||
$madeleine.execute_command(PaintCommand.new(x, y))
|
||||
},
|
||||
"%x %y")
|
||||
|
||||
Tk.mainloop
|
||||
|
320
vendor/madeleine-0.7.1/test/test.rb
vendored
320
vendor/madeleine-0.7.1/test/test.rb
vendored
|
@ -1,320 +0,0 @@
|
|||
#!/usr/bin/env ruby
|
||||
#
|
||||
|
||||
$LOAD_PATH.unshift("lib")
|
||||
$LOAD_PATH.unshift("test")
|
||||
|
||||
require 'madeleine'
|
||||
require 'test/unit'
|
||||
|
||||
|
||||
class Append
|
||||
def initialize(value)
|
||||
@value = value
|
||||
end
|
||||
|
||||
def execute(system)
|
||||
system << @value
|
||||
end
|
||||
end
|
||||
|
||||
|
||||
module TestUtils
|
||||
def delete_directory(directory_name)
|
||||
return unless File.exists?(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
|
||||
end
|
||||
|
||||
|
||||
class SnapshotMadeleineTest < Test::Unit::TestCase
|
||||
include TestUtils
|
||||
|
||||
def teardown
|
||||
delete_directory(persistence_base)
|
||||
end
|
||||
|
||||
def persistence_base
|
||||
"closing-test"
|
||||
end
|
||||
|
||||
def test_closing
|
||||
madeleine = SnapshotMadeleine.new(persistence_base) { "hello" }
|
||||
madeleine.close
|
||||
assert_raises(RuntimeError) do
|
||||
madeleine.execute_command(Append.new("world"))
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
class NumberedFileTest < Test::Unit::TestCase
|
||||
|
||||
def test_main
|
||||
target = Madeleine::NumberedFile.new(File::SEPARATOR + "foo", "bar", 321)
|
||||
assert_equal(File::SEPARATOR + "foo" + File::SEPARATOR +
|
||||
"000000000000000000321.bar",
|
||||
target.name)
|
||||
end
|
||||
end
|
||||
|
||||
|
||||
class LoggerTest < Test::Unit::TestCase
|
||||
include TestUtils
|
||||
|
||||
def teardown
|
||||
delete_directory("whoah")
|
||||
end
|
||||
|
||||
def test_creation
|
||||
@log = Object.new
|
||||
def @log.store(command)
|
||||
unless defined? @commands
|
||||
@commands = []
|
||||
end
|
||||
@commands << command
|
||||
end
|
||||
def @log.commands
|
||||
@commands
|
||||
end
|
||||
|
||||
log_factory = self
|
||||
target = Madeleine::Logger.new("whoah", log_factory)
|
||||
target.store(:foo)
|
||||
assert(@log.commands.include?(:foo))
|
||||
end
|
||||
|
||||
# Self-shunt
|
||||
def create_log(directory_name)
|
||||
@log
|
||||
end
|
||||
end
|
||||
|
||||
class CommandVerificationTest < Test::Unit::TestCase
|
||||
|
||||
def teardown
|
||||
Dir.delete("foo")
|
||||
end
|
||||
|
||||
def test_broken_command
|
||||
target = SnapshotMadeleine.new("foo") { :a_system }
|
||||
assert_raises(Madeleine::InvalidCommandException) do
|
||||
target.execute_command(:not_a_command)
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
|
||||
class CustomMarshallerTest < Test::Unit::TestCase
|
||||
include TestUtils
|
||||
|
||||
def teardown
|
||||
delete_directory(prevalence_base)
|
||||
end
|
||||
|
||||
def prevalence_base
|
||||
"custom-marshaller-test"
|
||||
end
|
||||
|
||||
def madeleine_class
|
||||
SnapshotMadeleine
|
||||
end
|
||||
|
||||
def test_changing_marshaller
|
||||
@log = ""
|
||||
marshaller = self
|
||||
target = madeleine_class.new(prevalence_base, marshaller) { "hello world" }
|
||||
target.take_snapshot
|
||||
assert_equal("dump ", @log)
|
||||
target = nil
|
||||
|
||||
madeleine_class.new(prevalence_base, marshaller) { flunk() }
|
||||
assert_equal("dump load ", @log)
|
||||
end
|
||||
|
||||
def load(io)
|
||||
@log << "load "
|
||||
assert_equal("dump data", io.read())
|
||||
end
|
||||
|
||||
def dump(system, io)
|
||||
@log << "dump "
|
||||
assert_equal("hello world", system)
|
||||
io.write("dump data")
|
||||
end
|
||||
end
|
||||
|
||||
|
||||
class ErrorRaisingCommand
|
||||
def execute(system)
|
||||
raise "this is an exception from a command"
|
||||
end
|
||||
end
|
||||
|
||||
class ErrorHandlingTest < Test::Unit::TestCase
|
||||
include TestUtils
|
||||
|
||||
def teardown
|
||||
delete_directory(prevalence_base)
|
||||
end
|
||||
|
||||
def prevalence_base
|
||||
"error-handling-base"
|
||||
end
|
||||
|
||||
def test_exception_in_command
|
||||
madeleine = SnapshotMadeleine.new(prevalence_base) { "hello" }
|
||||
assert_raises(RuntimeError) do
|
||||
madeleine.execute_command(ErrorRaisingCommand.new)
|
||||
end
|
||||
madeleine.close
|
||||
madeleine = SnapshotMadeleine.new(prevalence_base) { "hello" }
|
||||
madeleine.close
|
||||
end
|
||||
end
|
||||
|
||||
class QueryTest < Test::Unit::TestCase
|
||||
include TestUtils
|
||||
|
||||
def teardown
|
||||
delete_directory(prevalence_base)
|
||||
end
|
||||
|
||||
def prevalence_base
|
||||
"query-base"
|
||||
end
|
||||
|
||||
def test_querying
|
||||
madeleine = SnapshotMadeleine.new(prevalence_base) { "hello" }
|
||||
query = Object.new
|
||||
def query.execute(system)
|
||||
system.size
|
||||
end
|
||||
# 'query' is an un-marshallable singleton, so we implicitly test
|
||||
# that querys aren't stored.
|
||||
assert_equal(5, madeleine.execute_query(query))
|
||||
# TODO: assert that no logging was done
|
||||
# TODO: assert that lock was held
|
||||
end
|
||||
end
|
||||
|
||||
|
||||
class TimeOptimizingLoggerTest < Test::Unit::TestCase
|
||||
include TestUtils
|
||||
|
||||
def setup
|
||||
@target = Madeleine::Logger.new("some_directory", self)
|
||||
@log = []
|
||||
def @log.store(command)
|
||||
self << command
|
||||
end
|
||||
end
|
||||
|
||||
def teardown
|
||||
delete_directory("some_directory")
|
||||
end
|
||||
|
||||
def test_optimizing_ticks
|
||||
assert_equal(0, @log.size)
|
||||
@target.store(Madeleine::Clock::Tick.new(Time.at(3)))
|
||||
assert_equal(0, @log.size)
|
||||
@target.store(Madeleine::Clock::Tick.new(Time.at(22)))
|
||||
assert_equal(0, @log.size)
|
||||
@target.store(Addition.new(100))
|
||||
assert_kind_of(Madeleine::Clock::Tick, @log[0])
|
||||
assert_equal(22, value_of_tick(@log[0]))
|
||||
assert_equal(100, @log[1].value)
|
||||
assert_equal(2, @log.size)
|
||||
end
|
||||
|
||||
def value_of_tick(tick)
|
||||
@clock = Object.new
|
||||
def @clock.forward_to(time)
|
||||
@value = time.to_i
|
||||
end
|
||||
def @clock.value
|
||||
@value
|
||||
end
|
||||
tick.execute(self)
|
||||
@clock.value
|
||||
end
|
||||
|
||||
# Self-shunt
|
||||
def create_log(directory_name)
|
||||
assert_equal("some_directory", directory_name)
|
||||
@log
|
||||
end
|
||||
|
||||
# Self-shunt
|
||||
def clock
|
||||
@clock
|
||||
end
|
||||
end
|
||||
|
||||
|
||||
class SharedLockQueryTest < Test::Unit::TestCase
|
||||
include TestUtils
|
||||
|
||||
def prevalence_base
|
||||
"shared_lock_test"
|
||||
end
|
||||
|
||||
def teardown
|
||||
delete_directory(prevalence_base)
|
||||
end
|
||||
|
||||
def test_query
|
||||
madeleine = SnapshotMadeleine.new(prevalence_base) { "hello" }
|
||||
lock = Object.new
|
||||
madeleine.instance_eval { @lock = lock } # FIXME: The horror, the horror
|
||||
|
||||
$shared = false
|
||||
$was_shared = false
|
||||
def lock.synchronize_shared(&block)
|
||||
$shared = true
|
||||
block.call
|
||||
$shared = false
|
||||
end
|
||||
query = Object.new
|
||||
def query.execute(system)
|
||||
$was_shared = $shared
|
||||
end
|
||||
madeleine.execute_query(query)
|
||||
assert($was_shared)
|
||||
end
|
||||
end
|
||||
|
||||
suite = Test::Unit::TestSuite.new("Madeleine")
|
||||
|
||||
suite << SnapshotMadeleineTest.suite
|
||||
suite << NumberedFileTest.suite
|
||||
require 'test_command_log'
|
||||
suite << CommandLogTest.suite
|
||||
suite << LoggerTest.suite
|
||||
suite << CommandVerificationTest.suite
|
||||
suite << CustomMarshallerTest.suite
|
||||
suite << ErrorHandlingTest.suite
|
||||
suite << QueryTest.suite
|
||||
suite << TimeOptimizingLoggerTest.suite
|
||||
suite << SharedLockQueryTest.suite
|
||||
require 'test_executer'
|
||||
suite << ExecuterTest.suite
|
||||
|
||||
require 'test_clocked'
|
||||
add_clocked_tests(suite)
|
||||
require 'test_automatic'
|
||||
add_automatic_tests(suite)
|
||||
require 'test_persistence'
|
||||
add_persistence_tests(suite)
|
||||
require 'test_platforms'
|
||||
add_platforms_tests(suite)
|
||||
require 'test_zmarshal'
|
||||
add_zmarshal_tests(suite)
|
||||
|
||||
require 'test/unit/ui/console/testrunner'
|
||||
Test::Unit::UI::Console::TestRunner.run(suite)
|
559
vendor/madeleine-0.7.1/test/test_automatic.rb
vendored
559
vendor/madeleine-0.7.1/test/test_automatic.rb
vendored
|
@ -1,559 +0,0 @@
|
|||
#!/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
|
94
vendor/madeleine-0.7.1/test/test_clocked.rb
vendored
94
vendor/madeleine-0.7.1/test/test_clocked.rb
vendored
|
@ -1,94 +0,0 @@
|
|||
|
||||
require 'madeleine/clock'
|
||||
|
||||
class ClockedAddingSystem
|
||||
include Madeleine::Clock::ClockedSystem
|
||||
|
||||
attr_reader :total
|
||||
|
||||
def initialize
|
||||
@total = 0
|
||||
end
|
||||
|
||||
def add(value)
|
||||
@total += value
|
||||
@total
|
||||
end
|
||||
end
|
||||
|
||||
class TimeTest < Test::Unit::TestCase
|
||||
|
||||
def test_clock
|
||||
target = Madeleine::Clock::Clock.new
|
||||
assert_equal(0, target.time.to_i)
|
||||
assert_equal(0, target.time.usec)
|
||||
|
||||
t1 = Time.at(10000)
|
||||
target.forward_to(t1)
|
||||
assert_equal(t1, target.time)
|
||||
t2 = Time.at(20000)
|
||||
target.forward_to(t2)
|
||||
assert_equal(t2, target.time)
|
||||
|
||||
assert_nothing_raised() {
|
||||
target.forward_to(t2)
|
||||
}
|
||||
end
|
||||
|
||||
def test_time_actor
|
||||
@forward_calls = 0
|
||||
@last_time = Time.at(0)
|
||||
|
||||
target = Madeleine::Clock::TimeActor.launch(self, 0.01)
|
||||
|
||||
# When launch() has returned it should have sent
|
||||
# one synchronous clock-tick before it went to sleep
|
||||
assert_equal(1, @forward_calls)
|
||||
|
||||
sleep(0.1)
|
||||
assert(@forward_calls > 1)
|
||||
target.destroy
|
||||
|
||||
@forward_calls = 0
|
||||
sleep(0.1)
|
||||
assert_equal(0, @forward_calls)
|
||||
end
|
||||
|
||||
# Self-shunt
|
||||
def execute_command(command)
|
||||
mock_system = self
|
||||
command.execute(mock_system)
|
||||
end
|
||||
|
||||
# Self-shunt (system)
|
||||
def clock
|
||||
self
|
||||
end
|
||||
|
||||
# Self-shunt (clock)
|
||||
def forward_to(time)
|
||||
if time < @last_time
|
||||
raise "non-monotonous time"
|
||||
end
|
||||
@last_time = time
|
||||
@forward_calls += 1
|
||||
end
|
||||
|
||||
def test_clocked_system
|
||||
target = Object.new
|
||||
target.extend(Madeleine::Clock::ClockedSystem)
|
||||
t1 = Time.at(10000)
|
||||
target.clock.forward_to(t1)
|
||||
assert_equal(t1, target.clock.time)
|
||||
t2 = Time.at(20000)
|
||||
target.clock.forward_to(t2)
|
||||
assert_equal(t2, target.clock.time)
|
||||
reloaded_target = Marshal.load(Marshal.dump(target))
|
||||
assert_equal(t2, reloaded_target.clock.time)
|
||||
end
|
||||
end
|
||||
|
||||
|
||||
def add_clocked_tests(suite)
|
||||
suite << TimeTest.suite
|
||||
end
|
110
vendor/madeleine-0.7.1/test/test_command_log.rb
vendored
110
vendor/madeleine-0.7.1/test/test_command_log.rb
vendored
|
@ -1,110 +0,0 @@
|
|||
|
||||
unless $LOAD_PATH.include?("lib")
|
||||
$LOAD_PATH.unshift("lib")
|
||||
end
|
||||
unless $LOAD_PATH.include?("test")
|
||||
$LOAD_PATH.unshift("test")
|
||||
end
|
||||
|
||||
require 'madeleine'
|
||||
require 'test/unit'
|
||||
require 'stringio'
|
||||
|
||||
class ExampleCommand
|
||||
attr :value
|
||||
|
||||
def initialize(value)
|
||||
@value = value
|
||||
end
|
||||
|
||||
def execute(system)
|
||||
system.add(@value)
|
||||
end
|
||||
end
|
||||
|
||||
class CommandLogTest < Test::Unit::TestCase
|
||||
|
||||
class MockFile < StringIO
|
||||
def fsync
|
||||
@was_fsynced = true
|
||||
super
|
||||
end
|
||||
|
||||
attr :was_fsynced
|
||||
end
|
||||
|
||||
def test_file_opening
|
||||
file_service = Object.new
|
||||
def file_service.exist?(path)
|
||||
[
|
||||
["some", "path"].join(File::SEPARATOR),
|
||||
["some", "path", "000000000000000000001.command_log"].join(File::SEPARATOR),
|
||||
["some", "path", "000000000000000000002.command_log"].join(File::SEPARATOR),
|
||||
["some", "path", "000000000000000000003.command_log"].join(File::SEPARATOR),
|
||||
].include?(path)
|
||||
end
|
||||
def file_service.dir_entries(path, &block)
|
||||
if path != ["some", "path"].join(File::SEPARATOR)
|
||||
raise "wrong path"
|
||||
end
|
||||
[
|
||||
"000000000000000000001.command_log",
|
||||
"000000000000000000003.command_log",
|
||||
"000000000000000000002.command_log",
|
||||
]
|
||||
end
|
||||
def file_service.open(path, flags)
|
||||
@was_open_called = true
|
||||
if path != ["some", "path", "000000000000000000004.command_log"].join(File::SEPARATOR)
|
||||
raise "wrong file id"
|
||||
end
|
||||
if flags != "wb"
|
||||
raise "wrong flags"
|
||||
end
|
||||
MockFile.new
|
||||
end
|
||||
def file_service.was_open_called
|
||||
@was_open_called
|
||||
end
|
||||
|
||||
target = Madeleine::CommandLog.new("some/path", file_service)
|
||||
assert(file_service.was_open_called)
|
||||
end
|
||||
|
||||
def test_writing_command
|
||||
file_service = Object.new
|
||||
def file_service.exist?(path)
|
||||
[
|
||||
["some", "path"].join(File::SEPARATOR),
|
||||
].include?(path)
|
||||
end
|
||||
def file_service.dir_entries(path, &block)
|
||||
if path != ["some", "path"].join(File::SEPARATOR)
|
||||
raise "wrong path"
|
||||
end
|
||||
[]
|
||||
end
|
||||
def file_service.open(path, flags)
|
||||
@file = MockFile.new
|
||||
@file
|
||||
end
|
||||
def file_service.file
|
||||
@file
|
||||
end
|
||||
def file_service.verify
|
||||
unless @file.was_fsynced
|
||||
raise "file wasn't fsynced"
|
||||
end
|
||||
end
|
||||
command = ExampleCommand.new(1234)
|
||||
|
||||
target = Madeleine::CommandLog.new("some/path", file_service)
|
||||
target.store(command)
|
||||
|
||||
file_service.verify
|
||||
|
||||
file_service.file.rewind
|
||||
assert_equal(Marshal.dump(command), file_service.file.read)
|
||||
end
|
||||
end
|
||||
|
54
vendor/madeleine-0.7.1/test/test_executer.rb
vendored
54
vendor/madeleine-0.7.1/test/test_executer.rb
vendored
|
@ -1,54 +0,0 @@
|
|||
|
||||
unless $LOAD_PATH.include?("lib")
|
||||
$LOAD_PATH.unshift("lib")
|
||||
end
|
||||
unless $LOAD_PATH.include?("test")
|
||||
$LOAD_PATH.unshift("test")
|
||||
end
|
||||
|
||||
require 'test/unit'
|
||||
require 'madeleine'
|
||||
|
||||
class ExecuterTest < Test::Unit::TestCase
|
||||
|
||||
def test_executer
|
||||
system = Object.new
|
||||
command = self
|
||||
executer = Madeleine::Executer.new(system)
|
||||
@executed_with = nil
|
||||
executer.execute(command)
|
||||
assert_same(system, @executed_with)
|
||||
end
|
||||
|
||||
# Self-shunt
|
||||
def execute(system)
|
||||
@executed_with = system
|
||||
end
|
||||
|
||||
def test_execute_with_exception
|
||||
system = Object.new
|
||||
command = Object.new
|
||||
def command.execute(system)
|
||||
raise "this is an exception from a command"
|
||||
end
|
||||
executer = Madeleine::Executer.new(system)
|
||||
assert_raises(RuntimeError) {
|
||||
executer.execute(command)
|
||||
}
|
||||
end
|
||||
|
||||
def test_exception_in_recovery
|
||||
system = Object.new
|
||||
command = Object.new
|
||||
def command.execute(system)
|
||||
raise "this is an exception from a command"
|
||||
end
|
||||
executer = Madeleine::Executer.new(system)
|
||||
executer.recovery {
|
||||
executer.execute(command)
|
||||
}
|
||||
assert_raises(RuntimeError) {
|
||||
executer.execute(command)
|
||||
}
|
||||
end
|
||||
end
|
169
vendor/madeleine-0.7.1/test/test_persistence.rb
vendored
169
vendor/madeleine-0.7.1/test/test_persistence.rb
vendored
|
@ -1,169 +0,0 @@
|
|||
#!/usr/local/bin/ruby -w
|
||||
#
|
||||
# Copyright(c) 2003 Anders Bengtsson
|
||||
#
|
||||
# PersistenceTest is based on the unit tests from Prevayler,
|
||||
# Copyright(c) 2001-2003 Klaus Wuestefeld.
|
||||
#
|
||||
|
||||
$LOAD_PATH.unshift("lib")
|
||||
|
||||
require 'madeleine'
|
||||
require 'test/unit'
|
||||
|
||||
class AddingSystem
|
||||
attr_reader :total
|
||||
|
||||
def initialize
|
||||
@total = 0
|
||||
end
|
||||
|
||||
def add(value)
|
||||
@total += value
|
||||
@total
|
||||
end
|
||||
end
|
||||
|
||||
|
||||
class Addition
|
||||
|
||||
attr_reader :value
|
||||
|
||||
def initialize(value)
|
||||
@value = value
|
||||
end
|
||||
|
||||
def execute(system)
|
||||
system.add(@value)
|
||||
end
|
||||
end
|
||||
|
||||
|
||||
class PersistenceTest < Test::Unit::TestCase
|
||||
|
||||
def setup
|
||||
@madeleine = nil
|
||||
end
|
||||
|
||||
def teardown
|
||||
delete_prevalence_files(prevalence_base)
|
||||
Dir.delete(prevalence_base)
|
||||
end
|
||||
|
||||
def verify(expected_total)
|
||||
assert_equal(expected_total, prevalence_system().total(), "Total")
|
||||
end
|
||||
|
||||
def prevalence_system
|
||||
@madeleine.system
|
||||
end
|
||||
|
||||
def prevalence_base
|
||||
"PrevalenceBase"
|
||||
end
|
||||
|
||||
def clear_prevalence_base
|
||||
@madeleine.close unless @madeleine.nil?
|
||||
delete_prevalence_files(prevalence_base())
|
||||
end
|
||||
|
||||
def delete_prevalence_files(directory_name)
|
||||
return unless File.exist?(directory_name)
|
||||
Dir.foreach(directory_name) {|file_name|
|
||||
next if file_name == '.'
|
||||
next if file_name == '..'
|
||||
file_name.untaint
|
||||
assert(File.delete(directory_name + File::SEPARATOR + file_name) == 1,
|
||||
"Unable to delete #{file_name}")
|
||||
}
|
||||
end
|
||||
|
||||
def crash_recover
|
||||
@madeleine.close unless @madeleine.nil?
|
||||
@madeleine = create_madeleine()
|
||||
end
|
||||
|
||||
def create_madeleine
|
||||
SnapshotMadeleine.new(prevalence_base()) { AddingSystem.new }
|
||||
end
|
||||
|
||||
def snapshot
|
||||
@madeleine.take_snapshot
|
||||
end
|
||||
|
||||
def add(value, expected_total)
|
||||
total = @madeleine.execute_command(Addition.new(value))
|
||||
assert_equal(expected_total, total, "Total")
|
||||
end
|
||||
|
||||
def verify_snapshots(expected_count)
|
||||
count = 0
|
||||
Dir.foreach(prevalence_base) {|name|
|
||||
if name =~ /\.snapshot$/
|
||||
count += 1
|
||||
end
|
||||
}
|
||||
assert_equal(expected_count, count, "snapshots")
|
||||
end
|
||||
|
||||
def test_main
|
||||
clear_prevalence_base
|
||||
|
||||
# There is nothing to recover at first.
|
||||
# A new system will be created.
|
||||
crash_recover
|
||||
|
||||
crash_recover
|
||||
add(40,40)
|
||||
add(30,70)
|
||||
verify(70)
|
||||
|
||||
crash_recover
|
||||
verify(70)
|
||||
|
||||
add(20,90)
|
||||
add(15,105)
|
||||
verify_snapshots(0)
|
||||
snapshot
|
||||
verify_snapshots(1)
|
||||
snapshot
|
||||
verify_snapshots(2)
|
||||
verify(105)
|
||||
|
||||
crash_recover
|
||||
snapshot
|
||||
add(10,115)
|
||||
snapshot
|
||||
add(5,120)
|
||||
add(4,124)
|
||||
verify(124)
|
||||
|
||||
crash_recover
|
||||
add(3,127)
|
||||
verify(127)
|
||||
|
||||
verify_snapshots(4)
|
||||
|
||||
clear_prevalence_base
|
||||
snapshot
|
||||
|
||||
crash_recover
|
||||
add(10,137)
|
||||
add(2,139)
|
||||
crash_recover
|
||||
verify(139)
|
||||
end
|
||||
|
||||
def test_main_in_safe_level_one
|
||||
thread = Thread.new {
|
||||
$SAFE = 1
|
||||
test_main
|
||||
}
|
||||
thread.join
|
||||
end
|
||||
end
|
||||
|
||||
|
||||
def add_persistence_tests(suite)
|
||||
suite << PersistenceTest.suite
|
||||
end
|
65
vendor/madeleine-0.7.1/test/test_platforms.rb
vendored
65
vendor/madeleine-0.7.1/test/test_platforms.rb
vendored
|
@ -1,65 +0,0 @@
|
|||
|
||||
class AddCommand
|
||||
def initialize(obj)
|
||||
@obj = obj
|
||||
end
|
||||
|
||||
def execute(system)
|
||||
system[@obj.myid] = @obj
|
||||
end
|
||||
end
|
||||
|
||||
class Foo
|
||||
attr_accessor :myid
|
||||
end
|
||||
|
||||
|
||||
# Checks for a strange marshalling or IO bug observed in the
|
||||
# native win32-port of Ruby on WinXP.
|
||||
#
|
||||
# Test case provided by Steve Conover.
|
||||
|
||||
class WierdWin32CorruptionTest < Test::Unit::TestCase
|
||||
include TestUtils
|
||||
|
||||
def teardown
|
||||
(1..5).each {|i|
|
||||
delete_directory("corruption_test#{i}")
|
||||
}
|
||||
end
|
||||
|
||||
def doCorruptionTest(idstr, storagenumber)
|
||||
m = SnapshotMadeleine.new("corruption_test" + storagenumber) { Hash.new() }
|
||||
|
||||
f = Foo.new()
|
||||
f.myid = idstr
|
||||
|
||||
m.execute_command(AddCommand.new(f))
|
||||
m.close()
|
||||
m = SnapshotMadeleine.new("corruption_test" + storagenumber) { Hash.new() }
|
||||
end
|
||||
|
||||
def testErrorOne
|
||||
doCorruptionTest("123456789012345678901", "1")
|
||||
end
|
||||
|
||||
def testErrorTwo
|
||||
doCorruptionTest("aaaaaaaaaaaaaaaaaaaaa", "2")
|
||||
end
|
||||
|
||||
def testNoErrorOne
|
||||
doCorruptionTest("12345678901234567890", "3")
|
||||
end
|
||||
|
||||
def testNoErrorTwo
|
||||
doCorruptionTest("1234567890123456789012", "4")
|
||||
end
|
||||
|
||||
def testWhiteSpace
|
||||
doCorruptionTest("\n\r\t \r\n", "5")
|
||||
end
|
||||
end
|
||||
|
||||
def add_platforms_tests(suite)
|
||||
suite << WierdWin32CorruptionTest.suite
|
||||
end
|
52
vendor/madeleine-0.7.1/test/test_zmarshal.rb
vendored
52
vendor/madeleine-0.7.1/test/test_zmarshal.rb
vendored
|
@ -1,52 +0,0 @@
|
|||
|
||||
require 'madeleine/zmarshal'
|
||||
|
||||
require 'stringio'
|
||||
require 'yaml'
|
||||
|
||||
class ZMarshalTest < Test::Unit::TestCase
|
||||
|
||||
def test_full_circle_marshal
|
||||
target = Madeleine::ZMarshal.new(Marshal)
|
||||
object = ["foo", "bar"]
|
||||
stream = StringIO.new
|
||||
|
||||
target.dump(object, stream)
|
||||
stream.rewind
|
||||
result = target.load(stream)
|
||||
|
||||
assert_equal(object, result)
|
||||
end
|
||||
|
||||
def test_full_circle_yaml
|
||||
target = Madeleine::ZMarshal.new(YAML)
|
||||
object = ["foo", "bar"]
|
||||
stream = StringIO.new
|
||||
|
||||
target.dump(object, stream)
|
||||
stream.rewind
|
||||
result = target.load(stream)
|
||||
|
||||
assert_equal(object, result)
|
||||
end
|
||||
|
||||
def test_compression
|
||||
target = Madeleine::ZMarshal.new(Marshal)
|
||||
object = "x" * 1000
|
||||
|
||||
stream = StringIO.new
|
||||
Marshal.dump(object, stream)
|
||||
reference_size = stream.size
|
||||
|
||||
stream = StringIO.new
|
||||
target.dump(object, stream)
|
||||
compressed_size = stream.size
|
||||
|
||||
assert(compressed_size < reference_size)
|
||||
end
|
||||
end
|
||||
|
||||
|
||||
def add_zmarshal_tests(suite)
|
||||
suite << ZMarshalTest.suite
|
||||
end
|
Loading…
Reference in a new issue