contracts 0.11.0 → 0.12.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 +13 -5
- data/CHANGELOG.markdown +8 -0
- data/Gemfile +3 -0
- data/README.md +14 -10
- data/TUTORIAL.md +34 -1
- data/benchmarks/io.rb +3 -3
- data/cucumber.yml +1 -0
- data/features/README.md +17 -0
- data/features/basics/functype.feature +71 -0
- data/features/basics/simple_example.feature +210 -0
- data/features/builtin_contracts/README.md +22 -0
- data/features/builtin_contracts/and.feature +103 -0
- data/features/builtin_contracts/any.feature +44 -0
- data/features/builtin_contracts/args.feature +1 -0
- data/features/builtin_contracts/array_of.feature +1 -0
- data/features/builtin_contracts/bool.feature +64 -0
- data/features/builtin_contracts/enum.feature +1 -0
- data/features/builtin_contracts/eq.feature +1 -0
- data/features/builtin_contracts/exactly.feature +1 -0
- data/features/builtin_contracts/func.feature +1 -0
- data/features/builtin_contracts/hash_of.feature +1 -0
- data/features/builtin_contracts/keyword_args.feature +1 -0
- data/features/builtin_contracts/maybe.feature +1 -0
- data/features/builtin_contracts/nat.feature +115 -0
- data/features/builtin_contracts/neg.feature +115 -0
- data/features/builtin_contracts/none.feature +145 -0
- data/features/builtin_contracts/not.feature +1 -0
- data/features/builtin_contracts/num.feature +64 -0
- data/features/builtin_contracts/or.feature +83 -0
- data/features/builtin_contracts/pos.feature +116 -0
- data/features/builtin_contracts/range_of.feature +1 -0
- data/features/builtin_contracts/respond_to.feature +78 -0
- data/features/builtin_contracts/send.feature +147 -0
- data/features/builtin_contracts/set_of.feature +1 -0
- data/features/builtin_contracts/xor.feature +99 -0
- data/features/support/env.rb +6 -0
- data/lib/contracts.rb +1 -1
- data/lib/contracts/builtin_contracts.rb +356 -351
- data/lib/contracts/core.rb +11 -2
- data/lib/contracts/formatters.rb +2 -2
- data/lib/contracts/validators.rb +6 -0
- data/lib/contracts/version.rb +1 -1
- data/script/cucumber +5 -0
- data/script/docs-release +3 -0
- data/script/docs-staging +3 -0
- data/spec/builtin_contracts_spec.rb +1 -1
- data/spec/contracts_spec.rb +29 -0
- data/spec/fixtures/fixtures.rb +12 -0
- data/spec/validators_spec.rb +25 -3
- 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"
|