test_file.py 34 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588589590591592593594595596597598599600601602603604605606607608609610611612613614615616617618619620621622623624625626627628629630631632633634635636637638639640641642643644645646647648649650651652653654655656657658659660661662663664665666667668669670671672673674675676677678679680681682683684685686687688689690691692693694695696697698699700701702703704705706707708709710711712713714715716717718719720721722723724725726727728729730731732733734735736737738739740741742743744745746747748749750751752753754755756757758759760761762763764765766767768769770771772773774775776777778779780781782783784785786787788789790791792793794795796797798799800801802803804805806807808809810811812813814815816817818819820821822823824825826827828829830831832833834835836837838839840841842843844845846847848849850851852853854855856857858859860861862863864865866867868869870871872873874875876877878879880881882883884885886887888889890891892893894895896897898899900901902903904905906907908909910911912913914915916917918919920921922923924925926927928929930931932933934935936937938939940941942943944945946947948949950951952953954955956957958959960961962963964965966967968969970971972973974975976977978979980981982983984985986987988989990991992993994995996997998999100010011002100310041005100610071008100910101011101210131014101510161017101810191020102110221023102410251026102710281029103010311032103310341035103610371038103910401041104210431044104510461047104810491050105110521053105410551056105710581059106010611062106310641065106610671068106910701071107210731074107510761077107810791080108110821083108410851086108710881089109010911092109310941095109610971098109911001101
  1. # This file is part of h5py, a Python interface to the HDF5 library.
  2. #
  3. # http://www.h5py.org
  4. #
  5. # Copyright 2008-2013 Andrew Collette and contributors
  6. #
  7. # License: Standard 3-clause BSD; see "license.txt" for full license terms
  8. # and contributor agreement.
  9. """
  10. File object test module.
  11. Tests all aspects of File objects, including their creation.
  12. """
  13. import multiprocessing
  14. import os
  15. import stat
  16. import pickle
  17. import tempfile
  18. import time
  19. import sys
  20. from concurrent.futures import ProcessPoolExecutor
  21. from hashlib import sha256
  22. import pytest
  23. from .common import ut, TestCase, UNICODE_FILENAMES, closed_tempfile, make_name
  24. from h5py._hl.files import direct_vfd
  25. from h5py import File
  26. import h5py
  27. import pathlib
  28. class TestFileOpen(TestCase):
  29. """
  30. Feature: Opening files with Python-style modes.
  31. """
  32. def test_default(self):
  33. """ Default semantics in the presence or absence of a file """
  34. fname = self.mktemp()
  35. # No existing file; error
  36. with pytest.raises(FileNotFoundError):
  37. with File(fname):
  38. pass
  39. # Existing readonly file; open read-only
  40. with File(fname, 'w'):
  41. pass
  42. os.chmod(fname, stat.S_IREAD)
  43. try:
  44. with File(fname) as f:
  45. self.assertTrue(f)
  46. self.assertEqual(f.mode, 'r')
  47. finally:
  48. os.chmod(fname, stat.S_IWRITE)
  49. # File exists but is not HDF5; raise OSError
  50. with open(fname, 'wb') as f:
  51. f.write(b'\x00')
  52. with self.assertRaises(OSError):
  53. File(fname)
  54. def test_create(self):
  55. """ Mode 'w' opens file in overwrite mode """
  56. fname = self.mktemp()
  57. fid = File(fname, 'w')
  58. self.assertTrue(fid)
  59. fid.create_group('foo')
  60. fid.close()
  61. fid = File(fname, 'w')
  62. self.assertNotIn('foo', fid)
  63. fid.close()
  64. def test_create_exclusive(self):
  65. """ Mode 'w-' opens file in exclusive mode """
  66. fname = self.mktemp()
  67. fid = File(fname, 'w-')
  68. self.assertTrue(fid)
  69. fid.close()
  70. with self.assertRaises(FileExistsError):
  71. File(fname, 'w-')
  72. def test_append(self):
  73. """ Mode 'a' opens file in append/readwrite mode, creating if necessary """
  74. fname = self.mktemp()
  75. fid = File(fname, 'a')
  76. try:
  77. self.assertTrue(fid)
  78. fid.create_group('foo')
  79. assert 'foo' in fid
  80. finally:
  81. fid.close()
  82. fid = File(fname, 'a')
  83. try:
  84. assert 'foo' in fid
  85. fid.create_group('bar')
  86. assert 'bar' in fid
  87. finally:
  88. fid.close()
  89. # Observed on cibuildwheel v2.19.1
  90. # https://github.com/pypa/cibuildwheel/issues/1882
  91. @pytest.mark.skipif(
  92. os.getenv("CIBUILDWHEEL") == "1" and sys.platform == "linux",
  93. reason="Linux docker cibuildwheel environment permissions issue",
  94. )
  95. def test_append_permissions(self):
  96. """ Mode 'a' fails when file is read-only """
  97. fname = self.mktemp()
  98. with File(fname, 'a') as fid:
  99. fid.create_group('foo')
  100. os.chmod(fname, stat.S_IREAD) # Make file read-only
  101. try:
  102. with pytest.raises(PermissionError):
  103. File(fname, 'a')
  104. finally:
  105. # Make it writable again so it can be deleted on Windows
  106. os.chmod(fname, stat.S_IREAD | stat.S_IWRITE)
  107. def test_readonly(self):
  108. """ Mode 'r' opens file in readonly mode """
  109. fname = self.mktemp()
  110. fid = File(fname, 'w')
  111. fid.close()
  112. self.assertFalse(fid)
  113. fid = File(fname, 'r')
  114. self.assertTrue(fid)
  115. with self.assertRaises(ValueError):
  116. fid.create_group('foo')
  117. fid.close()
  118. def test_readwrite(self):
  119. """ Mode 'r+' opens existing file in readwrite mode """
  120. fname = self.mktemp()
  121. fid = File(fname, 'w')
  122. fid.create_group('foo')
  123. fid.close()
  124. fid = File(fname, 'r+')
  125. assert 'foo' in fid
  126. fid.create_group('bar')
  127. assert 'bar' in fid
  128. fid.close()
  129. def test_nonexistent_file(self):
  130. """ Modes 'r' and 'r+' do not create files """
  131. fname = self.mktemp()
  132. with self.assertRaises(FileNotFoundError):
  133. File(fname, 'r')
  134. with self.assertRaises(FileNotFoundError):
  135. File(fname, 'r+')
  136. def test_invalid_mode(self):
  137. """ Invalid modes raise ValueError """
  138. with self.assertRaises(ValueError):
  139. File(self.mktemp(), 'mongoose')
  140. class TestSpaceStrategy(TestCase):
  141. """
  142. Feature: Create file with specified file space strategy
  143. """
  144. def test_create_with_space_strategy(self):
  145. """ Create file with file space strategy """
  146. fname = self.mktemp()
  147. fid = File(fname, 'w', fs_strategy="page",
  148. fs_persist=True, fs_threshold=100)
  149. self.assertTrue(fid)
  150. # Unable to set file space strategy of an existing file
  151. with self.assertRaises(ValueError):
  152. File(fname, 'a', fs_strategy="page")
  153. # Invalid file space strategy type
  154. with self.assertRaises(ValueError):
  155. File(self.mktemp(), 'w', fs_strategy="invalid")
  156. x = make_name("x")
  157. y = make_name("y")
  158. z = make_name("z")
  159. dset = fid.create_dataset(x, (100,), dtype='uint8')
  160. dset[...] = 1
  161. dset = fid.create_dataset(y, (100,), dtype='uint8')
  162. dset[...] = 1
  163. del fid[x]
  164. fid.close()
  165. fid = File(fname, 'a')
  166. plist = fid.id.get_create_plist()
  167. fs_strat = plist.get_file_space_strategy()
  168. assert(fs_strat[0] == 1)
  169. assert(fs_strat[1] == True)
  170. assert(fs_strat[2] == 100)
  171. dset = fid.create_dataset(z, (100,), dtype='uint8')
  172. dset[...] = 1
  173. fid.close()
  174. @pytest.mark.mpi_skip
  175. class TestPageBuffering(TestCase):
  176. """
  177. Feature: Use page buffering
  178. """
  179. def test_only_with_page_strategy(self):
  180. """Allow page buffering only with fs_strategy="page".
  181. """
  182. fname = self.mktemp()
  183. with File(fname, mode='w', fs_strategy='page', page_buf_size=16*1024):
  184. pass
  185. with self.assertRaises(OSError):
  186. File(fname, mode='w', page_buf_size=16*1024)
  187. with self.assertRaises(OSError):
  188. File(fname, mode='w', fs_strategy='fsm', page_buf_size=16*1024)
  189. with self.assertRaises(OSError):
  190. File(fname, mode='w', fs_strategy='aggregate', page_buf_size=16*1024)
  191. def test_check_page_buf_size(self):
  192. """Verify set page buffer size, and minimum meta and raw eviction criteria."""
  193. fname = self.mktemp()
  194. pbs = 16 * 1024
  195. mm = 19
  196. mr = 67
  197. with File(fname, mode='w', fs_strategy='page',
  198. page_buf_size=pbs, min_meta_keep=mm, min_raw_keep=mr) as f:
  199. fapl = f.id.get_access_plist()
  200. self.assertEqual(fapl.get_page_buffer_size(), (pbs, mm, mr))
  201. @pytest.mark.skipif(h5py.version.hdf5_version_tuple > (1, 14, 3),
  202. reason='Requires HDF5 <= 1.14.3')
  203. def test_too_small_pbs(self):
  204. """Page buffer size must be greater than file space page size."""
  205. fname = self.mktemp()
  206. fsp = 16 * 1024
  207. with File(fname, mode='w', fs_strategy='page', fs_page_size=fsp):
  208. pass
  209. with self.assertRaises(OSError):
  210. File(fname, mode="r", page_buf_size=fsp-1)
  211. @pytest.mark.skipif(h5py.version.hdf5_version_tuple < (1, 14, 4),
  212. reason='Requires HDF5 >= 1.14.4')
  213. def test_open_nonpage_pbs(self):
  214. """Open non-PAGE file with page buffer set."""
  215. fname = self.mktemp()
  216. fsp = 16 * 1024
  217. with File(fname, mode='w'):
  218. pass
  219. with File(fname, mode='r', page_buf_size=fsp) as f:
  220. fapl = f.id.get_access_plist()
  221. assert fapl.get_page_buffer_size()[0] == 0
  222. @pytest.mark.skipif(h5py.version.hdf5_version_tuple < (1, 14, 4),
  223. reason='Requires HDF5 >= 1.14.4')
  224. def test_smaller_pbs(self):
  225. """Adjust page buffer size automatically when smaller than file page."""
  226. fname = self.mktemp()
  227. fsp = 16 * 1024
  228. with File(fname, mode='w', fs_strategy='page', fs_page_size=fsp):
  229. pass
  230. with File(fname, mode='r', page_buf_size=fsp-100) as f:
  231. fapl = f.id.get_access_plist()
  232. assert fapl.get_page_buffer_size()[0] == fsp
  233. def test_actual_pbs(self):
  234. """Verify actual page buffer size."""
  235. fname = self.mktemp()
  236. fsp = 16 * 1024
  237. pbs = 2 * fsp
  238. with File(fname, mode='w', fs_strategy='page', fs_page_size=fsp):
  239. pass
  240. with File(fname, mode='r', page_buf_size=pbs-1) as f:
  241. fapl = f.id.get_access_plist()
  242. self.assertEqual(fapl.get_page_buffer_size()[0], fsp)
  243. class TestModes(TestCase):
  244. """
  245. Feature: File mode can be retrieved via file.mode
  246. """
  247. def test_mode_attr(self):
  248. """ Mode equivalent can be retrieved via property """
  249. fname = self.mktemp()
  250. with File(fname, 'w') as f:
  251. self.assertEqual(f.mode, 'r+')
  252. with File(fname, 'r') as f:
  253. self.assertEqual(f.mode, 'r')
  254. def test_mode_external(self):
  255. """ Mode property works for files opened via external links
  256. Issue 190.
  257. """
  258. fname1 = self.mktemp()
  259. fname2 = self.mktemp()
  260. f1 = File(fname1, 'w')
  261. f1.close()
  262. f2 = File(fname2, 'w')
  263. try:
  264. f2['External'] = h5py.ExternalLink(fname1, '/')
  265. f3 = f2['External'].file
  266. self.assertEqual(f3.mode, 'r+')
  267. finally:
  268. f2.close()
  269. f3.close()
  270. f2 = File(fname2, 'r')
  271. try:
  272. f3 = f2['External'].file
  273. self.assertEqual(f3.mode, 'r')
  274. finally:
  275. f2.close()
  276. f3.close()
  277. class TestDrivers(TestCase):
  278. """
  279. Feature: Files can be opened with low-level HDF5 drivers. Does not
  280. include MPI drivers (see bottom).
  281. """
  282. @ut.skipUnless(os.name == 'posix', "Stdio driver is supported on posix")
  283. def test_stdio(self):
  284. """ Stdio driver is supported on posix """
  285. fid = File(self.mktemp(), 'w', driver='stdio')
  286. self.assertTrue(fid)
  287. self.assertEqual(fid.driver, 'stdio')
  288. fid.close()
  289. # Testing creation with append flag
  290. fid = File(self.mktemp(), 'a', driver='stdio')
  291. self.assertTrue(fid)
  292. self.assertEqual(fid.driver, 'stdio')
  293. fid.close()
  294. @ut.skipUnless(direct_vfd,
  295. "DIRECT driver is supported on Linux if hdf5 is "
  296. "built with the appriorate flags.")
  297. def test_direct(self):
  298. """ DIRECT driver is supported on Linux"""
  299. fid = File(self.mktemp(), 'w', driver='direct')
  300. self.assertTrue(fid)
  301. self.assertEqual(fid.driver, 'direct')
  302. default_fapl = fid.id.get_access_plist().get_fapl_direct()
  303. fid.close()
  304. # Testing creation with append flag
  305. fid = File(self.mktemp(), 'a', driver='direct')
  306. self.assertTrue(fid)
  307. self.assertEqual(fid.driver, 'direct')
  308. fid.close()
  309. # 2022/02/26: hnmaarrfk
  310. # I'm actually not too sure of the restriction on the
  311. # different valid block_sizes and cbuf_sizes on different hardware
  312. # platforms.
  313. #
  314. # I've learned a few things:
  315. # * cbuf_size: Copy buffer size must be a multiple of block size
  316. # The alignment (on my platform x86-64bit with an NVMe SSD
  317. # could be an integer multiple of 512
  318. #
  319. # To allow HDF5 to do the heavy lifting for different platform,
  320. # We didn't provide any arguments to the first call
  321. # and obtained HDF5's default values there.
  322. # Testing creation with a few different property lists
  323. for alignment, block_size, cbuf_size in [
  324. default_fapl,
  325. (default_fapl[0], default_fapl[1], 3 * default_fapl[1]),
  326. (default_fapl[0] * 2, default_fapl[1], 3 * default_fapl[1]),
  327. (default_fapl[0], 2 * default_fapl[1], 6 * default_fapl[1]),
  328. ]:
  329. with File(self.mktemp(), 'w', driver='direct',
  330. alignment=alignment,
  331. block_size=block_size,
  332. cbuf_size=cbuf_size) as fid:
  333. actual_fapl = fid.id.get_access_plist().get_fapl_direct()
  334. actual_alignment = actual_fapl[0]
  335. actual_block_size = actual_fapl[1]
  336. actual_cbuf_size = actual_fapl[2]
  337. assert actual_alignment == alignment
  338. assert actual_block_size == block_size
  339. assert actual_cbuf_size == actual_cbuf_size
  340. @ut.skipUnless(os.name == 'posix', "Sec2 driver is supported on posix")
  341. def test_sec2(self):
  342. """ Sec2 driver is supported on posix """
  343. fid = File(self.mktemp(), 'w', driver='sec2')
  344. self.assertTrue(fid)
  345. self.assertEqual(fid.driver, 'sec2')
  346. fid.close()
  347. # Testing creation with append flag
  348. fid = File(self.mktemp(), 'a', driver='sec2')
  349. self.assertTrue(fid)
  350. self.assertEqual(fid.driver, 'sec2')
  351. fid.close()
  352. def test_core(self):
  353. """ Core driver is supported (no backing store) """
  354. fname = self.mktemp()
  355. fid = File(fname, 'w', driver='core', backing_store=False)
  356. self.assertTrue(fid)
  357. self.assertEqual(fid.driver, 'core')
  358. fid.close()
  359. self.assertFalse(os.path.exists(fname))
  360. # Testing creation with append flag
  361. fid = File(self.mktemp(), 'a', driver='core')
  362. self.assertTrue(fid)
  363. self.assertEqual(fid.driver, 'core')
  364. fid.close()
  365. def test_backing(self):
  366. """ Core driver saves to file when backing store used """
  367. fname = self.mktemp()
  368. fid = File(fname, 'w', driver='core', backing_store=True)
  369. fid.create_group('foo')
  370. fid.close()
  371. fid = File(fname, 'r')
  372. assert 'foo' in fid
  373. fid.close()
  374. # keywords for other drivers are invalid when using the default driver
  375. with self.assertRaises(TypeError):
  376. File(fname, 'w', backing_store=True)
  377. def test_readonly(self):
  378. """ Core driver can be used to open existing files """
  379. fname = self.mktemp()
  380. fid = File(fname, 'w')
  381. fid.create_group('foo')
  382. fid.close()
  383. fid = File(fname, 'r', driver='core')
  384. self.assertTrue(fid)
  385. assert 'foo' in fid
  386. with self.assertRaises(ValueError):
  387. fid.create_group('bar')
  388. fid.close()
  389. def test_blocksize(self):
  390. """ Core driver supports variable block size """
  391. fname = self.mktemp()
  392. fid = File(fname, 'w', driver='core', block_size=1024,
  393. backing_store=False)
  394. self.assertTrue(fid)
  395. fid.close()
  396. def test_split(self):
  397. """ Split stores metadata in a separate file """
  398. fname = self.mktemp()
  399. fid = File(fname, 'w', driver='split')
  400. fid.close()
  401. self.assertTrue(os.path.exists(fname + '-m.h5'))
  402. fid = File(fname, 'r', driver='split')
  403. self.assertTrue(fid)
  404. fid.close()
  405. def test_fileobj(self):
  406. """ Python file object driver is supported """
  407. tf = tempfile.TemporaryFile()
  408. fid = File(tf, 'w', driver='fileobj')
  409. self.assertTrue(fid)
  410. self.assertEqual(fid.driver, 'fileobj')
  411. fid.close()
  412. # Driver must be 'fileobj' for file-like object if specified
  413. with self.assertRaises(ValueError):
  414. File(tf, 'w', driver='core')
  415. tf.close()
  416. # TODO: family driver tests
  417. @pytest.mark.skipif(
  418. h5py.version.hdf5_version_tuple[0] == 1 and
  419. h5py.version.hdf5_version_tuple[1] % 2 != 0 ,
  420. reason='Not HDF5 release version 1.x.y'
  421. )
  422. class TestNewLibver(TestCase):
  423. """
  424. Feature: File format compatibility bounds can be specified when
  425. opening a file.
  426. """
  427. @classmethod
  428. def setUpClass(cls):
  429. super().setUpClass()
  430. # Current latest library bound label
  431. if h5py.version.hdf5_version_tuple < (1, 11, 4):
  432. cls.latest = 'v110'
  433. elif h5py.version.hdf5_version_tuple < (1, 13, 0):
  434. cls.latest = 'v112'
  435. elif h5py.version.hdf5_version_tuple < (2, 0, 0):
  436. cls.latest = 'v114'
  437. else:
  438. cls.latest = 'v200'
  439. def test_default(self):
  440. """ Opening with no libver arg """
  441. f = File(self.mktemp(), 'w')
  442. self.assertEqual(f.libver, ('earliest', self.latest))
  443. f.close()
  444. def test_single(self):
  445. """ Opening with single libver arg """
  446. f = File(self.mktemp(), 'w', libver='latest')
  447. self.assertEqual(f.libver, (self.latest, self.latest))
  448. f.close()
  449. def test_single_v108(self):
  450. """ Opening with "v108" libver arg """
  451. f = File(self.mktemp(), 'w', libver='v108')
  452. self.assertEqual(f.libver, ('v108', self.latest))
  453. f.close()
  454. def test_single_v110(self):
  455. """ Opening with "v110" libver arg """
  456. f = File(self.mktemp(), 'w', libver='v110')
  457. self.assertEqual(f.libver, ('v110', self.latest))
  458. f.close()
  459. @ut.skipIf(h5py.version.hdf5_version_tuple < (1, 11, 4),
  460. 'Requires HDF5 1.11.4 or later')
  461. def test_single_v112(self):
  462. """ Opening with "v112" libver arg """
  463. f = File(self.mktemp(), 'w', libver='v112')
  464. self.assertEqual(f.libver, ('v112', self.latest))
  465. f.close()
  466. @ut.skipIf(h5py.version.hdf5_version_tuple < (1, 14, 0),
  467. 'Requires HDF5 1.14 or later')
  468. def test_single_v114(self):
  469. """ Opening with "v114" libver arg """
  470. f = File(self.mktemp(), 'w', libver='v114')
  471. self.assertEqual(f.libver, ('v114', self.latest))
  472. f.close()
  473. @ut.skipIf(h5py.version.hdf5_version_tuple < (2, 0, 0),
  474. 'Requires HDF5 2.0 or later')
  475. def test_single_v200(self):
  476. """ Opening with "v200" libver arg """
  477. f = File(self.mktemp(), 'w', libver='v200')
  478. self.assertEqual(f.libver, ('v200', self.latest))
  479. f.close()
  480. def test_multiple(self):
  481. """ Opening with two libver args """
  482. f = File(self.mktemp(), 'w', libver=('earliest', 'v108'))
  483. self.assertEqual(f.libver, ('earliest', 'v108'))
  484. f.close()
  485. def test_none(self):
  486. """ Omitting libver arg results in maximum compatibility """
  487. f = File(self.mktemp(), 'w')
  488. self.assertEqual(f.libver, ('earliest', self.latest))
  489. f.close()
  490. class TestUserblock(TestCase):
  491. """
  492. Feature: Files can be create with user blocks
  493. """
  494. def test_create_blocksize(self):
  495. """ User blocks created with w, w-, x and properties work correctly """
  496. f = File(self.mktemp(), 'w-', userblock_size=512)
  497. try:
  498. self.assertEqual(f.userblock_size, 512)
  499. finally:
  500. f.close()
  501. f = File(self.mktemp(), 'x', userblock_size=512)
  502. try:
  503. self.assertEqual(f.userblock_size, 512)
  504. finally:
  505. f.close()
  506. f = File(self.mktemp(), 'w', userblock_size=512)
  507. try:
  508. self.assertEqual(f.userblock_size, 512)
  509. finally:
  510. f.close()
  511. # User block size must be an integer
  512. with self.assertRaises(ValueError):
  513. File(self.mktemp(), 'w', userblock_size='non')
  514. def test_write_only(self):
  515. """ User block only allowed for write """
  516. name = self.mktemp()
  517. f = File(name, 'w')
  518. f.close()
  519. with self.assertRaises(ValueError):
  520. f = h5py.File(name, 'r', userblock_size=512)
  521. with self.assertRaises(ValueError):
  522. f = h5py.File(name, 'r+', userblock_size=512)
  523. def test_match_existing(self):
  524. """ User block size must match that of file when opening for append """
  525. name = self.mktemp()
  526. f = File(name, 'w', userblock_size=512)
  527. f.close()
  528. with self.assertRaises(ValueError):
  529. f = File(name, 'a', userblock_size=1024)
  530. f = File(name, 'a', userblock_size=512)
  531. try:
  532. self.assertEqual(f.userblock_size, 512)
  533. finally:
  534. f.close()
  535. def test_power_of_two(self):
  536. """ User block size must be a power of 2 and at least 512 """
  537. name = self.mktemp()
  538. with self.assertRaises(ValueError):
  539. f = File(name, 'w', userblock_size=128)
  540. with self.assertRaises(ValueError):
  541. f = File(name, 'w', userblock_size=513)
  542. with self.assertRaises(ValueError):
  543. f = File(name, 'w', userblock_size=1023)
  544. def test_write_block(self):
  545. """ Test that writing to a user block does not destroy the file """
  546. name = self.mktemp()
  547. f = File(name, 'w', userblock_size=512)
  548. f.create_group("Foobar")
  549. f.close()
  550. pyfile = open(name, 'r+b')
  551. try:
  552. pyfile.write(b'X' * 512)
  553. finally:
  554. pyfile.close()
  555. f = h5py.File(name, 'r')
  556. try:
  557. assert "Foobar" in f
  558. finally:
  559. f.close()
  560. pyfile = open(name, 'rb')
  561. try:
  562. self.assertEqual(pyfile.read(512), b'X' * 512)
  563. finally:
  564. pyfile.close()
  565. class TestContextManager(TestCase):
  566. """
  567. Feature: File objects can be used as context managers
  568. """
  569. def test_context_manager(self):
  570. """ File objects can be used in with statements """
  571. with File(self.mktemp(), 'w') as fid:
  572. self.assertTrue(fid)
  573. self.assertTrue(not fid)
  574. @ut.skipIf(not UNICODE_FILENAMES, "Filesystem unicode support required")
  575. class TestUnicode(TestCase):
  576. """
  577. Feature: Unicode filenames are supported
  578. """
  579. def test_unicode(self):
  580. """ Unicode filenames can be used, and retrieved properly via .filename
  581. """
  582. fname = self.mktemp(prefix=chr(0x201a))
  583. fid = File(fname, 'w')
  584. try:
  585. self.assertEqual(fid.filename, fname)
  586. self.assertIsInstance(fid.filename, str)
  587. finally:
  588. fid.close()
  589. def test_unicode_hdf5_python_consistent(self):
  590. """ Unicode filenames can be used, and seen correctly from python
  591. """
  592. fname = self.mktemp(prefix=chr(0x201a))
  593. with File(fname, 'w'):
  594. pass
  595. assert os.path.exists(fname)
  596. def test_nonexistent_file_unicode(self):
  597. """
  598. Modes 'r' and 'r+' do not create files even when given unicode names
  599. """
  600. fname = self.mktemp(prefix=chr(0x201a))
  601. with self.assertRaises(OSError):
  602. File(fname, 'r')
  603. with self.assertRaises(OSError):
  604. File(fname, 'r+')
  605. class TestFileProperty(TestCase):
  606. """
  607. Feature: A File object can be retrieved from any child object,
  608. via the .file property
  609. """
  610. def test_property(self):
  611. """ File object can be retrieved from subgroup """
  612. fname = self.mktemp()
  613. hfile = File(fname, 'w')
  614. try:
  615. hfile2 = hfile['/'].file
  616. self.assertEqual(hfile, hfile2)
  617. finally:
  618. hfile.close()
  619. def test_close(self):
  620. """ All retrieved File objects are closed at the same time """
  621. fname = self.mktemp()
  622. hfile = File(fname, 'w')
  623. grp = hfile.create_group('foo')
  624. hfile2 = grp.file
  625. hfile3 = hfile['/'].file
  626. hfile2.close()
  627. self.assertFalse(hfile)
  628. self.assertFalse(hfile2)
  629. self.assertFalse(hfile3)
  630. def test_mode(self):
  631. """ Retrieved File objects have a meaningful mode attribute """
  632. hfile = File(self.mktemp(), 'w')
  633. try:
  634. grp = hfile.create_group('foo')
  635. self.assertEqual(grp.file.mode, hfile.mode)
  636. finally:
  637. hfile.close()
  638. class TestClose(TestCase):
  639. """
  640. Feature: Files can be closed
  641. """
  642. def test_close(self):
  643. """ Close file via .close method """
  644. fid = File(self.mktemp(), 'w')
  645. self.assertTrue(fid)
  646. fid.close()
  647. self.assertFalse(fid)
  648. def test_closed_file(self):
  649. """ Trying to modify closed file raises ValueError """
  650. fid = File(self.mktemp(), 'w')
  651. fid.close()
  652. with self.assertRaises(ValueError):
  653. fid.create_group('foo')
  654. def test_close_multiple_default_driver(self):
  655. fname = self.mktemp()
  656. f = h5py.File(fname, 'w')
  657. f.create_group("test")
  658. f.close()
  659. f.close()
  660. class TestFlush(TestCase):
  661. """
  662. Feature: Files can be flushed
  663. """
  664. def test_flush(self):
  665. """ Flush via .flush method """
  666. fid = File(self.mktemp(), 'w')
  667. fid.flush()
  668. fid.close()
  669. class TestRepr(TestCase):
  670. """
  671. Feature: File objects provide a helpful __repr__ string
  672. """
  673. def test_repr(self):
  674. """ __repr__ behaves itself when files are open and closed """
  675. fid = File(self.mktemp(), 'w')
  676. self.assertIsInstance(repr(fid), str)
  677. fid.close()
  678. self.assertIsInstance(repr(fid), str)
  679. class TestFilename(TestCase):
  680. """
  681. Feature: The name of a File object can be retrieved via .filename
  682. """
  683. def test_filename(self):
  684. """ .filename behaves properly for string data """
  685. fname = self.mktemp()
  686. fid = File(fname, 'w')
  687. try:
  688. self.assertEqual(fid.filename, fname)
  689. self.assertIsInstance(fid.filename, str)
  690. finally:
  691. fid.close()
  692. class TestCloseInvalidatesOpenObjectIDs(TestCase):
  693. """
  694. Ensure that closing a file invalidates object IDs, as appropriate
  695. """
  696. def test_close(self):
  697. """ Closing a file invalidates any of the file's open objects """
  698. with File(self.mktemp(), 'w') as f1:
  699. g1 = f1.create_group('foo')
  700. self.assertTrue(bool(f1.id))
  701. self.assertTrue(bool(g1.id))
  702. f1.close()
  703. self.assertFalse(bool(f1.id))
  704. self.assertFalse(bool(g1.id))
  705. with File(self.mktemp(), 'w') as f2:
  706. g2 = f2.create_group('foo')
  707. self.assertTrue(bool(f2.id))
  708. self.assertTrue(bool(g2.id))
  709. self.assertFalse(bool(f1.id))
  710. self.assertFalse(bool(g1.id))
  711. def test_close_one_handle(self):
  712. fname = self.mktemp()
  713. with File(fname, 'w') as f:
  714. f.create_group('foo')
  715. f1 = File(fname)
  716. f2 = File(fname)
  717. g1 = f1['foo']
  718. g2 = f2['foo']
  719. assert g1.id.valid
  720. assert g2.id.valid
  721. f1.close()
  722. assert not g1.id.valid
  723. # Closing f1 shouldn't close f2 or objects belonging to it
  724. assert f2.id.valid
  725. assert g2.id.valid
  726. f2.close()
  727. assert not f2.id.valid
  728. assert not g2.id.valid
  729. class TestPathlibSupport(TestCase):
  730. """
  731. Check that h5py doesn't break on pathlib
  732. """
  733. def test_pathlib_accepted_file(self):
  734. """ Check that pathlib is accepted by h5py.File """
  735. with closed_tempfile() as f:
  736. path = pathlib.Path(f)
  737. with File(path, 'w') as f2:
  738. self.assertTrue(True)
  739. def test_pathlib_name_match(self):
  740. """ Check that using pathlib does not affect naming """
  741. with closed_tempfile() as f:
  742. path = pathlib.Path(f)
  743. with File(path, 'w') as h5f1:
  744. pathlib_name = h5f1.filename
  745. with File(f, 'w') as h5f2:
  746. normal_name = h5f2.filename
  747. self.assertEqual(pathlib_name, normal_name)
  748. class TestPickle(TestCase):
  749. """Check that h5py.File can't be pickled"""
  750. def test_dump_error(self):
  751. with File(self.mktemp(), 'w') as f1:
  752. with self.assertRaises(TypeError):
  753. pickle.dumps(f1)
  754. # unittest doesn't work with pytest fixtures (and possibly other features),
  755. # hence no subclassing TestCase
  756. @pytest.mark.mpi
  757. class TestMPI:
  758. def test_mpio(self, mpi_file_name):
  759. """ MPIO driver and options """
  760. from mpi4py import MPI
  761. with File(mpi_file_name, 'w', driver='mpio', comm=MPI.COMM_WORLD) as f:
  762. assert f
  763. assert f.driver == 'mpio'
  764. def test_mpio_append(self, mpi_file_name):
  765. """ Testing creation of file with append """
  766. from mpi4py import MPI
  767. with File(mpi_file_name, 'a', driver='mpio', comm=MPI.COMM_WORLD) as f:
  768. assert f
  769. assert f.driver == 'mpio'
  770. def test_mpi_atomic(self, mpi_file_name):
  771. """ Enable atomic mode for MPIO driver """
  772. from mpi4py import MPI
  773. with File(mpi_file_name, 'w', driver='mpio', comm=MPI.COMM_WORLD) as f:
  774. assert not f.atomic
  775. f.atomic = True
  776. assert f.atomic
  777. def test_close_multiple_mpio_driver(self, mpi_file_name):
  778. """ MPIO driver and options """
  779. from mpi4py import MPI
  780. f = File(mpi_file_name, 'w', driver='mpio', comm=MPI.COMM_WORLD)
  781. f.create_group(make_name())
  782. f.close()
  783. f.close()
  784. class TestSWMRMode(TestCase):
  785. """
  786. Feature: Create file that switches on SWMR mode
  787. """
  788. def test_file_mode_generalizes(self):
  789. fname = self.mktemp()
  790. fid = File(fname, 'w', libver='latest')
  791. g = fid.create_group('foo')
  792. # fid and group member file attribute should have the same mode
  793. assert fid.mode == g.file.mode == 'r+'
  794. fid.swmr_mode = True
  795. # fid and group member file attribute should still be 'r+'
  796. # even though file intent has changed
  797. assert fid.mode == g.file.mode == 'r+'
  798. fid.close()
  799. def test_swmr_mode_consistency(self):
  800. fname = self.mktemp()
  801. fid = File(fname, 'w', libver='latest')
  802. g = fid.create_group('foo')
  803. assert fid.swmr_mode == g.file.swmr_mode == False
  804. fid.swmr_mode = True
  805. # This setter should affect both fid and group member file attribute
  806. assert fid.swmr_mode == g.file.swmr_mode == True
  807. fid.close()
  808. @pytest.mark.skipif("HDF5_USE_FILE_LOCKING" in os.environ,
  809. reason="HDF5_USE_FILE_LOCKING env. var. is set")
  810. class TestFileLocking:
  811. """Test h5py.File file locking option"""
  812. def test_reopen(self, tmp_path):
  813. """Test file locking when opening twice the same file"""
  814. fname = tmp_path / make_name("test{}.h5")
  815. with h5py.File(fname, mode="w", locking=True) as f:
  816. f.flush()
  817. # Opening same file in same process without locking is expected to fail
  818. with pytest.raises(OSError):
  819. with h5py.File(fname, mode="r", locking=False) as h5f_read:
  820. pass
  821. with h5py.File(fname, mode="r", locking=True) as h5f_read:
  822. pass
  823. if h5py.version.hdf5_version_tuple < (1, 14, 4):
  824. with h5py.File(fname, mode="r", locking='best-effort') as h5f_read:
  825. pass
  826. else:
  827. with pytest.raises(OSError):
  828. with h5py.File(fname, mode="r", locking='best-effort') as h5f_read:
  829. pass
  830. def test_unsupported_locking(self, tmp_path):
  831. """Test with erroneous file locking value"""
  832. fname = tmp_path / make_name("test{}.h5")
  833. with pytest.raises(ValueError):
  834. with h5py.File(fname, mode="r", locking='unsupported-value') as h5f_read:
  835. pass
  836. def test_multiprocess(self, tmp_path):
  837. """Test file locking option from different concurrent processes"""
  838. fname = tmp_path / make_name("test{}.h5")
  839. # Create test file
  840. with File(fname, mode="w", locking=True) as f:
  841. f["data"] = 1
  842. ctx = multiprocessing.get_context("spawn")
  843. with (
  844. ProcessPoolExecutor(mp_context=ctx, max_workers=1) as ex,
  845. File(fname, mode="r", locking=False) as f,
  846. ):
  847. # Opening in write mode with locking is expected to work
  848. future = ex.submit(open_and_close, fname, mode="w", locking=True)
  849. future.result(timeout=10)
  850. def open_and_close(*args, **kwargs):
  851. """Open and close HDF5 file, for use in a subprocess"""
  852. with File(*args, **kwargs):
  853. pass
  854. @pytest.mark.skipif(
  855. h5py.version.hdf5_version_tuple < (1, 14, 4),
  856. reason="Requires HDF5 >= 1.14.4",
  857. )
  858. @pytest.mark.skipif(
  859. "HDF5_USE_FILE_LOCKING" in os.environ,
  860. reason="HDF5_USE_FILE_LOCKING env. var. is set",
  861. )
  862. @pytest.mark.parametrize(
  863. 'locking_arg,file_locking_props',
  864. [
  865. (False, (0, 0)),
  866. (True, (1, 0)),
  867. ('best-effort', (1, 1)),
  868. ]
  869. )
  870. def test_file_locking_external_link(tmp_path, locking_arg, file_locking_props):
  871. """Test that same file locking is used for external link"""
  872. fname_main = tmp_path / make_name("test_main{}.h5")
  873. fname_elink = tmp_path / make_name("test_linked{}.h5")
  874. # Create test files
  875. with h5py.File(fname_elink, "w") as f:
  876. f["data"] = 1
  877. with h5py.File(fname_main, "w") as f:
  878. f["link"] = h5py.ExternalLink(fname_elink, "/data")
  879. with h5py.File(fname_main, "r", locking=locking_arg) as f:
  880. locking_info = f.id.get_access_plist().get_file_locking()
  881. assert locking_info == file_locking_props
  882. # Test that external link file is also opened without file locking
  883. elink_dataset = f["link"]
  884. elink_locking_info = elink_dataset.file.id.get_access_plist().get_file_locking()
  885. assert elink_locking_info == file_locking_props
  886. def test_close_gc(tmp_path):
  887. # https://github.com/h5py/h5py/issues/1852
  888. filename = tmp_path / make_name("test{}.h5")
  889. with h5py.File(filename, 'w') as f:
  890. for i in range(100):
  891. f[str(i)] = []
  892. # Ensure that Python's garbage collection doesn't interfere with closing
  893. # a file. Try a few times - the problem is not 100% consistent, but
  894. # normally showed up on the 1st or 2nd iteration for me. -TAK, 2021
  895. for i in range(10):
  896. with h5py.File(filename, 'r') as f:
  897. refs = [d.id for d in f.values()]
  898. refs.append(refs) # Make a reference cycle so GC is involved
  899. del refs # GC is likely to fire while closing the file
  900. @pytest.mark.slow
  901. def test_reproducible_file(tmp_path):
  902. x = make_name("x")
  903. y = make_name("y")
  904. def write(path):
  905. with File(path, 'w', track_order=True) as hf:
  906. g = hf.create_group(x, track_order=True)
  907. g.create_dataset(y, shape=10, dtype='f4')
  908. f1 = tmp_path / make_name("f1{}.h5")
  909. write(f1)
  910. time.sleep(1.1) # Ensure any timestamps are different
  911. f2 = tmp_path / make_name("f2{}.h5")
  912. write(f2)
  913. assert sha256(f1.read_bytes()).hexdigest() == sha256(f2.read_bytes()).hexdigest()