test_metainfo.py 26.4 KB
Newer Older
Markus Scheidgen's avatar
Markus Scheidgen committed
1
2
3
4
#
# Copyright The NOMAD Authors.
#
# This file is part of NOMAD. See https://nomad-lab.eu for further info.
5
6
7
8
9
#
# Licensed under the Apache License, Version 2.0 (the "License");
# you may not use this file except in compliance with the License.
# You may obtain a copy of the License at
#
Markus Scheidgen's avatar
Markus Scheidgen committed
10
#     http://www.apache.org/licenses/LICENSE-2.0
11
12
#
# Unless required by applicable law or agreed to in writing, software
Markus Scheidgen's avatar
Markus Scheidgen committed
13
# distributed under the License is distributed on an "AS IS" BASIS,
14
15
16
# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
# See the License for the specific language governing permissions and
# limitations under the License.
Markus Scheidgen's avatar
Markus Scheidgen committed
17
#
18
19

import pytest
20
import numpy as np
21
import pint.quantity
22
import datetime
23

24
25
from nomad.metainfo.metainfo import (
    MSection, MCategory, Section, Quantity, SubSection, Definition, Package, DeriveError,
Lauri Himanen's avatar
Lauri Himanen committed
26
    MetainfoError, Environment, MResource, Datetime, Annotation, SectionAnnotation,
27
    DefinitionAnnotation, Reference, MProxy, derived, SectionProxy, JSON)
28
from nomad.metainfo.example import Run, VaspRun, System, SystemHash, Parsing, SCC, m_package as example_package
29
from nomad import utils
Lauri Himanen's avatar
Lauri Himanen committed
30
from nomad.units import ureg
31
32

from tests import utils as test_utils
33

34
35
36

def assert_section_def(section_def: Section):
    assert isinstance(section_def, Section)
37
38
39
40
    assert section_def.m_def is not None
    assert isinstance(section_def.m_def, Section)
    assert section_def.m_def.name is not None
    assert section_def.m_def.m_def == Section.m_def
41
42
43
44

    assert section_def.name is not None


Markus Scheidgen's avatar
Markus Scheidgen committed
45
def assert_section_instance(section: MSection):
46
    assert_section_def(section.m_def)
47
48

    if section.m_parent is not None:
49
        assert section.m_parent.m_get_sub_section(section.m_parent_sub_section, section.m_parent_index) == section
50
51
52


class TestM3:
53
    ''' Test for meta-info definition that are used to define other definitions. '''
54

55
    def test_section(self):
56
57
        assert Section.m_def == Section.m_def.m_def
        assert Section.m_def.name == 'Section'
58
59
        assert Section.name is not None
        assert Section.name == Definition.name
60
        assert Section.name.m_def == Quantity.m_def
61
62
        assert Section.description.description is not None

63
        for quantity in iter(Section.m_def.quantities):
64
65
66
67
            assert quantity.name in Section.m_def.all_properties
            assert quantity.name in Section.m_def.all_quantities
            assert quantity.m_parent == Section.m_def

68
        for sub_section in iter(Section.m_def.sub_sections):
69
70
71
72
73
74
75
            assert sub_section.name in Section.m_def.all_properties
            assert sub_section.name in Section.m_def.all_sub_sections
            assert sub_section.sub_section in Section.m_def.all_sub_sections_by_section
            assert sub_section.m_parent == Section.m_def

        assert 'quantities' in Section.m_def.all_sub_sections
        assert 'sub_sections' in Section.m_def.all_sub_sections
76

77
        assert_section_instance(Section.m_def)
78
79

    def test_quantity(self):
80
81
        assert Quantity.m_def.m_def == Section.m_def
        assert Quantity.m_def.name == 'Quantity'
82

83
        assert_section_instance(Quantity.m_def)
84

85
86
87
88
89
    def test_definition(self):
        assert len(Section.m_def.base_sections) == 1
        assert len(Section.m_def.all_base_sections) == 1
        assert Section.m_def.m_follows(Definition.m_def)

90
91

class TestPureReflection:
92
    ''' Test for using meta-info instances without knowing/using the respective definitions. '''
93
94
95
96
97

    def test_instantiation(self):
        test_section_def = Section(name='TestSection')
        test_section_def.m_create(Quantity, name='test_quantity')

Markus Scheidgen's avatar
Markus Scheidgen committed
98
        obj = MSection(m_def=test_section_def)
99
        assert obj.m_def.name == 'TestSection'
100
        # FIXME assert obj.m_get('test_quantity') is None
101
102
        setattr(obj, 'test_quantity', 'test_value')
        assert getattr(obj, 'test_quantity') == 'test_value'
103
104


Markus Scheidgen's avatar
Markus Scheidgen committed
105
class MaterialDefining(MCategory):
106
    '''Quantities that add to what constitutes a different material.'''
Markus Scheidgen's avatar
Markus Scheidgen committed
107
    pass
108

Markus Scheidgen's avatar
Markus Scheidgen committed
109

110
class TestM2:
111
    ''' Test for meta-info definitions. '''
112

113
    def test_basics(self):
114
115
        assert_section_def(Run.m_def)
        assert_section_def(System.m_def)
116

117
    def test_default_section_def(self):
118
        ''' A section class without an explicit section def must set a default section def. '''
119
120
        assert Run.m_def is not None
        assert Run.m_def.name == 'Run'
121
122

    def test_quantities(self):
123
124
        assert len(Run.m_def.extending_sections) == 1
        assert len(Run.m_def.all_quantities) == 3
125
126
        assert Run.m_def.all_quantities['code_name'] in Run.m_def.quantities
        assert Run.m_def.all_quantities['code_name'] == Run.__dict__['code_name']
127
128

    def test_sub_sections(self):
129
        assert len(Run.m_def.sub_sections) == 3
130
131
        assert Run.m_def.all_sub_sections['systems'] in Run.m_def.sub_sections
        assert Run.m_def.all_sub_sections['systems'].sub_section == System.m_def
132
133
        assert len(Run.m_def.all_sub_sections_by_section[System.m_def]) == 1
        assert Run.m_def.all_sub_sections_by_section[System.m_def][0].sub_section == System.m_def
134

135
136
137
138
139
    def test_unset_sub_section(self):
        run = Run()
        assert run.systems == []
        assert run.parsing is None

140
    def test_properties(self):
141
        assert len(Run.m_def.all_properties) == 6
142

143
    def test_get_quantity_def(self):
144
        assert System.n_atoms == System.m_def.all_properties['n_atoms']
145
146

    def test_section_name(self):
147
        assert Run.m_def.name == 'Run'
148
149
150
151
152

    def test_quantity_name(self):
        assert Run.code_name.name == 'code_name'

    def test_section_description(self):
153
154
        assert Run.m_def.description is not None
        assert Run.m_def.description.strip() == Run.m_def.description.strip()
155
156
157

    def test_quantity_description(self):
        assert Run.code_name.description is not None
158
        assert Run.code_name.description == 'The name of the code that was run.'
159
160
        assert Run.code_name.description.strip() == Run.code_name.description.strip()

161
    def test_direct_category(self):
162
163
164
        assert len(System.atom_labels.categories) == 1
        assert SystemHash.m_def in System.atom_labels.categories
        assert System.atom_labels in SystemHash.m_def.definitions
165

166
    def test_package(self):
167
168
        assert example_package.name == 'nomad.metainfo.example'
        assert example_package.description == 'An example metainfo package.'
169
        assert example_package.m_sub_section_count(Package.section_definitions) == 5
170
        assert example_package.m_sub_section_count(Package.category_definitions) == 1
171
        assert len(example_package.all_definitions) == 6
172

Markus Scheidgen's avatar
Markus Scheidgen committed
173
    def test_base_sections(self):
174
        assert Definition.m_def in iter(Section.m_def.base_sections)
Markus Scheidgen's avatar
Markus Scheidgen committed
175
176
177
        assert 'name' in Section.m_def.all_quantities
        assert 'name' in Quantity.m_def.all_quantities

178
179
180
    def test_unit(self):
        assert System.lattice_vectors.unit is not None

181
182
183
184
    def test_extension(self):
        assert getattr(Run, 'x_vasp_raw_format', None) is not None
        assert 'x_vasp_raw_format' in Run.m_def.all_quantities

Markus Scheidgen's avatar
Markus Scheidgen committed
185
186
187
    def test_constraints(self):
        assert len(Run.m_def.constraints) > 0

188
189
190
191
    def test_unique_names(self):
        class TestBase(MSection):
            name = Quantity(type=str)

192
        with pytest.raises(MetainfoError):
193
194
195
196
197
198
199
            class TestSection(TestBase):  # pylint: disable=unused-variable
                name = Quantity(type=int)

    def test_unique_names_extends(self):
        class TestBase(MSection):
            name = Quantity(type=str)

200
        with pytest.raises(MetainfoError):
201
202
203
204
            class TestSection(TestBase):  # pylint: disable=unused-variable
                m_def = Section(extends_base_section=True)
                name = Quantity(type=int)

205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
    def test_alias(self):
        class SubTest(MSection):
            pass

        class Test(MSection):
            one = Quantity(type=str, aliases=['two', 'three'])
            section_one = SubSection(sub_section=SubTest, aliases=['section_two'])

        t = Test()
        t.one = 'value 1'
        assert t.one == 'value 1'
        assert t.two == 'value 1'
        assert t.three == 'value 1'

        t.two = 'value 2'
        assert t.one == 'value 2'
        assert t.two == 'value 2'
        assert t.three == 'value 2'

        sub_section = t.m_create(SubTest)
        assert t.section_one == sub_section
        assert t.section_two == sub_section

228
229
230
231
232
233
    def test_multiple_sub_sections(self):
        class TestSection(MSection):  # pylint: disable=unused-variable
            one = SubSection(sub_section=System)
            two = SubSection(sub_section=System)

        assert len(TestSection.m_def.all_sub_sections_by_section[System.m_def]) == 2
234
235

    def test_dimension_exists(self):
236
237
238
239
        class TestSection(MSection):  # pylint: disable=unused-variable
            test = Quantity(type=str, shape=['does_not_exist'])

        assert len(TestSection.m_def.warnings) > 0
240
241

    def test_dimension_is_int(self):
242
243
244
245
246
        class TestSection(MSection):  # pylint: disable=unused-variable
            dim = Quantity(type=str)
            test = Quantity(type=str, shape=['dim'])

        assert len(TestSection.m_def.warnings) > 0
247
248

    def test_dimension_is_shapeless(self):
249
250
251
252
253
        class TestSection(MSection):  # pylint: disable=unused-variable
            dim = Quantity(type=int, shape=[1])
            test = Quantity(type=str, shape=['dim'])

        assert len(TestSection.m_def.warnings) > 0
254
255

    def test_higher_shapes_require_dtype(self):
256
        with pytest.raises(MetainfoError):
257
258
259
260
            class TestSection(MSection):  # pylint: disable=unused-variable
                test = Quantity(type=int, shape=[3, 3])

    def test_only_extends_one_base(self):
261
        with pytest.raises(MetainfoError):
262
263
264
            class TestSection(Run, System):  # pylint: disable=unused-variable
                m_def = Section(extends_base_section=True)

265
266
267
    def test_qualified_name(self):
        assert System.m_def.qualified_name() == 'nomad.metainfo.example.System'

268
269
270
    def test_derived_virtual(self):
        assert System.n_atoms.virtual

271
    def test_annotations(self):
272
        class TestSectionAnnotation(SectionAnnotation):
273
274
            def init_annotation(self, definition):
                super().init_annotation(definition)
275
                section_cls = definition.section_cls
276
277
                assert definition.name == 'TestSection'
                assert 'test_quantity' in definition.all_quantities
278
279
280
281
282
283
                assert section_cls.test_quantity.m_get_annotations('test').initialized
                assert section_cls.test_quantity.a_test.initialized
                assert section_cls.test_quantity.m_get_annotations('test', as_list=True)[0].initialized
                assert section_cls.test_quantity.m_get_annotations(Annotation).initialized
                assert all(a.initialized for a in section_cls.list_test_quantity.a_test)
                assert all(a.initialized for a in section_cls.list_test_quantity.m_get_annotations(Annotation))
284
285
                self.initialized = True

286
287
288
289
            def new(self, section):
                return dict(test='test annotation')

        class TestQuantityAnnotation(DefinitionAnnotation):
290
291
292
293
294
295
296
297
298
299
300
301
302
303
            def init_annotation(self, definition):
                super().init_annotation(definition)
                assert definition.name in ['test_quantity', 'list_test_quantity']
                assert definition.m_parent is not None
                self.initialized = True

        class TestSection(MSection):
            m_def = Section(a_test=TestSectionAnnotation())

            test_quantity = Quantity(type=str, a_test=TestQuantityAnnotation())
            list_test_quantity = Quantity(
                type=str,
                a_test=[TestQuantityAnnotation(), TestQuantityAnnotation()])

304
305
306
307
        assert TestSection.m_def.a_test.initialized
        assert TestSection.m_def.m_get_annotations(TestSectionAnnotation).initialized

        assert TestSection().a_test == 'test annotation'
308

309
310

class TestM1:
311
    ''' Test for meta-info instances. '''
312
313

    def test_run(self):
Markus Scheidgen's avatar
Markus Scheidgen committed
314
        class Run(MSection):
315
            pass
316
317
318

        run = Run()

319
320
        assert run.m_def == Run.m_def
        assert run.m_def.name == 'Run'
321

322
323
        assert_section_instance(run)

324
    def test_system(self):
Markus Scheidgen's avatar
Markus Scheidgen committed
325
        class System(MSection):
326
            m_def = Section()
327
328
329
330
331
            atom_labels = Quantity(type=str, shape=['1..*'])

        system = System()
        system.atom_labels = ['H']
        assert len(system.atom_labels) == 1
332

333
334
        assert_section_instance(system)

335
    def test_set_none(self):
336
337
338
339
340
341
342
343
344
345
        def run_test(section, quantity):
            assert getattr(section, quantity.name) is not None
            assert section.m_is_set(quantity)

            for _ in range(0, 2):
                setattr(section, quantity.name, None)
                assert not section.m_is_set(quantity)
                assert getattr(section, quantity.name) is None
                assert quantity.name not in section.m_to_dict()

346
347
        run = Run()
        run.code_name = 'test'
348
        run_test(run, Run.code_name)
349

350
351
352
        system = System()
        system.atom_positions = [[0, 0, 0]]
        run_test(system, System.atom_positions)
353
354
355
356
357
358
359
360
361
362
363
364
365
366
367
368
369

    def test_set_subsection(self):
        run = Run()
        first = Parsing()
        run.parsing = first
        assert first.m_parent == run
        assert run.parsing == first

        second = Parsing()
        run.parsing = second
        assert first.m_parent is None
        assert second.m_parent == run
        assert run.parsing == second

        run.parsing = None
        assert run.parsing is None

370
    def test_defaults(self):
371
        assert len(System().periodic_dimensions) == 3
372
        assert System().atom_labels is None
373

374
        with pytest.raises(AttributeError):
Markus Scheidgen's avatar
Markus Scheidgen committed
375
            getattr(System(), 'does_not_exist')
376
377

    def test_m_section(self):
378
        assert Run().m_def == Run.m_def
379

380
381
    def test_children_parent(self):
        run = Run()
382
383
        system1 = run.m_create(System)
        run.m_create(System)
384

385
386
387
        assert run.systems[0] == system1  # pylint: disable=E1101
        assert run.m_get_sub_section(Run.systems, 0) == system1
        assert run.m_sub_section_count(Run.systems) == 2
388

389
390
391
392
393
394
395
396
397
398
399
400
401
402
403
    def test_parent_repeats(self):
        run = Run()
        system = run.m_create(System)

        assert system.m_parent == run
        assert system.m_parent_index == 0

    def test_parent_not_repeats(self):
        run = Run()
        parsing = run.m_create(Parsing)

        assert parsing.m_parent == run
        assert parsing.m_parent_index == -1

    def test_wrong_type(self):
404
        with pytest.raises(TypeError):
405
406
407
            Run().code_name = 1

    def test_wrong_shape_1(self):
408
        with pytest.raises(TypeError):
409
410
411
            Run().code_name = ['name']

    def test_wrong_shape_2(self):
412
        with pytest.raises(TypeError):
413
            System().atom_labels = 'label'
414

415
    def test_np_array(self):
416
417
        system = System()
        system.atom_positions = [[1, 2, 3]]
418
419
        assert isinstance(system.atom_positions, pint.quantity._Quantity)

420
421
422
423
424
425
426
427
428
    def test_np_scalar(self):
        class TestSection(MSection):
            test_quantity = Quantity(type=np.dtype('int16'))

        test_section = TestSection()
        test_section.test_quantity = 12
        assert test_section.test_quantity == 12
        assert type(test_section.test_quantity) == np.int16

429
430
    def test_unit_conversion(self):
        system = System()
Lauri Himanen's avatar
Lauri Himanen committed
431
432
433
        system.atom_positions = [[1, 2, 3]] * ureg.angstrom
        assert system.atom_positions.units == ureg.meter
        assert system.atom_positions[0][0] < 0.1 * ureg.meter
434

Markus Scheidgen's avatar
Markus Scheidgen committed
435
436
437
    def test_synonym(self):
        system = System()
        system.lattice_vectors = [[1.2e-10, 0, 0], [0, 1.2e-10, 0], [0, 0, 1.2e-10]]
438
439
        assert isinstance(system.lattice_vectors, pint.quantity._Quantity)
        assert isinstance(system.unit_cell, pint.quantity._Quantity)
440
        assert np.array_equal(system.unit_cell.magnitude, system.lattice_vectors.magnitude)
Markus Scheidgen's avatar
Markus Scheidgen committed
441

442
443
444
445
    @pytest.fixture(scope='function')
    def example_data(self):
        run = Run()
        run.code_name = 'test code name'
446
        run.m_create(Parsing)
447
        system: System = run.m_create(System)
448
        system.atom_labels = ['H', 'H', 'O']
449
        system.atom_positions = np.array([[1.2e-10, 0, 0], [0, 1.2e-10, 0], [0, 0, 1.2e-10]])
450
        system.atom_labels = np.array(['H', 'H', 'O'])
451
452
453
454
        return run

    def assert_example_data(self, data: Run):
        assert_section_instance(data)
455
        assert data.m_def == Run.m_def
456
        assert data.code_name == 'test code name'
457
        system: System = data.systems[0]
458
        assert_section_instance(system)
459
        assert system.m_def == System.m_def
460
        assert system.n_atoms == 3
461
        assert system.atom_labels == ['H', 'H', 'O']
462
        assert isinstance(system.atom_positions, pint.quantity._Quantity)
463
464
465
466
467
468

    def test_to_dict(self, example_data):
        dct = example_data.m_to_dict()
        new_example_data = Run.m_from_dict(dct)

        self.assert_example_data(new_example_data)
469

470
471
472
473
474
475
476
477
    def test_to_dict_category_filter(self, example_data: Run):
        system = example_data.systems[0]
        system.system_type = 'bulk'
        dct = system.m_to_dict(categories=[SystemHash])
        assert 'atom_labels' in dct
        assert 'n_atoms' not in dct  # derived
        assert 'system_type' not in dct  # not system hash

478
479
480
481
482
483
484
485
486
    def test_to_dict_defaults(self, example_data):
        dct = example_data.m_to_dict()
        assert 'nomad_version' not in dct['parsing']
        assert 'n_atoms' not in dct['systems'][0]

        dct = example_data.m_to_dict(include_defaults=True)
        assert 'nomad_version' in dct['parsing']
        assert 'n_atoms' not in dct['systems'][0]

487
488
489
    def test_derived(self):
        system = System()

490
        with pytest.raises(DeriveError):
491
492
493
494
            assert system.n_atoms == 3

        system.atom_labels = ['H', 'H', 'O']
        assert system.n_atoms == 3
495
496
497

        assert 'n_atoms' not in system.m_to_dict()
        assert 'n_atoms' in system.m_to_dict(include_derived=True)
498
499
        pass

500
501
502
503
504
505
506
507
508
509
510
511
512
513
514
515
516
    def test_derived_cached(self):
        class TestSection(MSection):
            value = Quantity(type=str)
            list = Quantity(type=str, shape=[1])

            @derived(cached=True)
            def derived(self):
                return self.value + self.list[0]

        assert TestSection.derived.cached
        test_section = TestSection(value='test', list=['1'])
        assert test_section.derived == 'test1'
        test_section.value = '2'
        assert test_section.derived == '21'
        test_section.list[0] = '2'
        assert test_section.derived == '21'

517
518
519
520
    def test_extension(self):
        run = Run()
        run.m_as(VaspRun).x_vasp_raw_format = 'outcar'
        assert run.m_as(VaspRun).x_vasp_raw_format == 'outcar'
521
522
523
524
525
526
527

    def test_resolve(self):
        run = Run()
        system = run.m_create(System)

        assert run.m_resolve('/systems/0') == system
        assert system.m_resolve('/systems/0') == system
Markus Scheidgen's avatar
Markus Scheidgen committed
528
529
530
531
532

    def test_validate(self):
        run = Run()
        run.m_create(System)

533
        errors, _ = run.m_validate()
Markus Scheidgen's avatar
Markus Scheidgen committed
534
        assert len(errors) == 1
535
536
537
538
539
540

    def test_validate_dimension(self):
        system = System()
        system.atom_labels = ['H']
        system.atom_positions = []
        assert len(system.m_validate()) > 0
541
542
543
544
545
546
547

    def test_multiple_sub_sections(self):
        class TestSection(MSection):  # pylint: disable=unused-variable
            one = SubSection(sub_section=System)
            two = SubSection(sub_section=System)

        test_section = TestSection()
548
        with pytest.raises(Exception):
549
550
551
552
553
            test_section.m_create(System)

        test_section.m_create(System, TestSection.one)

        assert test_section.one is not None
554
555
556
557
558
559
560
561
562

    def test_resource(self):
        resource = MResource()
        run = resource.create(Run)
        run.m_create(System)
        run.m_create(System)

        assert len(resource.all(System)) == 2

563
564
565
566
567
568
569
570
571
572
573
574
575
576
577
578
    def test_resource_add(self):
        # adding
        resource = MResource()
        run = Run()
        resource.add(run)
        run.m_create(System)
        run.m_create(System)
        assert len(resource.all(Run)) == 1
        assert len(resource.all(System)) == 2

        # implicitly moving to another resource
        resource = MResource()
        resource.add(run)
        assert len(resource.all(Run)) == 1
        assert len(resource.all(System)) == 2

579
580
581
582
583
584
585
586
    def test_resource_move(self):
        resource = MResource()
        run = resource.create(Run)
        system = run.m_create(System)

        run = Run()
        run.m_add_sub_section(Run.systems, system)

587
588
589
590
591
592
593
594
595
596
597
    def test_mapping(self):
        run = Run()
        run.m_create(Parsing).parser_name = 'test'
        system = run.m_create(System)
        system.atom_labels = ['H', 'O']

        assert run.systems[0].atom_labels == ['H', 'O']
        assert run['systems.0.atom_labels'] == ['H', 'O']
        assert run['systems/0/atom_labels'] == ['H', 'O']
        assert run['parsing.parser_name'] == 'test'

598
599
600
601
602
    def test_np_dtype(self):
        scc = SCC()
        scc.energy_total_0 = 1.0
        scc.an_int = 1
        assert scc.energy_total_0.m == 1.0  # pylint: disable=no-member
Lauri Himanen's avatar
Lauri Himanen committed
603
        assert scc.energy_total_0 == 1.0 * ureg.J
604
605
606
607
608
        assert scc.m_to_dict()['energy_total_0'] == 1.0
        assert scc.an_int == 1
        assert scc.an_int.__class__ == np.int32
        assert scc.an_int.item() == 1  # pylint: disable=no-member

609
610
611
612
613
614
615
    def test_np_allow_wrong_shape(self, caplog):
        resource = MResource(logger=utils.get_logger(__name__))
        scc = resource.create(SCC)
        scc.energy_total_0 = np.array([1.0, 1.0, 1.0])
        scc.m_to_dict()
        test_utils.assert_log(caplog, 'WARN', 'wrong shape')

616
617
618
619
620
621
622
623
624
625
626
627
628
629
630
631
632
633
634
    def test_proxy(self):
        class OtherSection(MSection):
            name = Quantity(type=str)

        class ReferencingSection(MSection):
            proxy = Quantity(type=Reference(OtherSection.m_def))
            sub = SubSection(sub_section=OtherSection.m_def)

        obj = ReferencingSection()
        referenced = obj.m_create(OtherSection)
        referenced.name = 'test_value'
        obj.proxy = referenced

        assert obj.proxy == referenced
        assert obj.m_to_dict()['proxy'] == '/sub'
        assert obj.m_resolve('sub') == referenced
        assert obj.m_resolve('/sub') == referenced

        obj.proxy = MProxy('doesnotexist', m_proxy_section=obj, m_proxy_quantity=ReferencingSection.proxy)
635
        with pytest.raises(ReferenceError):
636
637
638
639
640
641
642
643
644
            obj.proxy.name

        obj.proxy = MProxy('sub', m_proxy_section=obj, m_proxy_quantity=ReferencingSection.proxy)
        assert obj.proxy.name == 'test_value'
        assert not isinstance(obj.proxy, MProxy)

        obj = ReferencingSection.m_from_dict(obj.m_to_dict(with_meta=True))
        assert obj.proxy.name == 'test_value'

645
646
647
648
649
650
651
652
653
654
655
656
657
658
659
660
661
662
    def test_ref_with_section_proxy(self):
        package = Package(name='test_package')

        class OtherSection(MSection):
            name = Quantity(type=str)

        class ReferencingSection(MSection):
            reference = Quantity(type=Reference(SectionProxy('OtherSection')))

        package.m_add_sub_section(Package.section_definitions, OtherSection.m_def)
        package.m_add_sub_section(Package.section_definitions, ReferencingSection.m_def)

        referencing = ReferencingSection()
        reference = OtherSection()
        referencing.reference = reference

        assert referencing.reference == reference

663
664
665
666
667
668
669
670
671
672
673
674
675
676
677
678
679
680
    def test_copy(self):
        run = Run()
        run.m_create(Parsing).parser_name = 'test'
        system = run.m_create(System)
        system.atom_labels = ['H', 'O']

        copy = run.m_copy()
        assert copy is not run
        assert copy.m_def is run.m_def
        assert copy.systems is run.systems

        copy = run.m_copy(deep=True)
        assert copy is not run
        assert copy.systems is not run.systems
        assert copy.systems[0] is not run.systems[0]
        assert copy.systems[0].m_parent_index == 0
        assert copy.systems[0].m_parent_sub_section is run.systems[0].m_parent_sub_section

681
    def test_not_default_defaults(self):
682
683
684
685
686
687
        class TestSection(MSection):
            int_quantity = Quantity(type=int)
            float_quantity = Quantity(type=float)
            bool_quantity = Quantity(type=bool)

        section = TestSection()
688
689
690
        assert section.int_quantity is None
        assert section.float_quantity is None
        assert section.bool_quantity is None
691

692
    @pytest.mark.filterwarnings("ignore")
693
694
695
696
697
698
699
700
701
702
703
704
705
706
707
708
    def test_xpath(self):
        run = Run()
        run.code_name = 'amazingX'
        system = run.m_create(System)
        system.atom_labels = ['H', 'O']
        system.system_type = 'molecule'
        calc = run.m_create(SCC)
        calc.energy_total = -1.20E-23
        calc.system = system

        assert run.m_xpath('code_name') == 'amazingX'
        assert run.m_xpath('systems[-1].system_type') == 'molecule'
        assert run.m_xpath('sccs[0].system.atom_labels') == ['H', 'O']
        assert run.m_xpath('systems[?system_type == `molecule`].atom_labels') == [['H', 'O']]
        assert run.m_xpath('sccs[?energy_total < `1.0E-23`].system') == [{'atom_labels': ['H', 'O'], 'system_type': 'molecule'}]

709

710
711
712
713
714
715
716
717
718
719
720
721
722
723
724
725
726
727
728
729
class TestDatatypes:

    def test_datetime(self):
        class TestSection(MSection):
            datetime = Quantity(type=Datetime)

        obj = TestSection()
        assert obj.datetime is None
        assert 'datetime' not in obj.m_to_dict()

        obj.datetime = datetime.datetime.now()
        assert obj.datetime is not None
        assert isinstance(obj.m_to_dict()['datetime'], str)

        obj.datetime = obj.datetime.isoformat()
        assert obj.datetime is not None
        assert isinstance(obj.m_to_dict()['datetime'], str)

        obj.datetime = None
        assert obj.datetime is None
730
        assert 'datetime' not in obj.m_to_dict()
731

732
733
734
735
736
737
738
739
740
741
742
743
744
745
    def test_json(self):
        class TestSection(MSection):
            json = Quantity(type=JSON)

        obj = TestSection()
        assert obj.json is None
        assert 'json' not in obj.m_to_dict()

        obj.json = dict(test_key='test_value')
        assert obj.json is not None
        assert isinstance(obj.m_to_dict()['json'], dict)

        obj.json = None
        assert obj.json is None
746
        assert 'json' not in obj.m_to_dict()
747

748

749
750
751
752
753
754
755
756
757
758
759
760
761
762
763
class TestEnvironment:

    @pytest.fixture
    def env(self) -> Environment:
        env = Environment()
        env.m_add_sub_section(Environment.packages, example_package)
        return env

    def test_create(self, env):
        assert env is not None

    def test_resolve(self, env: Environment):
        sub_section_system = env.resolve_definition('systems', SubSection)
        assert sub_section_system.m_def == SubSection.m_def
        assert sub_section_system.name == 'systems'