blob: a99b4ba89acbf1e566274d8fb4464bf1027157f4 [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
Serhiy Storchaka16994912020-04-25 10:06:29 +030011from test.support import TESTFN, requires, unlink, bigmemtest
Giampaolo Rodola82374972019-12-10 17:31:06 +080012from test.support import SHORT_TIMEOUT
Serhiy Storchaka16994912020-04-25 10:06:29 +030013from test.support import socket_helper
Benjamin Peterson4fa88fa2009-03-04 00:14:51 +000014import io # C implementation of io
15import _pyio as pyio # Python implementation of io
Trent Mickf29f47b2000-08-11 19:02:59 +000016
Victor Stinner8c663fd2017-11-08 14:44:44 -080017# size of file to create (>2 GiB; 2 GiB == 2,147,483,648 bytes)
Stéphane Wirtel74a8b6e2018-10-18 01:05:04 +020018size = 2_500_000_000
Giampaolo Rodola5bcc6d82019-09-30 12:51:55 +080019TESTFN2 = TESTFN + '2'
20
Guido van Rossuma31ddbb2001-09-10 15:03:18 +000021
Serhiy Storchakac406a122013-07-17 13:42:24 +030022class LargeFileTest:
Christian Heimes77c02eb2008-02-09 02:18:51 +000023
Serhiy Storchakac406a122013-07-17 13:42:24 +030024 def setUp(self):
25 if os.path.exists(TESTFN):
26 mode = 'r+b'
27 else:
28 mode = 'w+b'
29
30 with self.open(TESTFN, mode) as f:
31 current_size = os.fstat(f.fileno())[stat.ST_SIZE]
32 if current_size == size+1:
33 return
34
35 if current_size == 0:
36 f.write(b'z')
37
Christian Heimes77c02eb2008-02-09 02:18:51 +000038 f.seek(0)
39 f.seek(size)
Alexandre Vassalottia351f772008-03-03 02:59:49 +000040 f.write(b'a')
Christian Heimes77c02eb2008-02-09 02:18:51 +000041 f.flush()
Christian Heimes77c02eb2008-02-09 02:18:51 +000042 self.assertEqual(os.fstat(f.fileno())[stat.ST_SIZE], size+1)
Christian Heimes77c02eb2008-02-09 02:18:51 +000043
Serhiy Storchakac406a122013-07-17 13:42:24 +030044 @classmethod
45 def tearDownClass(cls):
46 with cls.open(TESTFN, 'wb'):
47 pass
48 if not os.stat(TESTFN)[stat.ST_SIZE] == 0:
49 raise cls.failureException('File was not truncated by opening '
50 'with mode "wb"')
Giampaolo Rodola5bcc6d82019-09-30 12:51:55 +080051 unlink(TESTFN2)
52
53
54class TestFileMethods(LargeFileTest):
55 """Test that each file function works as expected for large
56 (i.e. > 2 GiB) files.
57 """
Serhiy Storchakac406a122013-07-17 13:42:24 +030058
Stéphane Wirtel74a8b6e2018-10-18 01:05:04 +020059 # _pyio.FileIO.readall() uses a temporary bytearray then casted to bytes,
60 # so memuse=2 is needed
61 @bigmemtest(size=size, memuse=2, dry_run=False)
62 def test_large_read(self, _size):
63 # bpo-24658: Test that a read greater than 2GB does not fail.
64 with self.open(TESTFN, "rb") as f:
65 self.assertEqual(len(f.read()), size + 1)
66 self.assertEqual(f.tell(), size + 1)
67
Christian Heimes77c02eb2008-02-09 02:18:51 +000068 def test_osstat(self):
Christian Heimes77c02eb2008-02-09 02:18:51 +000069 self.assertEqual(os.stat(TESTFN)[stat.ST_SIZE], size+1)
70
71 def test_seek_read(self):
Benjamin Peterson4fa88fa2009-03-04 00:14:51 +000072 with self.open(TESTFN, 'rb') as f:
Christian Heimes77c02eb2008-02-09 02:18:51 +000073 self.assertEqual(f.tell(), 0)
Alexandre Vassalottia351f772008-03-03 02:59:49 +000074 self.assertEqual(f.read(1), b'z')
Christian Heimes77c02eb2008-02-09 02:18:51 +000075 self.assertEqual(f.tell(), 1)
76 f.seek(0)
77 self.assertEqual(f.tell(), 0)
78 f.seek(0, 0)
79 self.assertEqual(f.tell(), 0)
80 f.seek(42)
81 self.assertEqual(f.tell(), 42)
82 f.seek(42, 0)
83 self.assertEqual(f.tell(), 42)
84 f.seek(42, 1)
85 self.assertEqual(f.tell(), 84)
86 f.seek(0, 1)
87 self.assertEqual(f.tell(), 84)
88 f.seek(0, 2) # seek from the end
89 self.assertEqual(f.tell(), size + 1 + 0)
90 f.seek(-10, 2)
91 self.assertEqual(f.tell(), size + 1 - 10)
92 f.seek(-size-1, 2)
93 self.assertEqual(f.tell(), 0)
94 f.seek(size)
95 self.assertEqual(f.tell(), size)
96 # the 'a' that was written at the end of file above
Alexandre Vassalottia351f772008-03-03 02:59:49 +000097 self.assertEqual(f.read(1), b'a')
Christian Heimes77c02eb2008-02-09 02:18:51 +000098 f.seek(-size-1, 1)
Alexandre Vassalottia351f772008-03-03 02:59:49 +000099 self.assertEqual(f.read(1), b'z')
Christian Heimes77c02eb2008-02-09 02:18:51 +0000100 self.assertEqual(f.tell(), 1)
Christian Heimes77c02eb2008-02-09 02:18:51 +0000101
102 def test_lseek(self):
Benjamin Peterson4fa88fa2009-03-04 00:14:51 +0000103 with self.open(TESTFN, 'rb') as f:
Christian Heimes77c02eb2008-02-09 02:18:51 +0000104 self.assertEqual(os.lseek(f.fileno(), 0, 0), 0)
105 self.assertEqual(os.lseek(f.fileno(), 42, 0), 42)
106 self.assertEqual(os.lseek(f.fileno(), 42, 1), 84)
107 self.assertEqual(os.lseek(f.fileno(), 0, 1), 84)
108 self.assertEqual(os.lseek(f.fileno(), 0, 2), size+1+0)
109 self.assertEqual(os.lseek(f.fileno(), -10, 2), size+1-10)
110 self.assertEqual(os.lseek(f.fileno(), -size-1, 2), 0)
111 self.assertEqual(os.lseek(f.fileno(), size, 0), size)
112 # the 'a' that was written at the end of file above
Alexandre Vassalottia351f772008-03-03 02:59:49 +0000113 self.assertEqual(f.read(1), b'a')
Christian Heimes77c02eb2008-02-09 02:18:51 +0000114
115 def test_truncate(self):
Benjamin Peterson4fa88fa2009-03-04 00:14:51 +0000116 with self.open(TESTFN, 'r+b') as f:
Christian Heimes180510d2008-03-03 19:15:45 +0000117 if not hasattr(f, 'truncate'):
Serhiy Storchakac406a122013-07-17 13:42:24 +0300118 raise unittest.SkipTest("open().truncate() not available "
119 "on this system")
Christian Heimes77c02eb2008-02-09 02:18:51 +0000120 f.seek(0, 2)
121 # else we've lost track of the true size
122 self.assertEqual(f.tell(), size+1)
123 # Cut it back via seek + truncate with no argument.
124 newsize = size - 10
125 f.seek(newsize)
126 f.truncate()
127 self.assertEqual(f.tell(), newsize) # else pointer moved
128 f.seek(0, 2)
129 self.assertEqual(f.tell(), newsize) # else wasn't truncated
130 # Ensure that truncate(smaller than true size) shrinks
131 # the file.
132 newsize -= 1
133 f.seek(42)
134 f.truncate(newsize)
Antoine Pitrou905a2ff2010-01-31 22:47:27 +0000135 self.assertEqual(f.tell(), 42)
Alexandre Vassalotti77250f42008-05-06 19:48:38 +0000136 f.seek(0, 2)
137 self.assertEqual(f.tell(), newsize)
Christian Heimes77c02eb2008-02-09 02:18:51 +0000138 # XXX truncate(larger than true size) is ill-defined
139 # across platform; cut it waaaaay back
140 f.seek(0)
141 f.truncate(1)
Antoine Pitrou905a2ff2010-01-31 22:47:27 +0000142 self.assertEqual(f.tell(), 0) # else pointer moved
Alexandre Vassalotti77250f42008-05-06 19:48:38 +0000143 f.seek(0)
Christian Heimes77c02eb2008-02-09 02:18:51 +0000144 self.assertEqual(len(f.read()), 1) # else wasn't truncated
Christian Heimes77c02eb2008-02-09 02:18:51 +0000145
Antoine Pitroua28fcfd2009-03-13 23:42:55 +0000146 def test_seekable(self):
147 # Issue #5016; seekable() can return False when the current position
148 # is negative when truncated to an int.
149 for pos in (2**31-1, 2**31, 2**31+1):
150 with self.open(TESTFN, 'rb') as f:
151 f.seek(pos)
Benjamin Petersonc9c0f202009-06-30 23:06:06 +0000152 self.assertTrue(f.seekable())
Antoine Pitroua28fcfd2009-03-13 23:42:55 +0000153
Giampaolo Rodola5bcc6d82019-09-30 12:51:55 +0800154
Giampaolo Rodolab39fb8e2020-02-05 18:20:52 +0100155def skip_no_disk_space(path, required):
156 def decorator(fun):
157 def wrapper(*args, **kwargs):
158 if shutil.disk_usage(os.path.realpath(path)).free < required:
159 hsize = int(required / 1024 / 1024)
160 raise unittest.SkipTest(
161 f"required {hsize} MiB of free disk space")
162 return fun(*args, **kwargs)
163 return wrapper
164 return decorator
165
166
Giampaolo Rodola5bcc6d82019-09-30 12:51:55 +0800167class TestCopyfile(LargeFileTest, unittest.TestCase):
168 open = staticmethod(io.open)
169
Giampaolo Rodolab39fb8e2020-02-05 18:20:52 +0100170 # Exact required disk space would be (size * 2), but let's give it a
171 # bit more tolerance.
172 @skip_no_disk_space(TESTFN, size * 2.5)
Giampaolo Rodola5bcc6d82019-09-30 12:51:55 +0800173 def test_it(self):
174 # Internally shutil.copyfile() can use "fast copy" methods like
175 # os.sendfile().
176 size = os.path.getsize(TESTFN)
177 shutil.copyfile(TESTFN, TESTFN2)
178 self.assertEqual(os.path.getsize(TESTFN2), size)
179 with open(TESTFN2, 'rb') as f:
180 self.assertEqual(f.read(5), b'z\x00\x00\x00\x00')
181 f.seek(size - 5)
182 self.assertEqual(f.read(), b'\x00\x00\x00\x00a')
183
184
185@unittest.skipIf(not hasattr(os, 'sendfile'), 'sendfile not supported')
186class TestSocketSendfile(LargeFileTest, unittest.TestCase):
187 open = staticmethod(io.open)
Giampaolo Rodola82374972019-12-10 17:31:06 +0800188 timeout = SHORT_TIMEOUT
Giampaolo Rodola5bcc6d82019-09-30 12:51:55 +0800189
190 def setUp(self):
191 super().setUp()
192 self.thread = None
193
194 def tearDown(self):
195 super().tearDown()
196 if self.thread is not None:
197 self.thread.join(self.timeout)
198 self.thread = None
199
200 def tcp_server(self, sock):
201 def run(sock):
202 with sock:
203 conn, _ = sock.accept()
Giampaolo Rodola82374972019-12-10 17:31:06 +0800204 conn.settimeout(self.timeout)
Giampaolo Rodola5bcc6d82019-09-30 12:51:55 +0800205 with conn, open(TESTFN2, 'wb') as f:
206 event.wait(self.timeout)
207 while True:
208 chunk = conn.recv(65536)
209 if not chunk:
210 return
211 f.write(chunk)
212
213 event = threading.Event()
214 sock.settimeout(self.timeout)
215 self.thread = threading.Thread(target=run, args=(sock, ))
216 self.thread.start()
217 event.set()
218
Giampaolo Rodolab39fb8e2020-02-05 18:20:52 +0100219 # Exact required disk space would be (size * 2), but let's give it a
220 # bit more tolerance.
221 @skip_no_disk_space(TESTFN, size * 2.5)
Giampaolo Rodola5bcc6d82019-09-30 12:51:55 +0800222 def test_it(self):
Serhiy Storchaka16994912020-04-25 10:06:29 +0300223 port = socket_helper.find_unused_port()
Giampaolo Rodola5bcc6d82019-09-30 12:51:55 +0800224 with socket.create_server(("", port)) as sock:
225 self.tcp_server(sock)
226 with socket.create_connection(("127.0.0.1", port)) as client:
227 with open(TESTFN, 'rb') as f:
228 client.sendfile(f)
229 self.tearDown()
230
231 size = os.path.getsize(TESTFN)
232 self.assertEqual(os.path.getsize(TESTFN2), size)
233 with open(TESTFN2, 'rb') as f:
234 self.assertEqual(f.read(5), b'z\x00\x00\x00\x00')
235 f.seek(size - 5)
236 self.assertEqual(f.read(), b'\x00\x00\x00\x00a')
237
238
Serhiy Storchakac406a122013-07-17 13:42:24 +0300239def setUpModule():
240 try:
241 import signal
242 # The default handler for SIGXFSZ is to abort the process.
243 # By ignoring it, system calls exceeding the file size resource
244 # limit will raise OSError instead of crashing the interpreter.
245 signal.signal(signal.SIGXFSZ, signal.SIG_IGN)
246 except (ImportError, AttributeError):
247 pass
Antoine Pitroua28fcfd2009-03-13 23:42:55 +0000248
Mike53f7a7c2017-12-14 14:04:53 +0300249 # On Windows and Mac OSX this test consumes large resources; It
Victor Stinner8c663fd2017-11-08 14:44:44 -0800250 # takes a long time to build the >2 GiB file and takes >2 GiB of disk
Christian Heimes77c02eb2008-02-09 02:18:51 +0000251 # space therefore the resource must be enabled to run this test.
252 # If not, nothing after this line stanza will be executed.
Victor Stinner937ee9e2018-06-26 02:11:06 +0200253 if sys.platform[:3] == 'win' or sys.platform == 'darwin':
Christian Heimes77c02eb2008-02-09 02:18:51 +0000254 requires('largefile',
255 'test requires %s bytes and a long time to run' % str(size))
Guido van Rossum47f40342001-09-10 13:34:12 +0000256 else:
Christian Heimes77c02eb2008-02-09 02:18:51 +0000257 # Only run if the current filesystem supports large files.
258 # (Skip this test on Windows, since we now always support
259 # large files.)
Benjamin Peterson4fa88fa2009-03-04 00:14:51 +0000260 f = open(TESTFN, 'wb', buffering=0)
Christian Heimes77c02eb2008-02-09 02:18:51 +0000261 try:
262 # 2**31 == 2147483648
263 f.seek(2147483649)
Serhiy Storchakac406a122013-07-17 13:42:24 +0300264 # Seeking is not enough of a test: you must write and flush, too!
Alexandre Vassalottia351f772008-03-03 02:59:49 +0000265 f.write(b'x')
Christian Heimes77c02eb2008-02-09 02:18:51 +0000266 f.flush()
Andrew Svetlovf7a17b42012-12-25 16:47:37 +0200267 except (OSError, OverflowError):
Serhiy Storchakac406a122013-07-17 13:42:24 +0300268 raise unittest.SkipTest("filesystem does not have "
269 "largefile support")
270 finally:
Christian Heimes77c02eb2008-02-09 02:18:51 +0000271 f.close()
272 unlink(TESTFN)
Serhiy Storchakac406a122013-07-17 13:42:24 +0300273
274
Giampaolo Rodola5bcc6d82019-09-30 12:51:55 +0800275class CLargeFileTest(TestFileMethods, unittest.TestCase):
Serhiy Storchakac406a122013-07-17 13:42:24 +0300276 open = staticmethod(io.open)
277
Giampaolo Rodola5bcc6d82019-09-30 12:51:55 +0800278
279class PyLargeFileTest(TestFileMethods, unittest.TestCase):
Serhiy Storchakac406a122013-07-17 13:42:24 +0300280 open = staticmethod(pyio.open)
281
Giampaolo Rodola5bcc6d82019-09-30 12:51:55 +0800282
Serhiy Storchakac406a122013-07-17 13:42:24 +0300283def tearDownModule():
284 unlink(TESTFN)
Giampaolo Rodola5bcc6d82019-09-30 12:51:55 +0800285 unlink(TESTFN2)
286
Guido van Rossum47f40342001-09-10 13:34:12 +0000287
Christian Heimes77c02eb2008-02-09 02:18:51 +0000288if __name__ == '__main__':
Serhiy Storchakac406a122013-07-17 13:42:24 +0300289 unittest.main()