~ubuntu-branches/ubuntu/lucid/thuban/lucid

« back to all changes in this revision

Viewing changes to test/test_save.py

  • Committer: Bazaar Package Importer
  • Author(s): Silke Reimer
  • Date: 2004-01-28 12:47:34 UTC
  • Revision ID: james.westby@ubuntu.com-20040128124734-6xotwcqilok6ngut
Tags: upstream-1.0.0
ImportĀ upstreamĀ versionĀ 1.0.0

Show diffs side-by-side

added added

removed removed

Lines of Context:
 
1
# Copyright (c) 2002, 2003 by Intevation GmbH
 
2
# Authors:
 
3
# Bernhard Herzog <bh@intevation.de>
 
4
#
 
5
# This program is free software under the GPL (>=v2)
 
6
# Read the file COPYING coming with Thuban for details.
 
7
 
 
8
"""
 
9
Test saving a thuban session as XML
 
10
"""
 
11
 
 
12
__version__ = "$Revision: 1.31 $"
 
13
# $Source: /thubanrepository/thuban/test/test_save.py,v $
 
14
# $Id: test_save.py,v 1.31 2003/12/22 17:49:43 bh Exp $
 
15
 
 
16
import os
 
17
import unittest
 
18
from StringIO import StringIO
 
19
 
 
20
import xmlsupport
 
21
import postgissupport
 
22
 
 
23
import support
 
24
support.initthuban()
 
25
 
 
26
import dbflib
 
27
 
 
28
from Thuban.Lib.fileutil import relative_filename
 
29
from Thuban.Model.save import XMLWriter, save_session, sort_data_stores
 
30
from Thuban.Model.session import Session
 
31
from Thuban.Model.map import Map
 
32
from Thuban.Model.layer import Layer, RasterLayer
 
33
from Thuban.Model.proj import Projection
 
34
from Thuban.Model.table import DBFTable
 
35
from Thuban.Model.transientdb import TransientJoinedTable
 
36
from Thuban.Model.data import DerivedShapeStore
 
37
 
 
38
from Thuban.Model.classification import ClassGroupSingleton, ClassGroupRange, \
 
39
    ClassGroupProperties
 
40
 
 
41
from Thuban.Model.range import Range
 
42
 
 
43
from Thuban.Model.postgisdb import PostGISConnection, PostGISShapeStore
 
44
 
 
45
 
 
46
class XMLWriterTest(unittest.TestCase):
 
47
 
 
48
    def testEncode(self):
 
49
        """Test XMLWriter.encode"""
 
50
        writer = XMLWriter()
 
51
        eq = self.assertEquals
 
52
 
 
53
        eq(writer.encode("hello world"), "hello world")
 
54
        eq(writer.encode(unicode("hello world")), unicode("hello world"))
 
55
 
 
56
        eq(writer.encode("\x80\x90\xc2\x100"), 
 
57
                         "\xc2\x80\xc2\x90\xc3\x82\x100")
 
58
        eq(writer.encode(u"\x80\x90\xc2\x100"), 
 
59
                         "\xc2\x80\xc2\x90\xc3\x82\x100")
 
60
        eq(writer.encode(u"\xFF5E"), "\xc3\xbf5E")
 
61
 
 
62
        eq(writer.encode('&"\'<>'), "&amp;&quot;&apos;&lt;&gt;")
 
63
        eq(writer.encode(unicode('&"\'<>')), "&amp;&quot;&apos;&lt;&gt;")
 
64
 
 
65
class SaveSessionTest(unittest.TestCase, support.FileTestMixin,
 
66
                      xmlsupport.ValidationTest):
 
67
 
 
68
    dtd = "http://thuban.intevation.org/dtds/thuban-1.0.0.dtd"
 
69
    thubanids = [((dtd, n), (None, "id")) for n in
 
70
                 ["fileshapesource", "filetable", "jointable",
 
71
                  "derivedshapesource", "dbshapesource", "dbconnection"]]
 
72
    thubanidrefs = [((dtd, n), (None, m)) for n, m in
 
73
                    [("layer", "shapestore"),
 
74
                     ("jointable", "left"),
 
75
                     ("jointable", "right"),
 
76
                     ("derivedshapesource", "table"),
 
77
                     ("derivedshapesource", "shapesource"),
 
78
                     ("dbshapesource", "dbconn")]]
 
79
    del n, m, dtd
 
80
 
 
81
    def tearDown(self):
 
82
        """Call self.session.Destroy
 
83
 
 
84
        Test cases that create session should bind it to self.session so
 
85
        that it gets destroyed properly
 
86
        """
 
87
        if hasattr(self, "session"):
 
88
            self.session.Destroy()
 
89
            self.session = None
 
90
 
 
91
    def compare_xml(self, xml1, xml2):
 
92
        list1 = xmlsupport.sax_eventlist(xml1, ids = self.thubanids,
 
93
                                         idrefs = self.thubanidrefs)
 
94
        list2 = xmlsupport.sax_eventlist(xml2, ids = self.thubanids,
 
95
                                         idrefs = self.thubanidrefs)
 
96
        if list1 != list2:
 
97
            for a, b in zip(list1, list2):
 
98
                if a != b:
 
99
                    self.fail("%r != %r" % (a, b))
 
100
 
 
101
 
 
102
    def testEmptySession(self):
 
103
        """Save an empty session"""
 
104
        session = Session("empty session")
 
105
        filename = self.temp_file_name("save_emptysession.thuban")
 
106
        save_session(session, filename)
 
107
        session.Destroy()
 
108
 
 
109
        file = open(filename)
 
110
        written_contents = file.read()
 
111
        file.close()
 
112
        self.compare_xml(written_contents,
 
113
                         '<?xml version="1.0" encoding="UTF-8"?>\n'
 
114
                         '<!DOCTYPE session SYSTEM "thuban-1.0.dtd">\n'
 
115
                         '<session title="empty session" '
 
116
         'xmlns="http://thuban.intevation.org/dtds/thuban-1.0.0.dtd">'
 
117
                         '\n</session>\n')
 
118
 
 
119
        self.validate_data(written_contents)
 
120
 
 
121
    def testSingleLayer(self):
 
122
        """Save a session with a single map with a single layer"""
 
123
        # deliberately put an apersand in the title :)
 
124
        session = Session("single map&layer")
 
125
        proj = Projection(["proj=utm", "zone=27", "ellps=WGS84",
 
126
                           "datum=WGS84", "units=m"],
 
127
                          name = "WGS 84 / UTM zone 27N",
 
128
                          epsg = "32627")
 
129
        map = Map("Test Map", projection = proj)
 
130
        session.AddMap(map)
 
131
        # use shapefile from the example data
 
132
        shpfile = os.path.join(os.path.dirname(__file__),
 
133
                               os.pardir, "Data", "iceland", "political.shp")
 
134
        layer = Layer("My Layer", session.OpenShapefile(shpfile))
 
135
        map.AddLayer(layer)
 
136
 
 
137
        filename = self.temp_file_name("save_singlemap.thuban")
 
138
        save_session(session, filename)
 
139
 
 
140
        file = open(filename)
 
141
        written_contents = file.read()
 
142
        file.close()
 
143
        expected_template = '''<?xml version="1.0" encoding="UTF-8"?>
 
144
        <!DOCTYPE session SYSTEM "thuban-1.0.dtd">
 
145
        <session title="single map&amp;layer"
 
146
           xmlns="http://thuban.intevation.org/dtds/thuban-1.0.0.dtd">
 
147
            <fileshapesource id="D1"
 
148
                filename="../../Data/iceland/political.shp"
 
149
                filetype="shapefile"/>
 
150
            <map title="Test Map">
 
151
                <projection epsg="32627" name="WGS 84 / UTM zone 27N">
 
152
                    <parameter value="proj=utm"/>
 
153
                    <parameter value="zone=27"/>
 
154
                    <parameter value="ellps=WGS84"/>
 
155
                    <parameter value="datum=WGS84"/>
 
156
                    <parameter value="units=m"/>
 
157
                </projection>
 
158
                <layer title="My Layer" shapestore="D1"
 
159
                fill="None" stroke="#000000" stroke_width="1" visible="%s"/>
 
160
            </map>
 
161
        </session>'''
 
162
 
 
163
        expected_contents = expected_template % "true"
 
164
 
 
165
        self.compare_xml(written_contents, expected_contents)
 
166
 
 
167
        self.validate_data(written_contents)
 
168
 
 
169
        # Repeat with an invisible layer
 
170
        layer.SetVisible(False)
 
171
        save_session(session, filename)
 
172
 
 
173
        file = open(filename)
 
174
        written_contents = file.read()
 
175
        file.close()
 
176
        expected_contents = expected_template % "false"
 
177
        self.compare_xml(written_contents, expected_contents)
 
178
        self.validate_data(written_contents)
 
179
 
 
180
        session.Destroy()
 
181
 
 
182
    def testLayerProjection(self):
 
183
        """Test saving layers with projections"""
 
184
        # deliberately put an apersand in the title :)
 
185
        session = self.session = Session("single map&layer")
 
186
        proj = Projection(["zone=26", "proj=utm", "ellps=clrk66"])
 
187
        map = Map("Test Map", projection = proj)
 
188
        session.AddMap(map)
 
189
        # use shapefile from the example data
 
190
        shpfile = os.path.join(os.path.dirname(__file__),
 
191
                               os.pardir, "Data", "iceland", "political.shp")
 
192
        layer = Layer("My Layer", session.OpenShapefile(shpfile))
 
193
        proj = Projection(["proj=lcc", "ellps=clrk66",
 
194
                           "lat_1=0", "lat_2=20"],
 
195
                          "Layer Projection")
 
196
        layer.SetProjection(proj)
 
197
        map.AddLayer(layer)
 
198
 
 
199
        filename = self.temp_file_name("save_layerproj.thuban")
 
200
        save_session(session, filename)
 
201
 
 
202
        file = open(filename)
 
203
        written_contents = file.read()
 
204
        file.close()
 
205
        expected_contents = '''<?xml version="1.0" encoding="UTF-8"?>
 
206
        <!DOCTYPE session SYSTEM "thuban-1.0.dtd">
 
207
        <session title="single map&amp;layer"
 
208
           xmlns="http://thuban.intevation.org/dtds/thuban-1.0.0.dtd">
 
209
            <fileshapesource id="D1"
 
210
                filename="../../Data/iceland/political.shp"
 
211
                filetype="shapefile"/>
 
212
            <map title="Test Map">
 
213
                <projection name="Unknown">
 
214
                    <parameter value="zone=26"/>
 
215
                    <parameter value="proj=utm"/>
 
216
                    <parameter value="ellps=clrk66"/>
 
217
                </projection>
 
218
                <layer title="My Layer" shapestore="D1"
 
219
                fill="None" stroke="#000000" stroke_width="1" visible="true">
 
220
                    <projection name="Layer Projection">
 
221
                        <parameter value="proj=lcc"/>
 
222
                        <parameter value="ellps=clrk66"/>
 
223
                        <parameter value="lat_1=0"/>
 
224
                        <parameter value="lat_2=20"/>
 
225
                    </projection>
 
226
                </layer>
 
227
            </map>
 
228
        </session>'''
 
229
        #print written_contents
 
230
        #print "********************************************"
 
231
        #print expected_contents
 
232
        self.compare_xml(written_contents, expected_contents)
 
233
 
 
234
        self.validate_data(written_contents)
 
235
 
 
236
    def testRasterLayer(self):
 
237
        # deliberately put an apersand in the title :)
 
238
        session = Session("single map&layer")
 
239
        map = Map("Test Map")
 
240
        session.AddMap(map)
 
241
        # use shapefile from the example data
 
242
        imgfile = os.path.join(os.path.dirname(__file__),
 
243
                               os.pardir, "Data", "iceland", "island.tif")
 
244
        layer = RasterLayer("My RasterLayer", imgfile)
 
245
        map.AddLayer(layer)
 
246
 
 
247
        filename = self.temp_file_name("%s.thuban" % self.id())
 
248
        save_session(session, filename)
 
249
        session.Destroy()
 
250
 
 
251
        file = open(filename)
 
252
        written_contents = file.read()
 
253
        file.close()
 
254
        expected_contents = '''<?xml version="1.0" encoding="UTF-8"?>
 
255
        <!DOCTYPE session SYSTEM "thuban-1.0.dtd">
 
256
        <session title="single map&amp;layer"
 
257
           xmlns="http://thuban.intevation.org/dtds/thuban-1.0.0.dtd">
 
258
            <map title="Test Map">
 
259
                <rasterlayer title="My RasterLayer"
 
260
                        filename="../../Data/iceland/island.tif"
 
261
                        visible="true">
 
262
                </rasterlayer>
 
263
            </map>
 
264
        </session>'''
 
265
        #print written_contents
 
266
        #print "********************************************"
 
267
        #print expected_contents
 
268
        self.compare_xml(written_contents, expected_contents)
 
269
 
 
270
        self.validate_data(written_contents)
 
271
 
 
272
    def testClassifiedLayer(self):
 
273
        """Save a session with a single map with classifications"""
 
274
        # deliberately put an apersand in the title :)
 
275
        session = Session("Map with Classifications")
 
276
        proj = Projection(["zone=26", "proj=utm", "ellps=clrk66"])
 
277
        map = Map("Test Map", projection = proj)
 
278
        session.AddMap(map)
 
279
        # use shapefile from the example data
 
280
        shpfile = os.path.join(os.path.dirname(__file__),
 
281
                               os.pardir, "Data", "iceland", "political.shp")
 
282
        layer = Layer("My Layer", session.OpenShapefile(shpfile))
 
283
        map.AddLayer(layer)
 
284
        layer2 = Layer("My Layer", layer.ShapeStore())
 
285
        map.AddLayer(layer2)
 
286
 
 
287
        clazz = layer.GetClassification()
 
288
 
 
289
        layer.SetClassificationColumn("AREA")
 
290
 
 
291
        clazz.AppendGroup(ClassGroupSingleton(42, ClassGroupProperties(),
 
292
                                              "single"))
 
293
        clazz.AppendGroup(ClassGroupSingleton("text", ClassGroupProperties(),
 
294
                                              "single-text"))
 
295
 
 
296
        clazz.AppendGroup(ClassGroupRange((0, 42),
 
297
                                           ClassGroupProperties(),
 
298
                                           "range"))
 
299
 
 
300
        range = ClassGroupRange(Range("[0;42]"))
 
301
        range.SetProperties(ClassGroupProperties())
 
302
        range.SetLabel("new-range")
 
303
        clazz.AppendGroup(range)
 
304
 
 
305
 
 
306
        clazz = layer2.GetClassification()
 
307
        layer2.SetClassificationColumn("POPYCOUN")
 
308
 
 
309
        # Classification with Latin 1 text
 
310
        clazz.AppendGroup(ClassGroupSingleton('\xe4\xf6\xfc', # ae, oe, ue
 
311
                                              ClassGroupProperties(),
 
312
                                              '\xdcml\xe4uts')) # Uemlaeuts
 
313
 
 
314
 
 
315
        filename = self.temp_file_name("%s.thuban" % self.id())
 
316
        save_session(session, filename)
 
317
 
 
318
        file = open(filename)
 
319
        written_contents = file.read()
 
320
        file.close()
 
321
        expected_contents = '''<?xml version="1.0" encoding="UTF-8"?>
 
322
        <!DOCTYPE session SYSTEM "thuban-1.0.dtd">
 
323
        <session title="Map with Classifications"
 
324
           xmlns="http://thuban.intevation.org/dtds/thuban-1.0.0.dtd">
 
325
            <fileshapesource id="D1"
 
326
                filename="../../Data/iceland/political.shp"
 
327
                filetype="shapefile"/>
 
328
            <map title="Test Map">
 
329
                <projection name="Unknown">
 
330
                    <parameter value="zone=26"/>
 
331
                    <parameter value="proj=utm"/>
 
332
                    <parameter value="ellps=clrk66"/>
 
333
                </projection>
 
334
                <layer title="My Layer" shapestore="D1"
 
335
                fill="None" stroke="#000000" stroke_width="1" visible="true">
 
336
                    <classification field="AREA" field_type="double">
 
337
                        <clnull label="">
 
338
                            <cldata fill="None" stroke="#000000" stroke_width="1"/>
 
339
                        </clnull>
 
340
                        <clpoint value="42" label="single">
 
341
                            <cldata fill="None" stroke="#000000" stroke_width="1"/>
 
342
                        </clpoint>
 
343
                        <clpoint value="text" label="single-text">
 
344
                            <cldata fill="None" stroke="#000000" stroke_width="1"/>
 
345
                        </clpoint>
 
346
                        <clrange range="[0;42[" label="range">
 
347
                            <cldata fill="None" stroke="#000000" stroke_width="1"/>
 
348
                        </clrange>
 
349
                        <clrange range="[0;42]" label="new-range">
 
350
                            <cldata fill="None" stroke="#000000" stroke_width="1"/>
 
351
                        </clrange>
 
352
                    </classification>
 
353
                </layer>
 
354
                <layer title="My Layer" shapestore="D1"
 
355
                fill="None" stroke="#000000" stroke_width="1" visible="true">
 
356
                    <classification field="POPYCOUN" field_type="string">
 
357
                        <clnull label="">
 
358
                            <cldata fill="None" stroke="#000000" stroke_width="1"/>
 
359
                        </clnull>
 
360
                        <clpoint value="\xc3\xa4\xc3\xb6\xc3\xbc"
 
361
                             label="\xc3\x9cml\xc3\xa4uts">
 
362
                            <cldata fill="None" stroke="#000000" stroke_width="1"/>
 
363
                        </clpoint>
 
364
                    </classification>
 
365
                </layer>
 
366
            </map>
 
367
        </session>'''
 
368
 
 
369
        #print written_contents
 
370
        #print "********************************************"
 
371
        #print expected_contents
 
372
        self.compare_xml(written_contents, expected_contents)
 
373
 
 
374
        self.validate_data(written_contents)
 
375
 
 
376
        session.Destroy()
 
377
 
 
378
    def test_dbf_table(self):
 
379
        """Test saving a session with a dbf table link"""
 
380
        session = self.session = Session("a DBF Table session")
 
381
        # use shapefile from the example data
 
382
        dbffile = os.path.join(os.path.dirname(__file__),
 
383
                               os.pardir, "Data", "iceland", "political.dbf")
 
384
        table = session.AddTable(DBFTable(dbffile))
 
385
 
 
386
        filename = self.temp_file_name("save_singletable.thuban")
 
387
        save_session(session, filename)
 
388
 
 
389
        file = open(filename)
 
390
        written_contents = file.read()
 
391
        file.close()
 
392
        expected_contents = '''<?xml version="1.0" encoding="UTF-8"?>
 
393
        <!DOCTYPE session SYSTEM "thuban-1.0.dtd">
 
394
        <session title="a DBF Table session"
 
395
           xmlns="http://thuban.intevation.org/dtds/thuban-1.0.0.dtd">
 
396
            <filetable id="D1" filename="../../Data/iceland/political.dbf"
 
397
                filetype="DBF" title="political"/>
 
398
        </session>'''
 
399
 
 
400
        self.compare_xml(written_contents, expected_contents)
 
401
        self.validate_data(written_contents)
 
402
 
 
403
    def test_joined_table(self):
 
404
        """Test saving a session with joined table"""
 
405
        # Create a simple table to use in the join
 
406
        dbffile = self.temp_file_name("save_joinedtable.dbf")
 
407
        dbf = dbflib.create(dbffile)
 
408
        dbf.add_field("RDTYPE", dbflib.FTInteger, 10, 0)
 
409
        dbf.add_field("TEXT", dbflib.FTString, 10, 0)
 
410
        dbf.write_record(0, {'RDTYPE': 8, "TEXT": "foo"})
 
411
        dbf.write_record(1, {'RDTYPE': 2, "TEXT": "bar"})
 
412
        dbf.write_record(2, {'RDTYPE': 3, "TEXT": "baz"})
 
413
        dbf.close()
 
414
 
 
415
        # Create the session and a map
 
416
        session = Session("A Joined Table session")
 
417
        try:
 
418
            map = Map("Test Map")
 
419
            session.AddMap(map)
 
420
 
 
421
            # Add the dbf file to the session
 
422
            dbftable = session.AddTable(DBFTable(dbffile))
 
423
 
 
424
            # Create a layer with the shapefile to use in the join
 
425
            shpfile = os.path.join(os.path.abspath(os.path.dirname(__file__)),
 
426
                                   os.pardir, "Data", "iceland",
 
427
                                   "roads-line.shp")
 
428
            layer = Layer("My Layer", session.OpenShapefile(shpfile))
 
429
            map.AddLayer(layer)
 
430
 
 
431
            # Do the join
 
432
            store = layer.ShapeStore()
 
433
            #for col in store.Table().Columns():
 
434
            #    print col.name
 
435
            joined = TransientJoinedTable(session.TransientDB(),
 
436
                                          store.Table(), "RDLNTYPE",
 
437
                                          dbftable, "RDTYPE",
 
438
                                          outer_join = True)
 
439
            store = session.AddShapeStore(DerivedShapeStore(store, joined))
 
440
            layer.SetShapeStore(store)
 
441
 
 
442
            # Save the session
 
443
            filename = self.temp_file_name("save_joinedtable.thuban")
 
444
            save_session(session, filename)
 
445
 
 
446
            # Read it back and compare
 
447
            file = open(filename)
 
448
            written_contents = file.read()
 
449
            file.close()
 
450
            expected_contents = '''<?xml version="1.0" encoding="UTF-8"?>
 
451
            <!DOCTYPE session SYSTEM "thuban-1.0.dtd">
 
452
            <session title="A Joined Table session"
 
453
             xmlns="http://thuban.intevation.org/dtds/thuban-1.0.0.dtd">
 
454
                <fileshapesource filename="../../Data/iceland/roads-line.shp"
 
455
                                 filetype="shapefile" id="D142197204"/>
 
456
                <filetable filename="save_joinedtable.dbf"
 
457
                           title="save_joinedtable"
 
458
                           filetype="DBF" id="D141881756"/>
 
459
                <jointable id="D142180284"
 
460
                           title="Join of roads-line and save_joinedtable"
 
461
                           leftcolumn="RDLNTYPE" left="D142197204"
 
462
                           rightcolumn="RDTYPE" right="D141881756"
 
463
                           jointype="LEFT OUTER" />
 
464
                <derivedshapesource id="D141915644"
 
465
                                    table="D142180284"
 
466
                                    shapesource="D142197204"/>
 
467
                <map title="Test Map">
 
468
                    <layer title="My Layer"
 
469
                           shapestore="D141915644" visible="true"
 
470
                           stroke="#000000" stroke_width="1" fill="None"/>
 
471
                </map>
 
472
            </session>'''
 
473
 
 
474
            self.compare_xml(written_contents, expected_contents)
 
475
            self.validate_data(written_contents)
 
476
        finally:
 
477
            session.Destroy()
 
478
            session = None
 
479
 
 
480
 
 
481
    def test_save_postgis(self):
 
482
        """Test saving a session with a postgis connection"""
 
483
 
 
484
        class NonConnection(PostGISConnection):
 
485
            """connection class that doesn't actually connect """
 
486
            def connect(self):
 
487
                pass
 
488
 
 
489
        class NonConnectionStore(PostGISShapeStore):
 
490
            """Shapestore that doesn't try to access the server"""
 
491
            def _fetch_table_information(self):
 
492
                pass
 
493
 
 
494
        session = Session("A PostGIS Session")
 
495
        try:
 
496
            dbconn = NonConnection(dbname="plugh", host="xyzzy", port="42",
 
497
                                   user="grue")
 
498
            session.AddDBConnection(dbconn)
 
499
            map = Map("Test Map")
 
500
            session.AddMap(map)
 
501
            store = NonConnectionStore(dbconn, "roads")
 
502
            session.AddShapeStore(store)
 
503
            layer = Layer("Roads to Nowhere", store)
 
504
            map.AddLayer(layer)
 
505
 
 
506
            # Save the session
 
507
            filename = self.temp_file_name(self.id() + ".thuban")
 
508
            save_session(session, filename)
 
509
 
 
510
            # Read it back and compare
 
511
            file = open(filename)
 
512
            written = file.read()
 
513
            file.close()
 
514
            expected = '''<?xml version="1.0" encoding="UTF-8"?>
 
515
            <!DOCTYPE session SYSTEM "thuban-1.0.dtd">
 
516
            <session title="A PostGIS Session"
 
517
             xmlns="http://thuban.intevation.org/dtds/thuban-1.0.0.dtd">
 
518
                <dbconnection id="DB"
 
519
                              dbtype="postgis" dbname="plugh"
 
520
                              host="xyzzy" port="42"
 
521
                              user="grue"/>
 
522
                <dbshapesource id="roads" dbconn="DB" tablename="roads"/>
 
523
                <map title="Test Map">
 
524
                    <layer title="Roads to Nowhere"
 
525
                           shapestore="roads" visible="true"
 
526
                           stroke="#000000" stroke_width="1" fill="None"/>
 
527
                </map>
 
528
            </session>'''
 
529
            self.compare_xml(written, expected)
 
530
            self.validate_data(written)
 
531
        finally:
 
532
            session.Destroy()
 
533
 
 
534
 
 
535
class MockDataStore:
 
536
 
 
537
    """A very simple data store that only has dependencies"""
 
538
 
 
539
    def __init__(self, name, *dependencies):
 
540
        self.name = name
 
541
        self.dependencies = dependencies
 
542
 
 
543
    def __repr__(self):
 
544
        return self.name
 
545
 
 
546
    def Dependencies(self):
 
547
        return self.dependencies
 
548
 
 
549
 
 
550
class TestStoreSort(unittest.TestCase):
 
551
 
 
552
    def check_sort(self, containers, sorted):
 
553
        """Check whether the list of data containers is sorted"""
 
554
        # check whether sorted is in the right order
 
555
        seen = {}
 
556
        for container in sorted:
 
557
            self.failIf(id(container) in seen,
 
558
                        "Container %r at least twice in %r" % (container,
 
559
                                                               sorted))
 
560
            for dep in container.Dependencies():
 
561
                self.assert_(id(dep) in seen,
 
562
                             "Dependency %r of %r not yet seen" % (dep,
 
563
                                                                   container))
 
564
            seen[id(container)] = 1
 
565
        # check whether all of containers is in sorted
 
566
        for container in containers:
 
567
            self.assert_(id(container) in seen,
 
568
                         "Container %r in containers but not in sorted")
 
569
        self.assertEquals(len(containers), len(sorted))
 
570
 
 
571
    def test_sort_data_stores(self):
 
572
        """Test Thuban.Model.save.sort_data_stores"""
 
573
        d1 = MockDataStore("d1")
 
574
        d2 = MockDataStore("d2")
 
575
        d3 = MockDataStore("d3", d1)
 
576
        d4 = MockDataStore("d4", d1, d3)
 
577
 
 
578
        containers = [d4, d1, d2, d3]
 
579
        self.check_sort(containers, sort_data_stores(containers))
 
580
        containers = [d1, d3, d2, d4]
 
581
        self.check_sort(containers, sort_data_stores(containers))
 
582
 
 
583
 
 
584
 
 
585
if __name__ == "__main__":
 
586
    # Fake the __file__ global because it's needed by a test
 
587
    import sys
 
588
    __file__ = sys.argv[0]
 
589
    support.run_tests()