Skip to content

Commit

Permalink
Add new daffodil-tdml-junit library
Browse files Browse the repository at this point in the history
There is currently a large amount of boilerplate and duplication
associated with defining TDML tests with JUnit. To fix this, this adds
two new traits that can be mixed in to the companion object and class
associated with a JUnit test suite to greatly simplify test suite
definitions. Test suites can now be defined with something like this:

    object MyTests extends TdmlSuite {
      val tdmlResource = "/resource/path/to/tests.tdml"
    }

    class MyTests extends TdmlTests {
      val tdmlSuite = MyTests

      @test def test1 = test
      @test def test2 = test
      @test def test3 = test
    }

To allow use with older versions of Daffodil, this defaults to using
creating a Runner with only the path parameter. This is sufficient for
all known schema projects, since most Runner parameters can either be
set directly in a TDML file (which is preferred), or are intended for
use by Daffodil tests and should not be changed by schema projects.

The sbt-plugin project will eventually be updated to include this as a
dependency for DFDL Schema projects, but until that change is released,
projects that want to use this new syntax should add the following to
dependency to build.sbt file, in addition to any existing daffodil
dependencies:

    libraryDependencies ++= Seq(
      "org.apache.daffodil" %% "daffodil-tdml-junit" % "<version>" % "test" intransitive()
    )

Where "<version>" is hardcoded to the first version that
daffodil-tdml-junit is released for. The new library works with older
versions of Daffodil, so by hardcoding the version and setting it as an
intransitive dependency, it allows the library the be used with TDML
tests even when testing older daffodil versions that do not publish the
library. As we no longer need to support older versions of Daffodil and
sbt-daffodil supports the new library, this will not be needed.

DAFFODIL-2958
  • Loading branch information
stevedlawrence committed Dec 10, 2024
1 parent 0f3793a commit a2c2eee
Show file tree
Hide file tree
Showing 4 changed files with 201 additions and 4 deletions.
14 changes: 10 additions & 4 deletions build.sbt
Original file line number Diff line number Diff line change
Expand Up @@ -44,6 +44,7 @@ lazy val daffodil = project
sapi,
schematron,
slf4jLogger,
tdmlJunit,
tdmlLib,
tdmlProc,
testDaf,
Expand Down Expand Up @@ -161,6 +162,11 @@ lazy val tdmlProc = Project("daffodil-tdml-processor", file("daffodil-tdml-proce
.dependsOn(tdmlLib, codeGenC, core, slf4jLogger)
.settings(commonSettings)

lazy val tdmlJunit = Project("daffodil-tdml-junit", file("daffodil-tdml-junit"))
.dependsOn(tdmlProc)
.settings(commonSettings)
.settings(libraryDependencies ++= Dependencies.junit)

lazy val cli = Project("daffodil-cli", file("daffodil-cli"))
.dependsOn(
tdmlProc,
Expand All @@ -184,7 +190,7 @@ lazy val schematron = Project("daffodil-schematron", file("daffodil-schematron")
.settings(libraryDependencies ++= Dependencies.schematron)

lazy val testDaf = Project("daffodil-test", file("daffodil-test"))
.dependsOn(tdmlProc % "test", codeGenC % "test->test", udf % "test->test")
.dependsOn(tdmlJunit % "test", codeGenC % "test->test", udf % "test->test")
.settings(commonSettings, nopublish)
//
// Uncomment the following line to run these tests
Expand All @@ -193,7 +199,7 @@ lazy val testDaf = Project("daffodil-test", file("daffodil-test"))
//.settings(IBMDFDLCrossTesterPlugin.settings)

lazy val testIBM1 = Project("daffodil-test-ibm1", file("daffodil-test-ibm1"))
.dependsOn(tdmlProc % "test")
.dependsOn(tdmlJunit % "test")
.settings(commonSettings, nopublish)
//
// Uncomment the following line to run these tests
Expand All @@ -216,11 +222,11 @@ lazy val testIntegration =
)

lazy val tutorials = Project("daffodil-tutorials", file("tutorials"))
.dependsOn(tdmlProc % "test")
.dependsOn(tdmlJunit % "test")
.settings(commonSettings, nopublish)

lazy val testStdLayout = Project("daffodil-test-stdLayout", file("test-stdLayout"))
.dependsOn(tdmlProc % "test")
.dependsOn(tdmlJunit % "test")
.settings(commonSettings, nopublish)

// Choices here are Java LTS versions, 8, 11, 17, 21,...
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,122 @@
/*
* Licensed to the Apache Software Foundation (ASF) under one or more
* contributor license agreements. See the NOTICE file distributed with
* this work for additional information regarding copyright ownership.
* The ASF licenses this file to You under the Apache License, Version 2.0
* (the "License"); you may not use this file except in compliance with
* the License. You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/

package org.apache.daffodil.junit.tdml

import java.io.File

import org.apache.daffodil.tdml.Runner
import org.apache.daffodil.tdml.TDMLTestNotCompatibleException

import org.junit.AfterClass
import org.junit.AssumptionViolatedException
import org.junit.Rule
import org.junit.rules.TestName

/**
* Mixin for a JUnit test suite companion object
*/
trait TdmlSuite {

/**
* Resource path to the TDML file to use for this JUnit suite
*/
val tdmlResource: String

/**
* Function to get the directory containing tdmlResource. Useful if createRunner is overridden
* to use the Runner(dir, file, ...) factory method
*/
def tdmlDir: String = new File(tdmlResource).getParent()

Check warning on line 44 in daffodil-tdml-junit/src/main/scala/org/apache/daffodil/junit/tdml/TdmlSuite.scala

View check run for this annotation

Codecov / codecov/patch

daffodil-tdml-junit/src/main/scala/org/apache/daffodil/junit/tdml/TdmlSuite.scala#L44

Added line #L44 was not covered by tests

/**
* Function to get basename of tdmlResource. Useful if createRunner is overridden to use the
* Runner(dir, file, ...) factory method
*/
def tdmlFile: String = new File(tdmlResource).getName()

Check warning on line 50 in daffodil-tdml-junit/src/main/scala/org/apache/daffodil/junit/tdml/TdmlSuite.scala

View check run for this annotation

Codecov / codecov/patch

daffodil-tdml-junit/src/main/scala/org/apache/daffodil/junit/tdml/TdmlSuite.scala#L50

Added line #L50 was not covered by tests

/**
* Default implementation to create a Runner using the tdmlResource. This function can be
* overridden if additional options need to be passed to the Runner factory object, for
* example to disable TDML validation. In most cases this should not be needed, since most
* parameters should not be changed by normal schema tests, or can be defined in the TDML
* file.
*/
def createRunner(): Runner = Runner(tdmlResource)

Check warning on line 59 in daffodil-tdml-junit/src/main/scala/org/apache/daffodil/junit/tdml/TdmlSuite.scala

View check run for this annotation

Codecov / codecov/patch

daffodil-tdml-junit/src/main/scala/org/apache/daffodil/junit/tdml/TdmlSuite.scala#L59

Added line #L59 was not covered by tests

/**
* Lazily build the runner when needed
*/
final lazy val runner = createRunner()

/**
* Ensure all resources associated with the Runner (e.g. cached compiled schemas, parsed TDML
* XML) to be freed once the test suite has completed
*/
@AfterClass
final def shutDown(): Unit = runner.reset

Check warning on line 71 in daffodil-tdml-junit/src/main/scala/org/apache/daffodil/junit/tdml/TdmlSuite.scala

View check run for this annotation

Codecov / codecov/patch

daffodil-tdml-junit/src/main/scala/org/apache/daffodil/junit/tdml/TdmlSuite.scala#L71

Added line #L71 was not covered by tests
}

/**
* Mixin for a JUnit test suite companion class
*/
trait TdmlTests {

/**
* The companion object that contains the runner to be used for tests in this class
*/
val tdmlSuite: TdmlSuite

/**
* Run a test from the runner with the same name as the JUnit test
*
* If a tdml test is not compatible with the DFDL implementation used for the test, we mark
* test test as having violated an assumption. This prevents the test from generating a test
* case failure, and instead it is marked as "skipped".
*/
final def test: Unit = {
try {
tdmlSuite.runner.runOneTest(testName)

Check warning on line 93 in daffodil-tdml-junit/src/main/scala/org/apache/daffodil/junit/tdml/TdmlSuite.scala

View check run for this annotation

Codecov / codecov/patch

daffodil-tdml-junit/src/main/scala/org/apache/daffodil/junit/tdml/TdmlSuite.scala#L93

Added line #L93 was not covered by tests
} catch {
case e: TDMLTestNotCompatibleException =>
throw new AssumptionViolatedException(e.getMessage, e)

Check warning on line 96 in daffodil-tdml-junit/src/main/scala/org/apache/daffodil/junit/tdml/TdmlSuite.scala

View check run for this annotation

Codecov / codecov/patch

daffodil-tdml-junit/src/main/scala/org/apache/daffodil/junit/tdml/TdmlSuite.scala#L96

Added line #L96 was not covered by tests
}
}

/**
* Helper function to easily trace a test. This should only be used for debugging and running
* a single test since "trace" modifies the state of the Runner causing all later tests in the
* suite to run with trace enabled
*/
final def trace: Unit = {
tdmlSuite.runner.trace
test

Check warning on line 107 in daffodil-tdml-junit/src/main/scala/org/apache/daffodil/junit/tdml/TdmlSuite.scala

View check run for this annotation

Codecov / codecov/patch

daffodil-tdml-junit/src/main/scala/org/apache/daffodil/junit/tdml/TdmlSuite.scala#L106-L107

Added lines #L106 - L107 were not covered by tests
}

/**
* Helper function to get the current JUnit test name. Useful for some tests that might want
* to provide special diagnostics about the test name
*/
final def testName = _name.getMethodName

Check warning on line 114 in daffodil-tdml-junit/src/main/scala/org/apache/daffodil/junit/tdml/TdmlSuite.scala

View check run for this annotation

Codecov / codecov/patch

daffodil-tdml-junit/src/main/scala/org/apache/daffodil/junit/tdml/TdmlSuite.scala#L114

Added line #L114 was not covered by tests

/**
* JUnit magic for setting the test name. Because the way scala turns vals into class members
* + methods, the Rule annotation must be on a def
*/
private val _name: TestName = new TestName()
@Rule final def name = _name

Check warning on line 121 in daffodil-tdml-junit/src/main/scala/org/apache/daffodil/junit/tdml/TdmlSuite.scala

View check run for this annotation

Codecov / codecov/patch

daffodil-tdml-junit/src/main/scala/org/apache/daffodil/junit/tdml/TdmlSuite.scala#L120-L121

Added lines #L120 - L121 were not covered by tests
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,65 @@
/*
* Licensed to the Apache Software Foundation (ASF) under one or more
* contributor license agreements. See the NOTICE file distributed with
* this work for additional information regarding copyright ownership.
* The ASF licenses this file to You under the Apache License, Version 2.0
* (the "License"); you may not use this file except in compliance with
* the License. You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/

package org.apache.daffodil.junit

/**
* <h3>Overview</h3>
*
* Provides two traits to reduce boilerplate related to defining TDML tests:
*
* [[TdmlSuite]]: mixed into a Junit test suite companion object ensures the TDML Runner is
* created and cleaned up appropriately. Direct access to the runner is available, though most
* tests should use the "test" or "trace" methods defined in [[TdmlTests]]. Note that a
* TdmlSuite only supports a single TDML file and runner. If multiple are needed, additional
* test class objects must be created. The TdmlSuite also only supports creating a Runner with
* the default arguments. This is sufficient in most cases, but if a different runner is needed,
* implementations can override the [[TdmlSuite.createRunner]] function.
*
* [[TdmlTests]]: contains [[TdmlTests.test]] and [[TdmlTests.trace]] helper functions to easily
* run or trace tests without having to provide the runner or the test name. The JUnit test name
* is used as the parameter passed to runOneTest (i.e. the name of the test in the TDML file).
* The runner to use is the runner associated with the companion object, which must be set in
* the [[TdmlTests.tdmlSuite]] val
*
* <h4>Examples</h4>
*
* The following example defines a JUnit test suite called "MyTests". The companion object mixes
* in TdmlSuite and defines the tdmlResoruce to use for all tests in the suite. The companion
* class mixes in TdmlTests and provides a reference to the companion object via the tdmlSuite
* val, and defines a number of JUnit tests that each call the "test" method to run the TDML
* test with the same name as the JUnit test.
* {{{
* import org.apache.daffodil.junit.tdml.TdmlSuite
* import org.apache.daffodil.junit.tdml.TdmlTests
*
* import org.junit.Tests
*
* object MyTests extends TdmlSuite {
* val tdmlResource = "/resource/path/to/tests.tdml"
* }
*
* class MyTests extends TdmlTests {
* val tdmlSuite = MyTests
*
* @Test def test1 = test
* @Test def test2 = test
* @Test def test3 = test
* }
*}}}
*/
package object tdml
4 changes: 4 additions & 0 deletions project/Dependencies.scala
Original file line number Diff line number Diff line change
Expand Up @@ -50,6 +50,10 @@ object Dependencies {
"net.sf.expectit" % "expectit-core" % "0.9.0" % "test"
)

lazy val junit = Seq(
"junit" % "junit" % "4.13.2"
)

lazy val test = Seq(
"junit" % "junit" % "4.13.2" % "test",
"com.github.sbt" % "junit-interface" % "0.13.3" % "test",
Expand Down

0 comments on commit a2c2eee

Please sign in to comment.