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