contracts-lite 0.14.0

Sign up to get free protection for your applications and to get access to all the features.
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,68 @@
1
+ RSpec.describe "Contracts::ErrorFormatters" do
2
+ before :all do
3
+ @o = GenericExample.new
4
+ end
5
+ C = Contracts::Builtin
6
+
7
+ describe "self.class_for" do
8
+ let(:keywordargs_contract) { C::KeywordArgs[:name => String, :age => Fixnum] }
9
+ let(:other_contract) { [C::Num, C::Num, C::Num] }
10
+
11
+ it "returns KeywordArgsErrorFormatter for KeywordArgs contract" do
12
+ data_keywordargs = {:contract => keywordargs_contract, :arg => {:b => 2}}
13
+ expect(Contracts::ErrorFormatters.class_for(data_keywordargs)).to eq(Contracts::KeywordArgsErrorFormatter)
14
+ end
15
+
16
+ it "returns Contracts::DefaultErrorFormatter for other contracts" do
17
+ data_default = {:contract => other_contract, :arg => {:b => 2}}
18
+ expect(Contracts::ErrorFormatters.class_for(data_default)).to eq(Contracts::DefaultErrorFormatter)
19
+ end
20
+ end
21
+
22
+ def format_message(str)
23
+ str.split("\n").map(&:strip).join("\n")
24
+ end
25
+
26
+ def fails(msg, &block)
27
+ expect { block.call }.to raise_error do |e|
28
+ expect(e).to be_a(ParamContractError)
29
+ expect(format_message(e.message)).to include(format_message(msg))
30
+ end
31
+ end
32
+
33
+ if ruby_version > 1.8
34
+ describe "self.failure_msg" do
35
+ it "includes normal information" do
36
+ msg = %{Contract violation for argument 1 of 1:
37
+ Expected: (KeywordArgs[{:name=>String, :age=>Fixnum}])
38
+ Actual: {:age=>"2", :invalid_third=>1}
39
+ Missing Contract: {:invalid_third=>1}
40
+ Invalid Args: [{:age=>"2", :contract=>Fixnum}]
41
+ Missing Args: {:name=>String}
42
+ Value guarded in: GenericExample::simple_keywordargs
43
+ With Contract: KeywordArgs => NilClass}
44
+ fails msg do
45
+ @o.simple_keywordargs(:age => "2", :invalid_third => 1)
46
+ end
47
+ end
48
+
49
+ it "includes Missing Contract information" do
50
+ fails %{Missing Contract: {:invalid_third=>1, :invalid_fourth=>1}} do
51
+ @o.simple_keywordargs(:age => "2", :invalid_third => 1, :invalid_fourth => 1)
52
+ end
53
+ end
54
+
55
+ it "includes Invalid Args information" do
56
+ fails %{Invalid Args: [{:age=>"2", :contract=>Fixnum}]} do
57
+ @o.simple_keywordargs(:age => "2", :invalid_third => 1)
58
+ end
59
+ end
60
+
61
+ it "includes Missing Args information" do
62
+ fails %{Missing Args: {:name=>String}} do
63
+ @o.simple_keywordargs(:age => "2", :invalid_third => 1)
64
+ end
65
+ end
66
+ end
67
+ end
68
+ end
@@ -0,0 +1,710 @@
1
+ require "date"
2
+
3
+ C = Contracts
4
+
5
+ class A
6
+ include Contracts::Core
7
+
8
+ Contract C::Num => C::Num
9
+ def self.a_class_method x
10
+ x + 1
11
+ end
12
+
13
+ def good
14
+ true
15
+ end
16
+
17
+ Contract C::Num => C::Num
18
+ def triple x
19
+ x * 3
20
+ end
21
+
22
+ Contract C::Num => C::Num
23
+ def instance_and_class_method x
24
+ x * 2
25
+ end
26
+
27
+ Contract String => String
28
+ def self.instance_and_class_method x
29
+ x * 2
30
+ end
31
+ end
32
+
33
+ class B
34
+ include Contracts::Core
35
+
36
+ def bad
37
+ false
38
+ end
39
+
40
+ Contract String => String
41
+ def triple x
42
+ x * 3
43
+ end
44
+ end
45
+
46
+ class F
47
+ include Contracts::Core
48
+
49
+ def good
50
+ false
51
+ end
52
+
53
+ def bad
54
+ true
55
+ end
56
+ end
57
+
58
+ class EmptyCont
59
+ def self.to_s
60
+ ""
61
+ end
62
+ end
63
+
64
+ class GenericExample
65
+ include Contracts::Core
66
+
67
+ Contract C::Num => C::Num
68
+ def self.a_class_method x
69
+ x + 1
70
+ end
71
+
72
+ Contract C::Num => nil
73
+ def bad_double(x)
74
+ x * 2
75
+ end
76
+
77
+ Contract C::Num => C::Num
78
+ def double(x)
79
+ x * 2
80
+ end
81
+
82
+ Contract 123, nil => nil
83
+ def constanty(num, nul)
84
+ 0
85
+ end
86
+
87
+ Contract String => nil
88
+ def hello(name)
89
+ end
90
+
91
+ Contract lambda { |x| x.is_a? Numeric } => C::Num
92
+ def square(x)
93
+ x ** 2
94
+ end
95
+
96
+ Contract [C::Num, C::Num, C::Num] => C::Num
97
+ def sum_three(vals)
98
+ vals.inject(0) do |acc, x|
99
+ acc + x
100
+ end
101
+ end
102
+
103
+ Contract ({ :name => String, :age => Fixnum }) => nil
104
+ def person(data)
105
+ end
106
+
107
+ Contract C::StrictHash[{ :name => String, :age => Fixnum }] => nil
108
+ def strict_person(data)
109
+ end
110
+
111
+ Contract ({ :rigged => C::Or[TrueClass, FalseClass] }) => nil
112
+ def hash_complex_contracts(data)
113
+ end
114
+
115
+ Contract ({ :rigged => C::Bool,
116
+ :contents => { :kind => C::Or[String, Symbol],
117
+ :total => C::Num }
118
+ }) => nil
119
+ def nested_hash_complex_contracts(data)
120
+ end
121
+
122
+ Contract C::KeywordArgs[:name => String, :age => Fixnum] => nil
123
+ def person_keywordargs(data)
124
+ end
125
+
126
+ # Testing overloaded method
127
+ Contract String, Fixnum => nil
128
+ def person_keywordargs(name, age)
129
+ end
130
+
131
+ Contract C::KeywordArgs[:hash => C::HashOf[Symbol, C::Num]] => nil
132
+ def hash_keywordargs(data)
133
+ end
134
+
135
+ Contract C::KeywordArgs[:name => String, :age => Fixnum] => nil
136
+ def simple_keywordargs(data)
137
+ end
138
+
139
+ Contract (/foo/) => nil
140
+ def should_contain_foo(s)
141
+ end
142
+
143
+ Contract ({ :host => /foo/ }) => nil
144
+ def hash_containing_foo(s)
145
+ end
146
+
147
+ Contract C::ArrayOf[/foo/] => nil
148
+ def array_containing_foo(s)
149
+ end
150
+
151
+ Contract [C::Or[TrueClass, FalseClass]] => nil
152
+ def array_complex_contracts(data)
153
+ end
154
+
155
+ Contract [C::Bool, [C::Or[String, Symbol]]] => nil
156
+ def nested_array_complex_contracts(data)
157
+ end
158
+
159
+ Contract Proc => C::Any
160
+ def do_call(&block)
161
+ block.call
162
+ end
163
+
164
+ Contract C::Args[C::Num], C::Maybe[Proc] => C::Any
165
+ def maybe_call(*vals, &block)
166
+ block.call if block
167
+ vals
168
+ end
169
+
170
+ Contract C::Args[C::Num] => C::Num
171
+ def sum(*vals)
172
+ vals.inject(0) do |acc, val|
173
+ acc + val
174
+ end
175
+ end
176
+
177
+ Contract C::Args[C::Num], Proc => C::Num
178
+ def with_partial_sums(*vals, &blk)
179
+ sum = vals.inject(0) do |acc, val|
180
+ blk[acc]
181
+ acc + val
182
+ end
183
+ blk[sum]
184
+ end
185
+
186
+ Contract C::Args[C::Num], C::Func[C::Num => C::Num] => C::Num
187
+ def with_partial_sums_contracted(*vals, &blk)
188
+ sum = vals.inject(0) do |acc, val|
189
+ blk[acc]
190
+ acc + val
191
+ end
192
+ blk[sum]
193
+ end
194
+
195
+ # Important to use different arg types or it falsely passes
196
+ Contract C::Num, C::Args[String] => C::ArrayOf[String]
197
+ def arg_then_splat(n, *vals)
198
+ vals.map { |v| v * n }
199
+ end
200
+
201
+ Contract C::Num, Proc => nil
202
+ def double_with_proc(x, &blk)
203
+ blk.call(x * 2)
204
+ nil
205
+ end
206
+
207
+ Contract C::Pos => nil
208
+ def pos_test(x)
209
+ end
210
+
211
+ Contract C::Neg => nil
212
+ def neg_test(x)
213
+ end
214
+
215
+ Contract C::Nat => nil
216
+ def nat_test(x)
217
+ end
218
+
219
+ Contract C::Any => nil
220
+ def show(x)
221
+ end
222
+
223
+ Contract C::None => nil
224
+ def fail_all(x)
225
+ end
226
+
227
+ Contract C::Or[C::Num, String] => nil
228
+ def num_or_string(x)
229
+ end
230
+
231
+ Contract C::Xor[C::RespondTo[:good], C::RespondTo[:bad]] => nil
232
+ def xor_test(x)
233
+ end
234
+
235
+ Contract C::And[A, C::RespondTo[:good]] => nil
236
+ def and_test(x)
237
+ end
238
+
239
+ Contract C::Enum[:a, :b, :c] => nil
240
+ def enum_test(x)
241
+ end
242
+
243
+ Contract C::RespondTo[:good] => nil
244
+ def responds_test(x)
245
+ end
246
+
247
+ Contract C::Send[:good] => nil
248
+ def send_test(x)
249
+ end
250
+
251
+ Contract C::Not[nil] => nil
252
+ def not_nil(x)
253
+ end
254
+
255
+ Contract C::ArrayOf[C::Num] => C::Num
256
+ def product(vals)
257
+ vals.inject(1) do |acc, x|
258
+ acc * x
259
+ end
260
+ end
261
+
262
+ Contract C::SetOf[C::Num] => C::Num
263
+ def product_from_set(vals)
264
+ vals.inject(1) do |acc, x|
265
+ acc * x
266
+ end
267
+ end
268
+
269
+ Contract C::RangeOf[C::Num] => C::Num
270
+ def first_in_range_num(r)
271
+ r.first
272
+ end
273
+
274
+ Contract C::RangeOf[Date] => Date
275
+ def first_in_range_date(r)
276
+ r.first
277
+ end
278
+
279
+ Contract C::DescendantOf[Enumerable] => nil
280
+ def enumerable_descendant_test(enum)
281
+ end
282
+
283
+ Contract C::Bool => nil
284
+ def bool_test(x)
285
+ end
286
+
287
+ Contract C::Num
288
+ def no_args
289
+ 1
290
+ end
291
+
292
+ # This function has a contract which says it has no args,
293
+ # but the function does have args.
294
+ Contract nil => C::Num
295
+ def old_style_no_args
296
+ 2
297
+ end
298
+
299
+ Contract C::ArrayOf[C::Num], C::Func[C::Num => C::Num] => C::ArrayOf[C::Num]
300
+ def map(arr, func)
301
+ ret = []
302
+ arr.each do |x|
303
+ ret << func[x]
304
+ end
305
+ ret
306
+ end
307
+
308
+ Contract C::ArrayOf[C::Any], Proc => C::ArrayOf[C::Any]
309
+ def tutorial_map(arr, func)
310
+ ret = []
311
+ arr.each do |x|
312
+ ret << func[x]
313
+ end
314
+ ret
315
+ end
316
+
317
+ # Need to test Func with weak contracts for other args
318
+ # and changing type from input to output otherwise it falsely passes!
319
+ Contract Array, C::Func[String => C::Num] => Array
320
+ def map_plain(arr, func)
321
+ arr.map do |x|
322
+ func[x]
323
+ end
324
+ end
325
+
326
+ Contract C::None => C::Func[String => C::Num]
327
+ def lambda_with_wrong_return
328
+ lambda { |x| x }
329
+ end
330
+
331
+ Contract C::None => C::Func[String => C::Num]
332
+ def lambda_with_correct_return
333
+ lambda { |x| x.length }
334
+ end
335
+
336
+ Contract C::Num => C::Num
337
+ def default_args(x = 1)
338
+ 2
339
+ end
340
+
341
+ Contract C::Maybe[C::Num] => C::Maybe[C::Num]
342
+ def maybe_double x
343
+ if x.nil?
344
+ nil
345
+ else
346
+ x * 2
347
+ end
348
+ end
349
+
350
+ Contract C::HashOf[Symbol, C::Num] => C::Num
351
+ def gives_max_value(hash)
352
+ hash.values.max
353
+ end
354
+
355
+ Contract C::HashOf[Symbol => C::Num] => C::Num
356
+ def pretty_gives_max_value(hash)
357
+ hash.values.max
358
+ end
359
+
360
+ Contract EmptyCont => C::Any
361
+ def using_empty_contract(a)
362
+ a
363
+ end
364
+
365
+ Contract (1..10) => nil
366
+ def method_with_range_contract(x)
367
+ end
368
+
369
+ Contract String
370
+ def a_private_method
371
+ "works"
372
+ end
373
+ private :a_private_method
374
+
375
+ Contract String
376
+ def a_protected_method
377
+ "works"
378
+ end
379
+ protected :a_protected_method
380
+
381
+ private
382
+
383
+ Contract String
384
+ def a_really_private_method
385
+ "works for sure"
386
+ end
387
+
388
+ protected
389
+
390
+ Contract String
391
+ def a_really_protected_method
392
+ "works for sure"
393
+ end
394
+ end
395
+
396
+ # for testing inheritance
397
+ class Parent
398
+ include Contracts::Core
399
+
400
+ Contract C::Num => C::Num
401
+ def double x
402
+ x * 2
403
+ end
404
+ end
405
+
406
+ class Child < Parent
407
+ end
408
+
409
+ class GenericExample
410
+ Contract Parent => Parent
411
+ def id_ a
412
+ a
413
+ end
414
+
415
+ Contract C::Exactly[Parent] => nil
416
+ def exactly_test(x)
417
+ end
418
+ end
419
+
420
+ # for testing equality
421
+ class Foo
422
+ end
423
+ module Bar
424
+ end
425
+ Baz = 1
426
+
427
+ class GenericExample
428
+ Contract C::Eq[Foo] => C::Any
429
+ def eq_class_test(x)
430
+ end
431
+
432
+ Contract C::Eq[Bar] => C::Any
433
+ def eq_module_test(x)
434
+ end
435
+
436
+ Contract C::Eq[Baz] => C::Any
437
+ def eq_value_test(x)
438
+ end
439
+ end
440
+
441
+ # pattern matching example with possible deep contract violation
442
+ class PatternMatchingExample
443
+ include Contracts::Core
444
+
445
+ class Success
446
+ attr_accessor :request
447
+ def initialize request
448
+ @request = request
449
+ end
450
+
451
+ def ==(other)
452
+ request == other.request
453
+ end
454
+ end
455
+
456
+ class Failure
457
+ end
458
+
459
+ Response = C::Or[Success, Failure]
460
+
461
+ class StringWithHello
462
+ def self.valid?(string)
463
+ string.is_a?(String) && !!string.match(/hello/i)
464
+ end
465
+ end
466
+
467
+ Contract Success => Response
468
+ def process_request(status)
469
+ Success.new(decorated_request(status.request))
470
+ end
471
+
472
+ Contract Failure => Response
473
+ def process_request(status)
474
+ Failure.new
475
+ end
476
+
477
+ Contract StringWithHello => String
478
+ def decorated_request(request)
479
+ request + "!"
480
+ end
481
+
482
+ Contract C::Num, String => String
483
+ def do_stuff(number, string)
484
+ "foo"
485
+ end
486
+
487
+ Contract C::Num, String, C::Num => String
488
+ def do_stuff(number, string, other_number)
489
+ "bar"
490
+ end
491
+
492
+ Contract C::Num => C::Num
493
+ def double x
494
+ "bad"
495
+ end
496
+
497
+ Contract String => String
498
+ def double x
499
+ x * 2
500
+ end
501
+ end
502
+
503
+ # invariant example (silliest implementation ever)
504
+ class MyBirthday
505
+ include Contracts::Core
506
+ include Contracts::Invariants
507
+
508
+ invariant(:day) { 1 <= day && day <= 31 }
509
+ invariant(:month) { 1 <= month && month <= 12 }
510
+
511
+ attr_accessor :day, :month
512
+ def initialize(day, month)
513
+ @day = day
514
+ @month = month
515
+ end
516
+
517
+ Contract C::None => Fixnum
518
+ def silly_next_day!
519
+ self.day += 1
520
+ end
521
+
522
+ Contract C::None => Fixnum
523
+ def silly_next_month!
524
+ self.month += 1
525
+ end
526
+
527
+ Contract C::None => Fixnum
528
+ def clever_next_day!
529
+ return clever_next_month! if day == 31
530
+ self.day += 1
531
+ end
532
+
533
+ Contract C::None => Fixnum
534
+ def clever_next_month!
535
+ return next_year! if month == 12
536
+ self.month += 1
537
+ self.day = 1
538
+ end
539
+
540
+ Contract C::None => Fixnum
541
+ def next_year!
542
+ self.month = 1
543
+ self.day = 1
544
+ end
545
+ end
546
+
547
+ class SingletonClassExample
548
+ # This turned out to be required line here to make singleton classes
549
+ # work properly under all platforms. Not sure if it worth trying to
550
+ # do something with it.
551
+ include Contracts::Core
552
+
553
+ class << self
554
+ Contract String => String
555
+ def hoge(str)
556
+ "super#{str}"
557
+ end
558
+
559
+ Contract C::Num, C::Num => C::Num
560
+ def add(a, b)
561
+ a + b
562
+ end
563
+ end
564
+ end
565
+
566
+ with_enabled_no_contracts do
567
+ class NoContractsSimpleExample
568
+ include Contracts::Core
569
+
570
+ Contract String => nil
571
+ def some_method(x)
572
+ nil
573
+ end
574
+ end
575
+
576
+ class NoContractsInvariantsExample
577
+ include Contracts::Core
578
+ include Contracts::Invariants
579
+
580
+ attr_accessor :day
581
+
582
+ invariant(:day_rule) { 1 <= day && day <= 7 }
583
+
584
+ Contract C::None => nil
585
+ def next_day
586
+ self.day += 1
587
+ end
588
+ end
589
+
590
+ class NoContractsPatternMatchingExample
591
+ include Contracts::Core
592
+
593
+ Contract 200, String => String
594
+ def on_response(status, body)
595
+ body + "!"
596
+ end
597
+
598
+ Contract Fixnum, String => String
599
+ def on_response(status, body)
600
+ "error #{status}: #{body}"
601
+ end
602
+ end
603
+ end
604
+
605
+ module ModuleExample
606
+ include Contracts::Core
607
+
608
+ Contract C::Num, C::Num => C::Num
609
+ def plus(a, b)
610
+ a + b
611
+ end
612
+
613
+ Contract String => String
614
+ def self.hoge(str)
615
+ "super#{str}"
616
+ end
617
+
618
+ class << self
619
+ Contract String => nil
620
+ def eat(food)
621
+ # yummy
622
+ nil
623
+ end
624
+ end
625
+ end
626
+
627
+ class KlassWithModuleExample
628
+ include ModuleExample
629
+ end
630
+
631
+ class SingletonInheritanceExample
632
+ include Contracts::Core
633
+
634
+ Contract C::Any => C::Any
635
+ def self.a_contracted_self
636
+ self
637
+ end
638
+ end
639
+
640
+ class SingletonInheritanceExampleSubclass < SingletonInheritanceExample
641
+ class << self
642
+ Contract Integer => Integer
643
+ def num(int)
644
+ int
645
+ end
646
+ end
647
+ end
648
+
649
+ class BareOptionalContractUsed
650
+ include Contracts::Core
651
+
652
+ Contract C::Num, C::Optional[C::Num] => nil
653
+ def something(a, b)
654
+ nil
655
+ end
656
+ end
657
+
658
+ module ModuleContractExample
659
+ include Contracts::Core
660
+
661
+ module AModule
662
+ end
663
+
664
+ module AnotherModule
665
+ end
666
+
667
+ module InheritedModule
668
+ include AModule
669
+ end
670
+
671
+ class AClassWithModule
672
+ include AModule
673
+ end
674
+
675
+ class AClassWithoutModule
676
+ end
677
+
678
+ class AClassWithAnotherModule
679
+ include AnotherModule
680
+ end
681
+
682
+ class AClassWithInheritedModule
683
+ include InheritedModule
684
+ end
685
+
686
+ class AClassWithBothModules
687
+ include AModule
688
+ include AnotherModule
689
+ end
690
+
691
+ Contract AModule => Symbol
692
+ def self.hello(thing)
693
+ :world
694
+ end
695
+ end
696
+
697
+ module ModuleWithContracts
698
+ def self.included(base)
699
+ base.extend ClassMethods
700
+ end
701
+
702
+ module ClassMethods
703
+ include Contracts::Core
704
+
705
+ Contract C::None => String
706
+ def foo
707
+ "bar"
708
+ end
709
+ end
710
+ end