Skip to content

@avdi /exception_tester.rb
Created

Embed URL

HTTPS clone URL

Subversion checkout URL

You can clone with
or
.
Download ZIP
A proof of concept for exception testing in Ruby
require 'set'
class ExceptionTester
class TestException < Exception
end
# Accepts a block containing the code you want to make exception safety
# assertions about.
def initialize(&exercise)
@exercise = exercise
end
# Accepts a block containing a predicate which will prove or disprove the
# exception safety of the exercised code
def assert(&invariant)
recording = record(&@exercise)
recording.size.times do |n|
playback(recording, n, &invariant)
unless invariant.call
raise "Assertion failed on call #{n}: #{@signature.inspect}"
end
end
end
private
# Makes a recording - which is a Set of codepoint tuples - of a given block of code.
def record(&block)
recording = Set.new
recorder = lambda do |event, file, line, id, binding, classname|
recording.add([event, file, line, id, classname])
end
set_trace_func(recorder)
block.call
set_trace_func(nil)
# We only care about method calls
recording.reject!{|event| !%w[call c-call].include?(event[0])}
# Get rid of calls outside of the block
recording.delete_if{|sig|
sig[0] == "c-call" &&
sig[1] == __FILE__ &&
sig[3] == :call &&
sig[4] == Proc
}
recording.delete_if{|sig|
sig[0] == "c-call" &&
sig[1] == __FILE__ &&
sig[3] == :set_trace_func &&
sig[4] == Kernel
}
recording
end
# Playback the given recording, and raise TestException once it reaches
# fail_index
def playback(recording, fail_index, &invariant)
recording = recording.dup
recording_size = recording.size
call_count = 0
player = lambda do |event, file, line, id, binding, classname|
signature = [event, file, line, id, classname]
if recording.member?(signature)
@signature = signature
call_count = recording_size - recording.size
recording.delete(signature)
if fail_index == call_count
raise TestException
end
end
end
set_trace_func(player)
begin
@exercise.call
rescue TestException
# do nothing
ensure
set_trace_func(nil)
end
end
end
if __FILE__ == $0
def swap_keys(hash, x_key, y_key)
temp = hash[x_key]
hash[x_key] = hash[y_key]
hash[y_key] = temp
end
h = {:a => 42, :b => 23}
tester = ExceptionTester.new{ swap_keys(h, :a, :b) }
tester.assert{
# Assert the keys are either fully swapped or not swapped at all
(h == {:a => 42, :b => 23}) ||
(h == {:a => 23, :b => 42})
}
end
@avdi
Owner

Note: this code is a companion to the eBook "Exceptional Ruby", which can be found at http://exceptionalruby.com.

@akostrikov

Avdi, this tester is truly inspiring on writing functional code in ruby.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Something went wrong with that request. Please try again.