Skip to content

Commit

Permalink
Fix XML exit code return + add tests
Browse files Browse the repository at this point in the history
  • Loading branch information
mbercx committed Oct 7, 2021
1 parent 62c3ad6 commit f9d401e
Show file tree
Hide file tree
Showing 12 changed files with 151 additions and 95 deletions.
2 changes: 2 additions & 0 deletions aiida_quantumespresso/calculations/projwfc.py
Original file line number Diff line number Diff line change
Expand Up @@ -55,6 +55,8 @@ def define(cls, spec):
spec.output('projections', valid_type=ProjectionData, required=False)
spec.output('bands', valid_type=BandsData, required=False)
spec.default_output_node = 'output_parameters'
spec.exit_code(301, 'ERROR_NO_RETRIEVED_TEMPORARY_FOLDER',
message='The retrieved temporary folder could not be accessed.')
spec.exit_code(303, 'ERROR_OUTPUT_XML_MISSING',
message='The retrieved folder did not contain the required XML file.')
spec.exit_code(310, 'ERROR_OUTPUT_STDOUT_READ',
Expand Down
20 changes: 15 additions & 5 deletions aiida_quantumespresso/parsers/projwfc.py
Original file line number Diff line number Diff line change
Expand Up @@ -12,6 +12,8 @@
from aiida_quantumespresso.parsers.parse_raw.base import (
parse_output_base, convert_qe2aiida_structure, convert_qe_to_kpoints
)
from aiida_quantumespresso.utils.mapping import get_logging_container

from .base import Parser


Expand Down Expand Up @@ -311,9 +313,13 @@ def parse(self, **kwargs):
self.out('output_parameters', Dict(dict=parsed_data))

# Parse the XML to obtain the `structure`, `kpoints` and spin-related settings from the parent calculation
self.exit_code_xml = None
parsed_xml, logs_xml = self._parse_xml(retrieved_temporary_folder)
self.emit_logs(logs_xml)

if self.exit_code_xml:
return self.exit(self.exit_code_xml)

# we create a dictionary the progressively accumulates more info
out_info_dict = {}

Expand Down Expand Up @@ -368,22 +374,26 @@ def _parse_xml(self, retrieved_temporary_folder):
from .parse_xml.exceptions import XMLParseError, XMLUnsupportedFormatError
from .parse_xml.pw.parse import parse_xml

logs = get_logging_container()
parsed_xml = {}

xml_filepath = Path(retrieved_temporary_folder) / self.node.process_class.xml_path.name

if not xml_filepath.exists():
self.exit(self.exit_codes.ERROR_OUTPUT_XML_MISSING)
self.exit_code_xml = self.exit_codes.ERROR_OUTPUT_XML_MISSING
return parsed_xml, logs

try:
with xml_filepath.open('r') as handle:
parsed_xml, logs = parse_xml(handle, None)
except IOError:
self.exit(self.exit_codes.ERROR_OUTPUT_XML_READ)
self.exit_code_xml = self.exit_codes.ERROR_OUTPUT_XML_READ
except XMLParseError:
self.exit(self.exit_codes.ERROR_OUTPUT_XML_PARSE)
self.exit_code_xml = self.exit_codes.ERROR_OUTPUT_XML_PARSE
except XMLUnsupportedFormatError:
self.exit(self.exit_codes.ERROR_OUTPUT_XML_FORMAT)
self.exit_code_xml = self.exit_codes.ERROR_OUTPUT_XML_FORMAT
except Exception:
self.exit(self.exit_codes.ERROR_UNEXPECTED_PARSER_EXCEPTION)
self.exit_code_xml = self.exit_codes.ERROR_UNEXPECTED_PARSER_EXCEPTION

return parsed_xml, logs

Expand Down
10 changes: 8 additions & 2 deletions tests/conftest.py
Original file line number Diff line number Diff line change
Expand Up @@ -259,7 +259,10 @@ def _generate_calc_job_node(
if retrieve_temporary:
dirpath, filenames = retrieve_temporary
for filename in filenames:
shutil.copy(os.path.join(filepath_folder, filename), os.path.join(dirpath, filename))
try:
shutil.copy(os.path.join(filepath_folder, filename), os.path.join(dirpath, filename))
except FileNotFoundError:
pass # To test the absence of files in the retrieve_temporary folder

if filepath_folder:
retrieved = orm.FolderData()
Expand All @@ -268,7 +271,10 @@ def _generate_calc_job_node(
# Remove files that are supposed to be only present in the retrieved temporary folder
if retrieve_temporary:
for filename in filenames:
retrieved.delete_object(filename)
try:
retrieved.delete_object(filename)
except OSError:
pass # To test the absence of files in the retrieve_temporary folder

retrieved.add_incoming(node, link_type=LinkType.CREATE, link_label='retrieved')
retrieved.store()
Expand Down
21 changes: 21 additions & 0 deletions tests/parsers/fixtures/projwfc/xml_format/aiida.out
Original file line number Diff line number Diff line change
@@ -0,0 +1,21 @@

Program PROJWFC v.6.6 starts on 3Oct2021 at 13:44:39

This program is part of the open-source Quantum ESPRESSO suite
for quantum simulation of materials; please cite
"P. Giannozzi et al., J. Phys.:Condens. Matter 21 395502 (2009);
"P. Giannozzi et al., J. Phys.:Condens. Matter 29 465901 (2017);
URL http://www.quantum-espresso.org",
in publications or presentations arising from this work. More details at
http://www.quantum-espresso.org/quote

...... OUTPUT TRIMMED

PROJWFC : 0.62s CPU 0.65s WALL


This run was terminated on: 13:44:40 3Oct2021

=------------------------------------------------------------------------------=
JOB DONE.
=------------------------------------------------------------------------------=
Original file line number Diff line number Diff line change
@@ -0,0 +1,4 @@
<?xml version="1.0" encoding="UTF-8"?>
<qes:espresso xsi:schemaLocation="I do not exist! 😭" Units="Hartree atomic units" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xmlns:qes="http://www.quantum-espresso.org/ns/qes/qes-1.0">
<!--All quantities are in Hartree atomic units unless otherwise specified-->
</qes:espresso>
21 changes: 21 additions & 0 deletions tests/parsers/fixtures/projwfc/xml_missing/aiida.out
Original file line number Diff line number Diff line change
@@ -0,0 +1,21 @@

Program PROJWFC v.6.6 starts on 3Oct2021 at 13:44:39

This program is part of the open-source Quantum ESPRESSO suite
for quantum simulation of materials; please cite
"P. Giannozzi et al., J. Phys.:Condens. Matter 21 395502 (2009);
"P. Giannozzi et al., J. Phys.:Condens. Matter 29 465901 (2017);
URL http://www.quantum-espresso.org",
in publications or presentations arising from this work. More details at
http://www.quantum-espresso.org/quote

...... OUTPUT TRIMMED

PROJWFC : 0.62s CPU 0.65s WALL


This run was terminated on: 13:44:40 3Oct2021

=------------------------------------------------------------------------------=
JOB DONE.
=------------------------------------------------------------------------------=
21 changes: 21 additions & 0 deletions tests/parsers/fixtures/projwfc/xml_parse/aiida.out
Original file line number Diff line number Diff line change
@@ -0,0 +1,21 @@

Program PROJWFC v.6.6 starts on 3Oct2021 at 13:44:39

This program is part of the open-source Quantum ESPRESSO suite
for quantum simulation of materials; please cite
"P. Giannozzi et al., J. Phys.:Condens. Matter 21 395502 (2009);
"P. Giannozzi et al., J. Phys.:Condens. Matter 29 465901 (2017);
URL http://www.quantum-espresso.org",
in publications or presentations arising from this work. More details at
http://www.quantum-espresso.org/quote

...... OUTPUT TRIMMED

PROJWFC : 0.62s CPU 0.65s WALL


This run was terminated on: 13:44:40 3Oct2021

=------------------------------------------------------------------------------=
JOB DONE.
=------------------------------------------------------------------------------=
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
I am not a properly formatted XML file! 😭
146 changes: 58 additions & 88 deletions tests/parsers/test_projwfc.py
Original file line number Diff line number Diff line change
Expand Up @@ -2,23 +2,40 @@
# pylint: disable=redefined-outer-name
"""Tests for the `ProjwfcParser`."""

import pytest

def test_projwfc_nonpolarised(fixture_localhost, generate_calc_job_node, generate_parser, data_regression, tmpdir):
"""Test ``ProjwfcParser`` on the results of a non-polarised ``projwfc.x`` calculation."""
entry_point_calc_job = 'quantumespresso.projwfc'
entry_point_parser = 'quantumespresso.projwfc'

retrieve_temporary_list = ['data-file-schema.xml']
attributes = {'retrieve_temporary_list': retrieve_temporary_list}
@pytest.fixture
def generate_projwfc_node(generate_calc_job_node, fixture_localhost, tmpdir):
"""Fixture to constructure a ``projwfc.x`` calcjob node for a specified test."""

node = generate_calc_job_node(
entry_point_name=entry_point_calc_job,
computer=fixture_localhost,
test_name='nonpolarised',
attributes=attributes,
retrieve_temporary=(tmpdir, retrieve_temporary_list)
)
parser = generate_parser(entry_point_parser)
def _generate_projwfc_node(test_name):
"""Generate a mock ``ProjwfcCalculation`` node for testing the parsing.
:param test_name: The name of the test folder that contains the output files.
"""
entry_point_calc_job = 'quantumespresso.projwfc'

retrieve_temporary_list = ['data-file-schema.xml']
attributes = {'retrieve_temporary_list': retrieve_temporary_list}

node = generate_calc_job_node(
entry_point_name=entry_point_calc_job,
computer=fixture_localhost,
test_name=test_name,
attributes=attributes,
retrieve_temporary=(tmpdir, retrieve_temporary_list)
)
return node

return _generate_projwfc_node


@pytest.mark.parametrize('test_name', ('nonpolarised', 'noncollinear', 'spinorbit'))
def test_projwfc(generate_projwfc_node, generate_parser, data_regression, tmpdir, test_name):
"""Test ``ProjwfcParser`` on the results of a non-polarised ``projwfc.x`` calculation."""
node = generate_projwfc_node(test_name)
parser = generate_parser('quantumespresso.projwfc')
results, calcfunction = parser.parse_from_node(node, store_provenance=False, retrieved_temporary_folder=tmpdir)

assert calcfunction.is_finished, calcfunction.exception
Expand All @@ -35,22 +52,10 @@ def test_projwfc_nonpolarised(fixture_localhost, generate_calc_job_node, generat
})


def test_projwfc_spinpolarised(fixture_localhost, generate_calc_job_node, generate_parser, data_regression, tmpdir):
def test_projwfc_spinpolarised(generate_projwfc_node, generate_parser, data_regression, tmpdir):
"""Test ``ProjwfcParser`` on the results of a spin-polarised ``projwfc.x`` calculation."""
entry_point_calc_job = 'quantumespresso.projwfc'
entry_point_parser = 'quantumespresso.projwfc'

retrieve_temporary_list = ['data-file-schema.xml']
attributes = {'retrieve_temporary_list': retrieve_temporary_list}

node = generate_calc_job_node(
entry_point_name=entry_point_calc_job,
computer=fixture_localhost,
test_name='spinpolarised',
attributes=attributes,
retrieve_temporary=(tmpdir, retrieve_temporary_list)
)
parser = generate_parser(entry_point_parser)
node = generate_projwfc_node('spinpolarised')
parser = generate_parser('quantumespresso.projwfc')
results, calcfunction = parser.parse_from_node(node, store_provenance=False, retrieved_temporary_folder=tmpdir)

assert calcfunction.is_finished, calcfunction.exception
Expand All @@ -70,65 +75,30 @@ def test_projwfc_spinpolarised(fixture_localhost, generate_calc_job_node, genera
})


def test_projwfc_noncollinear(fixture_localhost, generate_calc_job_node, generate_parser, data_regression, tmpdir):
"""Test ``ProjwfcParser`` on the results of a noncollinear ``projwfc.x`` calculation."""
entry_point_calc_job = 'quantumespresso.projwfc'
entry_point_parser = 'quantumespresso.projwfc'

retrieve_temporary_list = ['data-file-schema.xml']
attributes = {'retrieve_temporary_list': retrieve_temporary_list}

def test_projwfc_no_retrieved_temporary(generate_calc_job_node, fixture_localhost, generate_parser):
"""Test ``ProjwfcParser`` fails when the retrieved temporary folder is missing."""
node = generate_calc_job_node(
entry_point_name=entry_point_calc_job,
entry_point_name='quantumespresso.projwfc',
computer=fixture_localhost,
test_name='noncollinear',
attributes=attributes,
retrieve_temporary=(tmpdir, retrieve_temporary_list)
test_name='xml_missing',
)
parser = generate_parser(entry_point_parser)
results, calcfunction = parser.parse_from_node(node, store_provenance=False, retrieved_temporary_folder=tmpdir)

assert calcfunction.is_finished, calcfunction.exception
assert calcfunction.is_finished_ok, calcfunction.exit_message

for link_name in ['output_parameters', 'Dos', 'bands', 'projections']:
assert link_name in results, list(results.keys())

data_regression.check({
'Dos': results['Dos'].attributes,
'bands': results['bands'].attributes,
'projections':
{k: v for k, v in results['projections'].attributes.items() if k not in ['reference_bandsdata_uuid']}
})


def test_projwfc_spinorbit(fixture_localhost, generate_calc_job_node, generate_parser, data_regression, tmpdir):
"""Test ``ProjwfcParser`` on the results of a spinorbit ``projwfc.x`` calculation."""
entry_point_calc_job = 'quantumespresso.projwfc'
entry_point_parser = 'quantumespresso.projwfc'

retrieve_temporary_list = ['data-file-schema.xml']
attributes = {'retrieve_temporary_list': retrieve_temporary_list}

node = generate_calc_job_node(
entry_point_name=entry_point_calc_job,
computer=fixture_localhost,
test_name='spinorbit',
attributes=attributes,
retrieve_temporary=(tmpdir, retrieve_temporary_list)
)
parser = generate_parser(entry_point_parser)
results, calcfunction = parser.parse_from_node(node, store_provenance=False, retrieved_temporary_folder=tmpdir)

assert calcfunction.is_finished, calcfunction.exception
assert calcfunction.is_finished_ok, calcfunction.exit_message

for link_name in ['output_parameters', 'Dos', 'bands', 'projections']:
assert link_name in results, list(results.keys())

data_regression.check({
'Dos': results['Dos'].attributes,
'bands': results['bands'].attributes,
'projections':
{k: v for k, v in results['projections'].attributes.items() if k not in ['reference_bandsdata_uuid']}
})
parser = generate_parser('quantumespresso.projwfc')
_, calcfunction = parser.parse_from_node(node, store_provenance=False)

assert calcfunction.is_failed, calcfunction.process_state
assert calcfunction.exit_status == node.process_class.exit_codes.ERROR_NO_RETRIEVED_TEMPORARY_FOLDER.status


@pytest.mark.parametrize('test_name, exit_status', (
['xml_missing', 303],
['xml_parse', 321],
['xml_format', 322],
))
def test_projwfc_xml_failures(generate_projwfc_node, generate_parser, tmpdir, test_name, exit_status):
"""Test ``ProjwfcParser`` fails when the XML is missing."""
node = generate_projwfc_node(test_name)
parser = generate_parser('quantumespresso.projwfc')
_, calcfunction = parser.parse_from_node(node, store_provenance=False, retrieved_temporary_folder=tmpdir)

assert calcfunction.is_failed, calcfunction.process_state
assert calcfunction.exit_status == exit_status

0 comments on commit f9d401e

Please sign in to comment.