eeml 0.0.1

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,39 @@
1
+ #Provides some helper methods for modifying xml (as plain text). (removing and replacing nodes)
2
+ #so far, this module knows nothing about libxml, only about xml (and very roughly)
3
+
4
+ #This is intended for inclusion in Test::Unit::TestCase.
5
+ #If you're not mixing it in to such a context,
6
+ #note that it will need the assertions definitions to work:
7
+ #require 'test/unit/assertions'
8
+ #include Test::Unit::Assertions
9
+ module LibXMLTestHelper
10
+ #from given xml snippet str, removes first encountered node with the given name.
11
+ #e.g. ALL of FIRST title AND nested in the following
12
+ #<title>text here<n>other stuff</n></title><other_stuff><title>this one untouched</title>...
13
+ def remove_first_node_called(name, original_xml)
14
+ return remove_nodes_called(name, original_xml, true)
15
+ end
16
+
17
+ #from given xml snippet str, removes either first node or all nodes encountered with the given name.
18
+ def remove_nodes_called(name, original_xml, limit_to_first = false)
19
+ new_xml = replace_nodes_called(name, original_xml, '', limit_to_first)
20
+ assert_not_equal original_xml.gsub(/\n\w*/,''), new_xml.gsub(/\n\w*/,''), "shouldn't be equal - should have stripped '#{name}' tag"
21
+ return new_xml
22
+ end
23
+
24
+ #from given xml snippet str, replaces the first node with the given name, with the given contents.
25
+ def replace_first_node_called(name, original_xml, replacement_string)
26
+ replace_nodes_called(name, original_xml, replacement_string, true)
27
+ end
28
+
29
+ #from given xml snippet str, replaces either first node or all nodes encountered (of the given name) with the given contents.
30
+ def replace_nodes_called(name, original_xml, replacement_string, limit_to_first = false)
31
+ operation = limit_to_first ? :sub : :gsub
32
+ assert_not_nil original_xml, 'given nil for original xml to work with'
33
+ new_xml = original_xml.send(operation, /<#{name}.*?<\/#{name}>/m,replacement_string)#m is multiline. #*? is don't be greedy or we'll get more than first.
34
+ assert_not_equal original_xml.gsub(/\n\w*/,''), new_xml.gsub(/\n\w*/,''), "shouldn't be equal - should have replaced '#{name}' tag with #{replacement_string}"
35
+ assert_not_nil new_xml, 'substitution returned nil - something went wrong'
36
+ return new_xml
37
+ end
38
+ end
39
+
@@ -0,0 +1,370 @@
1
+ require File.join(File.dirname(__FILE__), "test_helper")
2
+
3
+ # spec sketch is here:
4
+ # http://mulube.unfuddle.com/projects/9384/notebooks/2319/pages/102977/latest
5
+ #TODO: split this into separate files, testing the xml parsing, the xml generation, etc.
6
+ class TestEnvironment < Test::Unit::TestCase
7
+ include LibXMLTestHelper
8
+
9
+
10
+ #TODO: this is mostly testing eeml V005 parser, not environment generally
11
+
12
+ test "creating Env obj from unparseable XML" do
13
+ assert_raises BadXML do
14
+ environment = Environment.new_from_eeml("this is bad xml")
15
+ end
16
+ assert_raises BadXML do
17
+ environment = Environment.new_from_eeml("12, 14, 99, a csv!, 99")
18
+ end
19
+ end
20
+
21
+ # Create an object from an eeml document
22
+ test 'creating Env obj - attrs' do
23
+ env = create_env_from_xml_file_one
24
+
25
+ # simple text-nodes available as attributes
26
+ assert_equal('title here', env.title)
27
+ assert_equal('description here', env.description)
28
+ assert_equal('http://example.com/api/1247.xml', env.feed_url)
29
+ assert_equal('http://example.com/studio/', env.website)
30
+ assert_equal('frozen', env.status)
31
+ assert_equal('someone@example.com', env.email)
32
+ assert_equal('http://example.com/some/icon.gif', env.icon)
33
+ #env attrs
34
+ assert_equal('1247', env.identifier)
35
+ assert_equal('2009-02-11T10:56:56Z', env.updated.strftime("%Y-%m-%dT%H:%M:%SZ"))
36
+ assert_equal('http://example.com/creator/', env.creator)
37
+ end
38
+
39
+ test 'location parses ok' do
40
+ env = create_env_from_xml_file_one
41
+
42
+ #<location domain="physical" exposure="outdoor" disposition="mobile">
43
+ # <name>Up on the roof (somewhere)</name>
44
+ # <lat>50.1</lat>
45
+ # <lon>48.7</lon>
46
+ # <ele>1.34</ele>
47
+ #</location>
48
+ assert_not_nil env.location
49
+ location = env.location
50
+ assert_equal "Up on the roof (somewhere)", location.name
51
+ assert_equal "50.1", location.latitude
52
+ assert_equal "48.7", location.longitude
53
+ assert_equal "1.34", location.elevation
54
+ assert_equal "physical", location.domain
55
+ assert_equal "outdoor", location.exposure
56
+ assert_equal "mobile", location.disposition
57
+ end
58
+
59
+
60
+ test "datastreams parse ok" do
61
+ env = create_env_from_xml_file_one
62
+
63
+ assert_equal 3, env.datastreams.size
64
+
65
+ #the expected values for the datastreams in the test doc
66
+ expectations_list = [
67
+ #id, tag array, min, max, value, unit_symbol, unit_type, unit_value
68
+ ['0', ['tagD0'], '-9999.0', '1022.0', '0', 'C', 'basicSI', 'Celsius'],
69
+ ['1', [], '0.0', '1023.0', '33', nil, nil, nil],
70
+ ['2', ['tagD2a', 'tagD2b', 'tagD2c'], '23.4', '1021.0', '42.1', nil, nil, nil]
71
+ ]
72
+ env.datastreams.each_with_index do |ds, i|
73
+
74
+ e_identifier, e_tags, e_min_value, e_max_value, e_value, e_unit_symbol, e_unit_type, e_unit_value = expectations_list[i]
75
+
76
+ assert_equal e_identifier, ds.identifier
77
+
78
+ assert_equal e_tags.size, ds.tags.size, "should have right num of tags"
79
+
80
+ e_tags.each do |expected_tag|
81
+ ds.tags.member? expected_tag
82
+ end
83
+
84
+ assert_equal e_min_value, ds.min_value
85
+ assert_equal e_max_value, ds.max_value
86
+ assert_equal e_value, ds.value
87
+ assert_equal e_unit_symbol, ds.unit_symbol
88
+ assert_equal e_unit_type, ds.unit_type
89
+ assert_equal e_unit_value, ds.unit_value
90
+ end
91
+
92
+ end
93
+
94
+ implement "should not try to parse huge xml string" do
95
+ #TODO: arguably, fetcher should also have rejected huge string
96
+ end
97
+
98
+ # should this actually pass? Location is essentially a datastream in it's own right, so a feed
99
+ # that only specified location should probably be alright.
100
+ test "passes if no datastreams" do
101
+ original_xml = File.read('test/data/doc_1.xml')
102
+ test_xml = remove_nodes_called('data', original_xml)
103
+
104
+ environment = Environment.new_from_eeml(test_xml)
105
+
106
+ assert_equal('title here', environment.title)
107
+ end
108
+
109
+ test "fail if any datastream has no value" do
110
+ test_xml = remove_first_node_called('value', read_xml_file_one)
111
+ assert_raises(DataMissingValue) do
112
+ Environment.new_from_eeml(test_xml)
113
+ end
114
+ end
115
+
116
+ test "fail if any datastream has two values" do
117
+ new_value_snippet=<<-END
118
+ <value minValue="20" maxValue="909">10</value>
119
+ <value minValue="-2" maxValue="9.0">14</value>
120
+ END
121
+ original_xml = read_xml_file_one
122
+ test_xml = replace_first_node_called('value', original_xml, new_value_snippet)
123
+ assert_raises(DataHasMultipleValues) { Environment.new_from_eeml(test_xml) }
124
+ end
125
+
126
+ test "fail if any datastream has two units" do
127
+ new_unit_snippet =<<-END
128
+ <unit symbol="C" type="basicSI">Celsius</unit>
129
+ <unit symbol="C" type="basicSI">Celsius</unit>
130
+ END
131
+ original_xml = read_xml_file_one
132
+ test_xml = replace_first_node_called('unit', original_xml, new_unit_snippet)
133
+ assert_raises(DataHasMultipleUnits) { Environment.new_from_eeml(test_xml) }
134
+ end
135
+
136
+ test "ok if no location" do
137
+ original_xml = read_xml_file_one
138
+ test_xml = remove_first_node_called('location', original_xml)
139
+
140
+ env = Environment.new_from_eeml(test_xml)
141
+
142
+ # all other attributes still accessible
143
+ assert_equal('title here', env.title)
144
+ assert_equal('description here', env.description)
145
+ assert_equal('http://example.com/api/1247.xml', env.feed_url)
146
+ assert_equal('http://example.com/studio/', env.website)
147
+ assert_equal('frozen', env.status)
148
+ assert_equal('someone@example.com', env.email)
149
+ assert_equal('http://example.com/some/icon.gif', env.icon)
150
+ #env attrs
151
+ assert_equal('1247', env.identifier)
152
+ assert_equal('2009-02-11T10:56:56Z', env.updated.strftime(XML_TIME_FORMAT_STRING))
153
+ assert_equal('http://example.com/creator/', env.creator)
154
+ end
155
+
156
+ test "ok parsing most minimal eeml file" do
157
+ env = create_env_from_xml_file('minimal.xml')
158
+ assert_equal 1, env.datastreams.size
159
+
160
+ datastream = env.datastreams[0]
161
+ assert_equal "0", datastream.identifier
162
+ # this value.value is a bit ugly
163
+ assert_equal "36.2", datastream.value
164
+ end
165
+
166
+ def read_xml_file_one
167
+ return File.read('test/data/doc_1.xml')
168
+ end
169
+
170
+ test "parses_all_previously_acceptable_feeds" do
171
+ passed_filenames = []
172
+ dir_name = 'test/data/real_xmls/v005/oks/'
173
+ #check dir exists:
174
+ filenames = Dir.entries(dir_name).select{|name| name =~ /.xml/}.sort
175
+ assert filenames.size > 1, "should be at least one file found in #{dir_name}"
176
+ filenames.each do |filename|
177
+ begin
178
+ xml = File.read(File.join(dir_name, filename))
179
+ env = Environment.new_from_eeml(xml)
180
+ passed_filenames << filename
181
+ rescue
182
+ #FileUtils.mv(File.join(dir_name, filename), 'test/real_xmls/v005/questioned')
183
+ raise "While parsing previously acceptable xml in file #{filename} got exception: #{$!.class}: #{$!}." +
184
+ "\nSTART OF XML SNIPPET from #{filename}\n#{xml[0..100]}\nEND OF XML SNIPPET" +
185
+ "\n Prior to error, successfully parsing #{passed_filenames.size} previously-ok xmls"
186
+ end
187
+ end
188
+ puts "passed all %d files" % passed_filenames.size
189
+ end
190
+
191
+ implement "throw detailed exception if missing mandatory node" do
192
+ original_xml = read_xml_file_one
193
+ #we'll test for
194
+ %w(status email).each do |missing_node_name|
195
+ test_xml = remove_first_node_called(missing_node_name, original_xml) #make test document from generic one
196
+ #should throw an exception with the right details
197
+ begin
198
+ Environment.new_from_eeml(test_xml)
199
+ flunk "should've raised an exception"
200
+ rescue MissingNode => e
201
+ assert_equal missing_node_name, e.sought_description, 'should have correct missing node name'
202
+ end
203
+ end
204
+ end
205
+
206
+ test "ignore unexpected attributes on title node" do
207
+ original_xml = read_xml_file_one
208
+ test_xml = original_xml.sub(/<title>/, '<title myattr="unexpected attr here">')
209
+ assert test_xml =~ /myattr/, 'substitution should have placed unexpected "myattr"'
210
+ env = Environment.new_from_eeml(test_xml)
211
+ assert_equal 'title here', env.title
212
+ end
213
+
214
+ test "handle missing optional nodes" do
215
+ original_xml = read_xml_file_one
216
+ #we'll remove the following
217
+ ['title', 'website', ['feed', 'feed_url']].each do |missing_node_name, accessor_name|
218
+ accessor_name = missing_node_name if accessor_name.nil?
219
+ test_xml = remove_first_node_called(missing_node_name, original_xml) #make test document from generic one
220
+ env = Environment.new_from_eeml(test_xml)
221
+ assert_nil env.send((accessor_name).to_sym)
222
+ end
223
+ end
224
+
225
+ # implement "fail if location missing" do
226
+ # original_xml = read_xml_file_one
227
+ # test_xml = remove_first_node_called('location', original_xml)
228
+ # #should throw an exception with the right details
229
+ # begin
230
+ # Environment.new_from_eeml(test_xml)
231
+ # flunk "should've raised an exception for missing location"
232
+ # rescue MissingNode => e
233
+ # assert_equal 'location', e.sought_description, 'should have correct missing node name'
234
+ # end
235
+ # end
236
+
237
+ # --- ----------------------------------------------------
238
+ # --- construction stuff ----------------------------------------------------
239
+ # --- ----------------------------------------------------
240
+
241
+ test "env constructor uses params" do
242
+ env = Environment.new(:identifier => 'whatever', :creator => 'http://www.example.com/ourstudio/',
243
+ :feed_url => 'http://www.example.com/ourstudio/feeds/house.xml')
244
+ assert_equal 'whatever', env.identifier
245
+ assert_equal 'http://www.example.com/ourstudio/', env.creator
246
+ assert_equal 'http://www.example.com/ourstudio/feeds/house.xml', env.feed_url
247
+ end
248
+
249
+ # --- ------------------------------------------------------------
250
+ # --- json stuff ------------------------------------------------------------
251
+ # --- ------------------------------------------------------------
252
+
253
+ test "creating Env obj from a JSON string" do
254
+ env = create_env_from_json_file_one
255
+ assert_not_nil env
256
+ assert_instance_of Environment, env
257
+ end
258
+
259
+
260
+ # --- -------------------------------------------------------------
261
+ # --- csv stuff -------------------------------------------------------------
262
+ # --- -------------------------------------------------------------
263
+
264
+ def create_env_with_datastream_values(values)
265
+ e = Environment.new
266
+ values.each do |v|
267
+ e.add_datastream DataStream.new(:value => v)
268
+ end
269
+ assert_equal values.size, e.datastreams.size
270
+ return e
271
+ end
272
+
273
+ test "update from csv values" do
274
+
275
+ #add a helper method to get an environments datastream values (ignoring min, max) as a simple array
276
+ #TODO: note, can't unload the class before other tests, so be careful with this approach to conveniences.
277
+ class Eeml::Environment
278
+ def values_quick
279
+ datastreams.map {|ds| ds.value}
280
+ end
281
+ end
282
+
283
+ values = ['11.01', '2', 'thirdval', '40'].freeze
284
+ #this env has NO datastreams, so each value will add a ds.
285
+ env = Environment.new
286
+ env.update_datastreams_from_csv_values!(values)
287
+ assert_equal values, env.values_quick
288
+
289
+ #When env has FEWER datastreams than the csv
290
+ #it should update the ones it has, AND add extra datastreams for the excess in the csv
291
+ #TODO: check this assumption:
292
+ # ALL datastreams should take their new value from csv, keeping previous min, max values.
293
+ env = create_env_with_datastream_values(%w(e1 e2 e3))
294
+ env.update_datastreams_from_csv_values!(values)
295
+ assert_equal values, env.values_quick
296
+
297
+ #When env has MORE datastreams than the csv
298
+ #it "should remove extra datastreams if less values are published to the csv feed" do
299
+ env = create_env_with_datastream_values(%w(e1 e2 e3 e4 e5))
300
+ env.update_datastreams_from_csv_values!(values)
301
+ assert_equal values, env.values_quick
302
+
303
+ #WHEN env has SAME number of ds as the csv
304
+ env = create_env_with_datastream_values(%w(e1 e2 e3 e4))
305
+ env.update_datastreams_from_csv_values!(values)
306
+ assert_equal values, env.values_quick
307
+ end
308
+ # --- ----------------------------------------------------------
309
+ # --- output stuff ----------------------------------------------------------
310
+ # --- ----------------------------------------------------------
311
+
312
+ test "should output to xml ok" do
313
+ env = create_env_from_xml_file_one
314
+ xml_output = env.to_eeml
315
+ assert_not_nil xml_output
316
+ #File.open('out.xml', 'w') {|f| f.write xml_output }
317
+
318
+ expected_xml = File.read('test/data/doc_1.xml')
319
+ assert_equal expected_xml, xml_output
320
+ end
321
+
322
+ implement "output - check that 'feed' url is correctly assembled" do
323
+ end
324
+
325
+ test "to_eeml of empty env should work" do
326
+ env = Environment.new
327
+ output_xml = env.to_eeml
328
+ #TODO: Review this 'expected' xml document for when env's empty. I've cheated so far.
329
+ # Perhaps we don't allow serialization at all when basics are absent.
330
+ expected_xml = File.read('test/data/out_empty.xml')
331
+ assert_equal expected_xml, output_xml
332
+ end
333
+
334
+
335
+
336
+ # --- convenience stuff - don't put tests under here ------------------------------
337
+
338
+
339
+
340
+
341
+
342
+ def create_env_from_xml_string(string)
343
+ environment = Environment.new_from_eeml(string)
344
+ assert_not_nil environment
345
+ assert_equal(Environment, environment.class)
346
+ return environment
347
+ end
348
+
349
+ def create_env_from_xml_file_one
350
+ return create_env_from_xml_file('doc_1.xml')
351
+ end
352
+
353
+ def create_env_from_xml_file(test_file)
354
+ filename = 'test/data/' + test_file
355
+ eeml_doc = File.read(filename)
356
+ assert_not_nil eeml_doc, "content of test xml file shouldn't be nil for file: #{filename}"
357
+ return create_env_from_xml_string(eeml_doc)
358
+ end
359
+
360
+ def create_env_from_json_file_one
361
+ return create_env_from_json_file('test/data/doc_1.json')
362
+ end
363
+
364
+ def create_env_from_json_file(filename)
365
+ json = File.read(filename)
366
+ return Environment.new_from_json(json)
367
+ end
368
+
369
+
370
+ end
@@ -0,0 +1,56 @@
1
+ require 'rubygems'
2
+ require 'test/unit'
3
+ require 'test/libxml_test_helper'
4
+
5
+ # Thought a mock library will be useful for testing out any stuff that
6
+ # does any http-ing, but have no strong feelings about which one to use.
7
+ # Looking at the syntax of each of the libraries, I liked the look of the
8
+ # mocha syntax more, so was thinking to use that when required.
9
+ #
10
+ # require 'flexmock'
11
+ require 'mocha'
12
+
13
+
14
+
15
+ #consider adding custom assertions to clean code up
16
+ #http://www.ruby-doc.org/stdlib/libdoc/test/unit/rdoc/classes/Test/Unit/Assertions.html
17
+
18
+
19
+ # Tiny patch to Test::Unit that lets us write our tests in a slightly
20
+ # more rspec style way. Similar thing is used in jeremymcanally's context
21
+ # gem (http://github.com/jeremymcanally/context/tree/master), but thought
22
+ # could do without the extra dependency
23
+ class Test::Unit::TestCase
24
+ def self.test(name, &block)
25
+ test_name = "test_#{name.gsub(/\s+/,'_')}".to_sym
26
+ defined = instance_method(test_name) rescue false
27
+ raise "#{test_name} is already defined in #{self}" if defined
28
+ if block_given?
29
+ define_method(test_name, &block)
30
+ else
31
+ define_method(test_name) do
32
+ flunk "No implementation provided for #{name}"
33
+ end
34
+ end
35
+ end
36
+
37
+ def self.implement(name, &block)
38
+ puts "UNIMPLEMENTED: #{name}"
39
+ end
40
+
41
+ def add_line_numbers(str)
42
+ return nil if str.nil?
43
+ out = ''
44
+ str.split(/\n/).each_with_index do |line, i|
45
+ out << "#{i+1}: #{line}\n"
46
+ end
47
+ out
48
+ end
49
+
50
+ end
51
+
52
+ $:.unshift(File.join(File.dirname(__FILE__), "..", "lib"))
53
+
54
+ require 'eeml'
55
+
56
+ include Eeml
@@ -0,0 +1,9 @@
1
+ require File.join(File.dirname(__FILE__), "test_helper")
2
+ require 'lib/eeml/libxml_eeml_parser_v005.rb'
3
+ class TestLibXMLEemlParserV005 < Test::Unit::TestCase
4
+ test "missing node ex raises detailed exception" do
5
+ p = LibXMLEemlParserV005.new
6
+ #p.make_environment_from_xml('bad')
7
+ end
8
+
9
+ end
@@ -0,0 +1,85 @@
1
+ require File.join(File.dirname(__FILE__), "test_helper")
2
+
3
+ class TestLibXMLTestHelper < Test::Unit::TestCase
4
+ include LibXMLTestHelper
5
+ #testing the convenience methods!
6
+ def test_remove_nodes
7
+ input_xml=<<-END
8
+ <list>
9
+ <food type='dairy' name='milk'>
10
+ <cost>32</cost>
11
+ </food>
12
+ <thing2></thing2>
13
+ <food type='other' name='food2'>
14
+ </food>
15
+ </list>
16
+ END
17
+
18
+ #test remove only first node
19
+ expected_xml=<<-END
20
+ <list>
21
+ <thing2></thing2>
22
+ <food type='other' name='food2'>
23
+ </food>
24
+ </list>
25
+ END
26
+ assert_equal expected_xml.gsub(/\n\W*/, ''), remove_first_node_called('food', input_xml).gsub(/\n\W*/, '')
27
+
28
+ #test remove all nodes
29
+ expected_xml=<<-END
30
+ <list>
31
+ <thing2></thing2>
32
+ </list>
33
+ END
34
+ assert_equal expected_xml.gsub(/\n\W*/, ''), remove_nodes_called('food', input_xml).gsub(/\n\W*/, '')
35
+
36
+ end
37
+
38
+
39
+
40
+
41
+ #testing the convenience methods!
42
+ def test_replace_nodes
43
+ input_xml=<<-END
44
+ <list>
45
+ <food type='dairy' name='milk'>
46
+ <cost>32</cost>
47
+ </food>
48
+ <thing2></thing2>
49
+ <food type='other' name='food2'>
50
+ </food>
51
+ </list>
52
+ END
53
+
54
+
55
+ #test replace only first node
56
+ expected_xml=<<-END
57
+ <list>
58
+ <snack name='lions'></snack><snack name='tigers'></snack>
59
+ <thing2></thing2>
60
+ <food type='other' name='food2'>
61
+ </food>
62
+ </list>
63
+ END
64
+ replacement_string = "<snack name='lions'></snack><snack name='tigers'></snack>"
65
+
66
+ output = replace_nodes_called('food', input_xml, replacement_string, true)
67
+ assert_equal expected_xml.gsub(/\n\W*/, ''), output.gsub(/\n\W*/, '')
68
+
69
+ output = replace_first_node_called('food', input_xml, replacement_string)
70
+ assert_equal expected_xml.gsub(/\n\W*/, ''), output.gsub(/\n\W*/, '')
71
+
72
+ #test replace all matching nodes
73
+ expected_xml=<<-END
74
+ <list>
75
+ <snack name='lions'></snack><snack name='tigers'></snack>
76
+ <thing2></thing2>
77
+ <snack name='lions'></snack><snack name='tigers'></snack>
78
+ </list>
79
+ END
80
+ output = replace_nodes_called('food', input_xml, replacement_string, false)
81
+ assert_equal expected_xml.gsub(/\n\W*/, ''), output.gsub(/\n\W*/, '')
82
+
83
+ end
84
+ end
85
+
data.tar.gz.sig ADDED
@@ -0,0 +1 @@
1
+ ������^T Jg����(?�P�e�����1�U�F�-͎t?21��ϫ��!�:�t9��8��~[S޽$�Y8e����e D�A�b���c� �P���ѻl�Ø>����ה.:�����v҂)���d-s���9 #IW