contracts 0.13.0 → 0.17

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 (42) hide show
  1. checksums.yaml +5 -5
  2. data/.github/workflows/code_style_checks.yaml +36 -0
  3. data/.github/workflows/tests.yaml +41 -0
  4. data/CHANGELOG.markdown +54 -7
  5. data/Gemfile +9 -5
  6. data/LICENSE +23 -0
  7. data/README.md +14 -6
  8. data/Rakefile +5 -6
  9. data/TUTORIAL.md +28 -1
  10. data/contracts.gemspec +9 -1
  11. data/dependabot.yml +20 -0
  12. data/features/basics/pretty-print.feature +241 -0
  13. data/features/support/env.rb +2 -0
  14. data/lib/contracts.rb +64 -19
  15. data/lib/contracts/attrs.rb +26 -0
  16. data/lib/contracts/builtin_contracts.rb +85 -7
  17. data/lib/contracts/call_with.rb +50 -28
  18. data/lib/contracts/core.rb +3 -3
  19. data/lib/contracts/decorators.rb +6 -2
  20. data/lib/contracts/engine.rb +2 -0
  21. data/lib/contracts/engine/base.rb +4 -3
  22. data/lib/contracts/engine/eigenclass.rb +3 -2
  23. data/lib/contracts/engine/target.rb +2 -0
  24. data/lib/contracts/errors.rb +3 -0
  25. data/lib/contracts/formatters.rb +17 -11
  26. data/lib/contracts/invariants.rb +8 -4
  27. data/lib/contracts/method_handler.rb +30 -28
  28. data/lib/contracts/method_reference.rb +4 -2
  29. data/lib/contracts/support.rb +14 -10
  30. data/lib/contracts/validators.rb +6 -2
  31. data/lib/contracts/version.rb +3 -1
  32. data/spec/attrs_spec.rb +119 -0
  33. data/spec/builtin_contracts_spec.rb +155 -97
  34. data/spec/contracts_spec.rb +54 -12
  35. data/spec/fixtures/fixtures.rb +49 -2
  36. data/spec/methods_spec.rb +54 -0
  37. data/spec/override_validators_spec.rb +3 -3
  38. data/spec/ruby_version_specific/contracts_spec_2.0.rb +17 -2
  39. data/spec/ruby_version_specific/contracts_spec_2.1.rb +1 -1
  40. data/spec/validators_spec.rb +1 -1
  41. metadata +22 -10
  42. data/script/cucumber +0 -5
@@ -1,3 +1,5 @@
1
+ # frozen_string_literal: true
2
+
1
3
  module Contracts
2
4
  # MethodReference represents original method reference that was
3
5
  # decorated by contracts.ruby. Used for instance methods.
@@ -39,8 +41,8 @@ module Contracts
39
41
 
40
42
  # Calls original method on specified `this` argument with
41
43
  # specified arguments `args` and block `&blk`.
42
- def send_to(this, *args, &blk)
43
- this.send(aliased_name, *args, &blk)
44
+ def send_to(this, *args, **kargs, &blk)
45
+ this.send(aliased_name, *args, **kargs, &blk)
44
46
  end
45
47
 
46
48
  private
@@ -1,18 +1,16 @@
1
+ # frozen_string_literal: true
2
+
1
3
  module Contracts
2
4
  module Support
3
5
  class << self
4
6
  def method_position(method)
5
7
  return method.method_position if method.is_a?(MethodReference)
6
8
 
7
- if RUBY_VERSION =~ /^1\.8/
8
- if method.respond_to?(:__file__)
9
- method.__file__ + ":" + method.__line__.to_s
10
- else
11
- method.inspect
12
- end
9
+ file, line = method.source_location
10
+ if file.nil? || line.nil?
11
+ ""
13
12
  else
14
- file, line = method.source_location
15
- file + ":" + line.to_s
13
+ "#{file}:#{line}"
16
14
  end
17
15
  end
18
16
 
@@ -34,8 +32,7 @@ module Contracts
34
32
  end
35
33
 
36
34
  def eigenclass_hierarchy_supported?
37
- return false if RUBY_PLATFORM == "java" && RUBY_VERSION.to_f < 2.0
38
- RUBY_VERSION.to_f > 1.8
35
+ RUBY_PLATFORM != "java" || RUBY_VERSION.to_f >= 2.0
39
36
  end
40
37
 
41
38
  def eigenclass_of(target)
@@ -47,6 +44,13 @@ module Contracts
47
44
  target <= eigenclass_of(Object)
48
45
  end
49
46
 
47
+ def indent_string(string, amount)
48
+ string.gsub(
49
+ /^(?!$)/,
50
+ (string[/^[ \t]/] || " ") * amount,
51
+ )
52
+ end
53
+
50
54
  private
51
55
 
52
56
  # Module eigenclass can be detected by its ancestor chain
@@ -1,3 +1,5 @@
1
+ # frozen_string_literal: true
2
+
1
3
  module Contracts
2
4
  module Validators
3
5
  DEFAULT_VALIDATOR_STRATEGIES = {
@@ -9,6 +11,7 @@ module Contracts
9
11
  Array => lambda do |contract|
10
12
  lambda do |arg|
11
13
  return false unless arg.is_a?(Array) && arg.length == contract.length
14
+
12
15
  arg.zip(contract).all? do |_arg, _contract|
13
16
  Contract.valid?(_arg, _contract)
14
17
  end
@@ -19,6 +22,7 @@ module Contracts
19
22
  Hash => lambda do |contract|
20
23
  lambda do |arg|
21
24
  return false unless arg.is_a?(Hash)
25
+
22
26
  contract.keys.all? do |k|
23
27
  Contract.valid?(arg[k], contract[k])
24
28
  end
@@ -59,7 +63,7 @@ module Contracts
59
63
 
60
64
  :default => lambda do |contract|
61
65
  lambda { |arg| contract == arg }
62
- end
66
+ end,
63
67
  }.freeze
64
68
 
65
69
  # Allows to override validator with custom one.
@@ -90,7 +94,7 @@ module Contracts
90
94
  else
91
95
  if contract.respond_to? :valid?
92
96
  :valid
93
- elsif klass == Class || klass == Module
97
+ elsif [Class, Module].include?(klass)
94
98
  :class
95
99
  else
96
100
  :default
@@ -1,3 +1,5 @@
1
+ # frozen_string_literal: true
2
+
1
3
  module Contracts
2
- VERSION = "0.13.0"
4
+ VERSION = "0.17"
3
5
  end
@@ -0,0 +1,119 @@
1
+ RSpec.describe "Contracts:" do
2
+ describe "Attrs:" do
3
+ class Person
4
+ include Contracts::Core
5
+ include Contracts::Attrs
6
+ include Contracts::Builtin
7
+
8
+ def initialize(name)
9
+ @name_r = name
10
+ @name_w = name
11
+ @name_rw = name
12
+
13
+ @name_r_2 = name
14
+ @name_w_2 = name
15
+ @name_rw_2 = name
16
+ end
17
+
18
+ attr_reader_with_contract :name_r, :name_r_2, String
19
+ attr_writer_with_contract :name_w, :name_w_2, String
20
+ attr_accessor_with_contract :name_rw, :name_rw_2, String
21
+ end
22
+
23
+ context "attr_reader_with_contract" do
24
+ it "getting valid type" do
25
+ expect(Person.new("bob").name_r)
26
+ .to(eq("bob"))
27
+ end
28
+
29
+ it "getting invalid type" do
30
+ expect { Person.new(1.3).name_r }
31
+ .to(raise_error(ReturnContractError))
32
+ end
33
+
34
+ it "getting valid type for second val" do
35
+ expect(Person.new("bob").name_r_2)
36
+ .to(eq("bob"))
37
+ end
38
+
39
+ it "getting invalid type for second val" do
40
+ expect { Person.new(1.3).name_r_2 }
41
+ .to(raise_error(ReturnContractError))
42
+ end
43
+
44
+ it "setting" do
45
+ expect { Person.new("bob").name_r = "alice" }
46
+ .to(raise_error(NoMethodError))
47
+ end
48
+ end
49
+
50
+ context "attr_writer_with_contract" do
51
+ it "getting" do
52
+ expect { Person.new("bob").name_w }
53
+ .to(raise_error(NoMethodError))
54
+ end
55
+
56
+ it "setting valid type" do
57
+ expect(Person.new("bob").name_w = "alice")
58
+ .to(eq("alice"))
59
+ end
60
+
61
+ it "setting invalid type" do
62
+ expect { Person.new("bob").name_w = 1.2 }
63
+ .to(raise_error(ParamContractError))
64
+ end
65
+
66
+ it "setting valid type for second val" do
67
+ expect(Person.new("bob").name_w_2 = "alice")
68
+ .to(eq("alice"))
69
+ end
70
+
71
+ it "setting invalid type for second val" do
72
+ expect { Person.new("bob").name_w_2 = 1.2 }
73
+ .to(raise_error(ParamContractError))
74
+ end
75
+ end
76
+
77
+ context "attr_accessor_with_contract" do
78
+ it "getting valid type" do
79
+ expect(Person.new("bob").name_rw)
80
+ .to(eq("bob"))
81
+ end
82
+
83
+ it "getting invalid type" do
84
+ expect { Person.new(1.2).name_rw }
85
+ .to(raise_error(ReturnContractError))
86
+ end
87
+
88
+ it "setting valid type" do
89
+ expect(Person.new("bob").name_rw = "alice")
90
+ .to(eq("alice"))
91
+ end
92
+
93
+ it "setting invalid type" do
94
+ expect { Person.new("bob").name_rw = 1.2 }
95
+ .to(raise_error(ParamContractError))
96
+ end
97
+
98
+ it "getting valid type for second val" do
99
+ expect(Person.new("bob").name_rw_2)
100
+ .to(eq("bob"))
101
+ end
102
+
103
+ it "getting invalid type for second val" do
104
+ expect { Person.new(1.2).name_rw_2 }
105
+ .to(raise_error(ReturnContractError))
106
+ end
107
+
108
+ it "setting valid type for second val" do
109
+ expect(Person.new("bob").name_rw_2 = "alice")
110
+ .to(eq("alice"))
111
+ end
112
+
113
+ it "setting invalid type for second val" do
114
+ expect { Person.new("bob").name_rw_2 = 1.2 }
115
+ .to(raise_error(ParamContractError))
116
+ end
117
+ end
118
+ end
119
+ end
@@ -3,278 +3,304 @@ RSpec.describe "Contracts:" do
3
3
  @o = GenericExample.new
4
4
  end
5
5
 
6
+ def fails(&some)
7
+ expect { some.call }.to raise_error(ContractError)
8
+ end
9
+
10
+ def passes(&some)
11
+ expect { some.call }.to_not raise_error
12
+ end
13
+
14
+ describe "DescendantOf:" do
15
+ it "should pass for Array" do
16
+ passes { @o.enumerable_descendant_test(Array) }
17
+ end
18
+
19
+ it "should pass for a hash" do
20
+ passes { @o.enumerable_descendant_test(Hash) }
21
+ end
22
+
23
+ it "should fail for a number class" do
24
+ fails { @o.enumerable_descendant_test(Integer) }
25
+ end
26
+
27
+ it "should fail for a non-class" do
28
+ fails { @o.enumerable_descendant_test(1) }
29
+ end
30
+ end
31
+
6
32
  describe "Num:" do
7
33
  it "should pass for Fixnums" do
8
- expect { @o.double(2) }.to_not raise_error
34
+ passes { @o.double(2) }
9
35
  end
10
36
 
11
37
  it "should pass for Floats" do
12
- expect { @o.double(2.2) }.to_not raise_error
38
+ passes { @o.double(2.2) }
13
39
  end
14
40
 
15
41
  it "should fail for nil and other data types" do
16
- expect { @o.double(nil) }.to raise_error(ContractError)
17
- expect { @o.double(:x) }.to raise_error(ContractError)
18
- expect { @o.double("x") }.to raise_error(ContractError)
19
- expect { @o.double(/x/) }.to raise_error(ContractError)
42
+ fails { @o.double(nil) }
43
+ fails { @o.double(:x) }
44
+ fails { @o.double("x") }
45
+ fails { @o.double(/x/) }
20
46
  end
21
47
  end
22
48
 
23
49
  describe "Pos:" do
24
50
  it "should pass for positive numbers" do
25
- expect { @o.pos_test(1) }.to_not raise_error
26
- expect { @o.pos_test(1.6) }.to_not raise_error
51
+ passes { @o.pos_test(1) }
52
+ passes { @o.pos_test(1.6) }
27
53
  end
28
54
 
29
55
  it "should fail for 0" do
30
- expect { @o.pos_test(0) }.to raise_error(ContractError)
56
+ fails { @o.pos_test(0) }
31
57
  end
32
58
 
33
59
  it "should fail for negative numbers" do
34
- expect { @o.pos_test(-1) }.to raise_error(ContractError)
35
- expect { @o.pos_test(-1.6) }.to raise_error(ContractError)
60
+ fails { @o.pos_test(-1) }
61
+ fails { @o.pos_test(-1.6) }
36
62
  end
37
63
 
38
64
  it "should fail for nil and other data types" do
39
- expect { @o.pos_test(nil) }.to raise_error(ContractError)
40
- expect { @o.pos_test(:x) }.to raise_error(ContractError)
41
- expect { @o.pos_test("x") }.to raise_error(ContractError)
42
- expect { @o.pos_test(/x/) }.to raise_error(ContractError)
65
+ fails { @o.pos_test(nil) }
66
+ fails { @o.pos_test(:x) }
67
+ fails { @o.pos_test("x") }
68
+ fails { @o.pos_test(/x/) }
43
69
  end
44
70
  end
45
71
 
46
72
  describe "Neg:" do
47
73
  it "should pass for negative numbers" do
48
- expect { @o.neg_test(-1) }.to_not raise_error
49
- expect { @o.neg_test(-1.6) }.to_not raise_error
74
+ passes { @o.neg_test(-1) }
75
+ passes { @o.neg_test(-1.6) }
50
76
  end
51
77
 
52
78
  it "should fail for 0" do
53
- expect { @o.neg_test(0) }.to raise_error(ContractError)
79
+ fails { @o.neg_test(0) }
54
80
  end
55
81
 
56
82
  it "should fail for positive numbers" do
57
- expect { @o.neg_test(1) }.to raise_error(ContractError)
58
- expect { @o.neg_test(1.6) }.to raise_error(ContractError)
83
+ fails { @o.neg_test(1) }
84
+ fails { @o.neg_test(1.6) }
59
85
  end
60
86
 
61
87
  it "should fail for nil and other data types" do
62
- expect { @o.neg_test(nil) }.to raise_error(ContractError)
63
- expect { @o.neg_test(:x) }.to raise_error(ContractError)
64
- expect { @o.neg_test("x") }.to raise_error(ContractError)
65
- expect { @o.neg_test(/x/) }.to raise_error(ContractError)
88
+ fails { @o.neg_test(nil) }
89
+ fails { @o.neg_test(:x) }
90
+ fails { @o.neg_test("x") }
91
+ fails { @o.neg_test(/x/) }
66
92
  end
67
93
  end
68
94
 
69
95
  describe "Nat:" do
70
96
  it "should pass for 0" do
71
- expect { @o.nat_test(0) }.to_not raise_error
97
+ passes { @o.nat_test(0) }
72
98
  end
73
99
 
74
100
  it "should pass for positive whole numbers" do
75
- expect { @o.nat_test(1) }.to_not raise_error
101
+ passes { @o.nat_test(1) }
76
102
  end
77
103
 
78
104
  it "should fail for positive non-whole numbers" do
79
- expect { @o.nat_test(1.5) }.to raise_error(ContractError)
105
+ fails { @o.nat_test(1.5) }
80
106
  end
81
107
 
82
108
  it "should fail for negative numbers" do
83
- expect { @o.nat_test(-1) }.to raise_error(ContractError)
84
- expect { @o.nat_test(-1.6) }.to raise_error(ContractError)
109
+ fails { @o.nat_test(-1) }
110
+ fails { @o.nat_test(-1.6) }
85
111
  end
86
112
 
87
113
  it "should fail for nil and other data types" do
88
- expect { @o.nat_test(nil) }.to raise_error(ContractError)
89
- expect { @o.nat_test(:x) }.to raise_error(ContractError)
90
- expect { @o.nat_test("x") }.to raise_error(ContractError)
91
- expect { @o.nat_test(/x/) }.to raise_error(ContractError)
114
+ fails { @o.nat_test(nil) }
115
+ fails { @o.nat_test(:x) }
116
+ fails { @o.nat_test("x") }
117
+ fails { @o.nat_test(/x/) }
92
118
  end
93
119
  end
94
120
 
95
121
  describe "Any:" do
96
122
  it "should pass for numbers" do
97
- expect { @o.show(1) }.to_not raise_error
123
+ passes { @o.show(1) }
98
124
  end
99
125
  it "should pass for strings" do
100
- expect { @o.show("bad") }.to_not raise_error
126
+ passes { @o.show("bad") }
101
127
  end
102
128
  it "should pass for procs" do
103
- expect { @o.show(lambda {}) }.to_not raise_error
129
+ passes { @o.show(lambda {}) }
104
130
  end
105
131
  it "should pass for nil" do
106
- expect { @o.show(nil) }.to_not raise_error
132
+ passes { @o.show(nil) }
107
133
  end
108
134
  end
109
135
 
110
136
  describe "None:" do
111
137
  it "should fail for numbers" do
112
- expect { @o.fail_all(1) }.to raise_error(ContractError)
138
+ fails { @o.fail_all(1) }
113
139
  end
114
140
  it "should fail for strings" do
115
- expect { @o.fail_all("bad") }.to raise_error(ContractError)
141
+ fails { @o.fail_all("bad") }
116
142
  end
117
143
  it "should fail for procs" do
118
- expect { @o.fail_all(lambda {}) }.to raise_error(ContractError)
144
+ fails { @o.fail_all(lambda {}) }
119
145
  end
120
146
  it "should fail for nil" do
121
- expect { @o.fail_all(nil) }.to raise_error(ContractError)
147
+ fails { @o.fail_all(nil) }
122
148
  end
123
149
  end
124
150
 
125
151
  describe "Or:" do
126
152
  it "should pass for nums" do
127
- expect { @o.num_or_string(1) }.to_not raise_error
153
+ passes { @o.num_or_string(1) }
128
154
  end
129
155
 
130
156
  it "should pass for strings" do
131
- expect { @o.num_or_string("bad") }.to_not raise_error
157
+ passes { @o.num_or_string("bad") }
132
158
  end
133
159
 
134
160
  it "should fail for nil" do
135
- expect { @o.num_or_string(nil) }.to raise_error(ContractError)
161
+ fails { @o.num_or_string(nil) }
136
162
  end
137
163
  end
138
164
 
139
165
  describe "Xor:" do
140
166
  it "should pass for an object with a method :good" do
141
- expect { @o.xor_test(A.new) }.to_not raise_error
167
+ passes { @o.xor_test(A.new) }
142
168
  end
143
169
 
144
170
  it "should pass for an object with a method :bad" do
145
- expect { @o.xor_test(B.new) }.to_not raise_error
171
+ passes { @o.xor_test(B.new) }
146
172
  end
147
173
 
148
174
  it "should fail for an object with neither method" do
149
- expect { @o.xor_test(1) }.to raise_error(ContractError)
175
+ fails { @o.xor_test(1) }
150
176
  end
151
177
 
152
178
  it "should fail for an object with both methods :good and :bad" do
153
- expect { @o.xor_test(F.new) }.to raise_error(ContractError)
179
+ fails { @o.xor_test(F.new) }
154
180
  end
155
181
  end
156
182
 
157
183
  describe "And:" do
158
184
  it "should pass for an object of class A that has a method :good" do
159
- expect { @o.and_test(A.new) }.to_not raise_error
185
+ passes { @o.and_test(A.new) }
160
186
  end
161
187
 
162
188
  it "should fail for an object that has a method :good but isn't of class A" do
163
- expect { @o.and_test(F.new) }.to raise_error(ContractError)
189
+ fails { @o.and_test(F.new) }
164
190
  end
165
191
  end
166
192
 
167
193
  describe "Enum:" do
168
194
  it "should pass for an object that is included" do
169
- expect { @o.enum_test(:a) }.to_not raise_error
195
+ passes { @o.enum_test(:a) }
170
196
  end
171
197
 
172
198
  it "should fail for an object that is not included" do
173
- expect { @o.enum_test(:z) }.to raise_error(ContractError)
199
+ fails { @o.enum_test(:z) }
174
200
  end
175
201
  end
176
202
 
177
203
  describe "RespondTo:" do
178
204
  it "should pass for an object that responds to :good" do
179
- expect { @o.responds_test(A.new) }.to_not raise_error
205
+ passes { @o.responds_test(A.new) }
180
206
  end
181
207
 
182
208
  it "should fail for an object that doesn't respond to :good" do
183
- expect { @o.responds_test(B.new) }.to raise_error(ContractError)
209
+ fails { @o.responds_test(B.new) }
184
210
  end
185
211
  end
186
212
 
187
213
  describe "Send:" do
188
214
  it "should pass for an object that returns true for method :good" do
189
- expect { @o.send_test(A.new) }.to_not raise_error
215
+ passes { @o.send_test(A.new) }
190
216
  end
191
217
 
192
218
  it "should fail for an object that returns false for method :good" do
193
- expect { @o.send_test(F.new) }.to raise_error(ContractError)
219
+ fails { @o.send_test(F.new) }
194
220
  end
195
221
  end
196
222
 
197
223
  describe "Exactly:" do
198
224
  it "should pass for an object that is exactly a Parent" do
199
- expect { @o.exactly_test(Parent.new) }.to_not raise_error
225
+ passes { @o.exactly_test(Parent.new) }
200
226
  end
201
227
 
202
228
  it "should fail for an object that inherits from Parent" do
203
- expect { @o.exactly_test(Child.new) }.to raise_error(ContractError)
229
+ fails { @o.exactly_test(Child.new) }
204
230
  end
205
231
 
206
232
  it "should fail for an object that is not related to Parent at all" do
207
- expect { @o.exactly_test(A.new) }.to raise_error(ContractError)
233
+ fails { @o.exactly_test(A.new) }
208
234
  end
209
235
  end
210
236
 
211
237
  describe "Eq:" do
212
238
  it "should pass for a class" do
213
- expect { @o.eq_class_test(Foo) }
239
+ passes { @o.eq_class_test(Foo) }
214
240
  end
215
241
 
216
242
  it "should pass for a module" do
217
- expect { @o.eq_module_test(Bar) }
243
+ passes { @o.eq_module_test(Bar) }
218
244
  end
219
245
 
220
246
  it "should pass for other values" do
221
- expect { @o.eq_value_test(Baz) }
247
+ passes { @o.eq_value_test(Baz) }
222
248
  end
223
249
 
224
250
  it "should fail when not equal" do
225
- expect { @o.eq_class_test(Bar) }.to raise_error(ContractError)
251
+ fails { @o.eq_class_test(Bar) }
226
252
  end
227
253
 
228
254
  it "should fail when given instance of class" do
229
- expect { @o.eq_class_test(Foo.new) }.to raise_error(ContractError)
255
+ fails { @o.eq_class_test(Foo.new) }
230
256
  end
231
257
  end
232
258
 
233
259
  describe "Not:" do
234
260
  it "should pass for an argument that isn't nil" do
235
- expect { @o.not_nil(1) }.to_not raise_error
261
+ passes { @o.not_nil(1) }
236
262
  end
237
263
 
238
264
  it "should fail for nil" do
239
- expect { @o.not_nil(nil) }.to raise_error(ContractError)
265
+ fails { @o.not_nil(nil) }
240
266
  end
241
267
  end
242
268
 
243
269
  describe "ArrayOf:" do
244
270
  it "should pass for an array of nums" do
245
- expect { @o.product([1, 2, 3]) }.to_not raise_error
271
+ passes { @o.product([1, 2, 3]) }
246
272
  end
247
273
 
248
274
  it "should fail for an array with one non-num" do
249
- expect { @o.product([1, 2, 3, "bad"]) }.to raise_error(ContractError)
275
+ fails { @o.product([1, 2, 3, "bad"]) }
250
276
  end
251
277
 
252
278
  it "should fail for a non-array" do
253
- expect { @o.product(1) }.to raise_error(ContractError)
279
+ fails { @o.product(1) }
254
280
  end
255
281
  end
256
282
 
257
283
  describe "RangeOf:" do
258
284
  require "date"
259
285
  it "should pass for a range of nums" do
260
- expect { @o.first_in_range_num(3..10) }.to_not raise_error
286
+ passes { @o.first_in_range_num(3..10) }
261
287
  end
262
288
 
263
289
  it "should pass for a range of dates" do
264
290
  d1 = Date.today
265
291
  d2 = d1 + 18
266
- expect { @o.first_in_range_date(d1..d2) }.to_not raise_error
292
+ passes { @o.first_in_range_date(d1..d2) }
267
293
  end
268
294
 
269
295
  it "should fail for a non-range" do
270
- expect { @o.first_in_range_num("foo") }.to raise_error(ContractError)
271
- expect { @o.first_in_range_num(:foo) }.to raise_error(ContractError)
272
- expect { @o.first_in_range_num(5) }.to raise_error(ContractError)
273
- expect { @o.first_in_range_num(nil) }.to raise_error(ContractError)
296
+ fails { @o.first_in_range_num("foo") }
297
+ fails { @o.first_in_range_num(:foo) }
298
+ fails { @o.first_in_range_num(5) }
299
+ fails { @o.first_in_range_num(nil) }
274
300
  end
275
301
 
276
302
  it "should fail for a range with incorrect data type" do
277
- expect { @o.first_in_range_num("a".."z") }.to raise_error(ContractError)
303
+ fails { @o.first_in_range_num("a".."z") }
278
304
  end
279
305
 
280
306
  it "should fail for a badly-defined range" do
@@ -284,8 +310,8 @@ RSpec.describe "Contracts:" do
284
310
  # This test guards against ranges with inconsistent data types.
285
311
  begin
286
312
  d1 = Date.today
287
- expect { @o.first_in_range_date(d1..10).to raise_error(ContractError) }
288
- expect { @o.first_in_range_num(d1..10).to raise_error(ContractError) }
313
+ fails { @o.first_in_range_date(d1..10) }
314
+ fails { @o.first_in_range_num(d1..10) }
289
315
  rescue ArgumentError
290
316
  # If Ruby doesn't like the range, we ignore the test.
291
317
  :nop
@@ -295,26 +321,26 @@ RSpec.describe "Contracts:" do
295
321
 
296
322
  describe "SetOf:" do
297
323
  it "should pass for a set of nums" do
298
- expect { @o.product_from_set(Set.new([1, 2, 3])) }.to_not raise_error
324
+ passes { @o.product_from_set(Set.new([1, 2, 3])) }
299
325
  end
300
326
 
301
327
  it "should fail for an array with one non-num" do
302
- expect { @o.product_from_set(Set.new([1, 2, 3, "bad"])) }.to raise_error(ContractError)
328
+ fails { @o.product_from_set(Set.new([1, 2, 3, "bad"])) }
303
329
  end
304
330
 
305
331
  it "should fail for a non-array" do
306
- expect { @o.product_from_set(1) }.to raise_error(ContractError)
332
+ fails { @o.product_from_set(1) }
307
333
  end
308
334
  end
309
335
 
310
336
  describe "Bool:" do
311
337
  it "should pass for an argument that is a boolean" do
312
- expect { @o.bool_test(true) }.to_not raise_error
313
- expect { @o.bool_test(false) }.to_not raise_error
338
+ passes { @o.bool_test(true) }
339
+ passes { @o.bool_test(false) }
314
340
  end
315
341
 
316
342
  it "should fail for nil" do
317
- expect { @o.bool_test(nil) }.to raise_error(ContractError)
343
+ fails { @o.bool_test(nil) }
318
344
  end
319
345
  end
320
346
 
@@ -328,27 +354,27 @@ RSpec.describe "Contracts:" do
328
354
  end
329
355
 
330
356
  it "should fail for strings" do
331
- expect { @o.maybe_double("foo") }.to raise_error(ContractError)
357
+ fails { @o.maybe_double("foo") }
332
358
  end
333
359
  end
334
360
 
335
361
  describe "KeywordArgs:" do
336
362
  it "should pass for exact correct input" do
337
- expect { @o.person_keywordargs(:name => "calvin", :age => 10) }.to_not raise_error
363
+ passes { @o.person_keywordargs(:name => "calvin", :age => 10) }
338
364
  end
339
365
 
340
366
  it "should fail if some keys don't have contracts" do
341
- expect { @o.person_keywordargs(:name => "calvin", :age => 10, :foo => "bar") }.to raise_error(ContractError)
367
+ fails { @o.person_keywordargs(:name => "calvin", :age => 10, :foo => "bar") }
342
368
  end
343
369
 
344
370
  it "should fail if a key with a contract on it isn't provided" do
345
- expect { @o.person_keywordargs(:name => "calvin") }.to raise_error(ContractError)
371
+ fails { @o.person_keywordargs(:name => "calvin") }
346
372
  end
347
373
 
348
374
  it "should fail for incorrect input" do
349
- expect { @o.person_keywordargs(:name => 50, :age => 10) }.to raise_error(ContractError)
350
- expect { @o.hash_keywordargs(:hash => nil) }.to raise_error(ContractError)
351
- expect { @o.hash_keywordargs(:hash => 1) }.to raise_error(ContractError)
375
+ fails { @o.person_keywordargs(:name => 50, :age => 10) }
376
+ fails { @o.hash_keywordargs(:hash => nil) }
377
+ fails { @o.hash_keywordargs(:hash => 1) }
352
378
  end
353
379
  end
354
380
 
@@ -375,15 +401,15 @@ RSpec.describe "Contracts:" do
375
401
  end
376
402
 
377
403
  context "given a fulfilled contract" do
378
- it { expect(@o.gives_max_value(:panda => 1, :bamboo => 2)).to eq(2) }
379
- it { expect(@o.pretty_gives_max_value(:panda => 1, :bamboo => 2)).to eq(2) }
404
+ it { expect(@o.gives_max_value({ :panda => 1, :bamboo => 2 })).to eq(2) }
405
+ it { expect(@o.pretty_gives_max_value({ :panda => 1, :bamboo => 2 })).to eq(2) }
380
406
  end
381
407
 
382
408
  context "given an unfulfilled contract" do
383
- it { expect { @o.gives_max_value(:panda => "1", :bamboo => "2") }.to raise_error(ContractError) }
384
- it { expect { @o.gives_max_value(nil) }.to raise_error(ContractError) }
385
- it { expect { @o.gives_max_value(1) }.to raise_error(ContractError) }
386
- it { expect { @o.pretty_gives_max_value(:panda => "1", :bamboo => "2") }.to raise_error(ContractError) }
409
+ it { fails { @o.gives_max_value({ :panda => "1", :bamboo => "2" }) } }
410
+ it { fails { @o.gives_max_value(nil) } }
411
+ it { fails { @o.gives_max_value(1) } }
412
+ it { fails { @o.pretty_gives_max_value({ :panda => "1", :bamboo => "2" }) } }
387
413
  end
388
414
 
389
415
  describe "#to_s" do
@@ -396,4 +422,36 @@ RSpec.describe "Contracts:" do
396
422
  end
397
423
  end
398
424
  end
425
+
426
+ describe "StrictHash:" do
427
+ context "when given an exact correct input" do
428
+ it "does not raise an error" do
429
+ passes { @o.strict_person({ :name => "calvin", :age => 10 }) }
430
+ end
431
+ end
432
+
433
+ context "when given an input with correct keys but wrong types" do
434
+ it "raises an error" do
435
+ fails { @o.strict_person({ :name => "calvin", :age => "10" }) }
436
+ end
437
+ end
438
+
439
+ context "when given an input with missing keys" do
440
+ it "raises an error" do
441
+ fails { @o.strict_person({ :name => "calvin" }) }
442
+ end
443
+ end
444
+
445
+ context "when given an input with extra keys" do
446
+ it "raises an error" do
447
+ fails { @o.strict_person({ :name => "calvin", :age => 10, :soft => true }) }
448
+ end
449
+ end
450
+
451
+ context "when given not a hash" do
452
+ it "raises an error" do
453
+ fails { @o.strict_person(1337) }
454
+ end
455
+ end
456
+ end
399
457
  end