contracts 0.0.6 → 0.0.7

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.
@@ -1,3 +1,5 @@
1
+ require 'testable'
2
+
1
3
  =begin rdoc
2
4
  This module contains all the builtin contracts.
3
5
  If you want to use them, first:
@@ -21,6 +23,14 @@ module Contracts
21
23
  def self.valid? val
22
24
  val.is_a? Numeric
23
25
  end
26
+
27
+ def self.testable?
28
+ true
29
+ end
30
+
31
+ def self.test_data
32
+ [-1, 0, 1, 1.5, 50000]
33
+ end
24
34
  end
25
35
 
26
36
  # Check that an argument is a positive number.
@@ -28,6 +38,14 @@ module Contracts
28
38
  def self.valid? val
29
39
  val > 0
30
40
  end
41
+
42
+ def testable?
43
+ true
44
+ end
45
+
46
+ def self.test_data
47
+ (0..5).map { rand(999) + 1 }
48
+ end
31
49
  end
32
50
 
33
51
  # Check that an argument is a negative number.
@@ -35,6 +53,14 @@ module Contracts
35
53
  def self.valid? val
36
54
  val < 0
37
55
  end
56
+
57
+ def testable?
58
+ true
59
+ end
60
+
61
+ def self.test_data
62
+ (0..5).map { (rand(999) + 1) * -1 }
63
+ end
38
64
  end
39
65
 
40
66
  # Passes for any argument.
@@ -83,6 +109,19 @@ module Contracts
83
109
  def to_s
84
110
  @vals[0, @vals.size-1].join(", ") + " or " + @vals[-1].to_s
85
111
  end
112
+
113
+ # this can only be tested IF all the sub-contracts have a test_data method
114
+ def testable?
115
+ @vals.all? do |val|
116
+ Testable.testable?(val)
117
+ end
118
+ end
119
+
120
+ def test_data
121
+ @vals.map { |val|
122
+ Testable.test_data(val)
123
+ }.flatten
124
+ end
86
125
  end
87
126
 
88
127
  # Takes a variable number of contracts.
@@ -104,6 +143,18 @@ module Contracts
104
143
  def to_s
105
144
  @vals[0, @vals.size-1].join(", ") + " xor " + @vals[-1].to_s
106
145
  end
146
+
147
+ def testable?
148
+ @vals.all? do |val|
149
+ Testable.testable? val
150
+ end
151
+ end
152
+
153
+ def test_data
154
+ @vals.map { |val|
155
+ Testable.test_data val
156
+ }.flatten
157
+ end
107
158
  end
108
159
 
109
160
  # Takes a variable number of contracts.
@@ -129,8 +180,8 @@ module Contracts
129
180
  # Takes a variable number of method names as symbols.
130
181
  # The contract passes if the argument responds to all
131
182
  # of those methods.
132
- # Example: <tt>RespondsTo[:password, :credit_card]</tt>
133
- class RespondsTo < CallableClass
183
+ # Example: <tt>RespondTo[:password, :credit_card]</tt>
184
+ class RespondTo < CallableClass
134
185
  def initialize(*meths)
135
186
  @meths = meths
136
187
  end
@@ -222,6 +273,14 @@ module Contracts
222
273
  def to_s
223
274
  "an array of #{@contract}"
224
275
  end
276
+
277
+ def testable?
278
+ Testable.testable? @contract
279
+ end
280
+
281
+ def test_data
282
+ [[], [Testable.test_data(@contract)], [Testable.test_data(@contract), Testable.test_data(@contract)]]
283
+ end
225
284
  end
226
285
 
227
286
  # Used for <tt>*args</tt> (variadic functions). Takes a contract
@@ -237,5 +296,60 @@ module Contracts
237
296
  def to_s
238
297
  "Args[#{@contract}]"
239
298
  end
240
- end
299
+
300
+ def testable?
301
+ Testable.testable? @contract
302
+ end
303
+
304
+ def test_data
305
+ [[], [Testable.test_data(@contract)], [Testable.test_data(@contract), Testable.test_data(@contract)]]
306
+ end
307
+ end
308
+
309
+ class Bool
310
+ def self.valid? val
311
+ val.is_a?(TrueClass) || val.is_a?(FalseClass)
312
+ end
313
+ end
314
+
315
+ class ::Hash
316
+ def testable?
317
+ self.values.all? do |val|
318
+ Testable.testable?(val)
319
+ end
320
+ end
321
+
322
+ def test_data
323
+ keys = self.keys
324
+ _vals = keys.map do |key|
325
+ ret = Testable.test_data(self[key])
326
+ if ret.is_a? Array
327
+ ret
328
+ else
329
+ [ret]
330
+ end
331
+ end
332
+ all_vals = Testable.product(_vals)
333
+ hashes = []
334
+ all_vals.each do |vals|
335
+ hash = {}
336
+ keys.zip(vals).each do |key, val|
337
+ hash[key] = val
338
+ end
339
+ hashes << hash
340
+ end
341
+ hashes
342
+ end
343
+ end
344
+
345
+ class ::String
346
+ def self.testable?
347
+ true
348
+ end
349
+
350
+ def self.test_data
351
+ # send a random string
352
+ ('a'..'z').to_a.shuffle[0, 10].join
353
+ end
354
+ end
241
355
  end
data/lib/contracts.rb CHANGED
@@ -1,15 +1,16 @@
1
1
  require 'decorators'
2
2
  require 'builtin_contracts'
3
3
 
4
- class Class
5
- include MethodDecorators
6
- end
4
+ module Contracts
5
+ def self.included(base)
6
+ base.extend MethodDecorators
7
+ end
7
8
 
8
- class Module
9
- include MethodDecorators
9
+ def self.extended(base)
10
+ base.extend MethodDecorators
11
+ end
10
12
  end
11
13
 
12
-
13
14
  # This is the main Contract class. When you write a new contract, you'll
14
15
  # write it as:
15
16
  #
@@ -18,7 +19,7 @@ end
18
19
  # This class also provides useful callbacks and a validation method.
19
20
  class Contract < Decorator
20
21
  attr_accessor :contracts, :klass, :method
21
- decorator_name :contract
22
+ # decorator_name :contract
22
23
  def initialize(klass, method, *contracts)
23
24
  @klass, @method, @contracts = klass, method, contracts
24
25
  end
@@ -185,3 +186,17 @@ Contracts: #{@contracts.map { |t| t.is_a?(Class) ? t.name : t.class.name }.join(
185
186
  end
186
187
  end
187
188
  end
189
+
190
+ # convenience function for small scripts.
191
+ # Use as:
192
+ #
193
+ # use_contracts self
194
+ #
195
+ # And then you can use contracts on functions
196
+ # that aren't in any module or class.
197
+ def use_contracts(this)
198
+ this.class.send(:include, Contracts)
199
+ def this.Contract(*args)
200
+ self.class.Contract(*args)
201
+ end
202
+ end
data/lib/decorators.rb CHANGED
@@ -5,27 +5,11 @@ module MethodDecorators
5
5
  end
6
6
  end
7
7
 
8
- def method_missing(name, *args, &blk)
9
- # if the const isn't capitalized, then const_defined will throw an
10
- # error. So first check to make sure it is capitalized.
11
- letter = name.to_s[0]
12
- if letter >= 65 && letter <= 90 && Object.const_defined?(name)
13
- const = Object.const_get(name)
14
- elsif Decorator.decorators.key?(name)
15
- const = Decorator.decorators[name]
16
- else
17
- return super
18
- end
19
-
20
- instance_eval <<-ruby_eval, __FILE__, __LINE__ + 1
21
- def #{name}(*args, &blk)
22
- decorate(#{const.name}, *args, &blk)
23
- end
24
- ruby_eval
25
-
26
- send(name, *args, &blk)
27
- end
28
-
8
+ # first, when you write a contract, the decorate method gets called which
9
+ # sets the @decorators variable. Then when the next method after the contract
10
+ # is defined, method_added is called and we look at the @decorators variable
11
+ # to find the decorator for that method. This is how we associate decorators
12
+ # with methods.
29
13
  def method_added(name)
30
14
  return unless @decorators
31
15
 
@@ -33,13 +17,21 @@ module MethodDecorators
33
17
  @decorators = nil
34
18
  @decorated_methods ||= Hash.new {|h,k| h[k] = []}
35
19
 
20
+ # attr_accessor on the class variable decorated_methods
36
21
  class << self; attr_accessor :decorated_methods; end
37
22
 
38
23
  decorators.each do |klass, args|
24
+ # a reference to the method gets passed into the contract here. This is good because
25
+ # we are going to redefine this method with a new name below...so this reference is
26
+ # now the *only* reference to the old method that exists.
39
27
  decorator = klass.respond_to?(:new) ? klass.new(self, instance_method(name), *args) : klass
40
28
  @decorated_methods[name] << decorator
41
29
  end
42
30
 
31
+ # in place of this method, we are going to define our own method. This method
32
+ # just calls the decorator passing in all args that were to be passed into the method.
33
+ # The decorator in turn has a reference to the actual method, so it can call it
34
+ # on its own, after doing it's decorating of course.
43
35
  class_eval <<-ruby_eval, __FILE__, __LINE__ + 1
44
36
  def #{name}(*args, &blk)
45
37
  ret = nil
@@ -49,6 +41,7 @@ module MethodDecorators
49
41
  ret
50
42
  end
51
43
  ruby_eval
44
+ super
52
45
  end
53
46
 
54
47
  def decorate(klass, *args)
@@ -58,19 +51,18 @@ module MethodDecorators
58
51
  end
59
52
 
60
53
  class Decorator
61
- class << self
62
- attr_accessor :decorators
63
- def decorator_name(name)
64
- Decorator.decorators ||= {}
65
- Decorator.decorators[name] = self
66
- end
67
- end
54
+ # an attr_accessor for a class variable:
55
+ class << self; attr_accessor :decorators; end
68
56
 
69
57
  def self.inherited(klass)
70
58
  name = klass.name.gsub(/^./) {|m| m.downcase}
71
59
 
72
60
  return if name =~ /^[^A-Za-z_]/ || name =~ /[^0-9A-Za-z_]/
73
61
 
62
+ # the file and line parameters set the text for error messages
63
+ # make a new method that is the name of your decorator.
64
+ # that method accepts random args and a block.
65
+ # inside, `decorate` is called with those params.
74
66
  MethodDecorators.module_eval <<-ruby_eval, __FILE__, __LINE__ + 1
75
67
  def #{klass}(*args, &blk)
76
68
  decorate(#{klass}, *args, &blk)
data/lib/foo.rb CHANGED
@@ -1,11 +1,10 @@
1
1
  require 'contracts'
2
- include Contracts
2
+ use_contracts(self)
3
3
 
4
- class Object
5
- Contract Num, Num, Num
6
- def add(a, b)
7
- a + b
8
- end
4
+ Contract [Num, String]
5
+ def mult
6
+ return 1, 2
9
7
  end
10
8
 
11
- puts add(1, "foo")
9
+ f = mult
10
+ p f
data/lib/test.rb CHANGED
@@ -1,7 +1,9 @@
1
1
  require 'contracts'
2
+ require 'testable'
2
3
  include Contracts
3
4
 
4
5
  class Object
6
+
5
7
  Contract Num, Num
6
8
  def double(x)
7
9
  x * 2
@@ -35,6 +37,24 @@ class Object
35
37
  def person(data)
36
38
  p data
37
39
  end
40
+
41
+ Contract Num, Num
42
+ def test(x)
43
+ x + 2
44
+ end
38
45
  end
39
46
 
40
- person({:name => "Adit", :age => 25, :bar => 45})
47
+ # this doesn't work
48
+ # p Object.send(:sum, 1, 2)
49
+
50
+ # but this does:
51
+ # p send(:sum, 1, 2)
52
+ #
53
+ # why???
54
+
55
+ Testable.check_all
56
+ # Testable.check(method(:double))
57
+
58
+ # p Object.test(5)
59
+ #
60
+ Object.Hello
data/lib/testable.rb ADDED
@@ -0,0 +1,69 @@
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 { |acc, x|
11
+ acc.product(x)
12
+ }.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
metadata CHANGED
@@ -1,13 +1,13 @@
1
1
  --- !ruby/object:Gem::Specification
2
2
  name: contracts
3
3
  version: !ruby/object:Gem::Version
4
- hash: 19
4
+ hash: 17
5
5
  prerelease: false
6
6
  segments:
7
7
  - 0
8
8
  - 0
9
- - 6
10
- version: 0.0.6
9
+ - 7
10
+ version: 0.0.7
11
11
  platform: ruby
12
12
  authors:
13
13
  - Aditya Bhargava
@@ -33,6 +33,7 @@ files:
33
33
  - lib/decorators.rb
34
34
  - lib/foo.rb
35
35
  - lib/test.rb
36
+ - lib/testable.rb
36
37
  has_rdoc: true
37
38
  homepage: http://github.com/egonSchiele/contracts.ruby
38
39
  licenses: []