contracts 0.4 → 0.5
Sign up to get free protection for your applications and to get access to all the features.
- 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
|