contracts 0.7 → 0.8
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 +4 -4
- data/Gemfile +1 -0
- data/Gemfile.lock +16 -1
- data/README.md +12 -4
- data/TUTORIAL.md +67 -18
- data/benchmarks/bench.rb +20 -20
- data/benchmarks/invariants.rb +28 -18
- data/benchmarks/io.rb +62 -0
- data/benchmarks/wrap_test.rb +11 -13
- data/contracts.gemspec +1 -1
- data/lib/contracts.rb +167 -109
- data/lib/contracts/builtin_contracts.rb +132 -84
- data/lib/contracts/decorators.rb +41 -53
- data/lib/contracts/eigenclass.rb +3 -7
- data/lib/contracts/errors.rb +1 -1
- data/lib/contracts/formatters.rb +134 -0
- data/lib/contracts/invariants.rb +7 -8
- data/lib/contracts/method_reference.rb +33 -9
- data/lib/contracts/support.rb +9 -3
- data/lib/contracts/testable.rb +6 -6
- data/lib/contracts/version.rb +1 -1
- data/script/rubocop.rb +5 -0
- data/spec/builtin_contracts_spec.rb +57 -9
- data/spec/contracts_spec.rb +182 -53
- data/spec/fixtures/fixtures.rb +130 -9
- data/spec/invariants_spec.rb +0 -2
- data/spec/module_spec.rb +2 -2
- data/spec/ruby_version_specific/contracts_spec_1.9.rb +18 -0
- data/spec/ruby_version_specific/contracts_spec_2.0.rb +22 -0
- data/spec/ruby_version_specific/contracts_spec_2.1.rb +55 -0
- data/spec/spec_helper.rb +9 -2
- data/spec/support.rb +4 -0
- data/spec/support_spec.rb +21 -0
- metadata +11 -3
- data/lib/contracts/core_ext.rb +0 -15
data/benchmarks/wrap_test.rb
CHANGED
@@ -1,4 +1,4 @@
|
|
1
|
-
require
|
1
|
+
require "benchmark"
|
2
2
|
|
3
3
|
module Wrapper
|
4
4
|
def self.extended(klass)
|
@@ -26,9 +26,9 @@ module Wrapper
|
|
26
26
|
end
|
27
27
|
|
28
28
|
class NotWrapped
|
29
|
-
def add a, b
|
30
|
-
|
31
|
-
end
|
29
|
+
def add a, b
|
30
|
+
a + b
|
31
|
+
end
|
32
32
|
end
|
33
33
|
|
34
34
|
class Wrapped
|
@@ -38,22 +38,20 @@ class Wrapped
|
|
38
38
|
end
|
39
39
|
end
|
40
40
|
|
41
|
-
|
42
41
|
w = Wrapped.new
|
43
42
|
nw = NotWrapped.new
|
44
|
-
#p w.add(1, 4)
|
45
|
-
#exit
|
43
|
+
# p w.add(1, 4)
|
44
|
+
# exit
|
46
45
|
# 30 is the width of the output column
|
47
46
|
Benchmark.bm 30 do |x|
|
48
|
-
x.report
|
49
|
-
|
47
|
+
x.report "wrapped" do
|
48
|
+
100_000.times do |_|
|
50
49
|
w.add(rand(1000), rand(1000))
|
51
50
|
end
|
52
51
|
end
|
53
|
-
x.report
|
54
|
-
|
55
|
-
nw.add(rand(1000), rand(1000))
|
52
|
+
x.report "not wrapped" do
|
53
|
+
100_000.times do |_|
|
54
|
+
nw.add(rand(1000), rand(1000))
|
56
55
|
end
|
57
56
|
end
|
58
57
|
end
|
59
|
-
|
data/contracts.gemspec
CHANGED
data/lib/contracts.rb
CHANGED
@@ -1,12 +1,12 @@
|
|
1
|
-
require
|
2
|
-
require
|
3
|
-
require
|
4
|
-
require
|
5
|
-
require
|
6
|
-
require
|
7
|
-
require
|
8
|
-
require
|
9
|
-
require
|
1
|
+
require "contracts/builtin_contracts"
|
2
|
+
require "contracts/decorators"
|
3
|
+
require "contracts/eigenclass"
|
4
|
+
require "contracts/errors"
|
5
|
+
require "contracts/formatters"
|
6
|
+
require "contracts/invariants"
|
7
|
+
require "contracts/method_reference"
|
8
|
+
require "contracts/modules"
|
9
|
+
require "contracts/support"
|
10
10
|
|
11
11
|
module Contracts
|
12
12
|
def self.included(base)
|
@@ -26,7 +26,7 @@ module Contracts
|
|
26
26
|
|
27
27
|
base.instance_eval do
|
28
28
|
def functype(funcname)
|
29
|
-
contracts =
|
29
|
+
contracts = decorated_methods[:class_methods][funcname]
|
30
30
|
if contracts.nil?
|
31
31
|
"No contract for #{self}.#{funcname}"
|
32
32
|
else
|
@@ -39,6 +39,13 @@ module Contracts
|
|
39
39
|
unless base.instance_of?(Module)
|
40
40
|
def Contract(*args)
|
41
41
|
return if ENV["NO_CONTRACTS"]
|
42
|
+
if self.class == Module
|
43
|
+
puts %{
|
44
|
+
Warning: You have added a Contract on a module function
|
45
|
+
without including Contracts::Modules. Your Contract will
|
46
|
+
just be ignored. Please include Contracts::Modules into
|
47
|
+
your module.}
|
48
|
+
end
|
42
49
|
self.class.Contract(*args)
|
43
50
|
end
|
44
51
|
end
|
@@ -65,28 +72,51 @@ class Contract < Contracts::Decorator
|
|
65
72
|
# Default implementation of failure_callback. Provided as a block to be able
|
66
73
|
# to monkey patch #failure_callback only temporary and then switch it back.
|
67
74
|
# First important usage - for specs.
|
68
|
-
DEFAULT_FAILURE_CALLBACK =
|
69
|
-
|
75
|
+
DEFAULT_FAILURE_CALLBACK = proc do |data|
|
76
|
+
fail data[:contracts].failure_exception.new(failure_msg(data), data)
|
70
77
|
end
|
71
78
|
|
72
79
|
attr_reader :args_contracts, :ret_contract, :klass, :method
|
73
|
-
# decorator_name :contract
|
74
80
|
def initialize(klass, method, *contracts)
|
75
81
|
if contracts[-1].is_a? Hash
|
76
82
|
# internally we just convert that return value syntax back to an array
|
77
83
|
@args_contracts = contracts[0, contracts.size - 1] + contracts[-1].keys
|
78
84
|
@ret_contract = contracts[-1].values[0]
|
79
|
-
@args_validators = @args_contracts.map do |contract|
|
80
|
-
Contract.make_validator(contract)
|
81
|
-
end
|
82
|
-
@ret_validator = Contract.make_validator(@ret_contract)
|
83
85
|
else
|
84
|
-
fail
|
86
|
+
fail %{
|
87
|
+
It looks like your contract for #{method} doesn't have a return value.
|
88
|
+
A contract should be written as `Contract arg1, arg2 => return_value`.
|
89
|
+
}.strip
|
90
|
+
end
|
91
|
+
|
92
|
+
@args_validators = args_contracts.map do |contract|
|
93
|
+
Contract.make_validator(contract)
|
85
94
|
end
|
86
|
-
|
87
|
-
@
|
88
|
-
contract.is_a? Contracts::
|
95
|
+
|
96
|
+
@args_contract_index = args_contracts.index do |contract|
|
97
|
+
contract.is_a? Contracts::Args
|
89
98
|
end
|
99
|
+
|
100
|
+
@ret_validator = Contract.make_validator(ret_contract)
|
101
|
+
|
102
|
+
# == @has_proc_contract
|
103
|
+
last_contract = args_contracts.last
|
104
|
+
is_a_proc = last_contract.is_a?(Class) && (last_contract <= Proc || last_contract <= Method)
|
105
|
+
|
106
|
+
@has_proc_contract = is_a_proc || last_contract.is_a?(Contracts::Func)
|
107
|
+
# ====
|
108
|
+
|
109
|
+
# == @has_options_contract
|
110
|
+
last_contract = args_contracts.last
|
111
|
+
penultimate_contract = args_contracts[-2]
|
112
|
+
@has_options_contract = if @has_proc_contract
|
113
|
+
penultimate_contract.is_a?(Hash)
|
114
|
+
else
|
115
|
+
last_contract.is_a?(Hash)
|
116
|
+
end
|
117
|
+
# ===
|
118
|
+
|
119
|
+
@klass, @method = klass, method
|
90
120
|
end
|
91
121
|
|
92
122
|
def pretty_contract c
|
@@ -94,8 +124,8 @@ class Contract < Contracts::Decorator
|
|
94
124
|
end
|
95
125
|
|
96
126
|
def to_s
|
97
|
-
args =
|
98
|
-
ret = pretty_contract(
|
127
|
+
args = args_contracts.map { |c| pretty_contract(c) }.join(", ")
|
128
|
+
ret = pretty_contract(ret_contract)
|
99
129
|
("#{args} => #{ret}").gsub("Contracts::", "")
|
100
130
|
end
|
101
131
|
|
@@ -103,27 +133,22 @@ class Contract < Contracts::Decorator
|
|
103
133
|
# This function is used by the default #failure_callback method
|
104
134
|
# and uses the hash passed into the failure_callback method.
|
105
135
|
def self.failure_msg(data)
|
106
|
-
|
107
|
-
|
108
|
-
|
109
|
-
|
110
|
-
|
111
|
-
|
112
|
-
|
113
|
-
|
114
|
-
|
115
|
-
|
116
|
-
|
117
|
-
|
118
|
-
|
119
|
-
|
120
|
-
|
121
|
-
|
122
|
-
Expected: #{expected},
|
123
|
-
Actual: #{data[:arg].inspect}
|
124
|
-
Value guarded in: #{data[:class]}::#{method_name}
|
125
|
-
With Contract: #{data[:contracts]}
|
126
|
-
At: #{position} }
|
136
|
+
expected = Contracts::Formatters::Expected.new(data[:contract]).contract
|
137
|
+
position = Contracts::Support.method_position(data[:method])
|
138
|
+
method_name = Contracts::Support.method_name(data[:method])
|
139
|
+
|
140
|
+
header = if data[:return_value]
|
141
|
+
"Contract violation for return value:"
|
142
|
+
else
|
143
|
+
"Contract violation for argument #{data[:arg_pos]} of #{data[:total_args]}:"
|
144
|
+
end
|
145
|
+
|
146
|
+
%{#{header}
|
147
|
+
Expected: #{expected},
|
148
|
+
Actual: #{data[:arg].inspect}
|
149
|
+
Value guarded in: #{data[:class]}::#{method_name}
|
150
|
+
With Contract: #{data[:contracts]}
|
151
|
+
At: #{position} }
|
127
152
|
end
|
128
153
|
|
129
154
|
# Callback for when a contract fails. By default it raises
|
@@ -139,7 +164,7 @@ class Contract < Contracts::Decorator
|
|
139
164
|
# puts failure_msg(data)
|
140
165
|
# exit
|
141
166
|
# end
|
142
|
-
def self.failure_callback(data, use_pattern_matching=true)
|
167
|
+
def self.failure_callback(data, use_pattern_matching = true)
|
143
168
|
if data[:contracts].pattern_match? && use_pattern_matching
|
144
169
|
return DEFAULT_FAILURE_CALLBACK.call(data)
|
145
170
|
end
|
@@ -194,29 +219,29 @@ class Contract < Contracts::Decorator
|
|
194
219
|
contract
|
195
220
|
elsif klass == Array
|
196
221
|
# e.g. [Num, String]
|
197
|
-
# TODO account for these errors too
|
198
|
-
lambda
|
222
|
+
# TODO: account for these errors too
|
223
|
+
lambda do |arg|
|
199
224
|
return false unless arg.is_a?(Array) && arg.length == contract.length
|
200
225
|
arg.zip(contract).all? do |_arg, _contract|
|
201
226
|
Contract.valid?(_arg, _contract)
|
202
227
|
end
|
203
|
-
|
228
|
+
end
|
204
229
|
elsif klass == Hash
|
205
230
|
# e.g. { :a => Num, :b => String }
|
206
|
-
lambda
|
231
|
+
lambda do |arg|
|
207
232
|
return false unless arg.is_a?(Hash)
|
208
233
|
contract.keys.all? do |k|
|
209
234
|
Contract.valid?(arg[k], contract[k])
|
210
235
|
end
|
211
|
-
|
236
|
+
end
|
212
237
|
elsif klass == Contracts::Args
|
213
|
-
lambda
|
238
|
+
lambda do |arg|
|
214
239
|
Contract.valid?(arg, contract.contract)
|
215
|
-
|
240
|
+
end
|
216
241
|
elsif klass == Contracts::Func
|
217
|
-
lambda
|
242
|
+
lambda do |arg|
|
218
243
|
arg.is_a?(Method) || arg.is_a?(Proc)
|
219
|
-
|
244
|
+
end
|
220
245
|
else
|
221
246
|
# classes and everything else
|
222
247
|
# e.g. Fixnum, Num
|
@@ -238,78 +263,111 @@ class Contract < Contracts::Decorator
|
|
238
263
|
call_with(nil, *args, &blk)
|
239
264
|
end
|
240
265
|
|
241
|
-
|
242
|
-
|
243
|
-
|
244
|
-
size = @args_contracts.size
|
266
|
+
# if we specified a proc in the contract but didn't pass one in,
|
267
|
+
# it's possible we are going to pass in a block instead. So lets
|
268
|
+
# append a nil to the list of args just so it doesn't fail.
|
245
269
|
|
246
|
-
|
247
|
-
|
248
|
-
|
249
|
-
|
250
|
-
|
251
|
-
|
252
|
-
|
253
|
-
splat_index += 1 unless splat_present
|
270
|
+
# a better way to handle this might be to take this into account
|
271
|
+
# before throwing a "mismatched # of args" error.
|
272
|
+
def maybe_append_block! args, blk
|
273
|
+
return unless @has_proc_contract && !blk &&
|
274
|
+
(@args_contract_index || args.size < args_contracts.size)
|
275
|
+
args << nil
|
276
|
+
end
|
254
277
|
|
255
|
-
|
256
|
-
|
257
|
-
|
258
|
-
|
278
|
+
# Same thing for when we have named params but didn't pass any in.
|
279
|
+
def maybe_append_options! args, blk
|
280
|
+
return unless @has_options_contract
|
281
|
+
if @has_proc_contract && args_contracts[-2].is_a?(Hash) && !args[-2].is_a?(Hash)
|
282
|
+
args.insert(-2, {})
|
283
|
+
elsif args_contracts[-1].is_a?(Hash) && !args[-1].is_a?(Hash)
|
284
|
+
args << {}
|
259
285
|
end
|
286
|
+
end
|
260
287
|
|
261
|
-
|
262
|
-
|
263
|
-
|
264
|
-
|
265
|
-
|
266
|
-
|
267
|
-
|
268
|
-
|
269
|
-
|
270
|
-
|
271
|
-
|
272
|
-
|
273
|
-
|
274
|
-
|
275
|
-
|
276
|
-
|
277
|
-
|
278
|
-
|
279
|
-
|
288
|
+
def call_with(this, *args, &blk)
|
289
|
+
args << blk if blk
|
290
|
+
|
291
|
+
# Explicitly append blk=nil if nil != Proc contract violation anticipated
|
292
|
+
maybe_append_block!(args, blk)
|
293
|
+
|
294
|
+
# Explicitly append options={} if Hash contract is present
|
295
|
+
maybe_append_options!(args, blk)
|
296
|
+
|
297
|
+
# Loop forward validating the arguments up to the splat (if there is one)
|
298
|
+
(@args_contract_index || args.size).times do |i|
|
299
|
+
contract = args_contracts[i]
|
300
|
+
arg = args[i]
|
301
|
+
validator = @args_validators[i]
|
302
|
+
|
303
|
+
unless validator && validator[arg]
|
304
|
+
return unless Contract.failure_callback(:arg => arg,
|
305
|
+
:contract => contract,
|
306
|
+
:class => klass,
|
307
|
+
:method => method,
|
308
|
+
:contracts => self,
|
309
|
+
:arg_pos => i+1,
|
310
|
+
:total_args => args.size)
|
311
|
+
end
|
312
|
+
|
313
|
+
if contract.is_a?(Contracts::Func)
|
314
|
+
args[i] = Contract.new(klass, arg, *contract.contracts)
|
280
315
|
end
|
281
316
|
end
|
282
317
|
|
283
|
-
|
284
|
-
|
285
|
-
|
286
|
-
|
287
|
-
|
318
|
+
# If there is a splat loop backwards to the lower index of the splat
|
319
|
+
# Once we hit the splat in this direction set its upper index
|
320
|
+
# Keep validating but use this upper index to get the splat validator.
|
321
|
+
if @args_contract_index
|
322
|
+
splat_upper_index = @args_contract_index
|
323
|
+
(args.size - @args_contract_index).times do |i|
|
324
|
+
arg = args[args.size - 1 - i]
|
288
325
|
|
289
|
-
if
|
290
|
-
|
326
|
+
if args_contracts[args_contracts.size - 1 - i].is_a?(Contracts::Args)
|
327
|
+
splat_upper_index = i
|
328
|
+
end
|
329
|
+
|
330
|
+
# Each arg after the spat is found must use the splat validator
|
331
|
+
j = i < splat_upper_index ? i : splat_upper_index
|
332
|
+
contract = args_contracts[args_contracts.size - 1 - j]
|
333
|
+
validator = @args_validators[args_contracts.size - 1 - j]
|
334
|
+
|
335
|
+
unless validator && validator[arg]
|
336
|
+
return unless Contract.failure_callback(:arg => arg,
|
337
|
+
:contract => contract,
|
338
|
+
:class => klass,
|
339
|
+
:method => method,
|
340
|
+
:contracts => self,
|
341
|
+
:arg_pos => i-1,
|
342
|
+
:total_args => args.size)
|
291
343
|
end
|
292
|
-
end
|
293
344
|
|
294
|
-
|
295
|
-
|
296
|
-
|
345
|
+
if contract.is_a?(Contracts::Func)
|
346
|
+
args[args.size - 1 - i] = Contract.new(klass, arg, *contract.contracts)
|
347
|
+
end
|
297
348
|
end
|
298
349
|
end
|
299
350
|
|
300
|
-
|
301
|
-
|
302
|
-
|
303
|
-
|
304
|
-
|
305
|
-
|
306
|
-
|
351
|
+
# If we put the block into args for validating, restore the args
|
352
|
+
args.slice!(-1) if blk
|
353
|
+
result = if method.respond_to?(:call)
|
354
|
+
# proc, block, lambda, etc
|
355
|
+
method.call(*args, &blk)
|
356
|
+
else
|
357
|
+
# original method name referrence
|
358
|
+
method.send_to(this, *args, &blk)
|
359
|
+
end
|
307
360
|
|
308
361
|
unless @ret_validator[result]
|
309
|
-
Contract.failure_callback(
|
362
|
+
Contract.failure_callback(:arg => result,
|
363
|
+
:contract => ret_contract,
|
364
|
+
:class => klass,
|
365
|
+
:method => method,
|
366
|
+
:contracts => self,
|
367
|
+
:return_value => true)
|
310
368
|
end
|
311
369
|
|
312
|
-
this.verify_invariants!(
|
370
|
+
this.verify_invariants!(method) if this.respond_to?(:verify_invariants!)
|
313
371
|
|
314
372
|
result
|
315
373
|
end
|
@@ -1,22 +1,23 @@
|
|
1
|
-
require
|
2
|
-
|
3
|
-
|
4
|
-
|
5
|
-
|
6
|
-
|
7
|
-
|
8
|
-
|
9
|
-
|
10
|
-
|
11
|
-
|
12
|
-
|
13
|
-
|
14
|
-
|
15
|
-
|
16
|
-
|
17
|
-
|
18
|
-
|
19
|
-
|
1
|
+
require "contracts/testable"
|
2
|
+
require "contracts/formatters"
|
3
|
+
|
4
|
+
# rdoc
|
5
|
+
# This module contains all the builtin contracts.
|
6
|
+
# If you want to use them, first:
|
7
|
+
#
|
8
|
+
# import Contracts
|
9
|
+
#
|
10
|
+
# And then use these or write your own!
|
11
|
+
#
|
12
|
+
# A simple example:
|
13
|
+
#
|
14
|
+
# Contract Num, Num => Num
|
15
|
+
# def add(a, b)
|
16
|
+
# a + b
|
17
|
+
# end
|
18
|
+
#
|
19
|
+
# The contract is <tt>Contract Num, Num, Num</tt>.
|
20
|
+
# That says that the +add+ function takes two numbers and returns a number.
|
20
21
|
module Contracts
|
21
22
|
# Check that an argument is +Numeric+.
|
22
23
|
class Num
|
@@ -29,7 +30,7 @@ module Contracts
|
|
29
30
|
end
|
30
31
|
|
31
32
|
def self.test_data
|
32
|
-
[-1, 0, 1, 1.5,
|
33
|
+
[-1, 0, 1, 1.5, 50_000]
|
33
34
|
end
|
34
35
|
end
|
35
36
|
|
@@ -63,6 +64,21 @@ module Contracts
|
|
63
64
|
end
|
64
65
|
end
|
65
66
|
|
67
|
+
# Check that an argument is a natural number.
|
68
|
+
class Nat
|
69
|
+
def self.valid? val
|
70
|
+
val >= 0 && val.integer?
|
71
|
+
end
|
72
|
+
|
73
|
+
def testable?
|
74
|
+
true
|
75
|
+
end
|
76
|
+
|
77
|
+
def self.test_data
|
78
|
+
(0..5).map { |n| n * rand(999) }
|
79
|
+
end
|
80
|
+
end
|
81
|
+
|
66
82
|
# Passes for any argument.
|
67
83
|
class Any
|
68
84
|
def self.valid? val
|
@@ -86,8 +102,9 @@ module Contracts
|
|
86
102
|
#
|
87
103
|
# Of course, <tt>.new</tt> still works.
|
88
104
|
class CallableClass
|
105
|
+
include ::Contracts::Formatters
|
89
106
|
def self.[](*vals)
|
90
|
-
|
107
|
+
new(*vals)
|
91
108
|
end
|
92
109
|
end
|
93
110
|
|
@@ -107,20 +124,22 @@ module Contracts
|
|
107
124
|
end
|
108
125
|
|
109
126
|
def to_s
|
110
|
-
@vals[0, @vals.size-1].
|
127
|
+
@vals[0, @vals.size-1].map do |x|
|
128
|
+
InspectWrapper.create(x)
|
129
|
+
end.join(", ") + " or " + InspectWrapper.create(@vals[-1]).to_s
|
111
130
|
end
|
112
131
|
|
113
132
|
# this can only be tested IF all the sub-contracts have a test_data method
|
114
133
|
def testable?
|
115
134
|
@vals.all? do |val|
|
116
135
|
Testable.testable?(val)
|
117
|
-
end
|
136
|
+
end
|
118
137
|
end
|
119
138
|
|
120
139
|
def test_data
|
121
|
-
@vals.map
|
140
|
+
@vals.map do |val|
|
122
141
|
Testable.test_data(val)
|
123
|
-
|
142
|
+
end.flatten
|
124
143
|
end
|
125
144
|
end
|
126
145
|
|
@@ -141,25 +160,27 @@ module Contracts
|
|
141
160
|
end
|
142
161
|
|
143
162
|
def to_s
|
144
|
-
@vals[0, @vals.size-1].
|
163
|
+
@vals[0, @vals.size-1].map do |x|
|
164
|
+
InspectWrapper.create(x)
|
165
|
+
end.join(", ") + " xor " + InspectWrapper.create(@vals[-1]).to_s
|
145
166
|
end
|
146
167
|
|
147
168
|
def testable?
|
148
169
|
@vals.all? do |val|
|
149
170
|
Testable.testable? val
|
150
|
-
end
|
171
|
+
end
|
151
172
|
end
|
152
173
|
|
153
174
|
def test_data
|
154
|
-
@vals.map
|
175
|
+
@vals.map do |val|
|
155
176
|
Testable.test_data val
|
156
|
-
|
157
|
-
end
|
158
|
-
end
|
177
|
+
end.flatten
|
178
|
+
end
|
179
|
+
end
|
159
180
|
|
160
181
|
# Takes a variable number of contracts.
|
161
182
|
# The contract passes if all contracts pass.
|
162
|
-
# Example: <tt>And[Fixnum, Float]</tt>
|
183
|
+
# Example: <tt>And[Fixnum, Float]</tt>
|
163
184
|
class And < CallableClass
|
164
185
|
def initialize(*vals)
|
165
186
|
@vals = vals
|
@@ -173,7 +194,9 @@ module Contracts
|
|
173
194
|
end
|
174
195
|
|
175
196
|
def to_s
|
176
|
-
@vals[0, @vals.size-1].
|
197
|
+
@vals[0, @vals.size-1].map do |x|
|
198
|
+
InspectWrapper.create(x)
|
199
|
+
end.join(", ") + " and " + InspectWrapper.create(@vals[-1]).to_s
|
177
200
|
end
|
178
201
|
end
|
179
202
|
|
@@ -215,10 +238,10 @@ module Contracts
|
|
215
238
|
|
216
239
|
def to_s
|
217
240
|
"a value that returns true for all of #{@meths.inspect}"
|
218
|
-
end
|
241
|
+
end
|
219
242
|
end
|
220
243
|
|
221
|
-
# Takes a class +A+. If argument is
|
244
|
+
# Takes a class +A+. If argument is object of type +A+, the contract passes.
|
222
245
|
# If it is a subclass of A (or not related to A in any way), it fails.
|
223
246
|
# Example: <tt>Exactly[Numeric]</tt>
|
224
247
|
class Exactly < CallableClass
|
@@ -234,7 +257,24 @@ module Contracts
|
|
234
257
|
"exactly #{@cls.inspect}"
|
235
258
|
end
|
236
259
|
end
|
237
|
-
|
260
|
+
|
261
|
+
# Takes a value +v+. If the argument is +.equal+ to +v+, the contract passes,
|
262
|
+
# otherwise the contract fails.
|
263
|
+
# Example: <tt>Eq[Class]</tt>
|
264
|
+
class Eq < CallableClass
|
265
|
+
def initialize(value)
|
266
|
+
@value = value
|
267
|
+
end
|
268
|
+
|
269
|
+
def valid?(val)
|
270
|
+
@value.equal?(val)
|
271
|
+
end
|
272
|
+
|
273
|
+
def to_s
|
274
|
+
"to be equal to #{@value.inspect}"
|
275
|
+
end
|
276
|
+
end
|
277
|
+
|
238
278
|
# Takes a variable number of contracts. The contract
|
239
279
|
# passes if all of those contracts fail for the given argument.
|
240
280
|
# Example: <tt>Not[nil]</tt>
|
@@ -281,8 +321,12 @@ module Contracts
|
|
281
321
|
end
|
282
322
|
|
283
323
|
def test_data
|
284
|
-
[
|
285
|
-
|
324
|
+
[
|
325
|
+
[],
|
326
|
+
[Testable.test_data(@contract)],
|
327
|
+
[Testable.test_data(@contract), Testable.test_data(@contract)]
|
328
|
+
]
|
329
|
+
end
|
286
330
|
end
|
287
331
|
|
288
332
|
# Used for <tt>*args</tt> (variadic functions). Takes a contract
|
@@ -304,8 +348,12 @@ module Contracts
|
|
304
348
|
end
|
305
349
|
|
306
350
|
def test_data
|
307
|
-
[
|
308
|
-
|
351
|
+
[
|
352
|
+
[],
|
353
|
+
[Testable.test_data(@contract)],
|
354
|
+
[Testable.test_data(@contract), Testable.test_data(@contract)]
|
355
|
+
]
|
356
|
+
end
|
309
357
|
end
|
310
358
|
|
311
359
|
class Bool
|
@@ -324,14 +372,14 @@ module Contracts
|
|
324
372
|
end
|
325
373
|
|
326
374
|
def valid?(hash)
|
327
|
-
keys_match = hash.keys.map {|k| Contract.valid?(k, @key) }.all?
|
328
|
-
vals_match = hash.values.map {|v| Contract.valid?(v, @value) }.all?
|
375
|
+
keys_match = hash.keys.map { |k| Contract.valid?(k, @key) }.all?
|
376
|
+
vals_match = hash.values.map { |v| Contract.valid?(v, @value) }.all?
|
329
377
|
|
330
378
|
[keys_match, vals_match].all?
|
331
379
|
end
|
332
380
|
|
333
381
|
def to_s
|
334
|
-
"Hash<#{@key
|
382
|
+
"Hash<#{@key}, #{@value}>"
|
335
383
|
end
|
336
384
|
end
|
337
385
|
|
@@ -344,51 +392,51 @@ module Contracts
|
|
344
392
|
end
|
345
393
|
end
|
346
394
|
|
347
|
-
class ::Hash
|
348
|
-
|
349
|
-
|
350
|
-
|
351
|
-
|
352
|
-
|
353
|
-
|
354
|
-
|
355
|
-
|
356
|
-
|
357
|
-
|
358
|
-
|
359
|
-
|
360
|
-
|
361
|
-
|
362
|
-
|
363
|
-
|
364
|
-
|
365
|
-
|
366
|
-
|
367
|
-
|
368
|
-
|
369
|
-
|
370
|
-
|
371
|
-
|
372
|
-
|
373
|
-
|
374
|
-
|
375
|
-
end
|
376
|
-
|
377
|
-
class ::String
|
378
|
-
|
379
|
-
|
380
|
-
|
381
|
-
|
382
|
-
|
383
|
-
|
384
|
-
|
385
|
-
|
386
|
-
end
|
395
|
+
# class ::Hash
|
396
|
+
# def testable?
|
397
|
+
# values.all? do |val|
|
398
|
+
# Testable.testable?(val)
|
399
|
+
# end
|
400
|
+
# end
|
401
|
+
|
402
|
+
# def test_data
|
403
|
+
# keys = self.keys
|
404
|
+
# _vals = keys.map do |key|
|
405
|
+
# ret = Testable.test_data(self[key])
|
406
|
+
# if ret.is_a? Array
|
407
|
+
# ret
|
408
|
+
# else
|
409
|
+
# [ret]
|
410
|
+
# end
|
411
|
+
# end
|
412
|
+
# all_vals = Testable.product(_vals)
|
413
|
+
# hashes = []
|
414
|
+
# all_vals.each do |vals|
|
415
|
+
# hash = {}
|
416
|
+
# keys.zip(vals).each do |key, val|
|
417
|
+
# hash[key] = val
|
418
|
+
# end
|
419
|
+
# hashes << hash
|
420
|
+
# end
|
421
|
+
# hashes
|
422
|
+
# end
|
423
|
+
# end
|
424
|
+
|
425
|
+
# class ::String
|
426
|
+
# def self.testable?
|
427
|
+
# true
|
428
|
+
# end
|
429
|
+
|
430
|
+
# def self.test_data
|
431
|
+
# # send a random string
|
432
|
+
# ("a".."z").to_a.shuffle[0, 10].join
|
433
|
+
# end
|
434
|
+
# end
|
387
435
|
|
388
436
|
# Used to define contracts on functions passed in as arguments.
|
389
437
|
# Example: <tt>Func[Num => Num] # the function should take a number and return a number</tt>
|
390
438
|
class Func < CallableClass
|
391
|
-
attr_reader :contracts
|
439
|
+
attr_reader :contracts
|
392
440
|
def initialize(*contracts)
|
393
441
|
@contracts = contracts
|
394
442
|
end
|