blob: 64456db482fbba47f9634aa767043c1685d62480 [file] [log] [blame]
Skip Montanarob4a04172003-03-20 23:29:12 +00001# Copyright (C) 2001,2002 Python Software Foundation
2# csv package unit tests
3
4import sys
5import unittest
6from StringIO import StringIO
7from csv import csv
8import gc
9
10class Test_Csv(unittest.TestCase):
11 """
12 Test the underlying C csv parser in ways that are not appropriate
13 from the high level interface. Further tests of this nature are done
14 in TestDialectRegistry.
15 """
16 def test_reader_arg_valid(self):
17 self.assertRaises(TypeError, csv.reader)
18 self.assertRaises(TypeError, csv.reader, None)
19 self.assertRaises(AttributeError, csv.reader, [], bad_attr = 0)
20 self.assertRaises(csv.Error, csv.reader, [], 'foo')
21 class BadClass:
22 def __init__(self):
23 raise IOError
24 self.assertRaises(IOError, csv.reader, [], BadClass)
25 self.assertRaises(TypeError, csv.reader, [], None)
26 class BadDialect:
27 bad_attr = 0
28 self.assertRaises(AttributeError, csv.reader, [], BadDialect)
29
30 def test_writer_arg_valid(self):
31 self.assertRaises(TypeError, csv.writer)
32 self.assertRaises(TypeError, csv.writer, None)
33 self.assertRaises(AttributeError, csv.writer, StringIO(), bad_attr = 0)
34
35 def _test_attrs(self, obj):
36 self.assertEqual(obj.dialect.delimiter, ',')
37 obj.dialect.delimiter = '\t'
38 self.assertEqual(obj.dialect.delimiter, '\t')
39 self.assertRaises(TypeError, delattr, obj.dialect, 'delimiter')
40 self.assertRaises(TypeError, setattr, obj.dialect,
41 'lineterminator', None)
42 obj.dialect.escapechar = None
43 self.assertEqual(obj.dialect.escapechar, None)
44 self.assertRaises(TypeError, delattr, obj.dialect, 'quoting')
45 self.assertRaises(TypeError, setattr, obj.dialect, 'quoting', None)
46 obj.dialect.quoting = csv.QUOTE_MINIMAL
47 self.assertEqual(obj.dialect.quoting, csv.QUOTE_MINIMAL)
48
49 def test_reader_attrs(self):
50 self._test_attrs(csv.reader([]))
51
52 def test_writer_attrs(self):
53 self._test_attrs(csv.writer(StringIO()))
54
55 def _write_test(self, fields, expect, **kwargs):
56 fileobj = StringIO()
57 writer = csv.writer(fileobj, **kwargs)
58 writer.writerow(fields)
59 self.assertEqual(fileobj.getvalue(),
60 expect + writer.dialect.lineterminator)
61
62 def test_write_arg_valid(self):
63 self.assertRaises(csv.Error, self._write_test, None, '')
64 self._write_test((), '')
65 self._write_test([None], '""')
66 self.assertRaises(csv.Error, self._write_test,
67 [None], None, quoting = csv.QUOTE_NONE)
68 # Check that exceptions are passed up the chain
69 class BadList:
70 def __len__(self):
71 return 10;
72 def __getitem__(self, i):
73 if i > 2:
74 raise IOError
75 self.assertRaises(IOError, self._write_test, BadList(), '')
76 class BadItem:
77 def __str__(self):
78 raise IOError
79 self.assertRaises(IOError, self._write_test, [BadItem()], '')
80
81 def test_write_bigfield(self):
82 # This exercises the buffer realloc functionality
83 bigstring = 'X' * 50000
84 self._write_test([bigstring,bigstring], '%s,%s' % \
85 (bigstring, bigstring))
86
87 def test_write_quoting(self):
88 self._write_test(['a','1','p,q'], 'a,1,"p,q"')
89 self.assertRaises(csv.Error,
90 self._write_test,
91 ['a','1','p,q'], 'a,1,"p,q"',
92 quoting = csv.QUOTE_NONE)
93 self._write_test(['a','1','p,q'], 'a,1,"p,q"',
94 quoting = csv.QUOTE_MINIMAL)
95 self._write_test(['a','1','p,q'], '"a",1,"p,q"',
96 quoting = csv.QUOTE_NONNUMERIC)
97 self._write_test(['a','1','p,q'], '"a","1","p,q"',
98 quoting = csv.QUOTE_ALL)
99
100 def test_write_escape(self):
101 self._write_test(['a','1','p,q'], 'a,1,"p,q"',
102 escapechar='\\')
103# FAILED - needs to be fixed [am]:
104# self._write_test(['a','1','p,"q"'], 'a,1,"p,\\"q\\"',
105# escapechar='\\', doublequote = 0)
106 self._write_test(['a','1','p,q'], 'a,1,p\\,q',
107 escapechar='\\', quoting = csv.QUOTE_NONE)
108
109 def test_writerows(self):
110 class BrokenFile:
111 def write(self, buf):
112 raise IOError
113 writer = csv.writer(BrokenFile())
114 self.assertRaises(IOError, writer.writerows, [['a']])
115 fileobj = StringIO()
116 writer = csv.writer(fileobj)
117 self.assertRaises(TypeError, writer.writerows, None)
118 writer.writerows([['a','b'],['c','d']])
119 self.assertEqual(fileobj.getvalue(), "a,b\r\nc,d\r\n")
120
121 def _read_test(self, input, expect, **kwargs):
122 reader = csv.reader(input, **kwargs)
123 result = list(reader)
124 self.assertEqual(result, expect)
125
126 def test_read_oddinputs(self):
127 self._read_test([], [])
128 self._read_test([''], [[]])
129 self.assertRaises(csv.Error, self._read_test,
130 ['"ab"c'], None, strict = 1)
131 # cannot handle null bytes for the moment
132 self.assertRaises(csv.Error, self._read_test,
133 ['ab\0c'], None, strict = 1)
134 self._read_test(['"ab"c'], [['abc']], doublequote = 0)
135
136 def test_read_eol(self):
137 self._read_test(['a,b'], [['a','b']])
138 self._read_test(['a,b\n'], [['a','b']])
139 self._read_test(['a,b\r\n'], [['a','b']])
140 self._read_test(['a,b\r'], [['a','b']])
141 self.assertRaises(csv.Error, self._read_test, ['a,b\rc,d'], [])
142 self.assertRaises(csv.Error, self._read_test, ['a,b\nc,d'], [])
143 self.assertRaises(csv.Error, self._read_test, ['a,b\r\nc,d'], [])
144
145 def test_read_escape(self):
146 self._read_test(['a,\\b,c'], [['a', '\\b', 'c']], escapechar='\\')
147 self._read_test(['a,b\\,c'], [['a', 'b,c']], escapechar='\\')
148 self._read_test(['a,"b\\,c"'], [['a', 'b,c']], escapechar='\\')
149 self._read_test(['a,"b,\\c"'], [['a', 'b,\\c']], escapechar='\\')
150 self._read_test(['a,"b,c\\""'], [['a', 'b,c"']], escapechar='\\')
151 self._read_test(['a,"b,c"\\'], [['a', 'b,c\\']], escapechar='\\')
152
153 def test_read_bigfield(self):
154 # This exercises the buffer realloc functionality
155 bigstring = 'X' * 50000
156 bigline = '%s,%s' % (bigstring, bigstring)
157 self._read_test([bigline], [[bigstring, bigstring]])
158
159class TestDialectRegistry(unittest.TestCase):
160 def test_registry_badargs(self):
161 self.assertRaises(TypeError, csv.list_dialects, None)
162 self.assertRaises(TypeError, csv.get_dialect)
163 self.assertRaises(csv.Error, csv.get_dialect, None)
164 self.assertRaises(csv.Error, csv.get_dialect, "nonesuch")
165 self.assertRaises(TypeError, csv.unregister_dialect)
166 self.assertRaises(csv.Error, csv.unregister_dialect, None)
167 self.assertRaises(csv.Error, csv.unregister_dialect, "nonesuch")
168 self.assertRaises(TypeError, csv.register_dialect, None)
169 self.assertRaises(TypeError, csv.register_dialect, None, None)
170 self.assertRaises(TypeError, csv.register_dialect, "nonesuch", None)
171 class bogus:
172 def __init__(self):
173 raise KeyError
174 self.assertRaises(KeyError, csv.register_dialect, "nonesuch", bogus)
175
176 def test_registry(self):
177 class myexceltsv(csv.excel):
178 delimiter = "\t"
179 name = "myexceltsv"
180 expected_dialects = csv.list_dialects() + [name]
181 expected_dialects.sort()
182 csv.register_dialect(name, myexceltsv)
183 try:
184 self.failUnless(isinstance(csv.get_dialect(name), myexceltsv))
185 got_dialects = csv.list_dialects()
186 got_dialects.sort()
187 self.assertEqual(expected_dialects, got_dialects)
188 finally:
189 csv.unregister_dialect(name)
190
191 def test_incomplete_dialect(self):
192 class myexceltsv(csv.Dialect):
193 delimiter = "\t"
194 self.assertRaises(csv.Error, myexceltsv)
195
196 def test_space_dialect(self):
197 class space(csv.excel):
198 delimiter = " "
199 quoting = csv.QUOTE_NONE
200 escapechar = "\\"
201
202 s = StringIO("abc def\nc1ccccc1 benzene\n")
203 rdr = csv.reader(s, dialect=space())
204 self.assertEqual(rdr.next(), ["abc", "def"])
205 self.assertEqual(rdr.next(), ["c1ccccc1", "benzene"])
206
207 def test_dialect_apply(self):
208 class testA(csv.excel):
209 delimiter = "\t"
210 class testB(csv.excel):
211 delimiter = ":"
212 class testC(csv.excel):
213 delimiter = "|"
214
215 csv.register_dialect('testC', testC)
216 try:
217 fileobj = StringIO()
218 writer = csv.writer(fileobj)
219 writer.writerow([1,2,3])
220 self.assertEqual(fileobj.getvalue(), "1,2,3\r\n")
221
222 fileobj = StringIO()
223 writer = csv.writer(fileobj, testA)
224 writer.writerow([1,2,3])
225 self.assertEqual(fileobj.getvalue(), "1\t2\t3\r\n")
226
227 fileobj = StringIO()
228 writer = csv.writer(fileobj, dialect=testB())
229 writer.writerow([1,2,3])
230 self.assertEqual(fileobj.getvalue(), "1:2:3\r\n")
231
232 fileobj = StringIO()
233 writer = csv.writer(fileobj, dialect='testC')
234 writer.writerow([1,2,3])
235 self.assertEqual(fileobj.getvalue(), "1|2|3\r\n")
236
237 fileobj = StringIO()
238 writer = csv.writer(fileobj, dialect=testA, delimiter=';')
239 writer.writerow([1,2,3])
240 self.assertEqual(fileobj.getvalue(), "1;2;3\r\n")
241 finally:
242 csv.unregister_dialect('testC')
243
244 def test_bad_dialect(self):
245 # Unknown parameter
246 self.assertRaises(AttributeError, csv.reader, [], bad_attr = 0)
247 # Bad values
248 self.assertRaises(TypeError, csv.reader, [], delimiter = None)
249 self.assertRaises(TypeError, csv.reader, [], quoting = -1)
250 self.assertRaises(TypeError, csv.reader, [], quoting = 100)
251
252class TestCsvBase(unittest.TestCase):
253 def readerAssertEqual(self, input, expected_result):
254 reader = csv.reader(StringIO(input), dialect = self.dialect)
255 fields = list(reader)
256 self.assertEqual(fields, expected_result)
257
258 def writerAssertEqual(self, input, expected_result):
259 fileobj = StringIO()
260 writer = csv.writer(fileobj, dialect = self.dialect)
261 writer.writerows(input)
262 self.assertEqual(fileobj.getvalue(), expected_result)
263
264class TestDialectExcel(TestCsvBase):
265 dialect = 'excel'
266
267 def test_single(self):
268 self.readerAssertEqual('abc', [['abc']])
269
270 def test_simple(self):
271 self.readerAssertEqual('1,2,3,4,5', [['1','2','3','4','5']])
272
273 def test_blankline(self):
274 self.readerAssertEqual('', [])
275
276 def test_empty_fields(self):
277 self.readerAssertEqual(',', [['', '']])
278
279 def test_singlequoted(self):
280 self.readerAssertEqual('""', [['']])
281
282 def test_singlequoted_left_empty(self):
283 self.readerAssertEqual('"",', [['','']])
284
285 def test_singlequoted_right_empty(self):
286 self.readerAssertEqual(',""', [['','']])
287
288 def test_single_quoted_quote(self):
289 self.readerAssertEqual('""""', [['"']])
290
291 def test_quoted_quotes(self):
292 self.readerAssertEqual('""""""', [['""']])
293
294 def test_inline_quote(self):
295 self.readerAssertEqual('a""b', [['a""b']])
296
297 def test_inline_quotes(self):
298 self.readerAssertEqual('a"b"c', [['a"b"c']])
299
300 def test_quotes_and_more(self):
301 self.readerAssertEqual('"a"b', [['ab']])
302
303 def test_lone_quote(self):
304 self.readerAssertEqual('a"b', [['a"b']])
305
306 def test_quote_and_quote(self):
307 self.readerAssertEqual('"a" "b"', [['a "b"']])
308
309 def test_space_and_quote(self):
310 self.readerAssertEqual(' "a"', [[' "a"']])
311
312 def test_quoted(self):
313 self.readerAssertEqual('1,2,3,"I think, therefore I am",5,6',
314 [['1', '2', '3',
315 'I think, therefore I am',
316 '5', '6']])
317
318 def test_quoted_quote(self):
319 self.readerAssertEqual('1,2,3,"""I see,"" said the blind man","as he picked up his hammer and saw"',
320 [['1', '2', '3',
321 '"I see," said the blind man',
322 'as he picked up his hammer and saw']])
323
324 def test_quoted_nl(self):
325 input = '''\
3261,2,3,"""I see,""
327said the blind man","as he picked up his
328hammer and saw"
3299,8,7,6'''
330 self.readerAssertEqual(input,
331 [['1', '2', '3',
332 '"I see,"\nsaid the blind man',
333 'as he picked up his\nhammer and saw'],
334 ['9','8','7','6']])
335
336 def test_dubious_quote(self):
337 self.readerAssertEqual('12,12,1",', [['12', '12', '1"', '']])
338
339 def test_null(self):
340 self.writerAssertEqual([], '')
341
342 def test_single(self):
343 self.writerAssertEqual([['abc']], 'abc\r\n')
344
345 def test_simple(self):
346 self.writerAssertEqual([[1, 2, 'abc', 3, 4]], '1,2,abc,3,4\r\n')
347
348 def test_quotes(self):
349 self.writerAssertEqual([[1, 2, 'a"bc"', 3, 4]], '1,2,"a""bc""",3,4\r\n')
350
351 def test_quote_fieldsep(self):
352 self.writerAssertEqual([['abc,def']], '"abc,def"\r\n')
353
354 def test_newlines(self):
355 self.writerAssertEqual([[1, 2, 'a\nbc', 3, 4]], '1,2,"a\nbc",3,4\r\n')
356
357class EscapedExcel(csv.excel):
358 quoting = csv.QUOTE_NONE
359 escapechar = '\\'
360
361class TestEscapedExcel(TestCsvBase):
362 dialect = EscapedExcel()
363
364 def test_escape_fieldsep(self):
365 self.writerAssertEqual([['abc,def']], 'abc\\,def\r\n')
366
367 def test_read_escape_fieldsep(self):
368 self.readerAssertEqual('abc\\,def\r\n', [['abc,def']])
369
370class QuotedEscapedExcel(csv.excel):
371 quoting = csv.QUOTE_NONNUMERIC
372 escapechar = '\\'
373
374class TestQuotedEscapedExcel(TestCsvBase):
375 dialect = QuotedEscapedExcel()
376
377 def test_write_escape_fieldsep(self):
378 self.writerAssertEqual([['abc,def']], '"abc,def"\r\n')
379
380 def test_read_escape_fieldsep(self):
381 self.readerAssertEqual('"abc\\,def"\r\n', [['abc,def']])
382
383# Disabled, pending support in csv.utils module
384class TestDictFields(unittest.TestCase):
385 ### "long" means the row is longer than the number of fieldnames
386 ### "short" means there are fewer elements in the row than fieldnames
387 def test_write_simple_dict(self):
388 fileobj = StringIO()
389 writer = csv.DictWriter(fileobj, fieldnames = ["f1", "f2", "f3"])
390 writer.writerow({"f1": 10, "f3": "abc"})
391 self.assertEqual(fileobj.getvalue(), "10,,abc\r\n")
392
393 def test_write_no_fields(self):
394 fileobj = StringIO()
395 self.assertRaises(TypeError, csv.DictWriter, fileobj)
396
397 def test_read_dict_fields(self):
398 reader = csv.DictReader(StringIO("1,2,abc\r\n"),
399 fieldnames=["f1", "f2", "f3"])
400 self.assertEqual(reader.next(), {"f1": '1', "f2": '2', "f3": 'abc'})
401
402 def test_read_long(self):
403 reader = csv.DictReader(StringIO("1,2,abc,4,5,6\r\n"),
404 fieldnames=["f1", "f2"])
405 self.assertEqual(reader.next(), {"f1": '1', "f2": '2',
406 None: ["abc", "4", "5", "6"]})
407
408 def test_read_long_with_rest(self):
409 reader = csv.DictReader(StringIO("1,2,abc,4,5,6\r\n"),
410 fieldnames=["f1", "f2"], restkey="_rest")
411 self.assertEqual(reader.next(), {"f1": '1', "f2": '2',
412 "_rest": ["abc", "4", "5", "6"]})
413
414 def test_read_short(self):
415 reader = csv.DictReader(["1,2,abc,4,5,6\r\n","1,2,abc\r\n"],
416 fieldnames="1 2 3 4 5 6".split(),
417 restval="DEFAULT")
418 self.assertEqual(reader.next(), {"1": '1', "2": '2', "3": 'abc',
419 "4": '4', "5": '5', "6": '6'})
420 self.assertEqual(reader.next(), {"1": '1', "2": '2', "3": 'abc',
421 "4": 'DEFAULT', "5": 'DEFAULT',
422 "6": 'DEFAULT'})
423
424 def test_read_with_blanks(self):
425 reader = csv.DictReader(["1,2,abc,4,5,6\r\n","\r\n",
426 "1,2,abc,4,5,6\r\n"],
427 fieldnames="1 2 3 4 5 6".split())
428 self.assertEqual(reader.next(), {"1": '1', "2": '2', "3": 'abc',
429 "4": '4', "5": '5', "6": '6'})
430 self.assertEqual(reader.next(), {"1": '1', "2": '2', "3": 'abc',
431 "4": '4', "5": '5', "6": '6'})
432
433class TestArrayWrites(unittest.TestCase):
434 def test_int_write(self):
435 import array
436 contents = [(20-i) for i in range(20)]
437 a = array.array('i', contents)
438 fileobj = StringIO()
439 writer = csv.writer(fileobj, dialect="excel")
440 writer.writerow(a)
441 expected = ",".join([str(i) for i in a])+"\r\n"
442 self.assertEqual(fileobj.getvalue(), expected)
443
444 def test_double_write(self):
445 import array
446 contents = [(20-i)*0.1 for i in range(20)]
447 a = array.array('d', contents)
448 fileobj = StringIO()
449 writer = csv.writer(fileobj, dialect="excel")
450 writer.writerow(a)
451 expected = ",".join([str(i) for i in a])+"\r\n"
452 self.assertEqual(fileobj.getvalue(), expected)
453
454 def test_float_write(self):
455 import array
456 contents = [(20-i)*0.1 for i in range(20)]
457 a = array.array('f', contents)
458 fileobj = StringIO()
459 writer = csv.writer(fileobj, dialect="excel")
460 writer.writerow(a)
461 expected = ",".join([str(i) for i in a])+"\r\n"
462 self.assertEqual(fileobj.getvalue(), expected)
463
464 def test_char_write(self):
465 import array, string
466 a = array.array('c', string.letters)
467 fileobj = StringIO()
468 writer = csv.writer(fileobj, dialect="excel")
469 writer.writerow(a)
470 expected = ",".join(a)+"\r\n"
471 self.assertEqual(fileobj.getvalue(), expected)
472
473class TestDialectValidity(unittest.TestCase):
474 def test_quoting(self):
475 class mydialect(csv.Dialect):
476 delimiter = ";"
477 escapechar = '\\'
478 doublequote = False
479 skipinitialspace = True
480 lineterminator = '\r\n'
481 quoting = csv.QUOTE_NONE
482 d = mydialect()
483
484 mydialect.quoting = None
485 self.assertRaises(csv.Error, mydialect)
486
487 mydialect.quoting = csv.QUOTE_NONE
488 mydialect.escapechar = None
489 self.assertRaises(csv.Error, mydialect)
490
491 mydialect.doublequote = True
492 mydialect.quoting = csv.QUOTE_ALL
493 mydialect.quotechar = '"'
494 d = mydialect()
495
496 mydialect.quotechar = "''"
497 self.assertRaises(csv.Error, mydialect)
498
499 mydialect.quotechar = 4
500 self.assertRaises(csv.Error, mydialect)
501
502 def test_delimiter(self):
503 class mydialect(csv.Dialect):
504 delimiter = ";"
505 escapechar = '\\'
506 doublequote = False
507 skipinitialspace = True
508 lineterminator = '\r\n'
509 quoting = csv.QUOTE_NONE
510 d = mydialect()
511
512 mydialect.delimiter = ":::"
513 self.assertRaises(csv.Error, mydialect)
514
515 mydialect.delimiter = 4
516 self.assertRaises(csv.Error, mydialect)
517
518 def test_lineterminator(self):
519 class mydialect(csv.Dialect):
520 delimiter = ";"
521 escapechar = '\\'
522 doublequote = False
523 skipinitialspace = True
524 lineterminator = '\r\n'
525 quoting = csv.QUOTE_NONE
526 d = mydialect()
527
528 mydialect.lineterminator = ":::"
529 d = mydialect()
530
531 mydialect.lineterminator = 4
532 self.assertRaises(csv.Error, mydialect)
533
534
535if not hasattr(sys, "gettotalrefcount"):
536 print "*** skipping leakage tests ***"
537else:
538 class NUL:
539 def write(s, *args):
540 pass
541 writelines = write
542
543 class TestLeaks(unittest.TestCase):
544 def test_create_read(self):
545 delta = 0
546 lastrc = sys.gettotalrefcount()
547 for i in xrange(20):
548 gc.collect()
549 self.assertEqual(gc.garbage, [])
550 rc = sys.gettotalrefcount()
551 csv.reader(["a,b,c\r\n"])
552 csv.reader(["a,b,c\r\n"])
553 csv.reader(["a,b,c\r\n"])
554 delta = rc-lastrc
555 lastrc = rc
556 # if csv.reader() leaks, last delta should be 3 or more
557 self.assertEqual(delta < 3, True)
558
559 def test_create_write(self):
560 delta = 0
561 lastrc = sys.gettotalrefcount()
562 s = NUL()
563 for i in xrange(20):
564 gc.collect()
565 self.assertEqual(gc.garbage, [])
566 rc = sys.gettotalrefcount()
567 csv.writer(s)
568 csv.writer(s)
569 csv.writer(s)
570 delta = rc-lastrc
571 lastrc = rc
572 # if csv.writer() leaks, last delta should be 3 or more
573 self.assertEqual(delta < 3, True)
574
575 def test_read(self):
576 delta = 0
577 rows = ["a,b,c\r\n"]*5
578 lastrc = sys.gettotalrefcount()
579 for i in xrange(20):
580 gc.collect()
581 self.assertEqual(gc.garbage, [])
582 rc = sys.gettotalrefcount()
583 rdr = csv.reader(rows)
584 for row in rdr:
585 pass
586 delta = rc-lastrc
587 lastrc = rc
588 # if reader leaks during read, delta should be 5 or more
589 self.assertEqual(delta < 5, True)
590
591 def test_write(self):
592 delta = 0
593 rows = [[1,2,3]]*5
594 s = NUL()
595 lastrc = sys.gettotalrefcount()
596 for i in xrange(20):
597 gc.collect()
598 self.assertEqual(gc.garbage, [])
599 rc = sys.gettotalrefcount()
600 writer = csv.writer(s)
601 for row in rows:
602 writer.writerow(row)
603 delta = rc-lastrc
604 lastrc = rc
605 # if writer leaks during write, last delta should be 5 or more
606 self.assertEqual(delta < 5, True)
607
608def _testclasses():
609 mod = sys.modules[__name__]
610 return [getattr(mod, name) for name in dir(mod) if name.startswith('Test')]
611
612def suite():
613 suite = unittest.TestSuite()
614 for testclass in _testclasses():
615 suite.addTest(unittest.makeSuite(testclass))
616 return suite
617
618if __name__ == '__main__':
619 unittest.main(defaultTest='suite')