blob: 3ef82be0b7280d211afe4a9cd584e8f0c5504483 [file] [log] [blame]
Fred Drake8ef67672000-09-27 22:45:25 +00001import ConfigParser
Guido van Rossum34d19282007-08-09 01:03:29 +00002import io
Fred Drakec6f28912002-10-25 19:40:49 +00003import unittest
Raymond Hettingerf80680d2008-02-06 00:07:11 +00004import collections
Fred Drake8ef67672000-09-27 22:45:25 +00005
Fred Drakec6f28912002-10-25 19:40:49 +00006from test import test_support
Fred Drake3d5f7e82000-12-04 16:30:40 +00007
Raymond Hettingerf80680d2008-02-06 00:07:11 +00008class SortedDict(collections.UserDict):
Thomas Wouters89f507f2006-12-13 04:49:30 +00009 def items(self):
Guido van Rossumcc2b0162007-02-11 06:12:03 +000010 return sorted(self.data.items())
Thomas Wouters89f507f2006-12-13 04:49:30 +000011
12 def keys(self):
Guido van Rossumcc2b0162007-02-11 06:12:03 +000013 return sorted(self.data.keys())
Thomas Wouters9fe394c2007-02-05 01:24:16 +000014
Thomas Wouters89f507f2006-12-13 04:49:30 +000015 def values(self):
Guido van Rossumcc2b0162007-02-11 06:12:03 +000016 return [i[1] for i in self.items()]
Thomas Wouters89f507f2006-12-13 04:49:30 +000017
18 def iteritems(self): return iter(self.items())
19 def iterkeys(self): return iter(self.keys())
20 __iter__ = iterkeys
21 def itervalues(self): return iter(self.values())
Fred Drake3d5f7e82000-12-04 16:30:40 +000022
Fred Drakec6f28912002-10-25 19:40:49 +000023class TestCaseBase(unittest.TestCase):
24 def newconfig(self, defaults=None):
25 if defaults is None:
26 self.cf = self.config_class()
27 else:
28 self.cf = self.config_class(defaults)
29 return self.cf
Fred Drake8ef67672000-09-27 22:45:25 +000030
Fred Drakec6f28912002-10-25 19:40:49 +000031 def fromstring(self, string, defaults=None):
32 cf = self.newconfig(defaults)
Guido van Rossum34d19282007-08-09 01:03:29 +000033 sio = io.StringIO(string)
Fred Drakec6f28912002-10-25 19:40:49 +000034 cf.readfp(sio)
35 return cf
Fred Drake8ef67672000-09-27 22:45:25 +000036
Fred Drakec6f28912002-10-25 19:40:49 +000037 def test_basic(self):
38 cf = self.fromstring(
39 "[Foo Bar]\n"
40 "foo=bar\n"
41 "[Spacey Bar]\n"
42 "foo = bar\n"
43 "[Commented Bar]\n"
44 "foo: bar ; comment\n"
45 "[Long Line]\n"
46 "foo: this line is much, much longer than my editor\n"
47 " likes it.\n"
48 "[Section\\with$weird%characters[\t]\n"
49 "[Internationalized Stuff]\n"
50 "foo[bg]: Bulgarian\n"
51 "foo=Default\n"
52 "foo[en]=English\n"
53 "foo[de]=Deutsch\n"
54 "[Spaces]\n"
55 "key with spaces : value\n"
56 "another with spaces = splat!\n"
57 )
58 L = cf.sections()
59 L.sort()
60 eq = self.assertEqual
61 eq(L, [r'Commented Bar',
62 r'Foo Bar',
63 r'Internationalized Stuff',
64 r'Long Line',
65 r'Section\with$weird%characters[' '\t',
66 r'Spaces',
67 r'Spacey Bar',
68 ])
Fred Drake8ef67672000-09-27 22:45:25 +000069
Fred Drakec6f28912002-10-25 19:40:49 +000070 # The use of spaces in the section names serves as a
71 # regression test for SourceForge bug #583248:
72 # http://www.python.org/sf/583248
73 eq(cf.get('Foo Bar', 'foo'), 'bar')
74 eq(cf.get('Spacey Bar', 'foo'), 'bar')
75 eq(cf.get('Commented Bar', 'foo'), 'bar')
76 eq(cf.get('Spaces', 'key with spaces'), 'value')
77 eq(cf.get('Spaces', 'another with spaces'), 'splat!')
Fred Drake3d5f7e82000-12-04 16:30:40 +000078
Fred Drakec6f28912002-10-25 19:40:49 +000079 self.failIf('__name__' in cf.options("Foo Bar"),
80 '__name__ "option" should not be exposed by the API!')
81
82 # Make sure the right things happen for remove_option();
83 # added to include check for SourceForge bug #123324:
84 self.failUnless(cf.remove_option('Foo Bar', 'foo'),
85 "remove_option() failed to report existance of option")
86 self.failIf(cf.has_option('Foo Bar', 'foo'),
87 "remove_option() failed to remove option")
88 self.failIf(cf.remove_option('Foo Bar', 'foo'),
89 "remove_option() failed to report non-existance of option"
90 " that was removed")
91
92 self.assertRaises(ConfigParser.NoSectionError,
93 cf.remove_option, 'No Such Section', 'foo')
94
95 eq(cf.get('Long Line', 'foo'),
Andrew M. Kuchling1bf71172002-03-08 18:10:12 +000096 'this line is much, much longer than my editor\nlikes it.')
Fred Drake3d5f7e82000-12-04 16:30:40 +000097
Fred Drakec6f28912002-10-25 19:40:49 +000098 def test_case_sensitivity(self):
99 cf = self.newconfig()
100 cf.add_section("A")
101 cf.add_section("a")
102 L = cf.sections()
103 L.sort()
104 eq = self.assertEqual
105 eq(L, ["A", "a"])
106 cf.set("a", "B", "value")
107 eq(cf.options("a"), ["b"])
108 eq(cf.get("a", "b"), "value",
Fred Drake3c823aa2001-02-26 21:55:34 +0000109 "could not locate option, expecting case-insensitive option names")
Fred Drakec6f28912002-10-25 19:40:49 +0000110 self.failUnless(cf.has_option("a", "b"))
111 cf.set("A", "A-B", "A-B value")
112 for opt in ("a-b", "A-b", "a-B", "A-B"):
113 self.failUnless(
114 cf.has_option("A", opt),
115 "has_option() returned false for option which should exist")
116 eq(cf.options("A"), ["a-b"])
117 eq(cf.options("a"), ["b"])
118 cf.remove_option("a", "B")
119 eq(cf.options("a"), [])
Fred Drake3c823aa2001-02-26 21:55:34 +0000120
Fred Drakec6f28912002-10-25 19:40:49 +0000121 # SF bug #432369:
122 cf = self.fromstring(
123 "[MySection]\nOption: first line\n\tsecond line\n")
124 eq(cf.options("MySection"), ["option"])
125 eq(cf.get("MySection", "Option"), "first line\nsecond line")
Fred Drakebeb67132001-07-06 17:22:48 +0000126
Fred Drakec6f28912002-10-25 19:40:49 +0000127 # SF bug #561822:
128 cf = self.fromstring("[section]\nnekey=nevalue\n",
129 defaults={"key":"value"})
130 self.failUnless(cf.has_option("section", "Key"))
Fred Drake309db062002-09-27 15:35:23 +0000131
Fred Drake3c823aa2001-02-26 21:55:34 +0000132
David Goodger68a1abd2004-10-03 15:40:25 +0000133 def test_default_case_sensitivity(self):
134 cf = self.newconfig({"foo": "Bar"})
135 self.assertEqual(
136 cf.get("DEFAULT", "Foo"), "Bar",
137 "could not locate option, expecting case-insensitive option names")
138 cf = self.newconfig({"Foo": "Bar"})
139 self.assertEqual(
140 cf.get("DEFAULT", "Foo"), "Bar",
141 "could not locate option, expecting case-insensitive defaults")
142
Fred Drakec6f28912002-10-25 19:40:49 +0000143 def test_parse_errors(self):
144 self.newconfig()
145 self.parse_error(ConfigParser.ParsingError,
146 "[Foo]\n extra-spaces: splat\n")
147 self.parse_error(ConfigParser.ParsingError,
148 "[Foo]\n extra-spaces= splat\n")
149 self.parse_error(ConfigParser.ParsingError,
150 "[Foo]\noption-without-value\n")
151 self.parse_error(ConfigParser.ParsingError,
152 "[Foo]\n:value-without-option-name\n")
153 self.parse_error(ConfigParser.ParsingError,
154 "[Foo]\n=value-without-option-name\n")
155 self.parse_error(ConfigParser.MissingSectionHeaderError,
156 "No Section!\n")
Fred Drake168bead2001-10-08 17:13:12 +0000157
Fred Drakec6f28912002-10-25 19:40:49 +0000158 def parse_error(self, exc, src):
Guido van Rossum34d19282007-08-09 01:03:29 +0000159 sio = io.StringIO(src)
Fred Drakec6f28912002-10-25 19:40:49 +0000160 self.assertRaises(exc, self.cf.readfp, sio)
Fred Drake168bead2001-10-08 17:13:12 +0000161
Fred Drakec6f28912002-10-25 19:40:49 +0000162 def test_query_errors(self):
163 cf = self.newconfig()
164 self.assertEqual(cf.sections(), [],
165 "new ConfigParser should have no defined sections")
166 self.failIf(cf.has_section("Foo"),
167 "new ConfigParser should have no acknowledged sections")
168 self.assertRaises(ConfigParser.NoSectionError,
169 cf.options, "Foo")
170 self.assertRaises(ConfigParser.NoSectionError,
171 cf.set, "foo", "bar", "value")
172 self.get_error(ConfigParser.NoSectionError, "foo", "bar")
173 cf.add_section("foo")
174 self.get_error(ConfigParser.NoOptionError, "foo", "bar")
Fred Drake8ef67672000-09-27 22:45:25 +0000175
Fred Drakec6f28912002-10-25 19:40:49 +0000176 def get_error(self, exc, section, option):
Fred Drake54782192002-12-31 06:57:25 +0000177 try:
178 self.cf.get(section, option)
Guido van Rossumb940e112007-01-10 16:19:56 +0000179 except exc as e:
Fred Drake54782192002-12-31 06:57:25 +0000180 return e
181 else:
182 self.fail("expected exception type %s.%s"
183 % (exc.__module__, exc.__name__))
Fred Drake3af0eb82002-10-25 18:09:24 +0000184
Fred Drakec6f28912002-10-25 19:40:49 +0000185 def test_boolean(self):
186 cf = self.fromstring(
187 "[BOOLTEST]\n"
188 "T1=1\n"
189 "T2=TRUE\n"
190 "T3=True\n"
191 "T4=oN\n"
192 "T5=yes\n"
193 "F1=0\n"
194 "F2=FALSE\n"
195 "F3=False\n"
196 "F4=oFF\n"
197 "F5=nO\n"
198 "E1=2\n"
199 "E2=foo\n"
200 "E3=-1\n"
201 "E4=0.1\n"
202 "E5=FALSE AND MORE"
203 )
204 for x in range(1, 5):
205 self.failUnless(cf.getboolean('BOOLTEST', 't%d' % x))
206 self.failIf(cf.getboolean('BOOLTEST', 'f%d' % x))
207 self.assertRaises(ValueError,
208 cf.getboolean, 'BOOLTEST', 'e%d' % x)
Fred Drake95b96d32001-02-12 17:23:20 +0000209
Fred Drakec6f28912002-10-25 19:40:49 +0000210 def test_weird_errors(self):
211 cf = self.newconfig()
Fred Drake8ef67672000-09-27 22:45:25 +0000212 cf.add_section("Foo")
Fred Drakec6f28912002-10-25 19:40:49 +0000213 self.assertRaises(ConfigParser.DuplicateSectionError,
214 cf.add_section, "Foo")
215
216 def test_write(self):
217 cf = self.fromstring(
218 "[Long Line]\n"
219 "foo: this line is much, much longer than my editor\n"
220 " likes it.\n"
221 "[DEFAULT]\n"
222 "foo: another very\n"
223 " long line"
224 )
Guido van Rossum34d19282007-08-09 01:03:29 +0000225 output = io.StringIO()
Fred Drakec6f28912002-10-25 19:40:49 +0000226 cf.write(output)
227 self.assertEqual(
228 output.getvalue(),
229 "[DEFAULT]\n"
230 "foo = another very\n"
231 "\tlong line\n"
232 "\n"
233 "[Long Line]\n"
234 "foo = this line is much, much longer than my editor\n"
235 "\tlikes it.\n"
236 "\n"
237 )
238
Fred Drakeabc086f2004-05-18 03:29:52 +0000239 def test_set_string_types(self):
240 cf = self.fromstring("[sect]\n"
241 "option1=foo\n")
242 # Check that we don't get an exception when setting values in
243 # an existing section using strings:
244 class mystr(str):
245 pass
246 cf.set("sect", "option1", "splat")
247 cf.set("sect", "option1", mystr("splat"))
248 cf.set("sect", "option2", "splat")
249 cf.set("sect", "option2", mystr("splat"))
Walter Dörwald5de48bd2007-06-11 21:38:39 +0000250 cf.set("sect", "option1", "splat")
251 cf.set("sect", "option2", "splat")
Fred Drakeabc086f2004-05-18 03:29:52 +0000252
Fred Drake82903142004-05-18 04:24:02 +0000253 def test_read_returns_file_list(self):
254 file1 = test_support.findfile("cfgparser.1")
255 # check when we pass a mix of readable and non-readable files:
256 cf = self.newconfig()
257 parsed_files = cf.read([file1, "nonexistant-file"])
258 self.assertEqual(parsed_files, [file1])
259 self.assertEqual(cf.get("Foo Bar", "foo"), "newbar")
260 # check when we pass only a filename:
261 cf = self.newconfig()
262 parsed_files = cf.read(file1)
263 self.assertEqual(parsed_files, [file1])
264 self.assertEqual(cf.get("Foo Bar", "foo"), "newbar")
265 # check when we pass only missing files:
266 cf = self.newconfig()
267 parsed_files = cf.read(["nonexistant-file"])
268 self.assertEqual(parsed_files, [])
269 # check when we pass no files:
270 cf = self.newconfig()
271 parsed_files = cf.read([])
272 self.assertEqual(parsed_files, [])
273
Fred Drakec6f28912002-10-25 19:40:49 +0000274 # shared by subclasses
275 def get_interpolation_config(self):
276 return self.fromstring(
277 "[Foo]\n"
278 "bar=something %(with1)s interpolation (1 step)\n"
279 "bar9=something %(with9)s lots of interpolation (9 steps)\n"
280 "bar10=something %(with10)s lots of interpolation (10 steps)\n"
281 "bar11=something %(with11)s lots of interpolation (11 steps)\n"
282 "with11=%(with10)s\n"
283 "with10=%(with9)s\n"
284 "with9=%(with8)s\n"
Fred Drakebc12b012004-05-18 02:25:51 +0000285 "with8=%(With7)s\n"
286 "with7=%(WITH6)s\n"
Fred Drakec6f28912002-10-25 19:40:49 +0000287 "with6=%(with5)s\n"
Fred Drakebc12b012004-05-18 02:25:51 +0000288 "With5=%(with4)s\n"
289 "WITH4=%(with3)s\n"
Fred Drakec6f28912002-10-25 19:40:49 +0000290 "with3=%(with2)s\n"
291 "with2=%(with1)s\n"
292 "with1=with\n"
293 "\n"
294 "[Mutual Recursion]\n"
295 "foo=%(bar)s\n"
Fred Drake54782192002-12-31 06:57:25 +0000296 "bar=%(foo)s\n"
297 "\n"
298 "[Interpolation Error]\n"
299 "name=%(reference)s\n",
300 # no definition for 'reference'
Fred Drakec6f28912002-10-25 19:40:49 +0000301 defaults={"getname": "%(__name__)s"})
Fred Drake95b96d32001-02-12 17:23:20 +0000302
Fred Drake98e3b292002-10-25 20:42:44 +0000303 def check_items_config(self, expected):
304 cf = self.fromstring(
305 "[section]\n"
306 "name = value\n"
307 "key: |%(name)s| \n"
308 "getdefault: |%(default)s|\n"
309 "getname: |%(__name__)s|",
310 defaults={"default": "<default>"})
311 L = list(cf.items("section"))
312 L.sort()
313 self.assertEqual(L, expected)
314
Fred Drake8ef67672000-09-27 22:45:25 +0000315
Fred Drakec6f28912002-10-25 19:40:49 +0000316class ConfigParserTestCase(TestCaseBase):
317 config_class = ConfigParser.ConfigParser
318
319 def test_interpolation(self):
320 cf = self.get_interpolation_config()
321 eq = self.assertEqual
322 eq(cf.get("Foo", "getname"), "Foo")
323 eq(cf.get("Foo", "bar"), "something with interpolation (1 step)")
324 eq(cf.get("Foo", "bar9"),
325 "something with lots of interpolation (9 steps)")
326 eq(cf.get("Foo", "bar10"),
327 "something with lots of interpolation (10 steps)")
328 self.get_error(ConfigParser.InterpolationDepthError, "Foo", "bar11")
Fred Drake95b96d32001-02-12 17:23:20 +0000329
Fred Drake54782192002-12-31 06:57:25 +0000330 def test_interpolation_missing_value(self):
331 cf = self.get_interpolation_config()
332 e = self.get_error(ConfigParser.InterpolationError,
333 "Interpolation Error", "name")
334 self.assertEqual(e.reference, "reference")
335 self.assertEqual(e.section, "Interpolation Error")
336 self.assertEqual(e.option, "name")
337
Fred Drake98e3b292002-10-25 20:42:44 +0000338 def test_items(self):
339 self.check_items_config([('default', '<default>'),
340 ('getdefault', '|<default>|'),
341 ('getname', '|section|'),
342 ('key', '|value|'),
343 ('name', 'value')])
344
David Goodger1cbf2062004-10-03 15:55:09 +0000345 def test_set_nonstring_types(self):
346 cf = self.newconfig()
347 cf.add_section('non-string')
348 cf.set('non-string', 'int', 1)
349 cf.set('non-string', 'list', [0, 1, 1, 2, 3, 5, 8, 13, '%('])
350 cf.set('non-string', 'dict', {'pi': 3.14159, '%(': 1,
351 '%(list)': '%(list)'})
352 cf.set('non-string', 'string_with_interpolation', '%(list)s')
353 self.assertEqual(cf.get('non-string', 'int', raw=True), 1)
354 self.assertRaises(TypeError, cf.get, 'non-string', 'int')
355 self.assertEqual(cf.get('non-string', 'list', raw=True),
356 [0, 1, 1, 2, 3, 5, 8, 13, '%('])
357 self.assertRaises(TypeError, cf.get, 'non-string', 'list')
358 self.assertEqual(cf.get('non-string', 'dict', raw=True),
359 {'pi': 3.14159, '%(': 1, '%(list)': '%(list)'})
360 self.assertRaises(TypeError, cf.get, 'non-string', 'dict')
361 self.assertEqual(cf.get('non-string', 'string_with_interpolation',
362 raw=True), '%(list)s')
363 self.assertRaises(ValueError, cf.get, 'non-string',
364 'string_with_interpolation', raw=False)
365
Fred Drake8ef67672000-09-27 22:45:25 +0000366
Fred Drakec6f28912002-10-25 19:40:49 +0000367class RawConfigParserTestCase(TestCaseBase):
368 config_class = ConfigParser.RawConfigParser
369
370 def test_interpolation(self):
371 cf = self.get_interpolation_config()
372 eq = self.assertEqual
373 eq(cf.get("Foo", "getname"), "%(__name__)s")
374 eq(cf.get("Foo", "bar"),
375 "something %(with1)s interpolation (1 step)")
376 eq(cf.get("Foo", "bar9"),
377 "something %(with9)s lots of interpolation (9 steps)")
378 eq(cf.get("Foo", "bar10"),
379 "something %(with10)s lots of interpolation (10 steps)")
380 eq(cf.get("Foo", "bar11"),
381 "something %(with11)s lots of interpolation (11 steps)")
Fred Drake95b96d32001-02-12 17:23:20 +0000382
Fred Drake98e3b292002-10-25 20:42:44 +0000383 def test_items(self):
384 self.check_items_config([('default', '<default>'),
385 ('getdefault', '|%(default)s|'),
386 ('getname', '|%(__name__)s|'),
387 ('key', '|%(name)s|'),
388 ('name', 'value')])
389
David Goodger1cbf2062004-10-03 15:55:09 +0000390 def test_set_nonstring_types(self):
391 cf = self.newconfig()
392 cf.add_section('non-string')
393 cf.set('non-string', 'int', 1)
394 cf.set('non-string', 'list', [0, 1, 1, 2, 3, 5, 8, 13])
395 cf.set('non-string', 'dict', {'pi': 3.14159})
396 self.assertEqual(cf.get('non-string', 'int'), 1)
397 self.assertEqual(cf.get('non-string', 'list'),
398 [0, 1, 1, 2, 3, 5, 8, 13])
399 self.assertEqual(cf.get('non-string', 'dict'), {'pi': 3.14159})
Tim Petersab9b32c2004-10-03 18:35:19 +0000400
Fred Drake8ef67672000-09-27 22:45:25 +0000401
Fred Drake0eebd5c2002-10-25 21:52:00 +0000402class SafeConfigParserTestCase(ConfigParserTestCase):
403 config_class = ConfigParser.SafeConfigParser
404
405 def test_safe_interpolation(self):
406 # See http://www.python.org/sf/511737
407 cf = self.fromstring("[section]\n"
408 "option1=xxx\n"
409 "option2=%(option1)s/xxx\n"
410 "ok=%(option1)s/%%s\n"
411 "not_ok=%(option2)s/%%s")
412 self.assertEqual(cf.get("section", "ok"), "xxx/%s")
413 self.assertEqual(cf.get("section", "not_ok"), "xxx/xxx/%s")
414
Guido van Rossumd8faa362007-04-27 19:54:29 +0000415 def test_set_malformatted_interpolation(self):
416 cf = self.fromstring("[sect]\n"
417 "option1=foo\n")
418
419 self.assertEqual(cf.get('sect', "option1"), "foo")
420
421 self.assertRaises(ValueError, cf.set, "sect", "option1", "%foo")
422 self.assertRaises(ValueError, cf.set, "sect", "option1", "foo%")
423 self.assertRaises(ValueError, cf.set, "sect", "option1", "f%oo")
424
425 self.assertEqual(cf.get('sect', "option1"), "foo")
426
David Goodger1cbf2062004-10-03 15:55:09 +0000427 def test_set_nonstring_types(self):
428 cf = self.fromstring("[sect]\n"
429 "option1=foo\n")
430 # Check that we get a TypeError when setting non-string values
431 # in an existing section:
432 self.assertRaises(TypeError, cf.set, "sect", "option1", 1)
433 self.assertRaises(TypeError, cf.set, "sect", "option1", 1.0)
434 self.assertRaises(TypeError, cf.set, "sect", "option1", object())
435 self.assertRaises(TypeError, cf.set, "sect", "option2", 1)
436 self.assertRaises(TypeError, cf.set, "sect", "option2", 1.0)
437 self.assertRaises(TypeError, cf.set, "sect", "option2", object())
438
Thomas Wouters89f507f2006-12-13 04:49:30 +0000439class SortedTestCase(RawConfigParserTestCase):
440 def newconfig(self, defaults=None):
441 self.cf = self.config_class(defaults=defaults, dict_type=SortedDict)
442 return self.cf
443
444 def test_sorted(self):
445 self.fromstring("[b]\n"
446 "o4=1\n"
447 "o3=2\n"
448 "o2=3\n"
449 "o1=4\n"
450 "[a]\n"
Thomas Wouters9fe394c2007-02-05 01:24:16 +0000451 "k=v\n")
Guido van Rossum34d19282007-08-09 01:03:29 +0000452 output = io.StringIO()
Thomas Wouters89f507f2006-12-13 04:49:30 +0000453 self.cf.write(output)
454 self.assertEquals(output.getvalue(),
455 "[a]\n"
Thomas Wouters9fe394c2007-02-05 01:24:16 +0000456 "k = v\n\n"
Thomas Wouters89f507f2006-12-13 04:49:30 +0000457 "[b]\n"
458 "o1 = 4\n"
459 "o2 = 3\n"
460 "o3 = 2\n"
461 "o4 = 1\n\n")
Fred Drake0eebd5c2002-10-25 21:52:00 +0000462
Fred Drakec6f28912002-10-25 19:40:49 +0000463def test_main():
Walter Dörwald21d3a322003-05-01 17:45:56 +0000464 test_support.run_unittest(
465 ConfigParserTestCase,
466 RawConfigParserTestCase,
Thomas Wouters89f507f2006-12-13 04:49:30 +0000467 SafeConfigParserTestCase,
468 SortedTestCase
Walter Dörwald21d3a322003-05-01 17:45:56 +0000469 )
Fred Drake3af0eb82002-10-25 18:09:24 +0000470
Fred Drakec6f28912002-10-25 19:40:49 +0000471if __name__ == "__main__":
472 test_main()