blob: 8f6bec16200534ae3686284b03e56a5cd1734265 [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
Hai Shibb0424b2020-08-04 00:47:42 +080011from test.support import requires, 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
Hai Shibb0424b2020-08-04 00:47:42 +080014from test.support.os_helper import TESTFN, unlink
Benjamin Peterson4fa88fa2009-03-04 00:14:51 +000015import io # C implementation of io
16import _pyio as pyio # Python implementation of io
Trent Mickf29f47b2000-08-11 19:02:59 +000017
Victor Stinner8c663fd2017-11-08 14:44:44 -080018# size of file to create (>2 GiB; 2 GiB == 2,147,483,648 bytes)
Stéphane Wirtel74a8b6e2018-10-18 01:05:04 +020019size = 2_500_000_000
Giampaolo Rodola5bcc6d82019-09-30 12:51:55 +080020TESTFN2 = TESTFN + '2'
21
Guido van Rossuma31ddbb2001-09-10 15:03:18 +000022
Serhiy Storchakac406a122013-07-17 13:42:24 +030023class LargeFileTest:
Christian Heimes77c02eb2008-02-09 02:18:51 +000024
Serhiy Storchakac406a122013-07-17 13:42:24 +030025 def setUp(self):
26 if os.path.exists(TESTFN):
27 mode = 'r+b'
28 else:
29 mode = 'w+b'
30
31 with self.open(TESTFN, mode) as f:
32 current_size = os.fstat(f.fileno())[stat.ST_SIZE]
33 if current_size == size+1:
34 return
35
36 if current_size == 0:
37 f.write(b'z')
38
Christian Heimes77c02eb2008-02-09 02:18:51 +000039 f.seek(0)
40 f.seek(size)
Alexandre Vassalottia351f772008-03-03 02:59:49 +000041 f.write(b'a')
Christian Heimes77c02eb2008-02-09 02:18:51 +000042 f.flush()
Christian Heimes77c02eb2008-02-09 02:18:51 +000043 self.assertEqual(os.fstat(f.fileno())[stat.ST_SIZE], size+1)
Christian Heimes77c02eb2008-02-09 02:18:51 +000044
Serhiy Storchakac406a122013-07-17 13:42:24 +030045 @classmethod
46 def tearDownClass(cls):
47 with cls.open(TESTFN, 'wb'):
48 pass
49 if not os.stat(TESTFN)[stat.ST_SIZE] == 0:
50 raise cls.failureException('File was not truncated by opening '
51 'with mode "wb"')
Giampaolo Rodola5bcc6d82019-09-30 12:51:55 +080052 unlink(TESTFN2)
53
54
55class TestFileMethods(LargeFileTest):
56 """Test that each file function works as expected for large
57 (i.e. > 2 GiB) files.
58 """
Serhiy Storchakac406a122013-07-17 13:42:24 +030059
Stéphane Wirtel74a8b6e2018-10-18 01:05:04 +020060 # _pyio.FileIO.readall() uses a temporary bytearray then casted to bytes,
61 # so memuse=2 is needed
62 @bigmemtest(size=size, memuse=2, dry_run=False)
63 def test_large_read(self, _size):
64 # bpo-24658: Test that a read greater than 2GB does not fail.
65 with self.open(TESTFN, "rb") as f:
66 self.assertEqual(len(f.read()), size + 1)
67 self.assertEqual(f.tell(), size + 1)
68
Christian Heimes77c02eb2008-02-09 02:18:51 +000069 def test_osstat(self):
Christian Heimes77c02eb2008-02-09 02:18:51 +000070 self.assertEqual(os.stat(TESTFN)[stat.ST_SIZE], size+1)
71
72 def test_seek_read(self):
Benjamin Peterson4fa88fa2009-03-04 00:14:51 +000073 with self.open(TESTFN, 'rb') as f:
Christian Heimes77c02eb2008-02-09 02:18:51 +000074 self.assertEqual(f.tell(), 0)
Alexandre Vassalottia351f772008-03-03 02:59:49 +000075 self.assertEqual(f.read(1), b'z')
Christian Heimes77c02eb2008-02-09 02:18:51 +000076 self.assertEqual(f.tell(), 1)
77 f.seek(0)
78 self.assertEqual(f.tell(), 0)
79 f.seek(0, 0)
80 self.assertEqual(f.tell(), 0)
81 f.seek(42)
82 self.assertEqual(f.tell(), 42)
83 f.seek(42, 0)
84 self.assertEqual(f.tell(), 42)
85 f.seek(42, 1)
86 self.assertEqual(f.tell(), 84)
87 f.seek(0, 1)
88 self.assertEqual(f.tell(), 84)
89 f.seek(0, 2) # seek from the end
90 self.assertEqual(f.tell(), size + 1 + 0)
91 f.seek(-10, 2)
92 self.assertEqual(f.tell(), size + 1 - 10)
93 f.seek(-size-1, 2)
94 self.assertEqual(f.tell(), 0)
95 f.seek(size)
96 self.assertEqual(f.tell(), size)
97 # the 'a' that was written at the end of file above
Alexandre Vassalottia351f772008-03-03 02:59:49 +000098 self.assertEqual(f.read(1), b'a')
Christian Heimes77c02eb2008-02-09 02:18:51 +000099 f.seek(-size-1, 1)
Alexandre Vassalottia351f772008-03-03 02:59:49 +0000100 self.assertEqual(f.read(1), b'z')
Christian Heimes77c02eb2008-02-09 02:18:51 +0000101 self.assertEqual(f.tell(), 1)
Christian Heimes77c02eb2008-02-09 02:18:51 +0000102
103 def test_lseek(self):
Benjamin Peterson4fa88fa2009-03-04 00:14:51 +0000104 with self.open(TESTFN, 'rb') as f:
Christian Heimes77c02eb2008-02-09 02:18:51 +0000105 self.assertEqual(os.lseek(f.fileno(), 0, 0), 0)
106 self.assertEqual(os.lseek(f.fileno(), 42, 0), 42)
107 self.assertEqual(os.lseek(f.fileno(), 42, 1), 84)
108 self.assertEqual(os.lseek(f.fileno(), 0, 1), 84)
109 self.assertEqual(os.lseek(f.fileno(), 0, 2), size+1+0)
110 self.assertEqual(os.lseek(f.fileno(), -10, 2), size+1-10)
111 self.assertEqual(os.lseek(f.fileno(), -size-1, 2), 0)
112 self.assertEqual(os.lseek(f.fileno(), size, 0), size)
113 # the 'a' that was written at the end of file above
Alexandre Vassalottia351f772008-03-03 02:59:49 +0000114 self.assertEqual(f.read(1), b'a')
Christian Heimes77c02eb2008-02-09 02:18:51 +0000115
116 def test_truncate(self):
Benjamin Peterson4fa88fa2009-03-04 00:14:51 +0000117 with self.open(TESTFN, 'r+b') as f:
Christian Heimes180510d2008-03-03 19:15:45 +0000118 if not hasattr(f, 'truncate'):
Serhiy Storchakac406a122013-07-17 13:42:24 +0300119 raise unittest.SkipTest("open().truncate() not available "
120 "on this system")
Christian Heimes77c02eb2008-02-09 02:18:51 +0000121 f.seek(0, 2)
122 # else we've lost track of the true size
123 self.assertEqual(f.tell(), size+1)
124 # Cut it back via seek + truncate with no argument.
125 newsize = size - 10
126 f.seek(newsize)
127 f.truncate()
128 self.assertEqual(f.tell(), newsize) # else pointer moved
129 f.seek(0, 2)
130 self.assertEqual(f.tell(), newsize) # else wasn't truncated
131 # Ensure that truncate(smaller than true size) shrinks
132 # the file.
133 newsize -= 1
134 f.seek(42)
135 f.truncate(newsize)
Antoine Pitrou905a2ff2010-01-31 22:47:27 +0000136 self.assertEqual(f.tell(), 42)
Alexandre Vassalotti77250f42008-05-06 19:48:38 +0000137 f.seek(0, 2)
138 self.assertEqual(f.tell(), newsize)
Christian Heimes77c02eb2008-02-09 02:18:51 +0000139 # XXX truncate(larger than true size) is ill-defined
140 # across platform; cut it waaaaay back
141 f.seek(0)
142 f.truncate(1)
Antoine Pitrou905a2ff2010-01-31 22:47:27 +0000143 self.assertEqual(f.tell(), 0) # else pointer moved
Alexandre Vassalotti77250f42008-05-06 19:48:38 +0000144 f.seek(0)
Christian Heimes77c02eb2008-02-09 02:18:51 +0000145 self.assertEqual(len(f.read()), 1) # else wasn't truncated
Christian Heimes77c02eb2008-02-09 02:18:51 +0000146
Antoine Pitroua28fcfd2009-03-13 23:42:55 +0000147 def test_seekable(self):
148 # Issue #5016; seekable() can return False when the current position
149 # is negative when truncated to an int.
150 for pos in (2**31-1, 2**31, 2**31+1):
151 with self.open(TESTFN, 'rb') as f:
152 f.seek(pos)
Benjamin Petersonc9c0f202009-06-30 23:06:06 +0000153 self.assertTrue(f.seekable())
Antoine Pitroua28fcfd2009-03-13 23:42:55 +0000154
Giampaolo Rodola5bcc6d82019-09-30 12:51:55 +0800155
Giampaolo Rodolab39fb8e2020-02-05 18:20:52 +0100156def skip_no_disk_space(path, required):
157 def decorator(fun):
158 def wrapper(*args, **kwargs):
159 if shutil.disk_usage(os.path.realpath(path)).free < required:
160 hsize = int(required / 1024 / 1024)
161 raise unittest.SkipTest(
162 f"required {hsize} MiB of free disk space")
163 return fun(*args, **kwargs)
164 return wrapper
165 return decorator
166
167
Giampaolo Rodola5bcc6d82019-09-30 12:51:55 +0800168class TestCopyfile(LargeFileTest, unittest.TestCase):
169 open = staticmethod(io.open)
170
Giampaolo Rodolab39fb8e2020-02-05 18:20:52 +0100171 # Exact required disk space would be (size * 2), but let's give it a
172 # bit more tolerance.
173 @skip_no_disk_space(TESTFN, size * 2.5)
Giampaolo Rodola5bcc6d82019-09-30 12:51:55 +0800174 def test_it(self):
175 # Internally shutil.copyfile() can use "fast copy" methods like
176 # os.sendfile().
177 size = os.path.getsize(TESTFN)
178 shutil.copyfile(TESTFN, TESTFN2)
179 self.assertEqual(os.path.getsize(TESTFN2), size)
180 with open(TESTFN2, 'rb') as f:
181 self.assertEqual(f.read(5), b'z\x00\x00\x00\x00')
182 f.seek(size - 5)
183 self.assertEqual(f.read(), b'\x00\x00\x00\x00a')
184
185
186@unittest.skipIf(not hasattr(os, 'sendfile'), 'sendfile not supported')
187class TestSocketSendfile(LargeFileTest, unittest.TestCase):
188 open = staticmethod(io.open)
Giampaolo Rodola82374972019-12-10 17:31:06 +0800189 timeout = SHORT_TIMEOUT
Giampaolo Rodola5bcc6d82019-09-30 12:51:55 +0800190
191 def setUp(self):
192 super().setUp()
193 self.thread = None
194
195 def tearDown(self):
196 super().tearDown()
197 if self.thread is not None:
198 self.thread.join(self.timeout)
199 self.thread = None
200
201 def tcp_server(self, sock):
202 def run(sock):
203 with sock:
204 conn, _ = sock.accept()
Giampaolo Rodola82374972019-12-10 17:31:06 +0800205 conn.settimeout(self.timeout)
Giampaolo Rodola5bcc6d82019-09-30 12:51:55 +0800206 with conn, open(TESTFN2, 'wb') as f:
207 event.wait(self.timeout)
208 while True:
209 chunk = conn.recv(65536)
210 if not chunk:
211 return
212 f.write(chunk)
213
214 event = threading.Event()
215 sock.settimeout(self.timeout)
216 self.thread = threading.Thread(target=run, args=(sock, ))
217 self.thread.start()
218 event.set()
219
Giampaolo Rodolab39fb8e2020-02-05 18:20:52 +0100220 # Exact required disk space would be (size * 2), but let's give it a
221 # bit more tolerance.
222 @skip_no_disk_space(TESTFN, size * 2.5)
Giampaolo Rodola5bcc6d82019-09-30 12:51:55 +0800223 def test_it(self):
Serhiy Storchaka16994912020-04-25 10:06:29 +0300224 port = socket_helper.find_unused_port()
Giampaolo Rodola5bcc6d82019-09-30 12:51:55 +0800225 with socket.create_server(("", port)) as sock:
226 self.tcp_server(sock)
227 with socket.create_connection(("127.0.0.1", port)) as client:
228 with open(TESTFN, 'rb') as f:
229 client.sendfile(f)
230 self.tearDown()
231
232 size = os.path.getsize(TESTFN)
233 self.assertEqual(os.path.getsize(TESTFN2), size)
234 with open(TESTFN2, 'rb') as f:
235 self.assertEqual(f.read(5), b'z\x00\x00\x00\x00')
236 f.seek(size - 5)
237 self.assertEqual(f.read(), b'\x00\x00\x00\x00a')
238
239
Serhiy Storchakac406a122013-07-17 13:42:24 +0300240def setUpModule():
241 try:
242 import signal
243 # The default handler for SIGXFSZ is to abort the process.
244 # By ignoring it, system calls exceeding the file size resource
245 # limit will raise OSError instead of crashing the interpreter.
246 signal.signal(signal.SIGXFSZ, signal.SIG_IGN)
247 except (ImportError, AttributeError):
248 pass
Antoine Pitroua28fcfd2009-03-13 23:42:55 +0000249
Mike53f7a7c2017-12-14 14:04:53 +0300250 # On Windows and Mac OSX this test consumes large resources; It
Victor Stinner8c663fd2017-11-08 14:44:44 -0800251 # takes a long time to build the >2 GiB file and takes >2 GiB of disk
Christian Heimes77c02eb2008-02-09 02:18:51 +0000252 # space therefore the resource must be enabled to run this test.
253 # If not, nothing after this line stanza will be executed.
Victor Stinner937ee9e2018-06-26 02:11:06 +0200254 if sys.platform[:3] == 'win' or sys.platform == 'darwin':
Christian Heimes77c02eb2008-02-09 02:18:51 +0000255 requires('largefile',
256 'test requires %s bytes and a long time to run' % str(size))
Guido van Rossum47f40342001-09-10 13:34:12 +0000257 else:
Christian Heimes77c02eb2008-02-09 02:18:51 +0000258 # Only run if the current filesystem supports large files.
259 # (Skip this test on Windows, since we now always support
260 # large files.)
Benjamin Peterson4fa88fa2009-03-04 00:14:51 +0000261 f = open(TESTFN, 'wb', buffering=0)
Christian Heimes77c02eb2008-02-09 02:18:51 +0000262 try:
263 # 2**31 == 2147483648
264 f.seek(2147483649)
Serhiy Storchakac406a122013-07-17 13:42:24 +0300265 # Seeking is not enough of a test: you must write and flush, too!
Alexandre Vassalottia351f772008-03-03 02:59:49 +0000266 f.write(b'x')
Christian Heimes77c02eb2008-02-09 02:18:51 +0000267 f.flush()
Andrew Svetlovf7a17b42012-12-25 16:47:37 +0200268 except (OSError, OverflowError):
Serhiy Storchakac406a122013-07-17 13:42:24 +0300269 raise unittest.SkipTest("filesystem does not have "
270 "largefile support")
271 finally:
Christian Heimes77c02eb2008-02-09 02:18:51 +0000272 f.close()
273 unlink(TESTFN)
Serhiy Storchakac406a122013-07-17 13:42:24 +0300274
275
Giampaolo Rodola5bcc6d82019-09-30 12:51:55 +0800276class CLargeFileTest(TestFileMethods, unittest.TestCase):
Serhiy Storchakac406a122013-07-17 13:42:24 +0300277 open = staticmethod(io.open)
278
Giampaolo Rodola5bcc6d82019-09-30 12:51:55 +0800279
280class PyLargeFileTest(TestFileMethods, unittest.TestCase):
Serhiy Storchakac406a122013-07-17 13:42:24 +0300281 open = staticmethod(pyio.open)
282
Giampaolo Rodola5bcc6d82019-09-30 12:51:55 +0800283
Serhiy Storchakac406a122013-07-17 13:42:24 +0300284def tearDownModule():
285 unlink(TESTFN)
Giampaolo Rodola5bcc6d82019-09-30 12:51:55 +0800286 unlink(TESTFN2)
287
Guido van Rossum47f40342001-09-10 13:34:12 +0000288
Christian Heimes77c02eb2008-02-09 02:18:51 +0000289if __name__ == '__main__':
Serhiy Storchakac406a122013-07-17 13:42:24 +0300290 unittest.main()