1import sys
2import os
3import unittest
4from array import array
5from weakref import proxy
6
7import io
8import _pyio as pyio
9
10from test.support import TESTFN
11from collections import UserList
12
13class AutoFileTests:
14    # file tests for which a test file is automatically set up
15
16    def setUp(self):
17        self.f = self.open(TESTFN, 'wb')
18
19    def tearDown(self):
20        if self.f:
21            self.f.close()
22        os.remove(TESTFN)
23
24    def testWeakRefs(self):
25        # verify weak references
26        p = proxy(self.f)
27        p.write(b'teststring')
28        self.assertEqual(self.f.tell(), p.tell())
29        self.f.close()
30        self.f = None
31        self.assertRaises(ReferenceError, getattr, p, 'tell')
32
33    def testAttributes(self):
34        # verify expected attributes exist
35        f = self.f
36        f.name     # merely shouldn't blow up
37        f.mode     # ditto
38        f.closed   # ditto
39
40    def testReadinto(self):
41        # verify readinto
42        self.f.write(b'12')
43        self.f.close()
44        a = array('b', b'x'*10)
45        self.f = self.open(TESTFN, 'rb')
46        n = self.f.readinto(a)
47        self.assertEqual(b'12', a.tobytes()[:n])
48
49    def testReadinto_text(self):
50        # verify readinto refuses text files
51        a = array('b', b'x'*10)
52        self.f.close()
53        self.f = self.open(TESTFN, 'r')
54        if hasattr(self.f, "readinto"):
55            self.assertRaises(TypeError, self.f.readinto, a)
56
57    def testWritelinesUserList(self):
58        # verify writelines with instance sequence
59        l = UserList([b'1', b'2'])
60        self.f.writelines(l)
61        self.f.close()
62        self.f = self.open(TESTFN, 'rb')
63        buf = self.f.read()
64        self.assertEqual(buf, b'12')
65
66    def testWritelinesIntegers(self):
67        # verify writelines with integers
68        self.assertRaises(TypeError, self.f.writelines, [1, 2, 3])
69
70    def testWritelinesIntegersUserList(self):
71        # verify writelines with integers in UserList
72        l = UserList([1,2,3])
73        self.assertRaises(TypeError, self.f.writelines, l)
74
75    def testWritelinesNonString(self):
76        # verify writelines with non-string object
77        class NonString:
78            pass
79
80        self.assertRaises(TypeError, self.f.writelines,
81                          [NonString(), NonString()])
82
83    def testErrors(self):
84        f = self.f
85        self.assertEqual(f.name, TESTFN)
86        self.assertFalse(f.isatty())
87        self.assertFalse(f.closed)
88
89        if hasattr(f, "readinto"):
90            self.assertRaises((OSError, TypeError), f.readinto, "")
91        f.close()
92        self.assertTrue(f.closed)
93
94    def testMethods(self):
95        methods = [('fileno', ()),
96                   ('flush', ()),
97                   ('isatty', ()),
98                   ('__next__', ()),
99                   ('read', ()),
100                   ('write', (b"",)),
101                   ('readline', ()),
102                   ('readlines', ()),
103                   ('seek', (0,)),
104                   ('tell', ()),
105                   ('write', (b"",)),
106                   ('writelines', ([],)),
107                   ('__iter__', ()),
108                   ]
109        methods.append(('truncate', ()))
110
111        # __exit__ should close the file
112        self.f.__exit__(None, None, None)
113        self.assertTrue(self.f.closed)
114
115        for methodname, args in methods:
116            method = getattr(self.f, methodname)
117            # should raise on closed file
118            self.assertRaises(ValueError, method, *args)
119
120        # file is closed, __exit__ shouldn't do anything
121        self.assertEqual(self.f.__exit__(None, None, None), None)
122        # it must also return None if an exception was given
123        try:
124            1/0
125        except:
126            self.assertEqual(self.f.__exit__(*sys.exc_info()), None)
127
128    def testReadWhenWriting(self):
129        self.assertRaises(OSError, self.f.read)
130
131class CAutoFileTests(AutoFileTests, unittest.TestCase):
132    open = io.open
133
134class PyAutoFileTests(AutoFileTests, unittest.TestCase):
135    open = staticmethod(pyio.open)
136
137
138class OtherFileTests:
139
140    def testModeStrings(self):
141        # check invalid mode strings
142        for mode in ("", "aU", "wU+", "U+", "+U", "rU+"):
143            try:
144                f = self.open(TESTFN, mode)
145            except ValueError:
146                pass
147            else:
148                f.close()
149                self.fail('%r is an invalid file mode' % mode)
150
151    def testBadModeArgument(self):
152        # verify that we get a sensible error message for bad mode argument
153        bad_mode = "qwerty"
154        try:
155            f = self.open(TESTFN, bad_mode)
156        except ValueError as msg:
157            if msg.args[0] != 0:
158                s = str(msg)
159                if TESTFN in s or bad_mode not in s:
160                    self.fail("bad error message for invalid mode: %s" % s)
161            # if msg.args[0] == 0, we're probably on Windows where there may be
162            # no obvious way to discover why open() failed.
163        else:
164            f.close()
165            self.fail("no error for invalid mode: %s" % bad_mode)
166
167    def testSetBufferSize(self):
168        # make sure that explicitly setting the buffer size doesn't cause
169        # misbehaviour especially with repeated close() calls
170        for s in (-1, 0, 1, 512):
171            try:
172                f = self.open(TESTFN, 'wb', s)
173                f.write(str(s).encode("ascii"))
174                f.close()
175                f.close()
176                f = self.open(TESTFN, 'rb', s)
177                d = int(f.read().decode("ascii"))
178                f.close()
179                f.close()
180            except OSError as msg:
181                self.fail('error setting buffer size %d: %s' % (s, str(msg)))
182            self.assertEqual(d, s)
183
184    def testTruncateOnWindows(self):
185        # SF bug <http://www.python.org/sf/801631>
186        # "file.truncate fault on windows"
187
188        os.unlink(TESTFN)
189        f = self.open(TESTFN, 'wb')
190
191        try:
192            f.write(b'12345678901')   # 11 bytes
193            f.close()
194
195            f = self.open(TESTFN,'rb+')
196            data = f.read(5)
197            if data != b'12345':
198                self.fail("Read on file opened for update failed %r" % data)
199            if f.tell() != 5:
200                self.fail("File pos after read wrong %d" % f.tell())
201
202            f.truncate()
203            if f.tell() != 5:
204                self.fail("File pos after ftruncate wrong %d" % f.tell())
205
206            f.close()
207            size = os.path.getsize(TESTFN)
208            if size != 5:
209                self.fail("File size after ftruncate wrong %d" % size)
210        finally:
211            f.close()
212            os.unlink(TESTFN)
213
214    def testIteration(self):
215        # Test the complex interaction when mixing file-iteration and the
216        # various read* methods.
217        dataoffset = 16384
218        filler = b"ham\n"
219        assert not dataoffset % len(filler), \
220            "dataoffset must be multiple of len(filler)"
221        nchunks = dataoffset // len(filler)
222        testlines = [
223            b"spam, spam and eggs\n",
224            b"eggs, spam, ham and spam\n",
225            b"saussages, spam, spam and eggs\n",
226            b"spam, ham, spam and eggs\n",
227            b"spam, spam, spam, spam, spam, ham, spam\n",
228            b"wonderful spaaaaaam.\n"
229        ]
230        methods = [("readline", ()), ("read", ()), ("readlines", ()),
231                   ("readinto", (array("b", b" "*100),))]
232
233        try:
234            # Prepare the testfile
235            bag = self.open(TESTFN, "wb")
236            bag.write(filler * nchunks)
237            bag.writelines(testlines)
238            bag.close()
239            # Test for appropriate errors mixing read* and iteration
240            for methodname, args in methods:
241                f = self.open(TESTFN, 'rb')
242                if next(f) != filler:
243                    self.fail, "Broken testfile"
244                meth = getattr(f, methodname)
245                meth(*args)  # This simply shouldn't fail
246                f.close()
247
248            # Test to see if harmless (by accident) mixing of read* and
249            # iteration still works. This depends on the size of the internal
250            # iteration buffer (currently 8192,) but we can test it in a
251            # flexible manner.  Each line in the bag o' ham is 4 bytes
252            # ("h", "a", "m", "\n"), so 4096 lines of that should get us
253            # exactly on the buffer boundary for any power-of-2 buffersize
254            # between 4 and 16384 (inclusive).
255            f = self.open(TESTFN, 'rb')
256            for i in range(nchunks):
257                next(f)
258            testline = testlines.pop(0)
259            try:
260                line = f.readline()
261            except ValueError:
262                self.fail("readline() after next() with supposedly empty "
263                          "iteration-buffer failed anyway")
264            if line != testline:
265                self.fail("readline() after next() with empty buffer "
266                          "failed. Got %r, expected %r" % (line, testline))
267            testline = testlines.pop(0)
268            buf = array("b", b"\x00" * len(testline))
269            try:
270                f.readinto(buf)
271            except ValueError:
272                self.fail("readinto() after next() with supposedly empty "
273                          "iteration-buffer failed anyway")
274            line = buf.tobytes()
275            if line != testline:
276                self.fail("readinto() after next() with empty buffer "
277                          "failed. Got %r, expected %r" % (line, testline))
278
279            testline = testlines.pop(0)
280            try:
281                line = f.read(len(testline))
282            except ValueError:
283                self.fail("read() after next() with supposedly empty "
284                          "iteration-buffer failed anyway")
285            if line != testline:
286                self.fail("read() after next() with empty buffer "
287                          "failed. Got %r, expected %r" % (line, testline))
288            try:
289                lines = f.readlines()
290            except ValueError:
291                self.fail("readlines() after next() with supposedly empty "
292                          "iteration-buffer failed anyway")
293            if lines != testlines:
294                self.fail("readlines() after next() with empty buffer "
295                          "failed. Got %r, expected %r" % (line, testline))
296            f.close()
297
298            # Reading after iteration hit EOF shouldn't hurt either
299            f = self.open(TESTFN, 'rb')
300            try:
301                for line in f:
302                    pass
303                try:
304                    f.readline()
305                    f.readinto(buf)
306                    f.read()
307                    f.readlines()
308                except ValueError:
309                    self.fail("read* failed after next() consumed file")
310            finally:
311                f.close()
312        finally:
313            os.unlink(TESTFN)
314
315class COtherFileTests(OtherFileTests, unittest.TestCase):
316    open = io.open
317
318class PyOtherFileTests(OtherFileTests, unittest.TestCase):
319    open = staticmethod(pyio.open)
320
321
322def tearDownModule():
323    # Historically, these tests have been sloppy about removing TESTFN.
324    # So get rid of it no matter what.
325    if os.path.exists(TESTFN):
326        os.unlink(TESTFN)
327
328if __name__ == '__main__':
329    unittest.main()
330