handshake 0.1.0 → 0.2.0

Sign up to get free protection for your applications and to get access to all the features.
data/lib/handshake.rb CHANGED
@@ -1 +1,590 @@
1
1
  Dir[File.join(File.dirname(__FILE__), 'handshake/**/*.rb')].sort.each { |lib| require lib }
2
+
3
+ require 'test/unit/assertions'
4
+
5
+ # A module for defining class and method contracts (as in design-by-contract
6
+ # programming). To use it in your code, include this module in a class.
7
+ # Note that when you do so, that class's +new+ method will be replaced with
8
+ # one that returns a contract-checked proxy object.
9
+ # There are three different types of contracts you can specify on a class.
10
+ # See Handshake::ClassMethods for more documentation.
11
+ module Handshake
12
+
13
+ # Catches any thrown :contract exception raised within the given block,
14
+ # appends the given message to the violation message, and re-raises the
15
+ # exception.
16
+ def Handshake.catch_contract(mesg=nil, &block) # :nodoc:
17
+ violation = catch(:contract, &block)
18
+ if violation.is_a?(Exception)
19
+ # Re-raise the violation with the given message, ensuring that the
20
+ # callback stack begins with the caller of this method rather than
21
+ # this method.
22
+ message = ( mesg.nil? ? "" : ( mesg + ": " ) ) + violation.message
23
+ raise violation.class, message, caller
24
+ end
25
+ end
26
+
27
+ # When Handshake is included in a class, that class's +new+ method is
28
+ # overridden to provide custom functionality. A proxy object, returned
29
+ # in place of the real object, filters all external method calls through
30
+ # any contracts that have been defined.
31
+ # <b>N.B.:<b> Handshake is designed to act as a barrier between an object and
32
+ # its callers. However, anything that takes place within that barrier
33
+ # is not checked. This means that Handshake is, at the moment, unable
34
+ # to enforce contracts on methods called only internally, notably private
35
+ # methods.
36
+ def Handshake.included(base)
37
+ base.extend(ClassMethods)
38
+ base.extend(ClauseMethods)
39
+
40
+ base.send(:include, Test::Unit::Assertions)
41
+ base.send(:include, Handshake::InstanceMethods)
42
+
43
+ base.class_inheritable_array :invariants
44
+ base.write_inheritable_array :invariants, []
45
+
46
+ base.class_inheritable_hash :method_contracts
47
+ base.write_inheritable_hash :method_contracts, {}
48
+
49
+ class << base
50
+ alias :instantiate :new
51
+ # Override the class-level new method of every class that includes
52
+ # Contract and cause it to return a proxy object for the original.
53
+ def new(*args, &block)
54
+ if @non_instantiable
55
+ raise ContractViolation, "This class has been marked as abstract and cannot be instantiated."
56
+ end
57
+ o = nil
58
+
59
+ # Special case: at this stage it's only possible to check arguments
60
+ # (before) and invariants (after). Maybe postconditions?
61
+ Handshake.catch_contract("Contract violated in call to constructor of class #{self}") do
62
+ if contract_defined? :initialize
63
+ method_contracts[:initialize].check_accepts!(*args, &block)
64
+ end
65
+ end
66
+
67
+ ### Instantiate the object itself.
68
+ o = self.instantiate(*args, &block)
69
+
70
+ Handshake.catch_contract("Invariant violated by constructor of class #{self}") do
71
+ o.check_invariants!
72
+ end
73
+
74
+ raise ContractError, "Could not instantiate object" if o.nil?
75
+
76
+ ### Wrap the object in a proxy.
77
+ p = Proxy.new( o )
78
+ # Make sure that the object has a reference back to the proxy.
79
+ o.instance_variable_set("@checked_self", p)
80
+ p
81
+ end
82
+ end
83
+ end
84
+
85
+ # This module contains methods that are mixed into any class that includes
86
+ # Handshake. They allow you to define constraints on that class and its
87
+ # methods. Subclasses will inherit the contracts and invariants of its
88
+ # superclass, but Handshake contracts currently can't be mixed-in via a
89
+ # module.
90
+ #
91
+ # This module defines three kinds of contracts: class invariants, method
92
+ # signature constraints, and more general method pre- and post-conditions.
93
+ # Invariants accept a block which should return a boolean. Pre- and post-
94
+ # conditions expect you to use assertions (all of Test::Unit's standard
95
+ # assertions are available) and will pass unless an assertion fails.
96
+ # Method signature contracts map inputs clauses to output clauses. A
97
+ # "clause" is defined as any object that implements the === method.
98
+ #
99
+ # All method contracts are defined on the method defined immediately after
100
+ # their declaration unless a method name is specified. For example,
101
+ #
102
+ # contract :foo, String => Integer
103
+ #
104
+ # is equivalent to
105
+ #
106
+ # contract String => Integer
107
+ # def foo ...
108
+ #
109
+ # ===Method signature contracts
110
+ # contract String => Integer
111
+ # contract [ String, 1..5, [ Integer ], Block ] => [ String, String ]
112
+ # contract clause("must equal 'foo'") { |o| o == "foo" } => anything
113
+ # contract Block(String => Integer) => Symbol
114
+ #
115
+ # A method signature contract is defined as a mapping from valid inputs to
116
+ # to valid outputs. A clause here is any object which implements the
117
+ # <tt>===</tt> method. Classes, ranges, regexes, and other useful objects
118
+ # are thus all valid values for a method signature contract.
119
+ #
120
+ # Multiple arguments are specified as an array. To specify that a method
121
+ # accepts varargs, define a nested array as the last or second-to-last
122
+ # item in the array. To specify that a method accepts a block, use the
123
+ # Block clause, which accepts a method signature hash as an argument,
124
+ # as above. To specify that a method should accept a block, but that the
125
+ # block should be unchecked, simply use Block instead of Block(... => ...).
126
+ #
127
+ # New clauses may be created easily with the Handshake::ClauseMethods#clause
128
+ # method. Handshake::ClauseMethods also provides a number of useful contract
129
+ # combinators for specifying rich input and output contracts.
130
+ #
131
+ # ===Contract-checked accessors
132
+ # contract_reader :foo => String, :bar => Integer
133
+ # contract_writer ...
134
+ # contract_accessor ...
135
+ # Defines contract-checked accessors. Method names and clauses are specified
136
+ # in a hash. Hash values are any valid clause.
137
+ #
138
+ # ===Invariants
139
+ # invariant(optional_message) { returns true }
140
+ # Aliased as +always+. Has access to instance variables and methods of object
141
+ # but calls to same are unchecked.
142
+ #
143
+ # ===Pre/post-conditions
144
+ # before(optional_message) { |arg1, ...| assert condition }
145
+ # after(optional_message) { |arg1, ..., returned| assert condition }
146
+ # around(optional_message) { |arg1, ...| assert condition }
147
+ # Check a set of conditions, using assertions, before and after method
148
+ # invocation. +before+ and +after+ are aliased as +requires+ and +ensures+
149
+ # respectively. +around+ currently throws a block argument warning; this
150
+ # should be fixed soon. Same scope rules as invariants, so you can check
151
+ # instance variables and local methods. All Test::Unit::Assertions are available
152
+ # for use, but any such AssertionFailed errors encountered are re-raised
153
+ # by Handshake as Handshake::AssertionFailed errors to avoid confusion
154
+ # with test case execution.
155
+ #
156
+ # ===Abstract class decorator
157
+ # class SuperDuperContract
158
+ # include Handshake; abstract!
159
+ # ...
160
+ # end
161
+ #
162
+ # To define a class as non-instantiable and have Handshake raise a
163
+ # ContractViolation if a caller attempts to do so, call <tt>abstract!</tt>
164
+ # at the top of the class definition. This attribute is not inherited
165
+ # by subclasses, but is useful if you would like to define a pure-contract
166
+ # superclass that isn't intended to be instantiated directly.
167
+ module ClassMethods
168
+
169
+ # Define this class as non-instantiable. Subclasses do not inherit this
170
+ # attribute.
171
+ def abstract!
172
+ @non_instantiable = true
173
+ end
174
+
175
+ # Specify an invariant, with a block and an optional error message.
176
+ def invariant(mesg=nil, &block) # :yields:
177
+ write_inheritable_array(:invariants, [ Invariant.new(mesg, &block) ] )
178
+ nil
179
+ end
180
+ alias :always :invariant
181
+
182
+ # In order for contract clauses to work in conjunction with Handshake
183
+ # proxy objects, the === method must be redefined in terms of is_a?.
184
+ def ===(other)
185
+ other.is_a? self
186
+ end
187
+
188
+ # Specify an argument contract, with argument clauses on one side of the
189
+ # hash arrow and returned values on the other. Each clause must implement
190
+ # the === method or have been created with the assert method. This
191
+ # method should generally not be called directly.
192
+ def contract(meth_or_hash, contract_hash=nil)
193
+ if meth_or_hash.is_a? Hash
194
+ defer :contract, meth_or_hash
195
+ else
196
+ define_contract(meth_or_hash, contract_hash)
197
+ end
198
+ end
199
+
200
+ # Specify a precondition.
201
+ def before(meth_or_mesg=nil, mesg=nil, &block)
202
+ condition(:before, meth_or_mesg, mesg, &block)
203
+ end
204
+ alias :requires :before
205
+
206
+ # Specify a postcondition.
207
+ def after(meth_or_mesg=nil, mesg=nil, &block)
208
+ condition(:after, meth_or_mesg, mesg, &block)
209
+ end
210
+ alias :ensures :after
211
+
212
+ # Specify a bothcondition.
213
+ def around(meth_or_mesg=nil, mesg=nil, &block)
214
+ condition(:around, meth_or_mesg, mesg, &block)
215
+ end
216
+
217
+ # Returns the MethodContract for the given method name. Side effect:
218
+ # creates one if none defined.
219
+ def contract_for(method)
220
+ if contract_defined?(method)
221
+ method_contracts[method]
222
+ else
223
+ contract = MethodContract.new("#{self}##{method}")
224
+ write_inheritable_hash :method_contracts, { method => contract }
225
+ contract
226
+ end
227
+ end
228
+
229
+ # Returns true if a contract is defined for the named method.
230
+ def contract_defined?(method)
231
+ method_contracts.has_key?(method)
232
+ end
233
+
234
+ # Defines contract-checked attribute readers with the given hash of method
235
+ # name to clause.
236
+ def contract_reader(meth_to_clause)
237
+ attr_reader *(meth_to_clause.keys)
238
+ meth_to_clause.each do |meth, cls|
239
+ contract meth, nil => cls
240
+ end
241
+ end
242
+
243
+ # Defines contract-checked attribute writers with the given hash of method
244
+ # name to clause.
245
+ def contract_writer(meth_to_clause)
246
+ attr_writer *(meth_to_clause.keys)
247
+ meth_to_clause.each do |meth, cls|
248
+ contract "#{meth}=".to_sym, cls => anything
249
+ end
250
+ end
251
+
252
+ # Defines contract-checked attribute accessors for the given hash of method
253
+ # name to clause.
254
+ def contract_accessor(meth_to_clause)
255
+ contract_reader meth_to_clause
256
+ contract_writer meth_to_clause
257
+ end
258
+
259
+ # Callback from method add event. If a previous method contract
260
+ # declaration was deferred, complete it now with the name of the newly-
261
+ # added method.
262
+ def method_added(meth_name)
263
+ @deferred ||= {}
264
+ unless @deferred.empty?
265
+ @deferred.each do |k, v|
266
+ case k
267
+ when :before, :after, :around
268
+ define_condition meth_name, k, v
269
+ when :contract
270
+ define_contract meth_name, v
271
+ end
272
+ end
273
+ @deferred.clear
274
+ end
275
+ end
276
+
277
+ private
278
+
279
+ def define_contract(method, contract_hash)
280
+ contract = contract_for(method).dup
281
+ contract.signature = contract_hash
282
+ write_inheritable_hash :method_contracts, { method => contract }
283
+ end
284
+
285
+ def define_condition(method, type, condition)
286
+ defined_before = [ :before, :around ].include? type
287
+ defined_after = [ :after, :around ].include? type
288
+ contract = contract_for(method).dup
289
+ contract.preconditions << condition if defined_before
290
+ contract.postconditions << condition if defined_after
291
+ write_inheritable_hash :method_contracts, { method => contract }
292
+ end
293
+
294
+ def condition(type, meth_or_mesg=nil, mesg=nil, &block)
295
+ method_specified = meth_or_mesg.is_a?(Symbol)
296
+ message = method_specified ? mesg : meth_or_mesg
297
+ condition = MethodCondition.new(message, &block)
298
+ if method_specified
299
+ define_condition(type, meth_or_mesg, condition)
300
+ else
301
+ defer type, condition
302
+ end
303
+ end
304
+
305
+ def defer(type, value)
306
+ ( @deferred ||= {} )[type] = value
307
+ end
308
+
309
+ end
310
+
311
+ module InstanceMethods
312
+ # Checks the invariants defined on this class against +self+, raising a
313
+ # ContractViolation if any of them fail.
314
+ def check_invariants!
315
+ self.class.invariants.each do |invar|
316
+ unless invar.holds?(self)
317
+ mesg = invar.mesg || "Invariant check failed"
318
+ throw :contract, ContractViolation.new(mesg)
319
+ end
320
+ end
321
+ end
322
+
323
+ protected
324
+ # Returns the contract-checked proxy of self.
325
+ def checked_self
326
+ @checked_self || self
327
+ end
328
+ end
329
+
330
+ # A ProcContract encapsulates knowledge about the signature of a method and
331
+ # can check arrays of values against the signature through the
332
+ # +check_equivalence!+ method.
333
+ class ProcContract # :nodoc:
334
+ attr_accessor :accepts, :returns
335
+
336
+ def initialize
337
+ @accepts, @returns = [], []
338
+ end
339
+
340
+ # Accepts signatures of the form:
341
+ # Clause => Clause
342
+ # [ Clause, Clause ] => Clause
343
+ def signature=(contract_hash)
344
+ raise ArgumentError unless contract_hash.length == 1
345
+ sig_accepts, sig_returns = [ contract_hash.keys.first, contract_hash.values.first ].map {|v| arrayify v}
346
+ self.accepts = sig_accepts
347
+ self.returns = sig_returns
348
+ end
349
+
350
+ def check_accepts!(*args, &block)
351
+ @accepts.each_with_index do |expected_arg, i|
352
+ # Varargs: consume all remaining arguments.
353
+ if expected_arg.is_a? Array
354
+ check_varargs!(args, expected_arg.first, i) and break
355
+ end
356
+ check_equivalence!(args[i], expected_arg)
357
+ end
358
+ end
359
+
360
+ def check_returns!(*args)
361
+ @returns.each_with_index do |expected, i|
362
+ check_equivalence!(args[i], expected)
363
+ end
364
+ end
365
+
366
+ def accepts_varargs?
367
+ accepts.last.is_a? Array
368
+ end
369
+
370
+ private
371
+ def check_varargs!(given_args, expected, index)
372
+ given_args[index..-1].each {|arg| check_equivalence!(arg, expected)}
373
+ end
374
+
375
+ def arrayify(value_or_array)
376
+ value_or_array.is_a?(Array) ? value_or_array : [ value_or_array ]
377
+ end
378
+
379
+
380
+ # Checks the given value against the expected value using === and throws
381
+ # :contract if it fails. This is a bit clunky.
382
+ def check_equivalence!(given, expected)
383
+ unless expected === given
384
+ mesg = "expected #{expected.inspect}, received #{given.inspect}"
385
+ throw :contract, ContractViolation.new(mesg)
386
+ end
387
+ end
388
+ end
389
+
390
+ # Class representing method contracts. Not for external use.
391
+ class MethodContract < ProcContract # :nodoc:
392
+ attr_accessor :preconditions, :postconditions
393
+ attr_reader :block_contract
394
+
395
+ def initialize(method_name)
396
+ @method_name = method_name
397
+ @preconditions, @postconditions = [], []
398
+ @accepts, @returns = [], []
399
+ end
400
+
401
+ def check_accepts!(*args, &block)
402
+ super(*args, &block)
403
+ if expects_block?
404
+ check_equivalence!(block, Proc)
405
+ end
406
+ end
407
+
408
+ # Returns true only if this MethodContract has been set up to check
409
+ # for one or more contract conditions.
410
+ def defined?
411
+ [ preconditions, postconditions, accepts, returns ].any? do |ary|
412
+ not ary.empty?
413
+ end
414
+ end
415
+
416
+ # Checks the postconditions of this contract against the given object
417
+ # and return values. Any assertions thrown are re-raised as
418
+ # Handshake::AssertionViolation errors.
419
+ def check_post!(o, *args)
420
+ check_conditions!(o, args, @postconditions)
421
+ end
422
+
423
+ # Checks the preconditions of this contract against the given object
424
+ # and arugment values. Any assertions thrown are re-raised as
425
+ # Handshake::AssertionFailed errors.
426
+ def check_pre!(o, *args)
427
+ check_conditions!(o, args, @preconditions)
428
+ end
429
+
430
+ # Checks the given conditions against the object, passing the given args
431
+ # into the block. Throws :contract if any fail or if an exception is
432
+ # raised. Because of the need to evaluate the condition in the context
433
+ # of the object itself, a temporary method called +bound_condition_passes?+
434
+ # is defined on the object, using the block associated with the condition.
435
+ # TODO Is there a better way to evaluate an arbitary block in a particular binding? There must be.
436
+ def check_conditions!(o, args, conditions)
437
+ conditions.each do |condition|
438
+ o.class.instance_eval do
439
+ define_method(:bound_condition_passes?, &(condition.block))
440
+ end
441
+ begin
442
+ o.bound_condition_passes?(*args)
443
+ rescue Test::Unit::AssertionFailedError => afe
444
+ throw :contract, AssertionFailed.new(afe.message)
445
+ rescue Exception => e
446
+ throw :contract, e
447
+ end
448
+ o.class.send(:remove_method, :bound_condition_passes?)
449
+ end
450
+ end
451
+
452
+ # If the last argument is a Block, handle it as a special case. We
453
+ # do this to ensure that there's no conflict with any real arguments
454
+ # which may accept Procs.
455
+ def accepts=(args)
456
+ if args.last == Block # Transform into a ProcContract
457
+ args.pop
458
+ @block_contract = ProcContract.new
459
+ @block_contract.accepts = ClauseMethods::ANYTHING
460
+ @block_contract.returns = ClauseMethods::ANYTHING
461
+ elsif args.last.is_a?(ProcContract)
462
+ @block_contract = args.pop
463
+ end
464
+
465
+ if args.find_all {|o| o.is_a? Array}.length > 1
466
+ raise ContractError, "Cannot define more than one expected variable argument"
467
+ end
468
+ super(args)
469
+ end
470
+
471
+ def expects_block?
472
+ not @block_contract.nil?
473
+ end
474
+
475
+ end
476
+
477
+ # Specifies a condition on a method. Not for external use.
478
+ class MethodCondition # :nodoc:
479
+ attr_accessor :message, :block
480
+ def initialize(message=nil, &block)
481
+ @message, @block = message, block
482
+ end
483
+ end
484
+
485
+ # This class defines a class invariant, which has a block and an optional
486
+ # method. Not for external use.
487
+ class Invariant # :nodoc:
488
+ def initialize(mesg=nil, &block)
489
+ @mesg = mesg
490
+ @block = block
491
+ end
492
+ # Any -> Boolean
493
+ # Evaluates this class's block in the binding of the given object.
494
+ def holds?(o)
495
+ block = @block
496
+ o.instance_eval &block
497
+ end
498
+ def mesg
499
+ @mesg || "Invariant check failed"
500
+ end
501
+ end
502
+
503
+ # This class filters all method calls to its proxied object through any
504
+ # contracts defined on that object's class. It attempts to look and act
505
+ # like its proxied object for all intents and purposes, although it notably
506
+ # does not proxy +__id__+, +__send__+, or +class+.
507
+ class Proxy
508
+ NOT_PROXIED = [ "__id__", "__send__" ]
509
+ SELF_PROXIED = Object.instance_methods - NOT_PROXIED
510
+
511
+ # Redefine language-level methods inherited from Object, ensuring that
512
+ # they are forwarded to the proxy object.
513
+ proxy_self *SELF_PROXIED
514
+
515
+ # Accepts an object to be proxied.
516
+ def initialize(proxied)
517
+ @proxied = proxied
518
+ end
519
+
520
+ # Returns the wrapped object. Method calls made against this object
521
+ # will not be checked.
522
+ def unchecked!
523
+ @proxied
524
+ end
525
+
526
+ # Returns the class of the proxied object.
527
+ def proxied_class
528
+ @proxied.class
529
+ end
530
+
531
+ # Override the send method, and alias method_missing to same.
532
+ # This method intercepts all method calls and runs them through the
533
+ # contract filter. The order of contract checks is as follows:
534
+ # * Before: invariants, method signature, precondition
535
+ # * Method is called
536
+ # * After: method signature, postcondition, invariants
537
+ def send(meth_name, *args, &block)
538
+ meth_string = "#{@proxied.class}##{meth_name}"
539
+ contract = @proxied.class.contract_for(meth_name)
540
+ return_val = nil
541
+ # Use throw/catch rather than raise/rescue in order to pull exceptions
542
+ # once and only once from within the stack trace.
543
+ Handshake.catch_contract("Contract violated in call to #{meth_string}") do
544
+ @proxied.check_invariants!
545
+ contract.check_accepts! *args, &block
546
+ contract.check_pre! @proxied, *args
547
+ end
548
+
549
+ # make actual call, wrapping the given block in a new block so that
550
+ # contract checks work if receiver uses yield.
551
+ return_val = nil
552
+ if contract.expects_block?
553
+ block.contract = contract.block_contract
554
+ return_val = @proxied.send(meth_name, *args) { |*argz| block.call(*argz) }
555
+ else
556
+ return_val = @proxied.send(meth_name, *args, &block)
557
+ end
558
+
559
+ Handshake.catch_contract("Contract violated by #{meth_string}") do
560
+ contract.check_returns! return_val
561
+ contract.check_post! @proxied, *(args << return_val)
562
+ @proxied.check_invariants!
563
+ end
564
+
565
+ return return_val
566
+ end
567
+ alias :method_missing :send
568
+
569
+ end
570
+
571
+ class ContractViolation < RuntimeError; end
572
+ class AssertionFailed < ContractViolation; end
573
+ class ContractError < RuntimeError; end
574
+ end
575
+
576
+ module Test # :nodoc:
577
+ module Unit # :nodoc:
578
+ module Assertions
579
+ # Asserts that the given block violates the contract by raising an
580
+ # instance of Handshake::ContractViolation.
581
+ def assert_violation(&block)
582
+ assert_raise(Handshake::ContractViolation, Handshake::AssertionFailed, &block)
583
+ end
584
+
585
+ def assert_passes(&block)
586
+ assert_nothing_raised(&block)
587
+ end
588
+ end
589
+ end
590
+ end
data/test/tc_handshake.rb CHANGED
@@ -491,4 +491,58 @@ class TestContract < Test::Unit::TestCase
491
491
  assert_passes { AcceptsSuperAndSub.new.call Subclass.new }
492
492
  assert_passes { Superclass.new == Subclass.new }
493
493
  end
494
+
495
+ class CheckedSelf
496
+ include Handshake
497
+ def call_checked(obj)
498
+ checked_self.call(obj)
499
+ end
500
+ def call_unchecked(obj)
501
+ call(obj)
502
+ end
503
+ contract String => anything
504
+ def call(str); str; end
505
+ end
506
+
507
+ class ExtendsCheckedSelf < CheckedSelf
508
+ private
509
+ contract Numeric => anything
510
+ def call(n); n; end
511
+ end
512
+
513
+ def test_checked_self
514
+ assert_violation { CheckedSelf.new.call(5) }
515
+ assert_violation { CheckedSelf.new.call_checked(5) }
516
+ assert_passes { CheckedSelf.new.call_unchecked(5) }
517
+ assert_passes { CheckedSelf.new.call_checked("foo") }
518
+ assert_violation { ExtendsCheckedSelf.new.call_checked("foo") }
519
+ assert_passes { ExtendsCheckedSelf.new.call_checked(5) }
520
+ assert_passes { ExtendsCheckedSelf.new.call_unchecked("foo") }
521
+ end
522
+
523
+ class CheckedBlockContract
524
+ include Handshake
525
+
526
+ contract [ anything, Block(String => Integer) ] => Integer
527
+ def yields(value); yield(value); end
528
+
529
+ contract [ anything, Block(String => Integer) ] => Integer
530
+ def calls(value, &block); block.call(value); end
531
+ end
532
+
533
+ def test_checked_block_contract_yields
534
+ assert_violation { CheckedBlockContract.new.yields("3") {|s| s.to_s } }
535
+ assert_violation { CheckedBlockContract.new.yields("3") {|s| "foo" } }
536
+ assert_violation { CheckedBlockContract.new.yields(3) {|s| s.to_i} }
537
+ assert_passes { CheckedBlockContract.new.yields("3") {|s| 3 } }
538
+ assert_passes { CheckedBlockContract.new.yields("3") {|s| s.to_i } }
539
+ end
540
+
541
+ def test_checked_block_contract_calls
542
+ assert_violation { CheckedBlockContract.new.calls("3") {|s| s.to_s } }
543
+ assert_violation { CheckedBlockContract.new.calls("3") {|s| "foo" } }
544
+ assert_violation { CheckedBlockContract.new.calls(3) {|s| s.to_i} }
545
+ assert_passes { CheckedBlockContract.new.calls("3") {|s| 3 } }
546
+ assert_passes { CheckedBlockContract.new.calls("3") {|s| s.to_i } }
547
+ end
494
548
  end
metadata CHANGED
@@ -3,8 +3,8 @@ rubygems_version: 0.9.0
3
3
  specification_version: 1
4
4
  name: handshake
5
5
  version: !ruby/object:Gem::Version
6
- version: 0.1.0
7
- date: 2007-03-26 00:00:00 -04:00
6
+ version: 0.2.0
7
+ date: 2007-05-01 00:00:00 -04:00
8
8
  summary: Handshake is a simple design-by-contract system for Ruby.
9
9
  require_paths:
10
10
  - lib
@@ -30,12 +30,14 @@ authors:
30
30
  - Brian Guthrie
31
31
  files:
32
32
  - Manifest.txt
33
- - README
33
+ - README.txt
34
34
  - MIT-LICENSE
35
35
  - Rakefile
36
36
  - lib/handshake.rb
37
- - lib/handshake/handshake.rb
37
+ - lib/handshake/block_contract.rb
38
+ - lib/handshake/clause_methods.rb
38
39
  - lib/handshake/inheritable_attributes.rb
40
+ - lib/handshake/proxy_self.rb
39
41
  - lib/handshake/version.rb
40
42
  - test/tc_handshake.rb
41
43
  test_files: