This page contains automated test results for code from O'Reilly's Ruby Cookbook. If this code looks interesting or useful, you might want to buy the whole book.
| Enforcing Software Contracts (written by Maurice Codik) | ||
|---|---|---|
| Code | Expected | Actual |
module Contracts
def valid_contract(input)
if @user_defined and @user_defined[input]
@user_defined[input]
else
case input
when :number
lambda { |x| x.is_a? Numeric }
when :string
lambda { |x| x.respond_to? :to_str }
when :anything
lambda { |x| true }
else
lambda { |x| false }
end
end
end
class ContractViolation < StandardError
end
def define_data(inputs={}.freeze)
@user_defined ||= {}
inputs.each do |name, contract|
@user_defined[name] = contract if contract.respond_to? :call
end
end
def contract(method, *inputs)
@contracts ||= {}
@contracts[method] = inputs
method_added(method)
end
def setup_contract(method, inputs)
@contracts[method] = nil
method_renamed = "__#{method}".intern
conditions = ""
inputs.flatten.each_with_index do |input, i|
conditions << %{
if not self.class.valid_contract(#{input.inspect}).call(args[#{i}])
raise ContractViolation, "argument #{i+1} of method '#{method}' must satisfy the '#{input}' contract", caller
end
}
end
class_eval %{
alias_method #{method_renamed.inspect}, #{method.inspect}
def #{method}(*args)
#{conditions}
return #{method_renamed}(*args)
end
}
end
def method_added(method)
inputs = @contracts[method]
setup_contract(method, inputs) if inputs
end
end
class TestContracts
def hello(n, s, f)
n.times { f.write "hello #{s}!\n" }
end
extend Contracts
writable_and_open = lambda do |x|
x.respond_to?('write') and x.respond_to?('closed?') and not x.closed?
end
define_data(:writable => writable_and_open,
:positive => lambda {|x| x >= 0 })
contract :hello, [:positive, :string, :writable]
end
tc = TestContracts.new
tc.hello(2, 'world', $stdout) |
hello world! hello world! |
hello world! hello world! |
tc.hello(-1, 'world', $stdout) |
Contracts::ContractViolation: argument 1 of method 'hello' must satisfy the 'positive' contract |
Error! (Exception?) Here's stdout: Contracts::ContractViolation: argument 1 of method 'hello' must satisfy the 'positive' contract from (irb):69 |
tc.hello(2, 3001, $stdout) |
test-contracts.rb:22: argument 2 of method 'hello' must satisfy the 'string' contract (Contracts::ContractViolation) |
Error! (Exception?) Here's stdout: Contracts::ContractViolation: argument 2 of method 'hello' must satisfy the 'string' contract from (irb):70 |
closed_file = open('file.txt', 'w') { }
tc.hello(2, 'world', closed_file) |
Contracts::ContractViolation: argument 3 of method 'hello' must satisfy the 'writable' contract |
Error! (Exception?) Here's stdout: Contracts::ContractViolation: argument 3 of method 'hello' must satisfy the 'writable' contract from (irb):72 |
contract :hello, [:positive, :string, :writable]
def hello(n,s,f)
if not (n >= 0)
raise ContractViolation,
"argument 1 of method 'hello' must satisfy the 'positive' contract", caller
end
if not (s.respond_to? String)
raise ContractViolation,
"argument 2 of method 'hello' must satisfy the 'string' contract",
caller
end
if not (f.respond_to?('write') and f.respond_to?('closed?')
and not f.closed?)
raise ContractViolation,
"argument 3 of method 'hello' must satisfy the 'writable' contract",
caller
end
return __hello(n,s,f)
end
def __hello(n,s,f)
n.times { f.write "hello #{s}!\n" }
end |
nil | |