test_tarfile.py revision bfdfdda1067ae32f72040a0b6aefa5807e41da51
1import sys
2import os
3import io
4import shutil
5import tempfile
6import io
7from hashlib import md5
8import errno
9
10import unittest
11import tarfile
12
13from test import support
14
15# Check for our compression modules.
16try:
17    import gzip
18    gzip.GzipFile
19except (ImportError, AttributeError):
20    gzip = None
21try:
22    import bz2
23except ImportError:
24    bz2 = None
25
26def md5sum(data):
27    return md5(data).hexdigest()
28
29def path(path):
30    return support.findfile(path)
31
32TEMPDIR = os.path.join(tempfile.gettempdir(), "test_tarfile_tmp")
33tarname = path("testtar.tar")
34gzipname = os.path.join(TEMPDIR, "testtar.tar.gz")
35bz2name = os.path.join(TEMPDIR, "testtar.tar.bz2")
36tmpname = os.path.join(TEMPDIR, "tmp.tar")
37
38md5_regtype = "65f477c818ad9e15f7feab0c6d37742f"
39md5_sparse = "a54fbc4ca4f4399a90e1b27164012fc6"
40
41
42class ReadTest(unittest.TestCase):
43
44    tarname = tarname
45    mode = "r:"
46
47    def setUp(self):
48        self.tar = tarfile.open(self.tarname, mode=self.mode, encoding="iso8859-1")
49
50    def tearDown(self):
51        self.tar.close()
52
53
54class UstarReadTest(ReadTest):
55
56    def test_fileobj_regular_file(self):
57        tarinfo = self.tar.getmember("ustar/regtype")
58        fobj = self.tar.extractfile(tarinfo)
59        data = fobj.read()
60        self.assertTrue((len(data), md5sum(data)) == (tarinfo.size, md5_regtype),
61                "regular file extraction failed")
62
63    def test_fileobj_readlines(self):
64        self.tar.extract("ustar/regtype", TEMPDIR)
65        tarinfo = self.tar.getmember("ustar/regtype")
66        fobj1 = open(os.path.join(TEMPDIR, "ustar/regtype"), "r")
67        fobj2 = io.TextIOWrapper(self.tar.extractfile(tarinfo))
68
69        lines1 = fobj1.readlines()
70        lines2 = fobj2.readlines()
71        self.assertTrue(lines1 == lines2,
72                "fileobj.readlines() failed")
73        self.assertTrue(len(lines2) == 114,
74                "fileobj.readlines() failed")
75        self.assertTrue(lines2[83] == \
76                "I will gladly admit that Python is not the fastest running scripting language.\n",
77                "fileobj.readlines() failed")
78
79    def test_fileobj_iter(self):
80        self.tar.extract("ustar/regtype", TEMPDIR)
81        tarinfo = self.tar.getmember("ustar/regtype")
82        fobj1 = open(os.path.join(TEMPDIR, "ustar/regtype"), "rU")
83        fobj2 = self.tar.extractfile(tarinfo)
84        lines1 = fobj1.readlines()
85        lines2 = list(io.TextIOWrapper(fobj2))
86        self.assertTrue(lines1 == lines2,
87                     "fileobj.__iter__() failed")
88
89    def test_fileobj_seek(self):
90        self.tar.extract("ustar/regtype", TEMPDIR)
91        fobj = open(os.path.join(TEMPDIR, "ustar/regtype"), "rb")
92        data = fobj.read()
93        fobj.close()
94
95        tarinfo = self.tar.getmember("ustar/regtype")
96        fobj = self.tar.extractfile(tarinfo)
97
98        text = fobj.read()
99        fobj.seek(0)
100        self.assertEqual(0, fobj.tell(),
101                     "seek() to file's start failed")
102        fobj.seek(2048, 0)
103        self.assertEqual(2048, fobj.tell(),
104                     "seek() to absolute position failed")
105        fobj.seek(-1024, 1)
106        self.assertEqual(1024, fobj.tell(),
107                     "seek() to negative relative position failed")
108        fobj.seek(1024, 1)
109        self.assertEqual(2048, fobj.tell(),
110                     "seek() to positive relative position failed")
111        s = fobj.read(10)
112        self.assertTrue(s == data[2048:2058],
113                     "read() after seek failed")
114        fobj.seek(0, 2)
115        self.assertEqual(tarinfo.size, fobj.tell(),
116                     "seek() to file's end failed")
117        self.assertTrue(fobj.read() == b"",
118                     "read() at file's end did not return empty string")
119        fobj.seek(-tarinfo.size, 2)
120        self.assertEqual(0, fobj.tell(),
121                     "relative seek() to file's end failed")
122        fobj.seek(512)
123        s1 = fobj.readlines()
124        fobj.seek(512)
125        s2 = fobj.readlines()
126        self.assertTrue(s1 == s2,
127                     "readlines() after seek failed")
128        fobj.seek(0)
129        self.assertEqual(len(fobj.readline()), fobj.tell(),
130                     "tell() after readline() failed")
131        fobj.seek(512)
132        self.assertTrue(len(fobj.readline()) + 512 == fobj.tell(),
133                     "tell() after seek() and readline() failed")
134        fobj.seek(0)
135        line = fobj.readline()
136        self.assertEqual(fobj.read(), data[len(line):],
137                     "read() after readline() failed")
138        fobj.close()
139
140
141class MiscReadTest(ReadTest):
142
143    def test_no_name_argument(self):
144        fobj = open(self.tarname, "rb")
145        tar = tarfile.open(fileobj=fobj, mode=self.mode)
146        self.assertEqual(tar.name, os.path.abspath(fobj.name))
147
148    def test_no_name_attribute(self):
149        data = open(self.tarname, "rb").read()
150        fobj = io.BytesIO(data)
151        self.assertRaises(AttributeError, getattr, fobj, "name")
152        tar = tarfile.open(fileobj=fobj, mode=self.mode)
153        self.assertEqual(tar.name, None)
154
155    def test_empty_name_attribute(self):
156        data = open(self.tarname, "rb").read()
157        fobj = io.BytesIO(data)
158        fobj.name = ""
159        tar = tarfile.open(fileobj=fobj, mode=self.mode)
160        self.assertEqual(tar.name, None)
161
162    def test_fileobj_with_offset(self):
163        # Skip the first member and store values from the second member
164        # of the testtar.
165        tar = tarfile.open(self.tarname, mode=self.mode)
166        tar.next()
167        t = tar.next()
168        name = t.name
169        offset = t.offset
170        data = tar.extractfile(t).read()
171        tar.close()
172
173        # Open the testtar and seek to the offset of the second member.
174        if self.mode.endswith(":gz"):
175            _open = gzip.GzipFile
176        elif self.mode.endswith(":bz2"):
177            _open = bz2.BZ2File
178        else:
179            _open = open
180        fobj = _open(self.tarname, "rb")
181        fobj.seek(offset)
182
183        # Test if the tarfile starts with the second member.
184        tar = tar.open(self.tarname, mode="r:", fileobj=fobj)
185        t = tar.next()
186        self.assertEqual(t.name, name)
187        # Read to the end of fileobj and test if seeking back to the
188        # beginning works.
189        tar.getmembers()
190        self.assertEqual(tar.extractfile(t).read(), data,
191                "seek back did not work")
192        tar.close()
193
194    def test_fail_comp(self):
195        # For Gzip and Bz2 Tests: fail with a ReadError on an uncompressed file.
196        if self.mode == "r:":
197            return
198        self.assertRaises(tarfile.ReadError, tarfile.open, tarname, self.mode)
199        fobj = open(tarname, "rb")
200        self.assertRaises(tarfile.ReadError, tarfile.open, fileobj=fobj, mode=self.mode)
201
202    def test_v7_dirtype(self):
203        # Test old style dirtype member (bug #1336623):
204        # Old V7 tars create directory members using an AREGTYPE
205        # header with a "/" appended to the filename field.
206        tarinfo = self.tar.getmember("misc/dirtype-old-v7")
207        self.assertTrue(tarinfo.type == tarfile.DIRTYPE,
208                "v7 dirtype failed")
209
210    def test_xstar_type(self):
211        # The xstar format stores extra atime and ctime fields inside the
212        # space reserved for the prefix field. The prefix field must be
213        # ignored in this case, otherwise it will mess up the name.
214        try:
215            self.tar.getmember("misc/regtype-xstar")
216        except KeyError:
217            self.fail("failed to find misc/regtype-xstar (mangled prefix?)")
218
219    def test_check_members(self):
220        for tarinfo in self.tar:
221            self.assertTrue(int(tarinfo.mtime) == 0o7606136617,
222                    "wrong mtime for %s" % tarinfo.name)
223            if not tarinfo.name.startswith("ustar/"):
224                continue
225            self.assertTrue(tarinfo.uname == "tarfile",
226                    "wrong uname for %s" % tarinfo.name)
227
228    def test_find_members(self):
229        self.assertTrue(self.tar.getmembers()[-1].name == "misc/eof",
230                "could not find all members")
231
232    def test_extract_hardlink(self):
233        # Test hardlink extraction (e.g. bug #857297).
234        tar = tarfile.open(tarname, errorlevel=1, encoding="iso8859-1")
235
236        tar.extract("ustar/regtype", TEMPDIR)
237        try:
238            tar.extract("ustar/lnktype", TEMPDIR)
239        except EnvironmentError as e:
240            if e.errno == errno.ENOENT:
241                self.fail("hardlink not extracted properly")
242
243        data = open(os.path.join(TEMPDIR, "ustar/lnktype"), "rb").read()
244        self.assertEqual(md5sum(data), md5_regtype)
245
246        try:
247            tar.extract("ustar/symtype", TEMPDIR)
248        except EnvironmentError as e:
249            if e.errno == errno.ENOENT:
250                self.fail("symlink not extracted properly")
251
252        data = open(os.path.join(TEMPDIR, "ustar/symtype"), "rb").read()
253        self.assertEqual(md5sum(data), md5_regtype)
254
255    def test_extractall(self):
256        # Test if extractall() correctly restores directory permissions
257        # and times (see issue1735).
258        tar = tarfile.open(tarname, encoding="iso8859-1")
259        directories = [t for t in tar if t.isdir()]
260        tar.extractall(TEMPDIR, directories)
261        for tarinfo in directories:
262            path = os.path.join(TEMPDIR, tarinfo.name)
263            if sys.platform != "win32":
264                # Win32 has no support for fine grained permissions.
265                self.assertEqual(tarinfo.mode & 0o777, os.stat(path).st_mode & 0o777)
266            self.assertEqual(tarinfo.mtime, os.path.getmtime(path))
267        tar.close()
268
269
270class StreamReadTest(ReadTest):
271
272    mode="r|"
273
274    def test_fileobj_regular_file(self):
275        tarinfo = self.tar.next() # get "regtype" (can't use getmember)
276        fobj = self.tar.extractfile(tarinfo)
277        data = fobj.read()
278        self.assertTrue((len(data), md5sum(data)) == (tarinfo.size, md5_regtype),
279                "regular file extraction failed")
280
281    def test_provoke_stream_error(self):
282        tarinfos = self.tar.getmembers()
283        f = self.tar.extractfile(tarinfos[0]) # read the first member
284        self.assertRaises(tarfile.StreamError, f.read)
285
286    def test_compare_members(self):
287        tar1 = tarfile.open(tarname, encoding="iso8859-1")
288        tar2 = self.tar
289
290        while True:
291            t1 = tar1.next()
292            t2 = tar2.next()
293            if t1 is None:
294                break
295            self.assertTrue(t2 is not None, "stream.next() failed.")
296
297            if t2.islnk() or t2.issym():
298                self.assertRaises(tarfile.StreamError, tar2.extractfile, t2)
299                continue
300
301            v1 = tar1.extractfile(t1)
302            v2 = tar2.extractfile(t2)
303            if v1 is None:
304                continue
305            self.assertTrue(v2 is not None, "stream.extractfile() failed")
306            self.assertEqual(v1.read(), v2.read(), "stream extraction failed")
307
308        tar1.close()
309
310
311class DetectReadTest(unittest.TestCase):
312
313    def _testfunc_file(self, name, mode):
314        try:
315            tarfile.open(name, mode)
316        except tarfile.ReadError as e:
317            self.fail()
318
319    def _testfunc_fileobj(self, name, mode):
320        try:
321            tarfile.open(name, mode, fileobj=open(name, "rb"))
322        except tarfile.ReadError as e:
323            self.fail()
324
325    def _test_modes(self, testfunc):
326        testfunc(tarname, "r")
327        testfunc(tarname, "r:")
328        testfunc(tarname, "r:*")
329        testfunc(tarname, "r|")
330        testfunc(tarname, "r|*")
331
332        if gzip:
333            self.assertRaises(tarfile.ReadError, tarfile.open, tarname, mode="r:gz")
334            self.assertRaises(tarfile.ReadError, tarfile.open, tarname, mode="r|gz")
335            self.assertRaises(tarfile.ReadError, tarfile.open, gzipname, mode="r:")
336            self.assertRaises(tarfile.ReadError, tarfile.open, gzipname, mode="r|")
337
338            testfunc(gzipname, "r")
339            testfunc(gzipname, "r:*")
340            testfunc(gzipname, "r:gz")
341            testfunc(gzipname, "r|*")
342            testfunc(gzipname, "r|gz")
343
344        if bz2:
345            self.assertRaises(tarfile.ReadError, tarfile.open, tarname, mode="r:bz2")
346            self.assertRaises(tarfile.ReadError, tarfile.open, tarname, mode="r|bz2")
347            self.assertRaises(tarfile.ReadError, tarfile.open, bz2name, mode="r:")
348            self.assertRaises(tarfile.ReadError, tarfile.open, bz2name, mode="r|")
349
350            testfunc(bz2name, "r")
351            testfunc(bz2name, "r:*")
352            testfunc(bz2name, "r:bz2")
353            testfunc(bz2name, "r|*")
354            testfunc(bz2name, "r|bz2")
355
356    def test_detect_file(self):
357        self._test_modes(self._testfunc_file)
358
359    def test_detect_fileobj(self):
360        self._test_modes(self._testfunc_fileobj)
361
362
363class MemberReadTest(ReadTest):
364
365    def _test_member(self, tarinfo, chksum=None, **kwargs):
366        if chksum is not None:
367            self.assertTrue(md5sum(self.tar.extractfile(tarinfo).read()) == chksum,
368                    "wrong md5sum for %s" % tarinfo.name)
369
370        kwargs["mtime"] = 0o7606136617
371        kwargs["uid"] = 1000
372        kwargs["gid"] = 100
373        if "old-v7" not in tarinfo.name:
374            # V7 tar can't handle alphabetic owners.
375            kwargs["uname"] = "tarfile"
376            kwargs["gname"] = "tarfile"
377        for k, v in kwargs.items():
378            self.assertTrue(getattr(tarinfo, k) == v,
379                    "wrong value in %s field of %s" % (k, tarinfo.name))
380
381    def test_find_regtype(self):
382        tarinfo = self.tar.getmember("ustar/regtype")
383        self._test_member(tarinfo, size=7011, chksum=md5_regtype)
384
385    def test_find_conttype(self):
386        tarinfo = self.tar.getmember("ustar/conttype")
387        self._test_member(tarinfo, size=7011, chksum=md5_regtype)
388
389    def test_find_dirtype(self):
390        tarinfo = self.tar.getmember("ustar/dirtype")
391        self._test_member(tarinfo, size=0)
392
393    def test_find_dirtype_with_size(self):
394        tarinfo = self.tar.getmember("ustar/dirtype-with-size")
395        self._test_member(tarinfo, size=255)
396
397    def test_find_lnktype(self):
398        tarinfo = self.tar.getmember("ustar/lnktype")
399        self._test_member(tarinfo, size=0, linkname="ustar/regtype")
400
401    def test_find_symtype(self):
402        tarinfo = self.tar.getmember("ustar/symtype")
403        self._test_member(tarinfo, size=0, linkname="regtype")
404
405    def test_find_blktype(self):
406        tarinfo = self.tar.getmember("ustar/blktype")
407        self._test_member(tarinfo, size=0, devmajor=3, devminor=0)
408
409    def test_find_chrtype(self):
410        tarinfo = self.tar.getmember("ustar/chrtype")
411        self._test_member(tarinfo, size=0, devmajor=1, devminor=3)
412
413    def test_find_fifotype(self):
414        tarinfo = self.tar.getmember("ustar/fifotype")
415        self._test_member(tarinfo, size=0)
416
417    def test_find_sparse(self):
418        tarinfo = self.tar.getmember("ustar/sparse")
419        self._test_member(tarinfo, size=86016, chksum=md5_sparse)
420
421    def test_find_umlauts(self):
422        tarinfo = self.tar.getmember("ustar/umlauts-\xc4\xd6\xdc\xe4\xf6\xfc\xdf")
423        self._test_member(tarinfo, size=7011, chksum=md5_regtype)
424
425    def test_find_ustar_longname(self):
426        name = "ustar/" + "12345/" * 39 + "1234567/longname"
427        self.assertTrue(name in self.tar.getnames())
428
429    def test_find_regtype_oldv7(self):
430        tarinfo = self.tar.getmember("misc/regtype-old-v7")
431        self._test_member(tarinfo, size=7011, chksum=md5_regtype)
432
433    def test_find_pax_umlauts(self):
434        self.tar = tarfile.open(self.tarname, mode=self.mode, encoding="iso8859-1")
435        tarinfo = self.tar.getmember("pax/umlauts-\xc4\xd6\xdc\xe4\xf6\xfc\xdf")
436        self._test_member(tarinfo, size=7011, chksum=md5_regtype)
437
438
439class LongnameTest(ReadTest):
440
441    def test_read_longname(self):
442        # Test reading of longname (bug #1471427).
443        longname = self.subdir + "/" + "123/" * 125 + "longname"
444        try:
445            tarinfo = self.tar.getmember(longname)
446        except KeyError:
447            self.fail("longname not found")
448        self.assertTrue(tarinfo.type != tarfile.DIRTYPE, "read longname as dirtype")
449
450    def test_read_longlink(self):
451        longname = self.subdir + "/" + "123/" * 125 + "longname"
452        longlink = self.subdir + "/" + "123/" * 125 + "longlink"
453        try:
454            tarinfo = self.tar.getmember(longlink)
455        except KeyError:
456            self.fail("longlink not found")
457        self.assertTrue(tarinfo.linkname == longname, "linkname wrong")
458
459    def test_truncated_longname(self):
460        longname = self.subdir + "/" + "123/" * 125 + "longname"
461        tarinfo = self.tar.getmember(longname)
462        offset = tarinfo.offset
463        self.tar.fileobj.seek(offset)
464        fobj = io.BytesIO(self.tar.fileobj.read(3 * 512))
465        self.assertRaises(tarfile.ReadError, tarfile.open, name="foo.tar", fileobj=fobj)
466
467    def test_header_offset(self):
468        # Test if the start offset of the TarInfo object includes
469        # the preceding extended header.
470        longname = self.subdir + "/" + "123/" * 125 + "longname"
471        offset = self.tar.getmember(longname).offset
472        fobj = open(tarname, "rb")
473        fobj.seek(offset)
474        tarinfo = tarfile.TarInfo.frombuf(fobj.read(512), "iso8859-1", "strict")
475        self.assertEqual(tarinfo.type, self.longnametype)
476
477
478class GNUReadTest(LongnameTest):
479
480    subdir = "gnu"
481    longnametype = tarfile.GNUTYPE_LONGNAME
482
483    def test_sparse_file(self):
484        tarinfo1 = self.tar.getmember("ustar/sparse")
485        fobj1 = self.tar.extractfile(tarinfo1)
486        tarinfo2 = self.tar.getmember("gnu/sparse")
487        fobj2 = self.tar.extractfile(tarinfo2)
488        self.assertEqual(fobj1.read(), fobj2.read(),
489                "sparse file extraction failed")
490
491
492class PaxReadTest(LongnameTest):
493
494    subdir = "pax"
495    longnametype = tarfile.XHDTYPE
496
497    def test_pax_global_headers(self):
498        tar = tarfile.open(tarname, encoding="iso8859-1")
499
500        tarinfo = tar.getmember("pax/regtype1")
501        self.assertEqual(tarinfo.uname, "foo")
502        self.assertEqual(tarinfo.gname, "bar")
503        self.assertEqual(tarinfo.pax_headers.get("VENDOR.umlauts"), "\xc4\xd6\xdc\xe4\xf6\xfc\xdf")
504
505        tarinfo = tar.getmember("pax/regtype2")
506        self.assertEqual(tarinfo.uname, "")
507        self.assertEqual(tarinfo.gname, "bar")
508        self.assertEqual(tarinfo.pax_headers.get("VENDOR.umlauts"), "\xc4\xd6\xdc\xe4\xf6\xfc\xdf")
509
510        tarinfo = tar.getmember("pax/regtype3")
511        self.assertEqual(tarinfo.uname, "tarfile")
512        self.assertEqual(tarinfo.gname, "tarfile")
513        self.assertEqual(tarinfo.pax_headers.get("VENDOR.umlauts"), "\xc4\xd6\xdc\xe4\xf6\xfc\xdf")
514
515    def test_pax_number_fields(self):
516        # All following number fields are read from the pax header.
517        tar = tarfile.open(tarname, encoding="iso8859-1")
518        tarinfo = tar.getmember("pax/regtype4")
519        self.assertEqual(tarinfo.size, 7011)
520        self.assertEqual(tarinfo.uid, 123)
521        self.assertEqual(tarinfo.gid, 123)
522        self.assertEqual(tarinfo.mtime, 1041808783.0)
523        self.assertEqual(type(tarinfo.mtime), float)
524        self.assertEqual(float(tarinfo.pax_headers["atime"]), 1041808783.0)
525        self.assertEqual(float(tarinfo.pax_headers["ctime"]), 1041808783.0)
526
527
528class WriteTestBase(unittest.TestCase):
529    # Put all write tests in here that are supposed to be tested
530    # in all possible mode combinations.
531
532    def test_fileobj_no_close(self):
533        fobj = io.BytesIO()
534        tar = tarfile.open(fileobj=fobj, mode=self.mode)
535        tar.addfile(tarfile.TarInfo("foo"))
536        tar.close()
537        self.assertTrue(fobj.closed is False, "external fileobjs must never closed")
538
539
540class WriteTest(WriteTestBase):
541
542    mode = "w:"
543
544    def test_100_char_name(self):
545        # The name field in a tar header stores strings of at most 100 chars.
546        # If a string is shorter than 100 chars it has to be padded with '\0',
547        # which implies that a string of exactly 100 chars is stored without
548        # a trailing '\0'.
549        name = "0123456789" * 10
550        tar = tarfile.open(tmpname, self.mode)
551        t = tarfile.TarInfo(name)
552        tar.addfile(t)
553        tar.close()
554
555        tar = tarfile.open(tmpname)
556        self.assertTrue(tar.getnames()[0] == name,
557                "failed to store 100 char filename")
558        tar.close()
559
560    def test_tar_size(self):
561        # Test for bug #1013882.
562        tar = tarfile.open(tmpname, self.mode)
563        path = os.path.join(TEMPDIR, "file")
564        fobj = open(path, "wb")
565        fobj.write(b"aaa")
566        fobj.close()
567        tar.add(path)
568        tar.close()
569        self.assertTrue(os.path.getsize(tmpname) > 0,
570                "tarfile is empty")
571
572    # The test_*_size tests test for bug #1167128.
573    def test_file_size(self):
574        tar = tarfile.open(tmpname, self.mode)
575
576        path = os.path.join(TEMPDIR, "file")
577        fobj = open(path, "wb")
578        fobj.close()
579        tarinfo = tar.gettarinfo(path)
580        self.assertEqual(tarinfo.size, 0)
581
582        fobj = open(path, "wb")
583        fobj.write(b"aaa")
584        fobj.close()
585        tarinfo = tar.gettarinfo(path)
586        self.assertEqual(tarinfo.size, 3)
587
588        tar.close()
589
590    def test_directory_size(self):
591        path = os.path.join(TEMPDIR, "directory")
592        os.mkdir(path)
593        try:
594            tar = tarfile.open(tmpname, self.mode)
595            tarinfo = tar.gettarinfo(path)
596            self.assertEqual(tarinfo.size, 0)
597        finally:
598            os.rmdir(path)
599
600    def test_link_size(self):
601        if hasattr(os, "link"):
602            link = os.path.join(TEMPDIR, "link")
603            target = os.path.join(TEMPDIR, "link_target")
604            open(target, "wb").close()
605            os.link(target, link)
606            try:
607                tar = tarfile.open(tmpname, self.mode)
608                tarinfo = tar.gettarinfo(link)
609                self.assertEqual(tarinfo.size, 0)
610            finally:
611                os.remove(target)
612                os.remove(link)
613
614    def test_symlink_size(self):
615        if hasattr(os, "symlink"):
616            path = os.path.join(TEMPDIR, "symlink")
617            os.symlink("link_target", path)
618            try:
619                tar = tarfile.open(tmpname, self.mode)
620                tarinfo = tar.gettarinfo(path)
621                self.assertEqual(tarinfo.size, 0)
622            finally:
623                os.remove(path)
624
625    def test_add_self(self):
626        # Test for #1257255.
627        dstname = os.path.abspath(tmpname)
628
629        tar = tarfile.open(tmpname, self.mode)
630        self.assertTrue(tar.name == dstname, "archive name must be absolute")
631
632        tar.add(dstname)
633        self.assertTrue(tar.getnames() == [], "added the archive to itself")
634
635        cwd = os.getcwd()
636        os.chdir(TEMPDIR)
637        tar.add(dstname)
638        os.chdir(cwd)
639        self.assertTrue(tar.getnames() == [], "added the archive to itself")
640
641    def test_exclude(self):
642        tempdir = os.path.join(TEMPDIR, "exclude")
643        os.mkdir(tempdir)
644        try:
645            for name in ("foo", "bar", "baz"):
646                name = os.path.join(tempdir, name)
647                open(name, "wb").close()
648
649            def exclude(name):
650                return os.path.isfile(name)
651
652            tar = tarfile.open(tmpname, self.mode, encoding="iso8859-1")
653            tar.add(tempdir, arcname="empty_dir", exclude=exclude)
654            tar.close()
655
656            tar = tarfile.open(tmpname, "r")
657            self.assertEqual(len(tar.getmembers()), 1)
658            self.assertEqual(tar.getnames()[0], "empty_dir")
659        finally:
660            shutil.rmtree(tempdir)
661
662    # Guarantee that stored pathnames are not modified. Don't
663    # remove ./ or ../ or double slashes. Still make absolute
664    # pathnames relative.
665    # For details see bug #6054.
666    def _test_pathname(self, path, cmp_path=None, dir=False):
667        # Create a tarfile with an empty member named path
668        # and compare the stored name with the original.
669        foo = os.path.join(TEMPDIR, "foo")
670        if not dir:
671            open(foo, "w").close()
672        else:
673            os.mkdir(foo)
674
675        tar = tarfile.open(tmpname, self.mode)
676        tar.add(foo, arcname=path)
677        tar.close()
678
679        tar = tarfile.open(tmpname, "r")
680        t = tar.next()
681        tar.close()
682
683        if not dir:
684            os.remove(foo)
685        else:
686            os.rmdir(foo)
687
688        self.assertEqual(t.name, cmp_path or path.replace(os.sep, "/"))
689
690    def test_pathnames(self):
691        self._test_pathname("foo")
692        self._test_pathname(os.path.join("foo", ".", "bar"))
693        self._test_pathname(os.path.join("foo", "..", "bar"))
694        self._test_pathname(os.path.join(".", "foo"))
695        self._test_pathname(os.path.join(".", "foo", "."))
696        self._test_pathname(os.path.join(".", "foo", ".", "bar"))
697        self._test_pathname(os.path.join(".", "foo", "..", "bar"))
698        self._test_pathname(os.path.join(".", "foo", "..", "bar"))
699        self._test_pathname(os.path.join("..", "foo"))
700        self._test_pathname(os.path.join("..", "foo", ".."))
701        self._test_pathname(os.path.join("..", "foo", ".", "bar"))
702        self._test_pathname(os.path.join("..", "foo", "..", "bar"))
703
704        self._test_pathname("foo" + os.sep + os.sep + "bar")
705        self._test_pathname("foo" + os.sep + os.sep, "foo", dir=True)
706
707    def test_abs_pathnames(self):
708        if sys.platform == "win32":
709            self._test_pathname("C:\\foo", "foo")
710        else:
711            self._test_pathname("/foo", "foo")
712            self._test_pathname("///foo", "foo")
713
714    def test_cwd(self):
715        # Test adding the current working directory.
716        cwd = os.getcwd()
717        os.chdir(TEMPDIR)
718        try:
719            open("foo", "w").close()
720
721            tar = tarfile.open(tmpname, self.mode)
722            tar.add(".")
723            tar.close()
724
725            tar = tarfile.open(tmpname, "r")
726            for t in tar:
727                self.assert_(t.name == "." or t.name.startswith("./"))
728            tar.close()
729        finally:
730            os.chdir(cwd)
731
732
733class StreamWriteTest(WriteTestBase):
734
735    mode = "w|"
736
737    def test_stream_padding(self):
738        # Test for bug #1543303.
739        tar = tarfile.open(tmpname, self.mode)
740        tar.close()
741
742        if self.mode.endswith("gz"):
743            fobj = gzip.GzipFile(tmpname)
744            data = fobj.read()
745            fobj.close()
746        elif self.mode.endswith("bz2"):
747            dec = bz2.BZ2Decompressor()
748            data = open(tmpname, "rb").read()
749            data = dec.decompress(data)
750            self.assertTrue(len(dec.unused_data) == 0,
751                    "found trailing data")
752        else:
753            fobj = open(tmpname, "rb")
754            data = fobj.read()
755            fobj.close()
756
757        self.assertTrue(data.count(b"\0") == tarfile.RECORDSIZE,
758                         "incorrect zero padding")
759
760
761class GNUWriteTest(unittest.TestCase):
762    # This testcase checks for correct creation of GNU Longname
763    # and Longlink extended headers (cp. bug #812325).
764
765    def _length(self, s):
766        blocks, remainder = divmod(len(s) + 1, 512)
767        if remainder:
768            blocks += 1
769        return blocks * 512
770
771    def _calc_size(self, name, link=None):
772        # Initial tar header
773        count = 512
774
775        if len(name) > tarfile.LENGTH_NAME:
776            # GNU longname extended header + longname
777            count += 512
778            count += self._length(name)
779        if link is not None and len(link) > tarfile.LENGTH_LINK:
780            # GNU longlink extended header + longlink
781            count += 512
782            count += self._length(link)
783        return count
784
785    def _test(self, name, link=None):
786        tarinfo = tarfile.TarInfo(name)
787        if link:
788            tarinfo.linkname = link
789            tarinfo.type = tarfile.LNKTYPE
790
791        tar = tarfile.open(tmpname, "w")
792        tar.format = tarfile.GNU_FORMAT
793        tar.addfile(tarinfo)
794
795        v1 = self._calc_size(name, link)
796        v2 = tar.offset
797        self.assertTrue(v1 == v2, "GNU longname/longlink creation failed")
798
799        tar.close()
800
801        tar = tarfile.open(tmpname)
802        member = tar.next()
803        self.assertFalse(member is None, "unable to read longname member")
804        self.assertTrue(tarinfo.name == member.name and \
805                     tarinfo.linkname == member.linkname, \
806                     "unable to read longname member")
807
808    def test_longname_1023(self):
809        self._test(("longnam/" * 127) + "longnam")
810
811    def test_longname_1024(self):
812        self._test(("longnam/" * 127) + "longname")
813
814    def test_longname_1025(self):
815        self._test(("longnam/" * 127) + "longname_")
816
817    def test_longlink_1023(self):
818        self._test("name", ("longlnk/" * 127) + "longlnk")
819
820    def test_longlink_1024(self):
821        self._test("name", ("longlnk/" * 127) + "longlink")
822
823    def test_longlink_1025(self):
824        self._test("name", ("longlnk/" * 127) + "longlink_")
825
826    def test_longnamelink_1023(self):
827        self._test(("longnam/" * 127) + "longnam",
828                   ("longlnk/" * 127) + "longlnk")
829
830    def test_longnamelink_1024(self):
831        self._test(("longnam/" * 127) + "longname",
832                   ("longlnk/" * 127) + "longlink")
833
834    def test_longnamelink_1025(self):
835        self._test(("longnam/" * 127) + "longname_",
836                   ("longlnk/" * 127) + "longlink_")
837
838
839class HardlinkTest(unittest.TestCase):
840    # Test the creation of LNKTYPE (hardlink) members in an archive.
841
842    def setUp(self):
843        self.foo = os.path.join(TEMPDIR, "foo")
844        self.bar = os.path.join(TEMPDIR, "bar")
845
846        fobj = open(self.foo, "wb")
847        fobj.write(b"foo")
848        fobj.close()
849
850        os.link(self.foo, self.bar)
851
852        self.tar = tarfile.open(tmpname, "w")
853        self.tar.add(self.foo)
854
855    def tearDown(self):
856        self.tar.close()
857        os.remove(self.foo)
858        os.remove(self.bar)
859
860    def test_add_twice(self):
861        # The same name will be added as a REGTYPE every
862        # time regardless of st_nlink.
863        tarinfo = self.tar.gettarinfo(self.foo)
864        self.assertTrue(tarinfo.type == tarfile.REGTYPE,
865                "add file as regular failed")
866
867    def test_add_hardlink(self):
868        tarinfo = self.tar.gettarinfo(self.bar)
869        self.assertTrue(tarinfo.type == tarfile.LNKTYPE,
870                "add file as hardlink failed")
871
872    def test_dereference_hardlink(self):
873        self.tar.dereference = True
874        tarinfo = self.tar.gettarinfo(self.bar)
875        self.assertTrue(tarinfo.type == tarfile.REGTYPE,
876                "dereferencing hardlink failed")
877
878
879class PaxWriteTest(GNUWriteTest):
880
881    def _test(self, name, link=None):
882        # See GNUWriteTest.
883        tarinfo = tarfile.TarInfo(name)
884        if link:
885            tarinfo.linkname = link
886            tarinfo.type = tarfile.LNKTYPE
887
888        tar = tarfile.open(tmpname, "w", format=tarfile.PAX_FORMAT)
889        tar.addfile(tarinfo)
890        tar.close()
891
892        tar = tarfile.open(tmpname)
893        if link:
894            l = tar.getmembers()[0].linkname
895            self.assertTrue(link == l, "PAX longlink creation failed")
896        else:
897            n = tar.getmembers()[0].name
898            self.assertTrue(name == n, "PAX longname creation failed")
899
900    def test_pax_global_header(self):
901        pax_headers = {
902                "foo": "bar",
903                "uid": "0",
904                "mtime": "1.23",
905                "test": "\xe4\xf6\xfc",
906                "\xe4\xf6\xfc": "test"}
907
908        tar = tarfile.open(tmpname, "w", format=tarfile.PAX_FORMAT, \
909                pax_headers=pax_headers)
910        tar.addfile(tarfile.TarInfo("test"))
911        tar.close()
912
913        # Test if the global header was written correctly.
914        tar = tarfile.open(tmpname, encoding="iso8859-1")
915        self.assertEqual(tar.pax_headers, pax_headers)
916        self.assertEqual(tar.getmembers()[0].pax_headers, pax_headers)
917
918        # Test if all the fields are strings.
919        for key, val in tar.pax_headers.items():
920            self.assertTrue(type(key) is not bytes)
921            self.assertTrue(type(val) is not bytes)
922            if key in tarfile.PAX_NUMBER_FIELDS:
923                try:
924                    tarfile.PAX_NUMBER_FIELDS[key](val)
925                except (TypeError, ValueError):
926                    self.fail("unable to convert pax header field")
927
928    def test_pax_extended_header(self):
929        # The fields from the pax header have priority over the
930        # TarInfo.
931        pax_headers = {"path": "foo", "uid": "123"}
932
933        tar = tarfile.open(tmpname, "w", format=tarfile.PAX_FORMAT, encoding="iso8859-1")
934        t = tarfile.TarInfo()
935        t.name = "\xe4\xf6\xfc" # non-ASCII
936        t.uid = 8**8 # too large
937        t.pax_headers = pax_headers
938        tar.addfile(t)
939        tar.close()
940
941        tar = tarfile.open(tmpname, encoding="iso8859-1")
942        t = tar.getmembers()[0]
943        self.assertEqual(t.pax_headers, pax_headers)
944        self.assertEqual(t.name, "foo")
945        self.assertEqual(t.uid, 123)
946
947
948class UstarUnicodeTest(unittest.TestCase):
949
950    format = tarfile.USTAR_FORMAT
951
952    def test_iso8859_1_filename(self):
953        self._test_unicode_filename("iso8859-1")
954
955    def test_utf7_filename(self):
956        self._test_unicode_filename("utf7")
957
958    def test_utf8_filename(self):
959        self._test_unicode_filename("utf8")
960
961    def _test_unicode_filename(self, encoding):
962        tar = tarfile.open(tmpname, "w", format=self.format, encoding=encoding, errors="strict")
963        name = "\xe4\xf6\xfc"
964        tar.addfile(tarfile.TarInfo(name))
965        tar.close()
966
967        tar = tarfile.open(tmpname, encoding=encoding)
968        self.assertEqual(tar.getmembers()[0].name, name)
969        tar.close()
970
971    def test_unicode_filename_error(self):
972        if self.format == tarfile.PAX_FORMAT:
973            # PAX_FORMAT ignores encoding in write mode.
974            return
975
976        tar = tarfile.open(tmpname, "w", format=self.format, encoding="ascii", errors="strict")
977        tarinfo = tarfile.TarInfo()
978
979        tarinfo.name = "\xe4\xf6\xfc"
980        self.assertRaises(UnicodeError, tar.addfile, tarinfo)
981
982        tarinfo.name = "foo"
983        tarinfo.uname = "\xe4\xf6\xfc"
984        self.assertRaises(UnicodeError, tar.addfile, tarinfo)
985
986    def test_unicode_argument(self):
987        tar = tarfile.open(tarname, "r", encoding="iso8859-1", errors="strict")
988        for t in tar:
989            self.assertTrue(type(t.name) is str)
990            self.assertTrue(type(t.linkname) is str)
991            self.assertTrue(type(t.uname) is str)
992            self.assertTrue(type(t.gname) is str)
993        tar.close()
994
995    def test_uname_unicode(self):
996        t = tarfile.TarInfo("foo")
997        t.uname = "\xe4\xf6\xfc"
998        t.gname = "\xe4\xf6\xfc"
999
1000        tar = tarfile.open(tmpname, mode="w", format=self.format, encoding="iso8859-1")
1001        tar.addfile(t)
1002        tar.close()
1003
1004        tar = tarfile.open(tmpname, encoding="iso8859-1")
1005        t = tar.getmember("foo")
1006        self.assertEqual(t.uname, "\xe4\xf6\xfc")
1007        self.assertEqual(t.gname, "\xe4\xf6\xfc")
1008
1009        if self.format != tarfile.PAX_FORMAT:
1010            tar = tarfile.open(tmpname, encoding="ascii")
1011            t = tar.getmember("foo")
1012            self.assertEqual(t.uname, "\ufffd\ufffd\ufffd")
1013            self.assertEqual(t.gname, "\ufffd\ufffd\ufffd")
1014
1015
1016class GNUUnicodeTest(UstarUnicodeTest):
1017
1018    format = tarfile.GNU_FORMAT
1019
1020
1021class PAXUnicodeTest(UstarUnicodeTest):
1022
1023    format = tarfile.PAX_FORMAT
1024
1025
1026class AppendTest(unittest.TestCase):
1027    # Test append mode (cp. patch #1652681).
1028
1029    def setUp(self):
1030        self.tarname = tmpname
1031        if os.path.exists(self.tarname):
1032            os.remove(self.tarname)
1033
1034    def _add_testfile(self, fileobj=None):
1035        tar = tarfile.open(self.tarname, "a", fileobj=fileobj)
1036        tar.addfile(tarfile.TarInfo("bar"))
1037        tar.close()
1038
1039    def _create_testtar(self, mode="w:"):
1040        src = tarfile.open(tarname, encoding="iso8859-1")
1041        t = src.getmember("ustar/regtype")
1042        t.name = "foo"
1043        f = src.extractfile(t)
1044        tar = tarfile.open(self.tarname, mode)
1045        tar.addfile(t, f)
1046        tar.close()
1047
1048    def _test(self, names=["bar"], fileobj=None):
1049        tar = tarfile.open(self.tarname, fileobj=fileobj)
1050        self.assertEqual(tar.getnames(), names)
1051
1052    def test_non_existing(self):
1053        self._add_testfile()
1054        self._test()
1055
1056    def test_empty(self):
1057        open(self.tarname, "w").close()
1058        self._add_testfile()
1059        self._test()
1060
1061    def test_empty_fileobj(self):
1062        fobj = io.BytesIO()
1063        self._add_testfile(fobj)
1064        fobj.seek(0)
1065        self._test(fileobj=fobj)
1066
1067    def test_fileobj(self):
1068        self._create_testtar()
1069        data = open(self.tarname, "rb").read()
1070        fobj = io.BytesIO(data)
1071        self._add_testfile(fobj)
1072        fobj.seek(0)
1073        self._test(names=["foo", "bar"], fileobj=fobj)
1074
1075    def test_existing(self):
1076        self._create_testtar()
1077        self._add_testfile()
1078        self._test(names=["foo", "bar"])
1079
1080    def test_append_gz(self):
1081        if gzip is None:
1082            return
1083        self._create_testtar("w:gz")
1084        self.assertRaises(tarfile.ReadError, tarfile.open, tmpname, "a")
1085
1086    def test_append_bz2(self):
1087        if bz2 is None:
1088            return
1089        self._create_testtar("w:bz2")
1090        self.assertRaises(tarfile.ReadError, tarfile.open, tmpname, "a")
1091
1092
1093class LimitsTest(unittest.TestCase):
1094
1095    def test_ustar_limits(self):
1096        # 100 char name
1097        tarinfo = tarfile.TarInfo("0123456789" * 10)
1098        tarinfo.tobuf(tarfile.USTAR_FORMAT)
1099
1100        # 101 char name that cannot be stored
1101        tarinfo = tarfile.TarInfo("0123456789" * 10 + "0")
1102        self.assertRaises(ValueError, tarinfo.tobuf, tarfile.USTAR_FORMAT)
1103
1104        # 256 char name with a slash at pos 156
1105        tarinfo = tarfile.TarInfo("123/" * 62 + "longname")
1106        tarinfo.tobuf(tarfile.USTAR_FORMAT)
1107
1108        # 256 char name that cannot be stored
1109        tarinfo = tarfile.TarInfo("1234567/" * 31 + "longname")
1110        self.assertRaises(ValueError, tarinfo.tobuf, tarfile.USTAR_FORMAT)
1111
1112        # 512 char name
1113        tarinfo = tarfile.TarInfo("123/" * 126 + "longname")
1114        self.assertRaises(ValueError, tarinfo.tobuf, tarfile.USTAR_FORMAT)
1115
1116        # 512 char linkname
1117        tarinfo = tarfile.TarInfo("longlink")
1118        tarinfo.linkname = "123/" * 126 + "longname"
1119        self.assertRaises(ValueError, tarinfo.tobuf, tarfile.USTAR_FORMAT)
1120
1121        # uid > 8 digits
1122        tarinfo = tarfile.TarInfo("name")
1123        tarinfo.uid = 0o10000000
1124        self.assertRaises(ValueError, tarinfo.tobuf, tarfile.USTAR_FORMAT)
1125
1126    def test_gnu_limits(self):
1127        tarinfo = tarfile.TarInfo("123/" * 126 + "longname")
1128        tarinfo.tobuf(tarfile.GNU_FORMAT)
1129
1130        tarinfo = tarfile.TarInfo("longlink")
1131        tarinfo.linkname = "123/" * 126 + "longname"
1132        tarinfo.tobuf(tarfile.GNU_FORMAT)
1133
1134        # uid >= 256 ** 7
1135        tarinfo = tarfile.TarInfo("name")
1136        tarinfo.uid = 0o4000000000000000000
1137        self.assertRaises(ValueError, tarinfo.tobuf, tarfile.GNU_FORMAT)
1138
1139    def test_pax_limits(self):
1140        tarinfo = tarfile.TarInfo("123/" * 126 + "longname")
1141        tarinfo.tobuf(tarfile.PAX_FORMAT)
1142
1143        tarinfo = tarfile.TarInfo("longlink")
1144        tarinfo.linkname = "123/" * 126 + "longname"
1145        tarinfo.tobuf(tarfile.PAX_FORMAT)
1146
1147        tarinfo = tarfile.TarInfo("name")
1148        tarinfo.uid = 0o4000000000000000000
1149        tarinfo.tobuf(tarfile.PAX_FORMAT)
1150
1151
1152class MiscTest(unittest.TestCase):
1153
1154    def test_char_fields(self):
1155        self.assertEqual(tarfile.stn("foo", 8, "ascii", "strict"), b"foo\0\0\0\0\0")
1156        self.assertEqual(tarfile.stn("foobar", 3, "ascii", "strict"), b"foo")
1157        self.assertEqual(tarfile.nts(b"foo\0\0\0\0\0", "ascii", "strict"), "foo")
1158        self.assertEqual(tarfile.nts(b"foo\0bar\0", "ascii", "strict"), "foo")
1159
1160    def test_number_fields(self):
1161        self.assertEqual(tarfile.itn(1), b"0000001\x00")
1162        self.assertEqual(tarfile.itn(0xffffffff), b"\x80\x00\x00\x00\xff\xff\xff\xff")
1163
1164
1165class GzipMiscReadTest(MiscReadTest):
1166    tarname = gzipname
1167    mode = "r:gz"
1168class GzipUstarReadTest(UstarReadTest):
1169    tarname = gzipname
1170    mode = "r:gz"
1171class GzipStreamReadTest(StreamReadTest):
1172    tarname = gzipname
1173    mode = "r|gz"
1174class GzipWriteTest(WriteTest):
1175    mode = "w:gz"
1176class GzipStreamWriteTest(StreamWriteTest):
1177    mode = "w|gz"
1178
1179
1180class Bz2MiscReadTest(MiscReadTest):
1181    tarname = bz2name
1182    mode = "r:bz2"
1183class Bz2UstarReadTest(UstarReadTest):
1184    tarname = bz2name
1185    mode = "r:bz2"
1186class Bz2StreamReadTest(StreamReadTest):
1187    tarname = bz2name
1188    mode = "r|bz2"
1189class Bz2WriteTest(WriteTest):
1190    mode = "w:bz2"
1191class Bz2StreamWriteTest(StreamWriteTest):
1192    mode = "w|bz2"
1193
1194class Bz2PartialReadTest(unittest.TestCase):
1195    # Issue5068: The _BZ2Proxy.read() method loops forever
1196    # on an empty or partial bzipped file.
1197
1198    def _test_partial_input(self, mode):
1199        class MyBytesIO(io.BytesIO):
1200            hit_eof = False
1201            def read(self, n):
1202                if self.hit_eof:
1203                    raise AssertionError("infinite loop detected in tarfile.open()")
1204                self.hit_eof = self.tell() == len(self.getvalue())
1205                return super(MyBytesIO, self).read(n)
1206
1207        data = bz2.compress(tarfile.TarInfo("foo").tobuf())
1208        for x in range(len(data) + 1):
1209            tarfile.open(fileobj=MyBytesIO(data[:x]), mode=mode)
1210
1211    def test_partial_input(self):
1212        self._test_partial_input("r")
1213
1214    def test_partial_input_bz2(self):
1215        self._test_partial_input("r:bz2")
1216
1217
1218def test_main():
1219    if not os.path.exists(TEMPDIR):
1220        os.mkdir(TEMPDIR)
1221
1222    tests = [
1223        UstarReadTest,
1224        MiscReadTest,
1225        StreamReadTest,
1226        DetectReadTest,
1227        MemberReadTest,
1228        GNUReadTest,
1229        PaxReadTest,
1230        WriteTest,
1231        StreamWriteTest,
1232        GNUWriteTest,
1233        PaxWriteTest,
1234        UstarUnicodeTest,
1235        GNUUnicodeTest,
1236        PAXUnicodeTest,
1237        AppendTest,
1238        LimitsTest,
1239        MiscTest,
1240    ]
1241
1242    if hasattr(os, "link"):
1243        tests.append(HardlinkTest)
1244
1245    fobj = open(tarname, "rb")
1246    data = fobj.read()
1247    fobj.close()
1248
1249    if gzip:
1250        # Create testtar.tar.gz and add gzip-specific tests.
1251        tar = gzip.open(gzipname, "wb")
1252        tar.write(data)
1253        tar.close()
1254
1255        tests += [
1256            GzipMiscReadTest,
1257            GzipUstarReadTest,
1258            GzipStreamReadTest,
1259            GzipWriteTest,
1260            GzipStreamWriteTest,
1261        ]
1262
1263    if bz2:
1264        # Create testtar.tar.bz2 and add bz2-specific tests.
1265        tar = bz2.BZ2File(bz2name, "wb")
1266        tar.write(data)
1267        tar.close()
1268
1269        tests += [
1270            Bz2MiscReadTest,
1271            Bz2UstarReadTest,
1272            Bz2StreamReadTest,
1273            Bz2WriteTest,
1274            Bz2StreamWriteTest,
1275            Bz2PartialReadTest,
1276        ]
1277
1278    try:
1279        support.run_unittest(*tests)
1280    finally:
1281        if os.path.exists(TEMPDIR):
1282            shutil.rmtree(TEMPDIR)
1283
1284if __name__ == "__main__":
1285    test_main()
1286