-
Notifications
You must be signed in to change notification settings - Fork 1
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
Devops: Add unit tests for calculation and parser plugin
Unit tests are added for the `CalcJob` and `Parser` plugins. The reference data for the parser tests were taken from the data of the AiiDA archive that was part of the AiiDA common workflow paper: https://archive.materialscloud.org/record/file?filename=acwf-verification_unaries-verification-PBE-v1_results_wien2k.aiida&record_id=1770 The `pytest` package is used for the tests with `pytest-regressions` being used to compare results with reference files. The `pre-commit` extra is renamed to `dev` and these dependencies are added to it. Older versions are declared because currently only Python 3.7 and 3.8 are declared as being supported.
- Loading branch information
Showing
139 changed files
with
14,807 additions
and
5 deletions.
There are no files selected for viewing
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,107 @@ | ||
# -*- coding: utf-8 -*- | ||
"""Tests for the :mod:`aiida_wien2k.calculations.run123_lapw` module.""" | ||
# pylint: disable=redefined-outer-name | ||
from __future__ import annotations | ||
|
||
import io | ||
import typing as t | ||
|
||
import pytest | ||
from aiida.orm import Dict, SinglefileData | ||
|
||
from aiida_wien2k.calculations.run123_lapw import Wien2kRun123Lapw | ||
|
||
|
||
def recursive_merge(left: dict[t.Any, t.Any], right: dict[t.Any, t.Any]) -> None: | ||
"""Recursively merge the ``right`` dictionary into the ``left`` dictionary. | ||
:param left: Base dictionary. | ||
:param right: Dictionary to recurisvely merge on top of ``left`` dictionary. | ||
""" | ||
for key, value in right.items(): | ||
if key in left and isinstance(left[key], dict) and isinstance(value, dict): | ||
recursive_merge(left[key], value) | ||
else: | ||
left[key] = value | ||
|
||
|
||
@pytest.fixture | ||
def generate_inputs(aiida_local_code_factory, generate_structure): | ||
"""Return a dictionary of inputs for the ``Wien2kRun123Lapw`.""" | ||
|
||
def factory(**kwargs): | ||
parameters = {} | ||
recursive_merge(parameters, kwargs.pop("parameters", {})) | ||
|
||
inputs = { | ||
"code": aiida_local_code_factory("wien2k-run123_lapw", "/bin/true"), | ||
"aiida_structure": generate_structure(), | ||
"parameters": Dict(dict=parameters), | ||
"metadata": {"options": {"resources": {"num_machines": 1, "num_mpiprocs_per_machine": 1}}}, | ||
} | ||
inputs.update(**kwargs) | ||
return inputs | ||
|
||
return factory | ||
|
||
|
||
def test_default_structure(generate_calc_job, generate_inputs): | ||
"""Test the plugin for default inputs with structure as ``StructureData``.""" | ||
inputs = generate_inputs() | ||
_, calc_info = generate_calc_job(Wien2kRun123Lapw, inputs=inputs) | ||
|
||
assert calc_info.remote_copy_list == [] | ||
assert sorted(calc_info.retrieve_list) == sorted( | ||
[ | ||
("case/*.scf0"), | ||
("case/*.scf1"), | ||
("case/*.scf2"), | ||
("case/*.scfm"), | ||
("case/*.scfc"), | ||
("case/*.error*"), | ||
("case/*.dayfile"), | ||
("case/*.klist"), | ||
("case/*.in0"), | ||
("case/case.struct"), | ||
] | ||
) | ||
|
||
assert len(calc_info.local_copy_list) == 1 | ||
assert calc_info.local_copy_list[0][-1] == "case/case.struct" | ||
|
||
|
||
def test_default_singlefile(generate_calc_job, generate_inputs): | ||
"""Test the plugin for default inputs with structure as ``SinglefileData``.""" | ||
structure = SinglefileData(io.BytesIO(b"content"), filename="structure.wien2k").store() | ||
inputs = generate_inputs() | ||
inputs.pop("aiida_structure") | ||
inputs["wien2k_structure"] = structure | ||
_, calc_info = generate_calc_job(Wien2kRun123Lapw, inputs=inputs) | ||
|
||
assert calc_info.remote_copy_list == [] | ||
assert sorted(calc_info.retrieve_list) == sorted( | ||
[ | ||
("case/*.scf0"), | ||
("case/*.scf1"), | ||
("case/*.scf2"), | ||
("case/*.scfm"), | ||
("case/*.scfc"), | ||
("case/*.error*"), | ||
("case/*.dayfile"), | ||
("case/*.klist"), | ||
("case/*.in0"), | ||
("case/case.struct"), | ||
] | ||
) | ||
|
||
assert len(calc_info.local_copy_list) == 1 | ||
assert calc_info.local_copy_list[0] == (structure.uuid, "structure.wien2k", "case/case.struct") | ||
|
||
|
||
def test_parameters(generate_calc_job, generate_inputs): | ||
"""Test the ``parameters`` input.""" | ||
parameters = {"-i": "100", "-p": True} | ||
inputs = generate_inputs(parameters=parameters) | ||
_, calc_info = generate_calc_job(Wien2kRun123Lapw, inputs=inputs) | ||
|
||
assert calc_info.codes_info[0].cmdline_params == ["-i", "100", "-p"] |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,138 @@ | ||
# -*- coding: utf-8 -*- | ||
# pylint: disable=redefined-outer-name | ||
"""Module with test fixtures.""" | ||
from __future__ import annotations | ||
|
||
import collections | ||
import pathlib | ||
|
||
import pytest | ||
from aiida.common.folders import Folder | ||
from aiida.common.links import LinkType | ||
from aiida.engine.utils import instantiate_process | ||
from aiida.manage.manager import get_manager | ||
from aiida.orm import CalcJobNode, FolderData, StructureData, TrajectoryData | ||
from aiida.plugins import ParserFactory | ||
from ase.build import bulk | ||
|
||
pytest_plugins = ["aiida.manage.tests.pytest_fixtures"] # pylint: disable=invalid-name | ||
|
||
|
||
@pytest.fixture | ||
def filepath_tests() -> pathlib.Path: | ||
"""Return the path to the tests folder.""" | ||
return pathlib.Path(__file__).resolve().parent | ||
|
||
|
||
@pytest.fixture | ||
def generate_calc_job(tmp_path): | ||
"""Return a factory to generate a :class:`aiida.engine.CalcJob` instance with the given inputs. | ||
The fixture will call ``prepare_for_submission`` and return a tuple of the temporary folder that was passed to it, | ||
as well as the ``CalcInfo`` instance that it returned. | ||
""" | ||
|
||
def factory(process_class, inputs=None, return_process=False): | ||
"""Create a :class:`aiida.engine.CalcJob` instance with the given inputs.""" | ||
manager = get_manager() | ||
runner = manager.get_runner() | ||
process = instantiate_process(runner, process_class, **inputs or {}) | ||
calc_info = process.prepare_for_submission(Folder(tmp_path)) | ||
|
||
if return_process: | ||
return process | ||
|
||
return tmp_path, calc_info | ||
|
||
return factory | ||
|
||
|
||
@pytest.fixture | ||
def generate_calc_job_node(filepath_tests, aiida_localhost, tmp_path): | ||
"""Create and return a :class:`aiida.orm.CalcJobNode` instance.""" | ||
|
||
def flatten_inputs(inputs, prefix=""): | ||
"""Flatten inputs recursively like :meth:`aiida.engine.processes.process::Process._flatten_inputs`.""" | ||
flat_inputs = [] | ||
for key, value in inputs.items(): | ||
if isinstance(value, collections.abc.Mapping): | ||
flat_inputs.extend(flatten_inputs(value, prefix=prefix + key + "__")) | ||
else: | ||
flat_inputs.append((prefix + key, value)) | ||
return flat_inputs | ||
|
||
def factory( | ||
entry_point: str, | ||
directory: str, | ||
test_name: str, | ||
inputs: dict = None, | ||
retrieve_temporary_list: list[str] | None = None, | ||
): | ||
"""Create and return a :class:`aiida.orm.CalcJobNode` instance.""" | ||
node = CalcJobNode( | ||
computer=aiida_localhost, process_type=f"aiida.calculations:{entry_point}" | ||
) | ||
|
||
if inputs: | ||
for link_label, input_node in flatten_inputs(inputs): | ||
input_node.store() | ||
node.add_incoming(input_node, link_type=LinkType.INPUT_CALC, link_label=link_label) | ||
|
||
node.store() | ||
|
||
filepath_retrieved = filepath_tests / "parsers" / "fixtures" / directory / test_name | ||
|
||
retrieved = FolderData() | ||
retrieved.put_object_from_tree(filepath_retrieved) | ||
retrieved.add_incoming(node, link_type=LinkType.CREATE, link_label="retrieved") | ||
retrieved.store() | ||
|
||
if retrieve_temporary_list: | ||
for pattern in retrieve_temporary_list: | ||
for filename in filepath_retrieved.glob(pattern): | ||
filepath = tmp_path / filename.relative_to(filepath_retrieved) | ||
filepath.write_bytes(filename.read_bytes()) | ||
|
||
return node, tmp_path | ||
|
||
return node | ||
|
||
return factory | ||
|
||
|
||
@pytest.fixture(scope="session") | ||
def generate_parser(): | ||
"""Fixture to load a parser class for testing parsers.""" | ||
|
||
def factory(entry_point_name): | ||
"""Fixture to load a parser class for testing parsers. | ||
:param entry_point_name: entry point name of the parser class | ||
:return: the `Parser` sub class | ||
""" | ||
return ParserFactory(entry_point_name) | ||
|
||
return factory | ||
|
||
|
||
@pytest.fixture | ||
def generate_structure(): | ||
"""Return factory to generate a ``StructureData`` instance.""" | ||
|
||
def factory(formula: str = "Si") -> StructureData: | ||
"""Generate a ``StructureData`` instance.""" | ||
atoms = bulk(formula) | ||
return StructureData(ase=atoms) | ||
|
||
return factory | ||
|
||
|
||
@pytest.fixture | ||
def generate_trajectory(generate_structure): | ||
"""Return factory to generate a ``TrajectoryData`` instance.""" | ||
|
||
def factory(formula: str = "Si") -> TrajectoryData: | ||
"""Generate a ``TrajectoryData`` instance.""" | ||
return TrajectoryData(structurelist=[generate_structure(formula=formula)]) | ||
|
||
return factory |
84 changes: 84 additions & 0 deletions
84
tests/parsers/fixtures/scf123/default/_scheduler-stderr.txt
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,84 @@ | ||
NN ENDS | ||
LSTART ENDS | ||
KGEN ENDS | ||
KGEN ENDS | ||
NN ENDS | ||
LSTART ENDS | ||
KGEN ENDS | ||
KGEN ENDS | ||
LAPW0 END | ||
LAPW1 END | ||
LAPW2 END | ||
CORE END | ||
MIXER END | ||
LAPW0 END | ||
LAPW1 END | ||
LAPW2 END | ||
CORE END | ||
MIXER END | ||
LAPW0 END | ||
LAPW1 END | ||
LAPW2 END | ||
CORE END | ||
MIXER END | ||
LAPW0 END | ||
LAPW1 END | ||
LAPW2 END | ||
CORE END | ||
MIXER END | ||
LAPW0 END | ||
LAPW1 END | ||
LAPW2 END | ||
CORE END | ||
MIXER END | ||
LAPW0 END | ||
LAPW1 END | ||
LAPW2 END | ||
CORE END | ||
MIXER END | ||
LAPW0 END | ||
LAPW1 END | ||
LAPW2 END | ||
CORE END | ||
MIXER END | ||
LAPW0 END | ||
LAPW1 END | ||
LAPW2 END | ||
CORE END | ||
MIXER END | ||
LAPW0 END | ||
LAPW1 END | ||
LAPW2 END | ||
CORE END | ||
MIXER END | ||
LAPW0 END | ||
LAPW1 END | ||
LAPW2 END | ||
CORE END | ||
MIXER END | ||
KGEN ENDS | ||
LAPW0 END | ||
LAPW1 END | ||
LAPW2 END | ||
CORE END | ||
MIXER END | ||
LAPW0 END | ||
LAPW1 END | ||
LAPW2 END | ||
CORE END | ||
MIXER END | ||
LAPW0 END | ||
LAPW1 END | ||
LAPW2 END | ||
CORE END | ||
MIXER END | ||
LAPW0 END | ||
LAPW1 END | ||
LAPW2 END | ||
CORE END | ||
MIXER END | ||
LAPW0 END | ||
LAPW1 END | ||
LAPW2 END | ||
CORE END | ||
MIXER END |
Empty file.
Oops, something went wrong.