From 83b99b468af6792d1313f18ff741f3d32a7c3585 Mon Sep 17 00:00:00 2001 From: "Michael R. Crusoe" Date: Fri, 29 Sep 2023 16:49:23 +0200 Subject: [PATCH] extend validation to the inputs of the top level proces --- cwltool/context.py | 3 +++ cwltool/executors.py | 12 +++++++++ cwltool/main.py | 6 ++++- tests/test_validate.py | 40 +++++++++++++++++++++++++++- tests/wf/1st-workflow_bad_inputs.yml | 4 +++ 5 files changed, 63 insertions(+), 2 deletions(-) create mode 100644 tests/wf/1st-workflow_bad_inputs.yml diff --git a/cwltool/context.py b/cwltool/context.py index 7f30e4c30..cbfa24102 100644 --- a/cwltool/context.py +++ b/cwltool/context.py @@ -2,6 +2,7 @@ import copy import os import shutil +import sys import tempfile import threading from typing import ( @@ -197,6 +198,8 @@ def __init__(self, kwargs: Optional[Dict[str, Any]] = None) -> None: self.mpi_config: MpiConfig = MpiConfig() self.default_stdout: Optional[Union[IO[bytes], TextIO]] = None self.default_stderr: Optional[Union[IO[bytes], TextIO]] = None + self.validate_only: bool = False + self.validate_stdout: Union[IO[bytes], TextIO, IO[str]] = sys.stdout super().__init__(kwargs) if self.tmp_outdir_prefix == "": self.tmp_outdir_prefix = self.tmpdir_prefix diff --git a/cwltool/executors.py b/cwltool/executors.py index 3cbac72c7..02b4a40d8 100644 --- a/cwltool/executors.py +++ b/cwltool/executors.py @@ -143,6 +143,8 @@ def check_for_abstract_op(tool: CWLObjectType) -> None: process.requirements.append(req) self.run_jobs(process, job_order_object, logger, runtime_context) + if runtime_context.validate_only is True: + return (None, "ValidationSuccess") if self.final_output and self.final_output[0] is not None and finaloutdir is not None: self.final_output[0] = relocateOutputs( @@ -238,6 +240,16 @@ def run_jobs( process_run_id = prov_obj.record_process_start(process, job) runtime_context = runtime_context.copy() runtime_context.process_run_id = process_run_id + if runtime_context.validate_only is True: + if isinstance(job, WorkflowJob): + name = job.tool.lc.filename + else: + name = getattr(job, "name", str(job)) + print( + f"{name} is valid CWL. No errors detected in the inputs.", + file=runtime_context.validate_stdout, + ) + return job.run(runtime_context) else: logger.error("Workflow cannot make any more progress.") diff --git a/cwltool/main.py b/cwltool/main.py index 965f863a0..527275f4c 100755 --- a/cwltool/main.py +++ b/cwltool/main.py @@ -1134,7 +1134,7 @@ def main( make_template(tool, stdout) return 0 - if args.validate: + if len(args.job_order) == 0 and args.validate: print(f"{args.workflow} is valid CWL.", file=stdout) return 0 @@ -1294,10 +1294,14 @@ def main( use_biocontainers=args.beta_use_biocontainers, container_image_cache_path=args.beta_dependencies_directory, ) + runtimeContext.validate_only = args.validate + runtimeContext.validate_stdout = stdout (out, status) = real_executor( tool, initialized_job_order_object, runtimeContext, logger=_logger ) + if runtimeContext.validate_only is True: + return 0 if out is not None: if runtimeContext.research_obj is not None: diff --git a/tests/test_validate.py b/tests/test_validate.py index e809df386..be50d357d 100644 --- a/tests/test_validate.py +++ b/tests/test_validate.py @@ -1,5 +1,5 @@ """Tests --validation.""" - +import re from .util import get_data, get_main_output @@ -14,3 +14,41 @@ def test_validate_graph_with_no_default() -> None: assert "packed_no_main.cwl#cat is valid CWL" in stdout assert "packed_no_main.cwl#collision is valid CWL" in stdout assert "tests/wf/packed_no_main.cwl is valid CWL" in stdout + + +def test_validate_with_valid_input_object() -> None: + """Ensure that --validate with a valid input object.""" + exit_code, stdout, stderr = get_main_output( + [ + "--validate", + get_data("tests/wf/1st-workflow.cwl"), + "--inp", + get_data("tests/wf/1st-workflow.cwl"), + "--ex", + "FOO", + ] + ) + assert exit_code == 0 + assert "tests/wf/1st-workflow.cwl is valid CWL. No errors detected in the inputs." in stdout + + +def test_validate_with_invalid_input_object() -> None: + """Ensure that --validate with an invalid input object.""" + exit_code, stdout, stderr = get_main_output( + [ + "--validate", + get_data("tests/wf/1st-workflow.cwl"), + get_data("tests/wf/1st-workflow_bad_inputs.yml"), + ] + ) + assert exit_code == 1 + stderr = re.sub(r"\s\s+", " ", stderr) + assert "Invalid job input record" in stderr + assert ( + "tests/wf/1st-workflow_bad_inputs.yml:2:1: * the 'ex' field is not " + "valid because the value is not string" in stderr + ) + assert ( + "tests/wf/1st-workflow_bad_inputs.yml:1:1: * the 'inp' field is not " + "valid because is not a dict. Expected a File object." in stderr + ) diff --git a/tests/wf/1st-workflow_bad_inputs.yml b/tests/wf/1st-workflow_bad_inputs.yml new file mode 100644 index 000000000..d95783be2 --- /dev/null +++ b/tests/wf/1st-workflow_bad_inputs.yml @@ -0,0 +1,4 @@ +inp: 42 +ex: + class: File + path: 1st-workflow.cwl