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