diff --git a/src/deadline/client/cli/_groups/job_group.py b/src/deadline/client/cli/_groups/job_group.py index f0dfe696..9b92495f 100644 --- a/src/deadline/client/cli/_groups/job_group.py +++ b/src/deadline/client/cli/_groups/job_group.py @@ -259,6 +259,11 @@ def _download_job_output( output_paths_by_root = job_output_downloader.get_output_paths_by_root() + # If no output paths were found, log a message and exit. + if output_paths_by_root == {}: + click.echo(_get_no_output_message(is_json_format)) + return + # Check if the asset roots came from different OS. If so, prompt users to # select alternative root paths to download to, (regardless of the auto-accept.) asset_roots = list(output_paths_by_root.keys()) @@ -403,6 +408,17 @@ def _get_start_message( return f"Downloading output from Job {job_name!r} Step {step_id} Task {task_id}" +def _get_no_output_message(is_json_format: bool) -> str: + msg = ( + "No output files to download. Please check the job status and" + " ensure that it has completed without issues." + ) + if is_json_format: + return _get_json_line(JSON_MSG_TYPE_SUMMARY, msg) + else: + return msg + + def _get_mismatch_os_root_warning(root: str, is_json_format: bool) -> str: if is_json_format: return _get_json_line(JSON_MSG_TYPE_PATH, [root]) diff --git a/src/deadline/job_attachments/download.py b/src/deadline/job_attachments/download.py index 99b95ec2..aac17cfe 100644 --- a/src/deadline/job_attachments/download.py +++ b/src/deadline/job_attachments/download.py @@ -338,12 +338,12 @@ def download_file( if not s3_client: s3_client = get_s3_client(session=session) - # The modified time in the manifest is in microseconds, but utime requires the time be expressed in seconds. + # The modified time in the manifest is in microseconds, but utime requires the time be expressed in seconds. modified_time_override = file.mtime / 1000000 # type: ignore[attr-defined] file_bytes = file.size - # Python will handle the path seperator '/' correctly on every platform. + # Python will handle the path separator '/' correctly on every platform. local_file_name = Path(local_download_dir).joinpath(file.path) s3_key = f"{cas_prefix}/{file.hash}" if cas_prefix else file.hash diff --git a/test/unit/deadline_client/cli/test_cli_job.py b/test/unit/deadline_client/cli/test_cli_job.py index de5209f1..c15136a8 100644 --- a/test/unit/deadline_client/cli/test_cli_job.py +++ b/test/unit/deadline_client/cli/test_cli_job.py @@ -269,7 +269,7 @@ def test_cli_job_download_output_stdout_with_only_required_input( fresh_deadline_config, tmp_path: Path ): """ - Tests whether the ouptut messages printed to stdout match expected messages + Tests whether the output messages printed to stdout match expected messages when download-output command is executed. """ with patch.object(api._session, "get_deadline_endpoint_url") as session_endpoint: @@ -356,12 +356,76 @@ def test_cli_job_download_output_stdout_with_only_required_input( assert result.exit_code == 0 -def test_cli_job_dowuload_output_stdout_with_json_format( +def test_cli_job_download_no_output_stdout(fresh_deadline_config, tmp_path: Path): + """ + Tests whether the output messages printed to stdout match expected messages + when executing download-output command for a job that don't have any output yet. + """ + with patch.object(api._session, "get_deadline_endpoint_url") as session_endpoint: + session_endpoint.return_value = "fake-endpoint-url" + config.set_setting("defaults.farm_id", MOCK_FARM_ID) + config.set_setting("defaults.queue_id", MOCK_QUEUE_ID) + + with patch.object(api, "get_boto3_client") as boto3_client_mock, patch.object( + job_group, "OutputDownloader" + ) as MockOutputDownloader, patch.object( + job_group, "_get_conflicting_filenames", return_value=[] + ), patch.object( + job_group, "round", return_value=0 + ), patch.object( + api, "get_queue_user_boto3_session" + ): + mock_download = MagicMock() + MockOutputDownloader.return_value.download_job_output = mock_download + MockOutputDownloader.return_value.get_output_paths_by_root.return_value = {} + + mock_host_path_format_name = PathFormat.get_host_path_format_string() + boto3_client_mock().get_job.return_value = { + "name": "Mock Job", + "attachments": { + "manifests": [ + { + "rootPath": "/root/path", + "rootPathFormat": PathFormat(mock_host_path_format_name), + "outputRelativeDirectories": ["."], + } + ], + }, + } + boto3_client_mock().get_queue.side_effect = [MOCK_GET_QUEUE_RESPONSE] + + runner = CliRunner() + result = runner.invoke( + main, + ["job", "download-output", "--job-id", MOCK_JOB_ID, "--output", "verbose"], + input="", + ) + + MockOutputDownloader.assert_called_once_with( + s3_settings=JobAttachmentS3Settings(**MOCK_GET_QUEUE_RESPONSE["jobAttachmentSettings"]), # type: ignore + farm_id=MOCK_FARM_ID, + queue_id=MOCK_QUEUE_ID, + job_id=MOCK_JOB_ID, + step_id=None, + task_id=None, + session=ANY, + ) + + assert ( + """Downloading output from Job 'Mock Job' +No output files to download. Please check the job status and ensure that it has completed without issues. +""" + in result.output + ) + assert result.exit_code == 0 + + +def test_cli_job_download_output_stdout_with_json_format( fresh_deadline_config, tmp_path: Path, ): """ - Tests whether the ouptut messages printed to stdout match expected messages + Tests whether the output messages printed to stdout match expected messages when download-output command is executed with `--output json` option. """ with patch.object(api._session, "get_deadline_endpoint_url") as session_endpoint: