test_largefile.py 10 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292
  1. """Test largefile support on system where this makes sense.
  2. """
  3. import os
  4. import stat
  5. import sys
  6. import unittest
  7. import socket
  8. import shutil
  9. import threading
  10. from test.support import requires, bigmemtest
  11. from test.support import SHORT_TIMEOUT
  12. from test.support import socket_helper
  13. from test.support.os_helper import TESTFN, unlink
  14. import io # C implementation of io
  15. import _pyio as pyio # Python implementation of io
  16. # size of file to create (>2 GiB; 2 GiB == 2,147,483,648 bytes)
  17. size = 2_500_000_000
  18. TESTFN2 = TESTFN + '2'
  19. class LargeFileTest:
  20. def setUp(self):
  21. if os.path.exists(TESTFN):
  22. mode = 'r+b'
  23. else:
  24. mode = 'w+b'
  25. with self.open(TESTFN, mode) as f:
  26. current_size = os.fstat(f.fileno())[stat.ST_SIZE]
  27. if current_size == size+1:
  28. return
  29. if current_size == 0:
  30. f.write(b'z')
  31. f.seek(0)
  32. f.seek(size)
  33. f.write(b'a')
  34. f.flush()
  35. self.assertEqual(os.fstat(f.fileno())[stat.ST_SIZE], size+1)
  36. @classmethod
  37. def tearDownClass(cls):
  38. with cls.open(TESTFN, 'wb'):
  39. pass
  40. if not os.stat(TESTFN)[stat.ST_SIZE] == 0:
  41. raise cls.failureException('File was not truncated by opening '
  42. 'with mode "wb"')
  43. unlink(TESTFN2)
  44. class TestFileMethods(LargeFileTest):
  45. """Test that each file function works as expected for large
  46. (i.e. > 2 GiB) files.
  47. """
  48. # _pyio.FileIO.readall() uses a temporary bytearray then casted to bytes,
  49. # so memuse=2 is needed
  50. @bigmemtest(size=size, memuse=2, dry_run=False)
  51. def test_large_read(self, _size):
  52. # bpo-24658: Test that a read greater than 2GB does not fail.
  53. with self.open(TESTFN, "rb") as f:
  54. self.assertEqual(len(f.read()), size + 1)
  55. self.assertEqual(f.tell(), size + 1)
  56. def test_osstat(self):
  57. self.assertEqual(os.stat(TESTFN)[stat.ST_SIZE], size+1)
  58. def test_seek_read(self):
  59. with self.open(TESTFN, 'rb') as f:
  60. self.assertEqual(f.tell(), 0)
  61. self.assertEqual(f.read(1), b'z')
  62. self.assertEqual(f.tell(), 1)
  63. f.seek(0)
  64. self.assertEqual(f.tell(), 0)
  65. f.seek(0, 0)
  66. self.assertEqual(f.tell(), 0)
  67. f.seek(42)
  68. self.assertEqual(f.tell(), 42)
  69. f.seek(42, 0)
  70. self.assertEqual(f.tell(), 42)
  71. f.seek(42, 1)
  72. self.assertEqual(f.tell(), 84)
  73. f.seek(0, 1)
  74. self.assertEqual(f.tell(), 84)
  75. f.seek(0, 2) # seek from the end
  76. self.assertEqual(f.tell(), size + 1 + 0)
  77. f.seek(-10, 2)
  78. self.assertEqual(f.tell(), size + 1 - 10)
  79. f.seek(-size-1, 2)
  80. self.assertEqual(f.tell(), 0)
  81. f.seek(size)
  82. self.assertEqual(f.tell(), size)
  83. # the 'a' that was written at the end of file above
  84. self.assertEqual(f.read(1), b'a')
  85. f.seek(-size-1, 1)
  86. self.assertEqual(f.read(1), b'z')
  87. self.assertEqual(f.tell(), 1)
  88. def test_lseek(self):
  89. with self.open(TESTFN, 'rb') as f:
  90. self.assertEqual(os.lseek(f.fileno(), 0, 0), 0)
  91. self.assertEqual(os.lseek(f.fileno(), 42, 0), 42)
  92. self.assertEqual(os.lseek(f.fileno(), 42, 1), 84)
  93. self.assertEqual(os.lseek(f.fileno(), 0, 1), 84)
  94. self.assertEqual(os.lseek(f.fileno(), 0, 2), size+1+0)
  95. self.assertEqual(os.lseek(f.fileno(), -10, 2), size+1-10)
  96. self.assertEqual(os.lseek(f.fileno(), -size-1, 2), 0)
  97. self.assertEqual(os.lseek(f.fileno(), size, 0), size)
  98. # the 'a' that was written at the end of file above
  99. self.assertEqual(f.read(1), b'a')
  100. def test_truncate(self):
  101. with self.open(TESTFN, 'r+b') as f:
  102. if not hasattr(f, 'truncate'):
  103. raise unittest.SkipTest("open().truncate() not available "
  104. "on this system")
  105. f.seek(0, 2)
  106. # else we've lost track of the true size
  107. self.assertEqual(f.tell(), size+1)
  108. # Cut it back via seek + truncate with no argument.
  109. newsize = size - 10
  110. f.seek(newsize)
  111. f.truncate()
  112. self.assertEqual(f.tell(), newsize) # else pointer moved
  113. f.seek(0, 2)
  114. self.assertEqual(f.tell(), newsize) # else wasn't truncated
  115. # Ensure that truncate(smaller than true size) shrinks
  116. # the file.
  117. newsize -= 1
  118. f.seek(42)
  119. f.truncate(newsize)
  120. self.assertEqual(f.tell(), 42)
  121. f.seek(0, 2)
  122. self.assertEqual(f.tell(), newsize)
  123. # XXX truncate(larger than true size) is ill-defined
  124. # across platform; cut it waaaaay back
  125. f.seek(0)
  126. f.truncate(1)
  127. self.assertEqual(f.tell(), 0) # else pointer moved
  128. f.seek(0)
  129. self.assertEqual(len(f.read()), 1) # else wasn't truncated
  130. def test_seekable(self):
  131. # Issue #5016; seekable() can return False when the current position
  132. # is negative when truncated to an int.
  133. for pos in (2**31-1, 2**31, 2**31+1):
  134. with self.open(TESTFN, 'rb') as f:
  135. f.seek(pos)
  136. self.assertTrue(f.seekable())
  137. def skip_no_disk_space(path, required):
  138. def decorator(fun):
  139. def wrapper(*args, **kwargs):
  140. if not hasattr(shutil, "disk_usage"):
  141. raise unittest.SkipTest("requires shutil.disk_usage")
  142. if shutil.disk_usage(os.path.realpath(path)).free < required:
  143. hsize = int(required / 1024 / 1024)
  144. raise unittest.SkipTest(
  145. f"required {hsize} MiB of free disk space")
  146. return fun(*args, **kwargs)
  147. return wrapper
  148. return decorator
  149. class TestCopyfile(LargeFileTest, unittest.TestCase):
  150. open = staticmethod(io.open)
  151. # Exact required disk space would be (size * 2), but let's give it a
  152. # bit more tolerance.
  153. @skip_no_disk_space(TESTFN, size * 2.5)
  154. def test_it(self):
  155. # Internally shutil.copyfile() can use "fast copy" methods like
  156. # os.sendfile().
  157. size = os.path.getsize(TESTFN)
  158. shutil.copyfile(TESTFN, TESTFN2)
  159. self.assertEqual(os.path.getsize(TESTFN2), size)
  160. with open(TESTFN2, 'rb') as f:
  161. self.assertEqual(f.read(5), b'z\x00\x00\x00\x00')
  162. f.seek(size - 5)
  163. self.assertEqual(f.read(), b'\x00\x00\x00\x00a')
  164. @unittest.skipIf(not hasattr(os, 'sendfile'), 'sendfile not supported')
  165. class TestSocketSendfile(LargeFileTest, unittest.TestCase):
  166. open = staticmethod(io.open)
  167. timeout = SHORT_TIMEOUT
  168. def setUp(self):
  169. super().setUp()
  170. self.thread = None
  171. def tearDown(self):
  172. super().tearDown()
  173. if self.thread is not None:
  174. self.thread.join(self.timeout)
  175. self.thread = None
  176. def tcp_server(self, sock):
  177. def run(sock):
  178. with sock:
  179. conn, _ = sock.accept()
  180. conn.settimeout(self.timeout)
  181. with conn, open(TESTFN2, 'wb') as f:
  182. event.wait(self.timeout)
  183. while True:
  184. chunk = conn.recv(65536)
  185. if not chunk:
  186. return
  187. f.write(chunk)
  188. event = threading.Event()
  189. sock.settimeout(self.timeout)
  190. self.thread = threading.Thread(target=run, args=(sock, ))
  191. self.thread.start()
  192. event.set()
  193. # Exact required disk space would be (size * 2), but let's give it a
  194. # bit more tolerance.
  195. @skip_no_disk_space(TESTFN, size * 2.5)
  196. def test_it(self):
  197. port = socket_helper.find_unused_port()
  198. with socket.create_server(("", port)) as sock:
  199. self.tcp_server(sock)
  200. with socket.create_connection(("127.0.0.1", port)) as client:
  201. with open(TESTFN, 'rb') as f:
  202. client.sendfile(f)
  203. self.tearDown()
  204. size = os.path.getsize(TESTFN)
  205. self.assertEqual(os.path.getsize(TESTFN2), size)
  206. with open(TESTFN2, 'rb') as f:
  207. self.assertEqual(f.read(5), b'z\x00\x00\x00\x00')
  208. f.seek(size - 5)
  209. self.assertEqual(f.read(), b'\x00\x00\x00\x00a')
  210. def setUpModule():
  211. try:
  212. import signal
  213. # The default handler for SIGXFSZ is to abort the process.
  214. # By ignoring it, system calls exceeding the file size resource
  215. # limit will raise OSError instead of crashing the interpreter.
  216. signal.signal(signal.SIGXFSZ, signal.SIG_IGN)
  217. except (ImportError, AttributeError):
  218. pass
  219. # On Windows and Mac OSX this test consumes large resources; It
  220. # takes a long time to build the >2 GiB file and takes >2 GiB of disk
  221. # space therefore the resource must be enabled to run this test.
  222. # If not, nothing after this line stanza will be executed.
  223. if sys.platform[:3] == 'win' or sys.platform == 'darwin':
  224. requires('largefile',
  225. 'test requires %s bytes and a long time to run' % str(size))
  226. else:
  227. # Only run if the current filesystem supports large files.
  228. # (Skip this test on Windows, since we now always support
  229. # large files.)
  230. f = open(TESTFN, 'wb', buffering=0)
  231. try:
  232. # 2**31 == 2147483648
  233. f.seek(2147483649)
  234. # Seeking is not enough of a test: you must write and flush, too!
  235. f.write(b'x')
  236. f.flush()
  237. except (OSError, OverflowError):
  238. raise unittest.SkipTest("filesystem does not have "
  239. "largefile support")
  240. finally:
  241. f.close()
  242. unlink(TESTFN)
  243. class CLargeFileTest(TestFileMethods, unittest.TestCase):
  244. open = staticmethod(io.open)
  245. class PyLargeFileTest(TestFileMethods, unittest.TestCase):
  246. open = staticmethod(pyio.open)
  247. def tearDownModule():
  248. unlink(TESTFN)
  249. unlink(TESTFN2)
  250. if __name__ == '__main__':
  251. unittest.main()