filename | ../lib/contract.rb |
total coverage | 97.9 |
code coverage | 95.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