handshake 0.1.0 → 0.2.0

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