test_group.py 34 KB

12345678910111213141516171819202122232425262728293031323334353637383940414243444546474849505152535455565758596061626364656667686970717273747576777879808182838485868788899091929394959697989910010110210310410510610710810911011111211311411511611711811912012112212312412512612712812913013113213313413513613713813914014114214314414514614714814915015115215315415515615715815916016116216316416516616716816917017117217317417517617717817918018118218318418518618718818919019119219319419519619719819920020120220320420520620720820921021121221321421521621721821922022122222322422522622722822923023123223323423523623723823924024124224324424524624724824925025125225325425525625725825926026126226326426526626726826927027127227327427527627727827928028128228328428528628728828929029129229329429529629729829930030130230330430530630730830931031131231331431531631731831932032132232332432532632732832933033133233333433533633733833934034134234334434534634734834935035135235335435535635735835936036136236336436536636736836937037137237337437537637737837938038138238338438538638738838939039139239339439539639739839940040140240340440540640740840941041141241341441541641741841942042142242342442542642742842943043143243343443543643743843944044144244344444544644744844945045145245345445545645745845946046146246346446546646746846947047147247347447547647747847948048148248348448548648748848949049149249349449549649749849950050150250350450550650750850951051151251351451551651751851952052152252352452552652752852953053153253353453553653753853954054154254354454554654754854955055155255355455555655755855956056156256356456556656756856957057157257357457557657757857958058158258358458558658758858959059159259359459559659759859960060160260360460560660760860961061161261361461561661761861962062162262362462562662762862963063163263363463563663763863964064164264364464564664764864965065165265365465565665765865966066166266366466566666766866967067167267367467567667767867968068168268368468568668768868969069169269369469569669769869970070170270370470570670770870971071171271371471571671771871972072172272372472572672772872973073173273373473573673773873974074174274374474574674774874975075175275375475575675775875976076176276376476576676776876977077177277377477577677777877978078178278378478578678778878979079179279379479579679779879980080180280380480580680780880981081181281381481581681781881982082182282382482582682782882983083183283383483583683783883984084184284384484584684784884985085185285385485585685785885986086186286386486586686786886987087187287387487587687787887988088188288388488588688788888989089189289389489589689789889990090190290390490590690790890991091191291391491591691791891992092192292392492592692792892993093193293393493593693793893994094194294394494594694794894995095195295395495595695795895996096196296396496596696796896997097197297397497597697797897998098198298398498598698798898999099199299399499599699799899910001001100210031004100510061007100810091010101110121013101410151016101710181019102010211022102310241025102610271028102910301031103210331034103510361037103810391040104110421043104410451046104710481049105010511052105310541055105610571058105910601061106210631064106510661067106810691070107110721073107410751076107710781079108010811082
  1. # -*- coding: utf-8 -*-
  2. # This file is part of h5py, a Python interface to the HDF5 library.
  3. #
  4. # http://www.h5py.org
  5. #
  6. # Copyright 2008-2013 Andrew Collette and contributors
  7. #
  8. # License: Standard 3-clause BSD; see "license.txt" for full license terms
  9. # and contributor agreement.
  10. """
  11. Group test module.
  12. Tests all methods and properties of Group objects, with the following
  13. exceptions:
  14. 1. Method create_dataset is tested in module test_dataset
  15. """
  16. import numpy as np
  17. import os
  18. import os.path
  19. import sys
  20. from tempfile import mkdtemp
  21. from collections.abc import MutableMapping
  22. from .common import ut, TestCase
  23. import h5py
  24. from h5py import File, Group, SoftLink, HardLink, ExternalLink
  25. from h5py import Dataset, Datatype
  26. from h5py import h5t
  27. from h5py._hl.compat import filename_encode
  28. # If we can't encode unicode filenames, there's not much point failing tests
  29. # which must fail
  30. try:
  31. filename_encode(u"α")
  32. except UnicodeEncodeError:
  33. NO_FS_UNICODE = True
  34. else:
  35. NO_FS_UNICODE = False
  36. class BaseGroup(TestCase):
  37. def setUp(self):
  38. self.f = File(self.mktemp(), 'w')
  39. def tearDown(self):
  40. if self.f:
  41. self.f.close()
  42. class TestCreate(BaseGroup):
  43. """
  44. Feature: New groups can be created via .create_group method
  45. """
  46. def test_create(self):
  47. """ Simple .create_group call """
  48. grp = self.f.create_group('foo')
  49. self.assertIsInstance(grp, Group)
  50. grp2 = self.f.create_group(b'bar')
  51. self.assertIsInstance(grp, Group)
  52. def test_create_intermediate(self):
  53. """ Intermediate groups can be created automatically """
  54. grp = self.f.create_group('foo/bar/baz')
  55. self.assertEqual(grp.name, '/foo/bar/baz')
  56. grp2 = self.f.create_group(b'boo/bar/baz')
  57. self.assertEqual(grp2.name, '/boo/bar/baz')
  58. def test_create_exception(self):
  59. """ Name conflict causes group creation to fail with ValueError """
  60. self.f.create_group('foo')
  61. with self.assertRaises(ValueError):
  62. self.f.create_group('foo')
  63. def test_unicode(self):
  64. """ Unicode names are correctly stored """
  65. name = u"/Name" + chr(0x4500)
  66. group = self.f.create_group(name)
  67. self.assertEqual(group.name, name)
  68. self.assertEqual(group.id.links.get_info(name.encode('utf8')).cset, h5t.CSET_UTF8)
  69. def test_unicode_default(self):
  70. """ Unicode names convertible to ASCII are stored as ASCII (issue 239)
  71. """
  72. name = u"/Hello, this is a name"
  73. group = self.f.create_group(name)
  74. self.assertEqual(group.name, name)
  75. self.assertEqual(group.id.links.get_info(name.encode('utf8')).cset, h5t.CSET_ASCII)
  76. def test_appropriate_low_level_id(self):
  77. " Binding a group to a non-group identifier fails with ValueError "
  78. dset = self.f.create_dataset('foo', [1])
  79. with self.assertRaises(ValueError):
  80. Group(dset.id)
  81. class TestDatasetAssignment(BaseGroup):
  82. """
  83. Feature: Datasets can be created by direct assignment of data
  84. """
  85. def test_ndarray(self):
  86. """ Dataset auto-creation by direct assignment """
  87. data = np.ones((4,4),dtype='f')
  88. self.f['a'] = data
  89. self.assertIsInstance(self.f['a'], Dataset)
  90. self.assertArrayEqual(self.f['a'][...], data)
  91. def test_name_bytes(self):
  92. data = np.ones((4, 4), dtype='f')
  93. self.f[b'b'] = data
  94. self.assertIsInstance(self.f[b'b'], Dataset)
  95. class TestDtypeAssignment(BaseGroup):
  96. """
  97. Feature: Named types can be created by direct assignment of dtypes
  98. """
  99. def test_dtype(self):
  100. """ Named type creation """
  101. dtype = np.dtype('|S10')
  102. self.f['a'] = dtype
  103. self.assertIsInstance(self.f['a'], Datatype)
  104. self.assertEqual(self.f['a'].dtype, dtype)
  105. def test_name_bytes(self):
  106. """ Named type creation """
  107. dtype = np.dtype('|S10')
  108. self.f[b'b'] = dtype
  109. self.assertIsInstance(self.f[b'b'], Datatype)
  110. class TestRequire(BaseGroup):
  111. """
  112. Feature: Groups can be auto-created, or opened via .require_group
  113. """
  114. def test_open_existing(self):
  115. """ Existing group is opened and returned """
  116. grp = self.f.create_group('foo')
  117. grp2 = self.f.require_group('foo')
  118. self.assertEqual(grp2, grp)
  119. grp3 = self.f.require_group(b'foo')
  120. self.assertEqual(grp3, grp)
  121. def test_create(self):
  122. """ Group is created if it doesn't exist """
  123. grp = self.f.require_group('foo')
  124. self.assertIsInstance(grp, Group)
  125. self.assertEqual(grp.name, '/foo')
  126. def test_require_exception(self):
  127. """ Opening conflicting object results in TypeError """
  128. self.f.create_dataset('foo', (1,), 'f')
  129. with self.assertRaises(TypeError):
  130. self.f.require_group('foo')
  131. def test_intermediate_create_dataset(self):
  132. """ Intermediate is created if it doesn't exist """
  133. dt = h5py.string_dtype()
  134. self.f.require_dataset("foo/bar/baz", (1,), dtype=dt)
  135. group = self.f.get('foo')
  136. assert isinstance(group, Group)
  137. group = self.f.get('foo/bar')
  138. assert isinstance(group, Group)
  139. def test_intermediate_create_group(self):
  140. dt = h5py.string_dtype()
  141. self.f.require_group("foo/bar/baz")
  142. group = self.f.get('foo')
  143. assert isinstance(group, Group)
  144. group = self.f.get('foo/bar')
  145. assert isinstance(group, Group)
  146. group = self.f.get('foo/bar/baz')
  147. assert isinstance(group, Group)
  148. class TestDelete(BaseGroup):
  149. """
  150. Feature: Objects can be unlinked via "del" operator
  151. """
  152. def test_delete(self):
  153. """ Object deletion via "del" """
  154. self.f.create_group('foo')
  155. self.assertIn('foo', self.f)
  156. del self.f['foo']
  157. self.assertNotIn('foo', self.f)
  158. def test_nonexisting(self):
  159. """ Deleting non-existent object raises KeyError """
  160. with self.assertRaises(KeyError):
  161. del self.f['foo']
  162. def test_readonly_delete_exception(self):
  163. """ Deleting object in readonly file raises KeyError """
  164. # Note: it is impossible to restore the old behavior (ValueError)
  165. # without breaking the above test (non-existing objects)
  166. fname = self.mktemp()
  167. hfile = File(fname, 'w')
  168. try:
  169. hfile.create_group('foo')
  170. finally:
  171. hfile.close()
  172. hfile = File(fname, 'r')
  173. try:
  174. with self.assertRaises(KeyError):
  175. del hfile['foo']
  176. finally:
  177. hfile.close()
  178. class TestOpen(BaseGroup):
  179. """
  180. Feature: Objects can be opened via indexing syntax obj[name]
  181. """
  182. def test_open(self):
  183. """ Simple obj[name] opening """
  184. grp = self.f.create_group('foo')
  185. grp2 = self.f['foo']
  186. grp3 = self.f['/foo']
  187. self.assertEqual(grp, grp2)
  188. self.assertEqual(grp, grp3)
  189. def test_nonexistent(self):
  190. """ Opening missing objects raises KeyError """
  191. with self.assertRaises(KeyError):
  192. self.f['foo']
  193. def test_reference(self):
  194. """ Objects can be opened by HDF5 object reference """
  195. grp = self.f.create_group('foo')
  196. grp2 = self.f[grp.ref]
  197. self.assertEqual(grp2, grp)
  198. def test_reference_numpyobj(self):
  199. """ Object can be opened by numpy.object_ containing object ref
  200. Test for issue 181, issue 202.
  201. """
  202. g = self.f.create_group('test')
  203. dt = np.dtype([('a', 'i'),('b', h5py.ref_dtype)])
  204. dset = self.f.create_dataset('test_dset', (1,), dt)
  205. dset[0] =(42,g.ref)
  206. data = dset[0]
  207. self.assertEqual(self.f[data[1]], g)
  208. def test_invalid_ref(self):
  209. """ Invalid region references should raise an exception """
  210. ref = h5py.h5r.Reference()
  211. with self.assertRaises(ValueError):
  212. self.f[ref]
  213. self.f.create_group('x')
  214. ref = self.f['x'].ref
  215. del self.f['x']
  216. with self.assertRaises(Exception):
  217. self.f[ref]
  218. def test_path_type_validation(self):
  219. """ Access with non bytes or str types should raise an exception """
  220. self.f.create_group('group')
  221. with self.assertRaises(TypeError):
  222. self.f[0]
  223. with self.assertRaises(TypeError):
  224. self.f[...]
  225. # TODO: check that regionrefs also work with __getitem__
  226. class TestRepr(BaseGroup):
  227. """Opened and closed groups provide a useful __repr__ string"""
  228. def test_repr(self):
  229. """ Opened and closed groups provide a useful __repr__ string """
  230. g = self.f.create_group('foo')
  231. self.assertIsInstance(repr(g), str)
  232. g.id._close()
  233. self.assertIsInstance(repr(g), str)
  234. g = self.f['foo']
  235. # Closing the file shouldn't break it
  236. self.f.close()
  237. self.assertIsInstance(repr(g), str)
  238. class BaseMapping(BaseGroup):
  239. """
  240. Base class for mapping tests
  241. """
  242. def setUp(self):
  243. self.f = File(self.mktemp(), 'w')
  244. self.groups = ('a', 'b', 'c', 'd')
  245. for x in self.groups:
  246. self.f.create_group(x)
  247. self.f['x'] = h5py.SoftLink('/mongoose')
  248. self.groups = self.groups + ('x',)
  249. def tearDown(self):
  250. if self.f:
  251. self.f.close()
  252. class TestLen(BaseMapping):
  253. """
  254. Feature: The Python len() function returns the number of groups
  255. """
  256. def test_len(self):
  257. """ len() returns number of group members """
  258. self.assertEqual(len(self.f), len(self.groups))
  259. self.f.create_group('e')
  260. self.assertEqual(len(self.f), len(self.groups)+1)
  261. class TestContains(BaseGroup):
  262. """
  263. Feature: The Python "in" builtin tests for membership
  264. """
  265. def test_contains(self):
  266. """ "in" builtin works for membership (byte and Unicode) """
  267. self.f.create_group('a')
  268. self.assertIn(b'a', self.f)
  269. self.assertIn('a', self.f)
  270. self.assertIn(b'/a', self.f)
  271. self.assertIn('/a', self.f)
  272. self.assertNotIn(b'mongoose', self.f)
  273. self.assertNotIn('mongoose', self.f)
  274. def test_exc(self):
  275. """ "in" on closed group returns False (see also issue 174) """
  276. self.f.create_group('a')
  277. self.f.close()
  278. self.assertFalse(b'a' in self.f)
  279. self.assertFalse('a' in self.f)
  280. def test_empty(self):
  281. """ Empty strings work properly and aren't contained """
  282. self.assertNotIn('', self.f)
  283. self.assertNotIn(b'', self.f)
  284. def test_dot(self):
  285. """ Current group "." is always contained """
  286. self.assertIn(b'.', self.f)
  287. self.assertIn('.', self.f)
  288. def test_root(self):
  289. """ Root group (by itself) is contained """
  290. self.assertIn(b'/', self.f)
  291. self.assertIn('/', self.f)
  292. def test_trailing_slash(self):
  293. """ Trailing slashes are unconditionally ignored """
  294. self.f.create_group('group')
  295. self.f['dataset'] = 42
  296. self.assertIn('/group/', self.f)
  297. self.assertIn('group/', self.f)
  298. self.assertIn('/dataset/', self.f)
  299. self.assertIn('dataset/', self.f)
  300. def test_softlinks(self):
  301. """ Broken softlinks are contained, but their members are not """
  302. self.f.create_group('grp')
  303. self.f['/grp/soft'] = h5py.SoftLink('/mongoose')
  304. self.f['/grp/external'] = h5py.ExternalLink('mongoose.hdf5', '/mongoose')
  305. self.assertIn('/grp/soft', self.f)
  306. self.assertNotIn('/grp/soft/something', self.f)
  307. self.assertIn('/grp/external', self.f)
  308. self.assertNotIn('/grp/external/something', self.f)
  309. def test_oddball_paths(self):
  310. """ Technically legitimate (but odd-looking) paths """
  311. self.f.create_group('x/y/z')
  312. self.f['dset'] = 42
  313. self.assertIn('/', self.f)
  314. self.assertIn('//', self.f)
  315. self.assertIn('///', self.f)
  316. self.assertIn('.///', self.f)
  317. self.assertIn('././/', self.f)
  318. grp = self.f['x']
  319. self.assertIn('.//x/y/z', self.f)
  320. self.assertNotIn('.//x/y/z', grp)
  321. self.assertIn('x///', self.f)
  322. self.assertIn('./x///', self.f)
  323. self.assertIn('dset///', self.f)
  324. self.assertIn('/dset//', self.f)
  325. class TestIter(BaseMapping):
  326. """
  327. Feature: You can iterate over group members via "for x in y", etc.
  328. """
  329. def test_iter(self):
  330. """ "for x in y" iteration """
  331. lst = [x for x in self.f]
  332. self.assertSameElements(lst, self.groups)
  333. def test_iter_zero(self):
  334. """ Iteration works properly for the case with no group members """
  335. hfile = File(self.mktemp(), 'w')
  336. try:
  337. lst = [x for x in hfile]
  338. self.assertEqual(lst, [])
  339. finally:
  340. hfile.close()
  341. class TestTrackOrder(BaseGroup):
  342. def populate(self, g):
  343. for i in range(100):
  344. # Mix group and dataset creation.
  345. if i % 10 == 0:
  346. g.create_group(str(i))
  347. else:
  348. g[str(i)] = [i]
  349. def test_track_order(self):
  350. g = self.f.create_group('order', track_order=True) # creation order
  351. self.populate(g)
  352. self.assertEqual(list(g),
  353. [str(i) for i in range(100)])
  354. def test_no_track_order(self):
  355. g = self.f.create_group('order', track_order=False) # name alphanumeric
  356. self.populate(g)
  357. self.assertEqual(list(g),
  358. sorted([str(i) for i in range(100)]))
  359. class TestPy3Dict(BaseMapping):
  360. def test_keys(self):
  361. """ .keys provides a key view """
  362. kv = getattr(self.f, 'keys')()
  363. self.assertSameElements(list(kv), self.groups)
  364. for x in self.groups:
  365. self.assertIn(x, kv)
  366. self.assertEqual(len(kv), len(self.groups))
  367. def test_values(self):
  368. """ .values provides a value view """
  369. vv = getattr(self.f, 'values')()
  370. self.assertSameElements(list(vv), [self.f.get(x) for x in self.groups])
  371. self.assertEqual(len(vv), len(self.groups))
  372. for x in self.groups:
  373. self.assertIn(self.f.get(x), vv)
  374. def test_items(self):
  375. """ .items provides an item view """
  376. iv = getattr(self.f, 'items')()
  377. self.assertSameElements(list(iv), [(x,self.f.get(x)) for x in self.groups])
  378. self.assertEqual(len(iv), len(self.groups))
  379. for x in self.groups:
  380. self.assertIn((x, self.f.get(x)), iv)
  381. class TestAdditionalMappingFuncs(BaseMapping):
  382. """
  383. Feature: Other dict methods (pop, pop_item, clear, update, setdefault) are
  384. available.
  385. """
  386. def setUp(self):
  387. self.f = File(self.mktemp(), 'w')
  388. for x in ('/test/a', '/test/b', '/test/c', '/test/d'):
  389. self.f.create_group(x)
  390. self.group = self.f['test']
  391. def tearDown(self):
  392. if self.f:
  393. self.f.close()
  394. def test_pop_item(self):
  395. """.pop_item exists and removes item"""
  396. key, val = self.group.popitem()
  397. self.assertNotIn(key, self.group)
  398. def test_pop(self):
  399. """.pop exists and removes specified item"""
  400. self.group.pop('a')
  401. self.assertNotIn('a', self.group)
  402. def test_pop_default(self):
  403. """.pop falls back to default"""
  404. # e shouldn't exist as a group
  405. value = self.group.pop('e', None)
  406. self.assertEqual(value, None)
  407. def test_pop_raises(self):
  408. """.pop raises KeyError for non-existence"""
  409. # e shouldn't exist as a group
  410. with self.assertRaises(KeyError):
  411. key = self.group.pop('e')
  412. def test_clear(self):
  413. """.clear removes groups"""
  414. self.group.clear()
  415. self.assertEqual(len(self.group), 0)
  416. def test_update_dict(self):
  417. """.update works with dict"""
  418. new_items = {'e': np.array([42])}
  419. self.group.update(new_items)
  420. self.assertIn('e', self.group)
  421. def test_update_iter(self):
  422. """.update works with list"""
  423. new_items = [
  424. ('e', np.array([42])),
  425. ('f', np.array([42]))
  426. ]
  427. self.group.update(new_items)
  428. self.assertIn('e', self.group)
  429. def test_update_kwargs(self):
  430. """.update works with kwargs"""
  431. new_items = {'e': np.array([42])}
  432. self.group.update(**new_items)
  433. self.assertIn('e', self.group)
  434. def test_setdefault(self):
  435. """.setdefault gets group if it exists"""
  436. value = self.group.setdefault('a')
  437. self.assertEqual(value, self.group.get('a'))
  438. def test_setdefault_with_default(self):
  439. """.setdefault gets default if group doesn't exist"""
  440. # e shouldn't exist as a group
  441. # 42 used as groups should be strings
  442. value = self.group.setdefault('e', np.array([42]))
  443. self.assertEqual(value, 42)
  444. def test_setdefault_no_default(self):
  445. """
  446. .setdefault gets None if group doesn't exist, but as None isn't defined
  447. as data for a dataset, this should raise a TypeError.
  448. """
  449. # e shouldn't exist as a group
  450. with self.assertRaises(TypeError):
  451. self.group.setdefault('e')
  452. class TestGet(BaseGroup):
  453. """
  454. Feature: The .get method allows access to objects and metadata
  455. """
  456. def test_get_default(self):
  457. """ Object is returned, or default if it doesn't exist """
  458. default = object()
  459. out = self.f.get('mongoose', default)
  460. self.assertIs(out, default)
  461. grp = self.f.create_group('a')
  462. out = self.f.get(b'a')
  463. self.assertEqual(out, grp)
  464. def test_get_class(self):
  465. """ Object class is returned with getclass option """
  466. self.f.create_group('foo')
  467. out = self.f.get('foo', getclass=True)
  468. self.assertEqual(out, Group)
  469. self.f.create_dataset('bar', (4,))
  470. out = self.f.get('bar', getclass=True)
  471. self.assertEqual(out, Dataset)
  472. self.f['baz'] = np.dtype('|S10')
  473. out = self.f.get('baz', getclass=True)
  474. self.assertEqual(out, Datatype)
  475. def test_get_link_class(self):
  476. """ Get link classes """
  477. default = object()
  478. sl = SoftLink('/mongoose')
  479. el = ExternalLink('somewhere.hdf5', 'mongoose')
  480. self.f.create_group('hard')
  481. self.f['soft'] = sl
  482. self.f['external'] = el
  483. out_hl = self.f.get('hard', default, getlink=True, getclass=True)
  484. out_sl = self.f.get('soft', default, getlink=True, getclass=True)
  485. out_el = self.f.get('external', default, getlink=True, getclass=True)
  486. self.assertEqual(out_hl, HardLink)
  487. self.assertEqual(out_sl, SoftLink)
  488. self.assertEqual(out_el, ExternalLink)
  489. def test_get_link(self):
  490. """ Get link values """
  491. sl = SoftLink('/mongoose')
  492. el = ExternalLink('somewhere.hdf5', 'mongoose')
  493. self.f.create_group('hard')
  494. self.f['soft'] = sl
  495. self.f['external'] = el
  496. out_hl = self.f.get('hard', getlink=True)
  497. out_sl = self.f.get('soft', getlink=True)
  498. out_el = self.f.get('external', getlink=True)
  499. #TODO: redo with SoftLink/ExternalLink built-in equality
  500. self.assertIsInstance(out_hl, HardLink)
  501. self.assertIsInstance(out_sl, SoftLink)
  502. self.assertEqual(out_sl._path, sl._path)
  503. self.assertIsInstance(out_el, ExternalLink)
  504. self.assertEqual(out_el._path, el._path)
  505. self.assertEqual(out_el._filename, el._filename)
  506. class TestVisit(TestCase):
  507. """
  508. Feature: The .visit and .visititems methods allow iterative access to
  509. group and subgroup members
  510. """
  511. def setUp(self):
  512. self.f = File(self.mktemp(), 'w')
  513. self.groups = [
  514. 'grp1', 'grp1/sg1', 'grp1/sg2', 'grp2', 'grp2/sg1', 'grp2/sg1/ssg1'
  515. ]
  516. for x in self.groups:
  517. self.f.create_group(x)
  518. def tearDown(self):
  519. self.f.close()
  520. def test_visit(self):
  521. """ All subgroups are visited """
  522. l = []
  523. self.f.visit(l.append)
  524. self.assertSameElements(l, self.groups)
  525. def test_visititems(self):
  526. """ All subgroups and contents are visited """
  527. l = []
  528. comp = [(x, self.f[x]) for x in self.groups]
  529. self.f.visititems(lambda x, y: l.append((x,y)))
  530. self.assertSameElements(comp, l)
  531. def test_bailout(self):
  532. """ Returning a non-None value immediately aborts iteration """
  533. x = self.f.visit(lambda x: x)
  534. self.assertEqual(x, self.groups[0])
  535. x = self.f.visititems(lambda x, y: (x,y))
  536. self.assertEqual(x, (self.groups[0], self.f[self.groups[0]]))
  537. class TestSoftLinks(BaseGroup):
  538. """
  539. Feature: Create and manage soft links with the high-level interface
  540. """
  541. def test_spath(self):
  542. """ SoftLink path attribute """
  543. sl = SoftLink('/foo')
  544. self.assertEqual(sl.path, '/foo')
  545. def test_srepr(self):
  546. """ SoftLink path repr """
  547. sl = SoftLink('/foo')
  548. self.assertIsInstance(repr(sl), str)
  549. def test_create(self):
  550. """ Create new soft link by assignment """
  551. g = self.f.create_group('new')
  552. sl = SoftLink('/new')
  553. self.f['alias'] = sl
  554. g2 = self.f['alias']
  555. self.assertEqual(g, g2)
  556. def test_exc(self):
  557. """ Opening dangling soft link results in KeyError """
  558. self.f['alias'] = SoftLink('new')
  559. with self.assertRaises(KeyError):
  560. self.f['alias']
  561. class TestExternalLinks(TestCase):
  562. """
  563. Feature: Create and manage external links
  564. """
  565. def setUp(self):
  566. self.f = File(self.mktemp(), 'w')
  567. self.ename = self.mktemp()
  568. self.ef = File(self.ename, 'w')
  569. self.ef.create_group('external')
  570. self.ef.close()
  571. def tearDown(self):
  572. if self.f:
  573. self.f.close()
  574. if self.ef:
  575. self.ef.close()
  576. def test_epath(self):
  577. """ External link paths attributes """
  578. el = ExternalLink('foo.hdf5', '/foo')
  579. self.assertEqual(el.filename, 'foo.hdf5')
  580. self.assertEqual(el.path, '/foo')
  581. def test_erepr(self):
  582. """ External link repr """
  583. el = ExternalLink('foo.hdf5','/foo')
  584. self.assertIsInstance(repr(el), str)
  585. def test_create(self):
  586. """ Creating external links """
  587. self.f['ext'] = ExternalLink(self.ename, '/external')
  588. grp = self.f['ext']
  589. self.ef = grp.file
  590. self.assertNotEqual(self.ef, self.f)
  591. self.assertEqual(grp.name, '/external')
  592. def test_exc(self):
  593. """ KeyError raised when attempting to open broken link """
  594. self.f['ext'] = ExternalLink(self.ename, '/missing')
  595. with self.assertRaises(KeyError):
  596. self.f['ext']
  597. # I would prefer IOError but there's no way to fix this as the exception
  598. # class is determined by HDF5.
  599. def test_exc_missingfile(self):
  600. """ KeyError raised when attempting to open missing file """
  601. self.f['ext'] = ExternalLink('mongoose.hdf5','/foo')
  602. with self.assertRaises(KeyError):
  603. self.f['ext']
  604. def test_close_file(self):
  605. """ Files opened by accessing external links can be closed
  606. Issue 189.
  607. """
  608. self.f['ext'] = ExternalLink(self.ename, '/')
  609. grp = self.f['ext']
  610. f2 = grp.file
  611. f2.close()
  612. self.assertFalse(f2)
  613. @ut.skipIf(NO_FS_UNICODE, "No unicode filename support")
  614. def test_unicode_encode(self):
  615. """
  616. Check that external links encode unicode filenames properly
  617. Testing issue #732
  618. """
  619. ext_filename = os.path.join(mkdtemp(), u"α.hdf5")
  620. with File(ext_filename, "w") as ext_file:
  621. ext_file.create_group('external')
  622. self.f['ext'] = ExternalLink(ext_filename, '/external')
  623. @ut.skipIf(NO_FS_UNICODE, "No unicode filename support")
  624. def test_unicode_decode(self):
  625. """
  626. Check that external links decode unicode filenames properly
  627. Testing issue #732
  628. """
  629. ext_filename = os.path.join(mkdtemp(), u"α.hdf5")
  630. with File(ext_filename, "w") as ext_file:
  631. ext_file.create_group('external')
  632. ext_file["external"].attrs["ext_attr"] = "test"
  633. self.f['ext'] = ExternalLink(ext_filename, '/external')
  634. self.assertEqual(self.f["ext"].attrs["ext_attr"], "test")
  635. def test_unicode_hdf5_path(self):
  636. """
  637. Check that external links handle unicode hdf5 paths properly
  638. Testing issue #333
  639. """
  640. ext_filename = os.path.join(mkdtemp(), "external.hdf5")
  641. with File(ext_filename, "w") as ext_file:
  642. ext_file.create_group('α')
  643. ext_file["α"].attrs["ext_attr"] = "test"
  644. self.f['ext'] = ExternalLink(ext_filename, '/α')
  645. self.assertEqual(self.f["ext"].attrs["ext_attr"], "test")
  646. class TestExtLinkBugs(TestCase):
  647. """
  648. Bugs: Specific regressions for external links
  649. """
  650. def test_issue_212(self):
  651. """ Issue 212
  652. Fails with:
  653. AttributeError: 'SharedConfig' object has no attribute 'lapl'
  654. """
  655. def closer(x):
  656. def w():
  657. try:
  658. if x:
  659. x.close()
  660. except IOError:
  661. pass
  662. return w
  663. orig_name = self.mktemp()
  664. new_name = self.mktemp()
  665. f = File(orig_name, 'w')
  666. self.addCleanup(closer(f))
  667. f.create_group('a')
  668. f.close()
  669. g = File(new_name, 'w')
  670. self.addCleanup(closer(g))
  671. g['link'] = ExternalLink(orig_name, '/') # note root group
  672. g.close()
  673. h = File(new_name, 'r')
  674. self.addCleanup(closer(h))
  675. self.assertIsInstance(h['link']['a'], Group)
  676. class TestCopy(TestCase):
  677. def setUp(self):
  678. self.f1 = File(self.mktemp(), 'w')
  679. self.f2 = File(self.mktemp(), 'w')
  680. def tearDown(self):
  681. if self.f1:
  682. self.f1.close()
  683. if self.f2:
  684. self.f2.close()
  685. @ut.skipIf(h5py.version.hdf5_version_tuple < (1,8,9),
  686. "Bug in HDF5<1.8.8 prevents copying open dataset")
  687. def test_copy_path_to_path(self):
  688. foo = self.f1.create_group('foo')
  689. foo['bar'] = [1,2,3]
  690. self.f1.copy('foo', 'baz')
  691. baz = self.f1['baz']
  692. self.assertIsInstance(baz, Group)
  693. self.assertArrayEqual(baz['bar'], np.array([1,2,3]))
  694. @ut.skipIf(h5py.version.hdf5_version_tuple < (1,8,9),
  695. "Bug in HDF5<1.8.8 prevents copying open dataset")
  696. def test_copy_path_to_group(self):
  697. foo = self.f1.create_group('foo')
  698. foo['bar'] = [1,2,3]
  699. baz = self.f1.create_group('baz')
  700. self.f1.copy('foo', baz)
  701. baz = self.f1['baz']
  702. self.assertIsInstance(baz, Group)
  703. self.assertArrayEqual(baz['foo/bar'], np.array([1,2,3]))
  704. self.f1.copy('foo', self.f2['/'])
  705. self.assertIsInstance(self.f2['/foo'], Group)
  706. self.assertArrayEqual(self.f2['foo/bar'], np.array([1,2,3]))
  707. @ut.skipIf(h5py.version.hdf5_version_tuple < (1,8,9),
  708. "Bug in HDF5<1.8.8 prevents copying open dataset")
  709. def test_copy_group_to_path(self):
  710. foo = self.f1.create_group('foo')
  711. foo['bar'] = [1,2,3]
  712. self.f1.copy(foo, 'baz')
  713. baz = self.f1['baz']
  714. self.assertIsInstance(baz, Group)
  715. self.assertArrayEqual(baz['bar'], np.array([1,2,3]))
  716. self.f2.copy(foo, 'foo')
  717. self.assertIsInstance(self.f2['/foo'], Group)
  718. self.assertArrayEqual(self.f2['foo/bar'], np.array([1,2,3]))
  719. @ut.skipIf(h5py.version.hdf5_version_tuple < (1,8,9),
  720. "Bug in HDF5<1.8.8 prevents copying open dataset")
  721. def test_copy_group_to_group(self):
  722. foo = self.f1.create_group('foo')
  723. foo['bar'] = [1,2,3]
  724. baz = self.f1.create_group('baz')
  725. self.f1.copy(foo, baz)
  726. baz = self.f1['baz']
  727. self.assertIsInstance(baz, Group)
  728. self.assertArrayEqual(baz['foo/bar'], np.array([1,2,3]))
  729. self.f1.copy(foo, self.f2['/'])
  730. self.assertIsInstance(self.f2['/foo'], Group)
  731. self.assertArrayEqual(self.f2['foo/bar'], np.array([1,2,3]))
  732. @ut.skipIf(h5py.version.hdf5_version_tuple < (1,8,9),
  733. "Bug in HDF5<1.8.8 prevents copying open dataset")
  734. def test_copy_dataset(self):
  735. self.f1['foo'] = [1,2,3]
  736. foo = self.f1['foo']
  737. grp = self.f1.create_group("grp")
  738. self.f1.copy(foo, 'bar')
  739. self.assertArrayEqual(self.f1['bar'], np.array([1,2,3]))
  740. self.f1.copy('foo', 'baz')
  741. self.assertArrayEqual(self.f1['baz'], np.array([1,2,3]))
  742. self.f1.copy(foo, grp)
  743. self.assertArrayEqual(self.f1['/grp/foo'], np.array([1,2,3]))
  744. self.f1.copy('foo', self.f2)
  745. self.assertArrayEqual(self.f2['foo'], np.array([1,2,3]))
  746. self.f2.copy(self.f1['foo'], self.f2, 'bar')
  747. self.assertArrayEqual(self.f2['bar'], np.array([1,2,3]))
  748. @ut.skipIf(h5py.version.hdf5_version_tuple < (1,8,9),
  749. "Bug in HDF5<1.8.8 prevents copying open dataset")
  750. def test_copy_shallow(self):
  751. foo = self.f1.create_group('foo')
  752. bar = foo.create_group('bar')
  753. foo['qux'] = [1,2,3]
  754. bar['quux'] = [4,5,6]
  755. self.f1.copy(foo, 'baz', shallow=True)
  756. baz = self.f1['baz']
  757. self.assertIsInstance(baz, Group)
  758. self.assertIsInstance(baz['bar'], Group)
  759. self.assertEqual(len(baz['bar']), 0)
  760. self.assertArrayEqual(baz['qux'], np.array([1,2,3]))
  761. self.f2.copy(foo, 'foo', shallow=True)
  762. self.assertIsInstance(self.f2['/foo'], Group)
  763. self.assertIsInstance(self.f2['foo/bar'], Group)
  764. self.assertEqual(len(self.f2['foo/bar']), 0)
  765. self.assertArrayEqual(self.f2['foo/qux'], np.array([1,2,3]))
  766. @ut.skipIf(h5py.version.hdf5_version_tuple < (1,8,9),
  767. "Bug in HDF5<1.8.8 prevents copying open dataset")
  768. def test_copy_without_attributes(self):
  769. self.f1['foo'] = [1,2,3]
  770. foo = self.f1['foo']
  771. foo.attrs['bar'] = [4,5,6]
  772. self.f1.copy(foo, 'baz', without_attrs=True)
  773. self.assertArrayEqual(self.f1['baz'], np.array([1,2,3]))
  774. assert 'bar' not in self.f1['baz'].attrs
  775. self.f2.copy(foo, 'baz', without_attrs=True)
  776. self.assertArrayEqual(self.f2['baz'], np.array([1,2,3]))
  777. assert 'bar' not in self.f2['baz'].attrs
  778. @ut.skipIf(h5py.version.hdf5_version_tuple < (1,8,9),
  779. "Bug in HDF5<1.8.8 prevents copying open dataset")
  780. def test_copy_soft_links(self):
  781. self.f1['bar'] = [1, 2, 3]
  782. foo = self.f1.create_group('foo')
  783. foo['baz'] = SoftLink('/bar')
  784. self.f1.copy(foo, 'qux', expand_soft=True)
  785. self.f2.copy(foo, 'foo', expand_soft=True)
  786. del self.f1['bar']
  787. self.assertIsInstance(self.f1['qux'], Group)
  788. self.assertArrayEqual(self.f1['qux/baz'], np.array([1, 2, 3]))
  789. self.assertIsInstance(self.f2['/foo'], Group)
  790. self.assertArrayEqual(self.f2['foo/baz'], np.array([1, 2, 3]))
  791. @ut.skipIf(h5py.version.hdf5_version_tuple < (1,8,9),
  792. "Bug in HDF5<1.8.8 prevents copying open dataset")
  793. def test_copy_external_links(self):
  794. filename = self.f1.filename
  795. self.f1['foo'] = [1,2,3]
  796. self.f2['bar'] = ExternalLink(filename, 'foo')
  797. self.f1.close()
  798. self.f1 = None
  799. self.assertArrayEqual(self.f2['bar'], np.array([1,2,3]))
  800. self.f2.copy('bar', 'baz', expand_external=True)
  801. os.unlink(filename)
  802. self.assertArrayEqual(self.f2['baz'], np.array([1,2,3]))
  803. @ut.skipIf(h5py.version.hdf5_version_tuple < (1,8,9),
  804. "Bug in HDF5<1.8.8 prevents copying open dataset")
  805. def test_copy_refs(self):
  806. self.f1['foo'] = [1,2,3]
  807. self.f1['bar'] = [4,5,6]
  808. foo = self.f1['foo']
  809. bar = self.f1['bar']
  810. foo.attrs['bar'] = bar.ref
  811. self.f1.copy(foo, 'baz', expand_refs=True)
  812. self.assertArrayEqual(self.f1['baz'], np.array([1,2,3]))
  813. baz_bar = self.f1['baz'].attrs['bar']
  814. self.assertArrayEqual(self.f1[baz_bar], np.array([4,5,6]))
  815. # The reference points to a copy of bar, not to bar itself.
  816. self.assertNotEqual(self.f1[baz_bar].name, bar.name)
  817. self.f1.copy('foo', self.f2, 'baz', expand_refs=True)
  818. self.assertArrayEqual(self.f2['baz'], np.array([1,2,3]))
  819. baz_bar = self.f2['baz'].attrs['bar']
  820. self.assertArrayEqual(self.f2[baz_bar], np.array([4,5,6]))
  821. self.f1.copy('/', self.f2, 'root', expand_refs=True)
  822. self.assertArrayEqual(self.f2['root/foo'], np.array([1,2,3]))
  823. self.assertArrayEqual(self.f2['root/bar'], np.array([4,5,6]))
  824. foo_bar = self.f2['root/foo'].attrs['bar']
  825. self.assertArrayEqual(self.f2[foo_bar], np.array([4,5,6]))
  826. # There's only one copy of bar, which the reference points to.
  827. self.assertEqual(self.f2[foo_bar], self.f2['root/bar'])
  828. class TestMove(BaseGroup):
  829. """
  830. Feature: Group.move moves links in a file
  831. """
  832. def test_move_hardlink(self):
  833. """ Moving an object """
  834. grp = self.f.create_group("X")
  835. self.f.move("X", "Y")
  836. self.assertEqual(self.f["Y"], grp)
  837. self.f.move("Y", "new/nested/path")
  838. self.assertEqual(self.f['new/nested/path'], grp)
  839. def test_move_softlink(self):
  840. """ Moving a soft link """
  841. self.f['soft'] = h5py.SoftLink("relative/path")
  842. self.f.move('soft', 'new_soft')
  843. lnk = self.f.get('new_soft', getlink=True)
  844. self.assertEqual(lnk.path, "relative/path")
  845. def test_move_conflict(self):
  846. """ Move conflict raises ValueError """
  847. self.f.create_group("X")
  848. self.f.create_group("Y")
  849. with self.assertRaises(ValueError):
  850. self.f.move("X", "Y")
  851. def test_short_circuit(self):
  852. ''' Test that a null-move works '''
  853. self.f.create_group("X")
  854. self.f.move("X", "X")
  855. class TestMutableMapping(BaseGroup):
  856. '''Tests if the registration of Group as a MutableMapping
  857. behaves as expected
  858. '''
  859. def test_resolution(self):
  860. assert issubclass(Group, MutableMapping)
  861. grp = self.f.create_group("K")
  862. assert isinstance(grp, MutableMapping)
  863. def test_validity(self):
  864. '''
  865. Test that the required functions are implemented.
  866. '''
  867. Group.__getitem__
  868. Group.__setitem__
  869. Group.__delitem__
  870. Group.__iter__
  871. Group.__len__