1# -*- coding: iso-8859-1 -*- 2# Copyright (C) 2001,2002 Python Software Foundation 3# csv package unit tests 4 5import copy 6import sys 7import os 8import unittest 9from StringIO import StringIO 10import tempfile 11import csv 12import gc 13import io 14import pickle 15from test import test_support 16 17class Test_Csv(unittest.TestCase): 18 """ 19 Test the underlying C csv parser in ways that are not appropriate 20 from the high level interface. Further tests of this nature are done 21 in TestDialectRegistry. 22 """ 23 def _test_arg_valid(self, ctor, arg): 24 self.assertRaises(TypeError, ctor) 25 self.assertRaises(TypeError, ctor, None) 26 self.assertRaises(TypeError, ctor, arg, bad_attr = 0) 27 self.assertRaises(TypeError, ctor, arg, delimiter = 0) 28 self.assertRaises(TypeError, ctor, arg, delimiter = 'XX') 29 self.assertRaises(csv.Error, ctor, arg, 'foo') 30 self.assertRaises(TypeError, ctor, arg, delimiter=None) 31 self.assertRaises(TypeError, ctor, arg, delimiter=1) 32 self.assertRaises(TypeError, ctor, arg, quotechar=1) 33 self.assertRaises(TypeError, ctor, arg, lineterminator=None) 34 self.assertRaises(TypeError, ctor, arg, lineterminator=1) 35 self.assertRaises(TypeError, ctor, arg, quoting=None) 36 self.assertRaises(TypeError, ctor, arg, 37 quoting=csv.QUOTE_ALL, quotechar='') 38 self.assertRaises(TypeError, ctor, arg, 39 quoting=csv.QUOTE_ALL, quotechar=None) 40 41 def test_reader_arg_valid(self): 42 self._test_arg_valid(csv.reader, []) 43 44 def test_writer_arg_valid(self): 45 self._test_arg_valid(csv.writer, StringIO()) 46 47 def _test_default_attrs(self, ctor, *args): 48 obj = ctor(*args) 49 # Check defaults 50 self.assertEqual(obj.dialect.delimiter, ',') 51 self.assertEqual(obj.dialect.doublequote, True) 52 self.assertEqual(obj.dialect.escapechar, None) 53 self.assertEqual(obj.dialect.lineterminator, "\r\n") 54 self.assertEqual(obj.dialect.quotechar, '"') 55 self.assertEqual(obj.dialect.quoting, csv.QUOTE_MINIMAL) 56 self.assertEqual(obj.dialect.skipinitialspace, False) 57 self.assertEqual(obj.dialect.strict, False) 58 # Try deleting or changing attributes (they are read-only) 59 self.assertRaises(TypeError, delattr, obj.dialect, 'delimiter') 60 self.assertRaises(TypeError, setattr, obj.dialect, 'delimiter', ':') 61 self.assertRaises(AttributeError, delattr, obj.dialect, 'quoting') 62 self.assertRaises(AttributeError, setattr, obj.dialect, 63 'quoting', None) 64 65 def test_reader_attrs(self): 66 self._test_default_attrs(csv.reader, []) 67 68 def test_writer_attrs(self): 69 self._test_default_attrs(csv.writer, StringIO()) 70 71 def _test_kw_attrs(self, ctor, *args): 72 # Now try with alternate options 73 kwargs = dict(delimiter=':', doublequote=False, escapechar='\\', 74 lineterminator='\r', quotechar='*', 75 quoting=csv.QUOTE_NONE, skipinitialspace=True, 76 strict=True) 77 obj = ctor(*args, **kwargs) 78 self.assertEqual(obj.dialect.delimiter, ':') 79 self.assertEqual(obj.dialect.doublequote, False) 80 self.assertEqual(obj.dialect.escapechar, '\\') 81 self.assertEqual(obj.dialect.lineterminator, "\r") 82 self.assertEqual(obj.dialect.quotechar, '*') 83 self.assertEqual(obj.dialect.quoting, csv.QUOTE_NONE) 84 self.assertEqual(obj.dialect.skipinitialspace, True) 85 self.assertEqual(obj.dialect.strict, True) 86 87 def test_reader_kw_attrs(self): 88 self._test_kw_attrs(csv.reader, []) 89 90 def test_writer_kw_attrs(self): 91 self._test_kw_attrs(csv.writer, StringIO()) 92 93 def _test_dialect_attrs(self, ctor, *args): 94 # Now try with dialect-derived options 95 class dialect: 96 delimiter='-' 97 doublequote=False 98 escapechar='^' 99 lineterminator='$' 100 quotechar='#' 101 quoting=csv.QUOTE_ALL 102 skipinitialspace=True 103 strict=False 104 args = args + (dialect,) 105 obj = ctor(*args) 106 self.assertEqual(obj.dialect.delimiter, '-') 107 self.assertEqual(obj.dialect.doublequote, False) 108 self.assertEqual(obj.dialect.escapechar, '^') 109 self.assertEqual(obj.dialect.lineterminator, "$") 110 self.assertEqual(obj.dialect.quotechar, '#') 111 self.assertEqual(obj.dialect.quoting, csv.QUOTE_ALL) 112 self.assertEqual(obj.dialect.skipinitialspace, True) 113 self.assertEqual(obj.dialect.strict, False) 114 115 def test_reader_dialect_attrs(self): 116 self._test_dialect_attrs(csv.reader, []) 117 118 def test_writer_dialect_attrs(self): 119 self._test_dialect_attrs(csv.writer, StringIO()) 120 121 122 def _write_test(self, fields, expect, **kwargs): 123 fd, name = tempfile.mkstemp() 124 fileobj = os.fdopen(fd, "w+b") 125 try: 126 writer = csv.writer(fileobj, **kwargs) 127 writer.writerow(fields) 128 fileobj.seek(0) 129 self.assertEqual(fileobj.read(), 130 expect + writer.dialect.lineterminator) 131 finally: 132 fileobj.close() 133 os.unlink(name) 134 135 def _write_error_test(self, exc, fields, **kwargs): 136 fd, name = tempfile.mkstemp() 137 fileobj = os.fdopen(fd, "w+b") 138 try: 139 writer = csv.writer(fileobj, **kwargs) 140 with self.assertRaises(exc): 141 writer.writerow(fields) 142 fileobj.seek(0) 143 self.assertEqual(fileobj.read(), '') 144 finally: 145 fileobj.close() 146 os.unlink(name) 147 148 def test_write_arg_valid(self): 149 self._write_error_test(csv.Error, None) 150 self._write_test((), '') 151 self._write_test([None], '""') 152 self._write_error_test(csv.Error, [None], quoting = csv.QUOTE_NONE) 153 # Check that exceptions are passed up the chain 154 class BadList: 155 def __len__(self): 156 return 10; 157 def __getitem__(self, i): 158 if i > 2: 159 raise IOError 160 self._write_error_test(IOError, BadList()) 161 class BadItem: 162 def __str__(self): 163 raise IOError 164 self._write_error_test(IOError, [BadItem()]) 165 166 def test_write_bigfield(self): 167 # This exercises the buffer realloc functionality 168 bigstring = 'X' * 50000 169 self._write_test([bigstring,bigstring], '%s,%s' % \ 170 (bigstring, bigstring)) 171 172 def test_write_quoting(self): 173 self._write_test(['a',1,'p,q'], 'a,1,"p,q"') 174 self._write_error_test(csv.Error, ['a',1,'p,q'], 175 quoting = csv.QUOTE_NONE) 176 self._write_test(['a',1,'p,q'], 'a,1,"p,q"', 177 quoting = csv.QUOTE_MINIMAL) 178 self._write_test(['a',1,'p,q'], '"a",1,"p,q"', 179 quoting = csv.QUOTE_NONNUMERIC) 180 self._write_test(['a',1,'p,q'], '"a","1","p,q"', 181 quoting = csv.QUOTE_ALL) 182 self._write_test(['a\nb',1], '"a\nb","1"', 183 quoting = csv.QUOTE_ALL) 184 185 def test_write_escape(self): 186 self._write_test(['a',1,'p,q'], 'a,1,"p,q"', 187 escapechar='\\') 188 self._write_error_test(csv.Error, ['a',1,'p,"q"'], 189 escapechar=None, doublequote=False) 190 self._write_test(['a',1,'p,"q"'], 'a,1,"p,\\"q\\""', 191 escapechar='\\', doublequote = False) 192 self._write_test(['"'], '""""', 193 escapechar='\\', quoting = csv.QUOTE_MINIMAL) 194 self._write_test(['"'], '\\"', 195 escapechar='\\', quoting = csv.QUOTE_MINIMAL, 196 doublequote = False) 197 self._write_test(['"'], '\\"', 198 escapechar='\\', quoting = csv.QUOTE_NONE) 199 self._write_test(['a',1,'p,q'], 'a,1,p\\,q', 200 escapechar='\\', quoting = csv.QUOTE_NONE) 201 202 def test_writerows(self): 203 class BrokenFile: 204 def write(self, buf): 205 raise IOError 206 writer = csv.writer(BrokenFile()) 207 self.assertRaises(IOError, writer.writerows, [['a']]) 208 fd, name = tempfile.mkstemp() 209 fileobj = os.fdopen(fd, "w+b") 210 try: 211 writer = csv.writer(fileobj) 212 self.assertRaises(TypeError, writer.writerows, None) 213 writer.writerows([['a','b'],['c','d']]) 214 fileobj.seek(0) 215 self.assertEqual(fileobj.read(), "a,b\r\nc,d\r\n") 216 finally: 217 fileobj.close() 218 os.unlink(name) 219 220 def test_write_float(self): 221 # Issue 13573: loss of precision because csv.writer 222 # uses str() for floats instead of repr() 223 orig_row = [1.234567890123, 1.0/7.0, 'abc'] 224 f = StringIO() 225 c = csv.writer(f, quoting=csv.QUOTE_NONNUMERIC) 226 c.writerow(orig_row) 227 f.seek(0) 228 c = csv.reader(f, quoting=csv.QUOTE_NONNUMERIC) 229 new_row = next(c) 230 self.assertEqual(orig_row, new_row) 231 232 def _read_test(self, input, expect, **kwargs): 233 reader = csv.reader(input, **kwargs) 234 result = list(reader) 235 self.assertEqual(result, expect) 236 237 def test_read_oddinputs(self): 238 self._read_test([], []) 239 self._read_test([''], [[]]) 240 self.assertRaises(csv.Error, self._read_test, 241 ['"ab"c'], None, strict = 1) 242 # cannot handle null bytes for the moment 243 self.assertRaises(csv.Error, self._read_test, 244 ['ab\0c'], None, strict = 1) 245 self._read_test(['"ab"c'], [['abc']], doublequote = 0) 246 247 def test_read_eol(self): 248 self._read_test(['a,b'], [['a','b']]) 249 self._read_test(['a,b\n'], [['a','b']]) 250 self._read_test(['a,b\r\n'], [['a','b']]) 251 self._read_test(['a,b\r'], [['a','b']]) 252 self.assertRaises(csv.Error, self._read_test, ['a,b\rc,d'], []) 253 self.assertRaises(csv.Error, self._read_test, ['a,b\nc,d'], []) 254 self.assertRaises(csv.Error, self._read_test, ['a,b\r\nc,d'], []) 255 256 def test_read_eof(self): 257 self._read_test(['a,"'], [['a', '']]) 258 self._read_test(['"a'], [['a']]) 259 self._read_test(['^'], [['\n']], escapechar='^') 260 self.assertRaises(csv.Error, self._read_test, ['a,"'], [], strict=True) 261 self.assertRaises(csv.Error, self._read_test, ['"a'], [], strict=True) 262 self.assertRaises(csv.Error, self._read_test, 263 ['^'], [], escapechar='^', strict=True) 264 265 def test_read_escape(self): 266 self._read_test(['a,\\b,c'], [['a', 'b', 'c']], escapechar='\\') 267 self._read_test(['a,b\\,c'], [['a', 'b,c']], escapechar='\\') 268 self._read_test(['a,"b\\,c"'], [['a', 'b,c']], escapechar='\\') 269 self._read_test(['a,"b,\\c"'], [['a', 'b,c']], escapechar='\\') 270 self._read_test(['a,"b,c\\""'], [['a', 'b,c"']], escapechar='\\') 271 self._read_test(['a,"b,c"\\'], [['a', 'b,c\\']], escapechar='\\') 272 273 def test_read_quoting(self): 274 self._read_test(['1,",3,",5'], [['1', ',3,', '5']]) 275 self._read_test(['1,",3,",5'], [['1', '"', '3', '"', '5']], 276 quotechar=None, escapechar='\\') 277 self._read_test(['1,",3,",5'], [['1', '"', '3', '"', '5']], 278 quoting=csv.QUOTE_NONE, escapechar='\\') 279 # will this fail where locale uses comma for decimals? 280 self._read_test([',3,"5",7.3, 9'], [['', 3, '5', 7.3, 9]], 281 quoting=csv.QUOTE_NONNUMERIC) 282 self._read_test(['"a\nb", 7'], [['a\nb', ' 7']]) 283 self.assertRaises(ValueError, self._read_test, 284 ['abc,3'], [[]], 285 quoting=csv.QUOTE_NONNUMERIC) 286 287 def test_read_bigfield(self): 288 # This exercises the buffer realloc functionality and field size 289 # limits. 290 limit = csv.field_size_limit() 291 try: 292 size = 50000 293 bigstring = 'X' * size 294 bigline = '%s,%s' % (bigstring, bigstring) 295 self._read_test([bigline], [[bigstring, bigstring]]) 296 csv.field_size_limit(size) 297 self._read_test([bigline], [[bigstring, bigstring]]) 298 self.assertEqual(csv.field_size_limit(), size) 299 csv.field_size_limit(size-1) 300 self.assertRaises(csv.Error, self._read_test, [bigline], []) 301 self.assertRaises(TypeError, csv.field_size_limit, None) 302 self.assertRaises(TypeError, csv.field_size_limit, 1, None) 303 finally: 304 csv.field_size_limit(limit) 305 306 def test_read_linenum(self): 307 for r in (csv.reader(['line,1', 'line,2', 'line,3']), 308 csv.DictReader(['line,1', 'line,2', 'line,3'], 309 fieldnames=['a', 'b', 'c'])): 310 self.assertEqual(r.line_num, 0) 311 r.next() 312 self.assertEqual(r.line_num, 1) 313 r.next() 314 self.assertEqual(r.line_num, 2) 315 r.next() 316 self.assertEqual(r.line_num, 3) 317 self.assertRaises(StopIteration, r.next) 318 self.assertEqual(r.line_num, 3) 319 320 def test_roundtrip_quoteed_newlines(self): 321 fd, name = tempfile.mkstemp() 322 fileobj = os.fdopen(fd, "w+b") 323 try: 324 writer = csv.writer(fileobj) 325 self.assertRaises(TypeError, writer.writerows, None) 326 rows = [['a\nb','b'],['c','x\r\nd']] 327 writer.writerows(rows) 328 fileobj.seek(0) 329 for i, row in enumerate(csv.reader(fileobj)): 330 self.assertEqual(row, rows[i]) 331 finally: 332 fileobj.close() 333 os.unlink(name) 334 335class TestDialectRegistry(unittest.TestCase): 336 def test_registry_badargs(self): 337 self.assertRaises(TypeError, csv.list_dialects, None) 338 self.assertRaises(TypeError, csv.get_dialect) 339 self.assertRaises(csv.Error, csv.get_dialect, None) 340 self.assertRaises(csv.Error, csv.get_dialect, "nonesuch") 341 self.assertRaises(TypeError, csv.unregister_dialect) 342 self.assertRaises(csv.Error, csv.unregister_dialect, None) 343 self.assertRaises(csv.Error, csv.unregister_dialect, "nonesuch") 344 self.assertRaises(TypeError, csv.register_dialect, None) 345 self.assertRaises(TypeError, csv.register_dialect, None, None) 346 self.assertRaises(TypeError, csv.register_dialect, "nonesuch", 0, 0) 347 self.assertRaises(TypeError, csv.register_dialect, "nonesuch", 348 badargument=None) 349 self.assertRaises(TypeError, csv.register_dialect, "nonesuch", 350 quoting=None) 351 self.assertRaises(TypeError, csv.register_dialect, []) 352 353 def test_registry(self): 354 class myexceltsv(csv.excel): 355 delimiter = "\t" 356 name = "myexceltsv" 357 expected_dialects = csv.list_dialects() + [name] 358 expected_dialects.sort() 359 csv.register_dialect(name, myexceltsv) 360 self.addCleanup(csv.unregister_dialect, name) 361 self.assertEqual(csv.get_dialect(name).delimiter, '\t') 362 got_dialects = sorted(csv.list_dialects()) 363 self.assertEqual(expected_dialects, got_dialects) 364 365 def test_register_kwargs(self): 366 name = 'fedcba' 367 csv.register_dialect(name, delimiter=';') 368 self.addCleanup(csv.unregister_dialect, name) 369 self.assertEqual(csv.get_dialect(name).delimiter, ';') 370 self.assertEqual([['X', 'Y', 'Z']], list(csv.reader(['X;Y;Z'], name))) 371 372 def test_incomplete_dialect(self): 373 class myexceltsv(csv.Dialect): 374 delimiter = "\t" 375 self.assertRaises(csv.Error, myexceltsv) 376 377 def test_space_dialect(self): 378 class space(csv.excel): 379 delimiter = " " 380 quoting = csv.QUOTE_NONE 381 escapechar = "\\" 382 383 fd, name = tempfile.mkstemp() 384 fileobj = os.fdopen(fd, "w+b") 385 try: 386 fileobj.write("abc def\nc1ccccc1 benzene\n") 387 fileobj.seek(0) 388 rdr = csv.reader(fileobj, dialect=space()) 389 self.assertEqual(rdr.next(), ["abc", "def"]) 390 self.assertEqual(rdr.next(), ["c1ccccc1", "benzene"]) 391 finally: 392 fileobj.close() 393 os.unlink(name) 394 395 def test_dialect_apply(self): 396 class testA(csv.excel): 397 delimiter = "\t" 398 class testB(csv.excel): 399 delimiter = ":" 400 class testC(csv.excel): 401 delimiter = "|" 402 403 csv.register_dialect('testC', testC) 404 try: 405 fd, name = tempfile.mkstemp() 406 fileobj = os.fdopen(fd, "w+b") 407 try: 408 writer = csv.writer(fileobj) 409 writer.writerow([1,2,3]) 410 fileobj.seek(0) 411 self.assertEqual(fileobj.read(), "1,2,3\r\n") 412 finally: 413 fileobj.close() 414 os.unlink(name) 415 416 fd, name = tempfile.mkstemp() 417 fileobj = os.fdopen(fd, "w+b") 418 try: 419 writer = csv.writer(fileobj, testA) 420 writer.writerow([1,2,3]) 421 fileobj.seek(0) 422 self.assertEqual(fileobj.read(), "1\t2\t3\r\n") 423 finally: 424 fileobj.close() 425 os.unlink(name) 426 427 fd, name = tempfile.mkstemp() 428 fileobj = os.fdopen(fd, "w+b") 429 try: 430 writer = csv.writer(fileobj, dialect=testB()) 431 writer.writerow([1,2,3]) 432 fileobj.seek(0) 433 self.assertEqual(fileobj.read(), "1:2:3\r\n") 434 finally: 435 fileobj.close() 436 os.unlink(name) 437 438 fd, name = tempfile.mkstemp() 439 fileobj = os.fdopen(fd, "w+b") 440 try: 441 writer = csv.writer(fileobj, dialect='testC') 442 writer.writerow([1,2,3]) 443 fileobj.seek(0) 444 self.assertEqual(fileobj.read(), "1|2|3\r\n") 445 finally: 446 fileobj.close() 447 os.unlink(name) 448 449 fd, name = tempfile.mkstemp() 450 fileobj = os.fdopen(fd, "w+b") 451 try: 452 writer = csv.writer(fileobj, dialect=testA, delimiter=';') 453 writer.writerow([1,2,3]) 454 fileobj.seek(0) 455 self.assertEqual(fileobj.read(), "1;2;3\r\n") 456 finally: 457 fileobj.close() 458 os.unlink(name) 459 460 finally: 461 csv.unregister_dialect('testC') 462 463 def test_bad_dialect(self): 464 # Unknown parameter 465 self.assertRaises(TypeError, csv.reader, [], bad_attr = 0) 466 # Bad values 467 self.assertRaises(TypeError, csv.reader, [], delimiter = None) 468 self.assertRaises(TypeError, csv.reader, [], quoting = -1) 469 self.assertRaises(TypeError, csv.reader, [], quoting = 100) 470 471 # See issue #22995 472 ## def test_copy(self): 473 ## for name in csv.list_dialects(): 474 ## dialect = csv.get_dialect(name) 475 ## self.assertRaises(TypeError, copy.copy, dialect) 476 477 ## def test_pickle(self): 478 ## for name in csv.list_dialects(): 479 ## dialect = csv.get_dialect(name) 480 ## for proto in range(pickle.HIGHEST_PROTOCOL + 1): 481 ## self.assertRaises(TypeError, pickle.dumps, dialect, proto) 482 483class TestCsvBase(unittest.TestCase): 484 def readerAssertEqual(self, input, expected_result): 485 fd, name = tempfile.mkstemp() 486 fileobj = os.fdopen(fd, "w+b") 487 try: 488 fileobj.write(input) 489 fileobj.seek(0) 490 reader = csv.reader(fileobj, dialect = self.dialect) 491 fields = list(reader) 492 self.assertEqual(fields, expected_result) 493 finally: 494 fileobj.close() 495 os.unlink(name) 496 497 def writerAssertEqual(self, input, expected_result): 498 fd, name = tempfile.mkstemp() 499 fileobj = os.fdopen(fd, "w+b") 500 try: 501 writer = csv.writer(fileobj, dialect = self.dialect) 502 writer.writerows(input) 503 fileobj.seek(0) 504 self.assertEqual(fileobj.read(), expected_result) 505 finally: 506 fileobj.close() 507 os.unlink(name) 508 509class TestDialectExcel(TestCsvBase): 510 dialect = 'excel' 511 512 def test_single(self): 513 self.readerAssertEqual('abc', [['abc']]) 514 515 def test_simple(self): 516 self.readerAssertEqual('1,2,3,4,5', [['1','2','3','4','5']]) 517 518 def test_blankline(self): 519 self.readerAssertEqual('', []) 520 521 def test_empty_fields(self): 522 self.readerAssertEqual(',', [['', '']]) 523 524 def test_singlequoted(self): 525 self.readerAssertEqual('""', [['']]) 526 527 def test_singlequoted_left_empty(self): 528 self.readerAssertEqual('"",', [['','']]) 529 530 def test_singlequoted_right_empty(self): 531 self.readerAssertEqual(',""', [['','']]) 532 533 def test_single_quoted_quote(self): 534 self.readerAssertEqual('""""', [['"']]) 535 536 def test_quoted_quotes(self): 537 self.readerAssertEqual('""""""', [['""']]) 538 539 def test_inline_quote(self): 540 self.readerAssertEqual('a""b', [['a""b']]) 541 542 def test_inline_quotes(self): 543 self.readerAssertEqual('a"b"c', [['a"b"c']]) 544 545 def test_quotes_and_more(self): 546 # Excel would never write a field containing '"a"b', but when 547 # reading one, it will return 'ab'. 548 self.readerAssertEqual('"a"b', [['ab']]) 549 550 def test_lone_quote(self): 551 self.readerAssertEqual('a"b', [['a"b']]) 552 553 def test_quote_and_quote(self): 554 # Excel would never write a field containing '"a" "b"', but when 555 # reading one, it will return 'a "b"'. 556 self.readerAssertEqual('"a" "b"', [['a "b"']]) 557 558 def test_space_and_quote(self): 559 self.readerAssertEqual(' "a"', [[' "a"']]) 560 561 def test_quoted(self): 562 self.readerAssertEqual('1,2,3,"I think, therefore I am",5,6', 563 [['1', '2', '3', 564 'I think, therefore I am', 565 '5', '6']]) 566 567 def test_quoted_quote(self): 568 self.readerAssertEqual('1,2,3,"""I see,"" said the blind man","as he picked up his hammer and saw"', 569 [['1', '2', '3', 570 '"I see," said the blind man', 571 'as he picked up his hammer and saw']]) 572 573 def test_quoted_nl(self): 574 input = '''\ 5751,2,3,"""I see,"" 576said the blind man","as he picked up his 577hammer and saw" 5789,8,7,6''' 579 self.readerAssertEqual(input, 580 [['1', '2', '3', 581 '"I see,"\nsaid the blind man', 582 'as he picked up his\nhammer and saw'], 583 ['9','8','7','6']]) 584 585 def test_dubious_quote(self): 586 self.readerAssertEqual('12,12,1",', [['12', '12', '1"', '']]) 587 588 def test_null(self): 589 self.writerAssertEqual([], '') 590 591 def test_single_writer(self): 592 self.writerAssertEqual([['abc']], 'abc\r\n') 593 594 def test_simple_writer(self): 595 self.writerAssertEqual([[1, 2, 'abc', 3, 4]], '1,2,abc,3,4\r\n') 596 597 def test_quotes(self): 598 self.writerAssertEqual([[1, 2, 'a"bc"', 3, 4]], '1,2,"a""bc""",3,4\r\n') 599 600 def test_quote_fieldsep(self): 601 self.writerAssertEqual([['abc,def']], '"abc,def"\r\n') 602 603 def test_newlines(self): 604 self.writerAssertEqual([[1, 2, 'a\nbc', 3, 4]], '1,2,"a\nbc",3,4\r\n') 605 606class EscapedExcel(csv.excel): 607 quoting = csv.QUOTE_NONE 608 escapechar = '\\' 609 610class TestEscapedExcel(TestCsvBase): 611 dialect = EscapedExcel() 612 613 def test_escape_fieldsep(self): 614 self.writerAssertEqual([['abc,def']], 'abc\\,def\r\n') 615 616 def test_read_escape_fieldsep(self): 617 self.readerAssertEqual('abc\\,def\r\n', [['abc,def']]) 618 619class QuotedEscapedExcel(csv.excel): 620 quoting = csv.QUOTE_NONNUMERIC 621 escapechar = '\\' 622 623class TestQuotedEscapedExcel(TestCsvBase): 624 dialect = QuotedEscapedExcel() 625 626 def test_write_escape_fieldsep(self): 627 self.writerAssertEqual([['abc,def']], '"abc,def"\r\n') 628 629 def test_read_escape_fieldsep(self): 630 self.readerAssertEqual('"abc\\,def"\r\n', [['abc,def']]) 631 632class TestDictFields(unittest.TestCase): 633 ### "long" means the row is longer than the number of fieldnames 634 ### "short" means there are fewer elements in the row than fieldnames 635 def test_write_simple_dict(self): 636 fd, name = tempfile.mkstemp() 637 fileobj = io.open(fd, 'w+b') 638 try: 639 writer = csv.DictWriter(fileobj, fieldnames = ["f1", "f2", "f3"]) 640 writer.writeheader() 641 fileobj.seek(0) 642 self.assertEqual(fileobj.readline(), "f1,f2,f3\r\n") 643 writer.writerow({"f1": 10, "f3": "abc"}) 644 fileobj.seek(0) 645 fileobj.readline() # header 646 self.assertEqual(fileobj.read(), "10,,abc\r\n") 647 finally: 648 fileobj.close() 649 os.unlink(name) 650 651 def test_write_no_fields(self): 652 fileobj = StringIO() 653 self.assertRaises(TypeError, csv.DictWriter, fileobj) 654 655 def test_write_fields_not_in_fieldnames(self): 656 fd, name = tempfile.mkstemp() 657 fileobj = os.fdopen(fd, "w+b") 658 try: 659 writer = csv.DictWriter(fileobj, fieldnames = ["f1", "f2", "f3"]) 660 # Of special note is the non-string key (issue 19449) 661 with self.assertRaises(ValueError) as cx: 662 writer.writerow({"f4": 10, "f2": "spam", 1: "abc"}) 663 exception = str(cx.exception) 664 self.assertIn("fieldnames", exception) 665 self.assertIn("'f4'", exception) 666 self.assertNotIn("'f2'", exception) 667 self.assertIn("1", exception) 668 finally: 669 fileobj.close() 670 os.unlink(name) 671 672 def test_read_dict_fields(self): 673 fd, name = tempfile.mkstemp() 674 fileobj = os.fdopen(fd, "w+b") 675 try: 676 fileobj.write("1,2,abc\r\n") 677 fileobj.seek(0) 678 reader = csv.DictReader(fileobj, 679 fieldnames=["f1", "f2", "f3"]) 680 self.assertEqual(reader.next(), {"f1": '1', "f2": '2', "f3": 'abc'}) 681 finally: 682 fileobj.close() 683 os.unlink(name) 684 685 def test_read_dict_no_fieldnames(self): 686 fd, name = tempfile.mkstemp() 687 fileobj = os.fdopen(fd, "w+b") 688 try: 689 fileobj.write("f1,f2,f3\r\n1,2,abc\r\n") 690 fileobj.seek(0) 691 reader = csv.DictReader(fileobj) 692 self.assertEqual(reader.fieldnames, ["f1", "f2", "f3"]) 693 self.assertEqual(reader.next(), {"f1": '1', "f2": '2', "f3": 'abc'}) 694 finally: 695 fileobj.close() 696 os.unlink(name) 697 698 # Two test cases to make sure existing ways of implicitly setting 699 # fieldnames continue to work. Both arise from discussion in issue3436. 700 def test_read_dict_fieldnames_from_file(self): 701 fd, name = tempfile.mkstemp() 702 f = os.fdopen(fd, "w+b") 703 try: 704 f.write("f1,f2,f3\r\n1,2,abc\r\n") 705 f.seek(0) 706 reader = csv.DictReader(f, fieldnames=csv.reader(f).next()) 707 self.assertEqual(reader.fieldnames, ["f1", "f2", "f3"]) 708 self.assertEqual(reader.next(), {"f1": '1', "f2": '2', "f3": 'abc'}) 709 finally: 710 f.close() 711 os.unlink(name) 712 713 def test_read_dict_fieldnames_chain(self): 714 import itertools 715 fd, name = tempfile.mkstemp() 716 f = os.fdopen(fd, "w+b") 717 try: 718 f.write("f1,f2,f3\r\n1,2,abc\r\n") 719 f.seek(0) 720 reader = csv.DictReader(f) 721 first = next(reader) 722 for row in itertools.chain([first], reader): 723 self.assertEqual(reader.fieldnames, ["f1", "f2", "f3"]) 724 self.assertEqual(row, {"f1": '1', "f2": '2', "f3": 'abc'}) 725 finally: 726 f.close() 727 os.unlink(name) 728 729 def test_read_long(self): 730 fd, name = tempfile.mkstemp() 731 fileobj = os.fdopen(fd, "w+b") 732 try: 733 fileobj.write("1,2,abc,4,5,6\r\n") 734 fileobj.seek(0) 735 reader = csv.DictReader(fileobj, 736 fieldnames=["f1", "f2"]) 737 self.assertEqual(reader.next(), {"f1": '1', "f2": '2', 738 None: ["abc", "4", "5", "6"]}) 739 finally: 740 fileobj.close() 741 os.unlink(name) 742 743 def test_read_long_with_rest(self): 744 fd, name = tempfile.mkstemp() 745 fileobj = os.fdopen(fd, "w+b") 746 try: 747 fileobj.write("1,2,abc,4,5,6\r\n") 748 fileobj.seek(0) 749 reader = csv.DictReader(fileobj, 750 fieldnames=["f1", "f2"], restkey="_rest") 751 self.assertEqual(reader.next(), {"f1": '1', "f2": '2', 752 "_rest": ["abc", "4", "5", "6"]}) 753 finally: 754 fileobj.close() 755 os.unlink(name) 756 757 def test_read_long_with_rest_no_fieldnames(self): 758 fd, name = tempfile.mkstemp() 759 fileobj = os.fdopen(fd, "w+b") 760 try: 761 fileobj.write("f1,f2\r\n1,2,abc,4,5,6\r\n") 762 fileobj.seek(0) 763 reader = csv.DictReader(fileobj, restkey="_rest") 764 self.assertEqual(reader.fieldnames, ["f1", "f2"]) 765 self.assertEqual(reader.next(), {"f1": '1', "f2": '2', 766 "_rest": ["abc", "4", "5", "6"]}) 767 finally: 768 fileobj.close() 769 os.unlink(name) 770 771 def test_read_short(self): 772 fd, name = tempfile.mkstemp() 773 fileobj = os.fdopen(fd, "w+b") 774 try: 775 fileobj.write("1,2,abc,4,5,6\r\n1,2,abc\r\n") 776 fileobj.seek(0) 777 reader = csv.DictReader(fileobj, 778 fieldnames="1 2 3 4 5 6".split(), 779 restval="DEFAULT") 780 self.assertEqual(reader.next(), {"1": '1', "2": '2', "3": 'abc', 781 "4": '4', "5": '5', "6": '6'}) 782 self.assertEqual(reader.next(), {"1": '1', "2": '2', "3": 'abc', 783 "4": 'DEFAULT', "5": 'DEFAULT', 784 "6": 'DEFAULT'}) 785 finally: 786 fileobj.close() 787 os.unlink(name) 788 789 def test_read_multi(self): 790 sample = [ 791 '2147483648,43.0e12,17,abc,def\r\n', 792 '147483648,43.0e2,17,abc,def\r\n', 793 '47483648,43.0,170,abc,def\r\n' 794 ] 795 796 reader = csv.DictReader(sample, 797 fieldnames="i1 float i2 s1 s2".split()) 798 self.assertEqual(reader.next(), {"i1": '2147483648', 799 "float": '43.0e12', 800 "i2": '17', 801 "s1": 'abc', 802 "s2": 'def'}) 803 804 def test_read_with_blanks(self): 805 reader = csv.DictReader(["1,2,abc,4,5,6\r\n","\r\n", 806 "1,2,abc,4,5,6\r\n"], 807 fieldnames="1 2 3 4 5 6".split()) 808 self.assertEqual(reader.next(), {"1": '1', "2": '2', "3": 'abc', 809 "4": '4', "5": '5', "6": '6'}) 810 self.assertEqual(reader.next(), {"1": '1', "2": '2', "3": 'abc', 811 "4": '4', "5": '5', "6": '6'}) 812 813 def test_read_semi_sep(self): 814 reader = csv.DictReader(["1;2;abc;4;5;6\r\n"], 815 fieldnames="1 2 3 4 5 6".split(), 816 delimiter=';') 817 self.assertEqual(reader.next(), {"1": '1', "2": '2', "3": 'abc', 818 "4": '4', "5": '5', "6": '6'}) 819 820class TestArrayWrites(unittest.TestCase): 821 def test_int_write(self): 822 import array 823 contents = [(20-i) for i in range(20)] 824 a = array.array('i', contents) 825 826 fd, name = tempfile.mkstemp() 827 fileobj = os.fdopen(fd, "w+b") 828 try: 829 writer = csv.writer(fileobj, dialect="excel") 830 writer.writerow(a) 831 expected = ",".join([str(i) for i in a])+"\r\n" 832 fileobj.seek(0) 833 self.assertEqual(fileobj.read(), expected) 834 finally: 835 fileobj.close() 836 os.unlink(name) 837 838 def test_double_write(self): 839 import array 840 contents = [(20-i)*0.1 for i in range(20)] 841 a = array.array('d', contents) 842 fd, name = tempfile.mkstemp() 843 fileobj = os.fdopen(fd, "w+b") 844 try: 845 writer = csv.writer(fileobj, dialect="excel") 846 writer.writerow(a) 847 expected = ",".join([repr(i) for i in a])+"\r\n" 848 fileobj.seek(0) 849 self.assertEqual(fileobj.read(), expected) 850 finally: 851 fileobj.close() 852 os.unlink(name) 853 854 def test_float_write(self): 855 import array 856 contents = [(20-i)*0.1 for i in range(20)] 857 a = array.array('f', contents) 858 fd, name = tempfile.mkstemp() 859 fileobj = os.fdopen(fd, "w+b") 860 try: 861 writer = csv.writer(fileobj, dialect="excel") 862 writer.writerow(a) 863 expected = ",".join([repr(i) for i in a])+"\r\n" 864 fileobj.seek(0) 865 self.assertEqual(fileobj.read(), expected) 866 finally: 867 fileobj.close() 868 os.unlink(name) 869 870 def test_char_write(self): 871 import array, string 872 a = array.array('c', string.letters) 873 fd, name = tempfile.mkstemp() 874 fileobj = os.fdopen(fd, "w+b") 875 try: 876 writer = csv.writer(fileobj, dialect="excel") 877 writer.writerow(a) 878 expected = ",".join(a)+"\r\n" 879 fileobj.seek(0) 880 self.assertEqual(fileobj.read(), expected) 881 finally: 882 fileobj.close() 883 os.unlink(name) 884 885class TestDialectValidity(unittest.TestCase): 886 def test_quoting(self): 887 class mydialect(csv.Dialect): 888 delimiter = ";" 889 escapechar = '\\' 890 doublequote = False 891 skipinitialspace = True 892 lineterminator = '\r\n' 893 quoting = csv.QUOTE_NONE 894 d = mydialect() 895 self.assertEqual(d.quoting, csv.QUOTE_NONE) 896 897 mydialect.quoting = None 898 self.assertRaises(csv.Error, mydialect) 899 900 mydialect.doublequote = True 901 mydialect.quoting = csv.QUOTE_ALL 902 mydialect.quotechar = '"' 903 d = mydialect() 904 self.assertEqual(d.quoting, csv.QUOTE_ALL) 905 self.assertEqual(d.quotechar, '"') 906 self.assertTrue(d.doublequote) 907 908 mydialect.quotechar = "''" 909 with self.assertRaises(csv.Error) as cm: 910 mydialect() 911 self.assertEqual(str(cm.exception), 912 '"quotechar" must be an 1-character string') 913 914 mydialect.quotechar = 4 915 with self.assertRaises(csv.Error) as cm: 916 mydialect() 917 self.assertEqual(str(cm.exception), 918 '"quotechar" must be string, not int') 919 920 def test_delimiter(self): 921 class mydialect(csv.Dialect): 922 delimiter = ";" 923 escapechar = '\\' 924 doublequote = False 925 skipinitialspace = True 926 lineterminator = '\r\n' 927 quoting = csv.QUOTE_NONE 928 d = mydialect() 929 self.assertEqual(d.delimiter, ";") 930 931 mydialect.delimiter = ":::" 932 with self.assertRaises(csv.Error) as cm: 933 mydialect() 934 self.assertEqual(str(cm.exception), 935 '"delimiter" must be an 1-character string') 936 937 mydialect.delimiter = "" 938 with self.assertRaises(csv.Error) as cm: 939 mydialect() 940 self.assertEqual(str(cm.exception), 941 '"delimiter" must be an 1-character string') 942 943 mydialect.delimiter = u"," 944 with self.assertRaises(csv.Error) as cm: 945 mydialect() 946 self.assertEqual(str(cm.exception), 947 '"delimiter" must be string, not unicode') 948 949 mydialect.delimiter = 4 950 with self.assertRaises(csv.Error) as cm: 951 mydialect() 952 self.assertEqual(str(cm.exception), 953 '"delimiter" must be string, not int') 954 955 def test_lineterminator(self): 956 class mydialect(csv.Dialect): 957 delimiter = ";" 958 escapechar = '\\' 959 doublequote = False 960 skipinitialspace = True 961 lineterminator = '\r\n' 962 quoting = csv.QUOTE_NONE 963 d = mydialect() 964 self.assertEqual(d.lineterminator, '\r\n') 965 966 mydialect.lineterminator = ":::" 967 d = mydialect() 968 self.assertEqual(d.lineterminator, ":::") 969 970 mydialect.lineterminator = 4 971 with self.assertRaises(csv.Error) as cm: 972 mydialect() 973 self.assertEqual(str(cm.exception), 974 '"lineterminator" must be a string') 975 976 977class TestSniffer(unittest.TestCase): 978 sample1 = """\ 979Harry's, Arlington Heights, IL, 2/1/03, Kimi Hayes 980Shark City, Glendale Heights, IL, 12/28/02, Prezence 981Tommy's Place, Blue Island, IL, 12/28/02, Blue Sunday/White Crow 982Stonecutters Seafood and Chop House, Lemont, IL, 12/19/02, Week Back 983""" 984 sample2 = """\ 985'Harry''s':'Arlington Heights':'IL':'2/1/03':'Kimi Hayes' 986'Shark City':'Glendale Heights':'IL':'12/28/02':'Prezence' 987'Tommy''s Place':'Blue Island':'IL':'12/28/02':'Blue Sunday/White Crow' 988'Stonecutters ''Seafood'' and Chop House':'Lemont':'IL':'12/19/02':'Week Back' 989""" 990 header1 = '''\ 991"venue","city","state","date","performers" 992''' 993 sample3 = '''\ 99405/05/03?05/05/03?05/05/03?05/05/03?05/05/03?05/05/03 99505/05/03?05/05/03?05/05/03?05/05/03?05/05/03?05/05/03 99605/05/03?05/05/03?05/05/03?05/05/03?05/05/03?05/05/03 997''' 998 999 sample4 = '''\ 10002147483648;43.0e12;17;abc;def 1001147483648;43.0e2;17;abc;def 100247483648;43.0;170;abc;def 1003''' 1004 1005 sample5 = "aaa\tbbb\r\nAAA\t\r\nBBB\t\r\n" 1006 sample6 = "a|b|c\r\nd|e|f\r\n" 1007 sample7 = "'a'|'b'|'c'\r\n'd'|e|f\r\n" 1008 1009# Issue 18155: Use a delimiter that is a special char to regex: 1010 1011 header2 = '''\ 1012"venue"+"city"+"state"+"date"+"performers" 1013''' 1014 sample8 = """\ 1015Harry's+ Arlington Heights+ IL+ 2/1/03+ Kimi Hayes 1016Shark City+ Glendale Heights+ IL+ 12/28/02+ Prezence 1017Tommy's Place+ Blue Island+ IL+ 12/28/02+ Blue Sunday/White Crow 1018Stonecutters Seafood and Chop House+ Lemont+ IL+ 12/19/02+ Week Back 1019""" 1020 sample9 = """\ 1021'Harry''s'+ Arlington Heights'+ 'IL'+ '2/1/03'+ 'Kimi Hayes' 1022'Shark City'+ Glendale Heights'+' IL'+ '12/28/02'+ 'Prezence' 1023'Tommy''s Place'+ Blue Island'+ 'IL'+ '12/28/02'+ 'Blue Sunday/White Crow' 1024'Stonecutters ''Seafood'' and Chop House'+ 'Lemont'+ 'IL'+ '12/19/02'+ 'Week Back' 1025""" 1026 1027 def test_has_header(self): 1028 sniffer = csv.Sniffer() 1029 self.assertEqual(sniffer.has_header(self.sample1), False) 1030 self.assertEqual(sniffer.has_header(self.header1 + self.sample1), 1031 True) 1032 1033 def test_has_header_regex_special_delimiter(self): 1034 sniffer = csv.Sniffer() 1035 self.assertEqual(sniffer.has_header(self.sample8), False) 1036 self.assertEqual(sniffer.has_header(self.header2 + self.sample8), 1037 True) 1038 1039 def test_guess_quote_and_delimiter(self): 1040 sniffer = csv.Sniffer() 1041 for header in (";'123;4';", "'123;4';", ";'123;4'", "'123;4'"): 1042 dialect = sniffer.sniff(header, ",;") 1043 self.assertEqual(dialect.delimiter, ';') 1044 self.assertEqual(dialect.quotechar, "'") 1045 self.assertIs(dialect.doublequote, False) 1046 self.assertIs(dialect.skipinitialspace, False) 1047 1048 def test_sniff(self): 1049 sniffer = csv.Sniffer() 1050 dialect = sniffer.sniff(self.sample1) 1051 self.assertEqual(dialect.delimiter, ",") 1052 self.assertEqual(dialect.quotechar, '"') 1053 self.assertEqual(dialect.skipinitialspace, True) 1054 1055 dialect = sniffer.sniff(self.sample2) 1056 self.assertEqual(dialect.delimiter, ":") 1057 self.assertEqual(dialect.quotechar, "'") 1058 self.assertEqual(dialect.skipinitialspace, False) 1059 1060 def test_delimiters(self): 1061 sniffer = csv.Sniffer() 1062 dialect = sniffer.sniff(self.sample3) 1063 # given that all three lines in sample3 are equal, 1064 # I think that any character could have been 'guessed' as the 1065 # delimiter, depending on dictionary order 1066 self.assertIn(dialect.delimiter, self.sample3) 1067 dialect = sniffer.sniff(self.sample3, delimiters="?,") 1068 self.assertEqual(dialect.delimiter, "?") 1069 dialect = sniffer.sniff(self.sample3, delimiters="/,") 1070 self.assertEqual(dialect.delimiter, "/") 1071 dialect = sniffer.sniff(self.sample4) 1072 self.assertEqual(dialect.delimiter, ";") 1073 dialect = sniffer.sniff(self.sample5) 1074 self.assertEqual(dialect.delimiter, "\t") 1075 dialect = sniffer.sniff(self.sample6) 1076 self.assertEqual(dialect.delimiter, "|") 1077 dialect = sniffer.sniff(self.sample7) 1078 self.assertEqual(dialect.delimiter, "|") 1079 self.assertEqual(dialect.quotechar, "'") 1080 dialect = sniffer.sniff(self.sample8) 1081 self.assertEqual(dialect.delimiter, '+') 1082 dialect = sniffer.sniff(self.sample9) 1083 self.assertEqual(dialect.delimiter, '+') 1084 self.assertEqual(dialect.quotechar, "'") 1085 1086 def test_doublequote(self): 1087 sniffer = csv.Sniffer() 1088 dialect = sniffer.sniff(self.header1) 1089 self.assertFalse(dialect.doublequote) 1090 dialect = sniffer.sniff(self.header2) 1091 self.assertFalse(dialect.doublequote) 1092 dialect = sniffer.sniff(self.sample2) 1093 self.assertTrue(dialect.doublequote) 1094 dialect = sniffer.sniff(self.sample8) 1095 self.assertFalse(dialect.doublequote) 1096 dialect = sniffer.sniff(self.sample9) 1097 self.assertTrue(dialect.doublequote) 1098 1099class NUL: 1100 def write(s, *args): 1101 pass 1102 writelines = write 1103 1104@unittest.skipUnless(hasattr(sys, "gettotalrefcount"), 1105 'requires sys.gettotalrefcount()') 1106class TestLeaks(unittest.TestCase): 1107 def test_create_read(self): 1108 delta = 0 1109 lastrc = sys.gettotalrefcount() 1110 for i in xrange(20): 1111 gc.collect() 1112 self.assertEqual(gc.garbage, []) 1113 rc = sys.gettotalrefcount() 1114 csv.reader(["a,b,c\r\n"]) 1115 csv.reader(["a,b,c\r\n"]) 1116 csv.reader(["a,b,c\r\n"]) 1117 delta = rc-lastrc 1118 lastrc = rc 1119 # if csv.reader() leaks, last delta should be 3 or more 1120 self.assertEqual(delta < 3, True) 1121 1122 def test_create_write(self): 1123 delta = 0 1124 lastrc = sys.gettotalrefcount() 1125 s = NUL() 1126 for i in xrange(20): 1127 gc.collect() 1128 self.assertEqual(gc.garbage, []) 1129 rc = sys.gettotalrefcount() 1130 csv.writer(s) 1131 csv.writer(s) 1132 csv.writer(s) 1133 delta = rc-lastrc 1134 lastrc = rc 1135 # if csv.writer() leaks, last delta should be 3 or more 1136 self.assertEqual(delta < 3, True) 1137 1138 def test_read(self): 1139 delta = 0 1140 rows = ["a,b,c\r\n"]*5 1141 lastrc = sys.gettotalrefcount() 1142 for i in xrange(20): 1143 gc.collect() 1144 self.assertEqual(gc.garbage, []) 1145 rc = sys.gettotalrefcount() 1146 rdr = csv.reader(rows) 1147 for row in rdr: 1148 pass 1149 delta = rc-lastrc 1150 lastrc = rc 1151 # if reader leaks during read, delta should be 5 or more 1152 self.assertEqual(delta < 5, True) 1153 1154 def test_write(self): 1155 delta = 0 1156 rows = [[1,2,3]]*5 1157 s = NUL() 1158 lastrc = sys.gettotalrefcount() 1159 for i in xrange(20): 1160 gc.collect() 1161 self.assertEqual(gc.garbage, []) 1162 rc = sys.gettotalrefcount() 1163 writer = csv.writer(s) 1164 for row in rows: 1165 writer.writerow(row) 1166 delta = rc-lastrc 1167 lastrc = rc 1168 # if writer leaks during write, last delta should be 5 or more 1169 self.assertEqual(delta < 5, True) 1170 1171# commented out for now - csv module doesn't yet support Unicode 1172## class TestUnicode(unittest.TestCase): 1173## def test_unicode_read(self): 1174## import codecs 1175## f = codecs.EncodedFile(StringIO("Martin von L�wis," 1176## "Marc Andr� Lemburg," 1177## "Guido van Rossum," 1178## "Fran�ois Pinard\r\n"), 1179## data_encoding='iso-8859-1') 1180## reader = csv.reader(f) 1181## self.assertEqual(list(reader), [[u"Martin von L�wis", 1182## u"Marc Andr� Lemburg", 1183## u"Guido van Rossum", 1184## u"Fran�ois Pinardn"]]) 1185 1186def test_main(): 1187 mod = sys.modules[__name__] 1188 test_support.run_unittest( 1189 *[getattr(mod, name) for name in dir(mod) if name.startswith('Test')] 1190 ) 1191 1192if __name__ == '__main__': 1193 test_main() 1194