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,145 @@
|
|
1
|
+
Feature: None
|
2
|
+
|
3
|
+
Fails for any argument.
|
4
|
+
|
5
|
+
Often used to explicitly specify that method does not accept any arguments:
|
6
|
+
|
7
|
+
```ruby
|
8
|
+
Contract C::None => Symbol
|
9
|
+
def a_symbol
|
10
|
+
:a_symbol
|
11
|
+
end
|
12
|
+
```
|
13
|
+
|
14
|
+
The same behavior can be achieved when argument list omitted:
|
15
|
+
|
16
|
+
```ruby
|
17
|
+
Contract Symbol
|
18
|
+
def a_symbol
|
19
|
+
:a_symbol
|
20
|
+
end
|
21
|
+
```
|
22
|
+
|
23
|
+
Background:
|
24
|
+
Given a file named "helper.rb" with:
|
25
|
+
"""ruby
|
26
|
+
def autorescue
|
27
|
+
yield
|
28
|
+
rescue => e
|
29
|
+
puts e.inspect
|
30
|
+
end
|
31
|
+
"""
|
32
|
+
Given a file named "none_usage.rb" with:
|
33
|
+
"""ruby
|
34
|
+
require "contracts"
|
35
|
+
require "./helper"
|
36
|
+
C = Contracts
|
37
|
+
|
38
|
+
class Example
|
39
|
+
include Contracts::Core
|
40
|
+
|
41
|
+
Contract C::None => Symbol
|
42
|
+
def self.a_symbol(*args)
|
43
|
+
:a_symbol
|
44
|
+
end
|
45
|
+
end
|
46
|
+
"""
|
47
|
+
|
48
|
+
Scenario: Accepts nothing
|
49
|
+
Given a file named "nothing.rb" with:
|
50
|
+
"""ruby
|
51
|
+
require "./none_usage"
|
52
|
+
puts Example.a_symbol
|
53
|
+
"""
|
54
|
+
When I run `ruby nothing.rb`
|
55
|
+
Then output should contain:
|
56
|
+
"""
|
57
|
+
a_symbol
|
58
|
+
"""
|
59
|
+
|
60
|
+
Scenario: Rejects any argument
|
61
|
+
Given a file named "anything.rb" with:
|
62
|
+
"""ruby
|
63
|
+
require "./none_usage"
|
64
|
+
autorescue { Example.a_symbol(nil) }
|
65
|
+
autorescue { Example.a_symbol(12) }
|
66
|
+
autorescue { Example.a_symbol(37.5) }
|
67
|
+
autorescue { Example.a_symbol("foo") }
|
68
|
+
autorescue { Example.a_symbol(:foo) }
|
69
|
+
autorescue { Example.a_symbol({}) }
|
70
|
+
autorescue { Example.a_symbol([]) }
|
71
|
+
autorescue { Example.a_symbol(Object) }
|
72
|
+
"""
|
73
|
+
When I run `ruby anything.rb`
|
74
|
+
|
75
|
+
Then output should contain:
|
76
|
+
"""
|
77
|
+
ParamContractError: Contract violation for argument 1 of 1:
|
78
|
+
Expected: None,
|
79
|
+
Actual: nil
|
80
|
+
Value guarded in: Example::a_symbol
|
81
|
+
With Contract: None => Symbol
|
82
|
+
"""
|
83
|
+
|
84
|
+
Then output should contain:
|
85
|
+
"""
|
86
|
+
ParamContractError: Contract violation for argument 1 of 1:
|
87
|
+
Expected: None,
|
88
|
+
Actual: 12
|
89
|
+
Value guarded in: Example::a_symbol
|
90
|
+
With Contract: None => Symbol
|
91
|
+
"""
|
92
|
+
|
93
|
+
Then output should contain:
|
94
|
+
"""
|
95
|
+
ParamContractError: Contract violation for argument 1 of 1:
|
96
|
+
Expected: None,
|
97
|
+
Actual: 37.5
|
98
|
+
Value guarded in: Example::a_symbol
|
99
|
+
With Contract: None => Symbol
|
100
|
+
"""
|
101
|
+
|
102
|
+
Then output should contain:
|
103
|
+
"""
|
104
|
+
ParamContractError: Contract violation for argument 1 of 1:
|
105
|
+
Expected: None,
|
106
|
+
Actual: "foo"
|
107
|
+
Value guarded in: Example::a_symbol
|
108
|
+
With Contract: None => Symbol
|
109
|
+
"""
|
110
|
+
|
111
|
+
Then output should contain:
|
112
|
+
"""
|
113
|
+
ParamContractError: Contract violation for argument 1 of 1:
|
114
|
+
Expected: None,
|
115
|
+
Actual: :foo
|
116
|
+
Value guarded in: Example::a_symbol
|
117
|
+
With Contract: None => Symbol
|
118
|
+
"""
|
119
|
+
|
120
|
+
Then output should contain:
|
121
|
+
"""
|
122
|
+
ParamContractError: Contract violation for argument 1 of 1:
|
123
|
+
Expected: None,
|
124
|
+
Actual: {}
|
125
|
+
Value guarded in: Example::a_symbol
|
126
|
+
With Contract: None => Symbol
|
127
|
+
"""
|
128
|
+
|
129
|
+
Then output should contain:
|
130
|
+
"""
|
131
|
+
ParamContractError: Contract violation for argument 1 of 1:
|
132
|
+
Expected: None,
|
133
|
+
Actual: []
|
134
|
+
Value guarded in: Example::a_symbol
|
135
|
+
With Contract: None => Symbol
|
136
|
+
"""
|
137
|
+
|
138
|
+
Then output should contain:
|
139
|
+
"""
|
140
|
+
ParamContractError: Contract violation for argument 1 of 1:
|
141
|
+
Expected: None,
|
142
|
+
Actual: Object
|
143
|
+
Value guarded in: Example::a_symbol
|
144
|
+
With Contract: None => Symbol
|
145
|
+
"""
|
@@ -0,0 +1 @@
|
|
1
|
+
Feature: Not (TODO)
|
@@ -0,0 +1,64 @@
|
|
1
|
+
Feature: Num
|
2
|
+
|
3
|
+
Checks that an argument is `Numeric`.
|
4
|
+
|
5
|
+
```ruby
|
6
|
+
Contract C::Num => C::Num
|
7
|
+
```
|
8
|
+
|
9
|
+
Background:
|
10
|
+
Given a file named "num_usage.rb" with:
|
11
|
+
"""ruby
|
12
|
+
require "contracts"
|
13
|
+
C = Contracts
|
14
|
+
|
15
|
+
class Example
|
16
|
+
include Contracts::Core
|
17
|
+
|
18
|
+
Contract C::Num => C::Num
|
19
|
+
def increase(number)
|
20
|
+
number + 1
|
21
|
+
end
|
22
|
+
end
|
23
|
+
"""
|
24
|
+
|
25
|
+
Scenario: Accepts integers
|
26
|
+
Given a file named "accepts_integers.rb" with:
|
27
|
+
"""ruby
|
28
|
+
require "./num_usage"
|
29
|
+
puts Example.new.increase(7)
|
30
|
+
"""
|
31
|
+
When I run `ruby accepts_integers.rb`
|
32
|
+
Then output should contain:
|
33
|
+
"""
|
34
|
+
8
|
35
|
+
"""
|
36
|
+
|
37
|
+
Scenario: Accepts floats
|
38
|
+
Given a file named "accepts_floats.rb" with:
|
39
|
+
"""ruby
|
40
|
+
require "./num_usage"
|
41
|
+
puts Example.new.increase(7.5)
|
42
|
+
"""
|
43
|
+
When I run `ruby accepts_floats.rb`
|
44
|
+
Then output should contain:
|
45
|
+
"""
|
46
|
+
8.5
|
47
|
+
"""
|
48
|
+
|
49
|
+
Scenario: Rejects other values
|
50
|
+
Given a file named "rejects_others.rb" with:
|
51
|
+
"""ruby
|
52
|
+
require "./num_usage"
|
53
|
+
puts Example.new.increase("foo")
|
54
|
+
"""
|
55
|
+
When I run `ruby rejects_others.rb`
|
56
|
+
Then output should contain:
|
57
|
+
"""
|
58
|
+
: Contract violation for argument 1 of 1: (ParamContractError)
|
59
|
+
Expected: Num,
|
60
|
+
Actual: "foo"
|
61
|
+
Value guarded in: Example::increase
|
62
|
+
With Contract: Num => Num
|
63
|
+
"""
|
64
|
+
And output should contain "num_usage.rb:8"
|
@@ -0,0 +1,83 @@
|
|
1
|
+
Feature: Or
|
2
|
+
|
3
|
+
Takes a variable number of contracts. The contract passes if any of the
|
4
|
+
contracts pass.
|
5
|
+
|
6
|
+
```ruby
|
7
|
+
Contract C::Or[Float, C::Nat] => String
|
8
|
+
```
|
9
|
+
|
10
|
+
This example will validate first argument of a method and accept either
|
11
|
+
`Float` or natural integer.
|
12
|
+
|
13
|
+
Background:
|
14
|
+
Given a file named "or_usage.rb" with:
|
15
|
+
"""ruby
|
16
|
+
require "contracts"
|
17
|
+
C = Contracts
|
18
|
+
|
19
|
+
class Example
|
20
|
+
include Contracts::Core
|
21
|
+
|
22
|
+
Contract C::Or[Float, C::Nat] => String
|
23
|
+
def nat_string(number)
|
24
|
+
number.to_i.to_s
|
25
|
+
end
|
26
|
+
end
|
27
|
+
"""
|
28
|
+
|
29
|
+
Scenario: Accepts float
|
30
|
+
Given a file named "accepts_float.rb" with:
|
31
|
+
"""ruby
|
32
|
+
require "./or_usage"
|
33
|
+
puts Example.new.nat_string(3.7)
|
34
|
+
"""
|
35
|
+
When I run `ruby accepts_float.rb`
|
36
|
+
Then output should contain:
|
37
|
+
"""
|
38
|
+
3
|
39
|
+
"""
|
40
|
+
|
41
|
+
Scenario: Accepts natural
|
42
|
+
Given a file named "accepts_natural.rb" with:
|
43
|
+
"""ruby
|
44
|
+
require "./or_usage"
|
45
|
+
puts Example.new.nat_string(7)
|
46
|
+
"""
|
47
|
+
When I run `ruby accepts_natural.rb`
|
48
|
+
Then output should contain:
|
49
|
+
"""
|
50
|
+
7
|
51
|
+
"""
|
52
|
+
|
53
|
+
Scenario: Rejects negative integer
|
54
|
+
Given a file named "rejects_negative_integer.rb" with:
|
55
|
+
"""ruby
|
56
|
+
require "./or_usage"
|
57
|
+
puts Example.new.nat_string(-3)
|
58
|
+
"""
|
59
|
+
When I run `ruby rejects_negative_integer.rb`
|
60
|
+
Then output should contain:
|
61
|
+
"""
|
62
|
+
: Contract violation for argument 1 of 1: (ParamContractError)
|
63
|
+
Expected: (Float or Nat),
|
64
|
+
Actual: -3
|
65
|
+
Value guarded in: Example::nat_string
|
66
|
+
With Contract: Or => String
|
67
|
+
"""
|
68
|
+
|
69
|
+
Scenario: Rejects other values
|
70
|
+
Given a file named "rejects_other.rb" with:
|
71
|
+
"""ruby
|
72
|
+
require "./or_usage"
|
73
|
+
puts Example.new.nat_string(nil)
|
74
|
+
"""
|
75
|
+
When I run `ruby rejects_other.rb`
|
76
|
+
Then output should contain:
|
77
|
+
"""
|
78
|
+
: Contract violation for argument 1 of 1: (ParamContractError)
|
79
|
+
Expected: (Float or Nat),
|
80
|
+
Actual: nil
|
81
|
+
Value guarded in: Example::nat_string
|
82
|
+
With Contract: Or => String
|
83
|
+
"""
|
@@ -0,0 +1,116 @@
|
|
1
|
+
Feature: Pos
|
2
|
+
|
3
|
+
Checks that an argument is positive `Numeric`.
|
4
|
+
|
5
|
+
```ruby
|
6
|
+
Contract C::Pos => C::Pos
|
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::Pos, C::Pos => C::Pos
|
19
|
+
def power(number, power)
|
20
|
+
return number if power <= 1
|
21
|
+
number * self.power(number, power - 1)
|
22
|
+
end
|
23
|
+
end
|
24
|
+
"""
|
25
|
+
|
26
|
+
Scenario: Accepts positive integers
|
27
|
+
Given a file named "accepts_positive_integers.rb" with:
|
28
|
+
"""ruby
|
29
|
+
require "./pos_usage"
|
30
|
+
puts Example.new.power(3, 4)
|
31
|
+
"""
|
32
|
+
When I run `ruby accepts_positive_integers.rb`
|
33
|
+
Then output should contain:
|
34
|
+
"""
|
35
|
+
81
|
36
|
+
"""
|
37
|
+
|
38
|
+
Scenario: Accepts positive floats
|
39
|
+
Given a file named "accepts_positive_floats.rb" with:
|
40
|
+
"""ruby
|
41
|
+
require "./pos_usage"
|
42
|
+
puts Example.new.power(3.7, 4.5)
|
43
|
+
"""
|
44
|
+
When I run `ruby accepts_positive_floats.rb`
|
45
|
+
Then output should contain:
|
46
|
+
"""
|
47
|
+
693.4395
|
48
|
+
"""
|
49
|
+
|
50
|
+
Scenario: Rejects negative integers
|
51
|
+
Given a file named "rejects_negative_integers.rb" with:
|
52
|
+
"""ruby
|
53
|
+
require "./pos_usage"
|
54
|
+
puts Example.new.power(3, -4)
|
55
|
+
"""
|
56
|
+
When I run `ruby rejects_negative_integers.rb`
|
57
|
+
Then output should contain:
|
58
|
+
"""
|
59
|
+
: Contract violation for argument 2 of 2: (ParamContractError)
|
60
|
+
Expected: Pos,
|
61
|
+
Actual: -4
|
62
|
+
Value guarded in: Example::power
|
63
|
+
With Contract: Pos, Pos => Pos
|
64
|
+
"""
|
65
|
+
And output should contain "pos_usage.rb:8"
|
66
|
+
|
67
|
+
Scenario: Rejects negative floats
|
68
|
+
Given a file named "rejects_negative_floats.rb" with:
|
69
|
+
"""ruby
|
70
|
+
require "./pos_usage"
|
71
|
+
puts Example.new.power(3.7, -4.4)
|
72
|
+
"""
|
73
|
+
When I run `ruby rejects_negative_floats.rb`
|
74
|
+
Then output should contain:
|
75
|
+
"""
|
76
|
+
: Contract violation for argument 2 of 2: (ParamContractError)
|
77
|
+
Expected: Pos,
|
78
|
+
Actual: -4.4
|
79
|
+
Value guarded in: Example::power
|
80
|
+
With Contract: Pos, Pos => Pos
|
81
|
+
"""
|
82
|
+
And output should contain "pos_usage.rb:8"
|
83
|
+
|
84
|
+
Scenario: Rejects zero
|
85
|
+
Given a file named "rejects_zero.rb" with:
|
86
|
+
"""ruby
|
87
|
+
require "./pos_usage"
|
88
|
+
puts Example.new.power(3, 0)
|
89
|
+
"""
|
90
|
+
When I run `ruby rejects_zero.rb`
|
91
|
+
Then output should contain:
|
92
|
+
"""
|
93
|
+
: Contract violation for argument 2 of 2: (ParamContractError)
|
94
|
+
Expected: Pos,
|
95
|
+
Actual: 0
|
96
|
+
Value guarded in: Example::power
|
97
|
+
With Contract: Pos, Pos => Pos
|
98
|
+
"""
|
99
|
+
And output should contain "pos_usage.rb:8"
|
100
|
+
|
101
|
+
Scenario: Rejects other values
|
102
|
+
Given a file named "rejects_others.rb" with:
|
103
|
+
"""ruby
|
104
|
+
require "./pos_usage"
|
105
|
+
puts Example.new.power("foo", 2)
|
106
|
+
"""
|
107
|
+
When I run `ruby rejects_others.rb`
|
108
|
+
Then output should contain:
|
109
|
+
"""
|
110
|
+
: Contract violation for argument 1 of 2: (ParamContractError)
|
111
|
+
Expected: Pos,
|
112
|
+
Actual: "foo"
|
113
|
+
Value guarded in: Example::power
|
114
|
+
With Contract: Pos, Pos => Pos
|
115
|
+
"""
|
116
|
+
And output should contain "pos_usage.rb:8"
|
@@ -0,0 +1 @@
|
|
1
|
+
Feature: RangeOf (TODO)
|
@@ -0,0 +1,78 @@
|
|
1
|
+
Feature: RespondTo
|
2
|
+
|
3
|
+
Takes a variable number of method names as symbols. The contract passes if
|
4
|
+
the argument responds to all of those methods.
|
5
|
+
|
6
|
+
```ruby
|
7
|
+
Contract C::RespondTo[:email, :password, :confirmation] => C::Bool
|
8
|
+
```
|
9
|
+
|
10
|
+
This contract will pass only for objects that `respond_to?` `:email`,
|
11
|
+
`:password` and `:confirmation`.
|
12
|
+
|
13
|
+
Background:
|
14
|
+
Given a file named "signup_validator.rb" with:
|
15
|
+
"""ruby
|
16
|
+
require "contracts"
|
17
|
+
C = Contracts
|
18
|
+
|
19
|
+
class SignupValidator
|
20
|
+
include Contracts::Core
|
21
|
+
|
22
|
+
Contract C::RespondTo[:email, :password, :confirmation] => C::Bool
|
23
|
+
def valid?(signup)
|
24
|
+
!!signup.email.match("@") &&
|
25
|
+
signup.password.length > 6 &&
|
26
|
+
signup.password == signup.confirmation
|
27
|
+
end
|
28
|
+
end
|
29
|
+
"""
|
30
|
+
|
31
|
+
Given a file named "signup.rb" with:
|
32
|
+
"""ruby
|
33
|
+
Signup = Struct.new(:email, :password, :confirmation)
|
34
|
+
"""
|
35
|
+
|
36
|
+
Given a file named "signin.rb" with:
|
37
|
+
"""ruby
|
38
|
+
Signin = Struct.new(:email, :password)
|
39
|
+
"""
|
40
|
+
|
41
|
+
Given a file named "helper.rb" with:
|
42
|
+
"""ruby
|
43
|
+
require "./signup_validator"
|
44
|
+
require "./signup"
|
45
|
+
require "./signin"
|
46
|
+
"""
|
47
|
+
|
48
|
+
Scenario: Accepts correct object
|
49
|
+
Given a file named "correct.rb" with:
|
50
|
+
"""ruby
|
51
|
+
require "./helper"
|
52
|
+
|
53
|
+
puts SignupValidator.new.valid?(Signup["john@example.org", "welcome", "welcome"])
|
54
|
+
puts SignupValidator.new.valid?(Signup["john@example.org", "welcome", "welcomr"])
|
55
|
+
"""
|
56
|
+
When I run `ruby correct.rb`
|
57
|
+
Then output should contain:
|
58
|
+
"""
|
59
|
+
true
|
60
|
+
false
|
61
|
+
"""
|
62
|
+
|
63
|
+
Scenario: Rejects incorrect object
|
64
|
+
Given a file named "incorrect.rb" with:
|
65
|
+
"""ruby
|
66
|
+
require "./helper"
|
67
|
+
|
68
|
+
puts SignupValidator.new.valid?(Signin["john@example.org", "welcome"])
|
69
|
+
"""
|
70
|
+
When I run `ruby incorrect.rb`
|
71
|
+
Then output should contain:
|
72
|
+
"""
|
73
|
+
: Contract violation for argument 1 of 1: (ParamContractError)
|
74
|
+
Expected: (a value that responds to [:email, :password, :confirmation]),
|
75
|
+
Actual: #<struct Signin email="john@example.org", password="welcome">
|
76
|
+
Value guarded in: SignupValidator::valid?
|
77
|
+
With Contract: RespondTo => Bool
|
78
|
+
"""
|