contracts 0.11.0 → 0.12.0
Sign up to get free protection for your applications and to get access to all the features.
- 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"
|