contracts 0.12.0 → 0.16.1
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 +5 -13
- data/.github/workflows/code_style_checks.yaml +36 -0
- data/.github/workflows/tests.yaml +47 -0
- data/CHANGELOG.markdown +57 -6
- data/Gemfile +2 -2
- data/LICENSE +23 -0
- data/README.md +14 -6
- data/Rakefile +3 -6
- data/TUTORIAL.md +55 -26
- data/contracts.gemspec +6 -1
- data/dependabot.yml +20 -0
- data/features/basics/pretty-print.feature +241 -0
- data/features/builtin_contracts/args.feature +80 -1
- data/features/builtin_contracts/int.feature +93 -0
- data/features/builtin_contracts/nat_pos.feature +119 -0
- data/lib/contracts.rb +48 -12
- data/lib/contracts/attrs.rb +24 -0
- data/lib/contracts/builtin_contracts.rb +60 -1
- data/lib/contracts/call_with.rb +22 -11
- data/lib/contracts/core.rb +0 -2
- data/lib/contracts/decorators.rb +5 -0
- data/lib/contracts/engine/eigenclass.rb +4 -0
- data/lib/contracts/engine/target.rb +2 -0
- data/lib/contracts/formatters.rb +4 -2
- data/lib/contracts/method_handler.rb +14 -22
- data/lib/contracts/support.rb +11 -9
- data/lib/contracts/version.rb +1 -1
- data/spec/attrs_spec.rb +119 -0
- data/spec/builtin_contracts_spec.rb +157 -95
- data/spec/contracts_spec.rb +50 -29
- data/spec/fixtures/fixtures.rb +58 -0
- data/spec/methods_spec.rb +54 -0
- data/spec/override_validators_spec.rb +1 -1
- data/spec/ruby_version_specific/contracts_spec_2.0.rb +15 -0
- data/spec/ruby_version_specific/contracts_spec_2.1.rb +1 -1
- data/spec/validators_spec.rb +1 -1
- metadata +25 -14
- data/script/cucumber +0 -5
@@ -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
|
-
|
34
|
+
passes { @o.double(2) }
|
9
35
|
end
|
10
36
|
|
11
37
|
it "should pass for Floats" do
|
12
|
-
|
38
|
+
passes { @o.double(2.2) }
|
13
39
|
end
|
14
40
|
|
15
41
|
it "should fail for nil and other data types" do
|
16
|
-
|
17
|
-
|
18
|
-
|
19
|
-
|
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
|
-
|
26
|
-
|
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
|
-
|
56
|
+
fails { @o.pos_test(0) }
|
31
57
|
end
|
32
58
|
|
33
59
|
it "should fail for negative numbers" do
|
34
|
-
|
35
|
-
|
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
|
-
|
40
|
-
|
41
|
-
|
42
|
-
|
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
|
-
|
49
|
-
|
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
|
-
|
79
|
+
fails { @o.neg_test(0) }
|
54
80
|
end
|
55
81
|
|
56
82
|
it "should fail for positive numbers" do
|
57
|
-
|
58
|
-
|
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
|
-
|
63
|
-
|
64
|
-
|
65
|
-
|
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
|
-
|
97
|
+
passes { @o.nat_test(0) }
|
72
98
|
end
|
73
99
|
|
74
100
|
it "should pass for positive whole numbers" do
|
75
|
-
|
101
|
+
passes { @o.nat_test(1) }
|
76
102
|
end
|
77
103
|
|
78
104
|
it "should fail for positive non-whole numbers" do
|
79
|
-
|
105
|
+
fails { @o.nat_test(1.5) }
|
80
106
|
end
|
81
107
|
|
82
108
|
it "should fail for negative numbers" do
|
83
|
-
|
84
|
-
|
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
|
-
|
89
|
-
|
90
|
-
|
91
|
-
|
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
|
-
|
123
|
+
passes { @o.show(1) }
|
98
124
|
end
|
99
125
|
it "should pass for strings" do
|
100
|
-
|
126
|
+
passes { @o.show("bad") }
|
101
127
|
end
|
102
128
|
it "should pass for procs" do
|
103
|
-
|
129
|
+
passes { @o.show(lambda {}) }
|
104
130
|
end
|
105
131
|
it "should pass for nil" do
|
106
|
-
|
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
|
-
|
138
|
+
fails { @o.fail_all(1) }
|
113
139
|
end
|
114
140
|
it "should fail for strings" do
|
115
|
-
|
141
|
+
fails { @o.fail_all("bad") }
|
116
142
|
end
|
117
143
|
it "should fail for procs" do
|
118
|
-
|
144
|
+
fails { @o.fail_all(lambda {}) }
|
119
145
|
end
|
120
146
|
it "should fail for nil" do
|
121
|
-
|
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
|
-
|
153
|
+
passes { @o.num_or_string(1) }
|
128
154
|
end
|
129
155
|
|
130
156
|
it "should pass for strings" do
|
131
|
-
|
157
|
+
passes { @o.num_or_string("bad") }
|
132
158
|
end
|
133
159
|
|
134
160
|
it "should fail for nil" do
|
135
|
-
|
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
|
-
|
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
|
-
|
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
|
-
|
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
|
-
|
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
|
-
|
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
|
-
|
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
|
-
|
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
|
-
|
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
|
-
|
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
|
-
|
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
|
-
|
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
|
-
|
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
|
-
|
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
|
-
|
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
|
-
|
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
|
-
|
239
|
+
passes { @o.eq_class_test(Foo) }
|
214
240
|
end
|
215
241
|
|
216
242
|
it "should pass for a module" do
|
217
|
-
|
243
|
+
passes { @o.eq_module_test(Bar) }
|
218
244
|
end
|
219
245
|
|
220
246
|
it "should pass for other values" do
|
221
|
-
|
247
|
+
passes { @o.eq_value_test(Baz) }
|
222
248
|
end
|
223
249
|
|
224
250
|
it "should fail when not equal" do
|
225
|
-
|
251
|
+
fails { @o.eq_class_test(Bar) }
|
226
252
|
end
|
227
253
|
|
228
254
|
it "should fail when given instance of class" do
|
229
|
-
|
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
|
-
|
261
|
+
passes { @o.not_nil(1) }
|
236
262
|
end
|
237
263
|
|
238
264
|
it "should fail for nil" do
|
239
|
-
|
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
|
-
|
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
|
-
|
275
|
+
fails { @o.product([1, 2, 3, "bad"]) }
|
250
276
|
end
|
251
277
|
|
252
278
|
it "should fail for a non-array" do
|
253
|
-
|
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
|
-
|
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
|
-
|
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
|
-
|
271
|
-
|
272
|
-
|
273
|
-
|
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
|
-
|
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
|
-
|
288
|
-
|
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
|
-
|
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
|
-
|
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
|
-
|
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
|
-
|
313
|
-
|
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
|
-
|
343
|
+
fails { @o.bool_test(nil) }
|
318
344
|
end
|
319
345
|
end
|
320
346
|
|
@@ -328,27 +354,31 @@ RSpec.describe "Contracts:" do
|
|
328
354
|
end
|
329
355
|
|
330
356
|
it "should fail for strings" do
|
331
|
-
|
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
|
-
|
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
|
-
|
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
|
-
|
371
|
+
fails { @o.person_keywordargs(:name => "calvin") }
|
346
372
|
end
|
347
373
|
|
348
374
|
it "should fail for incorrect input" do
|
349
|
-
|
350
|
-
|
351
|
-
|
375
|
+
fails { @o.person_keywordargs(:name => 50, :age => 10) }
|
376
|
+
fails { @o.hash_keywordargs(:hash => nil) }
|
377
|
+
fails { @o.hash_keywordargs(:hash => 1) }
|
378
|
+
end
|
379
|
+
|
380
|
+
it "should pass if a method is overloaded with non-KeywordArgs" do
|
381
|
+
passes { @o.person_keywordargs("name", 10) }
|
352
382
|
end
|
353
383
|
end
|
354
384
|
|
@@ -380,10 +410,10 @@ RSpec.describe "Contracts:" do
|
|
380
410
|
end
|
381
411
|
|
382
412
|
context "given an unfulfilled contract" do
|
383
|
-
it {
|
384
|
-
it {
|
385
|
-
it {
|
386
|
-
it {
|
413
|
+
it { fails { @o.gives_max_value(:panda => "1", :bamboo => "2") } }
|
414
|
+
it { fails { @o.gives_max_value(nil) } }
|
415
|
+
it { fails { @o.gives_max_value(1) } }
|
416
|
+
it { fails { @o.pretty_gives_max_value(:panda => "1", :bamboo => "2") } }
|
387
417
|
end
|
388
418
|
|
389
419
|
describe "#to_s" do
|
@@ -396,4 +426,36 @@ RSpec.describe "Contracts:" do
|
|
396
426
|
end
|
397
427
|
end
|
398
428
|
end
|
429
|
+
|
430
|
+
describe "StrictHash:" do
|
431
|
+
context "when given an exact correct input" do
|
432
|
+
it "does not raise an error" do
|
433
|
+
passes { @o.strict_person(:name => "calvin", :age => 10) }
|
434
|
+
end
|
435
|
+
end
|
436
|
+
|
437
|
+
context "when given an input with correct keys but wrong types" do
|
438
|
+
it "raises an error" do
|
439
|
+
fails { @o.strict_person(:name => "calvin", :age => "10") }
|
440
|
+
end
|
441
|
+
end
|
442
|
+
|
443
|
+
context "when given an input with missing keys" do
|
444
|
+
it "raises an error" do
|
445
|
+
fails { @o.strict_person(:name => "calvin") }
|
446
|
+
end
|
447
|
+
end
|
448
|
+
|
449
|
+
context "when given an input with extra keys" do
|
450
|
+
it "raises an error" do
|
451
|
+
fails { @o.strict_person(:name => "calvin", :age => 10, :soft => true) }
|
452
|
+
end
|
453
|
+
end
|
454
|
+
|
455
|
+
context "when given not a hash" do
|
456
|
+
it "raises an error" do
|
457
|
+
fails { @o.strict_person(1337) }
|
458
|
+
end
|
459
|
+
end
|
460
|
+
end
|
399
461
|
end
|