diff --git a/CHANGELOG.md b/CHANGELOG.md index 9f4d9c30560..aecc6361de4 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -2,6 +2,10 @@ ## 87 Release Notes +### `upgrade` command removed from Womtool + +Womtool previously supported a `womtool upgrade` command for upgrading draft-2 WDLs to 1.0. With WDL 1.1 soon to become the latest supported version, this functionality is retiring. + ### Replacement of `gsutil` with `gcloud storage` In this release, all **localization** functionality on the GCP backend migrates to use the more modern and performant `gcloud storage`. With sufficiently powerful worker VMs, Cromwell can now localize at up to 1200 MB/s [0][1][2]. diff --git a/centaur/src/it/scala/centaur/AbstractCentaurTestCaseSpec.scala b/centaur/src/it/scala/centaur/AbstractCentaurTestCaseSpec.scala index 5a6606bce68..f90fb21e537 100644 --- a/centaur/src/it/scala/centaur/AbstractCentaurTestCaseSpec.scala +++ b/centaur/src/it/scala/centaur/AbstractCentaurTestCaseSpec.scala @@ -76,59 +76,6 @@ abstract class AbstractCentaurTestCaseSpec(cromwellBackends: List[String], runOrDont(testCase, tags, isIgnored, retries, runTestAndDeleteZippedImports()) } - def executeWdlUpgradeTest(testCase: CentaurTestCase): Unit = - executeStandardTest(wdlUpgradeTestWdl(testCase)) - - private def wdlUpgradeTestWdl(testCase: CentaurTestCase): CentaurTestCase = { - import better.files.File - import womtool.WomtoolMain - - // The suffix matters because WomGraphMaker.getBundle() uses it to choose the language factory - val rootWorkflowFile = File.newTemporaryFile(suffix = ".wdl").append(testCase.workflow.data.workflowContent.get) - val workingDir = File.newTemporaryDirectory() - val upgradedImportsDir = File.newTemporaryDirectory() - val rootWorkflowFilepath = workingDir / rootWorkflowFile.name - - // Un-upgraded imports go into the working directory - testCase.workflow.data.zippedImports match { - case Some(importsZip: File) => - importsZip.unzipTo(workingDir) - case None => () - } - - // Upgrade the imports and copy to main working dir (precludes transitive imports; no recursion yet) - workingDir.list.toList.map { file: File => - val upgradedWdl = WomtoolMain.upgrade(file.pathAsString).stdout.get - upgradedImportsDir.createChild(file.name).append(upgradedWdl) - } - - // Copy to working directory after we operate on the imports that are in it - rootWorkflowFile.copyTo(rootWorkflowFilepath) - - val upgradeResult = WomtoolMain.upgrade(rootWorkflowFilepath.pathAsString) - - upgradeResult.stderr match { - case Some(stderr) => println(stderr) - case _ => () - } - - val newCase = testCase.copy( - workflow = testCase.workflow.copy( - testName = testCase.workflow.testName + " (draft-2 to 1.0 upgrade)", - data = testCase.workflow.data.copy( - workflowContent = Option(upgradeResult.stdout.get), // this '.get' catches an error if upgrade fails - zippedImports = Option(upgradedImportsDir.zip()) - ) - ) - )(cromwellTracker) // An empty zip appears to be completely harmless, so no special handling - - rootWorkflowFile.delete(swallowIOExceptions = true) - upgradedImportsDir.delete(swallowIOExceptions = true) - workingDir.delete(swallowIOExceptions = true) - - newCase - } - private def runOrDont(testCase: CentaurTestCase, tags: List[Tag], ignore: Boolean, diff --git a/centaur/src/it/scala/centaur/CentaurTestSuite.scala b/centaur/src/it/scala/centaur/CentaurTestSuite.scala index 20f965ec453..c72d774c4e4 100644 --- a/centaur/src/it/scala/centaur/CentaurTestSuite.scala +++ b/centaur/src/it/scala/centaur/CentaurTestSuite.scala @@ -28,8 +28,6 @@ object CentaurTestSuite extends StrictLogging { logger.info(s"Cromwell under test configured with backends ${cromwellBackends.mkString(", ")}") logger.info(s"Unless overridden by workflow options file, tests use default backend: $defaultBackend") - def isWdlUpgradeTest(testCase: CentaurTestCase): Boolean = testCase.containsTag("wdl_upgrade") - def isEngineUpgradeTest(testCase: CentaurTestCase): Boolean = testCase.containsTag("engine_upgrade") def isPapiUpgradeTest(testCase: CentaurTestCase): Boolean = testCase.containsTag("papi_upgrade") diff --git a/centaur/src/it/scala/centaur/WdlUpgradeTestCaseSpec.scala b/centaur/src/it/scala/centaur/WdlUpgradeTestCaseSpec.scala deleted file mode 100644 index 93f4457d050..00000000000 --- a/centaur/src/it/scala/centaur/WdlUpgradeTestCaseSpec.scala +++ /dev/null @@ -1,15 +0,0 @@ -package centaur - -import org.scalatest.{DoNotDiscover, ParallelTestExecution} - -@DoNotDiscover -class WdlUpgradeTestCaseSpec(cromwellBackends: List[String]) - extends AbstractCentaurTestCaseSpec(cromwellBackends) - with ParallelTestExecution - with CentaurTestSuiteShutdown { - - def this() = this(CentaurTestSuite.cromwellBackends) - - // The WDL version upgrade tests are just regular draft-2 test cases tagged for re-use in testing the upgrade script - allTestCases.filter(CentaurTestSuite.isWdlUpgradeTest) foreach executeWdlUpgradeTest -} diff --git a/centaur/src/main/resources/standardTestCases/composedenginefunctions.test b/centaur/src/main/resources/standardTestCases/composedenginefunctions.test index 68628483740..d51f31c9929 100644 --- a/centaur/src/main/resources/standardTestCases/composedenginefunctions.test +++ b/centaur/src/main/resources/standardTestCases/composedenginefunctions.test @@ -1,6 +1,5 @@ name: composedenginefunctions testFormat: workflowsuccess -tags: ["wdl_upgrade"] files { workflow: composedenginefunctions/composedenginefunctions.wdl diff --git a/centaur/src/main/resources/standardTestCases/conditionals.ifs_in_scatters.test b/centaur/src/main/resources/standardTestCases/conditionals.ifs_in_scatters.test index 2bd93388199..9d20530291a 100644 --- a/centaur/src/main/resources/standardTestCases/conditionals.ifs_in_scatters.test +++ b/centaur/src/main/resources/standardTestCases/conditionals.ifs_in_scatters.test @@ -1,6 +1,6 @@ name: ifs_in_scatters testFormat: workflowsuccess -tags: [ conditionals, "wdl_upgrade" ] +tags: [ conditionals ] files { workflow: conditionals_tests/ifs_in_scatters/ifs_in_scatters.wdl diff --git a/centaur/src/main/resources/standardTestCases/conditionals.nested_lookups.test b/centaur/src/main/resources/standardTestCases/conditionals.nested_lookups.test index 6f5c0fa2c93..cf4ca22c876 100644 --- a/centaur/src/main/resources/standardTestCases/conditionals.nested_lookups.test +++ b/centaur/src/main/resources/standardTestCases/conditionals.nested_lookups.test @@ -1,6 +1,6 @@ name: nested_lookups testFormat: workflowsuccess -tags: [ conditionals, "wdl_upgrade" ] +tags: [ conditionals ] files { workflow: conditionals_tests/nested_lookups/nested_lookups.wdl diff --git a/centaur/src/main/resources/standardTestCases/conditionals.scatters_in_ifs.test b/centaur/src/main/resources/standardTestCases/conditionals.scatters_in_ifs.test index 8d9cc2193b7..f23865eae67 100644 --- a/centaur/src/main/resources/standardTestCases/conditionals.scatters_in_ifs.test +++ b/centaur/src/main/resources/standardTestCases/conditionals.scatters_in_ifs.test @@ -1,6 +1,6 @@ name: scatters_in_ifs testFormat: workflowsuccess -tags: [ conditionals, "wdl_upgrade" ] +tags: [ conditionals ] files { workflow: conditionals_tests/scatters_in_ifs/scatters_in_ifs.wdl diff --git a/centaur/src/main/resources/standardTestCases/conditionals.simple_if.test b/centaur/src/main/resources/standardTestCases/conditionals.simple_if.test index 82794b863d5..50a1c811892 100644 --- a/centaur/src/main/resources/standardTestCases/conditionals.simple_if.test +++ b/centaur/src/main/resources/standardTestCases/conditionals.simple_if.test @@ -1,6 +1,6 @@ name: simple_if testFormat: workflowsuccess -tags: [ conditionals, "wdl_upgrade" ] +tags: [ conditionals ] files { workflow: conditionals_tests/simple_if/simple_if.wdl diff --git a/centaur/src/main/resources/standardTestCases/conditionals.simple_if_workflow_outputs.test b/centaur/src/main/resources/standardTestCases/conditionals.simple_if_workflow_outputs.test index 3aed74d756b..ede32957e47 100644 --- a/centaur/src/main/resources/standardTestCases/conditionals.simple_if_workflow_outputs.test +++ b/centaur/src/main/resources/standardTestCases/conditionals.simple_if_workflow_outputs.test @@ -1,6 +1,6 @@ name: simple_if_workflow_outputs testFormat: workflowsuccess -tags: [ conditionals, "wdl_upgrade" ] +tags: [ conditionals ] files { workflow: conditionals_tests/simple_if_workflow_outputs/simple_if_workflow_outputs.wdl diff --git a/centaur/src/main/resources/standardTestCases/declarations.test b/centaur/src/main/resources/standardTestCases/declarations.test index 53766678b26..bae479145f0 100644 --- a/centaur/src/main/resources/standardTestCases/declarations.test +++ b/centaur/src/main/resources/standardTestCases/declarations.test @@ -2,7 +2,6 @@ name: declarations testFormat: workflowsuccess -tags: ["wdl_upgrade"] files { workflow: declarations/declarations.wdl diff --git a/centaur/src/main/resources/standardTestCases/declarations_as_nodes.test b/centaur/src/main/resources/standardTestCases/declarations_as_nodes.test index d18f39677f6..ff1f4416a24 100644 --- a/centaur/src/main/resources/standardTestCases/declarations_as_nodes.test +++ b/centaur/src/main/resources/standardTestCases/declarations_as_nodes.test @@ -1,6 +1,5 @@ name: declarations_as_nodes testFormat: workflowsuccess -tags: ["wdl_upgrade"] files { workflow: declarations_as_nodes/declarations_as_nodes.wdl diff --git a/centaur/src/main/resources/standardTestCases/hello.test b/centaur/src/main/resources/standardTestCases/hello.test index 33a49bd4071..2f7fd67087f 100644 --- a/centaur/src/main/resources/standardTestCases/hello.test +++ b/centaur/src/main/resources/standardTestCases/hello.test @@ -1,6 +1,5 @@ name: hello testFormat: workflowsuccess -tags: ["wdl_upgrade"] files { workflow: hello/hello.wdl diff --git a/centaur/src/main/resources/standardTestCases/object_access.test b/centaur/src/main/resources/standardTestCases/object_access.test index 1e824f6e78f..84d76d5a893 100644 --- a/centaur/src/main/resources/standardTestCases/object_access.test +++ b/centaur/src/main/resources/standardTestCases/object_access.test @@ -1,6 +1,5 @@ name: object_access testFormat: workflowsuccess -tags: ["wdl_upgrade"] files { workflow: object_access/object_access.wdl diff --git a/centaur/src/main/resources/standardTestCases/scatterchain.test b/centaur/src/main/resources/standardTestCases/scatterchain.test index 26fdb95b27b..c1b02be7abe 100644 --- a/centaur/src/main/resources/standardTestCases/scatterchain.test +++ b/centaur/src/main/resources/standardTestCases/scatterchain.test @@ -1,6 +1,5 @@ name: scatterchain testFormat: workflowsuccess -tags: ["wdl_upgrade"] files { workflow: scatter_chain/scatter_chain.wdl diff --git a/centaur/src/main/resources/standardTestCases/scattergather.test b/centaur/src/main/resources/standardTestCases/scattergather.test index fad78c0a08c..425be6fd3c4 100644 --- a/centaur/src/main/resources/standardTestCases/scattergather.test +++ b/centaur/src/main/resources/standardTestCases/scattergather.test @@ -1,6 +1,5 @@ name: scattergather testFormat: workflowsuccess -tags: ["wdl_upgrade"] files { workflow: scattergather/scattergather.wdl diff --git a/centaur/src/main/resources/standardTestCases/short_circuit.test b/centaur/src/main/resources/standardTestCases/short_circuit.test index a0727dc949f..a961f39071b 100644 --- a/centaur/src/main/resources/standardTestCases/short_circuit.test +++ b/centaur/src/main/resources/standardTestCases/short_circuit.test @@ -2,7 +2,7 @@ name: short_circuit testFormat: workflowsuccess backends: [Local] -tags: [localdockertest, "wdl_upgrade"] +tags: [localdockertest] files { diff --git a/centaur/src/main/resources/standardTestCases/square.test b/centaur/src/main/resources/standardTestCases/square.test index ef0ac9a9ef8..db4e5851ead 100644 --- a/centaur/src/main/resources/standardTestCases/square.test +++ b/centaur/src/main/resources/standardTestCases/square.test @@ -1,6 +1,5 @@ name: square testFormat: workflowsuccess -tags: [ "wdl_upgrade" ] files { workflow: square/square.wdl diff --git a/centaur/src/main/resources/standardTestCases/string_interpolation.test b/centaur/src/main/resources/standardTestCases/string_interpolation.test index 7e7e894f9fa..04736e59ea4 100644 --- a/centaur/src/main/resources/standardTestCases/string_interpolation.test +++ b/centaur/src/main/resources/standardTestCases/string_interpolation.test @@ -1,6 +1,5 @@ name: string_interpolation testFormat: workflowsuccess -tags: ["wdl_upgrade"] files { workflow: string_interpolation/string_interpolation.wdl diff --git a/centaur/src/main/resources/standardTestCases/sub_workflow_hello_world.test b/centaur/src/main/resources/standardTestCases/sub_workflow_hello_world.test index 4b216940cb2..cc18f753e42 100644 --- a/centaur/src/main/resources/standardTestCases/sub_workflow_hello_world.test +++ b/centaur/src/main/resources/standardTestCases/sub_workflow_hello_world.test @@ -1,6 +1,6 @@ name: sub_workflow_hello_world testFormat: workflowsuccess -tags: [subworkflow, "wdl_upgrade"] +tags: [subworkflow] files { workflow: sub_workflow_hello_world/sub_workflow_hello_world.wdl diff --git a/centaur/src/main/resources/standardTestCases/sub_workflow_var_refs.test b/centaur/src/main/resources/standardTestCases/sub_workflow_var_refs.test index 390c089359b..deca93aa195 100644 --- a/centaur/src/main/resources/standardTestCases/sub_workflow_var_refs.test +++ b/centaur/src/main/resources/standardTestCases/sub_workflow_var_refs.test @@ -1,6 +1,6 @@ name: sub_workflow_var_refs testFormat: workflowsuccess -tags: [subworkflow, "wdl_upgrade"] +tags: [subworkflow] files { workflow: sub_workflow_var_refs/sub_workflow_var_refs.wdl diff --git a/centaur/src/main/resources/standardTestCases/valid_labels.test b/centaur/src/main/resources/standardTestCases/valid_labels.test index 1ab748f8a86..6f7f4e7ceec 100644 --- a/centaur/src/main/resources/standardTestCases/valid_labels.test +++ b/centaur/src/main/resources/standardTestCases/valid_labels.test @@ -1,6 +1,6 @@ name: valid_labels testFormat: workflowsuccess -tags: [ labels, "wdl_upgrade" ] +tags: [ labels ] files { workflow: hello/hello.wdl diff --git a/centaur/src/main/resources/standardTestCases/write_lines.test b/centaur/src/main/resources/standardTestCases/write_lines.test index 977459a50c7..fa0c6edd973 100644 --- a/centaur/src/main/resources/standardTestCases/write_lines.test +++ b/centaur/src/main/resources/standardTestCases/write_lines.test @@ -1,6 +1,5 @@ name: write_lines testFormat: workflowsuccess -tags: ["wdl_upgrade"] files { workflow: write_lines/write_lines.wdl @@ -13,4 +12,3 @@ metadata { "calls.write_lines.f2a.executionStatus": Done "outputs.write_lines.a2f_second.x": "a\nb\nc\nd" } - diff --git a/docs/developers/Centaur.md b/docs/developers/Centaur.md index 35d2e350e7d..bbbb011ba32 100644 --- a/docs/developers/Centaur.md +++ b/docs/developers/Centaur.md @@ -133,7 +133,6 @@ our tests into groups depending on which configuration files they require. Below | PAPI Upgrade | `PapiUpgradeTestCaseSpec` | `papiUpgradeTestCases` | | PAPI Upgrade
New Workflows | `CentaurTestSuite` | `papiUpgradeNewWorkflowsTestCases` | | Azure Blob | `CentaurTestSuite ` | `azureBlobTestCases` | -| WDL Upgrade | `WdlUpgradeTestCaseSpec` | `standardTestCases`**** | | (other) | `CentaurTestSuite` | `standardTestCases` | @@ -143,9 +142,7 @@ or `papi_v2beta_centaur_application.conf` \*\* Cromwell Config overrides ([47 link](https://github.com/broadinstitute/cromwell/blob/47/src/ci/bin/test.inc.sh#L213-L221)) \*\*\* Test Directory overrides - ([47 link](https://github.com/broadinstitute/cromwell/blob/47/src/ci/bin/test.inc.sh#L440-L449)) -\*\*\*\* Test Directory only tests tagged with `wdl_upgrade` - ([47 link](https://github.com/broadinstitute/cromwell/blob/47/centaur/src/main/resources/standardTestCases/write_lines.test#L3)) + ([47 link](https://github.com/broadinstitute/cromwell/blob/47/src/ci/bin/test.inc.sh#L440-L449)) - Engine Upgrade: Retrieves the [Cromwell Version](https://github.com/broadinstitute/cromwell/blob/47/project/Version.scala#L8) then retrieves the previous jar/docker-image from DockerHub. Centaur starts with the prior version, then restarts with the compiled source code. diff --git a/src/ci/bin/test.inc.sh b/src/ci/bin/test.inc.sh index 71577ce1e4c..7aebd3698fb 100755 --- a/src/ci/bin/test.inc.sh +++ b/src/ci/bin/test.inc.sh @@ -169,7 +169,6 @@ cromwell::private::create_build_variables() { backend_type="${CROMWELL_BUILD_TYPE}" backend_type="${backend_type#centaurEngineUpgrade}" backend_type="${backend_type#centaurPapiUpgrade}" - backend_type="${backend_type#centaurWdlUpgrade}" backend_type="${backend_type#centaurHoricromtal}" backend_type="${backend_type#centaur}" backend_type="$(echo "${backend_type}" | sed 's/\([A-Z]\)/_\1/g' | tr '[:upper:]' '[:lower:]' | cut -c 2-)" diff --git a/src/ci/bin/testCentaurWdlUpgradeLocal.sh b/src/ci/bin/testCentaurWdlUpgradeLocal.sh deleted file mode 100755 index 31b56ee77d3..00000000000 --- a/src/ci/bin/testCentaurWdlUpgradeLocal.sh +++ /dev/null @@ -1,19 +0,0 @@ -#!/usr/bin/env bash - -set -o errexit -o nounset -o pipefail -# import in shellcheck / CI / IntelliJ compatible ways -# shellcheck source=/dev/null -source "${BASH_SOURCE%/*}/test.inc.sh" || source test.inc.sh - -cromwell::build::setup_common_environment - -cromwell::build::setup_centaur_environment - -cromwell::build::assemble_jars - -# WdlUpgradeTestCaseSpec takes a selection of ordinary draft-2 test cases (tagged as "upgrade"), runs -# them through the draft-2 to 1.0 upgrade script in Womtool, and runs them against local backend. -cromwell::build::run_centaur \ - -s "centaur.WdlUpgradeTestCaseSpec" - -cromwell::build::generate_code_coverage diff --git a/wdl/model/draft3/src/main/scala/wdl/model/draft3/elements/CallElement.scala b/wdl/model/draft3/src/main/scala/wdl/model/draft3/elements/CallElement.scala index 1d4a3e428ca..dbc3685e60d 100644 --- a/wdl/model/draft3/src/main/scala/wdl/model/draft3/elements/CallElement.scala +++ b/wdl/model/draft3/src/main/scala/wdl/model/draft3/elements/CallElement.scala @@ -7,4 +7,9 @@ final case class CallElement(callableReference: String, body: Option[CallBodyElement], override val sourceLocation: Option[SourceFileLocation] ) extends LanguageElement - with WorkflowGraphElement + with WorkflowGraphElement { + override def toString: String = + s"""Call "$callableReference${alias.map(alias => s" as $alias").getOrElse("")}${afters + .map(after => s" after $after") + .mkString}"""" +} diff --git a/wdl/model/draft3/src/main/scala/wdl/model/draft3/elements/DeclarationElement.scala b/wdl/model/draft3/src/main/scala/wdl/model/draft3/elements/DeclarationElement.scala index ef85d3973bf..ea05781a59c 100644 --- a/wdl/model/draft3/src/main/scala/wdl/model/draft3/elements/DeclarationElement.scala +++ b/wdl/model/draft3/src/main/scala/wdl/model/draft3/elements/DeclarationElement.scala @@ -12,7 +12,9 @@ final case class IntermediateValueDeclarationElement(typeElement: TypeElement, name: String, expression: ExpressionElement ) extends WorkflowGraphElement - with TaskSectionElement + with TaskSectionElement { + override def toString: String = s"""Declaration "$name" ($typeElement)""" +} object IntermediateValueDeclarationElement { def fromContent(content: DeclarationContent): IntermediateValueDeclarationElement = @@ -24,7 +26,9 @@ object IntermediateValueDeclarationElement { */ final case class OutputDeclarationElement(typeElement: TypeElement, name: String, expression: ExpressionElement) extends LanguageElement - with WorkflowGraphElement + with WorkflowGraphElement { + override def toString: String = s"""Output "$name" ($typeElement)""" +} object OutputDeclarationElement { def fromContent(content: DeclarationContent): OutputDeclarationElement = @@ -36,7 +40,9 @@ object OutputDeclarationElement { */ final case class InputDeclarationElement(typeElement: TypeElement, name: String, expression: Option[ExpressionElement]) extends LanguageElement - with WorkflowGraphElement + with WorkflowGraphElement { + override def toString: String = s"""Input "$name" ($typeElement)""" +} object DeclarationElement { /* Custom unapply so that elsewhere we can do things like this, and otherwise treat all declarations the same: diff --git a/wdl/model/draft3/src/main/scala/wdl/model/draft3/elements/IfElement.scala b/wdl/model/draft3/src/main/scala/wdl/model/draft3/elements/IfElement.scala index 24d851abead..967f220bedc 100644 --- a/wdl/model/draft3/src/main/scala/wdl/model/draft3/elements/IfElement.scala +++ b/wdl/model/draft3/src/main/scala/wdl/model/draft3/elements/IfElement.scala @@ -1,4 +1,6 @@ package wdl.model.draft3.elements final case class IfElement(conditionExpression: ExpressionElement, graphElements: Seq[WorkflowGraphElement]) - extends WorkflowGraphElement + extends WorkflowGraphElement { + override def toString: String = s"""Condition "$conditionExpression"""" +} diff --git a/wdl/model/draft3/src/main/scala/wdl/model/draft3/elements/ScatterElement.scala b/wdl/model/draft3/src/main/scala/wdl/model/draft3/elements/ScatterElement.scala index d9374e4aea9..8e5d77ead52 100644 --- a/wdl/model/draft3/src/main/scala/wdl/model/draft3/elements/ScatterElement.scala +++ b/wdl/model/draft3/src/main/scala/wdl/model/draft3/elements/ScatterElement.scala @@ -23,4 +23,6 @@ final case class ScatterElement(scatterName: String, // Shorthand to only include certain members for hashing purposes // https://stackoverflow.com/a/31915429/818054 override def hashCode(): Int = (scatterExpression, scatterVariableName, graphElements).## + + override def toString: String = s"""Scatter "$scatterName" ($scatterExpression)""" } diff --git a/wdl/transforms/draft3/src/test/scala/wdl/draft3/transforms/wdlom2wdl/WdlomToWdlFileSpec.scala b/wdl/transforms/draft3/src/test/scala/wdl/draft3/transforms/wdlom2wdl/WdlomToWdlFileSpec.scala deleted file mode 100644 index 88cef1394db..00000000000 --- a/wdl/transforms/draft3/src/test/scala/wdl/draft3/transforms/wdlom2wdl/WdlomToWdlFileSpec.scala +++ /dev/null @@ -1,63 +0,0 @@ -package wdl.draft3.transforms.wdlom2wdl - -import cats.instances.either._ -import better.files.File -import common.Checked -import common.assertion.CromwellTimeoutSpec -import org.scalatest.flatspec.AnyFlatSpec -import org.scalatest.matchers.should.Matchers -import wdl.draft3.transforms.ast2wdlom._ -import wdl.draft3.transforms.parsing.{fileToAst, stringToAst, FileStringParserInput} -import wdl.model.draft3.elements._ -import wdl.transforms.base.wdlom2wdl.WdlWriter.ops._ -import wdl.transforms.base.wdlom2wdl.WdlWriterImpl.fileElementWriter - -class WdlomToWdlFileSpec extends AnyFlatSpec with CromwellTimeoutSpec with Matchers { - - val testDirectory = File("wdl/transforms/draft3/src/test/cases") - - val testFiles = testDirectory.list - - assert(testFiles.nonEmpty) - - private def stripLocationFromGraphElement(ge: WorkflowGraphElement): WorkflowGraphElement = - ge match { - case se: ScatterElement => se.copy(sourceLocation = None) - case ce: CallElement => ce.copy(sourceLocation = None) - case _ => ge - } - - private def stripLocations(wf: WorkflowDefinitionElement): WorkflowDefinitionElement = - wf.copy(graphElements = wf.graphElements.map(stripLocationFromGraphElement)) - - // Remove the source file information, where it exists. - // - // Re-printing does not preserve line numbers at the moment. - private def stripSourceLocations(fe: FileElement): FileElement = - FileElement( - fe.imports, - fe.structs, - fe.workflows.map { case wf => - val wf2 = stripLocations(wf) - wf2.copy(sourceLocation = None) - }.toSeq, - fe.tasks.map { case task => task.copy(sourceLocation = None) }.toSeq - ) - - testFiles.foreach { file => - it should s"write a file that re-evaluates to the same case classes for ${file.name}" in { - - val model: Checked[FileElement] = (fileToAst andThen wrapAst andThen astToFileElement).run(file) - model match { - case Right(wdlModel) => - val newModel = (stringToAst andThen wrapAst andThen astToFileElement).run( - FileStringParserInput(wdlModel.toWdlV1, file.name) - ) - - // Scala case class deep equality is so nice here - newModel.map(stripSourceLocations) shouldEqual model.map(stripSourceLocations) - case Left(_) => fail("Could not load original") - } - } - } -} diff --git a/wdl/transforms/new-base/src/main/scala/wdl/transforms/base/linking/expression/values/LookupEvaluators.scala b/wdl/transforms/new-base/src/main/scala/wdl/transforms/base/linking/expression/values/LookupEvaluators.scala index 216673417cf..1cc006fa261 100644 --- a/wdl/transforms/new-base/src/main/scala/wdl/transforms/base/linking/expression/values/LookupEvaluators.scala +++ b/wdl/transforms/new-base/src/main/scala/wdl/transforms/base/linking/expression/values/LookupEvaluators.scala @@ -12,7 +12,7 @@ import wom.expression.IoFunctionSet import wom.types._ import wom.values._ import wdl.transforms.base.wdlom2wdl.WdlWriter.ops._ -import wdl.transforms.base.wdlom2wdl.WdlWriterImpl._ +import wdl.transforms.base.wdlom2wdl.WdlWriterImpl.indexAccessWriter object LookupEvaluators { diff --git a/wdl/transforms/new-base/src/main/scala/wdl/transforms/base/linking/graph/LinkedGraphMaker.scala b/wdl/transforms/new-base/src/main/scala/wdl/transforms/base/linking/graph/LinkedGraphMaker.scala index 01573c30737..e99b8799db5 100644 --- a/wdl/transforms/new-base/src/main/scala/wdl/transforms/base/linking/graph/LinkedGraphMaker.scala +++ b/wdl/transforms/new-base/src/main/scala/wdl/transforms/base/linking/graph/LinkedGraphMaker.scala @@ -13,8 +13,6 @@ import wom.callable.Callable import wom.types.WomType import scalax.collection.Graph import scalax.collection.GraphEdge.DiEdge -import wdl.transforms.base.wdlom2wdl.WdlWriter.ops._ -import wdl.transforms.base.wdlom2wdl.WdlWriterImpl.graphElementWriter object LinkedGraphMaker { def make(nodes: Set[WorkflowGraphElement], @@ -43,9 +41,6 @@ object LinkedGraphMaker { def getOrdering(linkedGraph: LinkedGraph): ErrorOr[List[WorkflowGraphElement]] = { - def nodeName(workflowGraphElement: WorkflowGraphElement): String = - workflowGraphElement.toWdlV1.linesIterator.toList.headOption.getOrElse("Unnamed Element").replace("\"", "") - // Find the topological order in which we must create the graph nodes: val edges = linkedGraph.edges map { case LinkedGraphEdge(from, to) => DiEdge(from, to) } @@ -63,7 +58,7 @@ object LinkedGraphMaker { // we want to start the cycle with the edge "a -> b" val edgeDict: Map[String, String] = cycle.value.edges.map { case graph.EdgeT(from, to) => - nodeName(from) -> nodeName(to) + from.toString -> to.toString }.toMap val startPoint = edgeDict.keys.toVector.sorted.head var cursor = startPoint @@ -83,7 +78,7 @@ object LinkedGraphMaker { case _ => val edgeStrings = linkedGraph.edges map { case LinkedGraphEdge(from, to) => - s""""${nodeName(from)}" -> "${nodeName(to)}"""" + s""""${from.toString}" -> "${to.toString}"""" } // sort the edges for determinism val edges = edgeStrings.toVector.sorted diff --git a/wdl/transforms/new-base/src/main/scala/wdl/transforms/base/wdlom2wdl/WdlWriterImpl.scala b/wdl/transforms/new-base/src/main/scala/wdl/transforms/base/wdlom2wdl/WdlWriterImpl.scala index a09e94534c4..d4a0b043b17 100644 --- a/wdl/transforms/new-base/src/main/scala/wdl/transforms/base/wdlom2wdl/WdlWriterImpl.scala +++ b/wdl/transforms/new-base/src/main/scala/wdl/transforms/base/wdlom2wdl/WdlWriterImpl.scala @@ -1,13 +1,8 @@ package wdl.transforms.base.wdlom2wdl -import wdl.model.draft3.elements.CommandPartElement.{PlaceholderCommandPartElement, StringCommandPartElement} import wdl.model.draft3.elements.ExpressionElement._ import wdl.model.draft3.elements._ -import wom.callable.MetaValueElement -import wom.callable.MetaValueElement._ import wom.types._ -import WdlWriter._ -import common.collections.EnhancedCollections._ import org.apache.commons.text.StringEscapeUtils object WdlWriterImpl { @@ -15,12 +10,6 @@ object WdlWriterImpl { // Auto-generated by simulacrum import WdlWriter.ops._ - implicit val ifWriter: WdlWriter[IfElement] = new WdlWriter[IfElement] { - override def toWdlV1(a: IfElement) = - s"""if (${a.conditionExpression.toWdlV1}) { - |${indentAndCombine(a.graphElements.map(_.toWdlV1))}}""".stripMargin - } - implicit val stringPieceWriter: WdlWriter[StringPiece] = new WdlWriter[StringPiece] { override def toWdlV1(a: StringPiece): String = a match { case a: StringLiteral => a.value @@ -114,33 +103,6 @@ object WdlWriterImpl { } } - implicit val graphElementWriter: WdlWriter[WorkflowGraphElement] = new WdlWriter[WorkflowGraphElement] { - override def toWdlV1(a: WorkflowGraphElement) = a match { - case a: CallElement => a.toWdlV1 - case a: IntermediateValueDeclarationElement => a.toWdlV1 - case a: OutputDeclarationElement => a.toWdlV1 - case a: InputDeclarationElement => a.toWdlV1 - case a: IfElement => a.toWdlV1 - case a: ScatterElement => a.toWdlV1 - } - } - - implicit val scatterElementWriter: WdlWriter[ScatterElement] = new WdlWriter[ScatterElement] { - override def toWdlV1(a: ScatterElement): String = - s"""scatter (${a.scatterVariableName} in ${a.scatterExpression.toWdlV1}) { - |${indentAndCombine(a.graphElements.map(_.toWdlV1))}}""".stripMargin - } - - implicit val callBodyElement: WdlWriter[CallBodyElement] = new WdlWriter[CallBodyElement] { - override def toWdlV1(a: CallBodyElement): String = - if (a.inputs.nonEmpty) { - s"""input: - |${indent(indent(a.inputs.map(_.toWdlV1).mkString(",\n")))}""".stripMargin - } else { - "" - } - } - object CallElementWriter { def withoutBody(a: CallElement): String = { val aliasExpression = a.alias match { @@ -154,28 +116,6 @@ object WdlWriterImpl { } } - implicit val callElementWriter: WdlWriter[CallElement] = new WdlWriter[CallElement] { - - override def toWdlV1(a: CallElement): String = { - - val bodyExpression = a.body match { - case Some(body) => - s""" { - | ${body.toWdlV1} - |}""".stripMargin - case None => "" - } - - s"${CallElementWriter.withoutBody(a)}$bodyExpression" - } - } - - implicit val intermediateValueDeclarationElementWriter: WdlWriter[IntermediateValueDeclarationElement] = - new WdlWriter[IntermediateValueDeclarationElement] { - override def toWdlV1(a: IntermediateValueDeclarationElement) = - s"${a.typeElement.toWdlV1} ${a.name} = ${a.expression.toWdlV1}" - } - implicit val typeElementWriter: WdlWriter[TypeElement] = new WdlWriter[TypeElement] { override def toWdlV1(a: TypeElement) = a match { case a: PrimitiveTypeElement => a.primitiveType.toWdlV1 @@ -195,160 +135,6 @@ object WdlWriterImpl { override def toWdlV1(a: WomPrimitiveType) = a.stableName } - implicit val workflowDefinitionElementWriter: WdlWriter[WorkflowDefinitionElement] = - new WdlWriter[WorkflowDefinitionElement] { - override def toWdlV1(a: WorkflowDefinitionElement) = { - val inputs = a.inputsSection match { - case Some(i) => i.toWdlV1 - case None => "" - } - val outputs = a.outputsSection match { - case Some(o) => o.toWdlV1 - case None => "" - } - - // Readability / cosmetic reordering - // TODO: use graph ordering - // https://github.com/broadinstitute/cromwell/issues/3796 - val inputDeclarationElements: List[InputDeclarationElement] = - a.graphElements.toList.filterByType[InputDeclarationElement] - val intermediateValueDeclarationElements: List[IntermediateValueDeclarationElement] = - a.graphElements.toList.filterByType[IntermediateValueDeclarationElement] - val ifElements: List[IfElement] = - a.graphElements.toList.filterByType[IfElement] - val scatterElements: List[ScatterElement] = - a.graphElements.toList.filterByType[ScatterElement] - val callElements: List[CallElement] = - a.graphElements.toList.filterByType[CallElement] - val outputDeclarationElements: List[OutputDeclarationElement] = - a.graphElements.toList.filterByType[OutputDeclarationElement] - - val combined: List[WorkflowGraphElement] = inputDeclarationElements ++ - intermediateValueDeclarationElements ++ - ifElements ++ - scatterElements ++ - callElements ++ - outputDeclarationElements - - s"""workflow ${a.name} { - |${indent(inputs)} - |${indentAndCombine(combined.map(_.toWdlV1))} - |${indent(outputs)} - |}""".stripMargin - } - } - - implicit val runtimeAttributesSectionElementWriter: WdlWriter[RuntimeAttributesSectionElement] = - new WdlWriter[RuntimeAttributesSectionElement] { - override def toWdlV1(a: RuntimeAttributesSectionElement): String = { - val runtimeMap = a.runtimeAttributes map { pair => - s"${pair.key}: ${pair.value.toWdlV1}" - } - - s"""runtime { - |${indentAndCombine(runtimeMap)}}""".stripMargin - } - } - - implicit val metaValueElementWriter: WdlWriter[MetaValueElement] = new WdlWriter[MetaValueElement] { - override def toWdlV1(a: MetaValueElement): String = a match { - case _: MetaValueElementNull.type => "null" - case a: MetaValueElementBoolean => a.value.toString - case a: MetaValueElementFloat => a.value.toString - case a: MetaValueElementInteger => a.value.toString - case a: MetaValueElementString => "\"" + a.value + "\"" - case a: MetaValueElementObject => - "{" + a.value - .map { pair => - s"${pair._1}: ${metaValueElementWriter.toWdlV1(pair._2)}" - } - .mkString(", ") + "}" - case a: MetaValueElementArray => "[" + a.value.map(metaValueElementWriter.toWdlV1).mkString(", ") + "]" - } - } - - implicit val metaSectionElementWriter: WdlWriter[MetaSectionElement] = new WdlWriter[MetaSectionElement] { - override def toWdlV1(a: MetaSectionElement): String = { - val map = a.meta.map { pair => - s"${pair._1}: ${pair._2.toWdlV1}" - } - s"""meta { - |${indentAndCombine(map)} - |}""".stripMargin - } - } - - implicit val parameterMetaSectionElementWriter: WdlWriter[ParameterMetaSectionElement] = - new WdlWriter[ParameterMetaSectionElement] { - override def toWdlV1(a: ParameterMetaSectionElement): String = { - val map = a.metaAttributes.map { pair => - s"${pair._1}: ${pair._2.toWdlV1}" - } - s"""parameter_meta { - |${indentAndCombine(map)} - |}""".stripMargin - } - } - - implicit val taskDefinitionTypeElementWriter: WdlWriter[TaskDefinitionElement] = - new WdlWriter[TaskDefinitionElement] { - override def toWdlV1(a: TaskDefinitionElement) = { - val inputs = a.inputsSection match { - case Some(i) => i.toWdlV1 - case None => "" - } - val outputs = a.outputsSection match { - case Some(o) => o.toWdlV1 - case None => "" - } - val runtime = a.runtimeSection match { - case Some(r) => r.toWdlV1 - case None => "" - } - val meta = a.metaSection match { - case Some(m) => m.toWdlV1 - case None => "" - } - val parameterMeta = a.parameterMetaSection match { - case Some(p) => p.toWdlV1 - case None => "" - } - - s"""task ${a.name} { - |${indent(inputs)} - |${indentAndCombine(a.declarations.map(_.toWdlV1))} - |${indent(outputs)} - |${a.commandSection.toWdlV1} - |${indent(runtime)} - |${indent(meta)} - |${indent(parameterMeta)}}""".stripMargin - } - } - - implicit val commandSectionElementWriter: WdlWriter[CommandSectionElement] = new WdlWriter[CommandSectionElement] { - override def toWdlV1(a: CommandSectionElement): String = - s""" command <<< - |${combine(a.parts.map(_.toWdlV1))} >>>""".stripMargin - } - - implicit val commandSectionLineWriter: WdlWriter[CommandSectionLine] = new WdlWriter[CommandSectionLine] { - override def toWdlV1(a: CommandSectionLine): String = - a.parts.map(_.toWdlV1).mkString - } - - implicit val commandPartElementWriter: WdlWriter[CommandPartElement] = new WdlWriter[CommandPartElement] { - override def toWdlV1(a: CommandPartElement): String = a match { - case a: StringCommandPartElement => a.value // .trim? - case a: PlaceholderCommandPartElement => - val attributes = a.attributes.toWdlV1 - - if (attributes.nonEmpty) - s"~{$attributes ${a.expressionElement.toWdlV1}}" - else - s"~{${a.expressionElement.toWdlV1}}" - } - } - implicit val placeholderAttributeSetWriter: WdlWriter[PlaceholderAttributeSet] = new WdlWriter[PlaceholderAttributeSet] { override def toWdlV1(a: PlaceholderAttributeSet): String = { @@ -363,36 +149,6 @@ object WdlWriterImpl { } } - implicit val inputsSectionElementWriter: WdlWriter[InputsSectionElement] = new WdlWriter[InputsSectionElement] { - override def toWdlV1(a: InputsSectionElement): String = - s"""input { - |${indentAndCombine(a.inputDeclarations.map(_.toWdlV1))}}""".stripMargin - } - - implicit val inputDeclarationElementWriter: WdlWriter[InputDeclarationElement] = - new WdlWriter[InputDeclarationElement] { - override def toWdlV1(a: InputDeclarationElement): String = { - val expression = a.expression match { - case Some(expr) => s" = ${expr.toWdlV1}" - case None => "" - } - - s"${a.typeElement.toWdlV1} ${a.name}$expression" - } - } - - implicit val outputsSectionElementWriter: WdlWriter[OutputsSectionElement] = new WdlWriter[OutputsSectionElement] { - override def toWdlV1(a: OutputsSectionElement): String = - s"""output { - |${indentAndCombine(a.outputs.map(_.toWdlV1))}}""".stripMargin - } - - implicit val outputDeclarationElementWriter: WdlWriter[OutputDeclarationElement] = - new WdlWriter[OutputDeclarationElement] { - override def toWdlV1(a: OutputDeclarationElement): String = - s"${a.typeElement.toWdlV1} ${a.name} = ${a.expression.toWdlV1}" - } - implicit val functionCallElementWriter: WdlWriter[FunctionCallElement] = new WdlWriter[FunctionCallElement] { override def toWdlV1(a: FunctionCallElement): String = a match { case _: StdoutElement.type => "stdout()" @@ -477,33 +233,6 @@ object WdlWriterImpl { } } - implicit val structElementWriter: WdlWriter[StructElement] = new WdlWriter[StructElement] { - override def toWdlV1(a: StructElement): String = - s"""struct ${a.name} { - |${indentAndCombine(a.entries.map(_.toWdlV1))}}""".stripMargin - } - - implicit val structEntryElementWriter: WdlWriter[StructEntryElement] = new WdlWriter[StructEntryElement] { - override def toWdlV1(a: StructEntryElement): String = s"${a.typeElement.toWdlV1} ${a.identifier}" - } - - implicit val importElementWriter: WdlWriter[ImportElement] = new WdlWriter[ImportElement] { - override def toWdlV1(a: ImportElement): String = - a.namespace match { - case Some(namespace) => s"""import "${a.importUrl}" as $namespace""" - case None => s"""import "${a.importUrl}"""" - } - } - - implicit val fileElementWriter: WdlWriter[FileElement] = new WdlWriter[FileElement] { - override def toWdlV1(a: FileElement) = - "version 1.0" + System.lineSeparator + - combine(a.imports.map(_.toWdlV1)) + - combine(a.structs.map(_.toWdlV1)) + - combine(a.workflows.map(_.toWdlV1)) + - combine(a.tasks.map(_.toWdlV1)) - } - implicit val kvPairWriter: WdlWriter[KvPair] = new WdlWriter[KvPair] { override def toWdlV1(a: KvPair): String = s"${a.key} = ${a.value.toWdlV1}" } diff --git a/wdl/transforms/new-base/src/main/scala/wdl/transforms/base/wdlom2wom/CommandPartElementToWomCommandPart.scala b/wdl/transforms/new-base/src/main/scala/wdl/transforms/base/wdlom2wom/CommandPartElementToWomCommandPart.scala index f7062441130..2ff236f5437 100644 --- a/wdl/transforms/new-base/src/main/scala/wdl/transforms/base/wdlom2wom/CommandPartElementToWomCommandPart.scala +++ b/wdl/transforms/new-base/src/main/scala/wdl/transforms/base/wdlom2wom/CommandPartElementToWomCommandPart.scala @@ -18,7 +18,7 @@ import wdl.model.draft3.graph.ExpressionValueConsumer.ops._ import wdl.model.draft3.graph.expression.WomExpressionMaker.ops._ import wdl.transforms.base.linking.expression._ import wdl.transforms.base.wdlom2wdl.WdlWriter.ops._ -import wdl.transforms.base.wdlom2wdl.WdlWriterImpl._ +import wdl.transforms.base.wdlom2wdl.WdlWriterImpl.{expressionElementWriter, placeholderAttributeSetWriter} object CommandPartElementToWomCommandPart { def convert(commandPart: CommandPartElement, @@ -57,8 +57,9 @@ case class WdlomWomStringCommandPart(stringCommandPartElement: StringCommandPart case class WdlomWomPlaceholderCommandPart(attributes: PlaceholderAttributeSet, expression: WdlomWomExpression) extends CommandPart { - def attributesToString: String = attributes.toWdlV1 + private def attributesToString: String = attributes.toWdlV1 // Yes, it's sad that we need to put ${} here, but otherwise we won't cache hit from draft-2 command sections + // Re-writes e.g. `echo $((5 + ~{x.a}))` commands to `echo $((5 + ${x.a}))` so they evaluate to equal override def toString: String = "${" + s"$attributesToString${expression.expressionElement.toWdlV1}" + "}" override def instantiate(inputsMap: Map[LocalName, WomValue], diff --git a/wdl/transforms/new-base/src/main/scala/wdl/transforms/base/wdlom2wom/graph/InputDeclarationElementToGraphNode.scala b/wdl/transforms/new-base/src/main/scala/wdl/transforms/base/wdlom2wom/graph/InputDeclarationElementToGraphNode.scala index d70bc22126f..f7e6f107dd7 100644 --- a/wdl/transforms/new-base/src/main/scala/wdl/transforms/base/wdlom2wom/graph/InputDeclarationElementToGraphNode.scala +++ b/wdl/transforms/new-base/src/main/scala/wdl/transforms/base/wdlom2wom/graph/InputDeclarationElementToGraphNode.scala @@ -15,7 +15,7 @@ import wom.graph.GraphNodePort.OutputPort import wom.graph._ import wom.types.{WomOptionalType, WomType} import wdl.transforms.base.wdlom2wdl.WdlWriter.ops._ -import wdl.transforms.base.wdlom2wdl.WdlWriterImpl._ +import wdl.transforms.base.wdlom2wdl.WdlWriterImpl.{expressionElementWriter, typeElementWriter} object InputDeclarationElementToGraphNode { def convert(a: GraphInputNodeMakerInputs)(implicit diff --git a/wdl/transforms/new-base/src/main/scala/wdl/transforms/base/wdlom2wom/graph/WorkflowGraphElementToGraphNode.scala b/wdl/transforms/new-base/src/main/scala/wdl/transforms/base/wdlom2wom/graph/WorkflowGraphElementToGraphNode.scala index 56ff7709605..f866fc94644 100644 --- a/wdl/transforms/new-base/src/main/scala/wdl/transforms/base/wdlom2wom/graph/WorkflowGraphElementToGraphNode.scala +++ b/wdl/transforms/new-base/src/main/scala/wdl/transforms/base/wdlom2wom/graph/WorkflowGraphElementToGraphNode.scala @@ -17,7 +17,7 @@ import wom.graph._ import wom.graph.expression.ExposedExpressionNode import wom.types.WomType import wdl.transforms.base.wdlom2wdl.WdlWriter.ops._ -import wdl.transforms.base.wdlom2wdl.WdlWriterImpl._ +import wdl.transforms.base.wdlom2wdl.WdlWriterImpl.{expressionElementWriter, typeElementWriter} object WorkflowGraphElementToGraphNode { def convert(a: GraphNodeMakerInputs)(implicit diff --git a/womtool/src/main/scala/womtool/WomtoolMain.scala b/womtool/src/main/scala/womtool/WomtoolMain.scala index 6fc1ccb43db..38c586e8bb1 100644 --- a/womtool/src/main/scala/womtool/WomtoolMain.scala +++ b/womtool/src/main/scala/womtool/WomtoolMain.scala @@ -2,16 +2,12 @@ package womtool import java.nio.file.Paths -import better.files.File import com.typesafe.scalalogging.StrictLogging import common.validation.Validation._ -import cromwell.core.path.{DefaultPathBuilder, Path} +import cromwell.core.path.Path import cromwell.languages.util.ImportResolver.HttpResolver -import languages.wdl.draft2.WdlDraft2LanguageFactory import wdl.draft2.model.formatter.{AnsiSyntaxHighlighter, HtmlSyntaxHighlighter, SyntaxFormatter, SyntaxHighlighter} import wdl.draft2.model.{AstTools, WdlNamespace} -import wdl.transforms.base.wdlom2wdl.WdlWriter.ops._ -import wdl.transforms.base.wdlom2wdl.WdlWriterImpl.fileElementWriter import wom.views.GraphPrint import womtool.cmdline.HighlightMode.{ConsoleHighlighting, HtmlHighlighting, UnrecognizedHighlightingMode} import womtool.cmdline._ @@ -20,9 +16,8 @@ import womtool.input.WomGraphMaker import womtool.inputs.Inputs import womtool.outputs.Outputs import womtool.validate.Validate -import womtool.wom2wdlom.WomToWdlom.womBundleToFileElement -import scala.util.{Failure, Success, Try} +import scala.util.{Failure, Success} object WomtoolMain extends App with StrictLogging { @@ -58,7 +53,6 @@ object WomtoolMain extends App with StrictLogging { case o: OutputsCommandLine => Outputs.outputsJson(o.workflowSource) case g: WomtoolGraphCommandLine => graph(g.workflowSource) case g: WomtoolWomGraphCommandLine => womGraph(g.workflowSource) - case u: WomtoolWdlUpgradeCommandLine => upgrade(u.workflowSource.pathAsString) case _ => BadUsageTermination(WomtoolCommandLineParser.instance.usage) } @@ -79,59 +73,6 @@ object WomtoolMain extends App with StrictLogging { def parse(workflowSourcePath: String): Termination = SuccessfulTermination(AstTools.getAst(Paths.get(workflowSourcePath)).toPrettyString) - def upgrade(workflowSourcePath: String): Termination = { - import wdl.draft2.model.Import - import wdl.model.draft3.elements.ImportElement - - // Get imports directly from WdlNamespace, because they are erased during WOMification - val maybeWdlNamespace: Try[WdlNamespace] = - WdlNamespace.loadUsingPath( - Paths.get(workflowSourcePath), - None, - Option( - List( - WdlNamespace.directoryResolver(File(workflowSourcePath).parent), - WdlNamespace.fileResolver, - WdlDraft2LanguageFactory.httpResolver - ) - ) - ) - - def upgradeImport(draft2Import: Import): ImportElement = - if (draft2Import.namespaceName.nonEmpty) - // draft-2 does not have structs, so the source WDL will not have any for us to rename - ImportElement(draft2Import.uri, Option(draft2Import.namespaceName), Map()) - else - ImportElement(draft2Import.uri, None, Map()) - - val maybeWdl: Try[Path] = DefaultPathBuilder.build(workflowSourcePath) - - (maybeWdl, maybeWdlNamespace) match { - case (Success(wdl), Success(wdlNamespace)) => - val maybeWomBundle = WomGraphMaker.getBundle(wdl) - maybeWomBundle match { - case Right(womBundle) => - val maybeFileElement = womBundleToFileElement.run(womBundle) - maybeFileElement match { - case Right(fileElement) => - SuccessfulTermination(fileElement.copy(imports = wdlNamespace.imports.map(upgradeImport)).toWdlV1) - case Left(errors) => - UnsuccessfulTermination( - s"WDL parsing succeeded but could not create WOM: ${errors.toList.mkString("[", ",", "]")}" - ) - } - case Left(errors) => - UnsuccessfulTermination( - s"WDL parsing succeeded but could not create WOM: ${errors.toList.mkString("[", ",", "]")}" - ) - } - case (Failure(throwable), _) => - UnsuccessfulTermination(s"Failed to load WDL source: ${throwable.getMessage}") - case (_, Failure(throwable)) => - UnsuccessfulTermination(s"Failed to load WDL source: ${throwable.getMessage}") - } - } - def graph(workflowSourcePath: Path): Termination = WomGraphMaker .getBundle(workflowSourcePath) diff --git a/womtool/src/main/scala/womtool/cmdline/PartialWomtoolCommandLineArguments.scala b/womtool/src/main/scala/womtool/cmdline/PartialWomtoolCommandLineArguments.scala index af33485e173..d7ec9b4c04e 100644 --- a/womtool/src/main/scala/womtool/cmdline/PartialWomtoolCommandLineArguments.scala +++ b/womtool/src/main/scala/womtool/cmdline/PartialWomtoolCommandLineArguments.scala @@ -19,7 +19,6 @@ final case class HighlightCommandLine(workflowSource: Path, highlightMode: Highl final case class InputsCommandLine(workflowSource: Path, showOptionals: Boolean) extends ValidatedWomtoolCommandLine final case class OutputsCommandLine(workflowSource: Path) extends ValidatedWomtoolCommandLine final case class WomtoolGraphCommandLine(workflowSource: Path) extends ValidatedWomtoolCommandLine -final case class WomtoolWdlUpgradeCommandLine(workflowSource: Path) extends ValidatedWomtoolCommandLine final case class WomtoolWomGraphCommandLine(workflowSource: Path) extends ValidatedWomtoolCommandLine sealed trait WomtoolCommand @@ -31,7 +30,6 @@ object WomtoolCommand { case object Inputs extends WomtoolCommand case object Outputs extends WomtoolCommand case object Graph extends WomtoolCommand - case object Upgrade extends WomtoolCommand case object WomGraph extends WomtoolCommand } diff --git a/womtool/src/main/scala/womtool/cmdline/WomtoolCommandLineParser.scala b/womtool/src/main/scala/womtool/cmdline/WomtoolCommandLineParser.scala index 8b5801d8a52..4ca04b5ce55 100644 --- a/womtool/src/main/scala/womtool/cmdline/WomtoolCommandLineParser.scala +++ b/womtool/src/main/scala/womtool/cmdline/WomtoolCommandLineParser.scala @@ -27,8 +27,6 @@ object WomtoolCommandLineParser { Option(WomtoolGraphCommandLine(mainFile)) case PartialWomtoolCommandLineArguments(Some(WomGraph), Some(mainFile), None, None, None, None) => Option(WomtoolWomGraphCommandLine(mainFile)) - case PartialWomtoolCommandLineArguments(Some(Upgrade), Some(mainFile), None, None, None, None) => - Option(WomtoolWdlUpgradeCommandLine(mainFile)) case _ => None } } @@ -107,10 +105,6 @@ class WomtoolCommandLineParser extends scopt.OptionParser[PartialWomtoolCommandL .action((_, c) => c.copy(command = Option(Graph))) .text("Generate and output a graph visualization of the workflow in .dot format" + System.lineSeparator) - cmd("upgrade") - .action((_, c) => c.copy(command = Option(Upgrade))) - .text("Automatically upgrade the WDL to version 1.0 and output the result." + System.lineSeparator) - cmd("womgraph") .action((_, c) => c.copy(command = Option(WomGraph))) .text( diff --git a/womtool/src/main/scala/womtool/wom2wdlom/WomToWdlom.scala b/womtool/src/main/scala/womtool/wom2wdlom/WomToWdlom.scala deleted file mode 100644 index 07382a3ceef..00000000000 --- a/womtool/src/main/scala/womtool/wom2wdlom/WomToWdlom.scala +++ /dev/null @@ -1,392 +0,0 @@ -package womtool.wom2wdlom - -import cats.syntax.traverse._ -import cats.instances.list._ -import cats.instances.either._ -import common.Checked -import common.collections.EnhancedCollections._ -import common.transforms.CheckedAtoB -import common.validation.Checked._ -import shapeless.{Inl, Inr} -import wdl.draft2.model.command.{ParameterCommandPart, StringCommandPart} -import wdl.model.draft3.elements._ -import wdl.model.draft3.elements.CommandPartElement.{PlaceholderCommandPartElement, StringCommandPartElement} -import wdl.model.draft3.elements.ExpressionElement.ExpressionLiteralElement -import wom.callable.Callable._ -import wom.callable.Callable.OutputDefinition -import wom.callable.{CallableTaskDefinition, MetaValueElement, WorkflowDefinition} -import wom.executable.WomBundle -import wom.expression.WomExpression -import wom.graph._ -import wom.graph.CallNode.InputDefinitionPointer -import wom.graph.expression._ -import wom.graph.GraphNodePort.GraphNodeOutputPort -import wom.RuntimeAttributes -import wom.types._ - -object WomToWdlom { - - private def invalidFromString(text: String) = - s"Value \'$text\' has no representation in the destination format (WDL)".invalidNelCheck - - def graphOutputNodeToWorkflowGraphElement: CheckedAtoB[GraphOutputNode, WorkflowGraphElement] = - CheckedAtoB.fromCheck { a: GraphOutputNode => - a match { - case a: ExpressionBasedGraphOutputNode => - for { - typeElement <- womTypeToTypeElement(a.womType) - expression <- womExpressionToExpressionElement(a.womExpression) - } yield OutputDeclarationElement(typeElement, a.identifier.localName.value, expression) - case _ => - invalidFromString(a.toString) - } - } - - def womBundleToFileElement: CheckedAtoB[WomBundle, FileElement] = - CheckedAtoB.fromCheck { a: WomBundle => - val tasks: Iterable[CallableTaskDefinition] = a.allCallables.values.filterByType[CallableTaskDefinition] - val workflows: Iterable[WorkflowDefinition] = a.allCallables.values.filterByType[WorkflowDefinition] - - for { - workflows <- workflows - .map(workflowDefinitionToWorkflowDefinitionElement(_)) - .toList - .sequence[Checked, WorkflowDefinitionElement] - tasks <- tasks - .map(callableTaskDefinitionToTaskDefinitionElement(_)) - .toList - .sequence[Checked, TaskDefinitionElement] - } yield FileElement( - Seq.empty, // Imports do not exist in WOM and cannot sensibly be added at this point - Seq.empty, // Structs do not exist in draft-2 - workflows, - tasks - ) - } - - def mapToMetaSectionElement: CheckedAtoB[Map[String, MetaValueElement], Option[MetaSectionElement]] = - CheckedAtoB.fromCheck { a: Map[String, MetaValueElement] => - if (a.nonEmpty) - Some(MetaSectionElement(a)).validNelCheck - else - None.validNelCheck - } - - def mapToParameterMetaSectionElement - : CheckedAtoB[Map[String, MetaValueElement], Option[ParameterMetaSectionElement]] = - CheckedAtoB.fromCheck { a: Map[String, MetaValueElement] => - if (a.nonEmpty) - Some(ParameterMetaSectionElement(a)).validNelCheck - else - None.validNelCheck - } - - def runtimeAttributesToRuntimeAttributesSectionElement - : CheckedAtoB[RuntimeAttributes, Option[RuntimeAttributesSectionElement]] = - CheckedAtoB.fromCheck { a: RuntimeAttributes => - def tupleToKvPair(tuple: (String, WomExpression)): Checked[ExpressionElement.KvPair] = - for { - expressionElement <- womExpressionToExpressionElement(tuple._2) - } yield ExpressionElement.KvPair(tuple._1, expressionElement) - - for { - kvPairs <- (a.attributes map tupleToKvPair).toList.sequence[Checked, ExpressionElement.KvPair] - } yield - if (kvPairs.nonEmpty) - Some(RuntimeAttributesSectionElement(kvPairs.toVector)) - else - None - } - - def outputDefinitionToOutputDeclarationElement: CheckedAtoB[OutputDefinition, OutputDeclarationElement] = - CheckedAtoB.fromCheck { a: OutputDefinition => - for { - typeElement <- womTypeToTypeElement(a.womType) - expression <- womExpressionToExpressionElement(a.expression) - } yield OutputDeclarationElement(typeElement, a.name, expression) - } - - def inputDefinitionToInputDeclarationElement: CheckedAtoB[InputDefinition, InputDeclarationElement] = - CheckedAtoB.fromCheck { a: InputDefinition => - womTypeToTypeElement(a.womType) flatMap { womType => - a match { - case a: RequiredInputDefinition => - InputDeclarationElement(womType, a.localName.value, None).validNelCheck - case a: OverridableInputDefinitionWithDefault => - womExpressionToExpressionElement(a.default) map { expression => - InputDeclarationElement(womType, a.localName.value, Some(expression)) - } - case a: FixedInputDefinitionWithDefault => - womExpressionToExpressionElement(a.default) map { expression => - InputDeclarationElement(womType, a.localName.value, Some(expression)) - } - case a: OptionalInputDefinition => - InputDeclarationElement(womType, a.localName.value, None).validNelCheck - } - } - } - - def callableTaskDefinitionToTaskDefinitionElement: CheckedAtoB[CallableTaskDefinition, TaskDefinitionElement] = - CheckedAtoB.fromCheck { a: CallableTaskDefinition => - val inputs: Checked[List[InputDeclarationElement]] = - a.inputs.map(inputDefinitionToInputDeclarationElement(_)).sequence[Checked, InputDeclarationElement] - val outputs: Checked[List[OutputDeclarationElement]] = - a.outputs.map(outputDefinitionToOutputDeclarationElement(_)).sequence[Checked, OutputDeclarationElement] - - for { - runtime <- runtimeAttributesToRuntimeAttributesSectionElement(a.runtimeAttributes) - meta <- mapToMetaSectionElement(a.meta) - parameterMeta <- mapToParameterMetaSectionElement(a.parameterMeta) - inputs <- inputs - outputs <- outputs - commands <- a.commandTemplateBuilder(Map()).toEither - } yield { - val commandLine = CommandSectionLine(commands map { - case s: StringCommandPart => - StringCommandPartElement(s.literal) - case p: ParameterCommandPart => - val attrs = PlaceholderAttributeSet( - defaultAttribute = p.attributes.get("default"), - trueAttribute = p.attributes.get("true"), - falseAttribute = p.attributes.get("false"), - sepAttribute = p.attributes.get("sep") - ) - - PlaceholderCommandPartElement(ExpressionLiteralElement(p.expression.toWomString), attrs) - }) - - TaskDefinitionElement( - a.name, - if (inputs.nonEmpty) Some(InputsSectionElement(inputs)) else None, - Seq.empty, // No such thing in draft-2 - if (outputs.nonEmpty) Some(OutputsSectionElement(outputs)) else None, - CommandSectionElement(Seq(commandLine)), - runtime, - meta, - parameterMeta, - a.sourceLocation - ) - } - } - - def workflowDefinitionToWorkflowDefinitionElement: CheckedAtoB[WorkflowDefinition, WorkflowDefinitionElement] = - CheckedAtoB.fromCheck { a: WorkflowDefinition => - // This is a bit odd, so let's explain. "Real" inputs/outputs that are specified by the WDL's author - // cannot have periods in them - period. So any input/output that has a period in it - // is an artifact of WOMification and should be dropped - val inputs = - a.inputs - .filter(!_.localName.value.contains(".")) - .map(inputDefinitionToInputDeclarationElement(_)) - .sequence[Checked, InputDeclarationElement] - val outputs = - a.outputs - .filter(!_.localName.value.contains(".")) - .map(outputDefinitionToOutputDeclarationElement(_)) - .sequence[Checked, OutputDeclarationElement] - - for { - meta <- mapToMetaSectionElement(a.meta) - parameterMeta <- mapToParameterMetaSectionElement(a.parameterMeta) - inputs <- inputs - outputs <- outputs - nodes <- selectWdlomRepresentableNodes(a.graph.nodes) - .map(graphNodeToWorkflowGraphElement(_)) - .toList - .sequence[Checked, WorkflowGraphElement] - } yield WorkflowDefinitionElement( - a.name, - if (inputs.nonEmpty) Some(InputsSectionElement(inputs)) else None, - nodes.toSet, - if (outputs.nonEmpty) Some(OutputsSectionElement(outputs)) else None, - meta, - parameterMeta, - a.sourceLocation - ) - } - - def expressionNodeLikeToWorkflowGraphElement: CheckedAtoB[ExpressionNodeLike, WorkflowGraphElement] = - CheckedAtoB.fromCheck { - case a: ExpressionNode => - for { - typeElement <- womTypeToTypeElement(a.womType) - expression <- expressionNodeToExpressionElement(a) - } yield IntermediateValueDeclarationElement(typeElement = typeElement, - name = a.identifier.localName.value, - expression = expression - ) - case a: ExpressionCallNode => invalidFromString(a.toString) - } - - // Select only nodes that have a Wdlom representation (i.e. were not synthesized during WOMification) - // WOM has some explicit representations that are implicit in WDL; they are necessary for execution, - // but do not make sense (or are illegal) in WDL source. - private def selectWdlomRepresentableNodes(allNodes: Set[GraphNode]): Set[GraphNode] = { - val expressions = allNodes.filterByType[ExposedExpressionNode] - val scatters = allNodes.filterByType[ScatterNode] - val calls = allNodes.filterByType[CallNode] - val conditionals = allNodes.filterByType[ConditionalNode] - - expressions ++ scatters ++ calls ++ conditionals - } - - def graphNodeToWorkflowGraphElement: CheckedAtoB[GraphNode, WorkflowGraphElement] = - CheckedAtoB.fromCheck { - case a: CallNode => - callNodeToCallElement(a) - case a: ConditionalNode => - for { - condition <- expressionNodeToExpressionElement(a.conditionExpression) - nodes <- selectWdlomRepresentableNodes(a.innerGraph.nodes).toList - .map(graphNodeToWorkflowGraphElement(_)) - .sequence[Checked, WorkflowGraphElement] - } yield IfElement( - conditionExpression = condition, - graphElements = nodes - ) - case a: ExpressionNodeLike => - expressionNodeLikeToWorkflowGraphElement(a) - case a: GraphNodeWithSingleOutputPort => - graphNodeWithSingleOutputPortToWorkflowGraphElement(a) - case a: GraphOutputNode => - graphOutputNodeToWorkflowGraphElement(a) - case a: ScatterNode => - // Only CWL has multi-variable scatters - if (a.scatterVariableNodes.size != 1) - s"In WDL, the scatter node count must be exactly one, was ${a.scatterVariableNodes.size}".invalidNelCheck - else - for { - expression <- expressionNodeToExpressionElement(a.scatterCollectionExpressionNodes.head) - graph <- selectWdlomRepresentableNodes(a.innerGraph.nodes).toList - .map(graphNodeToWorkflowGraphElement(_)) - .sequence[Checked, WorkflowGraphElement] - } yield ScatterElement( - scatterName = a.identifier.localName.value, - scatterExpression = expression, - scatterVariableName = a.inputPorts.toList.head.name, - graphElements = graph, - sourceLocation = None - ) - } - - def graphNodeWithSingleOutputPortToWorkflowGraphElement - : CheckedAtoB[GraphNodeWithSingleOutputPort, WorkflowGraphElement] = - CheckedAtoB.fromCheck { - case a: GraphInputNode => - womTypeToTypeElement(a.womType) map { typeElement => - InputDeclarationElement(typeElement, a.identifier.localName.value, None) - } - case a: ExpressionNode => - for { - typeElement <- womTypeToTypeElement(a.womType) - expression <- womExpressionToExpressionElement(a.womExpression) - } yield IntermediateValueDeclarationElement(typeElement, a.identifier.localName.value, expression) - } - - def womTypeToTypeElement: CheckedAtoB[WomType, TypeElement] = - CheckedAtoB.fromCheck { - case a: WomArrayType => - womTypeToTypeElement(a.memberType) map { typeElement => - if (a.guaranteedNonEmpty) - NonEmptyTypeElement(ArrayTypeElement(typeElement)) - else - ArrayTypeElement(typeElement) - } - case _: WomCoproductType => invalidFromString("WDL does not have coproducts - is this WOM from CWL?") - case _: WomFileType => PrimitiveTypeElement(WomSingleFileType).validNelCheck - case a: WomMapType => - for { - keyType <- womTypeToTypeElement(a.keyType) - valueType <- womTypeToTypeElement(a.valueType) - } yield MapTypeElement(keyType, valueType) - case _: WomNothingType.type => invalidFromString("WDL does not have the Nothing type - is this WOM from CWL?") - case _: WomObjectType.type => ObjectTypeElement.validNelCheck - case a: WomOptionalType => womOptionalTypeToOptionalTypeElement(a) - case a: WomPairType => - for { - leftType <- womTypeToTypeElement(a.leftType) - rightType <- womTypeToTypeElement(a.rightType) - } yield PairTypeElement(leftType, rightType) - case a: WomPrimitiveType => womPrimitiveTypeToPrimitiveTypeElement(a) - } - - def womOptionalTypeToOptionalTypeElement: CheckedAtoB[WomOptionalType, OptionalTypeElement] = - CheckedAtoB.fromCheck { a: WomOptionalType => - womTypeToTypeElement(a.memberType) map { typeElement => - OptionalTypeElement(typeElement) - } - } - - def womPrimitiveTypeToPrimitiveTypeElement: CheckedAtoB[WomPrimitiveType, PrimitiveTypeElement] = - CheckedAtoB.fromCheck { a: WomPrimitiveType => - PrimitiveTypeElement(a).validNelCheck - } - - def expressionNodeToExpressionElement: CheckedAtoB[ExpressionNode, ExpressionElement] = - CheckedAtoB.fromCheck { a: ExpressionNode => - womExpressionToExpressionElement(a.womExpression) - } - - def womExpressionToExpressionElement: CheckedAtoB[WomExpression, ExpressionElement] = - CheckedAtoB.fromCheck { - case a: WomExpression => ExpressionLiteralElement(a.sourceString).validNelCheck - case a => invalidFromString(a.toString) - } - - def inputDefinitionPointerToExpressionElement: CheckedAtoB[InputDefinitionPointer, Option[ExpressionElement]] = - CheckedAtoB.fromCheck { - // If the input definition is a node containing an expression, it's been declared explicitly - case Inl(a: GraphNodeOutputPort) => - a.graphNode match { - case _: OptionalGraphInputNode => - None.validNelCheck - case _: OptionalGraphInputNodeWithDefault => - None.validNelCheck - case a: PlainAnonymousExpressionNode => - womExpressionToExpressionElement(a.womExpression) map { expressionElement => - Some(expressionElement) - } - case a: TaskCallInputExpressionNode => - womExpressionToExpressionElement(a.womExpression) map { expressionElement => - Some(expressionElement) - } - case _: RequiredGraphInputNode => None.validNelCheck - } - // Input definitions that directly contain expressions are the result of accepting a default input defined by the callable - case Inr(Inl(_: WomExpression)) => None.validNelCheck - case Inr(a) => - invalidFromString(a.toString) - case a => - invalidFromString(a.toString) - } - - def callNodeToCallElement: CheckedAtoB[CallNode, CallElement] = - CheckedAtoB.fromCheck { call: CallNode => - def tupleToKvPair(tuple: (InputDefinition, InputDefinitionPointer)): Option[ExpressionElement.KvPair] = - inputDefinitionPointerToExpressionElement(tuple._2) match { - case Right(Some(value)) => Some(ExpressionElement.KvPair(tuple._1.name, value)) - case _ => None - } - - val inputs = (call.inputDefinitionMappings flatMap tupleToKvPair).toVector - - val callableName = call.callable.name - val callAlias = call.identifier.localName.value // If no alias, this is just the name; we evaluate for that below - - val maybeAlias = - if (callableName != callAlias) - Some(callAlias) - else - None - - val afters = call.nonInputBasedPrerequisites.map(_.localName).toVector - - CallElement( - callableName, - maybeAlias, - afters, - if (inputs.nonEmpty) Some(CallBodyElement(inputs)) else None, - call.sourceLocation - ).validNelCheck - } -} diff --git a/womtool/src/test/resources/validate/biscayne/invalid/after_circle/error.txt b/womtool/src/test/resources/validate/biscayne/invalid/after_circle/error.txt index 3d032e83fac..0930a319d51 100644 --- a/womtool/src/test/resources/validate/biscayne/invalid/after_circle/error.txt +++ b/womtool/src/test/resources/validate/biscayne/invalid/after_circle/error.txt @@ -1,3 +1,3 @@ Failed to process workflow definition 'after_circle' (reason 1 of 1): This workflow contains a cyclic dependency: -"call foo as foo1 after foo2 {" -> "call foo as foo2 after foo1 {" -"call foo as foo2 after foo1 {" -> "call foo as foo1 after foo2 {" +"Call "foo as foo1 after foo2"" -> "Call "foo as foo2 after foo1"" +"Call "foo as foo2 after foo1"" -> "Call "foo as foo1 after foo2"" diff --git a/womtool/src/test/resources/validate/wdl_draft3/invalid/cycle/error.txt b/womtool/src/test/resources/validate/wdl_draft3/invalid/cycle/error.txt index a24b586cfdc..263b1adc534 100644 --- a/womtool/src/test/resources/validate/wdl_draft3/invalid/cycle/error.txt +++ b/womtool/src/test/resources/validate/wdl_draft3/invalid/cycle/error.txt @@ -1,4 +1,4 @@ Failed to process workflow definition 'cycle' (reason 1 of 1): This workflow contains a cyclic dependency: -"call mirror as m1 {" -> "call mirror as m2 {" -"call mirror as m2 {" -> "call mirror as m3 {" -"call mirror as m3 {" -> "call mirror as m1 {" +"Call "mirror as m1"" -> "Call "mirror as m2"" +"Call "mirror as m2"" -> "Call "mirror as m3"" +"Call "mirror as m3"" -> "Call "mirror as m1"" diff --git a/womtool/src/test/scala/womtool/WomtoolMainSpec.scala b/womtool/src/test/scala/womtool/WomtoolMainSpec.scala index 1a71db15cf3..2986d7db759 100644 --- a/womtool/src/test/scala/womtool/WomtoolMainSpec.scala +++ b/womtool/src/test/scala/womtool/WomtoolMainSpec.scala @@ -20,7 +20,7 @@ class WomtoolMainSpec extends AnyFlatSpec with CromwellTimeoutSpec with Matchers WomtoolMain.runWomtool(Seq.empty[String]) match { case BadUsageTermination(msg) => msg should include( - "Usage: java -jar womtool.jar [validate|inputs|outputs|parse|highlight|graph|upgrade|womgraph] [options] workflow-source" + "Usage: java -jar womtool.jar [validate|inputs|outputs|parse|highlight|graph|womgraph] [options] workflow-source" ) case other => fail(s"Expected BadUsageTermination but got $other") }