/Lib/test/test_mmap.py

http://unladen-swallow.googlecode.com/ · Python · 586 lines · 432 code · 81 blank · 73 comment · 75 complexity · 9256735477a1eddb682d89cb09c5c4c2 MD5 · raw file

  1. from test.test_support import TESTFN, run_unittest
  2. import mmap
  3. import unittest
  4. import os, re, itertools
  5. PAGESIZE = mmap.PAGESIZE
  6. class MmapTests(unittest.TestCase):
  7. def setUp(self):
  8. if os.path.exists(TESTFN):
  9. os.unlink(TESTFN)
  10. def tearDown(self):
  11. try:
  12. os.unlink(TESTFN)
  13. except OSError:
  14. pass
  15. def test_basic(self):
  16. # Test mmap module on Unix systems and Windows
  17. # Create a file to be mmap'ed.
  18. f = open(TESTFN, 'w+')
  19. try:
  20. # Write 2 pages worth of data to the file
  21. f.write('\0'* PAGESIZE)
  22. f.write('foo')
  23. f.write('\0'* (PAGESIZE-3) )
  24. f.flush()
  25. m = mmap.mmap(f.fileno(), 2 * PAGESIZE)
  26. f.close()
  27. # Simple sanity checks
  28. tp = str(type(m)) # SF bug 128713: segfaulted on Linux
  29. self.assertEqual(m.find('foo'), PAGESIZE)
  30. self.assertEqual(len(m), 2*PAGESIZE)
  31. self.assertEqual(m[0], '\0')
  32. self.assertEqual(m[0:3], '\0\0\0')
  33. # Shouldn't crash on boundary (Issue #5292)
  34. self.assertRaises(IndexError, m.__getitem__, len(m))
  35. self.assertRaises(IndexError, m.__setitem__, len(m), '\0')
  36. # Modify the file's content
  37. m[0] = '3'
  38. m[PAGESIZE +3: PAGESIZE +3+3] = 'bar'
  39. # Check that the modification worked
  40. self.assertEqual(m[0], '3')
  41. self.assertEqual(m[0:3], '3\0\0')
  42. self.assertEqual(m[PAGESIZE-1 : PAGESIZE + 7], '\0foobar\0')
  43. m.flush()
  44. # Test doing a regular expression match in an mmap'ed file
  45. match = re.search('[A-Za-z]+', m)
  46. if match is None:
  47. self.fail('regex match on mmap failed!')
  48. else:
  49. start, end = match.span(0)
  50. length = end - start
  51. self.assertEqual(start, PAGESIZE)
  52. self.assertEqual(end, PAGESIZE + 6)
  53. # test seeking around (try to overflow the seek implementation)
  54. m.seek(0,0)
  55. self.assertEqual(m.tell(), 0)
  56. m.seek(42,1)
  57. self.assertEqual(m.tell(), 42)
  58. m.seek(0,2)
  59. self.assertEqual(m.tell(), len(m))
  60. # Try to seek to negative position...
  61. self.assertRaises(ValueError, m.seek, -1)
  62. # Try to seek beyond end of mmap...
  63. self.assertRaises(ValueError, m.seek, 1, 2)
  64. # Try to seek to negative position...
  65. self.assertRaises(ValueError, m.seek, -len(m)-1, 2)
  66. # Try resizing map
  67. try:
  68. m.resize(512)
  69. except SystemError:
  70. # resize() not supported
  71. # No messages are printed, since the output of this test suite
  72. # would then be different across platforms.
  73. pass
  74. else:
  75. # resize() is supported
  76. self.assertEqual(len(m), 512)
  77. # Check that we can no longer seek beyond the new size.
  78. self.assertRaises(ValueError, m.seek, 513, 0)
  79. # Check that the underlying file is truncated too
  80. # (bug #728515)
  81. f = open(TESTFN)
  82. f.seek(0, 2)
  83. self.assertEqual(f.tell(), 512)
  84. f.close()
  85. self.assertEqual(m.size(), 512)
  86. m.close()
  87. finally:
  88. try:
  89. f.close()
  90. except OSError:
  91. pass
  92. def test_access_parameter(self):
  93. # Test for "access" keyword parameter
  94. mapsize = 10
  95. open(TESTFN, "wb").write("a"*mapsize)
  96. f = open(TESTFN, "rb")
  97. m = mmap.mmap(f.fileno(), mapsize, access=mmap.ACCESS_READ)
  98. self.assertEqual(m[:], 'a'*mapsize, "Readonly memory map data incorrect.")
  99. # Ensuring that readonly mmap can't be slice assigned
  100. try:
  101. m[:] = 'b'*mapsize
  102. except TypeError:
  103. pass
  104. else:
  105. self.fail("Able to write to readonly memory map")
  106. # Ensuring that readonly mmap can't be item assigned
  107. try:
  108. m[0] = 'b'
  109. except TypeError:
  110. pass
  111. else:
  112. self.fail("Able to write to readonly memory map")
  113. # Ensuring that readonly mmap can't be write() to
  114. try:
  115. m.seek(0,0)
  116. m.write('abc')
  117. except TypeError:
  118. pass
  119. else:
  120. self.fail("Able to write to readonly memory map")
  121. # Ensuring that readonly mmap can't be write_byte() to
  122. try:
  123. m.seek(0,0)
  124. m.write_byte('d')
  125. except TypeError:
  126. pass
  127. else:
  128. self.fail("Able to write to readonly memory map")
  129. # Ensuring that readonly mmap can't be resized
  130. try:
  131. m.resize(2*mapsize)
  132. except SystemError: # resize is not universally supported
  133. pass
  134. except TypeError:
  135. pass
  136. else:
  137. self.fail("Able to resize readonly memory map")
  138. f.close()
  139. del m, f
  140. self.assertEqual(open(TESTFN, "rb").read(), 'a'*mapsize,
  141. "Readonly memory map data file was modified")
  142. # Opening mmap with size too big
  143. import sys
  144. f = open(TESTFN, "r+b")
  145. try:
  146. m = mmap.mmap(f.fileno(), mapsize+1)
  147. except ValueError:
  148. # we do not expect a ValueError on Windows
  149. # CAUTION: This also changes the size of the file on disk, and
  150. # later tests assume that the length hasn't changed. We need to
  151. # repair that.
  152. if sys.platform.startswith('win'):
  153. self.fail("Opening mmap with size+1 should work on Windows.")
  154. else:
  155. # we expect a ValueError on Unix, but not on Windows
  156. if not sys.platform.startswith('win'):
  157. self.fail("Opening mmap with size+1 should raise ValueError.")
  158. m.close()
  159. f.close()
  160. if sys.platform.startswith('win'):
  161. # Repair damage from the resizing test.
  162. f = open(TESTFN, 'r+b')
  163. f.truncate(mapsize)
  164. f.close()
  165. # Opening mmap with access=ACCESS_WRITE
  166. f = open(TESTFN, "r+b")
  167. m = mmap.mmap(f.fileno(), mapsize, access=mmap.ACCESS_WRITE)
  168. # Modifying write-through memory map
  169. m[:] = 'c'*mapsize
  170. self.assertEqual(m[:], 'c'*mapsize,
  171. "Write-through memory map memory not updated properly.")
  172. m.flush()
  173. m.close()
  174. f.close()
  175. f = open(TESTFN, 'rb')
  176. stuff = f.read()
  177. f.close()
  178. self.assertEqual(stuff, 'c'*mapsize,
  179. "Write-through memory map data file not updated properly.")
  180. # Opening mmap with access=ACCESS_COPY
  181. f = open(TESTFN, "r+b")
  182. m = mmap.mmap(f.fileno(), mapsize, access=mmap.ACCESS_COPY)
  183. # Modifying copy-on-write memory map
  184. m[:] = 'd'*mapsize
  185. self.assertEqual(m[:], 'd' * mapsize,
  186. "Copy-on-write memory map data not written correctly.")
  187. m.flush()
  188. self.assertEqual(open(TESTFN, "rb").read(), 'c'*mapsize,
  189. "Copy-on-write test data file should not be modified.")
  190. # Ensuring copy-on-write maps cannot be resized
  191. self.assertRaises(TypeError, m.resize, 2*mapsize)
  192. f.close()
  193. del m, f
  194. # Ensuring invalid access parameter raises exception
  195. f = open(TESTFN, "r+b")
  196. self.assertRaises(ValueError, mmap.mmap, f.fileno(), mapsize, access=4)
  197. f.close()
  198. if os.name == "posix":
  199. # Try incompatible flags, prot and access parameters.
  200. f = open(TESTFN, "r+b")
  201. self.assertRaises(ValueError, mmap.mmap, f.fileno(), mapsize,
  202. flags=mmap.MAP_PRIVATE,
  203. prot=mmap.PROT_READ, access=mmap.ACCESS_WRITE)
  204. f.close()
  205. def test_bad_file_desc(self):
  206. # Try opening a bad file descriptor...
  207. self.assertRaises(mmap.error, mmap.mmap, -2, 4096)
  208. def test_tougher_find(self):
  209. # Do a tougher .find() test. SF bug 515943 pointed out that, in 2.2,
  210. # searching for data with embedded \0 bytes didn't work.
  211. f = open(TESTFN, 'w+')
  212. data = 'aabaac\x00deef\x00\x00aa\x00'
  213. n = len(data)
  214. f.write(data)
  215. f.flush()
  216. m = mmap.mmap(f.fileno(), n)
  217. f.close()
  218. for start in range(n+1):
  219. for finish in range(start, n+1):
  220. slice = data[start : finish]
  221. self.assertEqual(m.find(slice), data.find(slice))
  222. self.assertEqual(m.find(slice + 'x'), -1)
  223. m.close()
  224. def test_find_end(self):
  225. # test the new 'end' parameter works as expected
  226. f = open(TESTFN, 'w+')
  227. data = 'one two ones'
  228. n = len(data)
  229. f.write(data)
  230. f.flush()
  231. m = mmap.mmap(f.fileno(), n)
  232. f.close()
  233. self.assertEqual(m.find('one'), 0)
  234. self.assertEqual(m.find('ones'), 8)
  235. self.assertEqual(m.find('one', 0, -1), 0)
  236. self.assertEqual(m.find('one', 1), 8)
  237. self.assertEqual(m.find('one', 1, -1), 8)
  238. self.assertEqual(m.find('one', 1, -2), -1)
  239. def test_rfind(self):
  240. # test the new 'end' parameter works as expected
  241. f = open(TESTFN, 'w+')
  242. data = 'one two ones'
  243. n = len(data)
  244. f.write(data)
  245. f.flush()
  246. m = mmap.mmap(f.fileno(), n)
  247. f.close()
  248. self.assertEqual(m.rfind('one'), 8)
  249. self.assertEqual(m.rfind('one '), 0)
  250. self.assertEqual(m.rfind('one', 0, -1), 8)
  251. self.assertEqual(m.rfind('one', 0, -2), 0)
  252. self.assertEqual(m.rfind('one', 1, -1), 8)
  253. self.assertEqual(m.rfind('one', 1, -2), -1)
  254. def test_double_close(self):
  255. # make sure a double close doesn't crash on Solaris (Bug# 665913)
  256. f = open(TESTFN, 'w+')
  257. f.write(2**16 * 'a') # Arbitrary character
  258. f.close()
  259. f = open(TESTFN)
  260. mf = mmap.mmap(f.fileno(), 2**16, access=mmap.ACCESS_READ)
  261. mf.close()
  262. mf.close()
  263. f.close()
  264. def test_entire_file(self):
  265. # test mapping of entire file by passing 0 for map length
  266. if hasattr(os, "stat"):
  267. f = open(TESTFN, "w+")
  268. f.write(2**16 * 'm') # Arbitrary character
  269. f.close()
  270. f = open(TESTFN, "rb+")
  271. mf = mmap.mmap(f.fileno(), 0)
  272. self.assertEqual(len(mf), 2**16, "Map size should equal file size.")
  273. self.assertEqual(mf.read(2**16), 2**16 * "m")
  274. mf.close()
  275. f.close()
  276. def test_move(self):
  277. # make move works everywhere (64-bit format problem earlier)
  278. f = open(TESTFN, 'w+')
  279. f.write("ABCDEabcde") # Arbitrary character
  280. f.flush()
  281. mf = mmap.mmap(f.fileno(), 10)
  282. mf.move(5, 0, 5)
  283. self.assertEqual(mf[:], "ABCDEABCDE", "Map move should have duplicated front 5")
  284. mf.close()
  285. f.close()
  286. # more excessive test
  287. data = "0123456789"
  288. for dest in range(len(data)):
  289. for src in range(len(data)):
  290. for count in range(len(data) - max(dest, src)):
  291. expected = data[:dest] + data[src:src+count] + data[dest+count:]
  292. m = mmap.mmap(-1, len(data))
  293. m[:] = data
  294. m.move(dest, src, count)
  295. self.assertEqual(m[:], expected)
  296. m.close()
  297. # segfault test (Issue 5387)
  298. m = mmap.mmap(-1, 100)
  299. offsets = [-100, -1, 0, 1, 100]
  300. for source, dest, size in itertools.product(offsets, offsets, offsets):
  301. try:
  302. m.move(source, dest, size)
  303. except ValueError:
  304. pass
  305. self.assertRaises(ValueError, m.move, -1, -1, -1)
  306. self.assertRaises(ValueError, m.move, -1, -1, 0)
  307. self.assertRaises(ValueError, m.move, -1, 0, -1)
  308. self.assertRaises(ValueError, m.move, 0, -1, -1)
  309. self.assertRaises(ValueError, m.move, -1, 0, 0)
  310. self.assertRaises(ValueError, m.move, 0, -1, 0)
  311. self.assertRaises(ValueError, m.move, 0, 0, -1)
  312. m.close()
  313. def test_anonymous(self):
  314. # anonymous mmap.mmap(-1, PAGE)
  315. m = mmap.mmap(-1, PAGESIZE)
  316. for x in xrange(PAGESIZE):
  317. self.assertEqual(m[x], '\0', "anonymously mmap'ed contents should be zero")
  318. for x in xrange(PAGESIZE):
  319. m[x] = ch = chr(x & 255)
  320. self.assertEqual(m[x], ch)
  321. def test_extended_getslice(self):
  322. # Test extended slicing by comparing with list slicing.
  323. s = "".join(chr(c) for c in reversed(range(256)))
  324. m = mmap.mmap(-1, len(s))
  325. m[:] = s
  326. self.assertEqual(m[:], s)
  327. indices = (0, None, 1, 3, 19, 300, -1, -2, -31, -300)
  328. for start in indices:
  329. for stop in indices:
  330. # Skip step 0 (invalid)
  331. for step in indices[1:]:
  332. self.assertEqual(m[start:stop:step],
  333. s[start:stop:step])
  334. def test_extended_set_del_slice(self):
  335. # Test extended slicing by comparing with list slicing.
  336. s = "".join(chr(c) for c in reversed(range(256)))
  337. m = mmap.mmap(-1, len(s))
  338. indices = (0, None, 1, 3, 19, 300, -1, -2, -31, -300)
  339. for start in indices:
  340. for stop in indices:
  341. # Skip invalid step 0
  342. for step in indices[1:]:
  343. m[:] = s
  344. self.assertEqual(m[:], s)
  345. L = list(s)
  346. # Make sure we have a slice of exactly the right length,
  347. # but with different data.
  348. data = L[start:stop:step]
  349. data = "".join(reversed(data))
  350. L[start:stop:step] = data
  351. m[start:stop:step] = data
  352. self.assertEquals(m[:], "".join(L))
  353. def make_mmap_file (self, f, halfsize):
  354. # Write 2 pages worth of data to the file
  355. f.write ('\0' * halfsize)
  356. f.write ('foo')
  357. f.write ('\0' * (halfsize - 3))
  358. f.flush ()
  359. return mmap.mmap (f.fileno(), 0)
  360. def test_offset (self):
  361. f = open (TESTFN, 'w+b')
  362. try: # unlink TESTFN no matter what
  363. halfsize = mmap.ALLOCATIONGRANULARITY
  364. m = self.make_mmap_file (f, halfsize)
  365. m.close ()
  366. f.close ()
  367. mapsize = halfsize * 2
  368. # Try invalid offset
  369. f = open(TESTFN, "r+b")
  370. for offset in [-2, -1, None]:
  371. try:
  372. m = mmap.mmap(f.fileno(), mapsize, offset=offset)
  373. self.assertEqual(0, 1)
  374. except (ValueError, TypeError, OverflowError):
  375. pass
  376. else:
  377. self.assertEqual(0, 0)
  378. f.close()
  379. # Try valid offset, hopefully 8192 works on all OSes
  380. f = open(TESTFN, "r+b")
  381. m = mmap.mmap(f.fileno(), mapsize - halfsize, offset=halfsize)
  382. self.assertEqual(m[0:3], 'foo')
  383. f.close()
  384. # Try resizing map
  385. try:
  386. m.resize(512)
  387. except SystemError:
  388. pass
  389. else:
  390. # resize() is supported
  391. self.assertEqual(len(m), 512)
  392. # Check that we can no longer seek beyond the new size.
  393. self.assertRaises(ValueError, m.seek, 513, 0)
  394. # Check that the content is not changed
  395. self.assertEqual(m[0:3], 'foo')
  396. # Check that the underlying file is truncated too
  397. f = open(TESTFN)
  398. f.seek(0, 2)
  399. self.assertEqual(f.tell(), halfsize + 512)
  400. f.close()
  401. self.assertEqual(m.size(), halfsize + 512)
  402. m.close()
  403. finally:
  404. f.close()
  405. try:
  406. os.unlink(TESTFN)
  407. except OSError:
  408. pass
  409. def test_subclass(self):
  410. class anon_mmap(mmap.mmap):
  411. def __new__(klass, *args, **kwargs):
  412. return mmap.mmap.__new__(klass, -1, *args, **kwargs)
  413. anon_mmap(PAGESIZE)
  414. def test_prot_readonly(self):
  415. if not hasattr(mmap, 'PROT_READ'):
  416. return
  417. mapsize = 10
  418. open(TESTFN, "wb").write("a"*mapsize)
  419. f = open(TESTFN, "rb")
  420. m = mmap.mmap(f.fileno(), mapsize, prot=mmap.PROT_READ)
  421. self.assertRaises(TypeError, m.write, "foo")
  422. f.close()
  423. def test_error(self):
  424. self.assert_(issubclass(mmap.error, EnvironmentError))
  425. self.assert_("mmap.error" in str(mmap.error))
  426. def test_io_methods(self):
  427. data = "0123456789"
  428. open(TESTFN, "wb").write("x"*len(data))
  429. f = open(TESTFN, "r+b")
  430. m = mmap.mmap(f.fileno(), len(data))
  431. f.close()
  432. # Test write_byte()
  433. for i in xrange(len(data)):
  434. self.assertEquals(m.tell(), i)
  435. m.write_byte(data[i:i+1])
  436. self.assertEquals(m.tell(), i+1)
  437. self.assertRaises(ValueError, m.write_byte, "x")
  438. self.assertEquals(m[:], data)
  439. # Test read_byte()
  440. m.seek(0)
  441. for i in xrange(len(data)):
  442. self.assertEquals(m.tell(), i)
  443. self.assertEquals(m.read_byte(), data[i:i+1])
  444. self.assertEquals(m.tell(), i+1)
  445. self.assertRaises(ValueError, m.read_byte)
  446. # Test read()
  447. m.seek(3)
  448. self.assertEquals(m.read(3), "345")
  449. self.assertEquals(m.tell(), 6)
  450. # Test write()
  451. m.seek(3)
  452. m.write("bar")
  453. self.assertEquals(m.tell(), 6)
  454. self.assertEquals(m[:], "012bar6789")
  455. m.seek(8)
  456. self.assertRaises(ValueError, m.write, "bar")
  457. if os.name == 'nt':
  458. def test_tagname(self):
  459. data1 = "0123456789"
  460. data2 = "abcdefghij"
  461. assert len(data1) == len(data2)
  462. # Test same tag
  463. m1 = mmap.mmap(-1, len(data1), tagname="foo")
  464. m1[:] = data1
  465. m2 = mmap.mmap(-1, len(data2), tagname="foo")
  466. m2[:] = data2
  467. self.assertEquals(m1[:], data2)
  468. self.assertEquals(m2[:], data2)
  469. m2.close()
  470. m1.close()
  471. # Test differnt tag
  472. m1 = mmap.mmap(-1, len(data1), tagname="foo")
  473. m1[:] = data1
  474. m2 = mmap.mmap(-1, len(data2), tagname="boo")
  475. m2[:] = data2
  476. self.assertEquals(m1[:], data1)
  477. self.assertEquals(m2[:], data2)
  478. m2.close()
  479. m1.close()
  480. def test_crasher_on_windows(self):
  481. # Should not crash (Issue 1733986)
  482. m = mmap.mmap(-1, 1000, tagname="foo")
  483. try:
  484. mmap.mmap(-1, 5000, tagname="foo")[:] # same tagname, but larger size
  485. except:
  486. pass
  487. m.close()
  488. # Should not crash (Issue 5385)
  489. open(TESTFN, "wb").write("x"*10)
  490. f = open(TESTFN, "r+b")
  491. m = mmap.mmap(f.fileno(), 0)
  492. f.close()
  493. try:
  494. m.resize(0) # will raise WindowsError
  495. except:
  496. pass
  497. try:
  498. m[:]
  499. except:
  500. pass
  501. m.close()
  502. def test_main():
  503. run_unittest(MmapTests)
  504. if __name__ == '__main__':
  505. test_main()