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.
Files changed (47) hide show
  1. checksums.yaml +7 -0
  2. data/CHANGELOG.markdown +80 -0
  3. data/Gemfile +16 -0
  4. data/LICENSE +23 -0
  5. data/README.md +102 -0
  6. data/TODO.markdown +6 -0
  7. data/TUTORIAL.md +747 -0
  8. data/benchmarks/bench.rb +67 -0
  9. data/benchmarks/hash.rb +69 -0
  10. data/benchmarks/invariants.rb +91 -0
  11. data/benchmarks/io.rb +62 -0
  12. data/benchmarks/wrap_test.rb +57 -0
  13. data/contracts.gemspec +13 -0
  14. data/lib/contracts.rb +231 -0
  15. data/lib/contracts/builtin_contracts.rb +541 -0
  16. data/lib/contracts/call_with.rb +97 -0
  17. data/lib/contracts/core.rb +52 -0
  18. data/lib/contracts/decorators.rb +47 -0
  19. data/lib/contracts/engine.rb +26 -0
  20. data/lib/contracts/engine/base.rb +136 -0
  21. data/lib/contracts/engine/eigenclass.rb +50 -0
  22. data/lib/contracts/engine/target.rb +70 -0
  23. data/lib/contracts/error_formatter.rb +121 -0
  24. data/lib/contracts/errors.rb +71 -0
  25. data/lib/contracts/formatters.rb +134 -0
  26. data/lib/contracts/invariants.rb +68 -0
  27. data/lib/contracts/method_handler.rb +195 -0
  28. data/lib/contracts/method_reference.rb +100 -0
  29. data/lib/contracts/support.rb +59 -0
  30. data/lib/contracts/validators.rb +139 -0
  31. data/lib/contracts/version.rb +3 -0
  32. data/script/rubocop +7 -0
  33. data/spec/builtin_contracts_spec.rb +461 -0
  34. data/spec/contracts_spec.rb +748 -0
  35. data/spec/error_formatter_spec.rb +68 -0
  36. data/spec/fixtures/fixtures.rb +710 -0
  37. data/spec/invariants_spec.rb +17 -0
  38. data/spec/module_spec.rb +18 -0
  39. data/spec/override_validators_spec.rb +162 -0
  40. data/spec/ruby_version_specific/contracts_spec_1.9.rb +24 -0
  41. data/spec/ruby_version_specific/contracts_spec_2.0.rb +55 -0
  42. data/spec/ruby_version_specific/contracts_spec_2.1.rb +63 -0
  43. data/spec/spec_helper.rb +102 -0
  44. data/spec/support.rb +10 -0
  45. data/spec/support_spec.rb +21 -0
  46. data/spec/validators_spec.rb +47 -0
  47. 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