diff --git a/autosubmit/autosubmit.py b/autosubmit/autosubmit.py index 7406281612354fa083eed7edb9357d09fbf06b5a..41a8087135c409d38d588563be54a7461f172675 100644 --- a/autosubmit/autosubmit.py +++ b/autosubmit/autosubmit.py @@ -626,6 +626,12 @@ class Autosubmit: subparser.add_argument('expid', help='experiment identifier') subparser.add_argument('-v', '--update_version', action='store_true', default=False, help='Update experiment version') + # Provenance + subparser = subparsers.add_parser( + 'provenance', description = 'Produce provenance for autosubmit') + subparser.add_argument('expid', help='experiment identifier') + subparser.add_argument('--rocrate', action='store_true', default=False, + help='Produce an RO-Crate file') # Archive subparser = subparsers.add_parser( 'archive', description='archives an experiment') @@ -767,6 +773,8 @@ class Autosubmit: return Autosubmit.update_version(args.expid) elif args.command == 'upgrade': return Autosubmit.upgrade_scripts(args.expid,files=args.files) + elif args.command == 'provenance': + return Autosubmit.provenance(args.expid, rocrate=args.rocrate) elif args.command == 'archive': return Autosubmit.archive(args.expid, noclean=args.noclean, uncompress=args.uncompress, rocrate=args.rocrate) elif args.command == 'unarchive': @@ -2433,6 +2441,12 @@ class Autosubmit: exp_history.finish_current_experiment_run() except Exception: Log.warning("Database is locked") + # Create rocrate object if requested + rocrate_data = as_conf.experiment_data.get("ROCRATE", None) + if rocrate_data: + Autosubmit.provenance(expid, rocrate=True) + else: + Log.warning("Can't find ROCRATE in YAML configuration") except BaseLockException: terminate_child_process(expid) raise @@ -4256,6 +4270,32 @@ class Autosubmit: from autosubmit.provenance.rocrate import create_rocrate_archive return create_rocrate_archive(as_conf, rocrate_json, jobs, start_time, end_time, path) + + @staticmethod + def provenance(expid, rocrate=False): + """" + :param expid: experiment identifier + :type expid: str + :param rocrate: flag to enable RO-Crate + :type rocrate: bool + """"" + aslogs_folder = Path( + BasicConfig.LOCAL_ROOT_DIR, + expid, + BasicConfig.LOCAL_TMP_DIR, + BasicConfig.LOCAL_ASLOG_DIR + ) + + if rocrate: + try: + Autosubmit.rocrate(expid, Path(aslogs_folder)) + Log.info('RO-Crate ZIP file created!') + except Exception as e: + raise AutosubmitCritical( + f"Error creating RO-Crate ZIP file: {str(e)}", 7012) + else: + raise AutosubmitCritical( + "Can not create RO-Crate ZIP file. Argument '--rocrate' required", 7012) @staticmethod def archive(expid, noclean=True, uncompress=True, rocrate=False): diff --git a/autosubmit/provenance/rocrate.py b/autosubmit/provenance/rocrate.py index cbfac2da5794c970055fdd442d89ccaadc8a12da..3f7aa9e8c3898f77f24cddf1e9bb936fe31431d3 100644 --- a/autosubmit/provenance/rocrate.py +++ b/autosubmit/provenance/rocrate.py @@ -23,6 +23,7 @@ import datetime import json import mimetypes import os +import time import subprocess from pathlib import Path from textwrap import dedent @@ -557,6 +558,7 @@ def create_rocrate_archive( crate.add_or_update_jsonld(jsonld_node) # Write RO-Crate ZIP. - crate.write_zip(Path(path, f"{expid}.zip")) + date = time.strftime("%Y%m%d%H%M%S", time.localtime(os.path.getmtime(path))) + crate.write_zip(Path(path, f"{expid}-{date}.zip")) Log.info(f'RO-Crate archive written to {experiment_path}') return crate diff --git a/test/unit/test_provenance.py b/test/unit/test_provenance.py new file mode 100644 index 0000000000000000000000000000000000000000..43d29272d858afad0171e9549ea749d77abe943f --- /dev/null +++ b/test/unit/test_provenance.py @@ -0,0 +1,59 @@ +import pytest +from pathlib import Path +from autosubmit.autosubmit import Autosubmit +from log.log import AutosubmitCritical +import os +from unittest.mock import patch + +@pytest.fixture +def mock_paths(tmp_path): + """ + Fixture to set temporary paths for BasicConfig values. + """ + with patch('autosubmitconfigparser.config.basicconfig.BasicConfig.LOCAL_ROOT_DIR', str(tmp_path)), \ + patch('autosubmitconfigparser.config.basicconfig.BasicConfig.LOCAL_TMP_DIR', 'tmp'), \ + patch('autosubmitconfigparser.config.basicconfig.BasicConfig.LOCAL_ASLOG_DIR', 'ASLOGS'): + yield tmp_path + +def test_provenance_rocrate_success(mock_paths): + """ + Test the provenance function when rocrate=True and the process is successful. + """ + with patch('autosubmit.autosubmit.Autosubmit.rocrate') as mock_rocrate, \ + patch('log.log.Log.info') as mock_log_info: + + expid = "expid123" + exp_folder = os.path.join(str(mock_paths), expid) + tmp_folder = os.path.join(exp_folder, 'tmp') + aslogs_folder = os.path.join(tmp_folder, 'ASLOGS') + expected_aslogs_path = aslogs_folder + + Autosubmit.provenance(expid, rocrate=True) + + mock_rocrate.assert_called_once_with(expid, Path(expected_aslogs_path)) + mock_log_info.assert_called_once_with('RO-Crate ZIP file created!') + +def test_provenance_rocrate_failure(): + """ + Test the provenance function when Autosubmit.rocrate fails + """ + with patch('autosubmit.autosubmit.Autosubmit.rocrate', side_effect=Exception("Mocked exception")) as mock_rocrate: + + with pytest.raises(AutosubmitCritical) as excinfo: + Autosubmit.provenance("expid123", rocrate=True) + + assert "Error creating RO-Crate ZIP file: Mocked exception" in str(excinfo) + + mock_rocrate.assert_called_once() + + +def test_provenance_no_rocrate(): + """ + Test the provenance function when rocrate=False + """ + with patch('autosubmit.autosubmit.Autosubmit.rocrate') as mock_rocrate: + with pytest.raises(AutosubmitCritical) as excinfo: + Autosubmit.provenance("expid123", rocrate=False) + + assert "Can not create RO-Crate ZIP file. Argument '--rocrate' required" in str(excinfo) + mock_rocrate.assert_not_called()