Repository: reframe-hpc/reframe

Issue:
`use_nodes_option` scheduler option does not work in ReFrame 4.0

Discussion:

Patch:
diff --git a/docs/config_reference.rst b/docs/config_reference.rst
index f471337f..f1d8c687 100644
--- a/docs/config_reference.rst
+++ b/docs/config_reference.rst
@@ -359,7 +359,6 @@ System Partition Configuration
    :default: ``false``
 
    Always emit the ``--nodes`` Slurm option in the preamble of the job script.
-   This option is relevant to Slurm backends only.
 
    This option is relevant for the Slurm backends only.
 
diff --git a/reframe/core/schedulers/__init__.py b/reframe/core/schedulers/__init__.py
index cf87ffd6..0424571e 100644
--- a/reframe/core/schedulers/__init__.py
+++ b/reframe/core/schedulers/__init__.py
@@ -45,7 +45,7 @@ class JobSchedulerMeta(abc.ABCMeta):
         obj = cls.__new__(cls, *args, **kwargs)
         if part_name:
             obj._config_prefix = (
-                f'systems/0/paritions/@{part_name}/sched_options'
+                f'systems/0/partitions/@{part_name}/sched_options'
             )
         else:
             obj._config_prefix = 'systems/0/sched_options'
diff --git a/reframe/core/schedulers/slurm.py b/reframe/core/schedulers/slurm.py
index 1dceb80a..2ced2d5c 100644
--- a/reframe/core/schedulers/slurm.py
+++ b/reframe/core/schedulers/slurm.py
@@ -192,7 +192,8 @@ class SlurmJobScheduler(sched.JobScheduler):
             )
 
         if self._use_nodes_opt:
-            num_nodes = job.num_tasks // job.num_tasks_per_node
+            num_tasks_per_node = job.num_tasks_per_node or 1
+            num_nodes = job.num_tasks // num_tasks_per_node
             preamble.append(self._format_option(num_nodes, '--nodes={0}'))
 
         if job.use_smt is None:
diff --git a/reframe/schemas/config.json b/reframe/schemas/config.json
index 2ca913db..1a9010b4 100644
--- a/reframe/schemas/config.json
+++ b/reframe/schemas/config.json
@@ -102,6 +102,19 @@
                 }
             ]
         },
+        "sched_options": {
+            "type": "object",
+            "properties": {
+                "ignore_reqnodenotavail": {"type": "boolean"},
+                "job_submit_timeout": {"type": "number"},
+                "resubmit_on_errors": {
+                    "type": "array",
+                    "items": {"type": "string"}
+                },
+                "use_nodes_option": {"type": "boolean"}
+            },
+            "additionalProperties": false
+        },
         "stream_handler": {
             "allOf": [
                 {"$ref": "#/defs/handler_common"},
@@ -242,6 +255,7 @@
                     "stagedir": {"type": "string"},
                     "outputdir": {"type": "string"},
                     "resourcesdir": {"type": "string"},
+                    "sched_options": {"$ref": "#/defs/sched_options"},
                     "partitions": {
                         "type": "array",
                         "items": {
@@ -256,19 +270,7 @@
                                         "sge", "slurm", "squeue", "torque"
                                     ]
                                 },
-                                "sched_options": {
-                                    "type": "object",
-                                    "properties": {
-                                        "ignore_reqnodenotavail": {"type": "boolean"},
-                                        "job_submit_timeout": {"type": "number"},
-                                        "resubmit_on_errors": {
-                                            "type": "array",
-                                            "items": {"type": "string"}
-                                        },
-                                        "use_nodes_option": {"type": "boolean"}
-                                    },
-                                    "additionalProperties": false
-                                },
+                                "sched_options": {"$ref": "#/defs/sched_options"},
                                 "launcher": {
                                     "type": "string"
                                 },
@@ -601,9 +603,9 @@
         "systems/partitions/time_limit": null,
         "systems/partitions/devices": [],
         "systems/partitions/extras": {},
-        "systems*/sched_options/ignore_reqnodenotavail": false,
-        "systems*/sched_options/job_submit_timeout": 60,
-        "systems*/sched_options/resubmit_on_errors": [],
-        "systems*/sched_options/use_nodes_option": false
+        "{systems,systems/partitions}/sched_options/ignore_reqnodenotavail": false,
+        "{systems,systems/partitions}/sched_options/job_submit_timeout": 60,
+        "{systems,systems/partitions}/sched_options/resubmit_on_errors": [],
+        "{systems,systems/partitions}/sched_options/use_nodes_option": false
     }
 }


Test patch:
diff --git a/unittests/resources/config/settings.py b/unittests/resources/config/settings.py
index a7746fb1..4e559566 100644
--- a/unittests/resources/config/settings.py
+++ b/unittests/resources/config/settings.py
@@ -17,6 +17,9 @@ site_configuration = {
             'resourcesdir': '.rfm_testing/resources',
             'modules': ['foo/1.0'],
             'env_vars': [['FOO_CMD', 'foobar']],
+            'sched_options': {
+                'job_submit_timeout': 10
+            },
             'partitions': [
                 {
                     'name': 'login',
@@ -63,7 +66,7 @@ site_configuration = {
                     'environs': ['PrgEnv-gnu', 'builtin'],
                     'max_jobs': 10,
                     'sched_options': {
-                        'use_nodes_option': False
+                        'use_nodes_option': True
                     },
                     'processor': {
                         'arch': 'skylake',
diff --git a/unittests/test_config.py b/unittests/test_config.py
index aee8d646..442d5120 100644
--- a/unittests/test_config.py
+++ b/unittests/test_config.py
@@ -296,7 +296,10 @@ def test_select_subconfig(site_config):
             [['FOO_GPU', 'yes']])
     assert site_config.get('systems/0/partitions/0/max_jobs') == 10
     assert site_config.get('systems/0/partitions/0/sched_options') == {
-        'use_nodes_option': False
+        'use_nodes_option': True
+    }
+    assert site_config.get('systems/0/sched_options') == {
+        'job_submit_timeout': 10
     }
     assert site_config.get('environments/@PrgEnv-gnu/cc') == 'cc'
     assert site_config.get('environments/1/cxx') == 'CC'
diff --git a/unittests/test_schedulers.py b/unittests/test_schedulers.py
index 154bab27..ab7726c0 100644
--- a/unittests/test_schedulers.py
+++ b/unittests/test_schedulers.py
@@ -72,9 +72,10 @@ def exec_ctx(make_exec_ctx, scheduler):
 
 @pytest.fixture
 def make_job(scheduler, launcher, tmp_path):
-    def _make_job(**jobargs):
+    def _make_job(sched_opts=None, **jobargs):
+        sched = scheduler(**sched_opts) if sched_opts else scheduler()
         return Job.create(
-            scheduler(), launcher(),
+            sched, launcher(),
             name='testjob',
             workdir=tmp_path,
             script_filename=str(tmp_path / 'job.sh'),
@@ -377,9 +378,8 @@ def test_prepare_without_smt(fake_job, slurm_only):
 
 
 def test_prepare_nodes_option(make_exec_ctx, make_job, slurm_only):
-    make_exec_ctx(test_util.TEST_CONFIG_FILE, 'generic',
-                  {'systems*/sched_options/use_nodes_option': True})
-    job = make_job()
+    make_exec_ctx(test_util.TEST_CONFIG_FILE, 'testsys')
+    job = make_job(sched_opts={'part_name': 'gpu'})
     job.num_tasks = 16
     job.num_tasks_per_node = 2
     prepare_job(job)
@@ -387,6 +387,15 @@ def test_prepare_nodes_option(make_exec_ctx, make_job, slurm_only):
         assert re.search(r'--nodes=8', fp.read()) is not None
 
 
+def test_prepare_nodes_option_minimal(make_exec_ctx, make_job, slurm_only):
+    make_exec_ctx(test_util.TEST_CONFIG_FILE, 'testsys')
+    job = make_job(sched_opts={'part_name': 'gpu'})
+    job.num_tasks = 16
+    prepare_job(job)
+    with open(job.script_filename) as fp:
+        assert re.search(r'--nodes=16', fp.read()) is not None
+
+
 def test_submit(make_job, exec_ctx):
     minimal_job = make_job(sched_access=exec_ctx.access)
     prepare_job(minimal_job)