contracts 0.8 → 0.9

Sign up to get free protection for your applications and to get access to all the features.
checksums.yaml CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  SHA1:
3
- metadata.gz: 124deda42f022fb31b53dc3389bd51ff85860d30
4
- data.tar.gz: 8ea3f78006952432311338880c563cdbdf327f33
3
+ metadata.gz: c872d0fc055f6b1ace99f6793c436ee222111992
4
+ data.tar.gz: 0bc9d2a28a4dd77395932974fe46e9175818fa73
5
5
  SHA512:
6
- metadata.gz: 644753384168c11234cd6a77b6d64384cc0f6fca93f3614e4d71ae3629ec7d05d85b02bfbd0150b3db21f94e04fe980f784d0e4decee0e5dcfd912f66526b2fb
7
- data.tar.gz: 8a92fd259e98d4320fe5a4a52cc0d2d6cd01e7362b0fc6ed809001ef4e4618a7d1fab4513b2cb2db5946028fdf7a1c745e13491bf9d1e4e897f2dfe39446aafe
6
+ metadata.gz: 2c2eb482b3c7b2e697ff29076734fa9ec74dc93b9f8344514fdfab3a999a1b40dff0d57bbf487dcd82eaf51b7b503c55cc55a6573745b36314f09f3ce2f5778c
7
+ data.tar.gz: 3535f6d2ceea6e1382b9810679fcb619b94ed9788434022b597f120165a534383c306e6d5ce305ac0808a1b0d34dd0f1b1ca1ec53e3e61f3481a860197e8be5a
@@ -0,0 +1,28 @@
1
+ ## v0.9
2
+ - MAJOR fix in pattern-matching: If the return contract for a pattern-matched function fails, it should NOT try the next pattern-match function. Pattern-matching is only for params, not return values.
3
+ - raise an error if multiple defns have the same contract for pattern matching.
4
+
5
+ - New syntax for functions with no input params.
6
+ Old way:
7
+ Contract nil => 1
8
+ def one
9
+
10
+ New way:
11
+ Contract 1
12
+ def one
13
+
14
+ - Prettier HashOf contract can now be written like this: `HashOf[Num => String]`
15
+ - Add `SetOf` contract
16
+ - various small fixes
17
+
18
+ ## v0.8
19
+ - code refactored (very slight loss of performance, big increase in readability)
20
+ - fail when defining a contract on a module without `include Contracts::Modules`
21
+ - fixed several bugs in argument parsing, functions with complex params get contracts applied correctly now.
22
+ - added rubocop to ci.
23
+ - if a contract is set on a protected method, it should not become public.
24
+ - fixed pattern matching when the multiple definitions of functions have different arities.
25
+ - couple of new built-in contracts: Nat, Eq.
26
+ - changed `Invariant` to `invariant`: `invariant(:day) { 1 <= day && day <= 31 }`
27
+ - prettier error messages (`Contracts::Num` is now just `Num`, for example)
28
+ - support for yard-contracts
data/Gemfile CHANGED
@@ -4,7 +4,7 @@ gemspec
4
4
 
5
5
  group :test do
6
6
  gem "rspec"
7
- gem "rubocop", "~> 0.29", :platform => [:ruby_20, :ruby_21]
7
+ gem "rubocop", "~> 0.29.1", :platform => [:ruby_20, :ruby_21]
8
8
  end
9
9
 
10
10
  group :development do
data/TUTORIAL.md CHANGED
@@ -79,7 +79,8 @@ contracts.ruby comes with a lot of built-in contracts, including the following:
79
79
  * [`And`](http://www.rubydoc.info/gems/contracts/Contracts/And) – passes if all contracts pass, e.g. `And[Fixnum, Float]`
80
80
  * [`Not`](http://www.rubydoc.info/gems/contracts/Contracts/Not) – passes if all contracts fail for the given argument, e.g. `Not[nil]`
81
81
  * [`ArrayOf`](http://www.rubydoc.info/gems/contracts/Contracts/ArrayOf) – checks that the argument is an array, and all elements pass the given contract, e.g. `ArrayOf[Num]`
82
- * [`HashOf`](http://www.rubydoc.info/gems/contracts/Contracts/HashOf) – checks that the argument is a hash, and all keys and values pass the given contract, e.g. `HashOf[Symbol, String]`
82
+ * [`SetOf`](http://www.rubydoc.info/gems/contracts/Contracts/SetOf) – checks that the argument is a set, and all elements pass the given contract, e.g. `SetOf[Num]`
83
+ * [`HashOf`](http://www.rubydoc.info/gems/contracts/Contracts/HashOf) – checks that the argument is a hash, and all keys and values pass the given contract, e.g. `HashOf[Symbol => String]`
83
84
  * [`Maybe`](http://www.rubydoc.info/gems/contracts/Contracts/Maybe) – passes if the argument is `nil`, or if the given contract passes
84
85
  * [`RespondTo`](http://www.rubydoc.info/gems/contracts/Contracts/RespondTo) – checks that the argument responds to all of the given methods, e.g. `RespondTo[:password, :credit_card]`
85
86
  * [`Send`](http://www.rubydoc.info/gems/contracts/Contracts/Send) – checks that all named methods return true, e.g. `Send[:valid?]`
data/lib/contracts.rb CHANGED
@@ -73,22 +73,33 @@ class Contract < Contracts::Decorator
73
73
  # to monkey patch #failure_callback only temporary and then switch it back.
74
74
  # First important usage - for specs.
75
75
  DEFAULT_FAILURE_CALLBACK = proc do |data|
76
- fail data[:contracts].failure_exception.new(failure_msg(data), data)
76
+ if data[:return_value]
77
+ # this failed on the return contract
78
+ fail ReturnContractError.new(failure_msg(data), data)
79
+ else
80
+ # this failed for a param contract
81
+ fail data[:contracts].failure_exception.new(failure_msg(data), data)
82
+ end
77
83
  end
78
84
 
79
85
  attr_reader :args_contracts, :ret_contract, :klass, :method
80
86
  def initialize(klass, method, *contracts)
81
- if contracts[-1].is_a? Hash
82
- # internally we just convert that return value syntax back to an array
83
- @args_contracts = contracts[0, contracts.size - 1] + contracts[-1].keys
84
- @ret_contract = contracts[-1].values[0]
85
- else
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
87
+ unless contracts.last.is_a?(Hash)
88
+ unless contracts.one?
89
+ fail %{
90
+ It looks like your contract for #{method.name} doesn't have a return
91
+ value. A contract should be written as `Contract arg1, arg2 =>
92
+ return_value`.
93
+ }.strip
94
+ end
95
+ contracts = [nil => contracts[-1]]
90
96
  end
91
97
 
98
+ # internally we just convert that return value syntax back to an array
99
+ @args_contracts = contracts[0, contracts.size - 1] + contracts[-1].keys
100
+
101
+ @ret_contract = contracts[-1].values[0]
102
+
92
103
  @args_validators = args_contracts.map do |contract|
93
104
  Contract.make_validator(contract)
94
105
  end
@@ -307,7 +318,8 @@ class Contract < Contracts::Decorator
307
318
  :method => method,
308
319
  :contracts => self,
309
320
  :arg_pos => i+1,
310
- :total_args => args.size)
321
+ :total_args => args.size,
322
+ :return_value => false)
311
323
  end
312
324
 
313
325
  if contract.is_a?(Contracts::Func)
@@ -339,7 +351,8 @@ class Contract < Contracts::Decorator
339
351
  :method => method,
340
352
  :contracts => self,
341
353
  :arg_pos => i-1,
342
- :total_args => args.size)
354
+ :total_args => args.size,
355
+ :return_value => false)
343
356
  end
344
357
 
345
358
  if contract.is_a?(Contracts::Func)
@@ -377,7 +390,7 @@ class Contract < Contracts::Decorator
377
390
  if @pattern_match
378
391
  PatternMatchingError
379
392
  else
380
- ContractError
393
+ ParamContractError
381
394
  end
382
395
  end
383
396
 
@@ -1,5 +1,5 @@
1
- require "contracts/testable"
2
1
  require "contracts/formatters"
2
+ require "set"
3
3
 
4
4
  # rdoc
5
5
  # This module contains all the builtin contracts.
@@ -24,14 +24,6 @@ module Contracts
24
24
  def self.valid? val
25
25
  val.is_a? Numeric
26
26
  end
27
-
28
- def self.testable?
29
- true
30
- end
31
-
32
- def self.test_data
33
- [-1, 0, 1, 1.5, 50_000]
34
- end
35
27
  end
36
28
 
37
29
  # Check that an argument is a positive number.
@@ -39,14 +31,6 @@ module Contracts
39
31
  def self.valid? val
40
32
  val > 0
41
33
  end
42
-
43
- def testable?
44
- true
45
- end
46
-
47
- def self.test_data
48
- (0..5).map { rand(999) + 1 }
49
- end
50
34
  end
51
35
 
52
36
  # Check that an argument is a negative number.
@@ -54,28 +38,12 @@ module Contracts
54
38
  def self.valid? val
55
39
  val < 0
56
40
  end
57
-
58
- def testable?
59
- true
60
- end
61
-
62
- def self.test_data
63
- (0..5).map { (rand(999) + 1) * -1 }
64
- end
65
41
  end
66
42
 
67
43
  # Check that an argument is a natural number.
68
44
  class Nat
69
45
  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) }
46
+ val && val >= 0 && val.integer?
79
47
  end
80
48
  end
81
49
 
@@ -128,19 +96,6 @@ module Contracts
128
96
  InspectWrapper.create(x)
129
97
  end.join(", ") + " or " + InspectWrapper.create(@vals[-1]).to_s
130
98
  end
131
-
132
- # this can only be tested IF all the sub-contracts have a test_data method
133
- def testable?
134
- @vals.all? do |val|
135
- Testable.testable?(val)
136
- end
137
- end
138
-
139
- def test_data
140
- @vals.map do |val|
141
- Testable.test_data(val)
142
- end.flatten
143
- end
144
99
  end
145
100
 
146
101
  # Takes a variable number of contracts.
@@ -164,18 +119,6 @@ module Contracts
164
119
  InspectWrapper.create(x)
165
120
  end.join(", ") + " xor " + InspectWrapper.create(@vals[-1]).to_s
166
121
  end
167
-
168
- def testable?
169
- @vals.all? do |val|
170
- Testable.testable? val
171
- end
172
- end
173
-
174
- def test_data
175
- @vals.map do |val|
176
- Testable.test_data val
177
- end.flatten
178
- end
179
122
  end
180
123
 
181
124
  # Takes a variable number of contracts.
@@ -295,17 +238,20 @@ module Contracts
295
238
  end
296
239
  end
297
240
 
298
- # Takes a contract. The related argument must be an array.
299
- # Checks the contract against every element of the array.
241
+ # @private
242
+ # Takes a collection(responds to :each) type and a contract.
243
+ # The related argument must be of specified collection type.
244
+ # Checks the contract against every element of the collection.
300
245
  # If it passes for all elements, the contract passes.
301
- # Example: <tt>ArrayOf[Num]</tt>
302
- class ArrayOf < CallableClass
303
- def initialize(contract)
246
+ # Example: <tt>CollectionOf[Array, Num]</tt>
247
+ class CollectionOf < CallableClass
248
+ def initialize(collection_class, contract)
249
+ @collection_class = collection_class
304
250
  @contract = contract
305
251
  end
306
252
 
307
253
  def valid?(vals)
308
- return false unless vals.is_a?(Array)
254
+ return false unless vals.is_a?(@collection_class)
309
255
  vals.all? do |val|
310
256
  res, _ = Contract.valid?(val, @contract)
311
257
  res
@@ -313,22 +259,36 @@ module Contracts
313
259
  end
314
260
 
315
261
  def to_s
316
- "an array of #{@contract}"
262
+ "a collection #{@collection_class} of #{@contract}"
317
263
  end
318
264
 
319
- def testable?
320
- Testable.testable? @contract
321
- end
265
+ class Factory
266
+ def initialize(collection_class, &before_new)
267
+ @collection_class = collection_class
268
+ @before_new = before_new
269
+ end
270
+
271
+ def new(contract)
272
+ @before_new && @before_new.call
273
+ CollectionOf.new(@collection_class, contract)
274
+ end
322
275
 
323
- def test_data
324
- [
325
- [],
326
- [Testable.test_data(@contract)],
327
- [Testable.test_data(@contract), Testable.test_data(@contract)]
328
- ]
276
+ alias_method :[], :new
329
277
  end
330
278
  end
331
279
 
280
+ # Takes a contract. The related argument must be an array.
281
+ # Checks the contract against every element of the array.
282
+ # If it passes for all elements, the contract passes.
283
+ # Example: <tt>ArrayOf[Num]</tt>
284
+ ArrayOf = CollectionOf::Factory.new(Array)
285
+
286
+ # Takes a contract. The related argument must be a set.
287
+ # Checks the contract against every element of the set.
288
+ # If it passes for all elements, the contract passes.
289
+ # Example: <tt>SetOf[Num]</tt>
290
+ SetOf = CollectionOf::Factory.new(Set)
291
+
332
292
  # Used for <tt>*args</tt> (variadic functions). Takes a contract
333
293
  # and uses it to validate every element passed in
334
294
  # through <tt>*args</tt>.
@@ -342,18 +302,6 @@ module Contracts
342
302
  def to_s
343
303
  "Args[#{@contract}]"
344
304
  end
345
-
346
- def testable?
347
- Testable.testable? @contract
348
- end
349
-
350
- def test_data
351
- [
352
- [],
353
- [Testable.test_data(@contract)],
354
- [Testable.test_data(@contract), Testable.test_data(@contract)]
355
- ]
356
- end
357
305
  end
358
306
 
359
307
  class Bool
@@ -366,9 +314,17 @@ module Contracts
366
314
  # one for hash keys and one for hash values.
367
315
  # Example: <tt>HashOf[Symbol, String]</tt>
368
316
  class HashOf < CallableClass
369
- def initialize(key, value)
370
- @key = key
371
- @value = value
317
+ INVALID_KEY_VALUE_PAIR = "You should provide only one key-value pair to HashOf contract"
318
+
319
+ def initialize(key, value = nil)
320
+ if value
321
+ @key = key
322
+ @value = value
323
+ else
324
+ validate_hash(key)
325
+ @key = key.keys.first
326
+ @value = key[@key]
327
+ end
372
328
  end
373
329
 
374
330
  def valid?(hash)
@@ -381,6 +337,12 @@ module Contracts
381
337
  def to_s
382
338
  "Hash<#{@key}, #{@value}>"
383
339
  end
340
+
341
+ private
342
+
343
+ def validate_hash(hash)
344
+ fail ArgumentError, INVALID_KEY_VALUE_PAIR unless hash.count == 1
345
+ end
384
346
  end
385
347
 
386
348
  # Takes a Contract.
@@ -392,47 +354,6 @@ module Contracts
392
354
  end
393
355
  end
394
356
 
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
435
-
436
357
  # Used to define contracts on functions passed in as arguments.
437
358
  # Example: <tt>Func[Num => Num] # the function should take a number and return a number</tt>
438
359
  class Func < CallableClass
@@ -61,6 +61,24 @@ module Contracts
61
61
 
62
62
  @decorated_methods[method_type][name] ||= []
63
63
 
64
+ unless decorators.size == 1
65
+ fail %{
66
+ Oops, it looks like method '#{name}' has multiple contracts:
67
+ #{decorators.map { |x| x[1][0].inspect }.join("\n")}
68
+
69
+ Did you accidentally put more than one contract on a single function, like so?
70
+
71
+ Contract String => String
72
+ Contract Num => String
73
+ def foo x
74
+ end
75
+
76
+ If you did NOT, then you have probably discovered a bug in this library.
77
+ Please file it along with the relevant code at:
78
+ https://github.com/egonSchiele/contracts.ruby/issues
79
+ }
80
+ end
81
+
64
82
  pattern_matching = false
65
83
  decorators.each do |klass, args|
66
84
  # a reference to the method gets passed into the contract here. This is good because
@@ -68,6 +86,21 @@ module Contracts
68
86
  # now the *only* reference to the old method that exists.
69
87
  # We assume here that the decorator (klass) responds to .new
70
88
  decorator = klass.new(self, method_reference, *args)
89
+ new_args_contract = decorator.args_contracts
90
+ matched = @decorated_methods[method_type][name].select do |contract|
91
+ contract.args_contracts == new_args_contract
92
+ end
93
+ unless matched.empty?
94
+ fail ContractError.new(%{
95
+ It looks like you are trying to use pattern-matching, but
96
+ multiple definitions for function '#{name}' have the same
97
+ contract for input parameters:
98
+
99
+ #{(matched + [decorator]).map(&:to_s).join("\n")}
100
+
101
+ Each definition needs to have a different contract for the parameters.
102
+ }, {})
103
+ end
71
104
  @decorated_methods[method_type][name] << decorator
72
105
  pattern_matching ||= decorator.pattern_match?
73
106
  end
@@ -93,14 +126,14 @@ module Contracts
93
126
  # Here's why: Suppose you have this code:
94
127
  #
95
128
  # class Foo
96
- # Contract nil => String
129
+ # Contract String
97
130
  # def to_s
98
131
  # "Foo"
99
132
  # end
100
133
  # end
101
134
  #
102
135
  # class Bar < Foo
103
- # Contract nil => String
136
+ # Contract String
104
137
  # def to_s
105
138
  # super + "Bar"
106
139
  # end
@@ -22,6 +22,12 @@ end
22
22
  class ContractError < ContractBaseError
23
23
  end
24
24
 
25
+ class ParamContractError < ContractError
26
+ end
27
+
28
+ class ReturnContractError < ContractError
29
+ end
30
+
25
31
  # @private
26
32
  # Special contract error used internally to detect pattern failure during pattern matching
27
33
  class PatternMatchingError < ContractBaseError
@@ -1,3 +1,3 @@
1
1
  module Contracts
2
- VERSION = "0.8"
2
+ VERSION = "0.9"
3
3
  end
@@ -215,6 +215,20 @@ RSpec.describe "Contracts:" do
215
215
  end
216
216
  end
217
217
 
218
+ describe "SetOf:" do
219
+ it "should pass for a set of nums" do
220
+ expect { @o.product_from_set(Set.new([1, 2, 3])) }.to_not raise_error
221
+ end
222
+
223
+ it "should fail for an array with one non-num" do
224
+ expect { @o.product_from_set(Set.new([1, 2, 3, "bad"])) }.to raise_error(ContractError)
225
+ end
226
+
227
+ it "should fail for a non-array" do
228
+ expect { @o.product_from_set(1) }.to raise_error(ContractError)
229
+ end
230
+ end
231
+
218
232
  describe "Bool:" do
219
233
  it "should pass for an argument that is a boolean" do
220
234
  expect { @o.bool_test(true) }.to_not raise_error
@@ -241,12 +255,27 @@ RSpec.describe "Contracts:" do
241
255
  end
242
256
 
243
257
  describe "HashOf:" do
258
+ it "doesn't allow to specify multiple key-value pairs with pretty syntax" do
259
+ expect do
260
+ Class.new do
261
+ include Contracts
262
+
263
+ Contract Contracts::HashOf[Symbol => String, Contracts::Num => Contracts::Num] => nil
264
+ def something(hash)
265
+ # ...
266
+ end
267
+ end
268
+ end.to raise_error(ArgumentError, "You should provide only one key-value pair to HashOf contract")
269
+ end
270
+
244
271
  context "given a fulfilled contract" do
245
272
  it { expect(@o.gives_max_value(:panda => 1, :bamboo => 2)).to eq(2) }
273
+ it { expect(@o.pretty_gives_max_value(:panda => 1, :bamboo => 2)).to eq(2) }
246
274
  end
247
275
 
248
276
  context "given an unfulfilled contract" do
249
277
  it { expect { @o.gives_max_value(:panda => "1", :bamboo => "2") }.to raise_error(ContractError) }
278
+ it { expect { @o.pretty_gives_max_value(:panda => "1", :bamboo => "2") }.to raise_error(ContractError) }
250
279
  end
251
280
 
252
281
  describe "#to_s" do
@@ -15,6 +15,27 @@ RSpec.describe "Contracts:" do
15
15
  end
16
16
  end
17
17
 
18
+ describe "contracts for functions with no arguments" do
19
+ it "should work for functions with no args" do
20
+ expect { @o.no_args }.to_not raise_error
21
+ end
22
+
23
+ it "should still work for old-style contracts for functions with no args" do
24
+ expect { @o.old_style_no_args }.to_not raise_error
25
+ end
26
+
27
+ it "should not work for a function with a bad contract" do
28
+ expect do
29
+ Class.new(GenericExample) do
30
+ Contract Num, Num
31
+ def no_args_bad_contract
32
+ 1
33
+ end
34
+ end
35
+ end.to raise_error
36
+ end
37
+ end
38
+
18
39
  describe "pattern matching" do
19
40
  let(:string_with_hello) { "Hello, world" }
20
41
  let(:string_without_hello) { "Hi, world" }
@@ -54,6 +75,28 @@ RSpec.describe "Contracts:" do
54
75
  ).to eq("foo")
55
76
  end
56
77
 
78
+ it "if the return contract for a pattern match fails, it should fail instead of trying the next pattern match" do
79
+ expect do
80
+ subject.double(1)
81
+ end.to raise_error(ContractError)
82
+ end
83
+
84
+ it "should fail if multiple methods are defined with the same contract (for pattern-matching)" do
85
+ expect do
86
+ Class.new(GenericExample) do
87
+ Contract Contracts::Num => Contracts::Num
88
+ def same_param_contract x
89
+ x + 2
90
+ end
91
+
92
+ Contract Contracts::Num => String
93
+ def same_param_contract x
94
+ "sdf"
95
+ end
96
+ end
97
+ end.to raise_error(ContractError)
98
+ end
99
+
57
100
  context "when failure_callback was overriden" do
58
101
  before do
59
102
  ::Contract.override_failure_callback do |_data|
@@ -73,6 +116,12 @@ RSpec.describe "Contracts:" do
73
116
  ).to be_a(PatternMatchingExample::Failure)
74
117
  end
75
118
 
119
+ it "if the return contract for a pattern match fails, it should fail instead of trying the next pattern match, even with the failure callback" do
120
+ expect do
121
+ subject.double(1)
122
+ end.to raise_error(ContractError)
123
+ end
124
+
76
125
  it "uses overriden failure_callback when pattern matching fails" do
77
126
  expect do
78
127
  subject.process_request("hello")
@@ -273,10 +322,6 @@ RSpec.describe "Contracts:" do
273
322
  end
274
323
  end
275
324
 
276
- it "should work for functions with no args" do
277
- expect { @o.no_args }.to_not raise_error
278
- end
279
-
280
325
  describe "classes" do
281
326
  it "should pass for correct input" do
282
327
  expect { @o.hello("calvin") }.to_not raise_error
@@ -212,15 +212,29 @@ class GenericExample
212
212
  end
213
213
  end
214
214
 
215
+ Contract SetOf[Num] => Num
216
+ def product_from_set(vals)
217
+ vals.inject(1) do |acc, x|
218
+ acc * x
219
+ end
220
+ end
221
+
215
222
  Contract Bool => nil
216
223
  def bool_test(x)
217
224
  end
218
225
 
219
- Contract nil => Num
226
+ Contract Num
220
227
  def no_args
221
228
  1
222
229
  end
223
230
 
231
+ # This function has a contract which says it has no args,
232
+ # but the function does have args.
233
+ Contract nil => Num
234
+ def old_style_no_args
235
+ 2
236
+ end
237
+
224
238
  Contract ArrayOf[Num], Func[Num => Num] => ArrayOf[Num]
225
239
  def map(arr, func)
226
240
  ret = []
@@ -267,18 +281,23 @@ class GenericExample
267
281
  hash.values.max
268
282
  end
269
283
 
284
+ Contract HashOf[Symbol => Num] => Num
285
+ def pretty_gives_max_value(hash)
286
+ hash.values.max
287
+ end
288
+
270
289
  Contract EmptyCont => Any
271
290
  def using_empty_contract(a)
272
291
  a
273
292
  end
274
293
 
275
- Contract nil => String
294
+ Contract String
276
295
  def a_private_method
277
296
  "works"
278
297
  end
279
298
  private :a_private_method
280
299
 
281
- Contract nil => String
300
+ Contract String
282
301
  def a_protected_method
283
302
  "works"
284
303
  end
@@ -286,14 +305,14 @@ class GenericExample
286
305
 
287
306
  private
288
307
 
289
- Contract nil => String
308
+ Contract String
290
309
  def a_really_private_method
291
310
  "works for sure"
292
311
  end
293
312
 
294
313
  protected
295
314
 
296
- Contract nil => String
315
+ Contract String
297
316
  def a_really_protected_method
298
317
  "works for sure"
299
318
  end
@@ -394,6 +413,16 @@ class PatternMatchingExample
394
413
  def do_stuff(number, string, other_number)
395
414
  "bar"
396
415
  end
416
+
417
+ Contract Num => Num
418
+ def double x
419
+ "bad"
420
+ end
421
+
422
+ Contract String => String
423
+ def double x
424
+ x * 2
425
+ end
397
426
  end
398
427
 
399
428
  # invariant example (silliest implementation ever)
@@ -3,6 +3,12 @@ class GenericExample
3
3
  def splat_then_arg(*vals, n)
4
4
  vals.map { |v| v * n }
5
5
  end
6
+
7
+ if ruby_version <= 1.9
8
+ Contract ({:foo => Nat}) => nil
9
+ def nat_test_with_kwarg(a_hash)
10
+ end
11
+ end
6
12
  end
7
13
 
8
14
  RSpec.describe "Contracts:" do
@@ -3,6 +3,10 @@ class GenericExample
3
3
  def splat_then_optional_named(*vals, repeat: 2)
4
4
  vals.map { |v| v * repeat }
5
5
  end
6
+
7
+ Contract ({foo: Nat}) => nil
8
+ def nat_test_with_kwarg(foo: 10)
9
+ end
6
10
  end
7
11
 
8
12
  RSpec.describe "Contracts:" do
@@ -19,4 +23,18 @@ RSpec.describe "Contracts:" do
19
23
  expect { @o.splat_then_optional_named("hello", "world", repeat: 3) }.to_not raise_error
20
24
  end
21
25
  end
26
+
27
+ describe "Nat:" do
28
+ it "should pass for keyword args with correct arg given" do
29
+ expect { @o.nat_test_with_kwarg(foo: 10) }.to_not raise_error
30
+ end
31
+
32
+ it "should fail with a ContractError for wrong keyword args input" do
33
+ expect { @o.nat_test_with_kwarg(foo: -10) }.to raise_error(ContractError)
34
+ end
35
+
36
+ it "should fail with a ContractError for no input" do
37
+ expect { @o.nat_test_with_kwarg }.to raise_error(ContractError)
38
+ end
39
+ end
22
40
  end
metadata CHANGED
@@ -1,14 +1,14 @@
1
1
  --- !ruby/object:Gem::Specification
2
2
  name: contracts
3
3
  version: !ruby/object:Gem::Version
4
- version: '0.8'
4
+ version: '0.9'
5
5
  platform: ruby
6
6
  authors:
7
7
  - Aditya Bhargava
8
8
  autorequire:
9
9
  bindir: bin
10
10
  cert_chain: []
11
- date: 2015-04-02 00:00:00.000000000 Z
11
+ date: 2015-04-24 00:00:00.000000000 Z
12
12
  dependencies: []
13
13
  description: This library provides contracts for Ruby. Contracts let you clearly express
14
14
  how your code behaves, and free you from writing tons of boilerplate, defensive
@@ -22,8 +22,8 @@ files:
22
22
  - ".rspec"
23
23
  - ".rubocop.yml"
24
24
  - ".travis.yml"
25
+ - CHANGELOG.markdown
25
26
  - Gemfile
26
- - Gemfile.lock
27
27
  - README.md
28
28
  - TODO.markdown
29
29
  - TUTORIAL.md
@@ -42,7 +42,6 @@ files:
42
42
  - lib/contracts/method_reference.rb
43
43
  - lib/contracts/modules.rb
44
44
  - lib/contracts/support.rb
45
- - lib/contracts/testable.rb
46
45
  - lib/contracts/version.rb
47
46
  - script/rubocop.rb
48
47
  - spec/builtin_contracts_spec.rb
data/Gemfile.lock DELETED
@@ -1,49 +0,0 @@
1
- PATH
2
- remote: .
3
- specs:
4
- contracts (0.7)
5
-
6
- GEM
7
- remote: http://rubygems.org/
8
- specs:
9
- ast (2.0.0)
10
- astrolabe (1.3.0)
11
- parser (>= 2.2.0.pre.3, < 3.0)
12
- diff-lcs (1.2.5)
13
- hirb (0.7.2)
14
- method_profiler (2.0.1)
15
- hirb (>= 0.6.0)
16
- parser (2.2.0.3)
17
- ast (>= 1.1, < 3.0)
18
- powerpack (0.1.0)
19
- rainbow (2.0.0)
20
- rspec (3.1.0)
21
- rspec-core (~> 3.1.0)
22
- rspec-expectations (~> 3.1.0)
23
- rspec-mocks (~> 3.1.0)
24
- rspec-core (3.1.7)
25
- rspec-support (~> 3.1.0)
26
- rspec-expectations (3.1.2)
27
- diff-lcs (>= 1.2.0, < 2.0)
28
- rspec-support (~> 3.1.0)
29
- rspec-mocks (3.1.3)
30
- rspec-support (~> 3.1.0)
31
- rspec-support (3.1.2)
32
- rubocop (0.29.1)
33
- astrolabe (~> 1.3)
34
- parser (>= 2.2.0.1, < 3.0)
35
- powerpack (~> 0.1)
36
- rainbow (>= 1.99.1, < 3.0)
37
- ruby-progressbar (~> 1.4)
38
- ruby-prof (0.15.2)
39
- ruby-progressbar (1.7.5)
40
-
41
- PLATFORMS
42
- ruby
43
-
44
- DEPENDENCIES
45
- contracts!
46
- method_profiler
47
- rspec
48
- rubocop (~> 0.29)
49
- ruby-prof
@@ -1,69 +0,0 @@
1
- module Contracts
2
- class Testable
3
- # Given an array-of-arrays of arguments,
4
- # gives you the product of those arguments so that
5
- # each possible combination is tried.
6
- # Example: <tt>[[1, 2], [3, 4]]</tt> would give you:
7
- #
8
- # [[1, 3], [1, 4], [2, 3], [2, 4]]
9
- def self.product(arrays)
10
- arrays.inject do |acc, x|
11
- acc.product(x)
12
- end.flatten(arrays.size - 2)
13
- end
14
-
15
- # Given a contract, tells if you it's testable
16
- def self.testable?(contract)
17
- if contract.respond_to?(:testable?)
18
- contract.testable?
19
- else
20
- contract.respond_to?(:new) && contract.method(:new).arity == 0
21
- end
22
- end
23
-
24
- # Given a contract, returns the test data associated with that contract
25
- def self.test_data(contract)
26
- if contract.respond_to?(:testable?)
27
- contract.test_data
28
- else
29
- contract.new
30
- end
31
- end
32
-
33
- # TODO: Should work on whatever class it was invoked on, no?
34
- def self.check_all
35
- o = Object.new
36
- Object.decorated_methods.each do |name, _contracts|
37
- check(o.method(name))
38
- end
39
- end
40
-
41
- def self.check(meth)
42
- contracts = meth.owner.decorated_methods[meth.name.to_sym][0].contracts
43
- arg_contracts = contracts[0, contracts.size - 1]
44
- return_val = contracts[-1]
45
- checkable = arg_contracts.all? do |arg_contract|
46
- Testable.testable?(arg_contract)
47
- end
48
-
49
- if checkable
50
- print "Checking #{meth.name}..."
51
- _test_data = arg_contracts.map do |arg_contract|
52
- data = Testable.test_data(arg_contract)
53
- data.is_a?(Array) ? data : [data]
54
- end
55
- test_data = Testable.product _test_data
56
- test_data.each do |args|
57
- if args.is_a? Hash
58
- # because *hash destroys the hash
59
- res = meth.call(args)
60
- else
61
- res = meth.call(*args)
62
- end
63
- Contract.valid?(res, return_val)
64
- end
65
- puts "#{test_data.size} tests run."
66
- end
67
- end
68
- end
69
- end