contracts 0.4 → 0.5
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- checksums.yaml +7 -0
- data/Gemfile +12 -0
- data/Gemfile.lock +34 -0
- data/README.md +75 -0
- data/TODO.markdown +6 -0
- data/TUTORIAL.md +485 -0
- data/benchmarks/bench.rb +67 -0
- data/benchmarks/invariants.rb +81 -0
- data/benchmarks/wrap_test.rb +59 -0
- data/contracts.gemspec +13 -0
- data/lib/contracts.rb +108 -23
- data/lib/{builtin_contracts.rb → contracts/builtin_contracts.rb} +1 -1
- data/lib/contracts/decorators.rb +179 -0
- data/lib/contracts/invariants.rb +75 -0
- data/lib/contracts/support.rb +22 -0
- data/lib/{testable.rb → contracts/testable.rb} +0 -0
- data/lib/contracts/version.rb +3 -0
- data/spec/builtin_contracts_spec.rb +216 -0
- data/spec/contracts_spec.rb +273 -0
- data/spec/fixtures/fixtures.rb +276 -0
- data/spec/invariants_spec.rb +19 -0
- data/spec/module_spec.rb +17 -0
- data/spec/spec_helper.rb +94 -0
- metadata +45 -43
- data/lib/decorators.rb +0 -164
@@ -0,0 +1,75 @@
|
|
1
|
+
class InvariantError < StandardError
|
2
|
+
def to_contract_error
|
3
|
+
self
|
4
|
+
end
|
5
|
+
end
|
6
|
+
|
7
|
+
module Contracts
|
8
|
+
module Invariants
|
9
|
+
def self.included(base)
|
10
|
+
common base
|
11
|
+
end
|
12
|
+
|
13
|
+
def self.extended(base)
|
14
|
+
common base
|
15
|
+
end
|
16
|
+
|
17
|
+
def self.common(base)
|
18
|
+
return if base.respond_to?(:Invariant)
|
19
|
+
|
20
|
+
base.extend(InvariantExtension)
|
21
|
+
end
|
22
|
+
|
23
|
+
def verify_invariants!(method)
|
24
|
+
return unless self.class.respond_to?(:invariants)
|
25
|
+
|
26
|
+
self.class.invariants.each do |invariant|
|
27
|
+
invariant.check_on(self, method)
|
28
|
+
end
|
29
|
+
end
|
30
|
+
|
31
|
+
module InvariantExtension
|
32
|
+
def Invariant(name, &condition)
|
33
|
+
return if ENV["NO_CONTRACTS"]
|
34
|
+
|
35
|
+
invariants << Invariant.new(self, name, &condition)
|
36
|
+
end
|
37
|
+
|
38
|
+
def invariants
|
39
|
+
@invariants ||= []
|
40
|
+
end
|
41
|
+
end
|
42
|
+
|
43
|
+
class Invariant
|
44
|
+
def initialize(klass, name, &condition)
|
45
|
+
@klass, @name, @condition = klass, name, condition
|
46
|
+
end
|
47
|
+
|
48
|
+
def expected
|
49
|
+
"#{@name} condition to be true"
|
50
|
+
end
|
51
|
+
|
52
|
+
def check_on(target, method)
|
53
|
+
return if target.instance_eval(&@condition)
|
54
|
+
|
55
|
+
self.class.failure_callback(:expected => expected,
|
56
|
+
:actual => false,
|
57
|
+
:target => target,
|
58
|
+
:method => method)
|
59
|
+
end
|
60
|
+
|
61
|
+
def self.failure_callback(data)
|
62
|
+
raise InvariantError, failure_msg(data)
|
63
|
+
end
|
64
|
+
|
65
|
+
def self.failure_msg(data)
|
66
|
+
%{Invariant violation:
|
67
|
+
Expected: #{data[:expected]}
|
68
|
+
Actual: #{data[:actual]}
|
69
|
+
Value guarded in: #{data[:target].class}::#{Support.method_name(data[:method])}
|
70
|
+
At: #{Support.method_position(data[:method])}}
|
71
|
+
end
|
72
|
+
end
|
73
|
+
|
74
|
+
end
|
75
|
+
end
|
@@ -0,0 +1,22 @@
|
|
1
|
+
module Contracts
|
2
|
+
module Support
|
3
|
+
|
4
|
+
def self.method_position(method)
|
5
|
+
if RUBY_VERSION =~ /^1\.8/
|
6
|
+
if method.respond_to?(:__file__)
|
7
|
+
method.__file__ + ":" + method.__line__.to_s
|
8
|
+
else
|
9
|
+
method.inspect
|
10
|
+
end
|
11
|
+
else
|
12
|
+
file, line = method.source_location
|
13
|
+
file + ":" + line.to_s
|
14
|
+
end
|
15
|
+
end
|
16
|
+
|
17
|
+
def self.method_name(method)
|
18
|
+
method.is_a?(Proc) ? "Proc" : method.name
|
19
|
+
end
|
20
|
+
|
21
|
+
end
|
22
|
+
end
|
File without changes
|
@@ -0,0 +1,216 @@
|
|
1
|
+
include Contracts
|
2
|
+
|
3
|
+
RSpec.describe "Contracts:" do
|
4
|
+
before :all do
|
5
|
+
@o = Object.new
|
6
|
+
end
|
7
|
+
|
8
|
+
describe "Num:" do
|
9
|
+
it "should pass for Fixnums" do
|
10
|
+
expect { @o.double(2) }.to_not raise_error
|
11
|
+
end
|
12
|
+
|
13
|
+
it "should pass for Floats" do
|
14
|
+
expect { @o.double(2.2) }.to_not raise_error
|
15
|
+
end
|
16
|
+
|
17
|
+
it "should fail for Strings" do
|
18
|
+
expect { @o.double("bad") }.to raise_error(ContractError)
|
19
|
+
end
|
20
|
+
end
|
21
|
+
|
22
|
+
describe "Pos:" do
|
23
|
+
it "should pass for positive numbers" do
|
24
|
+
expect { @o.pos_test(1) }.to_not raise_error
|
25
|
+
end
|
26
|
+
|
27
|
+
it "should fail for negative numbers" do
|
28
|
+
expect { @o.pos_test(-1) }.to raise_error(ContractError)
|
29
|
+
end
|
30
|
+
end
|
31
|
+
|
32
|
+
describe "Neg:" do
|
33
|
+
it "should pass for negative numbers" do
|
34
|
+
expect { @o.neg_test(-1) }.to_not raise_error
|
35
|
+
end
|
36
|
+
|
37
|
+
it "should fail for positive numbers" do
|
38
|
+
expect { @o.neg_test(1) }.to raise_error(ContractError)
|
39
|
+
end
|
40
|
+
end
|
41
|
+
|
42
|
+
describe "Any:" do
|
43
|
+
it "should pass for numbers" do
|
44
|
+
expect { @o.show(1) }.to_not raise_error
|
45
|
+
end
|
46
|
+
it "should pass for strings" do
|
47
|
+
expect { @o.show("bad") }.to_not raise_error
|
48
|
+
end
|
49
|
+
it "should pass for procs" do
|
50
|
+
expect { @o.show(lambda {}) }.to_not raise_error
|
51
|
+
end
|
52
|
+
it "should pass for nil" do
|
53
|
+
expect { @o.show(nil) }.to_not raise_error
|
54
|
+
end
|
55
|
+
end
|
56
|
+
|
57
|
+
describe "None:" do
|
58
|
+
it "should fail for numbers" do
|
59
|
+
expect { @o.fail_all(1) }.to raise_error(ContractError)
|
60
|
+
end
|
61
|
+
it "should fail for strings" do
|
62
|
+
expect { @o.fail_all("bad") }.to raise_error(ContractError)
|
63
|
+
end
|
64
|
+
it "should fail for procs" do
|
65
|
+
expect { @o.fail_all(lambda {}) }.to raise_error(ContractError)
|
66
|
+
end
|
67
|
+
it "should fail for nil" do
|
68
|
+
expect { @o.fail_all(nil) }.to raise_error(ContractError)
|
69
|
+
end
|
70
|
+
end
|
71
|
+
|
72
|
+
describe "Or:" do
|
73
|
+
it "should pass for nums" do
|
74
|
+
expect { @o.num_or_string(1) }.to_not raise_error
|
75
|
+
end
|
76
|
+
|
77
|
+
it "should pass for strings" do
|
78
|
+
expect { @o.num_or_string("bad") }.to_not raise_error
|
79
|
+
end
|
80
|
+
|
81
|
+
it "should fail for nil" do
|
82
|
+
expect { @o.num_or_string(nil) }.to raise_error(ContractError)
|
83
|
+
end
|
84
|
+
end
|
85
|
+
|
86
|
+
describe "Xor:" do
|
87
|
+
it "should pass for an object with a method :good" do
|
88
|
+
expect { @o.xor_test(A.new) }.to_not raise_error
|
89
|
+
end
|
90
|
+
|
91
|
+
it "should pass for an object with a method :bad" do
|
92
|
+
expect { @o.xor_test(B.new) }.to_not raise_error
|
93
|
+
end
|
94
|
+
|
95
|
+
it "should fail for an object with neither method" do
|
96
|
+
expect { @o.xor_test(1) }.to raise_error(ContractError)
|
97
|
+
end
|
98
|
+
|
99
|
+
it "should fail for an object with both methods :good and :bad" do
|
100
|
+
expect { @o.xor_test(C.new) }.to raise_error(ContractError)
|
101
|
+
end
|
102
|
+
end
|
103
|
+
|
104
|
+
describe "And:" do
|
105
|
+
it "should pass for an object of class A that has a method :good" do
|
106
|
+
expect { @o.and_test(A.new) }.to_not raise_error
|
107
|
+
end
|
108
|
+
|
109
|
+
it "should fail for an object that has a method :good but isn't of class A" do
|
110
|
+
expect { @o.and_test(C.new) }.to raise_error(ContractError)
|
111
|
+
end
|
112
|
+
end
|
113
|
+
|
114
|
+
describe "RespondTo:" do
|
115
|
+
it "should pass for an object that responds to :good" do
|
116
|
+
expect { @o.responds_test(A.new) }.to_not raise_error
|
117
|
+
end
|
118
|
+
|
119
|
+
it "should fail for an object that doesn't respond to :good" do
|
120
|
+
expect { @o.responds_test(B.new) }.to raise_error(ContractError)
|
121
|
+
end
|
122
|
+
end
|
123
|
+
|
124
|
+
describe "Send:" do
|
125
|
+
it "should pass for an object that returns true for method :good" do
|
126
|
+
expect { @o.send_test(A.new) }.to_not raise_error
|
127
|
+
end
|
128
|
+
|
129
|
+
it "should fail for an object that returns false for method :good" do
|
130
|
+
expect { @o.send_test(C.new) }.to raise_error(ContractError)
|
131
|
+
end
|
132
|
+
end
|
133
|
+
|
134
|
+
describe "Exactly:" do
|
135
|
+
it "should pass for an object that is exactly a Parent" do
|
136
|
+
expect { @o.exactly_test(Parent.new) }.to_not raise_error
|
137
|
+
end
|
138
|
+
|
139
|
+
it "should fail for an object that inherits from Parent" do
|
140
|
+
expect { @o.exactly_test(Child.new) }.to raise_error(ContractError)
|
141
|
+
end
|
142
|
+
|
143
|
+
it "should fail for an object that is not related to Parent at all" do
|
144
|
+
expect { @o.exactly_test(A.new) }.to raise_error(ContractError)
|
145
|
+
end
|
146
|
+
end
|
147
|
+
|
148
|
+
describe "Not:" do
|
149
|
+
it "should pass for an argument that isn't nil" do
|
150
|
+
expect { @o.not_nil(1) }.to_not raise_error
|
151
|
+
end
|
152
|
+
|
153
|
+
it "should fail for nil" do
|
154
|
+
expect { @o.not_nil(nil) }.to raise_error(ContractError)
|
155
|
+
end
|
156
|
+
end
|
157
|
+
|
158
|
+
describe "ArrayOf:" do
|
159
|
+
it "should pass for an array of nums" do
|
160
|
+
expect { @o.product([1, 2, 3]) }.to_not raise_error
|
161
|
+
end
|
162
|
+
|
163
|
+
it "should fail for an array with one non-num" do
|
164
|
+
expect { @o.product([1, 2, 3, "bad"]) }.to raise_error(ContractError)
|
165
|
+
end
|
166
|
+
|
167
|
+
it "should fail for a non-array" do
|
168
|
+
expect { @o.product(1) }.to raise_error(ContractError)
|
169
|
+
end
|
170
|
+
end
|
171
|
+
|
172
|
+
describe "Bool:" do
|
173
|
+
it "should pass for an argument that is a boolean" do
|
174
|
+
expect { @o.bool_test(true) }.to_not raise_error
|
175
|
+
expect { @o.bool_test(false) }.to_not raise_error
|
176
|
+
end
|
177
|
+
|
178
|
+
it "should fail for nil" do
|
179
|
+
expect { @o.bool_test(nil) }.to raise_error(ContractError)
|
180
|
+
end
|
181
|
+
end
|
182
|
+
|
183
|
+
describe "Maybe:" do
|
184
|
+
it "should pass for nums" do
|
185
|
+
expect(@o.maybe_double(1)).to eq(2)
|
186
|
+
end
|
187
|
+
|
188
|
+
it "should pass for nils" do
|
189
|
+
expect(@o.maybe_double(nil)).to eq(nil)
|
190
|
+
end
|
191
|
+
|
192
|
+
it "should fail for strings" do
|
193
|
+
expect { @o.maybe_double("foo") }.to raise_error(ContractError)
|
194
|
+
end
|
195
|
+
end
|
196
|
+
|
197
|
+
describe 'HashOf:' do
|
198
|
+
context 'given a fulfilled contract' do
|
199
|
+
it { expect(@o.gives_max_value(:panda => 1, :bamboo => 2)).to eq(2) }
|
200
|
+
end
|
201
|
+
|
202
|
+
context 'given an unfulfilled contract' do
|
203
|
+
it { expect { @o.gives_max_value(:panda => '1', :bamboo => '2') }.to raise_error(ContractError) }
|
204
|
+
end
|
205
|
+
|
206
|
+
describe '#to_s' do
|
207
|
+
context 'given Symbol => String' do
|
208
|
+
it { expect(HashOf[Symbol, String].to_s).to eq('Hash<Symbol, String>') }
|
209
|
+
end
|
210
|
+
|
211
|
+
context 'given String => Num' do
|
212
|
+
it { expect(HashOf[String, Num].to_s).to eq('Hash<String, Contracts::Num>') }
|
213
|
+
end
|
214
|
+
end
|
215
|
+
end
|
216
|
+
end
|
@@ -0,0 +1,273 @@
|
|
1
|
+
include Contracts
|
2
|
+
|
3
|
+
RSpec.describe "Contracts:" do
|
4
|
+
before :all do
|
5
|
+
@o = Object.new
|
6
|
+
end
|
7
|
+
|
8
|
+
describe "basic" do
|
9
|
+
it "should fail for insufficient arguments" do
|
10
|
+
expect {
|
11
|
+
@o.hello
|
12
|
+
}.to raise_error
|
13
|
+
end
|
14
|
+
|
15
|
+
it "should fail for insufficient contracts" do
|
16
|
+
expect { @o.bad_double(2) }.to raise_error(ContractError)
|
17
|
+
end
|
18
|
+
end
|
19
|
+
|
20
|
+
describe "pattern matching" do
|
21
|
+
let(:string_with_hello) { "Hello, world" }
|
22
|
+
let(:string_without_hello) { "Hi, world" }
|
23
|
+
let(:expected_decorated_string) { "Hello, world!" }
|
24
|
+
subject { PatternMatchingExample.new }
|
25
|
+
|
26
|
+
it "should work as expected when there is no contract violation" do
|
27
|
+
expect(
|
28
|
+
subject.process_request(PatternMatchingExample::Success[string_with_hello])
|
29
|
+
).to eq(PatternMatchingExample::Success[expected_decorated_string])
|
30
|
+
|
31
|
+
expect(
|
32
|
+
subject.process_request(PatternMatchingExample::Failure.new)
|
33
|
+
).to be_a(PatternMatchingExample::Failure)
|
34
|
+
end
|
35
|
+
|
36
|
+
it "should not fall through to next pattern when there is a deep contract violation" do
|
37
|
+
expect(PatternMatchingExample::Failure).not_to receive(:is_a?)
|
38
|
+
expect {
|
39
|
+
subject.process_request(PatternMatchingExample::Success[string_without_hello])
|
40
|
+
}.to raise_error(ContractError)
|
41
|
+
end
|
42
|
+
|
43
|
+
it "should fail when the pattern-matched method's contract fails" do
|
44
|
+
expect {
|
45
|
+
subject.process_request("bad input")
|
46
|
+
}.to raise_error(ContractError)
|
47
|
+
end
|
48
|
+
|
49
|
+
context "when failure_callback was overriden" do
|
50
|
+
before do
|
51
|
+
::Contract.override_failure_callback do |_data|
|
52
|
+
raise RuntimeError, "contract violation"
|
53
|
+
end
|
54
|
+
end
|
55
|
+
|
56
|
+
it "calls a method when first pattern matches" do
|
57
|
+
expect(
|
58
|
+
subject.process_request(PatternMatchingExample::Success[string_with_hello])
|
59
|
+
).to eq(PatternMatchingExample::Success[expected_decorated_string])
|
60
|
+
end
|
61
|
+
|
62
|
+
it "falls through to 2nd pattern when first pattern does not match" do
|
63
|
+
expect(
|
64
|
+
subject.process_request(PatternMatchingExample::Failure.new)
|
65
|
+
).to be_a(PatternMatchingExample::Failure)
|
66
|
+
end
|
67
|
+
|
68
|
+
it "uses overriden failure_callback when pattern matching fails" do
|
69
|
+
expect {
|
70
|
+
subject.process_request("hello")
|
71
|
+
}.to raise_error(RuntimeError, /contract violation/)
|
72
|
+
end
|
73
|
+
end
|
74
|
+
end
|
75
|
+
|
76
|
+
describe "instance methods" do
|
77
|
+
it "should allow two classes to have the same method with different contracts" do
|
78
|
+
a = A.new
|
79
|
+
b = B.new
|
80
|
+
expect {
|
81
|
+
a.triple(5)
|
82
|
+
b.triple("a string")
|
83
|
+
}.to_not raise_error
|
84
|
+
end
|
85
|
+
end
|
86
|
+
|
87
|
+
describe "instance and class methods" do
|
88
|
+
it "should allow a class to have an instance method and a class method with the same name" do
|
89
|
+
a = A.new
|
90
|
+
expect {
|
91
|
+
a.instance_and_class_method(5)
|
92
|
+
A.instance_and_class_method("a string")
|
93
|
+
}.to_not raise_error
|
94
|
+
end
|
95
|
+
end
|
96
|
+
|
97
|
+
describe "class methods" do
|
98
|
+
it "should pass for correct input" do
|
99
|
+
expect { Object.a_class_method(2) }.to_not raise_error
|
100
|
+
end
|
101
|
+
|
102
|
+
it "should fail for incorrect input" do
|
103
|
+
expect { Object.a_class_method("bad") }.to raise_error(ContractError)
|
104
|
+
end
|
105
|
+
end
|
106
|
+
|
107
|
+
it "should work for functions with no args" do
|
108
|
+
expect { @o.no_args }.to_not raise_error
|
109
|
+
end
|
110
|
+
|
111
|
+
describe "classes" do
|
112
|
+
it "should pass for correct input" do
|
113
|
+
expect { @o.hello("calvin") }.to_not raise_error
|
114
|
+
end
|
115
|
+
|
116
|
+
it "should fail for incorrect input" do
|
117
|
+
expect { @o.hello(1) }.to raise_error(ContractError)
|
118
|
+
end
|
119
|
+
end
|
120
|
+
|
121
|
+
describe "classes with a valid? class method" do
|
122
|
+
it "should pass for correct input" do
|
123
|
+
expect { @o.double(2) }.to_not raise_error
|
124
|
+
end
|
125
|
+
|
126
|
+
it "should fail for incorrect input" do
|
127
|
+
expect { @o.double("bad") }.to raise_error(ContractError)
|
128
|
+
end
|
129
|
+
end
|
130
|
+
|
131
|
+
describe "Procs" do
|
132
|
+
it "should pass for correct input" do
|
133
|
+
expect { @o.square(2) }.to_not raise_error
|
134
|
+
end
|
135
|
+
|
136
|
+
it "should fail for incorrect input" do
|
137
|
+
expect { @o.square("bad") }.to raise_error(ContractError)
|
138
|
+
end
|
139
|
+
end
|
140
|
+
|
141
|
+
describe "Arrays" do
|
142
|
+
it "should pass for correct input" do
|
143
|
+
expect { @o.sum_three([1, 2, 3]) }.to_not raise_error
|
144
|
+
end
|
145
|
+
|
146
|
+
it "should fail for insufficient items" do
|
147
|
+
expect { @o.square([1, 2]) }.to raise_error(ContractError)
|
148
|
+
end
|
149
|
+
|
150
|
+
it "should fail for some incorrect elements" do
|
151
|
+
expect { @o.sum_three([1, 2, "three"]) }.to raise_error(ContractError)
|
152
|
+
end
|
153
|
+
end
|
154
|
+
|
155
|
+
describe "Hashes" do
|
156
|
+
it "should pass for exact correct input" do
|
157
|
+
expect { @o.person({:name => "calvin", :age => 10}) }.to_not raise_error
|
158
|
+
end
|
159
|
+
|
160
|
+
it "should pass even if some keys don't have contracts" do
|
161
|
+
expect { @o.person({:name => "calvin", :age => 10, :foo => "bar"}) }.to_not raise_error
|
162
|
+
end
|
163
|
+
|
164
|
+
it "should fail if a key with a contract on it isn't provided" do
|
165
|
+
expect { @o.person({:name => "calvin"}) }.to raise_error(ContractError)
|
166
|
+
end
|
167
|
+
|
168
|
+
it "should fail for incorrect input" do
|
169
|
+
expect { @o.person({:name => 50, :age => 10}) }.to raise_error(ContractError)
|
170
|
+
end
|
171
|
+
end
|
172
|
+
|
173
|
+
describe "blocks" do
|
174
|
+
it "should pass for correct input" do
|
175
|
+
expect { @o.do_call {
|
176
|
+
2 + 2
|
177
|
+
}}.to_not raise_error
|
178
|
+
end
|
179
|
+
|
180
|
+
it "should fail for incorrect input" do
|
181
|
+
expect { @o.do_call(nil) }.to raise_error(ContractError)
|
182
|
+
end
|
183
|
+
end
|
184
|
+
|
185
|
+
describe "varargs" do
|
186
|
+
it "should pass for correct input" do
|
187
|
+
expect { @o.sum(1, 2, 3) }.to_not raise_error
|
188
|
+
end
|
189
|
+
|
190
|
+
it "should fail for incorrect input" do
|
191
|
+
expect { @o.sum(1, 2, "bad") }.to raise_error(ContractError)
|
192
|
+
end
|
193
|
+
end
|
194
|
+
|
195
|
+
describe "contracts on functions" do
|
196
|
+
it "should pass for a function that passes the contract" do
|
197
|
+
expect { @o.map([1, 2, 3], lambda { |x| x + 1 }) }.to_not raise_error
|
198
|
+
end
|
199
|
+
|
200
|
+
it "should fail for a function that doesn't pass the contract" do
|
201
|
+
expect { @o.map([1, 2, 3], lambda { |x| "bad return value" }) }.to raise_error(ContractError)
|
202
|
+
end
|
203
|
+
end
|
204
|
+
|
205
|
+
describe "default args to functions" do
|
206
|
+
it "should work for a function call that relies on default args" do
|
207
|
+
expect { @o.default_args }.to_not raise_error
|
208
|
+
expect { @o.default_args("foo") }.to raise_error(ContractError)
|
209
|
+
end
|
210
|
+
end
|
211
|
+
|
212
|
+
describe "classes" do
|
213
|
+
it "should not fail for an object that is the exact type as the contract" do
|
214
|
+
p = Parent.new
|
215
|
+
expect { @o.id_(p) }.to_not raise_error
|
216
|
+
end
|
217
|
+
|
218
|
+
it "should not fail for an object that is a subclass of the type in the contract" do
|
219
|
+
c = Child.new
|
220
|
+
expect { @o.id_(c) }.to_not raise_error
|
221
|
+
end
|
222
|
+
end
|
223
|
+
|
224
|
+
describe "failure callbacks" do
|
225
|
+
before :each do
|
226
|
+
::Contract.override_failure_callback do |_data|
|
227
|
+
should_call
|
228
|
+
end
|
229
|
+
end
|
230
|
+
|
231
|
+
context "when failure_callback returns false" do
|
232
|
+
let(:should_call) { false }
|
233
|
+
|
234
|
+
it "does not call a function for which the contract fails" do
|
235
|
+
res = @o.double("bad")
|
236
|
+
expect(res).to eq(nil)
|
237
|
+
end
|
238
|
+
end
|
239
|
+
|
240
|
+
context "when failure_callback returns true" do
|
241
|
+
let(:should_call) { true }
|
242
|
+
|
243
|
+
it "calls a function for which the contract fails" do
|
244
|
+
res = @o.double("bad")
|
245
|
+
expect(res).to eq("badbad")
|
246
|
+
end
|
247
|
+
end
|
248
|
+
end
|
249
|
+
|
250
|
+
describe "functype" do
|
251
|
+
it "should correctly print out a instance method's type" do
|
252
|
+
expect(@o.functype(:double)).not_to eq("")
|
253
|
+
end
|
254
|
+
|
255
|
+
it "should correctly print out a class method's type" do
|
256
|
+
expect(A.functype(:a_class_method)).not_to eq("")
|
257
|
+
end
|
258
|
+
end
|
259
|
+
|
260
|
+
describe "private methods" do
|
261
|
+
it "should raise an error if you try to access a private method" do
|
262
|
+
expect { @o.a_private_method }.to raise_error
|
263
|
+
end
|
264
|
+
end
|
265
|
+
|
266
|
+
describe "inherited methods" do
|
267
|
+
it "should apply the contract to an inherited method" do
|
268
|
+
c = Child.new
|
269
|
+
expect { c.double(2) }.to_not raise_error
|
270
|
+
expect { c.double("asd") }.to raise_error
|
271
|
+
end
|
272
|
+
end
|
273
|
+
end
|