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,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