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