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