ECS 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.
@@ -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