 
 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 | |