Skip to content

Commit 4d5e65a

Browse files
authored
Merge pull request #3467 from heplesser/better_conftest
Improved wrappers and OpenMPI handling in Python MPI tests
2 parents 7ea4cbc + 4401c4d commit 4d5e65a

12 files changed

+63
-30
lines changed

pyproject.toml

+1
Original file line numberDiff line numberDiff line change
@@ -4,6 +4,7 @@ markers = [
44
"skipif_missing_hdf5: skip test if NEST was built without HDF5 support",
55
"skipif_missing_mpi: skip test if NEST was built without MPI support",
66
"skipif_missing_threads: skip test if NEST was built without multithreading support",
7+
"skipif_incompatible_mpi: skip if NEST with built with MPI that does not work with subprocess",
78
"simulation: the simulation class to use. Always pass a 2nd dummy argument"
89
]
910

testsuite/do_tests.sh

+1-1
Original file line numberDiff line numberDiff line change
@@ -508,7 +508,7 @@ if test "${PYTHON}"; then
508508
XUNIT_FILE="${REPORTDIR}/${XUNIT_NAME}_sli2py_mpi.xml"
509509
env
510510
set +e
511-
"${PYTHON}" -m pytest --noconftest --verbose --timeout $TIME_LIMIT --junit-xml="${XUNIT_FILE}" --numprocesses=1 \
511+
"${PYTHON}" -m pytest --verbose --timeout $TIME_LIMIT --junit-xml="${XUNIT_FILE}" --numprocesses=1 \
512512
"${PYNEST_TEST_DIR}/sli2py_mpi" 2>&1 | tee -a "${TEST_LOGFILE}"
513513
set -e
514514
fi

testsuite/pytests/conftest.py

+20
Original file line numberDiff line numberDiff line change
@@ -34,6 +34,7 @@ def test_gsl():
3434
import dataclasses
3535
import os
3636
import pathlib
37+
import subprocess
3738
import sys
3839

3940
import nest
@@ -156,6 +157,25 @@ def have_plotting():
156157
return False
157158

158159

160+
@pytest.fixture(scope="session")
161+
def subprocess_compatible_mpi():
162+
"""Until at least OpenMPI 4.1.6, the following fails due to a bug in OpenMPI, from 5.0.7 is definitely safe."""
163+
164+
res = subprocess.run(["mpirun", "-np", "1", "echo"])
165+
return res.returncode == 0
166+
167+
168+
@pytest.fixture(autouse=True)
169+
def skipif_incompatible_mpi(request, subprocess_compatible_mpi):
170+
"""
171+
Globally applied fixture that skips tests marked to be skipped when MPI is
172+
not compatible with subprocess.
173+
"""
174+
175+
if not subprocess_compatible_mpi and request.node.get_closest_marker("skipif_incompatible_mpi"):
176+
pytest.skip("skipped because MPI is incompatible with subprocess")
177+
178+
159179
@pytest.fixture(autouse=True)
160180
def simulation_class(request):
161181
return getattr(request, "param", testsimulation.Simulation)

testsuite/pytests/sli2py_mpi/mpi_test_wrapper.py

+8-4
Original file line numberDiff line numberDiff line change
@@ -29,29 +29,33 @@
2929
3030
- The process is managed by subclasses of the `MPITestWrapper` base class
3131
- Each test file must contain **exactly one** test function
32+
- The test function must be protected with the `@pytest.mark.skipif_incompatible_mpi`
33+
marker to protect against buggy OpenMPI versions (at least up to 4.1.6; 5.0.7 and
34+
later are definitely good).
3235
- The test function must be decorated with a subclass of `MPITestWrapper`
3336
- The wrapper will write a modified version of the test file as `runner.py`
3437
to a temporary directory and mpirun it from there; results are collected
3538
in the temporary directory
3639
- The test function can be decorated with other pytest decorators. These
3740
are evaluated in the wrapping process
3841
- No decorators are written to the `runner.py` file.
39-
- Test files **must not import nest** outside the test function
42+
- Test files must import all required modules (especially `nest`) inside the
43+
test function.
44+
- The docstring for the test function shall in its last line specify what data
45+
the test function outputs for comparison by the test.
4046
- In `runner.py`, the following constants are defined:
4147
- `SPIKE_LABEL`
4248
- `MULTI_LABEL`
4349
- `OTHER_LABEL`
4450
They must be used as `label` for spike recorders and multimeters, respectively,
4551
or for other files for output data (CSV files). They are format strings expecting
4652
the number of processes with which NEST is run as argument.
47-
- `conftest.py` must not be loaded, otherwise mpirun will return a non-zero exit code;
48-
use `pytest --noconftest`
4953
- Set `debug=True` on the decorator to see debug output and keep the
5054
temporary directory that has been created (latter works only in
5155
Python 3.12 and later)
5256
- Evaluation criteria are determined by the `MPITestWrapper` subclass
5357
54-
This is still work in progress.
58+
5559
"""
5660

5761
import ast

testsuite/pytests/sli2py_mpi/test_all_to_all.py

+3-5
Original file line numberDiff line numberDiff line change
@@ -19,24 +19,22 @@
1919
# You should have received a copy of the GNU General Public License
2020
# along with NEST. If not, see <http://www.gnu.org/licenses/>.
2121

22-
import numpy as np
23-
import pandas
2422
import pytest
2523
from mpi_test_wrapper import MPITestAssertEqual
2624

2725

2826
# Parametrization over the number of nodes here only to show hat it works
27+
@pytest.mark.skipif_incompatible_mpi
2928
@pytest.mark.parametrize("N", [4, 7])
3029
@MPITestAssertEqual([1, 4], debug=False)
3130
def test_all_to_all(N):
3231
"""
3332
Confirm that all-to-all connections created correctly for more targets than local nodes.
33+
34+
The test is performed on connection data written to OTHER_LABEL.
3435
"""
3536

3637
import nest
37-
import pandas as pd
38-
39-
nest.ResetKernel()
4038

4139
nrns = nest.Create("parrot_neuron", n=N)
4240
nest.Connect(nrns, nrns, "all_to_all")

testsuite/pytests/sli2py_mpi/test_gap_junctions_mpi.py

+5-6
Original file line numberDiff line numberDiff line change
@@ -19,25 +19,24 @@
1919
# You should have received a copy of the GNU General Public License
2020
# along with NEST. If not, see <http://www.gnu.org/licenses/>.
2121

22-
22+
import pytest
2323
from mpi_test_wrapper import MPITestAssertEqual
2424

2525

26+
@pytest.mark.skipif_incompatible_mpi
27+
@pytest.mark.skipif_missing_gsl
2628
@MPITestAssertEqual([1, 2, 4], debug=False)
2729
def test_gap_junctions_mpi():
2830
"""
2931
Test gap junction functionality in parallel.
3032
3133
This is an overall test of the hh_psc_alpha_gap model connected by gap_junction.
3234
The test checks if the gap junction functionality works in parallel.
35+
36+
The test is performed on the spike data recorded to SPIKE_LABEL during the simulation.
3337
"""
3438

3539
import nest
36-
import pandas as pd
37-
38-
# We can only test here if GSL is available
39-
if not nest.ll_api.sli_func("statusdict/have_gsl ::"):
40-
return
4140

4241
total_vps = 4
4342
h = 0.1

testsuite/pytests/sli2py_mpi/test_issue_1957.py

+4-1
Original file line numberDiff line numberDiff line change
@@ -19,14 +19,17 @@
1919
# You should have received a copy of the GNU General Public License
2020
# along with NEST. If not, see <http://www.gnu.org/licenses/>.
2121

22-
22+
import pytest
2323
from mpi_test_wrapper import MPITestAssertEqual
2424

2525

26+
@pytest.mark.skipif_incompatible_mpi
2627
@MPITestAssertEqual([2, 4])
2728
def test_issue_1957():
2829
"""
2930
Confirm that GetConnections works in parallel without hanging if not all ranks have connections.
31+
32+
The test is performed on connection data written to OTHER_LABEL.
3033
"""
3134

3235
import nest

testsuite/pytests/sli2py_mpi/test_issue_2119.py

+3
Original file line numberDiff line numberDiff line change
@@ -32,11 +32,14 @@
3232
]
3333

3434

35+
@pytest.mark.skipif_incompatible_mpi
3536
@pytest.mark.parametrize(["kind", "specs"], random_params)
3637
@MPITestAssertEqual([1, 2, 4])
3738
def test_issue_2119(kind, specs):
3839
"""
3940
Confirm that randomized node parameters work correctly under MPI and OpenMP.
41+
42+
The test is performed on GID-V_m data written to OTHER_LABEL.
4043
"""
4144

4245
import nest

testsuite/pytests/sli2py_mpi/test_issue_281.py

+4-1
Original file line numberDiff line numberDiff line change
@@ -20,17 +20,20 @@
2020
# along with NEST. If not, see <http://www.gnu.org/licenses/>.
2121

2222

23+
import pytest
2324
from mpi_test_wrapper import MPITestAssertCompletes
2425

2526

27+
@pytest.mark.skipif_incompatible_mpi
2628
@MPITestAssertCompletes([1, 2, 4])
2729
def test_issue_281():
2830
"""
2931
Confirm that ConnectLayers works MPI-parallel for fixed fan-out.
32+
33+
This test only confirms completion, no data is tested.
3034
"""
3135

3236
import nest
33-
import pandas as pd
3437

3538
layer = nest.Create("parrot_neuron", positions=nest.spatial.grid(shape=[3, 3]))
3639
nest.Connect(

testsuite/pytests/sli2py_mpi/test_issue_600.py

+5-5
Original file line numberDiff line numberDiff line change
@@ -20,21 +20,21 @@
2020
# along with NEST. If not, see <http://www.gnu.org/licenses/>.
2121

2222

23+
import pytest
2324
from mpi_test_wrapper import MPITestAssertEqual
2425

2526

27+
@pytest.mark.skipif_incompatible_mpi
28+
@pytest.mark.skipif_missing_gsl
2629
@MPITestAssertEqual([1, 2, 4], debug=False)
2730
def test_issue_600():
2831
"""
2932
Confirm that waveform relaxation works with MPI.
33+
34+
The test compares data written by spike recorder to SPIKE_LABEL.
3035
"""
3136

3237
import nest
33-
import pandas as pd
34-
35-
# We can only test here if GSL is available
36-
if not nest.ll_api.sli_func("statusdict/have_gsl ::"):
37-
return
3838

3939
total_vps = 4
4040
h = 0.1

testsuite/pytests/sli2py_mpi/test_mini_brunel_ps.py

+4-3
Original file line numberDiff line numberDiff line change
@@ -19,20 +19,21 @@
1919
# You should have received a copy of the GNU General Public License
2020
# along with NEST. If not, see <http://www.gnu.org/licenses/>.
2121

22-
22+
import pytest
2323
from mpi_test_wrapper import MPITestAssertEqual
2424

2525

26+
@pytest.mark.skipif_incompatible_mpi
2627
@MPITestAssertEqual([1, 2, 4])
2728
def test_mini_brunel_ps():
2829
"""
2930
Confirm that downscaled Brunel net with precise neurons is invariant under number of MPI ranks.
31+
32+
The test compares data written by spike_recorder to SPIKE_LABEL.
3033
"""
3134

3235
import nest
3336

34-
nest.ResetKernel()
35-
3637
nest.set(total_num_virtual_procs=4, overwrite_files=True)
3738

3839
# Model parameters

testsuite/pytests/sli2py_mpi/test_self_get_conns_with_empty_ranks.py

+5-4
Original file line numberDiff line numberDiff line change
@@ -19,17 +19,18 @@
1919
# You should have received a copy of the GNU General Public License
2020
# along with NEST. If not, see <http://www.gnu.org/licenses/>.
2121

22-
import numpy as np
23-
import pandas
2422
import pytest
2523
from mpi_test_wrapper import MPITestAssertEqual
2624

2725

2826
# Parametrization over the number of nodes here only to show hat it works
27+
@pytest.mark.skipif_incompatible_mpi
2928
@MPITestAssertEqual([1, 2, 4], debug=False)
30-
def test_self_get_conns_with_empty_ranks():
29+
def test_get_conns_with_empty_ranks():
3130
"""
32-
Selftest: Confirm that connections can be gathered correctly even if some ranks have no neurons.
31+
Confirm that connections can be gathered correctly even if some ranks have no neurons.
32+
33+
The test compares connection data written to OTHER_LABEL.
3334
"""
3435

3536
import nest

0 commit comments

Comments
 (0)