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