Skip to content

Commit

Permalink
codelab: adding an assertion construct to WDL
Browse files Browse the repository at this point in the history
  • Loading branch information
mlin committed Apr 30, 2020
1 parent 50b4132 commit 2bb6a55
Show file tree
Hide file tree
Showing 6 changed files with 67 additions and 4 deletions.
8 changes: 8 additions & 0 deletions WDL/Tree.py
Original file line number Diff line number Diff line change
Expand Up @@ -230,6 +230,14 @@ def _workflow_node_dependencies(self) -> Iterable[str]:
yield from _expr_workflow_node_dependencies(self.expr)


class Assertion(Decl):
message: str

def __init__(self, pos: SourcePosition, expr: Expr.Base) -> None:
super().__init__(pos, Type.Boolean(), f"_assert_L{pos.line}C{pos.column}", expr)
self.message = f"assertion failed: {str(expr)} ({pos.uri} Ln {pos.line} Col {pos.column})"


class Task(SourceNode):
"""
WDL Task
Expand Down
9 changes: 6 additions & 3 deletions WDL/_grammar.py
Original file line number Diff line number Diff line change
Expand Up @@ -289,11 +289,11 @@
///////////////////////////////////////////////////////////////////////////////////////////////////
workflow: "workflow" CNAME "{" workflow_element* "}"
?workflow_element: input_decls | any_decl | call | scatter | conditional | workflow_outputs | meta_section
?workflow_element: input_decls | any_decl | call | scatter | conditional | workflow_outputs | meta_section | assertion
scatter: "scatter" "(" CNAME "in" expr ")" "{" inner_workflow_element* "}"
conditional: "if" "(" expr ")" "{" inner_workflow_element* "}"
?inner_workflow_element: any_decl | call | scatter | conditional
?inner_workflow_element: any_decl | call | scatter | conditional | assertion
call: "call" namespaced_ident call_body? -> call
| "call" namespaced_ident "as" CNAME call_body? -> call_as
Expand All @@ -314,6 +314,7 @@
| meta_section
| runtime_section
| any_decl -> noninput_decl
| assertion -> noninput_decl
tasks: task*
Expand Down Expand Up @@ -352,6 +353,8 @@
struct: "struct" CNAME "{" unbound_decl* "}"
assertion: "assert" expr
///////////////////////////////////////////////////////////////////////////////////////////////////
// type
///////////////////////////////////////////////////////////////////////////////////////////////////
Expand Down Expand Up @@ -484,7 +487,7 @@
%ignore COMMENT
"""
keywords["development"] = set(
"Array Float Int Map None Pair String alias as call command else false if import input left meta object output parameter_meta right runtime scatter struct task then true workflow".split(
"Array Float Int Map None Pair String alias as assert call command else false if import input left meta object output parameter_meta right runtime scatter struct task then true workflow".split(
" "
)
)
Expand Down
3 changes: 3 additions & 0 deletions WDL/_parser.py
Original file line number Diff line number Diff line change
Expand Up @@ -268,6 +268,9 @@ def decl(self, items, meta):
self._sp(meta), items[0], items[1].value, (items[2] if len(items) > 2 else None)
)

def assertion(self, items, meta):
return Tree.Assertion(self._sp(meta), items[0])

def input_decls(self, items, meta):
return {"inputs": items}

Expand Down
2 changes: 2 additions & 0 deletions WDL/runtime/task.py
Original file line number Diff line number Diff line change
Expand Up @@ -1035,6 +1035,8 @@ def map_files(v: Value.Base) -> Value.Base:
vj = json.dumps(v.json)
logger.info(_("eval", name=decl.name, value=(v.json if len(vj) < 4096 else "(((large)))")))
container_env = container_env.bind(decl.name, v)
if isinstance(decl, Tree.Assertion) and not v.value:
raise Error.RuntimeError(decl.message)

return container_env

Expand Down
4 changes: 3 additions & 1 deletion WDL/runtime/workflow.py
Original file line number Diff line number Diff line change
Expand Up @@ -45,7 +45,7 @@
from contextlib import ExitStack
import importlib_metadata
from .. import Env, Type, Value, Tree, StdLib
from ..Error import InputError
from ..Error import InputError, RuntimeError
from .task import run_local_task, _filenames, link_outputs
from .download import able as downloadable, run_cached as download
from .._util import (
Expand Down Expand Up @@ -354,6 +354,8 @@ def _do_job(
else:
assert job.node.type.optional
v = Value.Null()
if isinstance(job.node, Tree.Assertion) and not v.value:
raise RuntimeError(job.node.message)
return Env.Bindings(Env.Binding(job.node.name, v))

if isinstance(job.node, WorkflowOutputs):
Expand Down
45 changes: 45 additions & 0 deletions tests/test_7runner.py
Original file line number Diff line number Diff line change
Expand Up @@ -167,3 +167,48 @@ def test_download_cache4(self):
line = json.loads(line)
if "downloaded input files" in line["message"]:
self.assertEqual(line["downloaded"], 0)


class TestAssert(RunnerTestCase):
task1 = R"""
version development
task div {
input {
Int numerator
Int denominator
}
assert denominator != 0
command {
expr ~{numerator} / ~{denominator}
}
output {
Int quotient = read_int(stdout())
}
}
"""

def test_positive(self):
outputs = self._run(self.task1, {"numerator": 7, "denominator": 2})
self.assertEqual(outputs["quotient"], 3)

def test_negative(self):
self._run(self.task1, {"numerator": 7, "denominator": 0}, expected_exception=WDL.Error.RuntimeError)

wf1 = R"""
version development
workflow div {
input {
Int numerator
Int denominator
}
assert denominator != 0
output {
Int quotient = numerator / denominator
}
}
"""

def test_workflow(self):
outputs = self._run(self.wf1, {"numerator": 7, "denominator": 2})
self.assertEqual(outputs["quotient"], 3)
self._run(self.wf1, {"numerator": 7, "denominator": 0}, expected_exception=WDL.Error.RuntimeError)

0 comments on commit 2bb6a55

Please sign in to comment.