ECS 0.1.0

Sign up to get free protection for your applications and to get access to all the features.
@@ -0,0 +1,67 @@
1
+ #!/usr/local/bin/ruby
2
+
3
+ module ECS
4
+ class HelpResponseGroup
5
+ @xml=''
6
+
7
+ instance_variable_set( '@response_group_name', nil )
8
+ instance_variable_set( '@valid_operations', nil )
9
+ instance_variable_set( '@elements', nil )
10
+ instance_variable_set( '@help_xml', nil )
11
+ # @@response_group_name=nil
12
+
13
+ # @@help_map = {
14
+ # :valid_operations => { :call_path => 'HelpResponse.Information.ResponseGroupInformation.ValidOperations.Operation' },
15
+ # :elements => { :call_path => 'HelpResponse.Information.ResponseGroupInformation.Elements.Element' },
16
+ # }
17
+ # @@help_xml = nil
18
+
19
+
20
+
21
+ def self.valid_operations( force=false )
22
+ if force || @valid_operations.nil?
23
+ @valid_operations = []
24
+ self.help_xml.HelpResponse.Information.ResponseGroupInformation.ValidOperations.Operation.each do |o|
25
+ @valid_operations << o.content.to_s.to_sym
26
+ end
27
+ end
28
+ @valid_operations
29
+ end
30
+
31
+ def self.elements( force=false )
32
+ if force || @elements.nil?
33
+ @elements = []
34
+ self.help_xml.HelpResponse.Information.ResponseGroupInformation.Elements.Element.each do |el|
35
+ @elements << lambda { |xml_object| ; ( xml_object.respond_to?( :xml ) ? xml_object.xml : xml_object ).find( "//#{el.content}" ) }
36
+ end
37
+ end
38
+ @elements
39
+ end
40
+
41
+
42
+ def self.response_group_name
43
+ @response_group_name ||= self.resolve_response_group_name( self.name )
44
+ end
45
+
46
+ def self.help_xml( force=false )
47
+ if force || @help_xml.nil?
48
+ @help_xml = ECS.help( :HelpType => 'ResponseGroup', :About => self.aws_response_group_name ) { |x| ECS::Help.valid_aws_response?( x ) }.xml
49
+ end
50
+ @help_xml
51
+ end
52
+
53
+
54
+ def self.included( includer )
55
+ includer.extend( ClassMethods )
56
+ ECS::ResponseGroups[includer.response_group_name] = includer
57
+ end
58
+
59
+ def self.resolve_response_group_name( t )
60
+ t =~ /::([^:]*ResponseGroup)$/ ? $1 : t
61
+ end
62
+
63
+ def self.aws_response_group_name
64
+ self.response_group_name =~ /^(.*)ResponseGroup/ ? $1 : self.response_group_name
65
+ end
66
+ end
67
+ end
@@ -0,0 +1,39 @@
1
+ #!/usr/local/bin/ruby
2
+
3
+
4
+ module ECS
5
+ class Operations
6
+ @@klasses = {}
7
+
8
+ def self.clear
9
+ @@klasses = {}
10
+ end
11
+
12
+ def self.[]=( ideal, actual )
13
+ self.clear unless @@klasses.is_a?( Hash )
14
+ @@klasses[( ideal.is_a?( Class ) ? ideal.name : ideal.to_s ).to_sym] = actual.is_a?( Class ) ? actual : actual.class
15
+ end
16
+
17
+ def self.[]( ideal )
18
+ self.clear unless @@klasses.is_a?( Hash )
19
+ @@klasses[( ideal.is_a?( Class ) ? ideal.name : ideal.to_s ).to_sym]
20
+ end
21
+
22
+ def self.method_missing( meth, *args )
23
+ #You can send anything to ECS::Operations that you can send to a hash
24
+ #If there is a conflict between the hash's (@@klasses) method name and
25
+ #the ECS::Operations module's method name, you can call the method with o_ prepended
26
+ #
27
+ # E.G.
28
+ #
29
+ # ECS::Operations.include?( something ) # => calls the include? method on ECS::Operations
30
+ # ECS::Operations.o_include?( something ) # => calls the include? method on the @@klasses class variable
31
+ @@klasses = {} unless @@klasses.is_a?( Hash )
32
+ if meth == :each
33
+ @@klasses.each { |ideal,actual| yield( ideal, actual ) }
34
+ else
35
+ @@klasses.send( (meth.to_s =~ /^o_(.*)/ ? $1.to_sym : meth), *args )
36
+ end
37
+ end
38
+ end
39
+ end
@@ -0,0 +1,7 @@
1
+ #!/usr/local/bin/ruby
2
+
3
+
4
+ module ECS
5
+ class ResponseGroups < Operations
6
+ end
7
+ end
@@ -0,0 +1,58 @@
1
+ #!/usr/local/bin/ruby
2
+ require 'time'
3
+ require 'fileutils'
4
+ require 'yaml'
5
+
6
+ module ECS
7
+ module TimeManagement
8
+ @@period = 1.0 # seconds
9
+ @@time_file = ''
10
+
11
+ def self.sleep_duration( schroedingers_cat=true )
12
+ # if schroedingers_cat is true, observing the time_to_sleep affects
13
+ # it, effectively adding @@period to it
14
+ now = Time.now.to_f
15
+ next_available = self.next_available_timeslot
16
+ if schroedingers_cat == true
17
+ if next_available > now - @@period
18
+ next_available += @@period
19
+ else
20
+ next_available = now
21
+ end
22
+ self.next_available_timeslot = next_available
23
+ end
24
+ duration = next_available - now
25
+ duration > 0 ? duration : 0
26
+ end
27
+
28
+ def self.next_available_timeslot
29
+ if File.exist?( @@time_file )
30
+ t = YAML.load_file( @@time_file )
31
+ ( t.respond_to?( :to_f ) ? t : Time.now ).to_f
32
+ else
33
+ Time.now.to_f
34
+ end
35
+ end
36
+
37
+ def self.next_available_timeslot=( float_timestamp=nil )
38
+ raise ArgumentError, "float_timestamp must be a floating point number" unless float_timestamp.respond_to?( :to_f )
39
+ float_timestamp = Time.now.to_f if float_timestamp.nil?
40
+ File.open( @@time_file, 'w' ) { |f| f << YAML.dump( float_timestamp.to_f ) }
41
+ end
42
+
43
+
44
+ def self.time_file
45
+ @@time_file
46
+ end
47
+ def self.time_file=( f='' )
48
+ file = File.expand_path( f )
49
+ FileUtils.touch( file )
50
+ raise "#{file} is an unacceptable time file." unless File.writable?( file )
51
+ @@time_file = file
52
+ end
53
+ def self.kill_time_file
54
+ File.unlink( @@time_file )
55
+ rescue
56
+ end
57
+ end
58
+ end
@@ -0,0 +1,51 @@
1
+ #!/usr/local/bin/ruby
2
+
3
+ require 'rubygems'
4
+ require 'xml/libxml'
5
+
6
+ module XML
7
+ class Node
8
+ def method_missing( meth, *args )
9
+ # If one of the child nodes matches the string value of
10
+ # the method call that triggered this method_missing(),
11
+ # then why not use it?
12
+ #
13
+ # Furthermore, set an instance variable and create an
14
+ # instance method so we don't have to come back here again.
15
+ #
16
+ # To avoid the collision of a node name with a built-in XML::Node
17
+ # instance method (e.g., XML::Node#children would mask a <children> node),
18
+ # you can refer to the node as [node]_in_the_xml
19
+ # E.G.,
20
+ # n = XML::Node
21
+ # n.children # => calls the XML::Node children instance method
22
+ # n.children_in_the_xml # => looks for child nodes of n that happen to be named "children"
23
+ node_name = meth.to_s =~ /^(.*)_in_the_xml$/ ? $1 : meth.to_s
24
+
25
+ var_name = "@#{meth.to_s}_var"
26
+ self.instance_eval( "
27
+ def #{meth.to_s}(force=false)
28
+ if force || #{var_name}.nil?
29
+ #{var_name} = []
30
+ self.each_child { |c| #{var_name} << c if c.name == '#{node_name}' }
31
+ #{var_name} = #{var_name}.first if #{var_name}.size == 1
32
+ end
33
+ #{var_name}
34
+ end", __FILE__, __LINE__ )
35
+ self.send( meth, *args )
36
+ end
37
+ end
38
+
39
+
40
+ class Document
41
+ # In case there's already a method_missing, alias it
42
+ # Future proofing?
43
+
44
+ def method_missing( meth, *args )
45
+ return self.root if self.root.name == meth.to_s
46
+ raise NoMethodError, "XML::Document Additions: undefined method '#{meth.to_s}' for #{self.to_s}"
47
+ end
48
+
49
+ end
50
+ end
51
+
@@ -0,0 +1,15 @@
1
+ #!/usr/local/bin/ruby
2
+
3
+ class String
4
+ @camel_case = nil
5
+ @underscore = nil
6
+
7
+ def underscore
8
+ @underscore ||= self.gsub( /([A-Z]+)([A-Z][a-z])/, '\1_\2' ).gsub( /([a-z\d])([A-Z])/, '\1_\2' ).gsub( /\s\s*/, '_' ).downcase
9
+ end
10
+
11
+ def camel_case
12
+ @camel_case ||= self.underscore.capitalize.gsub( /([A-Z])([A-Z]+)/ ) { "#{$1}#{$2.downcase}" }.gsub( /_(.)/ ) { $1.upcase }
13
+ end
14
+ end
15
+
data/test/all_tests.rb ADDED
@@ -0,0 +1,16 @@
1
+ #!/usr/local/bin/ruby
2
+
3
+ def require_tests( dir )
4
+ Dir.entries( dir ).each do |f|
5
+ if f =~ /^[^\.]/
6
+ new_path = File.join( dir, f )
7
+ if f =~ /_test\.rb$/
8
+ require new_path
9
+ elsif File.directory?( new_path )
10
+ require_tests( new_path )
11
+ end
12
+ end
13
+ end
14
+ end
15
+
16
+ require_tests( File.expand_path( File.join( File.dirname( __FILE__ ), 'unit' ) ) )
@@ -0,0 +1,379 @@
1
+ #!/usr/local/bin/ruby
2
+
3
+ require 'test/unit'
4
+ require "#{File.expand_path( File.dirname( __FILE__ ) )}/unit_test_setup"
5
+
6
+ class ECSTest < Test::Unit::TestCase
7
+ def setup
8
+ ECS.default_locale = :us
9
+ ECS.cache = true
10
+ ECS.cache_directory = ECStest.default_test_cache_directory
11
+
12
+ @@temp_access_key_id = ECS.access_key_id
13
+ @@temp_associate_id = ECS.associate_id
14
+ end
15
+ def teardown
16
+ ECS.access_key_id = @@temp_access_key_id
17
+ ECS.clear_cache
18
+ ECS.associate_id = @@temp_associate_id
19
+ end
20
+
21
+ def test_requirements
22
+ assert_nothing_raised do
23
+ Gem
24
+ XML::Node
25
+ end
26
+ end
27
+
28
+ def test_response_group
29
+ rg = nil
30
+
31
+ assert_nothing_raised do
32
+ rg = ECS.request_response_group
33
+ end
34
+
35
+ assert rg.ancestors.include?( ECS::HelpResponseGroup )
36
+ assert_equal 'RequestResponseGroup', rg.response_group_name
37
+ end
38
+
39
+
40
+ def test_with_browse_node_lookup_as_the_archetype
41
+ assert_nothing_raised do
42
+ ECS.browse_node_lookup
43
+ end
44
+
45
+ b = nil
46
+ assert_nothing_raised do
47
+ b = ECS.browse_node_lookup( :something => 'or other' )
48
+ end
49
+ assert_match /BrowseNodeLookup$/, b.class.name
50
+ assert_equal 'or other', b.parameters[:something]
51
+ assert_equal nil, b.parameters[:ResponseGroup]
52
+
53
+
54
+ # make sure it's on the instance level
55
+ c = ECS.browse_node_lookup( :something => 'else entirely', :ResponseGroup => [ :Crazy ] )
56
+ assert_equal 'else entirely', c.parameters[:something]
57
+ assert_equal 'or other', b.parameters[:something]
58
+ assert c.parameters[:ResponseGroup].include?( :Request )
59
+
60
+ # see what happens when you put more than just a hash in there
61
+ assert_nothing_raised do
62
+ b = ECS.browse_node_lookup( {:something => 'or other'}, 7, false )
63
+ end
64
+ assert_equal nil, b.parameters[:something]
65
+
66
+ # Bad calls do not raise an error. You will have to check for the
67
+ # status on the xml
68
+ assert_nothing_raised do
69
+ ECS.browse_node_looku
70
+ end
71
+ end
72
+
73
+ def test_access_key_id
74
+ assert_nothing_raised do
75
+ ECS.access_key_id = 'hi there'
76
+ end
77
+ assert_equal 'hi there', ECS.access_key_id
78
+ ECS.access_key_id = 'ta ta'
79
+ assert_equal 'ta ta', ECS.access_key_id
80
+ end
81
+
82
+ def test_associate_id
83
+ assert_nothing_raised do
84
+ ECS.associate_id = 'hi there'
85
+ end
86
+ assert_equal 'hi there', ECS.associate_id
87
+ ECS.associate_id = 'ta ta'
88
+ assert_equal 'ta ta', ECS.associate_id
89
+ end
90
+
91
+
92
+
93
+ def test_default_locale
94
+ assert_equal :us, ECS.default_locale
95
+
96
+ ECS.available_locales.each do |loc|
97
+ assert_nothing_raised do
98
+ ECS.default_locale = loc
99
+ end
100
+ assert_equal loc, ECS.default_locale
101
+ end
102
+
103
+ ECS.default_locale = :ca
104
+ assert_equal :ca, ECS.default_locale
105
+ ECS.default_locale = 'nonsense'
106
+ assert_equal :ca, ECS.default_locale
107
+
108
+
109
+ ECS.default_locale = :de
110
+ assert_equal :de, ECS.default_locale
111
+ assert_nothing_raised do
112
+ ECS.reset_default_locale
113
+ end
114
+ assert_equal :us, ECS.default_locale
115
+ end
116
+
117
+
118
+ def test_resolve_locale
119
+ assert_equal :fr, ECS.resolve_locale( :fr )
120
+ assert_equal :fr, ECS.resolve_locale( :FR )
121
+ assert_equal :fr, ECS.resolve_locale( :Fr )
122
+ assert_equal :fr, ECS.resolve_locale( 'fr' )
123
+ assert_equal :fr, ECS.resolve_locale( 'FR' )
124
+ assert_equal :fr, ECS.resolve_locale( 'Fr' )
125
+ assert_equal :us, ECS.resolve_locale( 'france' )
126
+ assert_equal :us, ECS.resolve_locale( nil )
127
+ assert_equal :us, ECS.resolve_locale
128
+
129
+ ECS.default_locale = :ca
130
+ assert_equal :ca, ECS.resolve_locale( nil )
131
+ assert_equal :ca, ECS.resolve_locale
132
+ end
133
+
134
+ def test_avaialable_locales
135
+ [ :ca, :de, :fr, :jp, :uk, :us ].each do |l|
136
+ assert_equal l, ECS.resolve_locale( l )
137
+ end
138
+
139
+ ECS.available_locales.each do |l|
140
+ assert_match /^http:\/\//, ECS.base_urls[l].to_s
141
+ end
142
+ end
143
+
144
+ def test_cache_directory
145
+ assert_nothing_raised do
146
+ ECS.cache_directory = './test'
147
+ end
148
+ assert_equal File.join( Dir.getwd, 'test' ), ECS.cache_directory
149
+ end
150
+
151
+
152
+
153
+ def test_generate_dynamic_operation_class
154
+ assert_raises NameError do
155
+ ECS::SomethingCrazy
156
+ end
157
+ assert_nothing_raised do
158
+ ECS.something_crazy
159
+ end
160
+ s = ECS.something_crazy
161
+ s2 = ECS.something_crazy
162
+ assert_match /SomethingCrazy$/, s.class.name
163
+ assert_equal s.class.name, s2.class.name
164
+ assert s.class.ancestors.include?( ECS::Help )
165
+ end
166
+
167
+ def test_resolve_cache_tag
168
+ assert_equal ECS.resolve_cache_tag( nil ), ECS.resolve_cache_tag( {} )
169
+ assert_equal ECS.resolve_cache_tag( nil ), ECS.resolve_cache_tag( Fixnum )
170
+
171
+ assert_equal ECS.resolve_cache_tag( :a => 'b', :c => 'd' ), ECS.resolve_cache_tag( :c => 'd', :a => 'b' )
172
+ assert_equal ECS.resolve_cache_tag( :a => 'b', :c => 'd' ), ECS.resolve_cache_tag( 'a=b&c=d' )
173
+
174
+ assert_not_equal ECS.resolve_cache_tag( :a => 'b', :c => 'd' ), ECS.resolve_cache_tag( :c => 'd', :a => 'b', :e => 'f' )
175
+ assert_not_equal ECS.resolve_cache_tag( 'a=b&c=d' ), ECS.resolve_cache_tag( 'c=d&a=b' )
176
+ end
177
+ def test_strip_namespace
178
+ assert ECS.strip_namespace?
179
+ assert_no_match /xmlns\s*=\s*".*?"/, ECS.call_web_service( :Operation => 'Help', :HelpType => 'Operation', :About => 'Help' )
180
+
181
+ assert_nothing_raised do
182
+ ECS.strip_namespace = false
183
+ end
184
+ assert !ECS.strip_namespace?
185
+ assert_match /xmlns\s*=\s*".*?"/, ECS.call_web_service( :Operation => 'Help', :HelpType => 'Operation', :About => 'Help' )
186
+
187
+
188
+ ECS.strip_namespace = Fixnum
189
+ assert ECS.strip_namespace?
190
+ assert_no_match /xmlns\s*=\s*".*?"/, ECS.call_web_service( :Operation => 'Help', :HelpType => 'Operation', :About => 'Help' )
191
+ end
192
+
193
+ def test_read_and_write_and_clear_cache
194
+ tag = File.join( ECStest.default_test_cache_directory, "test_read_and_write_cache.ecs_cache" )
195
+
196
+ assert !ECS.cached?( tag )
197
+
198
+ assert_nothing_raised do
199
+ ECS.write_cache( tag, '<?xml version="1.0" encoding="ISO-8859-1"?><awesome><radical>Thursday</radical></awesome>' )
200
+ end
201
+
202
+ assert ECS.cached?( tag )
203
+
204
+ assert_equal 'Thursday', ECS.read_cache( tag ).awesome.radical.content
205
+
206
+ assert_nothing_raised do
207
+ ECS.clear_cache( tag )
208
+ end
209
+
210
+ assert !ECS.cached?( tag )
211
+ end
212
+
213
+ def test_clear_all_cache
214
+ tag = File.join( ECStest.default_test_cache_directory, "test_read_and_write_cache.ecs_cache" )
215
+ ECS.write_cache( tag, 'funky chicken!' )
216
+ tag = File.join( ECStest.default_test_cache_directory, "test_read_and_write_cache_2.ecs_cache" )
217
+ ECS.write_cache( tag, 'funky chicken 2!' )
218
+
219
+ assert Dir[File.join( ECS.cache_directory, "*.ecs_cache" )].size >= 2, "Actually, there are #{Dir[File.join( ECS.cache_directory, "*.#{ECS::cache_suffix}" )].size} such files."
220
+
221
+ assert_nothing_raised do
222
+ ECS.clear_cache
223
+ end
224
+
225
+ assert Dir[File.join( ECS.cache_directory, "*.#{ECS::cache_suffix}" )].size == 0
226
+ end
227
+
228
+ def test_query_string
229
+ assert_equal 'a=b&c=d', ECS.query_string( :a => 'b', :c => 'd' )
230
+ assert_equal '', ECS.query_string( 'c=d&a=b' )
231
+ end
232
+
233
+ def test_call_web_service
234
+ sleep 1 #make sure we don't need to sleep
235
+ time_1 = time_2 = time_3 = nil
236
+ xml = ''
237
+ parameters = { :Operation => 'BrowseNodeLookup', :BrowseNodeId => 22 }
238
+
239
+ assert_nothing_raised do
240
+ time_1 = Time.now.to_f
241
+ xml = ECS.call_web_service( parameters )
242
+ time_2 = Time.now.to_f
243
+ ECS.call_web_service( parameters )
244
+ time_3 = Time.now.to_f
245
+ end
246
+
247
+ assert time_3 - time_1 > 1
248
+ assert time_2 - time_1 < 1
249
+
250
+
251
+ assert_no_match /xmlns/, xml
252
+ end
253
+
254
+ def test_cache
255
+ parameters = { :Operation => 'Help', :HelpType => 'Operation', :About => 'BrowseNodeLookup' }
256
+ tag = ECS.resolve_cache_tag( parameters )
257
+ ECS.cache = true
258
+
259
+ assert !ECS.cached?( tag )
260
+ assert_nothing_raised do
261
+ ECS.xml_for_parameters( parameters ) { |x| false }
262
+ end
263
+ assert !ECS.cached?( tag )
264
+
265
+ assert_nothing_raised do
266
+ ECS.xml_for_parameters( parameters ) { |x| true }
267
+ end
268
+ assert ECS.cached?( tag )
269
+ ECS.clear_cache( tag )
270
+
271
+
272
+ # do it all again with cache? = false
273
+ ECS.cache = false
274
+
275
+ assert !ECS.cached?( tag )
276
+ assert_nothing_raised do
277
+ ECS.xml_for_parameters( parameters ) { |x| false }
278
+ end
279
+ assert !ECS.cached?( tag )
280
+
281
+ assert_nothing_raised do
282
+ ECS.xml_for_parameters( parameters ) { |x| true }
283
+ end
284
+ assert ECS.cached?( tag )
285
+ ECS.clear_cache( tag )
286
+
287
+
288
+ # false with no block should not cache
289
+ ECS.cache = false
290
+ assert !ECS.cached?( tag )
291
+ assert_nothing_raised do
292
+ ECS.xml_for_parameters( parameters )
293
+ end
294
+ assert !ECS.cached?( tag )
295
+
296
+ # true with no block should cache
297
+ ECS.cache = true
298
+ assert_nothing_raised do
299
+ ECS.xml_for_parameters( parameters )
300
+ end
301
+ assert ECS.cached?( tag )
302
+ end
303
+
304
+ def test_cache?
305
+ assert_nothing_raised do
306
+ ECS.cache = false
307
+ end
308
+ assert_equal false, ECS.cache?
309
+ ECS.cache = true
310
+ assert_equal true, ECS.cache?
311
+
312
+ ECS.cache = Fixnum
313
+ assert_equal true, ECS.cache?
314
+ end
315
+
316
+ def test_xml_for_parameters
317
+ parameters = { :Operation => 'Help', :HelpType => 'Operation', :About => 'BrowseNodeLookup' }
318
+ tag = ECS.resolve_cache_tag( parameters )
319
+
320
+ assert !ECS.cached?( tag )
321
+
322
+ xml = nil
323
+ assert_nothing_raised do
324
+ xml = ECS.xml_for_parameters( parameters )
325
+ end
326
+
327
+ assert ECS.cached?( tag )
328
+
329
+ assert_equal XML::Document, xml.class
330
+ end
331
+
332
+
333
+ def test_resolve_response_group_klass
334
+ assert_equal NilClass, ECS::ResponseGroups[:StinkyResponseGroup].class
335
+ klass = nil
336
+ assert_nothing_raised do
337
+ klass = ECS.resolve_response_group_klass( 'StinkyResponseGroup' )
338
+ end
339
+ assert !klass.nil?
340
+ assert_equal Class, klass.class
341
+ assert_equal Class, ECS::ResponseGroups[:StinkyResponseGroup].class
342
+ assert_equal klass, ECS::ResponseGroups[:StinkyResponseGroup]
343
+ end
344
+ def test_resolve_operation_klass
345
+ assert_equal NilClass, ECS::Operations[:Stinky].class
346
+ klass = nil
347
+ assert_nothing_raised do
348
+ klass = ECS.resolve_operation_klass( 'Stinky' )
349
+ end
350
+ assert !klass.nil?
351
+ assert_equal Class, klass.class
352
+ assert_equal Class, ECS::Operations[:Stinky].class
353
+ assert_equal klass, ECS::Operations[:Stinky]
354
+ end
355
+
356
+
357
+ def test_api_version
358
+ assert_equal '2007-02-22', ECS.api_version
359
+ end
360
+ end
361
+
362
+
363
+ module Test
364
+ module Unit
365
+ module Assertions
366
+ public
367
+ def assert_equal_hashes(hsh1, hsh2, message=nil)
368
+ _wrap_assertion do
369
+ assert_block( "assert_equal_hashes should not be called with a block." ) { !block_given? }
370
+ assert_block( "assert_equal_hashes expects a hash, but got a #{hsh1.class.to_s} in first parameter." ) { hsh1.is_a?( Hash ) }
371
+ assert_block( "assert_equal_hashes expects a hash, but got a #{hsh2.class.to_s} in second parameter." ) { hsh2.is_a?( Hash ) }
372
+ assert_block( build_message( message, "<?> is not equal to <?>.", hsh1, hsh2 ) ) do
373
+ hsh1 == hsh2
374
+ end
375
+ end
376
+ end
377
+ end
378
+ end
379
+ end