diff --git a/tests/firedrake/conftest.py b/tests/firedrake/conftest.py index 0874f5b01e..9401b565b1 100644 --- a/tests/firedrake/conftest.py +++ b/tests/firedrake/conftest.py @@ -1,7 +1,7 @@ """Global test configuration.""" import pytest -from firedrake.petsc import get_external_packages +from firedrake.petsc import PETSc, get_external_packages def pytest_configure(config): @@ -122,3 +122,34 @@ def fin(): assert len(tape.get_blocks()) == 0 request.addfinalizer(fin) + + +class _petsc_raises: + """Context manager for catching PETSc-raised exceptions. + + The usual `pytest.raises` exception handler is not suitable for errors + raised inside a callback to PETSc because the error is wrapped inside a + `PETSc.Error` object and so this context manager unpacks this to access + the actual internal error. + + Parameters + ---------- + exc_type : + The exception type that is expected to be raised inside a PETSc callback. + + """ + def __init__(self, exc_type): + self.exc_type = exc_type + + def __enter__(self): + pass + + def __exit__(self, exc_type, exc_val, traceback): + if exc_type is PETSc.Error and isinstance(exc_val.__cause__, self.exc_type): + return True + + +@pytest.fixture +def petsc_raises(): + # This function is needed because pytest does not support classes as fixtures. + return _petsc_raises diff --git a/tests/firedrake/macro/test_macro_multigrid.py b/tests/firedrake/macro/test_macro_multigrid.py index 0c91119383..c45b085555 100644 --- a/tests/firedrake/macro/test_macro_multigrid.py +++ b/tests/firedrake/macro/test_macro_multigrid.py @@ -140,7 +140,7 @@ def test_macro_grid_transfer(hierarchy, space, degrees, variant, transfer_type): @pytest.mark.parametrize("degree", (1,)) -def test_macro_multigrid_poisson(hierarchy, degree, variant): +def test_macro_multigrid_poisson(hierarchy, degree, variant, petsc_raises): mesh = hierarchy[-1] V = FunctionSpace(mesh, "CG", degree, variant=variant) u = TrialFunction(V) @@ -153,7 +153,7 @@ def test_macro_multigrid_poisson(hierarchy, degree, variant): problem = LinearVariationalProblem(a, L, uh, bcs=bcs) solver = LinearVariationalSolver(problem, solver_parameters=mg_params) if complex_mode and variant == "alfeld": - with pytest.raises(NotImplementedError): + with petsc_raises(NotImplementedError): solver.solve() else: solver.solve() @@ -172,7 +172,7 @@ def square_hierarchy(): @pytest.mark.parametrize("family", ("HCT-red", "HCT")) -def test_macro_multigrid_biharmonic(square_hierarchy, family): +def test_macro_multigrid_biharmonic(square_hierarchy, family, petsc_raises): mesh = square_hierarchy[-1] V = FunctionSpace(mesh, family, 3) u = TrialFunction(V) @@ -185,7 +185,7 @@ def test_macro_multigrid_biharmonic(square_hierarchy, family): problem = LinearVariationalProblem(a, L, uh, bcs=bcs) solver = LinearVariationalSolver(problem, solver_parameters=mg_params) if complex_mode: - with pytest.raises(NotImplementedError): + with petsc_raises(NotImplementedError): solver.solve() else: solver.solve() diff --git a/tests/firedrake/slate/test_slate_hybridization.py b/tests/firedrake/slate/test_slate_hybridization.py index 267c904867..ab7d4d4415 100644 --- a/tests/firedrake/slate/test_slate_hybridization.py +++ b/tests/firedrake/slate/test_slate_hybridization.py @@ -130,7 +130,7 @@ def test_slate_hybridization(degree, hdiv_family, quadrilateral): assert u_err < 1e-11 -def test_slate_hybridization_wrong_option(setup_poisson): +def test_slate_hybridization_wrong_option(setup_poisson, petsc_raises): a, L, W = setup_poisson w = Function(W) @@ -145,18 +145,9 @@ def test_slate_hybridization_wrong_option(setup_poisson): 'pc_fieldsplit_type': 'frog'}}} problem = LinearVariationalProblem(a, L, w) solver = LinearVariationalSolver(problem, solver_parameters=params) - with pytest.raises(ValueError): - # HybridizationPC isn't called directly from the Python interpreter, - # it's a callback that PETSc calls. This means that the call stack from pytest - # down to HybridizationPC goes via PETSc C code, which interferes with the exception - # before it is observed outside. Hence removing PETSc's error handler - # makes the problem go away, because PETSc stops interfering. - # We need to repush the error handler because popErrorHandler globally changes - # the system state for all future tests. - from firedrake.petsc import PETSc - PETSc.Sys.pushErrorHandler("ignore") + + with petsc_raises(ValueError): solver.solve() - PETSc.Sys.popErrorHandler("ignore") def test_slate_hybridization_nested_schur(setup_poisson):