Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
42 changes: 31 additions & 11 deletions openmc/executor.py
Original file line number Diff line number Diff line change
Expand Up @@ -92,6 +92,36 @@ def _process_CLI_arguments(volume=False, geometry_debug=False, particles=None,
return args


def _get_error_message(output):
"""Get the error message from the output of a failed OpenMC run

Parameters
----------
output : str
Standard output and standard error of the OpenMC executable

Returns
-------
str
Error message with whitespace simplified

"""
if 'ERROR: ' in output:
_, _, error_msg = output.partition('ERROR: ')
elif 'what()' in output:
_, _, error_msg = output.partition('what(): ')
elif 'terminating due to uncaught exception of type ' in output:
# libc++ (e.g. macOS) reports "...of type std::runtime_error: msg"
_, _, error_msg = output.partition(
'terminating due to uncaught exception of type ')
_, sep, error_msg = error_msg.partition(': ')
if not sep or not error_msg.strip():
error_msg = 'OpenMC aborted unexpectedly.'
else:
error_msg = 'OpenMC aborted unexpectedly.'
return ' '.join(error_msg.split())


def _run(args, output, cwd):
# Launch a subprocess
p = subprocess.Popen(args, cwd=cwd, stdout=subprocess.PIPE,
Expand All @@ -113,17 +143,7 @@ def _run(args, output, cwd):

# Raise an exception if return status is non-zero
if p.returncode != 0:
# Get error message from output and simplify whitespace
output = ''.join(lines)
if 'ERROR: ' in output:
_, _, error_msg = output.partition('ERROR: ')
elif 'what()' in output:
_, _, error_msg = output.partition('what(): ')
else:
error_msg = 'OpenMC aborted unexpectedly.'
error_msg = ' '.join(error_msg.split())

raise RuntimeError(error_msg)
raise RuntimeError(_get_error_message(''.join(lines)))


def plot_geometry(output=True, openmc_exec='openmc', cwd='.', path_input=None):
Expand Down
43 changes: 43 additions & 0 deletions tests/unit_tests/test_executor.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,43 @@
import io
import subprocess

import openmc
from openmc.executor import _get_error_message
import pytest


@pytest.mark.parametrize('output, message', [
# OpenMC's own error messages
(' ERROR: Could not add nuclide', 'Could not add nuclide'),
# libstdc++ (e.g. Linux)
("terminate called after throwing an instance of 'std::runtime_error'\n"
" what(): Could not add nuclide", 'Could not add nuclide'),
# libc++ (e.g. macOS)
('libc++abi: terminating due to uncaught exception of type '
'std::runtime_error: Could not add nuclide', 'Could not add nuclide'),
# libc++ with an exception that has no message
('libc++abi: terminating due to uncaught exception of type int',
'OpenMC aborted unexpectedly.'),
# Any other failure
('Segmentation fault', 'OpenMC aborted unexpectedly.'),
])
def test_error_message_from_output(output, message):
"""The error message is recovered from each way OpenMC can fail."""
assert _get_error_message(output) == message


def test_failed_run_raises_error_message(monkeypatch):
"""A failed run raises a RuntimeError with the message from its output."""
class FailedProcess:
def __init__(self, *args, **kwargs):
self.stdout = io.StringIO(
'libc++abi: terminating due to uncaught exception of type '
'std::runtime_error: Could not add nuclide\n')
self.returncode = 1

def poll(self):
return self.returncode

monkeypatch.setattr(subprocess, 'Popen', FailedProcess)
with pytest.raises(RuntimeError, match='^Could not add nuclide$'):
openmc.plot_geometry(output=False)
Loading