blob: c254b047e1ec52ef807e5199c1442dd8b7953f8e [file] [log] [blame]
Christian Heimes77c02eb2008-02-09 02:18:51 +00001"""Test largefile support on system where this makes sense.
2"""
Trent Mickf29f47b2000-08-11 19:02:59 +00003
Christian Heimes77c02eb2008-02-09 02:18:51 +00004import os
5import stat
6import sys
7import unittest
Giampaolo Rodola5bcc6d82019-09-30 12:51:55 +08008import socket
9import shutil
10import threading
11from test.support import TESTFN, requires, unlink, bigmemtest, find_unused_port
Giampaolo Rodola82374972019-12-10 17:31:06 +080012from test.support import SHORT_TIMEOUT
Benjamin Peterson4fa88fa2009-03-04 00:14:51 +000013import io # C implementation of io
14import _pyio as pyio # Python implementation of io
Trent Mickf29f47b2000-08-11 19:02:59 +000015
Victor Stinner8c663fd2017-11-08 14:44:44 -080016# size of file to create (>2 GiB; 2 GiB == 2,147,483,648 bytes)
Stéphane Wirtel74a8b6e2018-10-18 01:05:04 +020017size = 2_500_000_000
Giampaolo Rodola5bcc6d82019-09-30 12:51:55 +080018TESTFN2 = TESTFN + '2'
19
Guido van Rossuma31ddbb2001-09-10 15:03:18 +000020
Serhiy Storchakac406a122013-07-17 13:42:24 +030021class LargeFileTest:
Christian Heimes77c02eb2008-02-09 02:18:51 +000022
Serhiy Storchakac406a122013-07-17 13:42:24 +030023 def setUp(self):
24 if os.path.exists(TESTFN):
25 mode = 'r+b'
26 else:
27 mode = 'w+b'
28
29 with self.open(TESTFN, mode) as f:
30 current_size = os.fstat(f.fileno())[stat.ST_SIZE]
31 if current_size == size+1:
32 return
33
34 if current_size == 0:
35 f.write(b'z')
36
Christian Heimes77c02eb2008-02-09 02:18:51 +000037 f.seek(0)
38 f.seek(size)
Alexandre Vassalottia351f772008-03-03 02:59:49 +000039 f.write(b'a')
Christian Heimes77c02eb2008-02-09 02:18:51 +000040 f.flush()
Christian Heimes77c02eb2008-02-09 02:18:51 +000041 self.assertEqual(os.fstat(f.fileno())[stat.ST_SIZE], size+1)
Christian Heimes77c02eb2008-02-09 02:18:51 +000042
Serhiy Storchakac406a122013-07-17 13:42:24 +030043 @classmethod
44 def tearDownClass(cls):
45 with cls.open(TESTFN, 'wb'):
46 pass
47 if not os.stat(TESTFN)[stat.ST_SIZE] == 0:
48 raise cls.failureException('File was not truncated by opening '
49 'with mode "wb"')
Giampaolo Rodola5bcc6d82019-09-30 12:51:55 +080050 unlink(TESTFN2)
51
52
53class TestFileMethods(LargeFileTest):
54 """Test that each file function works as expected for large
55 (i.e. > 2 GiB) files.
56 """
Serhiy Storchakac406a122013-07-17 13:42:24 +030057
Stéphane Wirtel74a8b6e2018-10-18 01:05:04 +020058 # _pyio.FileIO.readall() uses a temporary bytearray then casted to bytes,
59 # so memuse=2 is needed
60 @bigmemtest(size=size, memuse=2, dry_run=False)
61 def test_large_read(self, _size):
62 # bpo-24658: Test that a read greater than 2GB does not fail.
63 with self.open(TESTFN, "rb") as f:
64 self.assertEqual(len(f.read()), size + 1)
65 self.assertEqual(f.tell(), size + 1)
66
Christian Heimes77c02eb2008-02-09 02:18:51 +000067 def test_osstat(self):
Christian Heimes77c02eb2008-02-09 02:18:51 +000068 self.assertEqual(os.stat(TESTFN)[stat.ST_SIZE], size+1)
69
70 def test_seek_read(self):
Benjamin Peterson4fa88fa2009-03-04 00:14:51 +000071 with self.open(TESTFN, 'rb') as f:
Christian Heimes77c02eb2008-02-09 02:18:51 +000072 self.assertEqual(f.tell(), 0)
Alexandre Vassalottia351f772008-03-03 02:59:49 +000073 self.assertEqual(f.read(1), b'z')
Christian Heimes77c02eb2008-02-09 02:18:51 +000074 self.assertEqual(f.tell(), 1)
75 f.seek(0)
76 self.assertEqual(f.tell(), 0)
77 f.seek(0, 0)
78 self.assertEqual(f.tell(), 0)
79 f.seek(42)
80 self.assertEqual(f.tell(), 42)
81 f.seek(42, 0)
82 self.assertEqual(f.tell(), 42)
83 f.seek(42, 1)
84 self.assertEqual(f.tell(), 84)
85 f.seek(0, 1)
86 self.assertEqual(f.tell(), 84)
87 f.seek(0, 2) # seek from the end
88 self.assertEqual(f.tell(), size + 1 + 0)
89 f.seek(-10, 2)
90 self.assertEqual(f.tell(), size + 1 - 10)
91 f.seek(-size-1, 2)
92 self.assertEqual(f.tell(), 0)
93 f.seek(size)
94 self.assertEqual(f.tell(), size)
95 # the 'a' that was written at the end of file above
Alexandre Vassalottia351f772008-03-03 02:59:49 +000096 self.assertEqual(f.read(1), b'a')
Christian Heimes77c02eb2008-02-09 02:18:51 +000097 f.seek(-size-1, 1)
Alexandre Vassalottia351f772008-03-03 02:59:49 +000098 self.assertEqual(f.read(1), b'z')
Christian Heimes77c02eb2008-02-09 02:18:51 +000099 self.assertEqual(f.tell(), 1)
Christian Heimes77c02eb2008-02-09 02:18:51 +0000100
101 def test_lseek(self):
Benjamin Peterson4fa88fa2009-03-04 00:14:51 +0000102 with self.open(TESTFN, 'rb') as f:
Christian Heimes77c02eb2008-02-09 02:18:51 +0000103 self.assertEqual(os.lseek(f.fileno(), 0, 0), 0)
104 self.assertEqual(os.lseek(f.fileno(), 42, 0), 42)
105 self.assertEqual(os.lseek(f.fileno(), 42, 1), 84)
106 self.assertEqual(os.lseek(f.fileno(), 0, 1), 84)
107 self.assertEqual(os.lseek(f.fileno(), 0, 2), size+1+0)
108 self.assertEqual(os.lseek(f.fileno(), -10, 2), size+1-10)
109 self.assertEqual(os.lseek(f.fileno(), -size-1, 2), 0)
110 self.assertEqual(os.lseek(f.fileno(), size, 0), size)
111 # the 'a' that was written at the end of file above
Alexandre Vassalottia351f772008-03-03 02:59:49 +0000112 self.assertEqual(f.read(1), b'a')
Christian Heimes77c02eb2008-02-09 02:18:51 +0000113
114 def test_truncate(self):
Benjamin Peterson4fa88fa2009-03-04 00:14:51 +0000115 with self.open(TESTFN, 'r+b') as f:
Christian Heimes180510d2008-03-03 19:15:45 +0000116 if not hasattr(f, 'truncate'):
Serhiy Storchakac406a122013-07-17 13:42:24 +0300117 raise unittest.SkipTest("open().truncate() not available "
118 "on this system")
Christian Heimes77c02eb2008-02-09 02:18:51 +0000119 f.seek(0, 2)
120 # else we've lost track of the true size
121 self.assertEqual(f.tell(), size+1)
122 # Cut it back via seek + truncate with no argument.
123 newsize = size - 10
124 f.seek(newsize)
125 f.truncate()
126 self.assertEqual(f.tell(), newsize) # else pointer moved
127 f.seek(0, 2)
128 self.assertEqual(f.tell(), newsize) # else wasn't truncated
129 # Ensure that truncate(smaller than true size) shrinks
130 # the file.
131 newsize -= 1
132 f.seek(42)
133 f.truncate(newsize)
Antoine Pitrou905a2ff2010-01-31 22:47:27 +0000134 self.assertEqual(f.tell(), 42)
Alexandre Vassalotti77250f42008-05-06 19:48:38 +0000135 f.seek(0, 2)
136 self.assertEqual(f.tell(), newsize)
Christian Heimes77c02eb2008-02-09 02:18:51 +0000137 # XXX truncate(larger than true size) is ill-defined
138 # across platform; cut it waaaaay back
139 f.seek(0)
140 f.truncate(1)
Antoine Pitrou905a2ff2010-01-31 22:47:27 +0000141 self.assertEqual(f.tell(), 0) # else pointer moved
Alexandre Vassalotti77250f42008-05-06 19:48:38 +0000142 f.seek(0)
Christian Heimes77c02eb2008-02-09 02:18:51 +0000143 self.assertEqual(len(f.read()), 1) # else wasn't truncated
Christian Heimes77c02eb2008-02-09 02:18:51 +0000144
Antoine Pitroua28fcfd2009-03-13 23:42:55 +0000145 def test_seekable(self):
146 # Issue #5016; seekable() can return False when the current position
147 # is negative when truncated to an int.
148 for pos in (2**31-1, 2**31, 2**31+1):
149 with self.open(TESTFN, 'rb') as f:
150 f.seek(pos)
Benjamin Petersonc9c0f202009-06-30 23:06:06 +0000151 self.assertTrue(f.seekable())
Antoine Pitroua28fcfd2009-03-13 23:42:55 +0000152
Giampaolo Rodola5bcc6d82019-09-30 12:51:55 +0800153
Giampaolo Rodolab39fb8e2020-02-05 18:20:52 +0100154def skip_no_disk_space(path, required):
155 def decorator(fun):
156 def wrapper(*args, **kwargs):
157 if shutil.disk_usage(os.path.realpath(path)).free < required:
158 hsize = int(required / 1024 / 1024)
159 raise unittest.SkipTest(
160 f"required {hsize} MiB of free disk space")
161 return fun(*args, **kwargs)
162 return wrapper
163 return decorator
164
165
Giampaolo Rodola5bcc6d82019-09-30 12:51:55 +0800166class TestCopyfile(LargeFileTest, unittest.TestCase):
167 open = staticmethod(io.open)
168
Giampaolo Rodolab39fb8e2020-02-05 18:20:52 +0100169 # Exact required disk space would be (size * 2), but let's give it a
170 # bit more tolerance.
171 @skip_no_disk_space(TESTFN, size * 2.5)
Giampaolo Rodola5bcc6d82019-09-30 12:51:55 +0800172 def test_it(self):
173 # Internally shutil.copyfile() can use "fast copy" methods like
174 # os.sendfile().
175 size = os.path.getsize(TESTFN)
176 shutil.copyfile(TESTFN, TESTFN2)
177 self.assertEqual(os.path.getsize(TESTFN2), size)
178 with open(TESTFN2, 'rb') as f:
179 self.assertEqual(f.read(5), b'z\x00\x00\x00\x00')
180 f.seek(size - 5)
181 self.assertEqual(f.read(), b'\x00\x00\x00\x00a')
182
183
184@unittest.skipIf(not hasattr(os, 'sendfile'), 'sendfile not supported')
185class TestSocketSendfile(LargeFileTest, unittest.TestCase):
186 open = staticmethod(io.open)
Giampaolo Rodola82374972019-12-10 17:31:06 +0800187 timeout = SHORT_TIMEOUT
Giampaolo Rodola5bcc6d82019-09-30 12:51:55 +0800188
189 def setUp(self):
190 super().setUp()
191 self.thread = None
192
193 def tearDown(self):
194 super().tearDown()
195 if self.thread is not None:
196 self.thread.join(self.timeout)
197 self.thread = None
198
199 def tcp_server(self, sock):
200 def run(sock):
201 with sock:
202 conn, _ = sock.accept()
Giampaolo Rodola82374972019-12-10 17:31:06 +0800203 conn.settimeout(self.timeout)
Giampaolo Rodola5bcc6d82019-09-30 12:51:55 +0800204 with conn, open(TESTFN2, 'wb') as f:
205 event.wait(self.timeout)
206 while True:
207 chunk = conn.recv(65536)
208 if not chunk:
209 return
210 f.write(chunk)
211
212 event = threading.Event()
213 sock.settimeout(self.timeout)
214 self.thread = threading.Thread(target=run, args=(sock, ))
215 self.thread.start()
216 event.set()
217
Giampaolo Rodolab39fb8e2020-02-05 18:20:52 +0100218 # Exact required disk space would be (size * 2), but let's give it a
219 # bit more tolerance.
220 @skip_no_disk_space(TESTFN, size * 2.5)
Giampaolo Rodola5bcc6d82019-09-30 12:51:55 +0800221 def test_it(self):
222 port = find_unused_port()
223 with socket.create_server(("", port)) as sock:
224 self.tcp_server(sock)
225 with socket.create_connection(("127.0.0.1", port)) as client:
226 with open(TESTFN, 'rb') as f:
227 client.sendfile(f)
228 self.tearDown()
229
230 size = os.path.getsize(TESTFN)
231 self.assertEqual(os.path.getsize(TESTFN2), size)
232 with open(TESTFN2, 'rb') as f:
233 self.assertEqual(f.read(5), b'z\x00\x00\x00\x00')
234 f.seek(size - 5)
235 self.assertEqual(f.read(), b'\x00\x00\x00\x00a')
236
237
Serhiy Storchakac406a122013-07-17 13:42:24 +0300238def setUpModule():
239 try:
240 import signal
241 # The default handler for SIGXFSZ is to abort the process.
242 # By ignoring it, system calls exceeding the file size resource
243 # limit will raise OSError instead of crashing the interpreter.
244 signal.signal(signal.SIGXFSZ, signal.SIG_IGN)
245 except (ImportError, AttributeError):
246 pass
Antoine Pitroua28fcfd2009-03-13 23:42:55 +0000247
Mike53f7a7c2017-12-14 14:04:53 +0300248 # On Windows and Mac OSX this test consumes large resources; It
Victor Stinner8c663fd2017-11-08 14:44:44 -0800249 # takes a long time to build the >2 GiB file and takes >2 GiB of disk
Christian Heimes77c02eb2008-02-09 02:18:51 +0000250 # space therefore the resource must be enabled to run this test.
251 # If not, nothing after this line stanza will be executed.
Victor Stinner937ee9e2018-06-26 02:11:06 +0200252 if sys.platform[:3] == 'win' or sys.platform == 'darwin':
Christian Heimes77c02eb2008-02-09 02:18:51 +0000253 requires('largefile',
254 'test requires %s bytes and a long time to run' % str(size))
Guido van Rossum47f40342001-09-10 13:34:12 +0000255 else:
Christian Heimes77c02eb2008-02-09 02:18:51 +0000256 # Only run if the current filesystem supports large files.
257 # (Skip this test on Windows, since we now always support
258 # large files.)
Benjamin Peterson4fa88fa2009-03-04 00:14:51 +0000259 f = open(TESTFN, 'wb', buffering=0)
Christian Heimes77c02eb2008-02-09 02:18:51 +0000260 try:
261 # 2**31 == 2147483648
262 f.seek(2147483649)
Serhiy Storchakac406a122013-07-17 13:42:24 +0300263 # Seeking is not enough of a test: you must write and flush, too!
Alexandre Vassalottia351f772008-03-03 02:59:49 +0000264 f.write(b'x')
Christian Heimes77c02eb2008-02-09 02:18:51 +0000265 f.flush()
Andrew Svetlovf7a17b42012-12-25 16:47:37 +0200266 except (OSError, OverflowError):
Serhiy Storchakac406a122013-07-17 13:42:24 +0300267 raise unittest.SkipTest("filesystem does not have "
268 "largefile support")
269 finally:
Christian Heimes77c02eb2008-02-09 02:18:51 +0000270 f.close()
271 unlink(TESTFN)
Serhiy Storchakac406a122013-07-17 13:42:24 +0300272
273
Giampaolo Rodola5bcc6d82019-09-30 12:51:55 +0800274class CLargeFileTest(TestFileMethods, unittest.TestCase):
Serhiy Storchakac406a122013-07-17 13:42:24 +0300275 open = staticmethod(io.open)
276
Giampaolo Rodola5bcc6d82019-09-30 12:51:55 +0800277
278class PyLargeFileTest(TestFileMethods, unittest.TestCase):
Serhiy Storchakac406a122013-07-17 13:42:24 +0300279 open = staticmethod(pyio.open)
280
Giampaolo Rodola5bcc6d82019-09-30 12:51:55 +0800281
Serhiy Storchakac406a122013-07-17 13:42:24 +0300282def tearDownModule():
283 unlink(TESTFN)
Giampaolo Rodola5bcc6d82019-09-30 12:51:55 +0800284 unlink(TESTFN2)
285
Guido van Rossum47f40342001-09-10 13:34:12 +0000286
Christian Heimes77c02eb2008-02-09 02:18:51 +0000287if __name__ == '__main__':
Serhiy Storchakac406a122013-07-17 13:42:24 +0300288 unittest.main()