1
# vim: tabstop=4 shiftwidth=4 softtabstop=4
3
# Copyright 2011 OpenStack LLC.
6
# Licensed under the Apache License, Version 2.0 (the "License"); you may
7
# not use this file except in compliance with the License. You may obtain
8
# a copy of the License at
10
# http://www.apache.org/licenses/LICENSE-2.0
12
# Unless required by applicable law or agreed to in writing, software
13
# distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
14
# WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
15
# License for the specific language governing permissions and limitations
18
from lxml import etree
20
from cinder import test
21
from cinder.api.openstack import xmlutil
24
class SelectorTest(test.TestCase):
37
def test_empty_selector(self):
38
sel = xmlutil.Selector()
39
self.assertEqual(len(sel.chain), 0)
40
self.assertEqual(sel(self.obj_for_test), self.obj_for_test)
42
def test_dict_selector(self):
43
sel = xmlutil.Selector('test')
44
self.assertEqual(len(sel.chain), 1)
45
self.assertEqual(sel.chain[0], 'test')
46
self.assertEqual(sel(self.obj_for_test),
47
self.obj_for_test['test'])
49
def test_datum_selector(self):
50
sel = xmlutil.Selector('test', 'name')
51
self.assertEqual(len(sel.chain), 2)
52
self.assertEqual(sel.chain[0], 'test')
53
self.assertEqual(sel.chain[1], 'name')
54
self.assertEqual(sel(self.obj_for_test), 'test')
56
def test_list_selector(self):
57
sel = xmlutil.Selector('test', 'values', 0)
58
self.assertEqual(len(sel.chain), 3)
59
self.assertEqual(sel.chain[0], 'test')
60
self.assertEqual(sel.chain[1], 'values')
61
self.assertEqual(sel.chain[2], 0)
62
self.assertEqual(sel(self.obj_for_test), 1)
64
def test_items_selector(self):
65
sel = xmlutil.Selector('test', 'attrs', xmlutil.get_items)
66
self.assertEqual(len(sel.chain), 3)
67
self.assertEqual(sel.chain[2], xmlutil.get_items)
68
for key, val in sel(self.obj_for_test):
69
self.assertEqual(self.obj_for_test['test']['attrs'][key], val)
71
def test_missing_key_selector(self):
72
sel = xmlutil.Selector('test2', 'attrs')
73
self.assertEqual(sel(self.obj_for_test), None)
74
self.assertRaises(KeyError, sel, self.obj_for_test, True)
76
def test_constant_selector(self):
77
sel = xmlutil.ConstantSelector('Foobar')
78
self.assertEqual(sel.value, 'Foobar')
79
self.assertEqual(sel(self.obj_for_test), 'Foobar')
82
class TemplateElementTest(test.TestCase):
83
def test_element_initial_attributes(self):
84
# Create a template element with some attributes
85
elem = xmlutil.TemplateElement('test', attrib=dict(a=1, b=2, c=3),
88
# Verify all the attributes are as expected
89
expected = dict(a=1, b=2, c=4, d=5, e=6)
90
for k, v in expected.items():
91
self.assertEqual(elem.attrib[k].chain[0], v)
93
def test_element_get_attributes(self):
94
expected = dict(a=1, b=2, c=3)
96
# Create a template element with some attributes
97
elem = xmlutil.TemplateElement('test', attrib=expected)
99
# Verify that get() retrieves the attributes
100
for k, v in expected.items():
101
self.assertEqual(elem.get(k).chain[0], v)
103
def test_element_set_attributes(self):
104
attrs = dict(a=None, b='foo', c=xmlutil.Selector('foo', 'bar'))
106
# Create a bare template element with no attributes
107
elem = xmlutil.TemplateElement('test')
109
# Set the attribute values
110
for k, v in attrs.items():
113
# Now verify what got set
114
self.assertEqual(len(elem.attrib['a'].chain), 1)
115
self.assertEqual(elem.attrib['a'].chain[0], 'a')
116
self.assertEqual(len(elem.attrib['b'].chain), 1)
117
self.assertEqual(elem.attrib['b'].chain[0], 'foo')
118
self.assertEqual(elem.attrib['c'], attrs['c'])
120
def test_element_attribute_keys(self):
121
attrs = dict(a=1, b=2, c=3, d=4)
122
expected = set(attrs.keys())
124
# Create a template element with some attributes
125
elem = xmlutil.TemplateElement('test', attrib=attrs)
128
self.assertEqual(set(elem.keys()), expected)
130
def test_element_attribute_items(self):
131
expected = dict(a=xmlutil.Selector(1),
132
b=xmlutil.Selector(2),
133
c=xmlutil.Selector(3))
134
keys = set(expected.keys())
136
# Create a template element with some attributes
137
elem = xmlutil.TemplateElement('test', attrib=expected)
140
for k, v in elem.items():
141
self.assertEqual(expected[k], v)
144
# Did we visit all keys?
145
self.assertEqual(len(keys), 0)
147
def test_element_selector_none(self):
148
# Create a template element with no selector
149
elem = xmlutil.TemplateElement('test')
151
self.assertEqual(len(elem.selector.chain), 0)
153
def test_element_selector_string(self):
154
# Create a template element with a string selector
155
elem = xmlutil.TemplateElement('test', selector='test')
157
self.assertEqual(len(elem.selector.chain), 1)
158
self.assertEqual(elem.selector.chain[0], 'test')
160
def test_element_selector(self):
161
sel = xmlutil.Selector('a', 'b')
163
# Create a template element with an explicit selector
164
elem = xmlutil.TemplateElement('test', selector=sel)
166
self.assertEqual(elem.selector, sel)
168
def test_element_subselector_none(self):
169
# Create a template element with no subselector
170
elem = xmlutil.TemplateElement('test')
172
self.assertEqual(elem.subselector, None)
174
def test_element_subselector_string(self):
175
# Create a template element with a string subselector
176
elem = xmlutil.TemplateElement('test', subselector='test')
178
self.assertEqual(len(elem.subselector.chain), 1)
179
self.assertEqual(elem.subselector.chain[0], 'test')
181
def test_element_subselector(self):
182
sel = xmlutil.Selector('a', 'b')
184
# Create a template element with an explicit subselector
185
elem = xmlutil.TemplateElement('test', subselector=sel)
187
self.assertEqual(elem.subselector, sel)
189
def test_element_append_child(self):
191
elem = xmlutil.TemplateElement('test')
193
# Make sure the element starts off empty
194
self.assertEqual(len(elem), 0)
196
# Create a child element
197
child = xmlutil.TemplateElement('child')
199
# Append the child to the parent
202
# Verify that the child was added
203
self.assertEqual(len(elem), 1)
204
self.assertEqual(elem[0], child)
205
self.assertEqual('child' in elem, True)
206
self.assertEqual(elem['child'], child)
208
# Ensure that multiple children of the same name are rejected
209
child2 = xmlutil.TemplateElement('child')
210
self.assertRaises(KeyError, elem.append, child2)
212
def test_element_extend_children(self):
214
elem = xmlutil.TemplateElement('test')
216
# Make sure the element starts off empty
217
self.assertEqual(len(elem), 0)
219
# Create a few children
221
xmlutil.TemplateElement('child1'),
222
xmlutil.TemplateElement('child2'),
223
xmlutil.TemplateElement('child3'),
226
# Extend the parent by those children
227
elem.extend(children)
229
# Verify that the children were added
230
self.assertEqual(len(elem), 3)
231
for idx in range(len(elem)):
232
self.assertEqual(children[idx], elem[idx])
233
self.assertEqual(children[idx].tag in elem, True)
234
self.assertEqual(elem[children[idx].tag], children[idx])
236
# Ensure that multiple children of the same name are rejected
238
xmlutil.TemplateElement('child4'),
239
xmlutil.TemplateElement('child1'),
241
self.assertRaises(KeyError, elem.extend, children2)
243
# Also ensure that child4 was not added
244
self.assertEqual(len(elem), 3)
245
self.assertEqual(elem[-1].tag, 'child3')
247
def test_element_insert_child(self):
249
elem = xmlutil.TemplateElement('test')
251
# Make sure the element starts off empty
252
self.assertEqual(len(elem), 0)
254
# Create a few children
256
xmlutil.TemplateElement('child1'),
257
xmlutil.TemplateElement('child2'),
258
xmlutil.TemplateElement('child3'),
261
# Extend the parent by those children
262
elem.extend(children)
264
# Create a child to insert
265
child = xmlutil.TemplateElement('child4')
268
elem.insert(1, child)
270
# Ensure the child was inserted in the right place
271
self.assertEqual(len(elem), 4)
272
children.insert(1, child)
273
for idx in range(len(elem)):
274
self.assertEqual(children[idx], elem[idx])
275
self.assertEqual(children[idx].tag in elem, True)
276
self.assertEqual(elem[children[idx].tag], children[idx])
278
# Ensure that multiple children of the same name are rejected
279
child2 = xmlutil.TemplateElement('child2')
280
self.assertRaises(KeyError, elem.insert, 2, child2)
282
def test_element_remove_child(self):
284
elem = xmlutil.TemplateElement('test')
286
# Make sure the element starts off empty
287
self.assertEqual(len(elem), 0)
289
# Create a few children
291
xmlutil.TemplateElement('child1'),
292
xmlutil.TemplateElement('child2'),
293
xmlutil.TemplateElement('child3'),
296
# Extend the parent by those children
297
elem.extend(children)
299
# Create a test child to remove
300
child = xmlutil.TemplateElement('child2')
303
self.assertRaises(ValueError, elem.remove, child)
305
# Ensure that no child was removed
306
self.assertEqual(len(elem), 3)
308
# Now remove a legitimate child
309
elem.remove(children[1])
311
# Ensure that the child was removed
312
self.assertEqual(len(elem), 2)
313
self.assertEqual(elem[0], children[0])
314
self.assertEqual(elem[1], children[2])
315
self.assertEqual('child2' in elem, False)
317
# Ensure the child cannot be retrieved by name
318
def get_key(elem, key):
320
self.assertRaises(KeyError, get_key, elem, 'child2')
322
def test_element_text(self):
324
elem = xmlutil.TemplateElement('test')
326
# Ensure that it has no text
327
self.assertEqual(elem.text, None)
329
# Try setting it to a string and ensure it becomes a selector
331
self.assertEqual(hasattr(elem.text, 'chain'), True)
332
self.assertEqual(len(elem.text.chain), 1)
333
self.assertEqual(elem.text.chain[0], 'test')
335
# Try resetting the text to None
337
self.assertEqual(elem.text, None)
339
# Now make up a selector and try setting the text to that
340
sel = xmlutil.Selector()
342
self.assertEqual(elem.text, sel)
344
# Finally, try deleting the text and see what happens
346
self.assertEqual(elem.text, None)
348
def test_apply_attrs(self):
349
# Create a template element
350
attrs = dict(attr1=xmlutil.ConstantSelector(1),
351
attr2=xmlutil.ConstantSelector(2))
352
tmpl_elem = xmlutil.TemplateElement('test', attrib=attrs)
354
# Create an etree element
355
elem = etree.Element('test')
357
# Apply the template to the element
358
tmpl_elem.apply(elem, None)
360
# Now, verify the correct attributes were set
361
for k, v in elem.items():
362
self.assertEqual(str(attrs[k].value), v)
364
def test_apply_text(self):
365
# Create a template element
366
tmpl_elem = xmlutil.TemplateElement('test')
367
tmpl_elem.text = xmlutil.ConstantSelector(1)
369
# Create an etree element
370
elem = etree.Element('test')
372
# Apply the template to the element
373
tmpl_elem.apply(elem, None)
375
# Now, verify the text was set
376
self.assertEqual(str(tmpl_elem.text.value), elem.text)
378
def test__render(self):
379
attrs = dict(attr1=xmlutil.ConstantSelector(1),
380
attr2=xmlutil.ConstantSelector(2),
381
attr3=xmlutil.ConstantSelector(3))
383
# Create a master template element
384
master_elem = xmlutil.TemplateElement('test', attr1=attrs['attr1'])
386
# Create a couple of slave template element
388
xmlutil.TemplateElement('test', attr2=attrs['attr2']),
389
xmlutil.TemplateElement('test', attr3=attrs['attr3']),
393
elem = master_elem._render(None, None, slave_elems, None)
395
# Verify the particulars of the render
396
self.assertEqual(elem.tag, 'test')
397
self.assertEqual(len(elem.nsmap), 0)
398
for k, v in elem.items():
399
self.assertEqual(str(attrs[k].value), v)
401
# Create a parent for the element to be rendered
402
parent = etree.Element('parent')
404
# Try the render again...
405
elem = master_elem._render(parent, None, slave_elems, dict(a='foo'))
407
# Verify the particulars of the render
408
self.assertEqual(len(parent), 1)
409
self.assertEqual(parent[0], elem)
410
self.assertEqual(len(elem.nsmap), 1)
411
self.assertEqual(elem.nsmap['a'], 'foo')
413
def test_render(self):
414
# Create a template element
415
tmpl_elem = xmlutil.TemplateElement('test')
416
tmpl_elem.text = xmlutil.Selector()
418
# Create the object we're going to render
419
obj = ['elem1', 'elem2', 'elem3', 'elem4']
421
# Try a render with no object
422
elems = tmpl_elem.render(None, None)
423
self.assertEqual(len(elems), 0)
425
# Try a render with one object
426
elems = tmpl_elem.render(None, 'foo')
427
self.assertEqual(len(elems), 1)
428
self.assertEqual(elems[0][0].text, 'foo')
429
self.assertEqual(elems[0][1], 'foo')
431
# Now, try rendering an object with multiple entries
432
parent = etree.Element('parent')
433
elems = tmpl_elem.render(parent, obj)
434
self.assertEqual(len(elems), 4)
437
for idx in range(len(obj)):
438
self.assertEqual(elems[idx][0].text, obj[idx])
439
self.assertEqual(elems[idx][1], obj[idx])
441
def test_subelement(self):
442
# Try the SubTemplateElement constructor
443
parent = xmlutil.SubTemplateElement(None, 'parent')
444
self.assertEqual(parent.tag, 'parent')
445
self.assertEqual(len(parent), 0)
447
# Now try it with a parent element
448
child = xmlutil.SubTemplateElement(parent, 'child')
449
self.assertEqual(child.tag, 'child')
450
self.assertEqual(len(parent), 1)
451
self.assertEqual(parent[0], child)
454
# These are strange methods, but they make things easier
455
elem = xmlutil.TemplateElement('test')
456
self.assertEqual(elem.unwrap(), elem)
457
self.assertEqual(elem.wrap().root, elem)
459
def test_dyntag(self):
460
obj = ['a', 'b', 'c']
462
# Create a template element with a dynamic tag
463
tmpl_elem = xmlutil.TemplateElement(xmlutil.Selector())
466
parent = etree.Element('parent')
467
elems = tmpl_elem.render(parent, obj)
469
# Verify the particulars of the render
470
self.assertEqual(len(elems), len(obj))
471
for idx in range(len(obj)):
472
self.assertEqual(elems[idx][0].tag, obj[idx])
475
class TemplateTest(test.TestCase):
477
# These are strange methods, but they make things easier
478
elem = xmlutil.TemplateElement('test')
479
tmpl = xmlutil.Template(elem)
480
self.assertEqual(tmpl.unwrap(), elem)
481
self.assertEqual(tmpl.wrap(), tmpl)
483
def test__siblings(self):
484
# Set up a basic template
485
elem = xmlutil.TemplateElement('test')
486
tmpl = xmlutil.Template(elem)
488
# Check that we get the right siblings
489
siblings = tmpl._siblings()
490
self.assertEqual(len(siblings), 1)
491
self.assertEqual(siblings[0], elem)
493
def test__nsmap(self):
494
# Set up a basic template
495
elem = xmlutil.TemplateElement('test')
496
tmpl = xmlutil.Template(elem, nsmap=dict(a="foo"))
498
# Check out that we get the right namespace dictionary
499
nsmap = tmpl._nsmap()
500
self.assertNotEqual(id(nsmap), id(tmpl.nsmap))
501
self.assertEqual(len(nsmap), 1)
502
self.assertEqual(nsmap['a'], 'foo')
504
def test_master_attach(self):
505
# Set up a master template
506
elem = xmlutil.TemplateElement('test')
507
tmpl = xmlutil.MasterTemplate(elem, 1)
509
# Make sure it has a root but no slaves
510
self.assertEqual(tmpl.root, elem)
511
self.assertEqual(len(tmpl.slaves), 0)
513
# Try to attach an invalid slave
514
bad_elem = xmlutil.TemplateElement('test2')
515
self.assertRaises(ValueError, tmpl.attach, bad_elem)
516
self.assertEqual(len(tmpl.slaves), 0)
518
# Try to attach an invalid and a valid slave
519
good_elem = xmlutil.TemplateElement('test')
520
self.assertRaises(ValueError, tmpl.attach, good_elem, bad_elem)
521
self.assertEqual(len(tmpl.slaves), 0)
523
# Try to attach an inapplicable template
524
class InapplicableTemplate(xmlutil.Template):
525
def apply(self, master):
527
inapp_tmpl = InapplicableTemplate(good_elem)
528
tmpl.attach(inapp_tmpl)
529
self.assertEqual(len(tmpl.slaves), 0)
531
# Now try attaching an applicable template
532
tmpl.attach(good_elem)
533
self.assertEqual(len(tmpl.slaves), 1)
534
self.assertEqual(tmpl.slaves[0].root, good_elem)
536
def test_master_copy(self):
537
# Construct a master template
538
elem = xmlutil.TemplateElement('test')
539
tmpl = xmlutil.MasterTemplate(elem, 1, nsmap=dict(a='foo'))
542
slave = xmlutil.TemplateElement('test')
548
# Check to see if we actually managed a copy
549
self.assertNotEqual(tmpl, copy)
550
self.assertEqual(tmpl.root, copy.root)
551
self.assertEqual(tmpl.version, copy.version)
552
self.assertEqual(id(tmpl.nsmap), id(copy.nsmap))
553
self.assertNotEqual(id(tmpl.slaves), id(copy.slaves))
554
self.assertEqual(len(tmpl.slaves), len(copy.slaves))
555
self.assertEqual(tmpl.slaves[0], copy.slaves[0])
557
def test_slave_apply(self):
558
# Construct a master template
559
elem = xmlutil.TemplateElement('test')
560
master = xmlutil.MasterTemplate(elem, 3)
562
# Construct a slave template with applicable minimum version
563
slave = xmlutil.SlaveTemplate(elem, 2)
564
self.assertEqual(slave.apply(master), True)
566
# Construct a slave template with equal minimum version
567
slave = xmlutil.SlaveTemplate(elem, 3)
568
self.assertEqual(slave.apply(master), True)
570
# Construct a slave template with inapplicable minimum version
571
slave = xmlutil.SlaveTemplate(elem, 4)
572
self.assertEqual(slave.apply(master), False)
574
# Construct a slave template with applicable version range
575
slave = xmlutil.SlaveTemplate(elem, 2, 4)
576
self.assertEqual(slave.apply(master), True)
578
# Construct a slave template with low version range
579
slave = xmlutil.SlaveTemplate(elem, 1, 2)
580
self.assertEqual(slave.apply(master), False)
582
# Construct a slave template with high version range
583
slave = xmlutil.SlaveTemplate(elem, 4, 5)
584
self.assertEqual(slave.apply(master), False)
586
# Construct a slave template with matching version range
587
slave = xmlutil.SlaveTemplate(elem, 3, 3)
588
self.assertEqual(slave.apply(master), True)
590
def test__serialize(self):
591
# Our test object to serialize
595
'values': [1, 2, 3, 4],
603
'name': 'image_foobar',
609
# Set up our master template
610
root = xmlutil.TemplateElement('test', selector='test',
612
value = xmlutil.SubTemplateElement(root, 'value', selector='values')
613
value.text = xmlutil.Selector()
614
attrs = xmlutil.SubTemplateElement(root, 'attrs', selector='attrs')
615
xmlutil.SubTemplateElement(attrs, 'attr', selector=xmlutil.get_items,
617
master = xmlutil.MasterTemplate(root, 1, nsmap=dict(f='foo'))
619
# Set up our slave template
620
root_slave = xmlutil.TemplateElement('test', selector='test')
621
image = xmlutil.SubTemplateElement(root_slave, 'image',
622
selector='image', id='id')
623
image.text = xmlutil.Selector('name')
624
slave = xmlutil.SlaveTemplate(root_slave, 1, nsmap=dict(b='bar'))
626
# Attach the slave to the master...
629
# Try serializing our object
630
siblings = master._siblings()
631
nsmap = master._nsmap()
632
result = master._serialize(None, obj, siblings, nsmap)
634
# Now we get to manually walk the element tree...
635
self.assertEqual(result.tag, 'test')
636
self.assertEqual(len(result.nsmap), 2)
637
self.assertEqual(result.nsmap['f'], 'foo')
638
self.assertEqual(result.nsmap['b'], 'bar')
639
self.assertEqual(result.get('name'), obj['test']['name'])
640
for idx, val in enumerate(obj['test']['values']):
641
self.assertEqual(result[idx].tag, 'value')
642
self.assertEqual(result[idx].text, str(val))
644
self.assertEqual(result[idx].tag, 'attrs')
645
for attr in result[idx]:
646
self.assertEqual(attr.tag, 'attr')
647
self.assertEqual(attr.get('value'),
648
str(obj['test']['attrs'][attr.get('key')]))
650
self.assertEqual(result[idx].tag, 'image')
651
self.assertEqual(result[idx].get('id'),
652
str(obj['test']['image']['id']))
653
self.assertEqual(result[idx].text, obj['test']['image']['name'])
656
class MasterTemplateBuilder(xmlutil.TemplateBuilder):
658
elem = xmlutil.TemplateElement('test')
659
return xmlutil.MasterTemplate(elem, 1)
662
class SlaveTemplateBuilder(xmlutil.TemplateBuilder):
664
elem = xmlutil.TemplateElement('test')
665
return xmlutil.SlaveTemplate(elem, 1)
668
class TemplateBuilderTest(test.TestCase):
669
def test_master_template_builder(self):
670
# Make sure the template hasn't been built yet
671
self.assertEqual(MasterTemplateBuilder._tmpl, None)
673
# Now, construct the template
674
tmpl1 = MasterTemplateBuilder()
676
# Make sure that there is a template cached...
677
self.assertNotEqual(MasterTemplateBuilder._tmpl, None)
679
# Make sure it wasn't what was returned...
680
self.assertNotEqual(MasterTemplateBuilder._tmpl, tmpl1)
682
# Make sure it doesn't get rebuilt
683
cached = MasterTemplateBuilder._tmpl
684
tmpl2 = MasterTemplateBuilder()
685
self.assertEqual(MasterTemplateBuilder._tmpl, cached)
687
# Make sure we're always getting fresh copies
688
self.assertNotEqual(tmpl1, tmpl2)
690
# Make sure we can override the copying behavior
691
tmpl3 = MasterTemplateBuilder(False)
692
self.assertEqual(MasterTemplateBuilder._tmpl, tmpl3)
694
def test_slave_template_builder(self):
695
# Make sure the template hasn't been built yet
696
self.assertEqual(SlaveTemplateBuilder._tmpl, None)
698
# Now, construct the template
699
tmpl1 = SlaveTemplateBuilder()
701
# Make sure there is a template cached...
702
self.assertNotEqual(SlaveTemplateBuilder._tmpl, None)
704
# Make sure it was what was returned...
705
self.assertEqual(SlaveTemplateBuilder._tmpl, tmpl1)
707
# Make sure it doesn't get rebuilt
708
tmpl2 = SlaveTemplateBuilder()
709
self.assertEqual(SlaveTemplateBuilder._tmpl, tmpl1)
711
# Make sure we're always getting the cached copy
712
self.assertEqual(tmpl1, tmpl2)
715
class MiscellaneousXMLUtilTests(test.TestCase):
716
def test_make_flat_dict(self):
717
expected_xml = ("<?xml version='1.0' encoding='UTF-8'?>\n"
718
'<wrapper><a>foo</a><b>bar</b></wrapper>')
719
root = xmlutil.make_flat_dict('wrapper')
720
tmpl = xmlutil.MasterTemplate(root, 1)
721
result = tmpl.serialize(dict(wrapper=dict(a='foo', b='bar')))
722
self.assertEqual(result, expected_xml)