contracts-lite 0.14.0
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/CHANGELOG.markdown +80 -0
- data/Gemfile +16 -0
- data/LICENSE +23 -0
- data/README.md +102 -0
- data/TODO.markdown +6 -0
- data/TUTORIAL.md +747 -0
- data/benchmarks/bench.rb +67 -0
- data/benchmarks/hash.rb +69 -0
- data/benchmarks/invariants.rb +91 -0
- data/benchmarks/io.rb +62 -0
- data/benchmarks/wrap_test.rb +57 -0
- data/contracts.gemspec +13 -0
- data/lib/contracts.rb +231 -0
- data/lib/contracts/builtin_contracts.rb +541 -0
- data/lib/contracts/call_with.rb +97 -0
- data/lib/contracts/core.rb +52 -0
- data/lib/contracts/decorators.rb +47 -0
- data/lib/contracts/engine.rb +26 -0
- data/lib/contracts/engine/base.rb +136 -0
- data/lib/contracts/engine/eigenclass.rb +50 -0
- data/lib/contracts/engine/target.rb +70 -0
- data/lib/contracts/error_formatter.rb +121 -0
- data/lib/contracts/errors.rb +71 -0
- data/lib/contracts/formatters.rb +134 -0
- data/lib/contracts/invariants.rb +68 -0
- data/lib/contracts/method_handler.rb +195 -0
- data/lib/contracts/method_reference.rb +100 -0
- data/lib/contracts/support.rb +59 -0
- data/lib/contracts/validators.rb +139 -0
- data/lib/contracts/version.rb +3 -0
- data/script/rubocop +7 -0
- data/spec/builtin_contracts_spec.rb +461 -0
- data/spec/contracts_spec.rb +748 -0
- data/spec/error_formatter_spec.rb +68 -0
- data/spec/fixtures/fixtures.rb +710 -0
- data/spec/invariants_spec.rb +17 -0
- data/spec/module_spec.rb +18 -0
- data/spec/override_validators_spec.rb +162 -0
- data/spec/ruby_version_specific/contracts_spec_1.9.rb +24 -0
- data/spec/ruby_version_specific/contracts_spec_2.0.rb +55 -0
- data/spec/ruby_version_specific/contracts_spec_2.1.rb +63 -0
- data/spec/spec_helper.rb +102 -0
- data/spec/support.rb +10 -0
- data/spec/support_spec.rb +21 -0
- data/spec/validators_spec.rb +47 -0
- metadata +94 -0
@@ -0,0 +1,748 @@
|
|
1
|
+
RSpec.describe "Contracts:" do
|
2
|
+
before :all do
|
3
|
+
@o = GenericExample.new
|
4
|
+
end
|
5
|
+
|
6
|
+
describe "basic" do
|
7
|
+
it "should fail for insufficient arguments" do
|
8
|
+
expect do
|
9
|
+
@o.hello
|
10
|
+
end.to raise_error
|
11
|
+
end
|
12
|
+
|
13
|
+
it "should fail for insufficient contracts" do
|
14
|
+
expect { @o.bad_double(2) }.to raise_error(ContractError)
|
15
|
+
end
|
16
|
+
end
|
17
|
+
|
18
|
+
describe "contracts for functions with no arguments" do
|
19
|
+
it "should work for functions with no args" do
|
20
|
+
expect { @o.no_args }.to_not raise_error
|
21
|
+
end
|
22
|
+
|
23
|
+
it "should still work for old-style contracts for functions with no args" do
|
24
|
+
expect { @o.old_style_no_args }.to_not raise_error
|
25
|
+
end
|
26
|
+
|
27
|
+
it "should not work for a function with a bad contract" do
|
28
|
+
expect do
|
29
|
+
Class.new(GenericExample) do
|
30
|
+
Contract Num, Num
|
31
|
+
def no_args_bad_contract
|
32
|
+
1
|
33
|
+
end
|
34
|
+
end
|
35
|
+
end.to raise_error
|
36
|
+
end
|
37
|
+
end
|
38
|
+
|
39
|
+
describe "pattern matching" do
|
40
|
+
let(:string_with_hello) { "Hello, world" }
|
41
|
+
let(:string_without_hello) { "Hi, world" }
|
42
|
+
let(:expected_decorated_string) { "Hello, world!" }
|
43
|
+
subject { PatternMatchingExample.new }
|
44
|
+
|
45
|
+
it "should work as expected when there is no contract violation" do
|
46
|
+
expect(
|
47
|
+
subject.process_request(PatternMatchingExample::Success.new(string_with_hello))
|
48
|
+
).to eq(PatternMatchingExample::Success.new(expected_decorated_string))
|
49
|
+
|
50
|
+
expect(
|
51
|
+
subject.process_request(PatternMatchingExample::Failure.new)
|
52
|
+
).to be_a(PatternMatchingExample::Failure)
|
53
|
+
end
|
54
|
+
|
55
|
+
it "should not fall through to next pattern when there is a deep contract violation" do
|
56
|
+
expect(PatternMatchingExample::Failure).not_to receive(:is_a?)
|
57
|
+
expect do
|
58
|
+
subject.process_request(PatternMatchingExample::Success.new(string_without_hello))
|
59
|
+
end.to raise_error(ContractError)
|
60
|
+
end
|
61
|
+
|
62
|
+
it "should fail when the pattern-matched method's contract fails" do
|
63
|
+
expect do
|
64
|
+
subject.process_request("bad input")
|
65
|
+
end.to raise_error(ContractError)
|
66
|
+
end
|
67
|
+
|
68
|
+
it "should work for differing arities" do
|
69
|
+
expect(
|
70
|
+
subject.do_stuff(1, "abc", 2)
|
71
|
+
).to eq("bar")
|
72
|
+
|
73
|
+
expect(
|
74
|
+
subject.do_stuff(3, "def")
|
75
|
+
).to eq("foo")
|
76
|
+
end
|
77
|
+
|
78
|
+
it "if the return contract for a pattern match fails, it should fail instead of trying the next pattern match" do
|
79
|
+
expect do
|
80
|
+
subject.double(1)
|
81
|
+
end.to raise_error(ContractError)
|
82
|
+
end
|
83
|
+
|
84
|
+
it "should fail if multiple methods are defined with the same contract (for pattern-matching)" do
|
85
|
+
expect do
|
86
|
+
Class.new(GenericExample) do
|
87
|
+
Contract Contracts::Num => Contracts::Num
|
88
|
+
def same_param_contract x
|
89
|
+
x + 2
|
90
|
+
end
|
91
|
+
|
92
|
+
Contract Contracts::Num => String
|
93
|
+
def same_param_contract x
|
94
|
+
"sdf"
|
95
|
+
end
|
96
|
+
end
|
97
|
+
end.to raise_error(ContractError)
|
98
|
+
end
|
99
|
+
|
100
|
+
context "when failure_callback was overriden" do
|
101
|
+
before do
|
102
|
+
::Contract.override_failure_callback do |_data|
|
103
|
+
fail "contract violation"
|
104
|
+
end
|
105
|
+
end
|
106
|
+
|
107
|
+
it "calls a method when first pattern matches" do
|
108
|
+
expect(
|
109
|
+
subject.process_request(PatternMatchingExample::Success.new(string_with_hello))
|
110
|
+
).to eq(PatternMatchingExample::Success.new(expected_decorated_string))
|
111
|
+
end
|
112
|
+
|
113
|
+
it "falls through to 2nd pattern when first pattern does not match" do
|
114
|
+
expect(
|
115
|
+
subject.process_request(PatternMatchingExample::Failure.new)
|
116
|
+
).to be_a(PatternMatchingExample::Failure)
|
117
|
+
end
|
118
|
+
|
119
|
+
it "if the return contract for a pattern match fails, it should fail instead of trying the next pattern match, even with the failure callback" do
|
120
|
+
expect do
|
121
|
+
subject.double(1)
|
122
|
+
end.to raise_error(ContractError)
|
123
|
+
end
|
124
|
+
|
125
|
+
it "uses overriden failure_callback when pattern matching fails" do
|
126
|
+
expect do
|
127
|
+
subject.process_request("hello")
|
128
|
+
end.to raise_error(RuntimeError, /contract violation/)
|
129
|
+
end
|
130
|
+
end
|
131
|
+
end
|
132
|
+
|
133
|
+
describe "usage in singleton class" do
|
134
|
+
it "should work normally when there is no contract violation" do
|
135
|
+
expect(SingletonClassExample.hoge("hoge")).to eq("superhoge")
|
136
|
+
end
|
137
|
+
|
138
|
+
it "should fail with proper error when there is contract violation" do
|
139
|
+
expect do
|
140
|
+
SingletonClassExample.hoge(3)
|
141
|
+
end.to raise_error(ContractError, /Expected: String/)
|
142
|
+
end
|
143
|
+
|
144
|
+
describe "builtin contracts usage" do
|
145
|
+
it "allows to use builtin contracts without namespacing and redundant Contracts inclusion" do
|
146
|
+
expect do
|
147
|
+
SingletonClassExample.add("55", 5.6)
|
148
|
+
end.to raise_error(ContractError, /Expected: Num/)
|
149
|
+
end
|
150
|
+
end
|
151
|
+
end
|
152
|
+
|
153
|
+
describe "usage in the singleton class of a subclass" do
|
154
|
+
subject { SingletonInheritanceExampleSubclass }
|
155
|
+
|
156
|
+
it "should work with a valid contract on a singleton method" do
|
157
|
+
expect(subject.num(1)).to eq(1)
|
158
|
+
end
|
159
|
+
end
|
160
|
+
|
161
|
+
describe "no contracts feature" do
|
162
|
+
it "disables normal contract checks" do
|
163
|
+
object = NoContractsSimpleExample.new
|
164
|
+
expect { object.some_method(3) }.not_to raise_error
|
165
|
+
end
|
166
|
+
|
167
|
+
it "disables invariants" do
|
168
|
+
object = NoContractsInvariantsExample.new
|
169
|
+
object.day = 7
|
170
|
+
expect { object.next_day }.not_to raise_error
|
171
|
+
end
|
172
|
+
|
173
|
+
it "does not disable pattern matching" do
|
174
|
+
object = NoContractsPatternMatchingExample.new
|
175
|
+
|
176
|
+
expect(object.on_response(200, "hello")).to eq("hello!")
|
177
|
+
expect(object.on_response(404, "Not found")).to eq("error 404: Not found")
|
178
|
+
expect { object.on_response(nil, "junk response") }.to raise_error(ContractError)
|
179
|
+
end
|
180
|
+
end
|
181
|
+
|
182
|
+
describe "module usage" do
|
183
|
+
context "with instance methods" do
|
184
|
+
it "should check contract" do
|
185
|
+
expect { KlassWithModuleExample.new.plus(3, nil) }.to raise_error(ContractError)
|
186
|
+
end
|
187
|
+
end
|
188
|
+
|
189
|
+
context "with singleton methods" do
|
190
|
+
it "should check contract" do
|
191
|
+
expect { ModuleExample.hoge(nil) }.to raise_error(ContractError)
|
192
|
+
end
|
193
|
+
end
|
194
|
+
|
195
|
+
context "with singleton class methods" do
|
196
|
+
it "should check contract" do
|
197
|
+
expect { ModuleExample.eat(:food) }.to raise_error(ContractError)
|
198
|
+
end
|
199
|
+
end
|
200
|
+
end
|
201
|
+
|
202
|
+
describe "singleton methods self in inherited methods" do
|
203
|
+
it "should be a proper self" do
|
204
|
+
expect(SingletonInheritanceExampleSubclass.a_contracted_self).to eq(SingletonInheritanceExampleSubclass)
|
205
|
+
end
|
206
|
+
end
|
207
|
+
|
208
|
+
describe "anonymous classes" do
|
209
|
+
let(:klass) do
|
210
|
+
Class.new do
|
211
|
+
include Contracts::Core
|
212
|
+
|
213
|
+
Contract String => String
|
214
|
+
def greeting(name)
|
215
|
+
"hello, #{name}"
|
216
|
+
end
|
217
|
+
end
|
218
|
+
end
|
219
|
+
|
220
|
+
let(:obj) { klass.new }
|
221
|
+
|
222
|
+
it "does not fail when contract is satisfied" do
|
223
|
+
expect(obj.greeting("world")).to eq("hello, world")
|
224
|
+
end
|
225
|
+
|
226
|
+
it "fails with error when contract is violated" do
|
227
|
+
expect { obj.greeting(3) }.to raise_error(ContractError, /Actual: 3/)
|
228
|
+
end
|
229
|
+
end
|
230
|
+
|
231
|
+
describe "anonymous modules" do
|
232
|
+
let(:mod) do
|
233
|
+
Module.new do
|
234
|
+
include Contracts::Core
|
235
|
+
|
236
|
+
Contract String => String
|
237
|
+
def greeting(name)
|
238
|
+
"hello, #{name}"
|
239
|
+
end
|
240
|
+
|
241
|
+
Contract String => String
|
242
|
+
def self.greeting(name)
|
243
|
+
"hello, #{name}"
|
244
|
+
end
|
245
|
+
end
|
246
|
+
end
|
247
|
+
|
248
|
+
let(:klass) do
|
249
|
+
Class.new.tap { |klass| klass.send(:include, mod) }
|
250
|
+
end
|
251
|
+
|
252
|
+
let(:obj) { klass.new }
|
253
|
+
|
254
|
+
it "does not fail when contract is satisfied" do
|
255
|
+
expect(obj.greeting("world")).to eq("hello, world")
|
256
|
+
end
|
257
|
+
|
258
|
+
it "fails with error when contract is violated" do
|
259
|
+
expect { obj.greeting(3) }.to raise_error(ContractError, /Actual: 3/)
|
260
|
+
end
|
261
|
+
|
262
|
+
context "when called on module itself" do
|
263
|
+
let(:obj) { mod }
|
264
|
+
|
265
|
+
it "does not fail when contract is satisfied" do
|
266
|
+
expect(obj.greeting("world")).to eq("hello, world")
|
267
|
+
end
|
268
|
+
|
269
|
+
it "fails with error when contract is violated" do
|
270
|
+
expect { obj.greeting(3) }.to raise_error(ContractError, /Actual: 3/)
|
271
|
+
end
|
272
|
+
end
|
273
|
+
end
|
274
|
+
|
275
|
+
describe "instance methods" do
|
276
|
+
it "should allow two classes to have the same method with different contracts" do
|
277
|
+
a = A.new
|
278
|
+
b = B.new
|
279
|
+
expect do
|
280
|
+
a.triple(5)
|
281
|
+
b.triple("a string")
|
282
|
+
end.to_not raise_error
|
283
|
+
end
|
284
|
+
end
|
285
|
+
|
286
|
+
describe "instance and class methods" do
|
287
|
+
it "should allow a class to have an instance method and a class method with the same name" do
|
288
|
+
a = A.new
|
289
|
+
expect do
|
290
|
+
a.instance_and_class_method(5)
|
291
|
+
A.instance_and_class_method("a string")
|
292
|
+
end.to_not raise_error
|
293
|
+
end
|
294
|
+
end
|
295
|
+
|
296
|
+
describe "class methods" do
|
297
|
+
it "should pass for correct input" do
|
298
|
+
expect { GenericExample.a_class_method(2) }.to_not raise_error
|
299
|
+
end
|
300
|
+
|
301
|
+
it "should fail for incorrect input" do
|
302
|
+
expect { GenericExample.a_class_method("bad") }.to raise_error(ContractError)
|
303
|
+
end
|
304
|
+
end
|
305
|
+
|
306
|
+
describe "classes" do
|
307
|
+
it "should pass for correct input" do
|
308
|
+
expect { @o.hello("calvin") }.to_not raise_error
|
309
|
+
end
|
310
|
+
|
311
|
+
it "should fail for incorrect input" do
|
312
|
+
expect { @o.hello(1) }.to raise_error(ContractError)
|
313
|
+
end
|
314
|
+
end
|
315
|
+
|
316
|
+
describe "classes with a valid? class method" do
|
317
|
+
it "should pass for correct input" do
|
318
|
+
expect { @o.double(2) }.to_not raise_error
|
319
|
+
end
|
320
|
+
|
321
|
+
it "should fail for incorrect input" do
|
322
|
+
expect { @o.double("bad") }.to raise_error(ContractError)
|
323
|
+
end
|
324
|
+
end
|
325
|
+
|
326
|
+
describe "Procs" do
|
327
|
+
it "should pass for correct input" do
|
328
|
+
expect { @o.square(2) }.to_not raise_error
|
329
|
+
end
|
330
|
+
|
331
|
+
it "should fail for incorrect input" do
|
332
|
+
expect { @o.square("bad") }.to raise_error(ContractError)
|
333
|
+
end
|
334
|
+
end
|
335
|
+
|
336
|
+
describe "Arrays" do
|
337
|
+
it "should pass for correct input" do
|
338
|
+
expect { @o.sum_three([1, 2, 3]) }.to_not raise_error
|
339
|
+
end
|
340
|
+
|
341
|
+
it "should fail for insufficient items" do
|
342
|
+
expect { @o.square([1, 2]) }.to raise_error(ContractError)
|
343
|
+
end
|
344
|
+
|
345
|
+
it "should fail for some incorrect elements" do
|
346
|
+
expect { @o.sum_three([1, 2, "three"]) }.to raise_error(ContractError)
|
347
|
+
end
|
348
|
+
end
|
349
|
+
|
350
|
+
describe "Hashes" do
|
351
|
+
it "should pass for exact correct input" do
|
352
|
+
expect { @o.person(:name => "calvin", :age => 10) }.to_not raise_error
|
353
|
+
end
|
354
|
+
|
355
|
+
it "should pass even if some keys don't have contracts" do
|
356
|
+
expect { @o.person(:name => "calvin", :age => 10, :foo => "bar") }.to_not raise_error
|
357
|
+
end
|
358
|
+
|
359
|
+
it "should fail if a key with a contract on it isn't provided" do
|
360
|
+
expect { @o.person(:name => "calvin") }.to raise_error(ContractError)
|
361
|
+
end
|
362
|
+
|
363
|
+
it "should fail for incorrect input" do
|
364
|
+
expect { @o.person(:name => 50, :age => 10) }.to raise_error(ContractError)
|
365
|
+
end
|
366
|
+
end
|
367
|
+
|
368
|
+
describe "blocks" do
|
369
|
+
it "should pass for correct input" do
|
370
|
+
expect do
|
371
|
+
@o.do_call do
|
372
|
+
2 + 2
|
373
|
+
end
|
374
|
+
end.to_not raise_error
|
375
|
+
end
|
376
|
+
|
377
|
+
it "should fail for incorrect input" do
|
378
|
+
expect do
|
379
|
+
@o.do_call(nil)
|
380
|
+
end.to raise_error(ContractError)
|
381
|
+
end
|
382
|
+
|
383
|
+
it "should handle properly lack of block when there are other arguments" do
|
384
|
+
expect do
|
385
|
+
@o.double_with_proc(4)
|
386
|
+
end.to raise_error(ContractError, /Actual: nil/)
|
387
|
+
end
|
388
|
+
|
389
|
+
it "should succeed for maybe proc with no proc" do
|
390
|
+
expect do
|
391
|
+
@o.maybe_call(5)
|
392
|
+
end.to_not raise_error
|
393
|
+
end
|
394
|
+
|
395
|
+
it "should succeed for maybe proc with proc" do
|
396
|
+
expect do
|
397
|
+
@o.maybe_call(5) do
|
398
|
+
2 + 2
|
399
|
+
end
|
400
|
+
end.to_not raise_error
|
401
|
+
end
|
402
|
+
|
403
|
+
it "should fail for maybe proc with invalid input" do
|
404
|
+
expect do
|
405
|
+
@o.maybe_call("bad")
|
406
|
+
end.to raise_error(ContractError)
|
407
|
+
end
|
408
|
+
|
409
|
+
describe "varargs are given with a maybe block" do
|
410
|
+
it "when a block is passed in, varargs should be correct" do
|
411
|
+
expect(@o.maybe_call(1, 2, 3) { 1 + 1 }).to eq([1, 2, 3])
|
412
|
+
end
|
413
|
+
|
414
|
+
it "when a block is NOT passed in, varargs should still be correct" do
|
415
|
+
expect(@o.maybe_call(1, 2, 3)).to eq([1, 2, 3])
|
416
|
+
end
|
417
|
+
end
|
418
|
+
end
|
419
|
+
|
420
|
+
describe "varargs" do
|
421
|
+
it "should pass for correct input" do
|
422
|
+
expect do
|
423
|
+
@o.sum(1, 2, 3)
|
424
|
+
end.to_not raise_error
|
425
|
+
end
|
426
|
+
|
427
|
+
it "should fail for incorrect input" do
|
428
|
+
expect do
|
429
|
+
@o.sum(1, 2, "bad")
|
430
|
+
end.to raise_error(ContractError)
|
431
|
+
end
|
432
|
+
|
433
|
+
it "should work with arg before splat" do
|
434
|
+
expect do
|
435
|
+
@o.arg_then_splat(3, "hello", "world")
|
436
|
+
end.to_not raise_error
|
437
|
+
end
|
438
|
+
end
|
439
|
+
|
440
|
+
describe "varargs with block" do
|
441
|
+
it "should pass for correct input" do
|
442
|
+
expect do
|
443
|
+
@o.with_partial_sums(1, 2, 3) do |partial_sum|
|
444
|
+
2 * partial_sum + 1
|
445
|
+
end
|
446
|
+
end.not_to raise_error
|
447
|
+
expect do
|
448
|
+
@o.with_partial_sums_contracted(1, 2, 3) do |partial_sum|
|
449
|
+
2 * partial_sum + 1
|
450
|
+
end
|
451
|
+
end.not_to raise_error
|
452
|
+
end
|
453
|
+
|
454
|
+
it "should fail for incorrect input" do
|
455
|
+
expect do
|
456
|
+
@o.with_partial_sums(1, 2, "bad") do |partial_sum|
|
457
|
+
2 * partial_sum + 1
|
458
|
+
end
|
459
|
+
end.to raise_error(ContractError, /Actual: "bad"/)
|
460
|
+
|
461
|
+
expect do
|
462
|
+
@o.with_partial_sums(1, 2, 3)
|
463
|
+
end.to raise_error(ContractError, /Actual: nil/)
|
464
|
+
|
465
|
+
expect do
|
466
|
+
@o.with_partial_sums(1, 2, 3, lambda { |x| x })
|
467
|
+
end.to raise_error(ContractError, /Actual: nil/)
|
468
|
+
end
|
469
|
+
|
470
|
+
context "when block has Func contract" do
|
471
|
+
it "should fail for incorrect input" do
|
472
|
+
expect do
|
473
|
+
@o.with_partial_sums_contracted(1, 2, "bad") { |partial_sum| 2 * partial_sum + 1 }
|
474
|
+
end.to raise_error(ContractError, /Actual: "bad"/)
|
475
|
+
|
476
|
+
expect do
|
477
|
+
@o.with_partial_sums_contracted(1, 2, 3)
|
478
|
+
end.to raise_error(ContractError, /Actual: nil/)
|
479
|
+
end
|
480
|
+
end
|
481
|
+
end
|
482
|
+
|
483
|
+
describe "contracts on functions" do
|
484
|
+
it "should pass for a function that passes the contract" do
|
485
|
+
expect { @o.map([1, 2, 3], lambda { |x| x + 1 }) }.to_not raise_error
|
486
|
+
end
|
487
|
+
|
488
|
+
it "should pass for a function that passes the contract as in tutorial" do
|
489
|
+
expect { @o.tutorial_map([1, 2, 3], lambda { |x| x + 1 }) }.to_not raise_error
|
490
|
+
end
|
491
|
+
|
492
|
+
it "should fail for a function that doesn't pass the contract" do
|
493
|
+
expect { @o.map([1, 2, 3], lambda { |_| "bad return value" }) }.to raise_error(ContractError)
|
494
|
+
end
|
495
|
+
|
496
|
+
it "should pass for a function that passes the contract with weak other args" do
|
497
|
+
expect { @o.map_plain(["hello", "joe"], lambda { |x| x.size }) }.to_not raise_error
|
498
|
+
end
|
499
|
+
|
500
|
+
it "should fail for a function that doesn't pass the contract with weak other args" do
|
501
|
+
expect { @o.map_plain(["hello", "joe"], lambda { |_| nil }) }.to raise_error(ContractError)
|
502
|
+
end
|
503
|
+
|
504
|
+
it "should fail for a returned function that doesn't pass the contract" do
|
505
|
+
expect { @o.lambda_with_wrong_return.call("hello") }.to raise_error(ContractError)
|
506
|
+
end
|
507
|
+
|
508
|
+
it "should fail for a returned function that receives the wrong argument type" do
|
509
|
+
expect { @o.lambda_with_correct_return.call(123) }.to raise_error(ContractError)
|
510
|
+
end
|
511
|
+
|
512
|
+
it "should not fail for a returned function that passes the contract" do
|
513
|
+
expect { @o.lambda_with_correct_return.call("hello") }.to_not raise_error
|
514
|
+
end
|
515
|
+
end
|
516
|
+
|
517
|
+
describe "default args to functions" do
|
518
|
+
it "should work for a function call that relies on default args" do
|
519
|
+
expect { @o.default_args }.to_not raise_error
|
520
|
+
expect { @o.default_args("foo") }.to raise_error(ContractError)
|
521
|
+
end
|
522
|
+
end
|
523
|
+
|
524
|
+
describe "classes" do
|
525
|
+
it "should not fail for an object that is the exact type as the contract" do
|
526
|
+
p = Parent.new
|
527
|
+
expect { @o.id_(p) }.to_not raise_error
|
528
|
+
end
|
529
|
+
|
530
|
+
it "should not fail for an object that is a subclass of the type in the contract" do
|
531
|
+
c = Child.new
|
532
|
+
expect { @o.id_(c) }.to_not raise_error
|
533
|
+
end
|
534
|
+
end
|
535
|
+
|
536
|
+
describe "failure callbacks" do
|
537
|
+
before :each do
|
538
|
+
::Contract.override_failure_callback do |_data|
|
539
|
+
should_call
|
540
|
+
end
|
541
|
+
end
|
542
|
+
|
543
|
+
context "when failure_callback returns false" do
|
544
|
+
let(:should_call) { false }
|
545
|
+
|
546
|
+
it "does not call a function for which the contract fails" do
|
547
|
+
res = @o.double("bad")
|
548
|
+
expect(res).to eq(nil)
|
549
|
+
end
|
550
|
+
end
|
551
|
+
|
552
|
+
context "when failure_callback returns true" do
|
553
|
+
let(:should_call) { true }
|
554
|
+
|
555
|
+
it "calls a function for which the contract fails" do
|
556
|
+
res = @o.double("bad")
|
557
|
+
expect(res).to eq("badbad")
|
558
|
+
end
|
559
|
+
end
|
560
|
+
end
|
561
|
+
|
562
|
+
describe "module contracts" do
|
563
|
+
it "passes for instance of class including module" do
|
564
|
+
expect(
|
565
|
+
ModuleContractExample.hello(ModuleContractExample::AClassWithModule.new)
|
566
|
+
).to eq(:world)
|
567
|
+
end
|
568
|
+
|
569
|
+
it "passes for instance of class including inherited module" do
|
570
|
+
expect(
|
571
|
+
ModuleContractExample.hello(ModuleContractExample::AClassWithInheritedModule.new)
|
572
|
+
).to eq(:world)
|
573
|
+
end
|
574
|
+
|
575
|
+
it "does not pass for instance of class not including module" do
|
576
|
+
expect do
|
577
|
+
ModuleContractExample.hello(ModuleContractExample::AClassWithoutModule.new)
|
578
|
+
end.to raise_error(ContractError, /Expected: ModuleContractExample::AModule/)
|
579
|
+
end
|
580
|
+
|
581
|
+
it "does not pass for instance of class including another module" do
|
582
|
+
expect do
|
583
|
+
ModuleContractExample.hello(ModuleContractExample::AClassWithAnotherModule.new)
|
584
|
+
end.to raise_error(ContractError, /Expected: ModuleContractExample::AModule/)
|
585
|
+
end
|
586
|
+
|
587
|
+
it "passes for instance of class including both modules" do
|
588
|
+
expect(
|
589
|
+
ModuleContractExample.hello(ModuleContractExample::AClassWithBothModules.new)
|
590
|
+
).to eq(:world)
|
591
|
+
end
|
592
|
+
end
|
593
|
+
|
594
|
+
describe "Contracts to_s formatting in expected" do
|
595
|
+
def not_s(match)
|
596
|
+
Regexp.new "[^\"\']#{match}[^\"\']"
|
597
|
+
end
|
598
|
+
|
599
|
+
def delim(match)
|
600
|
+
"(#{match})"
|
601
|
+
end
|
602
|
+
|
603
|
+
it "should not stringify native types" do
|
604
|
+
expect do
|
605
|
+
@o.constanty("bad", nil)
|
606
|
+
end.to raise_error(ContractError, not_s(123))
|
607
|
+
|
608
|
+
expect do
|
609
|
+
@o.constanty(123, "bad")
|
610
|
+
end.to raise_error(ContractError, not_s(nil))
|
611
|
+
end
|
612
|
+
|
613
|
+
it "should contain to_s representation within a Hash contract" do
|
614
|
+
expect do
|
615
|
+
@o.hash_complex_contracts(:rigged => "bad")
|
616
|
+
end.to raise_error(ContractError, not_s(delim "TrueClass or FalseClass"))
|
617
|
+
end
|
618
|
+
|
619
|
+
it "should contain to_s representation within a nested Hash contract" do
|
620
|
+
expect do
|
621
|
+
@o.nested_hash_complex_contracts(:rigged => true,
|
622
|
+
:contents => {
|
623
|
+
:kind => 0,
|
624
|
+
:total => 42 })
|
625
|
+
end.to raise_error(ContractError, not_s(delim "String or Symbol"))
|
626
|
+
end
|
627
|
+
|
628
|
+
it "should contain to_s representation within an Array contract" do
|
629
|
+
expect do
|
630
|
+
@o.array_complex_contracts(["bad"])
|
631
|
+
end.to raise_error(ContractError, not_s(delim "TrueClass or FalseClass"))
|
632
|
+
end
|
633
|
+
|
634
|
+
it "should contain to_s representation within a nested Array contract" do
|
635
|
+
expect do
|
636
|
+
@o.nested_array_complex_contracts([true, [0]])
|
637
|
+
end.to raise_error(ContractError, not_s(delim "String or Symbol"))
|
638
|
+
end
|
639
|
+
|
640
|
+
it "should not contain Contracts:: module prefix" do
|
641
|
+
expect do
|
642
|
+
@o.double("bad")
|
643
|
+
end.to raise_error(ContractError, /Expected: Num/)
|
644
|
+
end
|
645
|
+
|
646
|
+
it "should still show nils, not just blank space" do
|
647
|
+
expect do
|
648
|
+
@o.no_args("bad")
|
649
|
+
end.to raise_error(ContractError, /Expected: nil/)
|
650
|
+
end
|
651
|
+
|
652
|
+
it 'should show empty quotes as ""' do
|
653
|
+
expect do
|
654
|
+
@o.no_args("")
|
655
|
+
end.to raise_error(ContractError, /Actual: ""/)
|
656
|
+
end
|
657
|
+
|
658
|
+
it "should not use custom to_s if empty string" do
|
659
|
+
expect do
|
660
|
+
@o.using_empty_contract("bad")
|
661
|
+
end.to raise_error(ContractError, /Expected: EmptyCont/)
|
662
|
+
end
|
663
|
+
end
|
664
|
+
|
665
|
+
describe "functype" do
|
666
|
+
it "should correctly print out a instance method's type" do
|
667
|
+
expect(@o.functype(:double)).not_to eq("")
|
668
|
+
end
|
669
|
+
|
670
|
+
it "should correctly print out a class method's type" do
|
671
|
+
expect(A.functype(:a_class_method)).not_to eq("")
|
672
|
+
end
|
673
|
+
end
|
674
|
+
|
675
|
+
describe "private methods" do
|
676
|
+
it "should raise an error if you try to access a private method" do
|
677
|
+
expect { @o.a_private_method }.to raise_error(NoMethodError, /private/)
|
678
|
+
end
|
679
|
+
|
680
|
+
it "should raise an error if you try to access a private method" do
|
681
|
+
expect { @o.a_really_private_method }.to raise_error(NoMethodError, /private/)
|
682
|
+
end
|
683
|
+
end
|
684
|
+
|
685
|
+
describe "protected methods" do
|
686
|
+
it "should raise an error if you try to access a protected method" do
|
687
|
+
expect { @o.a_protected_method }.to raise_error(NoMethodError, /protected/)
|
688
|
+
end
|
689
|
+
|
690
|
+
it "should raise an error if you try to access a protected method" do
|
691
|
+
expect { @o.a_really_protected_method }.to raise_error(NoMethodError, /protected/)
|
692
|
+
end
|
693
|
+
end
|
694
|
+
|
695
|
+
describe "inherited methods" do
|
696
|
+
it "should apply the contract to an inherited method" do
|
697
|
+
c = Child.new
|
698
|
+
expect { c.double(2) }.to_not raise_error
|
699
|
+
expect { c.double("asd") }.to raise_error
|
700
|
+
end
|
701
|
+
end
|
702
|
+
|
703
|
+
describe "classes with extended modules" do
|
704
|
+
let(:klass) do
|
705
|
+
m = Module.new do
|
706
|
+
include Contracts::Core
|
707
|
+
end
|
708
|
+
|
709
|
+
Class.new do
|
710
|
+
include Contracts::Core
|
711
|
+
extend m
|
712
|
+
|
713
|
+
Contract String => nil
|
714
|
+
def foo(x)
|
715
|
+
end
|
716
|
+
end
|
717
|
+
end
|
718
|
+
|
719
|
+
it "is possible to define it" do
|
720
|
+
expect { klass }.not_to raise_error
|
721
|
+
end
|
722
|
+
|
723
|
+
it "works correctly with methods with passing contracts" do
|
724
|
+
expect { klass.new.foo("bar") }.not_to raise_error
|
725
|
+
end
|
726
|
+
|
727
|
+
it "works correctly with methods with passing contracts" do
|
728
|
+
expect { klass.new.foo(42) }.to raise_error(ContractError, /Expected: String/)
|
729
|
+
end
|
730
|
+
|
731
|
+
# See the discussion on this issue:
|
732
|
+
# https://github.com/egonSchiele/contracts.ruby/issues/229
|
733
|
+
it "should not fail with 'undefined method 'Contract''" do
|
734
|
+
expect do
|
735
|
+
class ModuleThenContracts
|
736
|
+
include ModuleWithContracts
|
737
|
+
include Contracts::Core
|
738
|
+
|
739
|
+
# fails on this line
|
740
|
+
Contract C::Num => C::Num
|
741
|
+
def double(x)
|
742
|
+
x * 2
|
743
|
+
end
|
744
|
+
end
|
745
|
+
end.to_not raise_error
|
746
|
+
end
|
747
|
+
end
|
748
|
+
end
|