handshake 0.1.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/MIT-LICENSE +20 -0
- data/Manifest.txt +10 -0
- data/README +33 -0
- data/Rakefile +54 -0
- data/lib/handshake/handshake.rb +737 -0
- data/lib/handshake/inheritable_attributes.rb +132 -0
- data/lib/handshake/version.rb +8 -0
- data/lib/handshake.rb +1 -0
- data/test/tc_handshake.rb +494 -0
- metadata +54 -0
@@ -0,0 +1,132 @@
|
|
1
|
+
# Copyright (c) 2004-2006 David Heinemeier Hansson
|
2
|
+
#
|
3
|
+
# Permission is hereby granted, free of charge, to any person obtaining
|
4
|
+
# a copy of this software and associated documentation files (the
|
5
|
+
# "Software"), to deal in the Software without restriction, including
|
6
|
+
# without limitation the rights to use, copy, modify, merge, publish,
|
7
|
+
# distribute, sublicense, and/or sell copies of the Software, and to
|
8
|
+
# permit persons to whom the Software is furnished to do so, subject to
|
9
|
+
# the following conditions:
|
10
|
+
#
|
11
|
+
# The above copyright notice and this permission notice shall be
|
12
|
+
# included in all copies or substantial portions of the Software.
|
13
|
+
#
|
14
|
+
# THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
|
15
|
+
# EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
|
16
|
+
# MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
|
17
|
+
# NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE
|
18
|
+
# LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION
|
19
|
+
# OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION
|
20
|
+
# WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
|
21
|
+
|
22
|
+
# Allows attributes to be shared within an inheritance hierarchy, but where each descendant gets a copy of
|
23
|
+
# their parents' attributes, instead of just a pointer to the same. This means that the child can add elements
|
24
|
+
# to, for example, an array without those additions being shared with either their parent, siblings, or
|
25
|
+
# children, which is unlike the regular class-level attributes that are shared across the entire hierarchy.
|
26
|
+
class Class # :nodoc:
|
27
|
+
def class_inheritable_reader(*syms)
|
28
|
+
syms.each do |sym|
|
29
|
+
class_eval <<-EOS
|
30
|
+
def self.#{sym}
|
31
|
+
read_inheritable_attribute(:#{sym})
|
32
|
+
end
|
33
|
+
|
34
|
+
def #{sym}
|
35
|
+
self.class.#{sym}
|
36
|
+
end
|
37
|
+
EOS
|
38
|
+
end
|
39
|
+
end
|
40
|
+
|
41
|
+
def class_inheritable_writer(*syms)
|
42
|
+
syms.each do |sym|
|
43
|
+
class_eval <<-EOS
|
44
|
+
def self.#{sym}=(obj)
|
45
|
+
write_inheritable_attribute(:#{sym}, obj)
|
46
|
+
end
|
47
|
+
|
48
|
+
def #{sym}=(obj)
|
49
|
+
self.class.#{sym} = obj
|
50
|
+
end
|
51
|
+
EOS
|
52
|
+
end
|
53
|
+
end
|
54
|
+
|
55
|
+
def class_inheritable_array_writer(*syms)
|
56
|
+
syms.each do |sym|
|
57
|
+
class_eval <<-EOS
|
58
|
+
def self.#{sym}=(obj)
|
59
|
+
write_inheritable_array(:#{sym}, obj)
|
60
|
+
end
|
61
|
+
|
62
|
+
def #{sym}=(obj)
|
63
|
+
self.class.#{sym} = obj
|
64
|
+
end
|
65
|
+
EOS
|
66
|
+
end
|
67
|
+
end
|
68
|
+
|
69
|
+
def class_inheritable_hash_writer(*syms)
|
70
|
+
syms.each do |sym|
|
71
|
+
class_eval <<-EOS
|
72
|
+
def self.#{sym}=(obj)
|
73
|
+
write_inheritable_hash(:#{sym}, obj)
|
74
|
+
end
|
75
|
+
|
76
|
+
def #{sym}=(obj)
|
77
|
+
self.class.#{sym} = obj
|
78
|
+
end
|
79
|
+
EOS
|
80
|
+
end
|
81
|
+
end
|
82
|
+
|
83
|
+
def class_inheritable_accessor(*syms)
|
84
|
+
class_inheritable_reader(*syms)
|
85
|
+
class_inheritable_writer(*syms)
|
86
|
+
end
|
87
|
+
|
88
|
+
def class_inheritable_array(*syms)
|
89
|
+
class_inheritable_reader(*syms)
|
90
|
+
class_inheritable_array_writer(*syms)
|
91
|
+
end
|
92
|
+
|
93
|
+
def class_inheritable_hash(*syms)
|
94
|
+
class_inheritable_reader(*syms)
|
95
|
+
class_inheritable_hash_writer(*syms)
|
96
|
+
end
|
97
|
+
|
98
|
+
def inheritable_attributes
|
99
|
+
@inheritable_attributes ||= {}
|
100
|
+
end
|
101
|
+
|
102
|
+
def write_inheritable_attribute(key, value)
|
103
|
+
inheritable_attributes[key] = value
|
104
|
+
end
|
105
|
+
|
106
|
+
def write_inheritable_array(key, elements)
|
107
|
+
write_inheritable_attribute(key, []) if read_inheritable_attribute(key).nil?
|
108
|
+
write_inheritable_attribute(key, read_inheritable_attribute(key) + elements)
|
109
|
+
end
|
110
|
+
|
111
|
+
def write_inheritable_hash(key, hash)
|
112
|
+
write_inheritable_attribute(key, {}) if read_inheritable_attribute(key).nil?
|
113
|
+
write_inheritable_attribute(key, read_inheritable_attribute(key).merge(hash))
|
114
|
+
end
|
115
|
+
|
116
|
+
def read_inheritable_attribute(key)
|
117
|
+
inheritable_attributes[key]
|
118
|
+
end
|
119
|
+
|
120
|
+
def reset_inheritable_attributes
|
121
|
+
inheritable_attributes.clear
|
122
|
+
end
|
123
|
+
|
124
|
+
private
|
125
|
+
def inherited_with_inheritable_attributes(child)
|
126
|
+
inherited_without_inheritable_attributes(child) if respond_to?(:inherited_without_inheritable_attributes)
|
127
|
+
child.instance_variable_set('@inheritable_attributes', inheritable_attributes.dup)
|
128
|
+
end
|
129
|
+
|
130
|
+
alias inherited_without_inheritable_attributes inherited
|
131
|
+
alias inherited inherited_with_inheritable_attributes
|
132
|
+
end
|
data/lib/handshake.rb
ADDED
@@ -0,0 +1 @@
|
|
1
|
+
Dir[File.join(File.dirname(__FILE__), 'handshake/**/*.rb')].sort.each { |lib| require lib }
|
@@ -0,0 +1,494 @@
|
|
1
|
+
# test_handshake.rb
|
2
|
+
# Copyright (c) 2007 Brian Guthrie
|
3
|
+
#
|
4
|
+
# Permission is hereby granted, free of charge, to any person obtaining
|
5
|
+
# a copy of this software and associated documentation files (the
|
6
|
+
# "Software"), to deal in the Software without restriction, including
|
7
|
+
# without limitation the rights to use, copy, modify, merge, publish,
|
8
|
+
# distribute, sublicense, and/or sell copies of the Software, and to
|
9
|
+
# permit persons to whom the Software is furnished to do so, subject to
|
10
|
+
# the following conditions:
|
11
|
+
#
|
12
|
+
# The above copyright notice and this permission notice shall be
|
13
|
+
# included in all copies or substantial portions of the Software.
|
14
|
+
#
|
15
|
+
# THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
|
16
|
+
# EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
|
17
|
+
# MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
|
18
|
+
# NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE
|
19
|
+
# LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION
|
20
|
+
# OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION
|
21
|
+
# WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
|
22
|
+
|
23
|
+
require 'test/unit'
|
24
|
+
require 'handshake'
|
25
|
+
|
26
|
+
class TestContract < Test::Unit::TestCase
|
27
|
+
|
28
|
+
class InvariantDeclarations
|
29
|
+
include Handshake
|
30
|
+
invariant { true }
|
31
|
+
end
|
32
|
+
|
33
|
+
class ExtendsInvariantDeclarations < InvariantDeclarations
|
34
|
+
invariant { true }
|
35
|
+
end
|
36
|
+
|
37
|
+
def test_invariant_declarations
|
38
|
+
assert_equal 1, InvariantDeclarations.invariants.length
|
39
|
+
assert_equal 2, ExtendsInvariantDeclarations.invariants.length
|
40
|
+
end
|
41
|
+
|
42
|
+
class NonFunctionalArray < Array
|
43
|
+
include Handshake
|
44
|
+
invariant { false }
|
45
|
+
end
|
46
|
+
|
47
|
+
def test_basic_invariant
|
48
|
+
assert_violation { NonFunctionalArray.new }
|
49
|
+
end
|
50
|
+
|
51
|
+
class NonEmptyArray < Array
|
52
|
+
include Handshake
|
53
|
+
invariant { not empty? }
|
54
|
+
end
|
55
|
+
class ExtendsNonEmptyArray < NonEmptyArray; end
|
56
|
+
|
57
|
+
class PositiveBalance
|
58
|
+
include Handshake
|
59
|
+
invariant { @balance > 0 }
|
60
|
+
attr_accessor :balance
|
61
|
+
def initialize(balance); @balance = balance; end
|
62
|
+
end
|
63
|
+
|
64
|
+
def test_invariants
|
65
|
+
assert_violation { NonEmptyArray.new }
|
66
|
+
assert_passes { NonEmptyArray.new [1] }
|
67
|
+
assert_violation { ExtendsNonEmptyArray.new }
|
68
|
+
assert_passes { ExtendsNonEmptyArray.new [1] }
|
69
|
+
|
70
|
+
assert_violation { NonEmptyArray.new([1]).pop }
|
71
|
+
|
72
|
+
assert_violation { PositiveBalance.new -10 }
|
73
|
+
assert_violation { PositiveBalance.new 0 }
|
74
|
+
assert_passes { PositiveBalance.new 10 }
|
75
|
+
assert_violation {
|
76
|
+
pb = PositiveBalance.new(10); pb.balance = -10
|
77
|
+
}
|
78
|
+
end
|
79
|
+
|
80
|
+
class MethodDeclarations
|
81
|
+
include Handshake
|
82
|
+
contract :accepts_str, String => anything
|
83
|
+
contract :accepts_int, Integer => anything
|
84
|
+
def accepts_str(str); str; end
|
85
|
+
def accepts_int(int); int; end
|
86
|
+
end
|
87
|
+
class ExtendsMethodDeclarations < MethodDeclarations; end
|
88
|
+
|
89
|
+
def test_method_declarations
|
90
|
+
assert MethodDeclarations.method_contracts.has_key?(:accepts_str)
|
91
|
+
assert MethodDeclarations.method_contracts.has_key?(:accepts_int)
|
92
|
+
assert ExtendsMethodDeclarations.method_contracts.has_key?(:accepts_str)
|
93
|
+
assert ExtendsMethodDeclarations.method_contracts.has_key?(:accepts_int)
|
94
|
+
end
|
95
|
+
|
96
|
+
class AcceptsString
|
97
|
+
include Handshake
|
98
|
+
contract :initialize, String => anything
|
99
|
+
def initialize(str); @str = str; end
|
100
|
+
contract String => anything
|
101
|
+
def str=(str); @str = str; end
|
102
|
+
end
|
103
|
+
class ExtendsAcceptsString < AcceptsString; end
|
104
|
+
class AcceptsIntegerInstead < AcceptsString
|
105
|
+
contract :initialize, Integer => anything
|
106
|
+
end
|
107
|
+
class AcceptsSymbolInstead < AcceptsString
|
108
|
+
contract :initialize, Symbol => anything
|
109
|
+
end
|
110
|
+
|
111
|
+
def test_method_accepts
|
112
|
+
assert_violation { AcceptsString.new 3 }
|
113
|
+
assert_violation { AcceptsString.new :foo }
|
114
|
+
assert_passes { AcceptsString.new "string" }
|
115
|
+
assert_violation { AcceptsString.new("foo").str = 3 }
|
116
|
+
assert_violation { ExtendsAcceptsString.new 3 }
|
117
|
+
assert_violation { ExtendsAcceptsString.new :foo }
|
118
|
+
assert_passes { ExtendsAcceptsString.new "string" }
|
119
|
+
assert_violation { ExtendsAcceptsString.new("foo").str = 3 }
|
120
|
+
assert_violation { AcceptsIntegerInstead.new("foo") }
|
121
|
+
assert_passes { AcceptsIntegerInstead.new 3 }
|
122
|
+
assert_violation { AcceptsSymbolInstead.new "foo" }
|
123
|
+
assert_violation { AcceptsSymbolInstead.new 3 }
|
124
|
+
assert_passes { AcceptsSymbolInstead.new :foo }
|
125
|
+
end
|
126
|
+
|
127
|
+
class ReturnsString
|
128
|
+
include Handshake
|
129
|
+
contract String => anything
|
130
|
+
def call(val); val; end
|
131
|
+
end
|
132
|
+
class ExtendsReturnsString < ReturnsString; end
|
133
|
+
|
134
|
+
def test_method_returns
|
135
|
+
assert_violation { ReturnsString.new.call(1) }
|
136
|
+
assert_violation { ReturnsString.new.call(true) }
|
137
|
+
assert_passes { ReturnsString.new.call("foo") }
|
138
|
+
assert_violation { ExtendsReturnsString.new.call(1) }
|
139
|
+
assert_violation { ExtendsReturnsString.new.call(true) }
|
140
|
+
assert_passes { ExtendsReturnsString.new.call("foo") }
|
141
|
+
end
|
142
|
+
|
143
|
+
class ReturnsMultiple
|
144
|
+
include Handshake
|
145
|
+
contract [ String, Integer ] => anything
|
146
|
+
def call(arg1, arg2); return arg1, arg2; end
|
147
|
+
end
|
148
|
+
|
149
|
+
def test_method_returns_multiple
|
150
|
+
assert_violation { ReturnsMultiple.new.call("foo", "foo") }
|
151
|
+
assert_violation { ReturnsMultiple.new.call(3, 3) }
|
152
|
+
assert_passes { ReturnsMultiple.new.call("foo", 3) }
|
153
|
+
end
|
154
|
+
|
155
|
+
class AcceptsVarargs
|
156
|
+
include Handshake
|
157
|
+
contract [[ String ]] => anything
|
158
|
+
def initialize(*strs); @strs = strs; end
|
159
|
+
end
|
160
|
+
|
161
|
+
def test_method_accepts_varargs
|
162
|
+
assert_passes { AcceptsVarargs.new }
|
163
|
+
assert_violation { AcceptsVarargs.new(1, 2, 3) }
|
164
|
+
assert_violation { AcceptsVarargs.new("foo", 1, 2) }
|
165
|
+
assert_violation { AcceptsVarargs.new(:foo, "foo") }
|
166
|
+
assert_passes { AcceptsVarargs.new("foo") }
|
167
|
+
assert_passes { AcceptsVarargs.new("foo1", "foo2") }
|
168
|
+
end
|
169
|
+
|
170
|
+
class AcceptsBlock
|
171
|
+
include Handshake
|
172
|
+
contract Block => anything
|
173
|
+
def call1; end
|
174
|
+
contract Block => anything
|
175
|
+
def call2(&block); end
|
176
|
+
end
|
177
|
+
|
178
|
+
def test_method_accepts_block
|
179
|
+
assert_violation { AcceptsBlock.new.call1 }
|
180
|
+
assert_violation { AcceptsBlock.new.call2 }
|
181
|
+
assert_passes { AcceptsBlock.new.call1 { true } }
|
182
|
+
assert_passes { AcceptsBlock.new.call2 { true } }
|
183
|
+
assert_passes { AcceptsBlock.new.call1 { "foo" } }
|
184
|
+
assert_violation { AcceptsBlock.new.call1("foo") }
|
185
|
+
assert_violation { AcceptsBlock.new.call2("foo") }
|
186
|
+
end
|
187
|
+
|
188
|
+
class AcceptsWriter
|
189
|
+
include Handshake
|
190
|
+
contract String => anything
|
191
|
+
def val=(str); @str = str; end
|
192
|
+
end
|
193
|
+
|
194
|
+
def test_writer_method_accepts_str
|
195
|
+
assert_violation { AcceptsWriter.new.val = 3 }
|
196
|
+
assert_violation { AcceptsWriter.new.val = :foo }
|
197
|
+
assert_passes { AcceptsWriter.new.val = "foo" }
|
198
|
+
end
|
199
|
+
|
200
|
+
# EXCELSIOR!
|
201
|
+
class AcceptsMixed
|
202
|
+
include Handshake
|
203
|
+
contract [ String, String, [ Integer ], Block ] => String
|
204
|
+
def call(str1, str2, *ints, &block); "foo"; end
|
205
|
+
end
|
206
|
+
|
207
|
+
def test_method_mixed
|
208
|
+
assert_violation { AcceptsMixed.new.call }
|
209
|
+
assert_violation { AcceptsMixed.new.call 3 }
|
210
|
+
assert_violation { AcceptsMixed.new.call "foo" }
|
211
|
+
assert_violation { AcceptsMixed.new.call "foo", 3 }
|
212
|
+
assert_violation { AcceptsMixed.new.call "foo", "bar" }
|
213
|
+
assert_passes { AcceptsMixed.new.call("foo", "bar") { true } }
|
214
|
+
assert_passes { AcceptsMixed.new.call("foo", "bar", 3) { true } }
|
215
|
+
assert_passes { AcceptsMixed.new.call("foo", "bar", 3, 4, 5) { true } }
|
216
|
+
end
|
217
|
+
|
218
|
+
class AcceptsSimpleAssertion
|
219
|
+
include Handshake
|
220
|
+
equals_foo = clause {|o| o == "foo"}
|
221
|
+
contract [ equals_foo ] => anything
|
222
|
+
def call(foo)
|
223
|
+
return foo
|
224
|
+
end
|
225
|
+
end
|
226
|
+
|
227
|
+
def test_method_simple_assertion
|
228
|
+
assert_violation { AcceptsSimpleAssertion.new.call }
|
229
|
+
assert_violation { AcceptsSimpleAssertion.new.call 3 }
|
230
|
+
assert_violation { AcceptsSimpleAssertion.new.call "bar", "bar" }
|
231
|
+
assert_passes { AcceptsSimpleAssertion.new.call "foo" }
|
232
|
+
end
|
233
|
+
|
234
|
+
class AcceptsAll
|
235
|
+
include Handshake
|
236
|
+
equals_five = clause {|o| o == 5}
|
237
|
+
contract all?(Integer, equals_five) => anything
|
238
|
+
def initialize(n); end
|
239
|
+
end
|
240
|
+
|
241
|
+
def test_accepts_all_of
|
242
|
+
assert_violation { AcceptsAll.new "foo" }
|
243
|
+
assert_violation { AcceptsAll.new 3 }
|
244
|
+
assert_violation { AcceptsAll.new 5.0 }
|
245
|
+
assert_passes { AcceptsAll.new 5 }
|
246
|
+
end
|
247
|
+
|
248
|
+
class AcceptsAny
|
249
|
+
include Handshake
|
250
|
+
equals_five = clause {|o| o == 5}
|
251
|
+
equals_three = clause {|o| o == 3}
|
252
|
+
contract any?(equals_five, equals_three) => anything
|
253
|
+
def three_or_five(n); end
|
254
|
+
contract any?(String, Integer, Symbol) => anything
|
255
|
+
def str_int_sym(o); end
|
256
|
+
end
|
257
|
+
|
258
|
+
def test_accepts_any_of
|
259
|
+
assert_violation { AcceptsAny.new.three_or_five "foo" }
|
260
|
+
assert_violation { AcceptsAny.new.three_or_five 7 }
|
261
|
+
assert_violation { AcceptsAny.new.three_or_five 8, 9 }
|
262
|
+
assert_passes { AcceptsAny.new.three_or_five 3 }
|
263
|
+
assert_passes { AcceptsAny.new.three_or_five 5 }
|
264
|
+
|
265
|
+
assert_violation { AcceptsAny.new.str_int_sym 5.3 }
|
266
|
+
assert_raises(ArgumentError) { AcceptsAny.new.str_int_sym "str", 3, :sym }
|
267
|
+
assert_passes { AcceptsAny.new.str_int_sym "str" }
|
268
|
+
assert_passes { AcceptsAny.new.str_int_sym 3 }
|
269
|
+
assert_passes { AcceptsAny.new.str_int_sym :foo }
|
270
|
+
end
|
271
|
+
|
272
|
+
class AcceptsNot
|
273
|
+
include Handshake
|
274
|
+
contract not?(String) => anything
|
275
|
+
def initialize(not_str); end
|
276
|
+
end
|
277
|
+
|
278
|
+
def test_accepts_not_string
|
279
|
+
assert_violation { AcceptsNot.new "string" }
|
280
|
+
assert_passes { AcceptsNot.new 3 }
|
281
|
+
assert_passes { AcceptsNot.new :symbol }
|
282
|
+
end
|
283
|
+
|
284
|
+
class AcceptsBoolean
|
285
|
+
include Handshake
|
286
|
+
contract boolean? => anything
|
287
|
+
def initialize(bool); end
|
288
|
+
end
|
289
|
+
|
290
|
+
def test_accepts_boolean
|
291
|
+
assert_violation { AcceptsBoolean.new "foo" }
|
292
|
+
assert_violation { AcceptsBoolean.new :foo }
|
293
|
+
assert_passes { AcceptsBoolean.new true }
|
294
|
+
assert_passes { AcceptsBoolean.new false }
|
295
|
+
end
|
296
|
+
|
297
|
+
class AcceptsNonzero
|
298
|
+
include Handshake
|
299
|
+
contract nonzero? => anything
|
300
|
+
def initialize(nonzero); end
|
301
|
+
end
|
302
|
+
|
303
|
+
def test_accepts_nonzero
|
304
|
+
assert_violation { AcceptsNonzero.new :foo }
|
305
|
+
assert_violation { AcceptsNonzero.new 0 }
|
306
|
+
assert_passes { AcceptsNonzero.new 3 }
|
307
|
+
end
|
308
|
+
|
309
|
+
class AcceptsHashOf
|
310
|
+
include Handshake
|
311
|
+
contract hash_of?(Symbol, String) => anything
|
312
|
+
def initialize(arg={}); end
|
313
|
+
end
|
314
|
+
|
315
|
+
def test_hash_of_sym_string
|
316
|
+
assert_passes { AcceptsHashOf.new({}) }
|
317
|
+
assert_passes { AcceptsHashOf.new({ :symbol => "String" }) }
|
318
|
+
assert_violation { AcceptsHashOf.new({ :another => :symbol }) }
|
319
|
+
assert_violation { AcceptsHashOf.new({ "two" => "strings" }) }
|
320
|
+
assert_violation { AcceptsHashOf.new({ false => true }) }
|
321
|
+
end
|
322
|
+
|
323
|
+
class AcceptsHashWithKeys
|
324
|
+
include Handshake
|
325
|
+
contract hash_with_keys(:foo, :bar) => anything
|
326
|
+
def initialize(options={}); end
|
327
|
+
end
|
328
|
+
|
329
|
+
def test_hash_with_keys_foo_bar
|
330
|
+
assert_passes { AcceptsHashWithKeys.new({}) }
|
331
|
+
assert_passes { AcceptsHashWithKeys.new({ :foo => "anything" }) }
|
332
|
+
assert_passes { AcceptsHashWithKeys.new({ :bar => "anything" }) }
|
333
|
+
assert_passes { AcceptsHashWithKeys.new({ :foo => "anything", :bar => "goes" }) }
|
334
|
+
assert_violation { AcceptsHashWithKeys.new({ :arbitrary => "key" }) }
|
335
|
+
end
|
336
|
+
|
337
|
+
class AcceptsHashContract
|
338
|
+
include Handshake
|
339
|
+
contract hash_contract({ :foo => String, :bar => Integer, :baz => Symbol }) => anything
|
340
|
+
def initialize(options={}); end
|
341
|
+
end
|
342
|
+
|
343
|
+
def test_hash_contract
|
344
|
+
assert_passes { AcceptsHashContract.new({}) }
|
345
|
+
assert_passes { AcceptsHashContract.new({ :foo => "bar"}) }
|
346
|
+
assert_violation { AcceptsHashContract.new({ :foo => :bar}) }
|
347
|
+
assert_passes { AcceptsHashContract.new({ :bar => 3 }) }
|
348
|
+
assert_violation { AcceptsHashContract.new({ :bar => "foo" }) }
|
349
|
+
assert_passes { AcceptsHashContract.new({ :baz => :foo }) }
|
350
|
+
assert_violation { AcceptsHashContract.new({ :baz => "baz" }) }
|
351
|
+
assert_passes { AcceptsHashContract.new({ :foo => "bar", :bar => 3, :baz => :qux }) }
|
352
|
+
end
|
353
|
+
|
354
|
+
class AcceptsRespondsTo
|
355
|
+
include Handshake
|
356
|
+
contract responds_to?(:each, :first) => anything
|
357
|
+
def initialize(duck_array); end
|
358
|
+
end
|
359
|
+
|
360
|
+
def test_responds_to_each_first
|
361
|
+
assert_violation { AcceptsRespondsTo.new({}) }
|
362
|
+
assert_violation { AcceptsRespondsTo.new "foo" }
|
363
|
+
assert_violation { AcceptsRespondsTo.new 3 }
|
364
|
+
assert_passes { AcceptsRespondsTo.new([]) }
|
365
|
+
end
|
366
|
+
|
367
|
+
class AcceptsIsA
|
368
|
+
include Handshake
|
369
|
+
contract is?(:String) => is?(:Symbol)
|
370
|
+
def call_is_a(str); return str.intern; end
|
371
|
+
end
|
372
|
+
|
373
|
+
def test_accepts_is_string_symbol
|
374
|
+
assert_violation { AcceptsIsA.new.call_is_a(3) }
|
375
|
+
assert_violation { AcceptsIsA.new.call_is_a(:foo) }
|
376
|
+
assert_passes { AcceptsIsA.new.call_is_a("foo") }
|
377
|
+
end
|
378
|
+
|
379
|
+
class SimpleBeforeCondition
|
380
|
+
include Handshake
|
381
|
+
before { assert false }
|
382
|
+
def call_fails; end
|
383
|
+
def call_passes; end
|
384
|
+
end
|
385
|
+
class ExtendsSimpleBeforeCondition < SimpleBeforeCondition; end
|
386
|
+
|
387
|
+
def test_simple_before_condition
|
388
|
+
assert_equal(1, SimpleBeforeCondition.method_contracts.length)
|
389
|
+
assert_not_nil(SimpleBeforeCondition.method_contracts[:call_fails])
|
390
|
+
assert_violation { SimpleBeforeCondition.new.call_fails }
|
391
|
+
assert_passes { SimpleBeforeCondition.new.call_passes }
|
392
|
+
assert_equal(1, ExtendsSimpleBeforeCondition.method_contracts.length)
|
393
|
+
assert_not_nil(ExtendsSimpleBeforeCondition.method_contracts[:call_fails])
|
394
|
+
assert_violation { ExtendsSimpleBeforeCondition.new.call_fails }
|
395
|
+
assert_passes { ExtendsSimpleBeforeCondition.new.call_passes }
|
396
|
+
end
|
397
|
+
|
398
|
+
class SimpleAfterCondition
|
399
|
+
include Handshake
|
400
|
+
after { |accepted, returned| assert returned }
|
401
|
+
def call(bool); bool; end
|
402
|
+
end
|
403
|
+
|
404
|
+
def test_simple_after_condition
|
405
|
+
assert_equal(1, SimpleAfterCondition.method_contracts.length)
|
406
|
+
assert_not_nil(SimpleAfterCondition.method_contracts[:call])
|
407
|
+
assert_violation { SimpleAfterCondition.new.call(false) }
|
408
|
+
assert_violation { SimpleAfterCondition.new.call(nil) }
|
409
|
+
assert_passes { SimpleAfterCondition.new.call(true) }
|
410
|
+
assert_passes { SimpleAfterCondition.new.call("foo") }
|
411
|
+
end
|
412
|
+
|
413
|
+
class SimpleAroundCondition
|
414
|
+
include Handshake
|
415
|
+
around {|arg| assert(!arg) }
|
416
|
+
def call(bool); bool; end
|
417
|
+
end
|
418
|
+
|
419
|
+
def test_simple_around_condition
|
420
|
+
[ 1, :foo, true, false, "bar", 8.3, nil ].each do |val|
|
421
|
+
assert_violation { SimpleAroundCondition.new.call(val) }
|
422
|
+
end
|
423
|
+
end
|
424
|
+
|
425
|
+
class ScopedBeforeCondition
|
426
|
+
include Handshake
|
427
|
+
def initialize(bool); @bool = bool; end
|
428
|
+
before { assert @bool }
|
429
|
+
def call; end
|
430
|
+
end
|
431
|
+
|
432
|
+
def test_scoped_before_condition
|
433
|
+
assert_violation { ScopedBeforeCondition.new(false).call }
|
434
|
+
assert_passes { ScopedBeforeCondition.new(true).call }
|
435
|
+
end
|
436
|
+
|
437
|
+
class ContractAccessor
|
438
|
+
include Handshake
|
439
|
+
contract_reader :foo => String
|
440
|
+
contract_writer :bar => Integer
|
441
|
+
contract_accessor :baz => Symbol, :qux => Float
|
442
|
+
def initialize(foo=nil); @foo = foo; end
|
443
|
+
end
|
444
|
+
|
445
|
+
def test_contract_accessor
|
446
|
+
assert_equal(6, ContractAccessor.method_contracts.length)
|
447
|
+
assert_violation { ContractAccessor.new.foo }
|
448
|
+
assert_violation { ContractAccessor.new(3).foo }
|
449
|
+
assert_passes { ContractAccessor.new("foo").foo }
|
450
|
+
assert_violation { ContractAccessor.new.bar = "bar" }
|
451
|
+
assert_passes { ContractAccessor.new.bar = 3 }
|
452
|
+
assert_violation { ContractAccessor.new.baz = "3" }
|
453
|
+
assert_violation { ContractAccessor.new.qux = 3 }
|
454
|
+
assert_passes { ContractAccessor.new.baz = :baz }
|
455
|
+
assert_passes { ContractAccessor.new.qux = 3.3 }
|
456
|
+
end
|
457
|
+
|
458
|
+
class BeforeClauseAssert
|
459
|
+
include Handshake
|
460
|
+
|
461
|
+
before do |arg|
|
462
|
+
assert_equal("foo", arg, "arg must equal foo")
|
463
|
+
end; def call(arg)
|
464
|
+
arg
|
465
|
+
end
|
466
|
+
end
|
467
|
+
|
468
|
+
def test_before_clause_assert
|
469
|
+
assert_violation { BeforeClauseAssert.new.call 3 }
|
470
|
+
assert_violation { BeforeClauseAssert.new.call "bar" }
|
471
|
+
assert_passes { BeforeClauseAssert.new.call "foo" }
|
472
|
+
end
|
473
|
+
|
474
|
+
class Superclass
|
475
|
+
include Handshake
|
476
|
+
contract Superclass => boolean?
|
477
|
+
def ==(other); self.class === other; end
|
478
|
+
end
|
479
|
+
|
480
|
+
class Subclass < Superclass; end
|
481
|
+
|
482
|
+
class AcceptsSuperAndSub
|
483
|
+
include Handshake
|
484
|
+
contract Superclass => anything
|
485
|
+
def call(cls); cls; end
|
486
|
+
end
|
487
|
+
|
488
|
+
def test_accepts_super_and_sub
|
489
|
+
assert_violation { AcceptsSuperAndSub.new.call 3 }
|
490
|
+
assert_passes { AcceptsSuperAndSub.new.call Superclass.new }
|
491
|
+
assert_passes { AcceptsSuperAndSub.new.call Subclass.new }
|
492
|
+
assert_passes { Superclass.new == Subclass.new }
|
493
|
+
end
|
494
|
+
end
|
metadata
ADDED
@@ -0,0 +1,54 @@
|
|
1
|
+
--- !ruby/object:Gem::Specification
|
2
|
+
rubygems_version: 0.9.0
|
3
|
+
specification_version: 1
|
4
|
+
name: handshake
|
5
|
+
version: !ruby/object:Gem::Version
|
6
|
+
version: 0.1.0
|
7
|
+
date: 2007-03-26 00:00:00 -04:00
|
8
|
+
summary: Handshake is a simple design-by-contract system for Ruby.
|
9
|
+
require_paths:
|
10
|
+
- lib
|
11
|
+
email: btguthrie@gmail.com
|
12
|
+
homepage: http://handshake.rubyforge.org
|
13
|
+
rubyforge_project: handshake
|
14
|
+
description: Handshake is a simple design-by-contract system for Ruby.
|
15
|
+
autorequire:
|
16
|
+
default_executable:
|
17
|
+
bindir: bin
|
18
|
+
has_rdoc: true
|
19
|
+
required_ruby_version: !ruby/object:Gem::Version::Requirement
|
20
|
+
requirements:
|
21
|
+
- - ">"
|
22
|
+
- !ruby/object:Gem::Version
|
23
|
+
version: 0.0.0
|
24
|
+
version:
|
25
|
+
platform: ruby
|
26
|
+
signing_key:
|
27
|
+
cert_chain:
|
28
|
+
post_install_message:
|
29
|
+
authors:
|
30
|
+
- Brian Guthrie
|
31
|
+
files:
|
32
|
+
- Manifest.txt
|
33
|
+
- README
|
34
|
+
- MIT-LICENSE
|
35
|
+
- Rakefile
|
36
|
+
- lib/handshake.rb
|
37
|
+
- lib/handshake/handshake.rb
|
38
|
+
- lib/handshake/inheritable_attributes.rb
|
39
|
+
- lib/handshake/version.rb
|
40
|
+
- test/tc_handshake.rb
|
41
|
+
test_files:
|
42
|
+
- test/tc_handshake.rb
|
43
|
+
rdoc_options: []
|
44
|
+
|
45
|
+
extra_rdoc_files: []
|
46
|
+
|
47
|
+
executables: []
|
48
|
+
|
49
|
+
extensions: []
|
50
|
+
|
51
|
+
requirements: []
|
52
|
+
|
53
|
+
dependencies: []
|
54
|
+
|