Skip to content
Merged
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
2 changes: 1 addition & 1 deletion src/fox
Submodule fox updated 1 files
+25 −2 common/m_common_error.F90
15 changes: 15 additions & 0 deletions src/libAtoms/System.F90
Original file line number Diff line number Diff line change
Expand Up @@ -48,6 +48,7 @@
module system_module
use error_module
use kind_module
use m_common_error, only: FoX_set_error_handler
!$ use omp_lib
#ifdef _MPI
#ifndef _OLDMPI
Expand Down Expand Up @@ -2320,8 +2321,22 @@ subroutine system_initialise(verbosity,seed, mpi_all_inoutput, common_seed, enab

system_quippy_running = optional_default(.false., quippy_running)

! Route FoX parse errors through system_abort so they propagate to
! Python as exceptions (libAtoms/QUIP#724). The handler is installed
! unconditionally -- non-quippy callers still get system_abort, which
! preserves the historical "abort on XML parse error" behaviour.
call FoX_set_error_handler(system_fox_error_handler)

end subroutine system_initialise

!% FoX error handler bridge: route FoX parse errors through system_abort
!% so they reach Python as exceptions when running under quippy
!% (see libAtoms/QUIP#724).
subroutine system_fox_error_handler(msg)
character(len=*), intent(in) :: msg
call system_abort('FoX: '//trim(msg))
end subroutine system_fox_error_handler

subroutine get_mainlog_errorlog_ptr(mainlog_ptr, errorlog_ptr)
type inoutput_ptr
type(inoutput), pointer :: p
Expand Down
1 change: 1 addition & 0 deletions src/libAtoms/meson.build
Original file line number Diff line number Diff line change
Expand Up @@ -83,6 +83,7 @@ libAtoms = library('libAtoms',
blas_dep,
mpi_dep,
],
link_with: [fox],
c_args: ['-DPROTOTYPES=1'],
link_args: libatoms_link_args,
override_options: ['b_lundef=false'], # Allow undefined symbols
Expand Down
2 changes: 1 addition & 1 deletion src/meson.build
Original file line number Diff line number Diff line change
@@ -1,5 +1,5 @@
subdir('libAtoms')
subdir('fox')
subdir('libAtoms')

if get_option('gap')
subdir('GAP')
Expand Down
72 changes: 72 additions & 0 deletions tests/test_fox_xml_errors.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,72 @@
#!/usr/bin/env python3
"""
Regression test for https://github.com/libAtoms/QUIP/issues/724

Invalid XML passed to quippy.potential.Potential(param_filename=...) must raise
a catchable Python exception. Previously, FoX's error handler called Fortran
`stop` directly, silently killing the Python process before any try/except
could run.

The test runs the user's reproducer in a subprocess because, in the RED state,
the bug terminates the interpreter; an in-process assertRaises would crash the
whole test runner instead of failing one test.
"""

import os
import subprocess
import sys
import tempfile
import textwrap
import unittest


class TestFoxXmlErrors(unittest.TestCase):

def _run_reproducer(self, args_str):
"""Run a tiny quippy script in a subprocess and return its result.

The script writes a non-XML file, then tries to load it as a Potential
param_filename. It prints CAUGHT:<exc-type> on a Python exception,
NO_EXCEPTION if Potential() returned without raising.
"""
with tempfile.TemporaryDirectory() as tmpdir:
bad_xml = os.path.join(tmpdir, "bad.xml")
with open(bad_xml, "w") as fh:
fh.write("pair_style lj/cut 3.5\n")

code = textwrap.dedent(f"""
import sys
from quippy.potential import Potential
try:
pot = Potential({args_str!r}, param_filename={bad_xml!r})
print("NO_EXCEPTION")
except BaseException as e:
print("CAUGHT:" + type(e).__name__ + ":" + str(e)[:200])
sys.exit(0)
""")

return subprocess.run(
[sys.executable, "-c", code],
capture_output=True,
text=True,
timeout=120,
)

def test_invalid_xml_raises_python_exception_sw(self):
"""IP SW with a non-XML param file must raise, not silently exit."""
result = self._run_reproducer("IP SW")
self.assertIn(
"CAUGHT:",
result.stdout,
msg=(
"Expected Python exception from invalid XML.\n"
f"returncode={result.returncode}\n"
f"stdout={result.stdout!r}\n"
f"stderr={result.stderr!r}"
),
)
self.assertNotIn("NO_EXCEPTION", result.stdout)


if __name__ == "__main__":
unittest.main()
Loading