Skip to content

Commit 8e9eb46

Browse files
Shell and SLURM scheduler: give an absolute error log file. Shell: test if MPI works with sub-processes. Added exception.py
1 parent 335d875 commit 8e9eb46

File tree

4 files changed

+45
-15
lines changed

4 files changed

+45
-15
lines changed

pytest_parallel/exception.py

Lines changed: 18 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,18 @@
1+
2+
def _to_bold_red(s):
3+
red = '\x1b[31m'
4+
bold = '\x1b[1m'
5+
reset = '\x1b[0m'
6+
return red + bold + s + reset
7+
8+
class PytestParallelInternalError(Exception):
9+
def __init__(self, msg):
10+
Exception.__init__(self, _to_bold_red('pytest_parallel internal error')+'\n' + msg)
11+
12+
class PytestParallelUsageError(Exception):
13+
def __init__(self, msg):
14+
Exception.__init__(self, _to_bold_red('You are calling pytest_parallel incorrectly')+'\n' + msg)
15+
16+
class PytestParallelEnvError(Exception):
17+
def __init__(self, msg):
18+
Exception.__init__(self, _to_bold_red('pytest_parallel environment error:')+'\n' + msg)

pytest_parallel/plugin.py

Lines changed: 14 additions & 15 deletions
Original file line numberDiff line numberDiff line change
@@ -11,8 +11,7 @@
1111
import pytest
1212
from _pytest.terminal import TerminalReporter
1313

14-
class PytestParallelError(ValueError):
15-
pass
14+
from .exception import PytestParallelUsageError, PytestParallelInternalError
1615

1716
# --------------------------------------------------------------------------
1817
def pytest_addoption(parser):
@@ -58,13 +57,13 @@ def pytest_addoption(parser):
5857
' (because importing mpi4py.MPI makes the current process look like and MPI process,' \
5958
' and SLURM does not like that)'
6059
if os.getenv('I_MPI_MPIRUN') is not None:
61-
err_msg = 'Internal pytest_parallel error: the environment variable I_MPI_MPIRUN is set' \
60+
err_msg = 'The environment variable I_MPI_MPIRUN is set' \
6261
f' (it has value "{os.getenv("I_MPI_MPIRUN")}"),\n' \
6362
' while pytest was invoked with "--scheduler=slurm".\n' \
6463
' This indicates that pytest was run through MPI, and SLURM generally does not like that.\n' \
6564
' With "--scheduler=slurm", just run `pytest` directly, not through `mpirun/mpiexec/srun`,\n' \
6665
' because it will launch MPI itself (you may want to use --n-workers=<number of processes>).'
67-
raise PytestParallelError(err_msg)
66+
raise PytestParallelInternalError(err_msg)
6867

6968
r = subprocess.run(['env','--null'], stdout=subprocess.PIPE) # `--null`: end each output line with NUL, required by `sbatch --export-file`
7069

@@ -109,41 +108,41 @@ def pytest_configure(config):
109108
assert not is_worker, f'Internal pytest_parallel error `--_worker` not available with`--scheduler={scheduler}`'
110109
if scheduler in ['slurm', 'shell'] and not is_worker:
111110
if n_workers is None:
112-
raise PytestParallelError(f'You need to specify `--n-workers` when `--scheduler={scheduler}`')
111+
raise PytestParallelUsageError(f'You need to specify `--n-workers` when `--scheduler={scheduler}`')
113112
if scheduler != 'slurm':
114113
if slurm_options is not None:
115-
raise PytestParallelError('Option `--slurm-options` only available when `--scheduler=slurm`')
114+
raise PytestParallelUsageError('Option `--slurm-options` only available when `--scheduler=slurm`')
116115
if slurm_srun_options is not None:
117-
raise PytestParallelError('Option `--slurms-run-options` only available when `--scheduler=slurm`')
116+
raise PytestParallelUsageError('Option `--slurms-run-options` only available when `--scheduler=slurm`')
118117
if slurm_init_cmds is not None:
119-
raise PytestParallelError('Option `--slurm-init-cmds` only available when `--scheduler=slurm`')
118+
raise PytestParallelUsageError('Option `--slurm-init-cmds` only available when `--scheduler=slurm`')
120119
if slurm_file is not None:
121-
raise PytestParallelError('Option `--slurm-file` only available when `--scheduler=slurm`')
120+
raise PytestParallelUsageError('Option `--slurm-file` only available when `--scheduler=slurm`')
122121

123122
if scheduler in ['shell', 'slurm'] and not is_worker:
124123
from mpi4py import MPI
125124
if MPI.COMM_WORLD.size != 1:
126125
err_msg = 'Do not launch `pytest_parallel` on more that one process when `--scheduler=shell` or `--scheduler=slurm`.\n' \
127126
'`pytest_parallel` will spawn MPI processes itself.\n' \
128127
f'You may want to use --n-workers={MPI.COMM_WORLD.size}.'
129-
raise PytestParallelError(err_msg)
128+
raise PytestParallelUsageError(err_msg)
130129

131130

132131

133132
if scheduler == 'slurm' and not is_worker:
134133
if slurm_options is None and slurm_file is None:
135-
raise PytestParallelError('You need to specify either `--slurm-options` or `--slurm-file` when `--scheduler=slurm`')
134+
raise PytestParallelUsageError('You need to specify either `--slurm-options` or `--slurm-file` when `--scheduler=slurm`')
136135
if slurm_options:
137136
if slurm_file:
138-
raise PytestParallelError('You need to specify either `--slurm-options` or `--slurm-file`, but not both')
137+
raise PytestParallelUsageError('You need to specify either `--slurm-options` or `--slurm-file`, but not both')
139138
if slurm_file:
140139
if slurm_options:
141-
raise PytestParallelError('You need to specify either `--slurm-options` or `--slurm-file`, but not both')
140+
raise PytestParallelUsageError('You need to specify either `--slurm-options` or `--slurm-file`, but not both')
142141
if slurm_init_cmds:
143-
raise PytestParallelError('You cannot specify `--slurm-init-cmds` together with `--slurm-file`')
142+
raise PytestParallelUsageError('You cannot specify `--slurm-init-cmds` together with `--slurm-file`')
144143

145144
if '-n=' in slurm_options or '--ntasks=' in slurm_options:
146-
raise PytestParallelError('Do not specify `-n/--ntasks` in `--slurm-options` (it is deduced from the `--n-worker` value).')
145+
raise PytestParallelUsageError('Do not specify `-n/--ntasks` in `--slurm-options` (it is deduced from the `--n-worker` value).')
147146

148147
from .slurm_scheduler import SlurmScheduler
149148

pytest_parallel/shell_static_scheduler.py

Lines changed: 11 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -3,6 +3,7 @@
33
import subprocess
44
import socket
55
import pickle
6+
from pathlib import Path
67

78
import pytest
89
from mpi4py import MPI
@@ -12,6 +13,7 @@
1213
from .utils.items import add_n_procs, run_item_test, mark_original_index, mark_skip
1314
from .utils.file import remove_exotic_chars, create_folders
1415
from .static_scheduler_utils import group_items_by_parallel_steps
16+
from .exception import PytestParallelEnvError
1517

1618
def mpi_command(current_proc, n_proc):
1719
mpi_vendor = MPI.get_vendor()[0]
@@ -39,6 +41,7 @@ def submit_items(items_to_run, SCHEDULER_IP_ADDRESS, port, session_folder, main_
3941
for item in items:
4042
test_idx = item.original_index
4143
test_out_file = f'.pytest_parallel/{session_folder}/{remove_exotic_chars(item.nodeid)}'
44+
test_out_file = str(Path(test_out_file).absolute()) # easier to find the file if absolute
4245
cmd = '('
4346
cmd += mpi_command(current_proc, item.n_proc)
4447
cmd += f' python3 -u -m pytest -s --_worker {socket_flags} {main_invoke_params} --_test_idx={test_idx} {item.config.rootpath}/{item.nodeid}'
@@ -106,6 +109,14 @@ def __init__(self, main_invoke_params, ntasks, detach):
106109

107110
self.socket = socket.socket(socket.AF_INET, socket.SOCK_STREAM) # TODO close at the end
108111

112+
# Check that MPI can be called in a subprocess (not the case with OpenMPI 4.0.5, see #17)
113+
p = subprocess.run('mpirun -np 1 echo mpi_can_be_called_from_subprocess', shell=True)
114+
if p.returncode != 0:
115+
raise PytestParallelEnvError(
116+
"Your MPI implementation does not handle MPI being called from a sub-process\n"
117+
"Either update your MPI version or use another scheduler. See https://github.com/onera/pytest_parallel/issues/17"
118+
)
119+
109120
@pytest.hookimpl(tryfirst=True)
110121
def pytest_pyfunc_call(self, pyfuncitem):
111122
# This is where the test is normally run.

pytest_parallel/slurm_scheduler.py

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,7 @@
11
import subprocess
22
import socket
33
import pickle
4+
from pathlib import Path
45

56
import pytest
67

@@ -44,6 +45,7 @@ def submit_items(items_to_run, socket, session_folder, main_invoke_params, ntask
4445
for item in items:
4546
test_idx = item.original_index
4647
test_out_file = f'.pytest_parallel/{session_folder}/{remove_exotic_chars(item.nodeid)}'
48+
test_out_file = str(Path(test_out_file).absolute()) # easier to find the file if absolute
4749
cmd = '('
4850
cmd += f'srun {srun_options}'
4951
cmd += ' --exclusive'

0 commit comments

Comments
 (0)