contracts 0.8 → 0.9

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 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