Handle gmxapi tmpi parameters better.
authorM. Eric Irrgang <ericirrgang@gmail.com>
Wed, 30 Sep 2020 20:32:20 +0000 (23:32 +0300)
committerM. Eric Irrgang <ericirrgang@gmail.com>
Fri, 2 Oct 2020 14:20:37 +0000 (17:20 +0300)
Use gmxconfig.json to support some additional testing support.

Configure thread allocation for gmxapi CI jobs more carefully.

Fixes #3710

admin/ci-scripts/build-and-test-py-gmxapi-0.2.sh
admin/ci-scripts/build-and-test-sample_restraint-2021.sh
python_packaging/sample_restraint/tests/test_binding.py
python_packaging/src/gmxapi/simulation/mdrun.py
python_packaging/src/gmxapi/testsupport.py
python_packaging/src/test/test_mdrun.py
python_packaging/test/pytesthelpers.py

index e7512e13e85072725ecdaaa28ddd63ccc0072f2d..828538828bd245a78a4ba4ab9fcaa1c48986179a 100644 (file)
@@ -39,7 +39,7 @@ pushd python_packaging/src
 popd
 
 # Run Python unit tests.
-python -m pytest python_packaging/src/test --junitxml=$PY_UNIT_TEST_XML
+python -m pytest python_packaging/src/test --junitxml=$PY_UNIT_TEST_XML --threads=2
 
 # Note: Multiple pytest processes getting --junitxml output file argument
 # may cause problems, so we set the option on only one of the launched processes.
@@ -47,7 +47,8 @@ python -m pytest python_packaging/src/test --junitxml=$PY_UNIT_TEST_XML
 # https://www.open-mpi.org/doc/v3.0/man1/mpiexec.1.php
 PROGRAM=(`which python` -m mpi4py -m pytest \
         -p no:cacheprovider \
-        $PWD/python_packaging/src/test)
+        $PWD/python_packaging/src/test \
+        --threads=1)
 # shellcheck disable=SC2068
 if [ -x `which mpiexec` ]; then
     PYTHONDONTWRITEBYTECODE=1 \
@@ -68,7 +69,8 @@ python -m pytest python_packaging/test --junitxml=$PY_ACCEPTANCE_TEST_XML
 # https://www.open-mpi.org/doc/v3.0/man1/mpiexec.1.php
 PROGRAM=(`which python` -m mpi4py -m pytest \
         -p no:cacheprovider \
-        $PWD/python_packaging/test)
+        $PWD/python_packaging/test \
+        --threads=1)
 # shellcheck disable=SC2068
 if [ -x `which mpiexec` ]; then
     PYTHONDONTWRITEBYTECODE=1 \
index 8de02b527024b95a3bab59b4c2587e3fea35943a..d8762577847c93b232e68de053c89157e9262c06 100644 (file)
@@ -65,7 +65,7 @@ pushd python_packaging/sample_restraint
     make install
   popd
 
-  python -m pytest $PWD/tests --junitxml=$PLUGIN_TEST_XML
+  python -m pytest $PWD/tests --junitxml=$PLUGIN_TEST_XML --threads=2
 
   # Note: Multiple pytest processes getting --junitxml output file argument
   # may cause problems, so we set the option on only one of the launched processes.
@@ -73,7 +73,8 @@ pushd python_packaging/sample_restraint
   # https://www.open-mpi.org/doc/v3.0/man1/mpiexec.1.php
   PROGRAM=(`which python` -m mpi4py -m pytest \
           -p no:cacheprovider \
-          $PWD/tests)
+          $PWD/tests \
+          --threads=1)
   # shellcheck disable=SC2068
   if [ -x `which mpiexec` ]; then
       PYTHONDONTWRITEBYTECODE=1 \
index 7dff38ca63e8a48a20065752086d12f456c37cd6..a483fd799634236353c2342c567842de8afca07b 100644 (file)
@@ -35,16 +35,14 @@ def test_import():
 
 
 @pytest.mark.usefixtures("cleandir")
-def test_ensemble_potential_nompi(spc_water_box):
+def test_ensemble_potential_nompi(spc_water_box, mdrun_kwargs):
     """Test ensemble potential without an ensemble.
     """
     tpr_filename = spc_water_box
     print("Testing plugin potential with input file {}".format(os.path.abspath(tpr_filename)))
 
     assert gmx.version.api_is_at_least(0, 0, 5)
-    # Note that *threads* argument causes errors for MPI-enabled GROMACS.
-    # Ref #3563 and #3573
-    md = from_tpr([tpr_filename], append_output=False, threads=2)
+    md = from_tpr([tpr_filename], append_output=False, **mdrun_kwargs)
 
     # Create a WorkElement for the potential
     params = {'sites': [1, 4],
@@ -75,15 +73,13 @@ def test_ensemble_potential_nompi(spc_water_box):
 
 @pytest.mark.withmpi_only
 @pytest.mark.usefixtures("cleandir")
-def test_ensemble_potential_withmpi(spc_water_box):
+def test_ensemble_potential_withmpi(spc_water_box, mdrun_kwargs):
     tpr_filename = spc_water_box
 
     logger.info("Testing plugin potential with input file {}".format(os.path.abspath(tpr_filename)))
 
     assert gmx_version.api_is_at_least(0, 0, 5)
-    # Note that *threads* argument causes errors for MPI-enabled GROMACS.
-    # Ref #3563 and #3573
-    md = from_tpr([tpr_filename, tpr_filename], append_output=False, threads=2)
+    md = from_tpr([tpr_filename, tpr_filename], append_output=False, **mdrun_kwargs)
 
     # Create a WorkElement for the potential
     params = {'sites': [1, 4],
index 92f904a0b818b81bf47e04ce4ed774c030fa2250..ed4d1229d6e950d1d91731974518148c12a4ef02 100644 (file)
@@ -261,8 +261,7 @@ class LegacyImplementationSubscription(object):
                                                                       ensemble_rank,
                                                                       self.workdir
                                                                       ))
-                    # TODO: Normalize the way we pass run time parameters to mdrun.
-                    # See also #3573
+                    # TODO: (#3718) Normalize the way we pass run time parameters to mdrun.
                     kwargs = getattr(resource_manager, 'mdrun_kwargs', {})
                     for key, value in kwargs.items():
                         logger.debug('Adding mdrun run time argument: {}'.format(key + '=' + str(value)))
index 64672fab50db91456cf4c9bb44b3fc9d9909c6b8..0ffdf02435a072d40ab230577a18cc289205fb94 100644 (file)
@@ -90,7 +90,7 @@ def pytest_runtest_setup(item):
 
 
 def pytest_addoption(parser):
-    """Add a command-line user option for the pytest invocation."""
+    """Add command-line user options for the pytest invocation."""
     parser.addoption(
         '--rm',
         action='store',
@@ -98,6 +98,11 @@ def pytest_addoption(parser):
         choices=['always', 'never', 'success'],
         help='Remove temporary directories "always", "never", or on "success".'
     )
+    parser.addoption(
+        '--threads',
+        type=int,
+        help='Maximum number of threads per process per gmxapi session.'
+    )
 
 
 class RmOption(Enum):
@@ -113,6 +118,46 @@ def remove_tempdir(request) -> RmOption:
     arg = request.config.getoption('--rm')
     return RmOption(arg)
 
+@pytest.fixture(scope='session')
+def gmxconfig():
+    try:
+        from importlib.resources import open_text
+        with open_text('gmxapi', 'gmxconfig.json') as textfile:
+            config = json.load(textfile)
+    except ImportError:
+        # TODO: Remove this when we require Python 3.7
+        try:
+            # A backport of importlib.resources is available as importlib_resources
+            # with a somewhat different interface.
+            from importlib_resources import files, as_file
+
+            source = files('gmxapi').joinpath('gmxconfig.json')
+            with as_file(source) as gmxconfig:
+                with open(gmxconfig, 'r') as fp:
+                    config = json.load(fp)
+        except ImportError:
+            config = None
+    yield config
+
+@pytest.fixture(scope='session')
+def mdrun_kwargs(request, gmxconfig):
+    """pytest fixture to provide a mdrun_kwargs dictionary for the mdrun ResourceManager.
+    """
+    from gmxapi.simulation.mdrun import ResourceManager as _ResourceManager
+    if gmxconfig is None:
+        raise RuntimeError('--threads argument requires a usable gmxconfig.json')
+    arg = request.config.getoption('--threads')
+    if arg is None:
+        return {}
+    mpi_type = gmxconfig['gmx_mpi_type']
+    if mpi_type is not None and mpi_type == "tmpi":
+        kwargs = {'threads': int(arg)}
+    else:
+        kwargs = {}
+    # TODO: (#3718) Normalize the handling of run-time arguments.
+    _ResourceManager.mdrun_kwargs = dict(**kwargs)
+    return kwargs
+
 
 @contextmanager
 def scoped_chdir(dir):
@@ -212,29 +257,14 @@ def cleandir(remove_tempdir: RmOption):
 
 class GmxBin:
     """Represent the detected GROMACS installation."""
-    def __init__(self):
+    def __init__(self, gmxconfig):
         # Try to use package resources to locate the "gmx" binary wrapper.
-        try:
-            from importlib.resources import open_text
-            with open_text('gmxapi', 'gmxconfig.json') as textfile:
-                config = json.load(textfile)
-                gmxbindir = config.get('gmx_bindir', None)
-                command = config.get('gmx_executable', None)
-        except ImportError:
-            try:
-                # A backport of importlib.resources is available as importlib_resources
-                # with a somewhat different interface.
-                from importlib_resources import files, as_file
-
-                source = files('gmxapi').joinpath('gmxconfig.json')
-                with as_file(source) as gmxconfig:
-                    with open(gmxconfig, 'r') as fp:
-                        config = json.load(fp)
-                        gmxbindir = config.get('gmx_bindir', None)
-                        command = config.get('gmx_executable', None)
-            except ImportError:
-                gmxbindir = None
-                command = None
+        if gmxconfig is not None:
+            gmxbindir = gmxconfig.get('gmx_bindir', None)
+            command = gmxconfig.get('gmx_executable', None)
+        else:
+            gmxbindir = None
+            command = None
 
         # TODO: Remove fall-back when we can rely on gmxconfig.json via importlib.resources in Py 3.7+.
         allowed_command_names = ['gmx', 'gmx_mpi']
@@ -266,12 +296,9 @@ class GmxBin:
         return self._bindir
 
 
-_gmx = GmxBin()
-
-
 @pytest.fixture(scope='session')
-def gmxcli():
-    command = _gmx.command()
+def gmxcli(gmxconfig):
+    command = GmxBin(gmxconfig).command()
     if command is None:
         message = "Tests need 'gmx' command line tool, but could not find it on the path."
         raise RuntimeError(message)
index 132267291ccadcf4894ce6742d1904340fffbf65..48d0808f8cc78eae34448e24f4fde7493937e288 100644 (file)
@@ -48,11 +48,6 @@ import pytest
 
 import gmxapi as gmx
 
-# TODO: (#3573) Normalize the handling of run-time arguments.
-from gmxapi.simulation.mdrun import ResourceManager as _ResourceManager
-# Note that *threads* argument causes errors for MPI-enabled GROMACS.
-# Ref #3563 and #3573
-_ResourceManager.mdrun_kwargs = {'threads': 2}
 
 # Configure the `logging` module before proceeding any further.
 gmx.logger.setLevel(logging.WARNING)
@@ -78,7 +73,7 @@ formatter = logging.Formatter(rank_tag + '%(name)s:%(levelname)s: %(message)s')
 
 
 @pytest.mark.usefixtures('cleandir')
-def test_run_from_tpr(spc_water_box):
+def test_run_from_tpr(spc_water_box, mdrun_kwargs):
     assert os.path.exists(spc_water_box)
 
     md = gmx.mdrun(spc_water_box)
@@ -88,7 +83,7 @@ def test_run_from_tpr(spc_water_box):
 
 @pytest.mark.withmpi_only
 @pytest.mark.usefixtures('cleandir')
-def test_run_trivial_ensemble(spc_water_box, caplog):
+def test_run_trivial_ensemble(spc_water_box, caplog, mdrun_kwargs):
     from mpi4py import MPI
     current_rank = MPI.COMM_WORLD.Get_rank()
     with caplog.at_level(logging.DEBUG):
@@ -124,7 +119,7 @@ def test_run_trivial_ensemble(spc_water_box, caplog):
 
 
 @pytest.mark.usefixtures('cleandir')
-def test_run_from_read_tpr_op(spc_water_box, caplog):
+def test_run_from_read_tpr_op(spc_water_box, caplog, mdrun_kwargs):
     with caplog.at_level(logging.DEBUG):
         caplog.handler.setFormatter(formatter)
         with caplog.at_level(logging.DEBUG, 'gmxapi'):
@@ -137,7 +132,7 @@ def test_run_from_read_tpr_op(spc_water_box, caplog):
 
 
 @pytest.mark.usefixtures('cleandir')
-def test_run_from_modify_input_op(spc_water_box, caplog):
+def test_run_from_modify_input_op(spc_water_box, caplog, mdrun_kwargs):
     with caplog.at_level(logging.DEBUG):
 
         simulation_input = gmx.read_tpr(spc_water_box)
index 480dd45f86611065e26ba6d5816efd9b34f493c2..4c171d49438ac31324ce2a3771fb1674463fcdd3 100644 (file)
@@ -39,10 +39,8 @@ Define the ``withmpi_only`` test decorator.
 
 import pytest
 
-# TODO: (#3573) Normalize the handling of run-time arguments.
+# TODO: (#3718) Normalize the handling of run-time arguments.
 from gmxapi.simulation.mdrun import ResourceManager as _ResourceManager
-# Note that *threads* argument causes errors for MPI-enabled GROMACS.
-# Ref #3563 and #3573
 _ResourceManager.mdrun_kwargs = {'threads': 2}
 
 withmpi_only = None