contracts 0.11.0 → 0.12.0

Sign up to get free protection for your applications and to get access to all the features.
Files changed (50) hide show
  1. checksums.yaml +13 -5
  2. data/CHANGELOG.markdown +8 -0
  3. data/Gemfile +3 -0
  4. data/README.md +14 -10
  5. data/TUTORIAL.md +34 -1
  6. data/benchmarks/io.rb +3 -3
  7. data/cucumber.yml +1 -0
  8. data/features/README.md +17 -0
  9. data/features/basics/functype.feature +71 -0
  10. data/features/basics/simple_example.feature +210 -0
  11. data/features/builtin_contracts/README.md +22 -0
  12. data/features/builtin_contracts/and.feature +103 -0
  13. data/features/builtin_contracts/any.feature +44 -0
  14. data/features/builtin_contracts/args.feature +1 -0
  15. data/features/builtin_contracts/array_of.feature +1 -0
  16. data/features/builtin_contracts/bool.feature +64 -0
  17. data/features/builtin_contracts/enum.feature +1 -0
  18. data/features/builtin_contracts/eq.feature +1 -0
  19. data/features/builtin_contracts/exactly.feature +1 -0
  20. data/features/builtin_contracts/func.feature +1 -0
  21. data/features/builtin_contracts/hash_of.feature +1 -0
  22. data/features/builtin_contracts/keyword_args.feature +1 -0
  23. data/features/builtin_contracts/maybe.feature +1 -0
  24. data/features/builtin_contracts/nat.feature +115 -0
  25. data/features/builtin_contracts/neg.feature +115 -0
  26. data/features/builtin_contracts/none.feature +145 -0
  27. data/features/builtin_contracts/not.feature +1 -0
  28. data/features/builtin_contracts/num.feature +64 -0
  29. data/features/builtin_contracts/or.feature +83 -0
  30. data/features/builtin_contracts/pos.feature +116 -0
  31. data/features/builtin_contracts/range_of.feature +1 -0
  32. data/features/builtin_contracts/respond_to.feature +78 -0
  33. data/features/builtin_contracts/send.feature +147 -0
  34. data/features/builtin_contracts/set_of.feature +1 -0
  35. data/features/builtin_contracts/xor.feature +99 -0
  36. data/features/support/env.rb +6 -0
  37. data/lib/contracts.rb +1 -1
  38. data/lib/contracts/builtin_contracts.rb +356 -351
  39. data/lib/contracts/core.rb +11 -2
  40. data/lib/contracts/formatters.rb +2 -2
  41. data/lib/contracts/validators.rb +6 -0
  42. data/lib/contracts/version.rb +1 -1
  43. data/script/cucumber +5 -0
  44. data/script/docs-release +3 -0
  45. data/script/docs-staging +3 -0
  46. data/spec/builtin_contracts_spec.rb +1 -1
  47. data/spec/contracts_spec.rb +29 -0
  48. data/spec/fixtures/fixtures.rb +12 -0
  49. data/spec/validators_spec.rb +25 -3
  50. metadata +42 -9
@@ -0,0 +1,22 @@
1
+ To use builtin contracts you can refer them with `Contracts::*`:
2
+
3
+ ```ruby
4
+ Contract Contracts::Num => Contracts::Maybe(Contracts::Num)
5
+ ```
6
+
7
+ It is recommended to use a short alias for `Contracts`, for example `C`:
8
+
9
+ ```ruby
10
+ C = Contracts
11
+
12
+ Contract C::Num => C::Maybe(C::Num)
13
+ ```
14
+
15
+ It is possible to `include Contracts` and refer them without namespace, but
16
+ this is deprecated and not recommended.
17
+
18
+ *NOTE: in the future it will be possible to do `include Contracts::Builtin`
19
+ instead.*
20
+
21
+ *NOTE: all contracts marked as (TODO) have their documentaion `.feature` file
22
+ as stub. Contributions to those are warmly welcome!*
@@ -0,0 +1,103 @@
1
+ Feature: And
2
+
3
+ Takes a variable number of contracts. The contract passes if all of the
4
+ contracts pass.
5
+
6
+ ```ruby
7
+ Contract C::And[Float, C::Neg] => String
8
+ ```
9
+
10
+ This example will validate first argument of a method and accept only
11
+ negative `Float`.
12
+
13
+ Background:
14
+ Given a file named "and_usage.rb" with:
15
+ """ruby
16
+ require "contracts"
17
+ C = Contracts
18
+
19
+ class Example
20
+ include Contracts::Core
21
+
22
+ Contract C::And[Float, C::Neg] => String
23
+ def fneg_string(number)
24
+ number.to_i.to_s
25
+ end
26
+ end
27
+ """
28
+
29
+ Scenario: Accepts negative float
30
+ Given a file named "accepts_negative_float.rb" with:
31
+ """ruby
32
+ require "./and_usage"
33
+ puts Example.new.fneg_string(-3.7)
34
+ """
35
+ When I run `ruby accepts_negative_float.rb`
36
+ Then output should contain:
37
+ """
38
+ -3
39
+ """
40
+
41
+ Scenario: Rejects positive float
42
+ Given a file named "rejects_positive_float.rb" with:
43
+ """ruby
44
+ require "./and_usage"
45
+ puts Example.new.fneg_string(7.5)
46
+ """
47
+ When I run `ruby rejects_positive_float.rb`
48
+ Then output should contain:
49
+ """
50
+ : Contract violation for argument 1 of 1: (ParamContractError)
51
+ Expected: (Float and Neg),
52
+ Actual: 7.5
53
+ Value guarded in: Example::fneg_string
54
+ With Contract: And => String
55
+ """
56
+
57
+ Scenario: Rejects negative integer
58
+ Given a file named "rejects_negative_integer.rb" with:
59
+ """ruby
60
+ require "./and_usage"
61
+ puts Example.new.fneg_string(-5)
62
+ """
63
+ When I run `ruby rejects_negative_integer.rb`
64
+ Then output should contain:
65
+ """
66
+ : Contract violation for argument 1 of 1: (ParamContractError)
67
+ Expected: (Float and Neg),
68
+ Actual: -5
69
+ Value guarded in: Example::fneg_string
70
+ With Contract: And => String
71
+ """
72
+
73
+ Scenario: Rejects positive integer
74
+ Given a file named "rejects_positive_integer.rb" with:
75
+ """ruby
76
+ require "./and_usage"
77
+ puts Example.new.fneg_string(5)
78
+ """
79
+ When I run `ruby rejects_positive_integer.rb`
80
+ Then output should contain:
81
+ """
82
+ : Contract violation for argument 1 of 1: (ParamContractError)
83
+ Expected: (Float and Neg),
84
+ Actual: 5
85
+ Value guarded in: Example::fneg_string
86
+ With Contract: And => String
87
+ """
88
+
89
+ Scenario: Rejects others
90
+ Given a file named "rejects_others.rb" with:
91
+ """ruby
92
+ require "./and_usage"
93
+ puts Example.new.fneg_string(:foo)
94
+ """
95
+ When I run `ruby rejects_others.rb`
96
+ Then output should contain:
97
+ """
98
+ : Contract violation for argument 1 of 1: (ParamContractError)
99
+ Expected: (Float and Neg),
100
+ Actual: :foo
101
+ Value guarded in: Example::fneg_string
102
+ With Contract: And => String
103
+ """
@@ -0,0 +1,44 @@
1
+ Feature: Any
2
+
3
+ Passes for any argument.
4
+
5
+ ```ruby
6
+ Contract C::Any => String
7
+ ```
8
+
9
+ Scenario: Accepts any argument
10
+ Given a file named "any_usage.rb" with:
11
+ """ruby
12
+ require "contracts"
13
+ C = Contracts
14
+
15
+ class Example
16
+ include Contracts::Core
17
+
18
+ Contract C::Any => String
19
+ def self.stringify(x)
20
+ x.inspect
21
+ end
22
+ end
23
+
24
+ puts Example.stringify(25)
25
+ puts Example.stringify(37.59)
26
+ puts Example.stringify("foo")
27
+ puts Example.stringify(:foo)
28
+ puts Example.stringify(nil)
29
+ puts Example.stringify(Object)
30
+ """
31
+ When I run `ruby any_usage.rb`
32
+ Then output should contain:
33
+ """
34
+ 25
35
+ 37.59
36
+ "foo"
37
+ :foo
38
+ nil
39
+ Object
40
+ """
41
+ And output should not contain:
42
+ """
43
+ Contract violation for
44
+ """
@@ -0,0 +1 @@
1
+ Feature: Args (TODO)
@@ -0,0 +1 @@
1
+ Feature: ArrayOf (TODO)
@@ -0,0 +1,64 @@
1
+ Feature: Bool
2
+
3
+ Checks that the argument is a `true` or `false`.
4
+
5
+ ```ruby
6
+ Contract String => C::Bool
7
+ ```
8
+
9
+ Background:
10
+ Given a file named "bool_usage.rb" with:
11
+ """ruby
12
+ require "contracts"
13
+ C = Contracts
14
+
15
+ class Example
16
+ include Contracts::Core
17
+
18
+ Contract String => C::Bool
19
+ def self.strong?(password)
20
+ return if password == ""
21
+ password.length > 22
22
+ end
23
+ end
24
+ """
25
+
26
+ Scenario: Accepts `true`
27
+ Given a file named "true.rb" with:
28
+ """ruby
29
+ require "./bool_usage"
30
+ puts Example.strong?("verystrongandLon774gPassword!ForYouHere")
31
+ """
32
+ When I run `ruby true.rb`
33
+ Then output should contain:
34
+ """
35
+ true
36
+ """
37
+
38
+ Scenario: Accepts `false`
39
+ Given a file named "false.rb" with:
40
+ """ruby
41
+ require "./bool_usage"
42
+ puts Example.strong?("welcome")
43
+ """
44
+ When I run `ruby false.rb`
45
+ Then output should contain:
46
+ """
47
+ false
48
+ """
49
+
50
+ Scenario: Rejects everything else
51
+ Given a file named "nil.rb" with:
52
+ """ruby
53
+ require "./bool_usage"
54
+ puts Example.strong?("")
55
+ """
56
+ When I run `ruby nil.rb`
57
+ Then output should contain:
58
+ """
59
+ : Contract violation for return value: (ReturnContractError)
60
+ Expected: Bool,
61
+ Actual: nil
62
+ Value guarded in: Example::strong?
63
+ With Contract: String => Bool
64
+ """
@@ -0,0 +1 @@
1
+ Feature: Enum (TODO)
@@ -0,0 +1 @@
1
+ Feature: Eq (TODO)
@@ -0,0 +1 @@
1
+ Feature: Exactly (TODO)
@@ -0,0 +1 @@
1
+ Feature: Func (TODO)
@@ -0,0 +1 @@
1
+ Feature: HashOf (TODO)
@@ -0,0 +1 @@
1
+ Feature: KeywordArgs (TODO)
@@ -0,0 +1 @@
1
+ Feature: Maybe (TODO)
@@ -0,0 +1,115 @@
1
+ Feature: Nat
2
+
3
+ Checks that an argument is a natural number.
4
+
5
+ ```ruby
6
+ Contract C::Nat => C::Nat
7
+ ```
8
+
9
+ Background:
10
+ Given a file named "nat_usage.rb" with:
11
+ """ruby
12
+ require "contracts"
13
+ C = Contracts
14
+
15
+ class Natural
16
+ include Contracts::Core
17
+
18
+ Contract C::Nat => C::Nat
19
+ def prev(number)
20
+ number - 1
21
+ end
22
+ end
23
+ """
24
+
25
+ Scenario: Accepts positive integers
26
+ Given a file named "accepts_positive_integers.rb" with:
27
+ """ruby
28
+ require "./nat_usage"
29
+ puts Natural.new.prev(7)
30
+ """
31
+ When I run `ruby accepts_positive_integers.rb`
32
+ Then output should contain:
33
+ """
34
+ 6
35
+ """
36
+
37
+ Scenario: Accepts zero
38
+ Given a file named "accepts_zero.rb" with:
39
+ """ruby
40
+ require "./nat_usage"
41
+ puts Natural.new.prev(1)
42
+ """
43
+ When I run `ruby accepts_zero.rb`
44
+ Then output should contain:
45
+ """
46
+ 0
47
+ """
48
+
49
+ Scenario: Rejects negative integers
50
+ Given a file named "rejects_negative_integers.rb" with:
51
+ """ruby
52
+ require "./nat_usage"
53
+ puts Natural.new.prev(-1)
54
+ """
55
+ When I run `ruby rejects_negative_integers.rb`
56
+ Then output should contain:
57
+ """
58
+ : Contract violation for argument 1 of 1: (ParamContractError)
59
+ Expected: Nat,
60
+ Actual: -1
61
+ Value guarded in: Natural::prev
62
+ With Contract: Nat => Nat
63
+ """
64
+ And output should contain "nat_usage.rb:8"
65
+
66
+ Scenario: Rejects negative integers as a return value
67
+ Given a file named "rejects_negative_integers.rb" with:
68
+ """ruby
69
+ require "./nat_usage"
70
+ puts Natural.new.prev(0)
71
+ """
72
+ When I run `ruby rejects_negative_integers.rb`
73
+ Then output should contain:
74
+ """
75
+ : Contract violation for return value: (ReturnContractError)
76
+ Expected: Nat,
77
+ Actual: -1
78
+ Value guarded in: Natural::prev
79
+ With Contract: Nat => Nat
80
+ """
81
+ And output should contain "nat_usage.rb:8"
82
+
83
+ Scenario: Rejects floats
84
+ Given a file named "rejects_floats.rb" with:
85
+ """ruby
86
+ require "./nat_usage"
87
+ puts Natural.new.prev(3.43)
88
+ """
89
+ When I run `ruby rejects_floats.rb`
90
+ Then output should contain:
91
+ """
92
+ : Contract violation for argument 1 of 1: (ParamContractError)
93
+ Expected: Nat,
94
+ Actual: 3.43
95
+ Value guarded in: Natural::prev
96
+ With Contract: Nat => Nat
97
+ """
98
+ And output should contain "nat_usage.rb:8"
99
+
100
+ Scenario: Rejects other values
101
+ Given a file named "rejects_others.rb" with:
102
+ """ruby
103
+ require "./nat_usage"
104
+ puts Natural.new.prev("foo")
105
+ """
106
+ When I run `ruby rejects_others.rb`
107
+ Then output should contain:
108
+ """
109
+ : Contract violation for argument 1 of 1: (ParamContractError)
110
+ Expected: Nat,
111
+ Actual: "foo"
112
+ Value guarded in: Natural::prev
113
+ With Contract: Nat => Nat
114
+ """
115
+ And output should contain "nat_usage.rb:8"
@@ -0,0 +1,115 @@
1
+ Feature: Neg
2
+
3
+ Checks that an argument is negative `Numeric`.
4
+
5
+ ```ruby
6
+ Contract C::Neg => C::Neg
7
+ ```
8
+
9
+ Background:
10
+ Given a file named "pos_usage.rb" with:
11
+ """ruby
12
+ require "contracts"
13
+ C = Contracts
14
+
15
+ class Example
16
+ include Contracts::Core
17
+
18
+ Contract C::Neg => C::Neg
19
+ def double_expense(amount)
20
+ amount * 2
21
+ end
22
+ end
23
+ """
24
+
25
+ Scenario: Accepts negative integers
26
+ Given a file named "accepts_negative_integers.rb" with:
27
+ """ruby
28
+ require "./pos_usage"
29
+ puts Example.new.double_expense(-50)
30
+ """
31
+ When I run `ruby accepts_negative_integers.rb`
32
+ Then output should contain:
33
+ """
34
+ -100
35
+ """
36
+
37
+ Scenario: Accepts negative floats
38
+ Given a file named "accepts_negative_floats.rb" with:
39
+ """ruby
40
+ require "./pos_usage"
41
+ puts Example.new.double_expense(-37.99)
42
+ """
43
+ When I run `ruby accepts_negative_floats.rb`
44
+ Then output should contain:
45
+ """
46
+ -75.98
47
+ """
48
+
49
+ Scenario: Rejects positive integers
50
+ Given a file named "rejects_positive_integers.rb" with:
51
+ """ruby
52
+ require "./pos_usage"
53
+ puts Example.new.double_expense(50)
54
+ """
55
+ When I run `ruby rejects_positive_integers.rb`
56
+ Then output should contain:
57
+ """
58
+ : Contract violation for argument 1 of 1: (ParamContractError)
59
+ Expected: Neg,
60
+ Actual: 50
61
+ Value guarded in: Example::double_expense
62
+ With Contract: Neg => Neg
63
+ """
64
+ And output should contain "pos_usage.rb:8"
65
+
66
+ Scenario: Rejects positive floats
67
+ Given a file named "rejects_positive_floats.rb" with:
68
+ """ruby
69
+ require "./pos_usage"
70
+ puts Example.new.double_expense(42.50)
71
+ """
72
+ When I run `ruby rejects_positive_floats.rb`
73
+ Then output should contain:
74
+ """
75
+ : Contract violation for argument 1 of 1: (ParamContractError)
76
+ Expected: Neg,
77
+ Actual: 42.5
78
+ Value guarded in: Example::double_expense
79
+ With Contract: Neg => Neg
80
+ """
81
+ And output should contain "pos_usage.rb:8"
82
+
83
+ Scenario: Rejects zero
84
+ Given a file named "rejects_zero.rb" with:
85
+ """ruby
86
+ require "./pos_usage"
87
+ puts Example.new.double_expense(0)
88
+ """
89
+ When I run `ruby rejects_zero.rb`
90
+ Then output should contain:
91
+ """
92
+ : Contract violation for argument 1 of 1: (ParamContractError)
93
+ Expected: Neg,
94
+ Actual: 0
95
+ Value guarded in: Example::double_expense
96
+ With Contract: Neg => Neg
97
+ """
98
+ And output should contain "pos_usage.rb:8"
99
+
100
+ Scenario: Rejects other values
101
+ Given a file named "rejects_others.rb" with:
102
+ """ruby
103
+ require "./pos_usage"
104
+ puts Example.new.double_expense("foo")
105
+ """
106
+ When I run `ruby rejects_others.rb`
107
+ Then output should contain:
108
+ """
109
+ : Contract violation for argument 1 of 1: (ParamContractError)
110
+ Expected: Neg,
111
+ Actual: "foo"
112
+ Value guarded in: Example::double_expense
113
+ With Contract: Neg => Neg
114
+ """
115
+ And output should contain "pos_usage.rb:8"