Skip to content

Commit f61366e

Browse files
authored
Merge pull request #281 from SCM-NV/cp2k_job
TST: Print the CP2K .out and .err files whenever a test job crashes
2 parents e02a689 + 08050d6 commit f61366e

File tree

8 files changed

+142
-29
lines changed

8 files changed

+142
-29
lines changed

src/qmflows/packages/SCM.py

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -105,7 +105,7 @@ def __init__(self, settings: Optional[Settings],
105105
dill_path: "None | str | os.PathLike[str]" = None,
106106
plams_dir: "None | str | os.PathLike[str]" = None,
107107
work_dir: "None | str | os.PathLike[str]" = None,
108-
status: str = 'done',
108+
status: str = 'successful',
109109
warnings: Optional[WarnMap] = None) -> None:
110110
# Load available property parser from yaml file.
111111
super().__init__(settings, molecule, job_name, dill_path,
@@ -157,7 +157,7 @@ def __init__(self, settings: Optional[Settings],
157157
dill_path: "None | str | os.PathLike[str]" = None,
158158
plams_dir: "None | str | os.PathLike[str]" = None,
159159
work_dir: "None | str | os.PathLike[str]" = None,
160-
status: str = 'done',
160+
status: str = 'successful',
161161
warnings: Optional[WarnMap] = None) -> None:
162162
# Read available propiety parsers from a yaml file
163163
super().__init__(settings, molecule, job_name, dill_path,

src/qmflows/packages/packages.py

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -67,7 +67,7 @@ def __init__(self, settings: Optional[Settings],
6767
dill_path: "None | str | os.PathLike[str]" = None,
6868
plams_dir: "None | str | os.PathLike[str]" = None,
6969
work_dir: "None | str | os.PathLike[str]" = None,
70-
status: str = 'done',
70+
status: str = 'successful',
7171
warnings: Optional[WarnMap] = None) -> None:
7272
"""Initialize a :class:`Result` instance.
7373

src/qmflows/test_utils.py

Lines changed: 61 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -6,6 +6,7 @@
66
.. autosummary::
77
fill_cp2k_defaults
88
get_mm_settings
9+
validate_status
910
PATH
1011
PATH_MOLECULES
1112
HAS_RDKIT
@@ -18,6 +19,7 @@
1819
---
1920
.. autofunction:: fill_cp2k_defaults
2021
.. autofunction:: get_mm_settings
22+
.. autofunction:: validate_status
2123
.. autodata:: PATH
2224
:annotation: : pathlib.Path
2325
.. autodata:: PATH_MOLECULES
@@ -32,19 +34,22 @@
3234
"""
3335

3436
import os
37+
import textwrap
3538
from pathlib import Path
3639

3740
import pytest
3841
from distutils.spawn import find_executable
3942

4043
from .settings import Settings
4144
from .warnings_qmflows import Assertion_Warning
45+
from .packages import Result
4246

4347
__all__ = [
4448
'get_mm_settings',
4549
'PATH',
4650
'PATH_MOLECULES',
4751
'Assertion_Warning',
52+
'validate_status',
4853
'HAS_RDKIT',
4954
'requires_cp2k',
5055
'requires_orca'
@@ -126,6 +131,62 @@ def get_mm_settings() -> Settings:
126131
return s
127132

128133

134+
def _read_result_file(result: Result, extension: str, max_line: int = 100) -> "None | str":
135+
"""Find and read the first file in ``result`` with the provided file extension.
136+
137+
Returns ``None`` if no such file can be found.
138+
"""
139+
root = result.archive["plams_dir"]
140+
if root is None:
141+
return None
142+
143+
iterator = (os.path.join(root, i) for i in os.listdir(root)
144+
if os.path.splitext(i)[1] == extension)
145+
for i in iterator:
146+
with open(i, "r") as f:
147+
ret_list = f.readlines()
148+
ret = "..." if len(ret_list) > max_line else ""
149+
ret += "".join(ret_list[-max_line:])
150+
return textwrap.indent(ret, 4 * " ")
151+
else:
152+
return None
153+
154+
155+
def validate_status(result: Result, *, print_out: bool = True, print_err: bool = True) -> None:
156+
"""Validate the status of the ``qmflows.Result`` object is set to ``"successful"``.
157+
158+
Parameters
159+
----------
160+
result : qmflows.Result
161+
The to-be validated ``Result`` object.
162+
print_out : bool
163+
Whether to included the content of the ``Result`` objects' .out file in the exception.
164+
print_err : bool
165+
Whether to included the content of the ``Result`` objects' .err file in the exception.
166+
167+
Raises
168+
------
169+
AssertionError
170+
Raised when :code:`result.status != "successful"`.
171+
172+
"""
173+
if result.status == "successful":
174+
return None
175+
176+
indent = 4 * " "
177+
msg = f"Unexpected {result.job_name} status: {result.status!r}"
178+
179+
if print_out:
180+
out = _read_result_file(result, ".out")
181+
if out is not None:
182+
msg += f"\n\nout_file:\n{out}"
183+
if print_err:
184+
err = _read_result_file(result, ".err")
185+
if err is not None:
186+
msg += f"\n\nerr_file:\n{err}"
187+
raise AssertionError(msg)
188+
189+
129190
def _has_exec(executable: str) -> bool:
130191
"""Check if the passed executable is installed."""
131192
path = find_executable(executable)

test/test_cp2k_mm_mock.py

Lines changed: 9 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -2,6 +2,7 @@
22

33
import os
44
import shutil
5+
from typing import Callable
56

67
import numpy as np
78
from assertionlib import assertion
@@ -11,7 +12,7 @@
1112
from qmflows import Settings, cp2k_mm, singlepoint, geometry, freq, md, cell_opt
1213
from qmflows.utils import InitRestart
1314
from qmflows.packages.cp2k_mm import CP2KMM_Result
14-
from qmflows.test_utils import get_mm_settings, PATH, PATH_MOLECULES
15+
from qmflows.test_utils import get_mm_settings, validate_status, PATH, PATH_MOLECULES
1516

1617
MOL = Molecule(PATH_MOLECULES / 'Cd68Cl26Se55__26_acetate.xyz')
1718
WORKDIR = PATH / 'output_cp2k_mm'
@@ -46,7 +47,7 @@ def overlap_coords(xyz1: np.ndarray, xyz2: np.ndarray) -> np.ndarray:
4647

4748
def mock_runner(mocker_instance: MockFixture,
4849
settings: Settings = SETTINGS,
49-
jobname: str = 'job') -> CP2KMM_Result:
50+
jobname: str = 'job') -> Callable[..., CP2KMM_Result]:
5051
"""Create a Result instance using a mocked runner."""
5152
run_mocked = mocker_instance.patch("qmflows.run")
5253

@@ -72,7 +73,7 @@ def test_cp2k_singlepoint_mock(mocker: MockFixture) -> None:
7273
run_mocked = mock_runner(mocker, settings=s, jobname="cp2k_mm_sp")
7374

7475
result = run_mocked(job)
75-
assertion.eq(result.status, 'successful')
76+
validate_status(result)
7677

7778
# Compare energies
7879
ref = -15.4431781758
@@ -88,7 +89,7 @@ def test_c2pk_opt_mock(mocker: MockFixture) -> None:
8889
run_mocked = mock_runner(mocker, settings=s, jobname="cp2k_mm_opt")
8990

9091
result = run_mocked(job)
91-
assertion.eq(result.status, 'successful')
92+
validate_status(result)
9293

9394
# Compare energies
9495
ref = -16.865587192150834
@@ -114,7 +115,7 @@ def test_c2pk_freq_mock(mocker: MockFixture) -> None:
114115
run_mocked = mock_runner(mocker, settings=s, jobname="cp2k_mm_freq")
115116

116117
result = run_mocked(job)
117-
assertion.eq(result.status, 'successful')
118+
validate_status(result)
118119

119120
freqs = result.frequencies
120121
freqs_ref = np.load(PATH / 'Cd68Cl26Se55__26_acetate.freq.npy')
@@ -136,7 +137,7 @@ def test_c2pk_md_mock(mocker: MockFixture) -> None:
136137
run_mocked = mock_runner(mocker, settings=s, jobname="cp2k_mm_md")
137138

138139
result = run_mocked(job)
139-
assertion.eq(result.status, 'successful')
140+
validate_status(result)
140141

141142
assertion.isfile(result.results['cp2k-1_1000.restart'])
142143

@@ -172,7 +173,7 @@ def test_c2pk_cell_opt_mock(mocker: MockFixture) -> None:
172173
job = cp2k_mm(s, mol)
173174
run_mocked = mock_runner(mocker, settings=s, jobname="cp2k_mm_cell_opt")
174175
result = run_mocked(job)
175-
assertion.eq(result.status, 'successful')
176+
validate_status(result)
176177

177178
ref_volume = np.load(PATH / 'volume.npy')
178179
ref_coordinates = np.load(PATH / 'coordinates.npy')
@@ -191,6 +192,7 @@ def test_c2pk_npt_mock(mocker: MockFixture) -> None:
191192
job = cp2k_mm(s, None)
192193
run_mocked = mock_runner(mocker, settings=s, jobname="cp2k_mm_npt")
193194
result = run_mocked(job)
195+
validate_status(result)
194196

195197
ref_pressure = np.load(PATH / 'pressure.npy')
196198
np.testing.assert_allclose(result.pressure, ref_pressure)

test/test_cp2k_mock.py

Lines changed: 22 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,6 @@
11
"""Mock CP2K funcionality."""
22
import copy
3+
from typing import Callable
34

45
import pytest
56
import numpy as np
@@ -9,25 +10,34 @@
910

1011
from qmflows import cp2k, templates
1112
from qmflows.packages.cp2k_package import CP2K_Result
12-
from qmflows.test_utils import PATH, PATH_MOLECULES, fill_cp2k_defaults, requires_cp2k
1313
from qmflows.utils import init_restart
1414
from qmflows.common import CP2KVersion
1515
from qmflows.parsers.cp2KParser import get_cp2k_version_run
16+
from qmflows.test_utils import (
17+
PATH,
18+
PATH_MOLECULES,
19+
fill_cp2k_defaults,
20+
requires_cp2k,
21+
validate_status,
22+
)
1623

1724
try:
1825
CP2K_VERSION = get_cp2k_version_run("cp2k.popt")
1926
except Exception:
2027
CP2K_VERSION = CP2KVersion(0, 0)
2128

2229

23-
def mock_runner(mocker_instance, jobname: str) -> CP2K_Result:
30+
def mock_runner(mocker_instance, jobname: str) -> Callable[..., CP2K_Result]:
2431
"""Create a Result instance using a mocked runner."""
2532
run_mocked = mocker_instance.patch("qmflows.run")
26-
dill_path = WORKDIR / jobname / f"{jobname}.dill"
27-
plams_dir = WORKDIR / jobname
28-
run_mocked.return_value = CP2K_Result(templates.geometry, ETHYLENE, jobname,
29-
dill_path=dill_path, plams_dir=plams_dir)
30-
33+
run_mocked.return_value = CP2K_Result(
34+
templates.geometry,
35+
ETHYLENE,
36+
jobname,
37+
status="successful",
38+
dill_path=WORKDIR / jobname / f"{jobname}.dill",
39+
plams_dir=WORKDIR / jobname,
40+
)
3141
return run_mocked
3242

3343
# module constants
@@ -71,6 +81,7 @@ def test_cp2k_singlepoint_mock(mocker: MockFixture):
7181
jobname = "cp2k_job"
7282
run_mocked = mock_runner(mocker, jobname)
7383
rs = run_mocked(job)
84+
validate_status(rs)
7485

7586
# electronic energy
7687
assertion.isfinite(rs.energy)
@@ -91,6 +102,8 @@ def test_c2pk_opt_mock(mocker: MockFixture):
91102

92103
job = cp2k(s, ETHYLENE, job_name=jobname)
93104
rs = run_mocked(job)
105+
validate_status(rs)
106+
94107
# check the optimized geometry
95108
mol = rs.geometry
96109
assertion.len_eq(mol, 6)
@@ -110,6 +123,7 @@ def test_c2pk_freq_mock(mocker: MockFixture):
110123
run_mocked = mock_runner(mocker, jobname)
111124
job = cp2k(s, ETHYLENE, job_name=jobname)
112125
rs = run_mocked(job)
126+
validate_status(rs)
113127

114128
# check properties
115129
assertion.isfinite(rs.enthalpy)
@@ -123,5 +137,6 @@ def test_dir(mocker: MockFixture) -> None:
123137

124138
job = cp2k(s, ETHYLENE, job_name=jobname)
125139
r = run_mocked(job)
140+
validate_status(r)
126141

127142
assertion.issubset(CP2K_Result.prop_mapping.keys(), dir(r))

test/test_using_plams/test_cp2k.py

Lines changed: 10 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -9,7 +9,13 @@
99
from scm.plams import Molecule
1010

1111
from qmflows import Settings, cp2k, run, templates
12-
from qmflows.test_utils import PATH, PATH_MOLECULES, fill_cp2k_defaults, requires_cp2k
12+
from qmflows.test_utils import (
13+
PATH,
14+
PATH_MOLECULES,
15+
fill_cp2k_defaults,
16+
requires_cp2k,
17+
validate_status,
18+
)
1319

1420

1521
@requires_cp2k
@@ -28,7 +34,7 @@ def test_cp2k_opt(tmp_path: Path) -> None:
2834
job = cp2k(s, water)
2935
result = run(job, path=tmp_path, folder="test_cp2k_opt")
3036

31-
assertion.eq(result.status, "successful")
37+
validate_status(result)
3238
assertion.isinstance(result.molecule, Molecule)
3339

3440

@@ -57,7 +63,7 @@ def test_cp2k_singlepoint(tmp_path: Path, mo_index_range: str) -> None:
5763
key = f"test_using_plams/test_cp2k/test_cp2k_singlepoint/{mo_index_range}"
5864
ref = f[key][...].view(np.recarray)
5965

60-
assertion.eq(result.status, "successful")
66+
validate_status(result)
6167
orbitals = result.orbitals
6268
assertion.is_not(orbitals, None)
6369
np.testing.assert_allclose(orbitals.eigenvalues, ref.eigenvalues)
@@ -74,7 +80,7 @@ def test_cp2k_freq(tmp_path: Path) -> None:
7480
job = cp2k(s, mol)
7581
result = run(job, path=tmp_path, folder="test_cp2k_freq")
7682

77-
assertion.eq(result.status, "successful")
83+
validate_status(result)
7884
assertion.isclose(result.free_energy, -10801.971213467135)
7985
assertion.isclose(result.enthalpy, -10790.423489727531)
8086
np.testing.assert_allclose(result.frequencies, [1622.952012, 3366.668885, 3513.89377])

test/test_using_plams/test_cp2k_mm.py

Lines changed: 6 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -6,7 +6,7 @@
66
from scm.plams import Molecule
77

88
from qmflows import Settings, run, cp2k_mm, singlepoint, geometry, freq, md, cell_opt
9-
from qmflows.test_utils import get_mm_settings, PATH, PATH_MOLECULES, requires_cp2k
9+
from qmflows.test_utils import get_mm_settings, validate_status, PATH, PATH_MOLECULES, requires_cp2k
1010

1111
MOL = Molecule(PATH_MOLECULES / 'Cd68Cl26Se55__26_acetate.xyz')
1212

@@ -41,7 +41,7 @@ def test_singlepoint(tmp_path: Path) -> None:
4141

4242
job = cp2k_mm(settings=s, mol=MOL, job_name='cp2k_mm_sp')
4343
result = run(job, path=tmp_path, folder="test_singlepoint")
44-
assertion.eq(result.status, 'successful')
44+
validate_status(result)
4545

4646
# Compare energies
4747
ref = -15.4431781758
@@ -57,7 +57,7 @@ def test_geometry(tmp_path: Path) -> None:
5757

5858
job = cp2k_mm(settings=s, mol=MOL, job_name='cp2k_mm_opt')
5959
result = run(job, path=tmp_path, folder="test_geometry")
60-
assertion.eq(result.status, 'successful')
60+
validate_status(result)
6161

6262
# Compare energies
6363
ref = -16.865587192150834
@@ -84,7 +84,7 @@ def test_freq(tmp_path: Path) -> None:
8484

8585
job = cp2k_mm(settings=s, mol=mol, job_name='cp2k_mm_freq')
8686
result = run(job, path=tmp_path, folder="test_freq")
87-
assertion.eq(result.status, 'successful')
87+
validate_status(result)
8888

8989
freqs = result.frequencies
9090
freqs_ref = np.load(PATH / 'Cd68Cl26Se55__26_acetate.freq.npy')
@@ -109,7 +109,7 @@ def test_md(tmp_path: Path) -> None:
109109

110110
job = cp2k_mm(settings=s, mol=mol, job_name='cp2k_mm_md')
111111
result = run(job, path=tmp_path, folder="test_md")
112-
assertion.eq(result.status, 'successful')
112+
validate_status(result)
113113

114114
plams_results = result.results
115115
assertion.isfile(plams_results['cp2k-1_1000.restart'])
@@ -147,4 +147,4 @@ def test_c2pk_cell_opt(tmp_path: Path) -> None:
147147

148148
job = cp2k_mm(settings=s, mol=mol, job_name='cp2k_mm_cell_opt')
149149
result = run(job, path=tmp_path, folder="test_c2pk_cell_opt")
150-
assertion.eq(result.status, 'successful')
150+
validate_status(result)

0 commit comments

Comments
 (0)