1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 122 123 124 125 126 127 128 129 130 131 132 133 134 135 136 137 138 139 140 141 142 143 144 145 146 147 148 149 150 151 152 153 154 155 156 157 158 159 160 161 162 163 164 165 166 167 168 169 170 171 172 173 174 175 176 177 178 179 180 181 182 183 184 185 186 187 188 189 190 191 192 193 194 195 196 197 198 199 200 201 202 203 204 205 206 207 208 209 210
|
Feature: Simple examples and Contract violations
Contracts.ruby allows specification of contracts on per-method basis, where
method arguments and return value will be validated upon method call.
Example:
```ruby
Contract C::Num, C::Num => C::Num
def add(a, b)
a + b
end
```
Here `Contract arg_contracts... => return_contract` defines list of argument
contracts `args_contracts...` as `C::Num, C::Num` (i.e.: both arguments
should be numbers) and return value contract `return_contract` as `C::Num`
(i.e.: return value should be a number too).
`Contract arg_contracts... => return_contract` affects next defined instance,
class or singleton method, meaning that all of these work:
- [Instance method](#instance-method),
- [Class method](#class-method),
- [Singleton method](#singleton-method).
Whenever invalid argument is passed to a contracted method, corresponding
`ContractError` will be raised. That happens right after bad value got into
system protected by contracts and prevents error propagation: first
non-contracts library frame in exception's backtrace is a culprit for passing
an invalid argument - you do not need to verify 20-30 frames to find a
culprit! Example of such error: [instance method contract
violation](#instance-method-contract-violation).
Whenever invalid return value is returned from a contracted method,
corresponding `ContractError` will be raised. That happens right after method
returned this value and prevents error propagation: `At: your_filename.rb:17`
part of error message points directly to a culprit method. Example of such
error: [return value contract
violation](#singleton-method-return-value-contract-violation).
Contract violation error consists of such parts:
- Violation type:
- `Contract violation for argument X of Y: (ParamContractError)`,
- `Contract violation for return value (ReturnContractError)`.
- Expected contract, example: `Expected: Num`.
- Actual value, example: `Actual: "foo"`.
- Location of violated contract, example: `Value guarded in: Example::add`.
- Full contract, example: `With Contract: Num, Num => Num`.
- Source code location of contracted method, example: `At: lib/your_library/some_class.rb:17`.
Scenario: Instance method
Given a file named "instance_method.rb" with:
"""ruby
require "contracts"
C = Contracts
class Example
include Contracts::Core
Contract C::Num, C::Num => C::Num
def add(a, b)
a + b
end
end
puts Example.new.add(2, 2)
"""
When I run `ruby instance_method.rb`
Then the output should contain:
"""
4
"""
Scenario: Instance method contract violation
Given a file named "instance_method_violation.rb" with:
"""ruby
require "contracts"
C = Contracts
class Example
include Contracts::Core
Contract C::Num, C::Num => C::Num
def add(a, b)
a + b
end
end
puts Example.new.add(2, "foo")
"""
When I run `ruby instance_method_violation.rb`
Then the output should contain:
"""
: Contract violation for argument 2 of 2: (ParamContractError)
Expected: Num,
Actual: "foo"
Value guarded in: Example::add
With Contract: Num, Num => Num
At: instance_method_violation.rb:8
"""
Scenario: Class method
Given a file named "class_method.rb" with:
"""ruby
require "contracts"
C = Contracts
class Example
include Contracts::Core
Contract C::Num, C::Num => C::Num
def self.add(a, b)
a + b
end
end
puts Example.add(2, 2)
"""
When I run `ruby class_method.rb`
Then the output should contain:
"""
4
"""
Scenario: Class method contract violation
Given a file named "class_method_violation.rb" with:
"""ruby
require "contracts"
C = Contracts
class Example
include Contracts::Core
Contract C::Num, C::Num => C::Num
def self.add(a, b)
a + b
end
end
puts Example.add(:foo, 2)
"""
When I run `ruby class_method_violation.rb`
Then the output should contain:
"""
: Contract violation for argument 1 of 2: (ParamContractError)
Expected: Num,
Actual: :foo
Value guarded in: Example::add
With Contract: Num, Num => Num
At: class_method_violation.rb:8
"""
Scenario: Singleton method
Given a file named "singleton_method.rb" with:
"""ruby
require "contracts"
C = Contracts
class Example
include Contracts::Core
class << self
Contract C::Num, C::Num => C::Num
def add(a, b)
a + b
end
end
end
puts Example.add(2, 2)
"""
When I run `ruby singleton_method.rb`
Then the output should contain:
"""
4
"""
Scenario: Singleton method return value contract violation
Given a file named "singleton_method_violation.rb" with:
"""ruby
require "contracts"
C = Contracts
class Example
include Contracts::Core
class << self
Contract C::Num, C::Num => C::Num
def add(a, b)
# notice here non-number is returned
nil
end
end
end
puts Example.add(2, 2)
"""
When I run `ruby singleton_method_violation.rb`
Then the output should contain:
"""
: Contract violation for return value: (ReturnContractError)
Expected: Num,
Actual: nil
Value guarded in: Example::add
With Contract: Num, Num => Num
At: singleton_method_violation.rb:9
"""
|