diff --git a/wdl/transforms/draft3/src/test/scala/AstToWorkflowDefinitionElementSpec.scala b/wdl/transforms/draft3/src/test/scala/AstToWorkflowDefinitionElementSpec.scala new file mode 100644 index 00000000000..99731de6aad --- /dev/null +++ b/wdl/transforms/draft3/src/test/scala/AstToWorkflowDefinitionElementSpec.scala @@ -0,0 +1,97 @@ +import cats.data.Validated._ +import org.scalatest.flatspec.AnyFlatSpec +import org.scalatest.matchers.should.Matchers._ +import wdl.model.draft3.elements.ExpressionElement._ +import wdl.model.draft3.elements.{InputDeclarationElement, InputsSectionElement, IntermediateValueDeclarationElement, OutputDeclarationElement, OutputsSectionElement, PrimitiveTypeElement} +import wom.types.{WomSingleFileType, WomStringType} +import wdl.transforms.base.ast2wdlom.AstToWorkflowDefinitionElement._ + + +class AstToWorkflowDefinitionElementSpec extends AnyFlatSpec{ + behavior of "Check Stdouts and Stderrs" + + val mockInputSectionStdout = Option(InputsSectionElement(Vector(InputDeclarationElement(PrimitiveTypeElement(WomSingleFileType), "i", Some(StdoutElement))))) + val mockInputSectionStderr = Option(InputsSectionElement(Vector(InputDeclarationElement(PrimitiveTypeElement(WomSingleFileType), "i", Some(StderrElement))))) + val mockInputSectionNonStd = Option(InputsSectionElement(Vector(InputDeclarationElement(PrimitiveTypeElement(WomStringType), "more", Some(StringLiteral("more")))))) + + + val mockIntermediatesStdout = Vector(IntermediateValueDeclarationElement(PrimitiveTypeElement(WomSingleFileType), "y", StdoutElement)) + val mockIntermediatesStderr = Vector(IntermediateValueDeclarationElement(PrimitiveTypeElement(WomSingleFileType), "y", StderrElement)) + val mockIntermediatesNonStd = Vector(IntermediateValueDeclarationElement(PrimitiveTypeElement(WomStringType), "here", StringLiteral("here"))) + + val mockOutputSectionStdout = Option(OutputsSectionElement(Vector(OutputDeclarationElement(PrimitiveTypeElement(WomSingleFileType), "s", StdoutElement)))) + val mockOutputSectionStderr = Option(OutputsSectionElement(Vector(OutputDeclarationElement(PrimitiveTypeElement(WomSingleFileType), "s", StderrElement)))) + val mockOutputSectionNonStd = Option(OutputsSectionElement(Vector(OutputDeclarationElement(PrimitiveTypeElement(WomStringType), "more", StringLiteral("more"))))) + + + it should "return an error when there is an stdout in input section" in { + val testInputs = checkDisallowedInputElement(mockInputSectionStdout, StdoutElement, "stdout") + testInputs match { + case Valid(_) => fail("Input section contained stdout. Should have failed.") + case Invalid(e) => e.head should be("Workflow cannot have stdout expression in input section at workflow-level.") + } + } + + it should "return an error when there is an stderr in input section" in { + val testInputs = checkDisallowedInputElement(mockInputSectionStderr, StderrElement, "stderr") + testInputs match { + case Valid(_) => fail("Input section contained stderr. Should have failed.") + case Invalid(e) => e.head should be("Workflow cannot have stderr expression in input section at workflow-level.") + } + } + + it should "not return an error for non-stdout/stderr in the inputs section" in { + val testInputs = checkDisallowedInputElement(mockInputSectionNonStd, StdoutElement, "non-stdout/stderr") + testInputs match { + case Valid(_) => // No action + case Invalid(_) => fail("Check shouldn't have returned error as input section had non-stdout/stderr inputs") + } + } + + it should "return an error when there is an stdout in output section" in { + val testOutputs = checkDisallowedOutputElement(mockOutputSectionStdout, StdoutElement, "stdout") + testOutputs match { + case Valid(_) => fail("Output section contained stdout. Should have failed.") + case Invalid(e) => e.head should be("Workflow cannot have stdout expression in output section at workflow-level.") + } + } + + it should "return an error when there is an stderr in output section" in { + val testOutputs = checkDisallowedOutputElement(mockOutputSectionStderr, StderrElement, "stderr") + testOutputs match { + case Valid(_) => fail("Output section contained stderr. Should have failed.") + case Invalid(e) => e.head should be("Workflow cannot have stderr expression in output section at workflow-level.") + } + } + it should "not return an error for non-stdout/stderr in the outputs section" in { + val testOutputs = checkDisallowedOutputElement(mockOutputSectionNonStd, StdoutElement, "non-stdout/stderr") + testOutputs match { + case Valid(_) => // No action + case Invalid(_) => fail("Check shouldn't have returned error as output section had non-stdout/stderr outputs") + } + } + + it should "return an error when there is an stdout at intermediate declaration section" in { + val testIntermediates = checkDisallowedIntermediates(mockIntermediatesStdout, StdoutElement, "stdout") + testIntermediates match { + case Valid(_) => fail("Intermediate section contained stdout. Should have failed.") + case Invalid(e) => e.head should be("Workflow cannot have stdout expression at intermediate declaration section at workflow-level.") + } + } + it should "return an error when there is an stderr at intermediate declaration section" in { + val testIntermediates = checkDisallowedIntermediates(mockIntermediatesStderr, StderrElement, "stderr") + testIntermediates match { + case Valid(_) => fail("Intermediate section contained stderr. Should have failed.") + case Invalid(e) => e.head should be("Workflow cannot have stderr expression at intermediate declaration section at workflow-level.") + } + } + + it should "not return an error for non-stdout/stderr in the intermediates section" in { + val testIntermediates = checkDisallowedIntermediates(mockIntermediatesNonStd, StdoutElement, "non-stdout/stderr") + testIntermediates match { + case Valid(_) => // No action + case Invalid(_) => fail("Check shouldn't have returned error as intermediate section had non-stdout/stderr intermediates.") + } + } + +} \ No newline at end of file diff --git a/wdl/transforms/new-base/src/main/scala/wdl/transforms/base/ast2wdlom/AstToWorkflowDefinitionElement.scala b/wdl/transforms/new-base/src/main/scala/wdl/transforms/base/ast2wdlom/AstToWorkflowDefinitionElement.scala index ad4c28f064a..6b8e7d08dd0 100644 --- a/wdl/transforms/new-base/src/main/scala/wdl/transforms/base/ast2wdlom/AstToWorkflowDefinitionElement.scala +++ b/wdl/transforms/new-base/src/main/scala/wdl/transforms/base/ast2wdlom/AstToWorkflowDefinitionElement.scala @@ -8,6 +8,7 @@ import common.transforms.CheckedAtoB import common.validation.ErrorOr._ import wdl.model.draft3.elements._ import wom.SourceFileLocation +import wdl.model.draft3.elements.ExpressionElement._ object AstToWorkflowDefinitionElement { @@ -26,26 +27,65 @@ object AstToWorkflowDefinitionElement { sourceLocation: Option[SourceFileLocation], bodyElements: Vector[WorkflowBodyElement]) = { - val inputsSectionValidation: ErrorOr[Option[InputsSectionElement]] = validateSize(bodyElements.filterByType[InputsSectionElement], "inputs", 1) - val outputsSectionValidation: ErrorOr[Option[OutputsSectionElement]] = validateSize(bodyElements.filterByType[OutputsSectionElement], "outputs", 1) + val inputsSectionValidation: ErrorOr[Option[InputsSectionElement]] = for { + inputValidateElement <- validateSize(bodyElements.filterByType[InputsSectionElement], "inputs", 1): ErrorOr[Option[InputsSectionElement]] + _ <- checkDisallowedInputElement(inputValidateElement, StdoutElement, "stdout") + _ <- checkDisallowedInputElement(inputValidateElement, StderrElement, "stderr") + } yield inputValidateElement + + val intermediateValueDeclarationStdoutCheck = checkDisallowedIntermediates(bodyElements.filterByType[IntermediateValueDeclarationElement], StdoutElement, "stdout") + val intermediateValueDeclarationStderrCheck = checkDisallowedIntermediates(bodyElements.filterByType[IntermediateValueDeclarationElement], StderrElement, "stderr") + + val outputsSectionValidation: ErrorOr[Option[OutputsSectionElement]] = for { + outputValidateElement <- validateSize(bodyElements.filterByType[OutputsSectionElement], "outputs", 1): ErrorOr[Option[OutputsSectionElement]] + _ <- checkDisallowedOutputElement(outputValidateElement, StdoutElement, "stdout") + _ <- checkDisallowedOutputElement(outputValidateElement, StderrElement, "stderr") + } yield outputValidateElement val graphSections: Vector[WorkflowGraphElement] = bodyElements.filterByType[WorkflowGraphElement] val metaSectionValidation: ErrorOr[Option[MetaSectionElement]] = validateSize(bodyElements.filterByType[MetaSectionElement], "meta", 1) val parameterMetaSectionValidation: ErrorOr[Option[ParameterMetaSectionElement]] = validateSize(bodyElements.filterByType[ParameterMetaSectionElement], "parameterMeta", 1) - (inputsSectionValidation, outputsSectionValidation, metaSectionValidation, parameterMetaSectionValidation) mapN { - (validInputs, validOutputs, meta, parameterMeta) => + (inputsSectionValidation, outputsSectionValidation, metaSectionValidation, parameterMetaSectionValidation, intermediateValueDeclarationStdoutCheck, intermediateValueDeclarationStderrCheck) mapN { + (validInputs, validOutputs, meta, parameterMeta, _, _) => WorkflowDefinitionElement(name, validInputs, graphSections.toSet, validOutputs, meta, parameterMeta, sourceLocation) } } + def checkDisallowedInputElement(inputSection: Option[InputsSectionElement], expressionType: FunctionCallElement, expressionName: String): ErrorOr[Unit] = { + inputSection match { + case Some(section) => + if (section.inputDeclarations.flatMap(_.expression).exists(_.isInstanceOf[expressionType.type])) { + s"Workflow cannot have $expressionName expression in input section at workflow-level.".invalidNel + } else ().validNel + case None => ().validNel + } + } + + def checkDisallowedOutputElement(outputSection: Option[OutputsSectionElement], expressionType: FunctionCallElement, expressionName: String): ErrorOr[Unit] = { + outputSection match { + case Some(section) => + if (section.outputs.map(_.expression).exists(_.isInstanceOf[expressionType.type])) { + s"Workflow cannot have $expressionName expression in output section at workflow-level.".invalidNel + } else ().validNel + case None => ().validNel + } + } + + def checkDisallowedIntermediates(intermediate: Vector[IntermediateValueDeclarationElement], expressionType: FunctionCallElement, expressionName: String): ErrorOr[Unit] = { + if (intermediate.map(_.expression).exists(_.isInstanceOf[expressionType.type])) { + s"Workflow cannot have $expressionName expression at intermediate declaration section at workflow-level.".invalidNel + } else ().validNel + } + private def validateSize[A](elements: Vector[A], sectionName: String, numExpected: Int): ErrorOr[Option[A]] = { val sectionValidation: ErrorOr[Option[A]] = if (elements.size > numExpected) { s"Workflow cannot have more than $numExpected $sectionName sections, found ${elements.size}.".invalidNel } else { elements.headOption.validNel } + sectionValidation } -} +} \ No newline at end of file