filename../lib/contract.rb
total coverage97.9
code coverage95.1
  1 # Contains the main implementation of the Contract class.
  2 # :main:Contract
  3 
  4 require 'test/unit/testcase'
  5 require 'test/unit/testresult'
  6 require 'test/unit/testsuite'
  7 
  8 require 'contract/exception'
  9 require 'contract/overrides'
 10 require 'contract/assertions'
 11 require 'contract/integration'
 12 
 13 # Represents a contract between Objects as a collection of test cases.
 14 # Objects are said to fulfill a contract if all test cases suceed. This
 15 # is useful for ensuring that Objects your code is getting behave in a
 16 # way that you expect them to behave so you can fail early or execute
 17 # different logic for Objects with different interfaces.
 18 #
 19 # The tests of the test suite will be run on a copy of the tested Object
 20 # so you can safely test its behavior without having to fear data loss.
 21 # By default Contracts obtain deep copies of Objects by serializing and
 22 # unserializing them with Ruby's +Marshal+ functionality. This will work
 23 # in most cases but can fail for Objects containing unserializable parts
 24 # like Procs, Files or Sockets. In those cases it is currently of your
 25 # responsibility to provide a fitting implementation by overwriting the
 26 # Contract.deep_copy method. In the future the contract library might
 27 # provide different implementations of it via Ruby's mixin mechanism.
 28 class Contract < Test::Unit::TestCase
 29   id = %q$Id: contract.rb 86 2005-02-02 20:58:46Z flgr $
 30   # The Version of the contract library you are using.
 31   Version = id.split(" ")[2].to_i
 32 
 33   # Returns true if the given object fulfills this contract.
 34   # This is useful for implementing dispatching mechanisms where you
 35   # want to hit different code branches based on whether an Object has
 36   # one or another interface.
 37   def self.fulfilled_by?(object)
 38     self.test(object).nil?
 39   end
 40 
 41   class << self
 42     # You can use contracts in +case+ ... +when+ statements or in
 43     # Module#signature checks. For example:
 44     #   case obj
 45     #     when Numeric then obj + 1
 46     #     when ListContract then obj + [1]
 47     #   end
 48     alias :=== :fulfilled_by?
 49   end
 50 
 51   # Enforces that object implements this contract. If it does not an
 52   # Exception will be raised. This is useful for example useful when you
 53   # need to ensure that the arguments given to a method fulfill a given 
 54   # contract.
 55   #
 56   # Note that using Module#enforce is a higher-level way of checking
 57   # arguments and return values for the conformance of a given type. You
 58   # might however still want to use Contract.enforce directly when you
 59   # need more flexibility.
 60   def self.enforce(object)
 61     reason = self.test(object)
 62     raise reason if reason
 63   end
 64 
 65   # Tests whether the given Object fulfils this contract.
 66   #
 67   # Note: This will return the first reason for the Object not fulfilling
 68   # the contract or +nil+ in case it fulfills it.
 69   def self.test(object, return_all = false)
 70     reasons = []
 71 
 72     result = Test::Unit::TestResult.new
 73     result.add_listener(Test::Unit::TestResult::FAULT) do |fault|
 74       reason = Contract.fault_to_exception(fault, object, self)
 75       return reason unless return_all
 76       reasons << reason
 77     end
 78 
 79     self.suite.run(result, deep_copy(object))
 80 
 81     return reasons unless result.passed?
 82   end
 83 
 84   # Same as Contract.test, but will return all reasons for the Object not
 85   # fulfilling the contract in an Array or nil in case of fulfillment.
 86   # (as an Array of Exceptions) or +nil+ in the case it does fulfill it.
 87   def self.test_all(object)
 88     test(object, true)
 89   end
 90 
 91   # This method is used internally for getting a copy of Objects that
 92   # the contract is checked against. By default it uses Ruby's +Marshal+
 93   # functionality for obtaining a copy, but this can fail if the Object
 94   # contains unserializable parts like Procs, Files or Sockets. It is
 95   # currently your responsibility to provide a fitting implementation
 96   # of this by overwriting the method in case the default implementation
 97   # does not work for you. In the future the contract library might offer
 98   # different implementations for this via Ruby's mixin mechanism.
 99   def self.deep_copy(object)
100     Marshal.load(Marshal.dump(object))
101   end
102 
103   # Fulfilling this Contract (via Module#fulfills) implies that the Object
104   # is automatically compatible with the specified mixins which will then
105   # be included automatically. For example the Enumerable relationship
106   # could be expressed like this:
107   #
108   #   class EnumerableContract < Contract
109   #     provides :each
110   #     implies Enumerable
111   #   end
112   def self.implies(*mixins)
113     mixins.each do |mixin|
114       if not mixin.is_a?(Module) then
115         raise(TypeError, "wrong argument type #{mixin.class} for " +
116           "#{mixin.inspect} (expected Module)")
117       end
118     end
119 
120     @implications ||= Array.new
121     @implications += mixins
122   end
123 
124   class << self
125     # Returns all implications of a given contract that were stated via
126     # calling Contract.implies.
127     attr_reader :implications
128   end
129 
130   def self.implications() # :nodoc:
131     @implications ||= Array.new
132 
133     ancestors[1 .. -1].inject(@implications) do |result, ancestor|
134       if ancestor.respond_to?(:implications) then
135         ancestor.implications + result
136       else
137         result
138       end
139     end.uniq
140   end
141 end

Valid XHTML 1.1! Valid CSS!