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