diff --git a/docs/config_reference.rst b/docs/config_reference.rst index 18f34cf0c8..c1da5a5594 100644 --- a/docs/config_reference.rst +++ b/docs/config_reference.rst @@ -2313,6 +2313,53 @@ General Configuration Save any log files generated by ReFrame to its output directory +.. py:attribute:: general.stagedir_hashes + + :required: No + :default: :obj:`True` + + Use parameter hashes in the stage and output directories of parameterized tests. + + This is useful to avoid long directory names with special characters as well as to avoid conflicts between different parameterized variants of the same test when the parameters are formatted in the same way. + However this makes it harder to associate a stage directory to a specific test variant (users would have to check the ReFrame's output to do the matching). + + If human-readable stage directories are important, users can set this to :obj:`False` but should make sure to format their parameters in a way that avoids too complex syntaxes. + For example, the parameter ``plist`` in the following test is a list of strings: + + .. code-block:: python + + @rfm.simple_test + class MyTest(rfm.RegressionTest): + plist = parameter([["one", "two"], ["three", "four"]]) + + If ``stagedir_hashes=False``, the stage directory of the first test variant will be the following: + + .. code-block:: console + + $STAGE_DIR////MyTest_plist=["one", "two"] + + Accessing this from a shell would require excessive escaping of the special characters which can be error-prone. + The users should instead format the parameter by defining a more shell-friendly formatting as follows: + + .. code-block:: python + + @rfm.simple_test + class MyTest(rfm.RegressionTest): + plist = parameter([["one", "two"], ["three", "four"]], fmt=lambda x: ",".join(x)) + + In this case, the stage directory of the first test variant will be the following: + + .. code-block:: console + + $STAGE_DIR////MyTest_plist=one,two + + + .. note:: + This option has the same effect also on the output directory of the tests. + + + .. versionadded:: 4.11 + .. py:attribute:: general.target_systems :required: No @@ -2328,6 +2375,7 @@ General Configuration - :attr:`~general.git_timeout` - :attr:`~general.use_login_shell` - :attr:`~general.flex_alloc_strict` + - :attr:`~general.stagedir_hashes` - :attr:`~general.trap_job_errors` diff --git a/docs/manpage.rst b/docs/manpage.rst index 0dfc2f3904..6cd6516776 100644 --- a/docs/manpage.rst +++ b/docs/manpage.rst @@ -2453,6 +2453,21 @@ Whenever an environment variable is associated with a configuration option, its ================================== ================== +.. envvar:: RFM_STAGEDIR_HASHES + + Use parameter hashes in the stage and output directories of parameterized tests. + + .. table:: + :align: left + + ================================== ================== + Associated command line option N/A + Associated configuration parameter :attr:`~config.general.stagedir_hashes` + ================================== ================== + + .. versionadded:: 4.11 + + .. envvar:: RFM_SQLITE_CONN_TIMEOUT Timeout for SQLite database connections. diff --git a/reframe/core/pipeline.py b/reframe/core/pipeline.py index a5cbb542eb..c725e9b80d 100644 --- a/reframe/core/pipeline.py +++ b/reframe/core/pipeline.py @@ -2407,14 +2407,19 @@ def _setup_paths(self): self.logger.debug('Setting up test paths') try: runtime = rt.runtime() + if runtime.get_option('general/0/stagedir_hashes'): + test_name = self.short_name + else: + test_name = self.display_name.replace(' ', '') + self._stagedir = runtime.make_stagedir( self.current_system.name, self._current_partition.name, - self._current_environ.name, self.short_name + self._current_environ.name, test_name ) if not self.is_dry_run(): self._outputdir = runtime.make_outputdir( self.current_system.name, self._current_partition.name, - self._current_environ.name, self.short_name + self._current_environ.name, test_name ) except OSError as e: raise PipelineError('failed to set up paths') from e diff --git a/reframe/frontend/cli.py b/reframe/frontend/cli.py index 2b605a7cc8..b949d3b5b4 100644 --- a/reframe/frontend/cli.py +++ b/reframe/frontend/cli.py @@ -923,6 +923,13 @@ def main(): help='DB file permissions (SQLite backend)', type=functools.partial(int, base=8) ) + output_options.add_argument( + dest='stagedir_hashes', + envvar='RFM_STAGEDIR_HASHES', + configvar='general/stagedir_hashes', + action='store_true', + help='Use hashes in stage and output directory names', + ) argparser.add_argument( dest='syslog_address', envvar='RFM_SYSLOG_ADDRESS', diff --git a/reframe/schemas/config.json b/reframe/schemas/config.json index 668cac0ee8..9be50b390a 100644 --- a/reframe/schemas/config.json +++ b/reframe/schemas/config.json @@ -561,6 +561,7 @@ "report_junit": {"type": ["string", "null"]}, "resolve_module_conflicts": {"type": "boolean"}, "save_log_files": {"type": "boolean"}, + "stagedir_hashes": {"type": "boolean"}, "target_systems": {"$ref": "#/defs/system_ref"}, "table_format": {"enum": ["csv", "plain", "pretty"]}, "table_format_delim": {"type": "string"}, @@ -638,6 +639,7 @@ "general/report_junit": null, "general/resolve_module_conflicts": true, "general/save_log_files": false, + "general/stagedir_hashes": true, "general/table_format": "pretty", "general/table_format_delim": ",", "general/target_systems": ["*"], diff --git a/unittests/resources/config/settings.py b/unittests/resources/config/settings.py index 7c8fbd7339..b820ab519d 100644 --- a/unittests/resources/config/settings.py +++ b/unittests/resources/config/settings.py @@ -314,6 +314,7 @@ def hostname(): }, { 'git_timeout': 20, + 'stagedir_hashes': False, 'target_systems': ['sys2:part2'] } ] diff --git a/unittests/test_cli.py b/unittests/test_cli.py index efee36fbc8..d72796ede6 100644 --- a/unittests/test_cli.py +++ b/unittests/test_cli.py @@ -406,6 +406,22 @@ def test_dont_restage(run_reframe, tmp_path): assert returncode != 0 +def test_stagedir_no_hashes(run_reframe, tmp_path): + returncode, stdout, stderr = run_reframe( + system='sys2:part2', + checkpath=['unittests/resources/checks/hellocheck.py'], + more_options=['-n', '^HelloTest$', '--repeat=1', '--keep-stage-files'], + action='run' + ) + assert returncode == 0 + assert 'Traceback' not in stdout + assert 'Traceback' not in stderr + assert os.path.exists(tmp_path / 'stage' / 'sys2' / 'part2' / + 'builtin' / r'HelloTest%.repeat_no=0') + assert os.path.exists(tmp_path / 'output' / 'sys2' / 'part2' / + 'builtin' / r'HelloTest%.repeat_no=0') + + def test_checkpath_symlink(run_reframe, tmp_path): # FIXME: This should move to test_loader.py checks_symlink = tmp_path / 'checks_symlink'