Skip to content

Commit 95fa69d

Browse files
committed
--validate works with $graph docs lacking a "main"
1 parent d130604 commit 95fa69d

File tree

6 files changed

+207
-34
lines changed

6 files changed

+207
-34
lines changed

cwltool/errors.py

+4
Original file line numberDiff line numberDiff line change
@@ -8,3 +8,7 @@ class UnsupportedRequirement(WorkflowException):
88

99
class ArgumentException(Exception):
1010
"""Mismatched command line arguments provided."""
11+
12+
13+
class GraphTargetMissingException(WorkflowException):
14+
"""When a $graph is encountered and there is no target and no main/#main."""

cwltool/load_tool.py

+2-2
Original file line numberDiff line numberDiff line change
@@ -34,7 +34,7 @@
3434

3535
from . import CWL_CONTENT_TYPES, process, update
3636
from .context import LoadingContext
37-
from .errors import WorkflowException
37+
from .errors import GraphTargetMissingException
3838
from .loghandler import _logger
3939
from .process import Process, get_schema, shortname
4040
from .update import ALLUPDATES
@@ -455,7 +455,7 @@ def make_tool(
455455
processobj = obj
456456
break
457457
if not processobj:
458-
raise WorkflowException(
458+
raise GraphTargetMissingException(
459459
"Tool file contains graph of multiple objects, must specify "
460460
"one of #%s"
461461
% ", #".join(

cwltool/main.py

+45-32
Original file line numberDiff line numberDiff line change
@@ -48,7 +48,12 @@
4848
from .argparser import arg_parser, generate_parser, get_default_args
4949
from .context import LoadingContext, RuntimeContext, getdefault
5050
from .cwlrdf import printdot, printrdf
51-
from .errors import ArgumentException, UnsupportedRequirement, WorkflowException
51+
from .errors import (
52+
ArgumentException,
53+
GraphTargetMissingException,
54+
UnsupportedRequirement,
55+
WorkflowException,
56+
)
5257
from .executors import JobExecutor, MultithreadedJobExecutor, SingleJobExecutor
5358
from .load_tool import (
5459
default_loader,
@@ -424,7 +429,7 @@ def init_job_order(
424429
input_required,
425430
)
426431
if args.tool_help:
427-
toolparser.print_help()
432+
toolparser.print_help(cast(IO[str], stdout))
428433
exit(0)
429434
cmd_line = vars(toolparser.parse_args(args.job_order))
430435
for record_name in records:
@@ -474,17 +479,6 @@ def init_job_order(
474479
job_order_object = {}
475480
job_order_object[shortname(inp["id"])] = inp["default"]
476481

477-
if len(job_order_object) == 0:
478-
if process.tool["inputs"]:
479-
if toolparser is not None:
480-
print(f"\nOptions for {args.workflow} ")
481-
toolparser.print_help()
482-
_logger.error("")
483-
_logger.error("Input object required, use --help for details")
484-
exit(1)
485-
else:
486-
job_order_object = {}
487-
488482
def path_to_loc(p: CWLObjectType) -> None:
489483
if "location" not in p and "path" in p:
490484
p["location"] = p["path"]
@@ -574,7 +568,7 @@ def printdeps(
574568
elif relative_deps == "cwd":
575569
base = os.getcwd()
576570
visit_class(deps, ("File", "Directory"), functools.partial(make_relative, base))
577-
stdout.write(json_dumps(deps, indent=4, default=str))
571+
print(json_dumps(deps, indent=4, default=str), file=stdout)
578572

579573

580574
def prov_deps(
@@ -946,18 +940,18 @@ def print_targets(
946940
for f in ("outputs", "inputs"):
947941
if tool.tool[f]:
948942
_logger.info("%s %s%s targets:", prefix[:-1], f[0].upper(), f[1:-1])
949-
stdout.write(
943+
print(
950944
" "
951-
+ "\n ".join([f"{prefix}{shortname(t['id'])}" for t in tool.tool[f]])
952-
+ "\n"
945+
+ "\n ".join([f"{prefix}{shortname(t['id'])}" for t in tool.tool[f]]),
946+
file=stdout,
953947
)
954948
if "steps" in tool.tool:
955949
loading_context = copy.copy(loading_context)
956950
loading_context.requirements = tool.requirements
957951
loading_context.hints = tool.hints
958952
_logger.info("%s steps targets:", prefix[:-1])
959953
for t in tool.tool["steps"]:
960-
stdout.write(f" {prefix}{shortname(t['id'])}\n")
954+
print(f" {prefix}{shortname(t['id'])}", file=stdout)
961955
run: Union[str, Process, Dict[str, Any]] = t["run"]
962956
if isinstance(run, str):
963957
process = make_tool(run, loading_context)
@@ -1040,20 +1034,20 @@ def main(
10401034
)
10411035

10421036
if args.version:
1043-
print(versionfunc())
1037+
print(versionfunc(), file=stdout)
10441038
return 0
10451039
_logger.info(versionfunc())
10461040

10471041
if args.print_supported_versions:
1048-
print("\n".join(supported_cwl_versions(args.enable_dev)))
1042+
print("\n".join(supported_cwl_versions(args.enable_dev)), file=stdout)
10491043
return 0
10501044

10511045
if not args.workflow:
10521046
if os.path.isfile("CWLFile"):
10531047
args.workflow = "CWLFile"
10541048
else:
10551049
_logger.error("CWL document required, no input file was provided")
1056-
parser.print_help()
1050+
parser.print_help(stderr)
10571051
return 1
10581052

10591053
if args.ga4gh_tool_registries:
@@ -1126,7 +1120,7 @@ def main(
11261120
processobj, metadata = loadingContext.loader.resolve_ref(uri)
11271121
processobj = cast(Union[CommentedMap, CommentedSeq], processobj)
11281122
if args.pack:
1129-
stdout.write(print_pack(loadingContext, uri))
1123+
print(print_pack(loadingContext, uri), file=stdout)
11301124
return 0
11311125

11321126
if args.provenance and runtimeContext.research_obj:
@@ -1136,29 +1130,45 @@ def main(
11361130
)
11371131

11381132
if args.print_pre:
1139-
stdout.write(
1133+
print(
11401134
json_dumps(
11411135
processobj,
11421136
indent=4,
11431137
sort_keys=True,
11441138
separators=(",", ": "),
11451139
default=str,
1146-
)
1140+
),
1141+
file=stdout,
11471142
)
11481143
return 0
11491144

1150-
tool = make_tool(uri, loadingContext)
1145+
try:
1146+
tool = make_tool(uri, loadingContext)
1147+
except GraphTargetMissingException as main_missing_exc:
1148+
if args.validate:
1149+
logging.warn(
1150+
"File contains $graph of multiple objects and no default "
1151+
"process (#main). Validating all objects:"
1152+
)
1153+
for entry in workflowobj["$graph"]:
1154+
entry_id = entry["id"]
1155+
make_tool(entry_id, loadingContext)
1156+
print(f"{entry_id} is valid CWL.", file=stdout)
1157+
else:
1158+
raise main_missing_exc
1159+
11511160
if args.make_template:
11521161
make_template(tool)
11531162
return 0
11541163

11551164
if args.validate:
1156-
print(f"{args.workflow} is valid CWL.")
1165+
print(f"{args.workflow} is valid CWL.", file=stdout)
11571166
return 0
11581167

11591168
if args.print_rdf:
1160-
stdout.write(
1161-
printrdf(tool, loadingContext.loader.ctx, args.rdf_serializer)
1169+
print(
1170+
printrdf(tool, loadingContext.loader.ctx, args.rdf_serializer),
1171+
file=stdout,
11621172
)
11631173
return 0
11641174

@@ -1194,14 +1204,15 @@ def main(
11941204
if args.print_subgraph:
11951205
if "name" in tool.tool:
11961206
del tool.tool["name"]
1197-
stdout.write(
1207+
print(
11981208
json_dumps(
11991209
tool.tool,
12001210
indent=4,
12011211
sort_keys=True,
12021212
separators=(",", ": "),
12031213
default=str,
1204-
)
1214+
),
1215+
file=stdout,
12051216
)
12061217
return 0
12071218

@@ -1361,8 +1372,10 @@ def loc_to_path(obj: CWLObjectType) -> None:
13611372
# Unsetting the Generation from final output object
13621373
visit_class(out, ("File",), MutationManager().unset_generation)
13631374

1364-
stdout.write(json_dumps(out, indent=4, ensure_ascii=False, default=str))
1365-
stdout.write("\n")
1375+
print(
1376+
json_dumps(out, indent=4, ensure_ascii=False, default=str),
1377+
file=stdout,
1378+
)
13661379
if hasattr(stdout, "flush"):
13671380
stdout.flush()
13681381

tests/test_misc_cli.py

+68
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,68 @@
1+
"""Tests for various command line options."""
2+
3+
from cwltool.utils import versionstring
4+
5+
from .util import get_data, get_main_output
6+
7+
8+
def test_version() -> None:
9+
"""Test --version."""
10+
return_code, stdout, stderr = get_main_output(["--version"])
11+
assert return_code == 0
12+
assert versionstring() in stdout
13+
14+
15+
def test_print_supported_versions() -> None:
16+
"""Test --print-supported-versions."""
17+
return_code, stdout, stderr = get_main_output(["--print-supported-versions"])
18+
assert return_code == 0
19+
assert "v1.2" in stdout
20+
21+
22+
def test_empty_cmdling() -> None:
23+
"""Test empty command line."""
24+
return_code, stdout, stderr = get_main_output([])
25+
assert return_code == 1
26+
assert "CWL document required, no input file was provided" in stderr
27+
28+
29+
def test_tool_help() -> None:
30+
"""Test --tool-help."""
31+
return_code, stdout, stderr = get_main_output(
32+
["--tool-help", get_data("tests/echo.cwl")]
33+
)
34+
assert return_code == 0
35+
assert "job_order Job input json file" in stdout
36+
37+
38+
def test_basic_pack() -> None:
39+
"""Basic test of --pack. See test_pack.py for detailed testing."""
40+
return_code, stdout, stderr = get_main_output(
41+
["--pack", get_data("tests/wf/revsort.cwl")]
42+
)
43+
assert return_code == 0
44+
assert "$graph" in stdout
45+
46+
47+
def test_basic_print_subgraph() -> None:
48+
"""Basic test of --print-subgraph. See test_subgraph.py for detailed testing."""
49+
return_code, stdout, stderr = get_main_output(
50+
[
51+
"--print-subgraph",
52+
get_data("tests/subgraph/count-lines1-wf.cwl"),
53+
]
54+
)
55+
assert return_code == 0
56+
assert "cwlVersion" in stdout
57+
58+
59+
def test_error_graph_with_no_default() -> None:
60+
"""Ensure a useful error is printed on $graph docs that lack a main/#main."""
61+
exit_code, stdout, stderr = get_main_output(
62+
["--tool-help", get_data("tests/wf/packed_no_main.cwl")]
63+
) # could be any command except --validate
64+
assert exit_code == 1
65+
assert (
66+
"Tool file contains graph of multiple objects, must specify one of #echo, #cat, #collision"
67+
in stderr
68+
)

tests/test_validate.py

+16
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,16 @@
1+
"""Tests --validation."""
2+
3+
4+
from .util import get_data, get_main_output
5+
6+
7+
def test_validate_graph_with_no_default() -> None:
8+
"""Ensure that --validate works on $graph docs that lack a main/#main."""
9+
exit_code, stdout, stderr = get_main_output(
10+
["--validate", get_data("tests/wf/packed_no_main.cwl")]
11+
)
12+
assert exit_code == 0
13+
assert "packed_no_main.cwl#echo is valid CWL" in stdout
14+
assert "packed_no_main.cwl#cat is valid CWL" in stdout
15+
assert "packed_no_main.cwl#collision is valid CWL" in stdout
16+
assert "tests/wf/packed_no_main.cwl is valid CWL" in stdout

tests/wf/packed_no_main.cwl

+72
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,72 @@
1+
cwlVersion: v1.2
2+
$graph:
3+
- id: echo
4+
class: CommandLineTool
5+
inputs:
6+
text:
7+
type: string
8+
inputBinding: {}
9+
10+
outputs:
11+
fileout:
12+
type: File
13+
outputBinding:
14+
glob: out.txt
15+
16+
baseCommand: echo
17+
stdout: out.txt
18+
19+
- id: cat
20+
class: CommandLineTool
21+
inputs:
22+
file1:
23+
type: File
24+
inputBinding:
25+
position: 1
26+
file2:
27+
type: File
28+
inputBinding:
29+
position: 2
30+
31+
outputs:
32+
fileout:
33+
type: File
34+
outputBinding:
35+
glob: out.txt
36+
37+
baseCommand: cat
38+
stdout: out.txt
39+
40+
- class: Workflow
41+
id: collision
42+
43+
inputs:
44+
input_1: string
45+
input_2: string
46+
47+
outputs:
48+
fileout:
49+
type: File
50+
outputSource: cat_step/fileout
51+
52+
steps:
53+
echo_1:
54+
run: "#echo"
55+
in:
56+
text: input_1
57+
out: [fileout]
58+
59+
echo_2:
60+
run: "#echo"
61+
in:
62+
text: input_2
63+
out: [fileout]
64+
65+
cat_step:
66+
run: "#cat"
67+
in:
68+
file1:
69+
source: echo_1/fileout
70+
file2:
71+
source: echo_2/fileout
72+
out: [fileout]

0 commit comments

Comments
 (0)