diff --git a/.scalafmt.conf b/.scalafmt.conf new file mode 100644 index 0000000..23d56d5 --- /dev/null +++ b/.scalafmt.conf @@ -0,0 +1,8 @@ +version = "3.9.5" +runner.dialect = scala213 + +maxColumn = 120 +lineEndings = unix + +# Only format files tracked by git. +project.git = true diff --git a/build.sbt b/build.sbt index 695d9a2..c0de9ec 100644 --- a/build.sbt +++ b/build.sbt @@ -42,7 +42,7 @@ Compile / unmanagedSourceDirectories ++= { val sourceDir = (Compile / sourceDirectory).value CrossVersion.partialVersion(scalaVersion.value).map { case (0 | 3, _) => sourceDir / "scala-3" - case (n, _) => sourceDir / s"scala-$n" + case (n, _) => sourceDir / s"scala-$n" } } @@ -69,24 +69,24 @@ import scala.xml.transform.{RewriteRule, RuleTransformer} pomPostProcess := { (node: XmlNode) => new RuleTransformer(new RewriteRule { override def transform(node: XmlNode): XmlNodeSeq = node match { - case e: Elem if e.label == "dependency" - && e.child.exists(child => child.label == "scope") => + case e: Elem + if e.label == "dependency" + && e.child.exists(child => child.label == "scope") => def txt(label: String): String = "\"" + e.child.filter(_.label == label).flatMap(_.text).mkString + "\"" - Comment(s""" scoped dependency ${txt("groupId")} % ${txt("artifactId")} % ${txt("version")} % ${txt("scope")} has been omitted """) + Comment(s""" scoped dependency ${txt("groupId")} % ${txt("artifactId")} % ${txt("version")} % ${txt( + "scope" + )} has been omitted """) case _ => node } }).transform(node).head } Test / testOptions := - Seq( - Tests.Argument(TestFrameworks.ScalaTest, - "-m", "org.scalatestplus.junit5", - )) + Seq(Tests.Argument(TestFrameworks.ScalaTest, "-m", "org.scalatestplus.junit5")) Test / fork := true -Test / javaOptions +="-Dorg.scalatestplus.junit5.ScalaTestEngine.disabled=true" +Test / javaOptions += "-Dorg.scalatestplus.junit5.ScalaTestEngine.disabled=true" enablePlugins(SbtOsgi) @@ -98,12 +98,12 @@ OsgiKeys.exportPackage := Seq( OsgiKeys.importPackage := Seq( "org.scalatest.*", - "org.scalactic.*", - "scala.*;version=\"$>\"", + "org.scalactic.*", + "scala.*;version=\"$>\"", "*;resolution:=optional" ) -OsgiKeys.additionalHeaders:= Map( +OsgiKeys.additionalHeaders := Map( "Bundle-Name" -> "ScalaTestPlusJUnit5", "Bundle-Description" -> "ScalaTest+JUnit5 is an open-source integration library between ScalaTest and JUnit 5 for Scala projects.", "Bundle-DocURL" -> "http://www.scalatest.org/", @@ -151,8 +151,7 @@ def docTask(docDir: File, resDir: File, projectName: String): File = { try { writer.println(css) writer.println(addlCss) - } - finally { writer.close } + } finally { writer.close } } if (projectName.contains("scalatest")) { @@ -163,10 +162,13 @@ def docTask(docDir: File, resDir: File, projectName: String): File = { docDir } -Compile / doc := docTask((Compile / doc).value, - (Compile / sourceDirectory).value, - name.value) +Compile / doc := docTask((Compile / doc).value, (Compile / sourceDirectory).value, name.value) -Compile / doc / scalacOptions := Seq("-doc-title", s"ScalaTest + JUnit5 ${version.value}", - "-sourcepath", baseDirectory.value.getAbsolutePath(), - "-doc-source-url", s"https://github.com/scalatest/releases-source/blob/main/scalatestplus-junit5/${version.value}€{FILE_PATH}.scala") \ No newline at end of file +Compile / doc / scalacOptions := Seq( + "-doc-title", + s"ScalaTest + JUnit5 ${version.value}", + "-sourcepath", + baseDirectory.value.getAbsolutePath(), + "-doc-source-url", + s"https://github.com/scalatest/releases-source/blob/main/scalatestplus-junit5/${version.value}€{FILE_PATH}.scala" +) diff --git a/examples/gradle-example/gradlew b/examples/gradle-example/gradlew old mode 100755 new mode 100644 diff --git a/examples/gradle-example/src/test/scala/co/helmethair/scalatest/example/CustomListener.scala b/examples/gradle-example/src/test/scala/co/helmethair/scalatest/example/CustomListener.scala index 12ad5b2..1c81201 100644 --- a/examples/gradle-example/src/test/scala/co/helmethair/scalatest/example/CustomListener.scala +++ b/examples/gradle-example/src/test/scala/co/helmethair/scalatest/example/CustomListener.scala @@ -8,6 +8,8 @@ class CustomListener extends TestExecutionListener { } override def executionFinished(testIdentifier: TestIdentifier, testExecutionResult: TestExecutionResult): Unit = { - System.err.println(s">>>>> CustomListener: execution FINISHED: ${testIdentifier.getDisplayName} with result: ${testExecutionResult.getStatus.name()}") + System.err.println( + s">>>>> CustomListener: execution FINISHED: ${testIdentifier.getDisplayName} with result: ${testExecutionResult.getStatus.name()}" + ) } } diff --git a/examples/gradle-example/src/test/scala/co/helmethair/scalatest/example/DynamicTest.scala b/examples/gradle-example/src/test/scala/co/helmethair/scalatest/example/DynamicTest.scala index f627d8d..380b80f 100644 --- a/examples/gradle-example/src/test/scala/co/helmethair/scalatest/example/DynamicTest.scala +++ b/examples/gradle-example/src/test/scala/co/helmethair/scalatest/example/DynamicTest.scala @@ -4,10 +4,8 @@ import org.scalatest.funsuite.AnyFunSuiteLike class DynamicTest extends AnyFunSuiteLike { - test(s"(won't run with gradle or intellij single run) this is dynamic value -> ${System.currentTimeMillis()}") { - } + test(s"(won't run with gradle or intellij single run) this is dynamic value -> ${System.currentTimeMillis()}") {} - test(s"(wwill run with gradle, intellij class run and intellij single run) this is test with static name") { - } + test(s"(wwill run with gradle, intellij class run and intellij single run) this is test with static name") {} } diff --git a/examples/gradle-example/src/test/scala/co/helmethair/scalatest/example/IncludeWithScalaTag.scala b/examples/gradle-example/src/test/scala/co/helmethair/scalatest/example/IncludeWithScalaTag.scala index 9e7d715..6eb3ccb 100644 --- a/examples/gradle-example/src/test/scala/co/helmethair/scalatest/example/IncludeWithScalaTag.scala +++ b/examples/gradle-example/src/test/scala/co/helmethair/scalatest/example/IncludeWithScalaTag.scala @@ -6,21 +6,13 @@ import org.scalatest.matchers.should.Matchers @co.helmethair.scalatest.tags.Include class IncludeWithScalaTag extends AnyFunSpec with Matchers { describe("test one") { - it("assert one") { - - } - it("assert two") { - - } + it("assert one") {} + it("assert two") {} } describe("test two") { - it("assert one") { - - } - - it("assert two") { + it("assert one") {} - } + it("assert two") {} } } diff --git a/examples/gradle-example/src/test/scala/co/helmethair/scalatest/example/SkipWithScalaTag.scala b/examples/gradle-example/src/test/scala/co/helmethair/scalatest/example/SkipWithScalaTag.scala index 408c930..48048f8 100644 --- a/examples/gradle-example/src/test/scala/co/helmethair/scalatest/example/SkipWithScalaTag.scala +++ b/examples/gradle-example/src/test/scala/co/helmethair/scalatest/example/SkipWithScalaTag.scala @@ -6,21 +6,13 @@ import org.scalatest.matchers.should.Matchers @co.helmethair.scalatest.tags.Skip class SkipWithScalaTag extends AnyFunSpec with Matchers { describe("test one") { - it("assert one") { - - } - it("assert two") { - - } + it("assert one") {} + it("assert two") {} } describe("test two") { - it("assert one") { - - } - it("assert two") { - - } + it("assert one") {} + it("assert two") {} } } diff --git a/examples/gradle-kotlin-dsl-example/gradlew b/examples/gradle-kotlin-dsl-example/gradlew old mode 100755 new mode 100644 diff --git a/examples/gradle-kotlin-dsl-example/src/test/scala/co/helmethair/scalatest/example/DynamicTest.scala b/examples/gradle-kotlin-dsl-example/src/test/scala/co/helmethair/scalatest/example/DynamicTest.scala index f627d8d..380b80f 100644 --- a/examples/gradle-kotlin-dsl-example/src/test/scala/co/helmethair/scalatest/example/DynamicTest.scala +++ b/examples/gradle-kotlin-dsl-example/src/test/scala/co/helmethair/scalatest/example/DynamicTest.scala @@ -4,10 +4,8 @@ import org.scalatest.funsuite.AnyFunSuiteLike class DynamicTest extends AnyFunSuiteLike { - test(s"(won't run with gradle or intellij single run) this is dynamic value -> ${System.currentTimeMillis()}") { - } + test(s"(won't run with gradle or intellij single run) this is dynamic value -> ${System.currentTimeMillis()}") {} - test(s"(wwill run with gradle, intellij class run and intellij single run) this is test with static name") { - } + test(s"(wwill run with gradle, intellij class run and intellij single run) this is test with static name") {} } diff --git a/examples/maven-example/src/test/scala/co/helmethair/scalatest/example/CustomListener.scala b/examples/maven-example/src/test/scala/co/helmethair/scalatest/example/CustomListener.scala index 3ee452b..d5df90d 100644 --- a/examples/maven-example/src/test/scala/co/helmethair/scalatest/example/CustomListener.scala +++ b/examples/maven-example/src/test/scala/co/helmethair/scalatest/example/CustomListener.scala @@ -9,6 +9,8 @@ class CustomListener extends TestExecutionListener { } override def executionFinished(testIdentifier: TestIdentifier, testExecutionResult: TestExecutionResult): Unit = { - System.err.println(s">>>>> CustomListener: execution FINISHED: ${testIdentifier.getDisplayName} with result: ${testExecutionResult.getStatus.name()}") + System.err.println( + s">>>>> CustomListener: execution FINISHED: ${testIdentifier.getDisplayName} with result: ${testExecutionResult.getStatus.name()}" + ) } } diff --git a/examples/maven-example/src/test/scala/co/helmethair/scalatest/example/DynamicTest.scala b/examples/maven-example/src/test/scala/co/helmethair/scalatest/example/DynamicTest.scala index f627d8d..380b80f 100644 --- a/examples/maven-example/src/test/scala/co/helmethair/scalatest/example/DynamicTest.scala +++ b/examples/maven-example/src/test/scala/co/helmethair/scalatest/example/DynamicTest.scala @@ -4,10 +4,8 @@ import org.scalatest.funsuite.AnyFunSuiteLike class DynamicTest extends AnyFunSuiteLike { - test(s"(won't run with gradle or intellij single run) this is dynamic value -> ${System.currentTimeMillis()}") { - } + test(s"(won't run with gradle or intellij single run) this is dynamic value -> ${System.currentTimeMillis()}") {} - test(s"(wwill run with gradle, intellij class run and intellij single run) this is test with static name") { - } + test(s"(wwill run with gradle, intellij class run and intellij single run) this is test with static name") {} } diff --git a/src/main/scala-2/org/scalatestplus/junit5/AssertionsForJUnitMacro.scala b/src/main/scala-2/org/scalatestplus/junit5/AssertionsForJUnitMacro.scala index ac53805..6fa8b7e 100644 --- a/src/main/scala-2/org/scalatestplus/junit5/AssertionsForJUnitMacro.scala +++ b/src/main/scala-2/org/scalatestplus/junit5/AssertionsForJUnitMacro.scala @@ -19,19 +19,24 @@ import org.scalactic._ import reflect.macros.Context import org.scalatest.Assertion -/** - * Macro implementation that provides rich error message for boolean expression assertion. +/** Macro implementation that provides rich error message for boolean expression assertion. */ private[junit5] object AssertionsForJUnitMacro { - /** - * Provides assertion implementation for Assertions.assert(booleanExpr: Boolean), with rich error message. + /** Provides assertion implementation for Assertions.assert(booleanExpr: Boolean), with rich error + * message. * - * @param context macro context - * @param condition original condition expression - * @return transformed expression that performs the assertion check and throw TestFailedException with rich error message if assertion failed + * @param context + * macro context + * @param condition + * original condition expression + * @return + * transformed expression that performs the assertion check and throw TestFailedException with rich + * error message if assertion failed */ - def assert(context: Context)(condition: context.Expr[Boolean])(prettifier: context.Expr[Prettifier], pos: context.Expr[source.Position]): context.Expr[Assertion] = { + def assert(context: Context)( + condition: context.Expr[Boolean] + )(prettifier: context.Expr[Prettifier], pos: context.Expr[source.Position]): context.Expr[Assertion] = { import context.universe._ new BooleanMacro[context.type](context).genMacro[Assertion]( Select( @@ -54,18 +59,27 @@ private[junit5] object AssertionsForJUnitMacro { "macroAssert", context.literal(""), prettifier, - pos) + pos + ) } - /** - * Provides assertion implementation for Assertions.assert(booleanExpr: Boolean, clue: Any), with rich error message. + /** Provides assertion implementation for Assertions.assert(booleanExpr: Boolean, clue: Any), with rich + * error message. * - * @param context macro context - * @param condition original condition expression - * @param clue original clue expression - * @return transformed expression that performs the assertion check and throw TestFailedException with rich error message (clue included) if assertion failed + * @param context + * macro context + * @param condition + * original condition expression + * @param clue + * original clue expression + * @return + * transformed expression that performs the assertion check and throw TestFailedException with rich + * error message (clue included) if assertion failed */ - def assertWithClue(context: Context)(condition: context.Expr[Boolean], clue: context.Expr[Any])(prettifier: context.Expr[Prettifier], pos: context.Expr[source.Position]): context.Expr[Assertion] = { + def assertWithClue(context: Context)( + condition: context.Expr[Boolean], + clue: context.Expr[Any] + )(prettifier: context.Expr[Prettifier], pos: context.Expr[source.Position]): context.Expr[Assertion] = { import context.universe._ new BooleanMacro[context.type](context).genMacro[Assertion]( Select( @@ -88,17 +102,23 @@ private[junit5] object AssertionsForJUnitMacro { "macroAssert", clue, prettifier, - pos) + pos + ) } - /** - * Provides implementation for Assertions.assume(booleanExpr: Boolean), with rich error message. + /** Provides implementation for Assertions.assume(booleanExpr: Boolean), with rich error message. * - * @param context macro context - * @param condition original condition expression - * @return transformed expression that performs the assumption check and throw TestCanceledException with rich error message if assumption failed + * @param context + * macro context + * @param condition + * original condition expression + * @return + * transformed expression that performs the assumption check and throw TestCanceledException with rich + * error message if assumption failed */ - def assume(context: Context)(condition: context.Expr[Boolean])(prettifier: context.Expr[Prettifier], pos: context.Expr[source.Position]): context.Expr[Assertion] = { + def assume(context: Context)( + condition: context.Expr[Boolean] + )(prettifier: context.Expr[Prettifier], pos: context.Expr[source.Position]): context.Expr[Assertion] = { import context.universe._ new BooleanMacro[context.type](context).genMacro[Assertion]( Select( @@ -121,18 +141,27 @@ private[junit5] object AssertionsForJUnitMacro { "macroAssume", context.literal(""), prettifier, - pos) + pos + ) } - /** - * Provides implementation for Assertions.assume(booleanExpr: Boolean, clue: Any), with rich error message. + /** Provides implementation for Assertions.assume(booleanExpr: Boolean, clue: Any), with rich error + * message. * - * @param context macro context - * @param condition original condition expression - * @param clue original clue expression - * @return transformed expression that performs the assumption check and throw TestCanceledException with rich error message (clue included) if assumption failed + * @param context + * macro context + * @param condition + * original condition expression + * @param clue + * original clue expression + * @return + * transformed expression that performs the assumption check and throw TestCanceledException with rich + * error message (clue included) if assumption failed */ - def assumeWithClue(context: Context)(condition: context.Expr[Boolean], clue: context.Expr[Any])(prettifier: context.Expr[Prettifier], pos: context.Expr[source.Position]): context.Expr[Assertion] = { + def assumeWithClue(context: Context)( + condition: context.Expr[Boolean], + clue: context.Expr[Any] + )(prettifier: context.Expr[Prettifier], pos: context.Expr[source.Position]): context.Expr[Assertion] = { import context.universe._ new BooleanMacro[context.type](context).genMacro[Assertion]( Select( @@ -155,6 +184,7 @@ private[junit5] object AssertionsForJUnitMacro { "macroAssume", clue, prettifier, - pos) + pos + ) } } diff --git a/src/main/scala-2/org/scalatestplus/junit5/VersionSpecificAssertionsForJUnit5.scala b/src/main/scala-2/org/scalatestplus/junit5/VersionSpecificAssertionsForJUnit5.scala index edbade8..8aa5f73 100644 --- a/src/main/scala-2/org/scalatestplus/junit5/VersionSpecificAssertionsForJUnit5.scala +++ b/src/main/scala-2/org/scalatestplus/junit5/VersionSpecificAssertionsForJUnit5.scala @@ -6,11 +6,15 @@ import org.scalatest.{Assertion, Assertions} trait VersionSpecificAssertionsForJUnit extends Assertions { import scala.language.experimental.macros - override def assert(condition: Boolean)(implicit prettifier: Prettifier, pos: source.Position): Assertion = macro AssertionsForJUnitMacro.assert + override def assert(condition: Boolean)(implicit prettifier: Prettifier, pos: source.Position): Assertion = + macro AssertionsForJUnitMacro.assert - override def assert(condition: Boolean, clue: Any)(implicit prettifier: Prettifier, pos: source.Position): Assertion = macro AssertionsForJUnitMacro.assertWithClue + override def assert(condition: Boolean, clue: Any)(implicit prettifier: Prettifier, pos: source.Position): Assertion = + macro AssertionsForJUnitMacro.assertWithClue - override def assume(condition: Boolean)(implicit prettifier: Prettifier, pos: source.Position): Assertion = macro AssertionsForJUnitMacro.assume + override def assume(condition: Boolean)(implicit prettifier: Prettifier, pos: source.Position): Assertion = + macro AssertionsForJUnitMacro.assume - override def assume(condition: Boolean, clue: Any)(implicit prettifier: Prettifier, pos: source.Position): Assertion = macro AssertionsForJUnitMacro.assumeWithClue + override def assume(condition: Boolean, clue: Any)(implicit prettifier: Prettifier, pos: source.Position): Assertion = + macro AssertionsForJUnitMacro.assumeWithClue } diff --git a/src/main/scala/org/scalatestplus/junit5/AssertionsForJUnit.scala b/src/main/scala/org/scalatestplus/junit5/AssertionsForJUnit.scala index 805a199..e775067 100644 --- a/src/main/scala/org/scalatestplus/junit5/AssertionsForJUnit.scala +++ b/src/main/scala/org/scalatestplus/junit5/AssertionsForJUnit.scala @@ -21,100 +21,95 @@ import org.scalactic._ import org.scalactic.exceptions.NullArgumentException import org.scalatest.exceptions.{StackDepthException, TestCanceledException} -/** - * Trait that contains ScalaTest's basic assertion methods, suitable for use with JUnit 5. - * - *

- * The assertion methods provided in this trait look and behave exactly like the ones in - * Assertions, except instead of throwing - * TestFailedException they throw - * JUnitTestFailedError, - * which extends org.opentest4j.AssertionFailedError. - * - *

- * JUnit 3 (release 3.8 and earlier) distinguishes between failures and errors. - * If a test fails because of a failed assertion, that is considered a failure. If a test - * fails for any other reason, either the test code or the application being tested threw an unexpected - * exception, that is considered an error. The way JUnit 3 decides whether an exception represents - * a failure or error is that only thrown junit.framework.AssertionFailedErrors are considered - * failures. Any other exception type is considered an error. The exception type thrown by the JUnit 3 - * assertion methods declared in junit.framework.Assert (such as assertEquals, - * assertTrue, and fail) is, therefore, AssertionFailedError. - *

- * - *

- * In JUnit 4, junit.framework.AssertionFailedError was made to extend java.lang.AssertionError, - * and the distinction between failures and errors was essentially dropped. However, some tools that integrate - * with JUnit carry on this distinction, so even if you are using JUnit 4 you may want to use this - * AssertionsForJUnit trait instead of plain-old ScalaTest - * Assertions. - *

- * - *

- * In JUnit 5, org.opentest4j.AssertionFailedError is used as test-related AssertionError instead. - *

- * - * - * @author Bill Venners - * @author Chua Chee Seng - */ +/** Trait that contains ScalaTest's basic assertion methods, suitable for use with JUnit 5. + * + *

The assertion methods provided in this trait look and behave exactly like the ones in Assertions, except instead of throwing TestFailedException they throw JUnitTestFailedError, which extends + * org.opentest4j.AssertionFailedError. + * + *

JUnit 3 (release 3.8 and earlier) distinguishes between failures and errors. If a test fails + * because of a failed assertion, that is considered a failure. If a test fails for any other reason, either + * the test code or the application being tested threw an unexpected exception, that is considered an error. + * The way JUnit 3 decides whether an exception represents a failure or error is that only thrown + * junit.framework.AssertionFailedErrors are considered failures. Any other exception type is considered + * an error. The exception type thrown by the JUnit 3 assertion methods declared in junit.framework.Assert + * (such as assertEquals, assertTrue, and fail) is, therefore, + * AssertionFailedError.

+ * + *

In JUnit 4, junit.framework.AssertionFailedError was made to extend + * java.lang.AssertionError, and the distinction between failures and errors was essentially dropped. + * However, some tools that integrate with JUnit carry on this distinction, so even if you are using JUnit 4 you may + * want to use this AssertionsForJUnit trait instead of plain-old ScalaTest Assertions.

+ * + *

In JUnit 5, org.opentest4j.AssertionFailedError is used as test-related AssertionError instead. + *

+ * + * @author + * Bill Venners + * @author + * Chua Chee Seng + */ trait AssertionsForJUnit extends VersionSpecificAssertionsForJUnit { - private[org] override def newAssertionFailedException(optionalMessage: Option[String], optionalCause: Option[Throwable], pos: source.Position, differences: scala.collection.immutable.IndexedSeq[String]): Throwable = { + private[org] override def newAssertionFailedException( + optionalMessage: Option[String], + optionalCause: Option[Throwable], + pos: source.Position, + differences: scala.collection.immutable.IndexedSeq[String] + ): Throwable = { new JUnitTestFailedError(optionalMessage, optionalCause, pos, None) } - private[org] override def newTestCanceledException(optionalMessage: Option[String], optionalCause: Option[Throwable], pos: source.Position): Throwable = + private[org] override def newTestCanceledException( + optionalMessage: Option[String], + optionalCause: Option[Throwable], + pos: source.Position + ): Throwable = new TestCanceledException(toExceptionFunction(optionalMessage), optionalCause, pos, None) - /** - * If message or message contents are null, throw a null exception, otherwise - * create a function that returns the option. - */ + /** If message or message contents are null, throw a null exception, otherwise create a function that returns the + * option. + */ def toExceptionFunction(message: Option[String]): StackDepthException => Option[String] = { message match { - case null => throw new NullArgumentException("message was null") + case null => throw new NullArgumentException("message was null") case Some(null) => throw new NullArgumentException("message was a Some(null)") - case _ => { e => message } + case _ => { e => message } } } } -/** - * Companion object that facilitates the importing of AssertionsForJUnit members as - * an alternative to mixing it in. One use case is to import AssertionsForJUnit members so you can use - * them in the Scala interpreter: - * - *
- * sbt:junit-5.9> console
- * [info] Starting scala interpreter...
- * Welcome to Scala 2.13.10 (OpenJDK 64-Bit Server VM, Java 1.8.0_352).
- * Type in expressions for evaluation. Or try :help.
- *
- * scala> import org.scalatestplus.junit5.AssertionsForJUnit._
- * import org.scalatestplus.junit5.AssertionsForJUnit._
- *
- * scala> assert(1 === 2)
- * org.scalatestplus.junit5.JUnitTestFailedError: 1 did not equal 2
- *   at org.scalatestplus.junit5.AssertionsForJUnit.newAssertionFailedException(AssertionsForJUnit.scala:64)
- *    at org.scalatestplus.junit5.AssertionsForJUnit.newAssertionFailedException$(AssertionsForJUnit.scala:63)
- *    at org.scalatestplus.junit5.AssertionsForJUnit$.newAssertionFailedException(AssertionsForJUnit.scala:111)
- *    at org.scalatestplus.junit5.AssertionsForJUnit$AssertionsHelper.macroAssert(AssertionsForJUnit.scala:148)
- *   ... 35 elided
- * scala> val caught = intercept[StringIndexOutOfBoundsException] { "hi".charAt(-1) }
- * val caught: StringIndexOutOfBoundsException = java.lang.StringIndexOutOfBoundsException: String index out of range: -1
- * 
- * - * @author Bill Venners - * @author Chua Chee Seng - */ +/** Companion object that facilitates the importing of AssertionsForJUnit members as an alternative to + * mixing it in. One use case is to import AssertionsForJUnit members so you can use them in the Scala + * interpreter: + * + *
 sbt:junit-5.9> console [info] Starting scala interpreter... Welcome to Scala 2.13.10 (OpenJDK 64-Bit Server
+  * VM, Java 1.8.0_352). Type in expressions for evaluation. Or try :help.
+  *
+  * scala> import org.scalatestplus.junit5.AssertionsForJUnit._ import org.scalatestplus.junit5.AssertionsForJUnit._
+  *
+  * scala> assert(1 === 2) org.scalatestplus.junit5.JUnitTestFailedError: 1 did not equal 2 at
+  * org.scalatestplus.junit5.AssertionsForJUnit.newAssertionFailedException(AssertionsForJUnit.scala:64) at
+  * org.scalatestplus.junit5.AssertionsForJUnit.newAssertionFailedException$(AssertionsForJUnit.scala:63) at
+  * org.scalatestplus.junit5.AssertionsForJUnit$.newAssertionFailedException(AssertionsForJUnit.scala:111) at
+  * org.scalatestplus.junit5.AssertionsForJUnit$AssertionsHelper.macroAssert(AssertionsForJUnit.scala:148) ... 35 elided
+  * scala> val caught = intercept[StringIndexOutOfBoundsException] { "hi".charAt(-1) } val caught:
+  * StringIndexOutOfBoundsException = java.lang.StringIndexOutOfBoundsException: String index out of range: -1 
+ * + * @author + * Bill Venners + * @author + * Chua Chee Seng + */ object AssertionsForJUnit extends AssertionsForJUnit { import Requirements._ - /** - * Helper class used by code generated by the assert macro. - */ + /** Helper class used by code generated by the assert macro. + */ class AssertionsHelper { private def append(currentMessage: Option[String], clue: Any) = { @@ -135,12 +130,14 @@ object AssertionsForJUnit extends AssertionsForJUnit { } } - /** - * Assert that the passed in Bool is true, else fail with TestFailedException. - * - * @param bool the Bool to assert for - * @param clue optional clue to be included in TestFailedException's error message when assertion failed - */ + /** Assert that the passed in Bool is true, else fail with + * TestFailedException. + * + * @param bool + * the Bool to assert for + * @param clue + * optional clue to be included in TestFailedException's error message when assertion failed + */ def macroAssert(bool: Bool, clue: Any, prettifier: Prettifier, pos: source.Position): Assertion = { requireNonNull(clue)(prettifier, pos) if (!bool.value) { @@ -150,22 +147,26 @@ object AssertionsForJUnit extends AssertionsForJUnit { Succeeded } - /** - * Assert that the passed in Bool is true, else fail with TestFailedException. - * - * @param bool the Bool to assert for - * @param clue optional clue to be included in TestFailedException's error message when assertion failed - * @param pos source position - */ + /** Assert that the passed in Bool is true, else fail with + * TestFailedException. + * + * @param bool + * the Bool to assert for + * @param clue + * optional clue to be included in TestFailedException's error message when assertion failed + * @param pos + * source position + */ def macroAssert(bool: Bool, clue: Any, pos: source.Position): Assertion = macroAssert(bool, clue, bool.prettifier, pos) - /** - * Assume that the passed in Bool is true, else throw TestCanceledException. - * - * @param bool the Bool to assume for - * @param clue optional clue to be included in TestCanceledException's error message when assertion failed - */ + /** Assume that the passed in Bool is true, else throw TestCanceledException. + * + * @param bool + * the Bool to assume for + * @param clue + * optional clue to be included in TestCanceledException's error message when assertion failed + */ def macroAssume(bool: Bool, clue: Any, prettifier: Prettifier, pos: source.Position): Assertion = { requireNonNull(clue)(prettifier, pos) if (!bool.value) { @@ -175,20 +176,21 @@ object AssertionsForJUnit extends AssertionsForJUnit { Succeeded } - /** - * Assume that the passed in Bool is true, else throw TestCanceledException. - * - * @param bool the Bool to assume for - * @param clue optional clue to be included in TestCanceledException's error message when assertion failed - * @param pos source position - */ + /** Assume that the passed in Bool is true, else throw TestCanceledException. + * + * @param bool + * the Bool to assume for + * @param clue + * optional clue to be included in TestCanceledException's error message when assertion failed + * @param pos + * source position + */ def macroAssume(bool: Bool, clue: Any, pos: source.Position): Assertion = macroAssume(bool, clue, bool.prettifier, pos) } - /** - * Helper instance used by code generated by macro assertion. - */ + /** Helper instance used by code generated by macro assertion. + */ val assertionsHelper = new AssertionsHelper } diff --git a/src/main/scala/org/scalatestplus/junit5/ConcurrentDistributor.scala b/src/main/scala/org/scalatestplus/junit5/ConcurrentDistributor.scala index 737d0a2..855a568 100644 --- a/src/main/scala/org/scalatestplus/junit5/ConcurrentDistributor.scala +++ b/src/main/scala/org/scalatestplus/junit5/ConcurrentDistributor.scala @@ -21,11 +21,11 @@ import java.util.concurrent.ExecutorService import java.util.concurrent.Future import java.util.concurrent.LinkedBlockingQueue -/** - * This Distributor can be used by multiple threads. - * - * @author Bill Venners - */ +/** This Distributor can be used by multiple threads. + * + * @author + * Bill Venners + */ private[junit5] class ConcurrentDistributor(args: Args, execSvc: ExecutorService) extends Distributor { private val futureQueue = new LinkedBlockingQueue[Future[_]] @@ -33,7 +33,7 @@ private[junit5] class ConcurrentDistributor(args: Args, execSvc: ExecutorService def apply(suite: Suite, tracker: Tracker): Unit = { apply(suite, args.copy(tracker = tracker)) } - + def apply(suite: Suite, args: Args): Status = { requireNonNull(suite, args) val status = new StatefulStatus diff --git a/src/main/scala/org/scalatestplus/junit5/EngineExecutionListenerReporter.scala b/src/main/scala/org/scalatestplus/junit5/EngineExecutionListenerReporter.scala index 9b6b5f8..dedc1dd 100644 --- a/src/main/scala/org/scalatestplus/junit5/EngineExecutionListenerReporter.scala +++ b/src/main/scala/org/scalatestplus/junit5/EngineExecutionListenerReporter.scala @@ -19,7 +19,11 @@ import org.junit.platform.engine.{EngineExecutionListener, TestDescriptor, TestE import org.scalatest.{Resources => _, _} import org.scalatest.events._ -private[junit5] class EngineExecutionListenerReporter(listener: EngineExecutionListener, clzDesc: ScalaTestClassDescriptor, engineDesc: TestDescriptor) extends Reporter { +private[junit5] class EngineExecutionListenerReporter( + listener: EngineExecutionListener, + clzDesc: ScalaTestClassDescriptor, + engineDesc: TestDescriptor +) extends Reporter { // This form isn't clearly specified in JUnit docs, but some tools may assume it, so why rock the boat. // Here's what JUnit code does: @@ -34,56 +38,197 @@ private[junit5] class EngineExecutionListenerReporter(listener: EngineExecutionL private def testDescriptionName(suiteName: String, suiteClassName: Option[String], testName: String) = suiteClassName match { case Some(suiteClassName) => testName + "(" + suiteClassName + ")" - case None => testName + "(" + suiteName + ")" + case None => testName + "(" + suiteName + ")" } private def suiteDescriptionName(suiteName: String, suiteClassName: Option[String]) = suiteClassName match { case Some(suiteClassName) => suiteClassName - case None => suiteName + case None => suiteName + } + + private def createTestDescriptor( + suiteId: String, + suiteName: String, + suiteClassName: Option[String], + testName: String, + locationOpt: Option[Location] + ): ScalaTestDescriptor = { + + val qualifiedName = s"$suiteName - $testName" + if (suiteId.==(clzDesc.suiteClass.getName)) { + val uniqueId = clzDesc.theUniqueId.append("test", testName) + + new ScalaTestDescriptor(uniqueId, qualifiedName, locationOpt) + } else { + + val qualifier = suiteId.stripPrefix(clzDesc.suiteClass.getName) + // nested test case, demands a qualified name + val uniqueId = clzDesc.theUniqueId + .append("nested-class", qualifier) + .append("test", testName) + + new ScalaTestDescriptor(uniqueId, qualifiedName, locationOpt) } - private def createTestDescriptor(suiteId: String, suiteName: String, suiteClassName: Option[String], testName: String, locationOpt: Option[Location]): ScalaTestDescriptor = { - val uniqueId = clzDesc.theUniqueId.append("test", testName) - new ScalaTestDescriptor(uniqueId, testName, locationOpt) } override def apply(event: Event): Unit = { event match { - case TestStarting(ordinal, suiteName, suiteId, suiteClassName, testName, testText, formatter, location, rerunnable, payload, threadName, timeStamp) => - val testDesc = createTestDescriptor(suiteId, suiteName, suiteClassName, testName, location) + case TestStarting( + ordinal, + suiteName, + suiteId, + suiteClassName, + testName, + testText, + formatter, + location, + rerunnable, + payload, + threadName, + timeStamp + ) => + + val testDesc: ScalaTestDescriptor = + createTestDescriptor(suiteId, suiteName, suiteClassName, testName, location) + clzDesc.addChild(testDesc) listener.dynamicTestRegistered(testDesc) listener.executionStarted(testDesc) - case TestFailed(ordinal, message, suiteName, suiteId, suiteClassName, testName, testText, recordedEvents, analysis, throwable, duration, formatter, location, rerunnable, payload, threadName, timeStamp) => - val throwableOrNull = throwable.orNull + case TestFailed( + ordinal, + message, + suiteName, + suiteId, + suiteClassName, + testName, + testText, + recordedEvents, + analysis, + throwable, + duration, + formatter, + location, + rerunnable, + payload, + threadName, + timeStamp + ) => val testDesc = createTestDescriptor(suiteId, suiteName, suiteClassName, testName, location) + val throwableOrNull = throwable.orNull listener.executionFinished(testDesc, TestExecutionResult.failed(throwableOrNull)) - case TestSucceeded(ordinal, suiteName, suiteId, suiteClassName, testName, testText, recordedEvents, duration, formatter, location, rerunnable, payload, threadName, timeStamp) => + case TestSucceeded( + ordinal, + suiteName, + suiteId, + suiteClassName, + testName, + testText, + recordedEvents, + duration, + formatter, + location, + rerunnable, + payload, + threadName, + timeStamp + ) => val testDesc = createTestDescriptor(suiteId, suiteName, suiteClassName, testName, location) listener.executionFinished(testDesc, TestExecutionResult.successful()) - case TestIgnored(ordinal, suiteName, suiteId, suiteClassName, testName, testText, formatter, location, payload, threadName, timeStamp) => + case TestIgnored( + ordinal, + suiteName, + suiteId, + suiteClassName, + testName, + testText, + formatter, + location, + payload, + threadName, + timeStamp + ) => val testDesc = createTestDescriptor(suiteId, suiteName, suiteClassName, testName, location) listener.executionSkipped(testDesc, "Test ignored.") - case TestCanceled(ordering, message, suiteName, suiteId, suiteClassName, testName, testText, recordedEvents, throwable, duration, formatter, location, rerunner, payload, threadName, timeStamp) => + case TestCanceled( + ordinal, + message, + suiteName, + suiteId, + suiteClassName, + testName, + testText, + recordedEvents, + throwable, + duration, + formatter, + location, + rerunner, + payload, + threadName, + timeStamp + ) => val testDesc = createTestDescriptor(suiteId, suiteName, suiteClassName, testName, location) - listener.executionSkipped(testDesc, throwable.map(t => "Test canceled: " + t.getMessage).getOrElse("Test canceled.")) - - case TestPending(ordinal, suiteName, suiteId, suiteClassName, testName, testText, recordedEvents, duration, formatter, location, payload, threadName, timeStamp) => + listener.executionSkipped( + testDesc, + throwable.map(t => "Test canceled: " + t.getMessage).getOrElse("Test canceled.") + ) + + case TestPending( + ordinal, + suiteName, + suiteId, + suiteClassName, + testName, + testText, + recordedEvents, + duration, + formatter, + location, + payload, + threadName, + timeStamp + ) => val testDesc = createTestDescriptor(suiteId, suiteName, suiteClassName, testName, location) listener.executionSkipped(testDesc, "Test pending.") - case SuiteAborted(ordinal, message, suiteName, suiteId, suiteClassName, throwable, duration, formatter, location, rerunnable, payload, threadName, timeStamp) => + case SuiteAborted( + ordinal, + message, + suiteName, + suiteId, + suiteClassName, + throwable, + duration, + formatter, + location, + rerunnable, + payload, + threadName, + timeStamp + ) => val throwableOrNull = throwable.orNull listener.executionFinished(clzDesc, TestExecutionResult.aborted(throwableOrNull)) - case RunAborted(ordinal, message, throwable, duration, summary, formatter, location, payload, threadName, timeStamp) => + case RunAborted( + ordinal, + message, + throwable, + duration, + summary, + formatter, + location, + payload, + threadName, + timeStamp + ) => val throwableOrNull = throwable.orNull listener.executionFinished(engineDesc, TestExecutionResult.aborted(throwableOrNull)) diff --git a/src/main/scala/org/scalatestplus/junit5/JUnitExecutionListener.scala b/src/main/scala/org/scalatestplus/junit5/JUnitExecutionListener.scala index 7ed1caf..83ab428 100644 --- a/src/main/scala/org/scalatestplus/junit5/JUnitExecutionListener.scala +++ b/src/main/scala/org/scalatestplus/junit5/JUnitExecutionListener.scala @@ -15,24 +15,27 @@ */ package org.scalatestplus.junit5; +import org.junit.platform.engine.TestExecutionResult import org.junit.platform.launcher.{TestExecutionListener, TestIdentifier} -import org.junit.platform.engine.{EngineExecutionListener, TestDescriptor, TestExecutionResult} -import org.scalatest.{Reporter, StatefulStatus, Tracker} -import org.scalatest.events.{MotionToSuppress, SeeStackDepthException, TestFailed, TestIgnored, TestStarting, TestSucceeded, TopOfMethod} +import org.scalatest.events._ import org.scalatest.exceptions.PayloadField -import JUnitHelper.getIndentedTextForTest +import org.scalatest.{Reporter, StatefulStatus, Tracker} +import org.scalatestplus.junit5.JUnitHelper.getIndentedTextForTest -import java.util.Collections -import java.util.HashSet +import java.util.{Collections, HashSet} import java.util.regex.Pattern -private[junit5] class JUnitExecutionListener(report: Reporter, - config: Map[String, Any], - theTracker: Tracker, - status: StatefulStatus) extends TestExecutionListener { +private[junit5] class JUnitExecutionListener( + report: Reporter, + config: Map[String, Any], + theTracker: Tracker, + status: StatefulStatus +) extends TestExecutionListener { val failedTests = Collections.synchronizedSet(new HashSet[String]) - def getTopOfMethod(className: String, methodName: String) = Some(TopOfMethod(className, "public void " + className + "." + methodName + "()")) + def getTopOfMethod(className: String, methodName: String) = Some( + TopOfMethod(className, "public void " + className + "." + methodName + "()") + ) override def executionFinished(testIdentifier: TestIdentifier, testExecutionResult: TestExecutionResult): Unit = { if (testIdentifier.isTest) { @@ -41,10 +44,22 @@ private[junit5] class JUnitExecutionListener(report: Reporter, if (testExecutionResult.getStatus == TestExecutionResult.Status.SUCCESSFUL) { // success val formatter = getIndentedTextForTest(testName, 1, true) - report(TestSucceeded(theTracker.nextOrdinal(), testClassName, testClass, Some(testClass), testName, testName, Vector.empty, None, Some(formatter), getTopOfMethod(testClass, testName))) + report( + TestSucceeded( + theTracker.nextOrdinal(), + testClassName, + testClass, + Some(testClass), + testName, + testName, + Vector.empty, + None, + Some(formatter), + getTopOfMethod(testClass, testName) + ) + ) // TODO: can I add a duration? - } - else { + } else { // fail or aborted failedTests.add(testIdentifier.getDisplayName) val throwable = Option(testExecutionResult.getThrowable.orElseGet(null)) @@ -57,7 +72,25 @@ private[junit5] class JUnitExecutionListener(report: Reporter, case _ => None } - report(TestFailed(theTracker.nextOrdinal(), message, testClassName, testClass, Some(testClass), testName, testName, Vector.empty, Vector.empty, throwable, None, Some(formatter), Some(SeeStackDepthException), None, payload)) + report( + TestFailed( + theTracker.nextOrdinal(), + message, + testClassName, + testClass, + Some(testClass), + testName, + testName, + Vector.empty, + Vector.empty, + throwable, + None, + Some(formatter), + Some(SeeStackDepthException), + None, + payload + ) + ) // TODO: can I add a duration? status.setFailed() } @@ -70,7 +103,18 @@ private[junit5] class JUnitExecutionListener(report: Reporter, parseTestDescription(testIdentifier.getUniqueId) val formatter = getIndentedTextForTest(testName, 1, true) // TODO: What to do with reason? - report(TestIgnored(theTracker.nextOrdinal(), testClassName, testClass, Some(testClass), testName, testName, Some(formatter), getTopOfMethod(testClass, testName))) + report( + TestIgnored( + theTracker.nextOrdinal(), + testClassName, + testClass, + Some(testClass), + testName, + testName, + Some(formatter), + getTopOfMethod(testClass, testName) + ) + ) } } @@ -79,23 +123,35 @@ private[junit5] class JUnitExecutionListener(report: Reporter, val (testName, testClass, testClassName) = parseTestDescription(testIdentifier.getUniqueId) - report(TestStarting(theTracker.nextOrdinal(), testClassName, testClass, Some(testClass), testName, testName, Some(MotionToSuppress), getTopOfMethod(testClass, testName))) + report( + TestStarting( + theTracker.nextOrdinal(), + testClassName, + testClass, + Some(testClass), + testName, + testName, + Some(MotionToSuppress), + getTopOfMethod(testClass, testName) + ) + ) } } - //private val TEST_DESCRIPTION_PATTERN = Pattern.compile("""^(.*)\((.*)\)""") + // private val TEST_DESCRIPTION_PATTERN = Pattern.compile("""^(.*)\((.*)\)""") private val TEST_DESCRIPTION_PATTERN = Pattern.compile("""\[(.*)\]/\[class:(.*)\]/\[method:(.*)\(\)\]""") // ###uniqueId: [engine:junit-jupiter]/[class:org.scalatestplus.junit.helpers5.HappySuite]/[method:verifySomething()] - private def parseTestDescription(displayName: String): - (String, String, String) = { + private def parseTestDescription(displayName: String): (String, String, String) = { val matcher = TEST_DESCRIPTION_PATTERN.matcher(displayName) if (!matcher.find()) - throw new RuntimeException("unexpected displayName [" + - displayName + "]") + throw new RuntimeException( + "unexpected displayName [" + + displayName + "]" + ) val testName = matcher.group(3) val testClass = matcher.group(2) diff --git a/src/main/scala/org/scalatestplus/junit5/JUnitHelper.scala b/src/main/scala/org/scalatestplus/junit5/JUnitHelper.scala index ee7e8fc..63c84ec 100644 --- a/src/main/scala/org/scalatestplus/junit5/JUnitHelper.scala +++ b/src/main/scala/org/scalatestplus/junit5/JUnitHelper.scala @@ -40,7 +40,7 @@ private[junit5] object JUnitHelper { else Map.empty[String, Set[String]] - mergeMap[String, Set[String]](List(tags, autoTestTags)) ( _ ++ _ ) + mergeMap[String, Set[String]](List(tags, autoTestTags))(_ ++ _) } def getIndentedTextForTest(testText: String, level: Int, includeIcon: Boolean) = { @@ -49,8 +49,7 @@ private[junit5] object JUnitHelper { if (includeIcon) { val testSucceededIcon = Resources.testSucceededIconChar() (" " * (if (level == 0) 0 else (level - 1))) + Resources.iconPlusShortName(testSucceededIcon, decodedTestText) - } - else { + } else { (" " * level) + decodedTestText } IndentedText(formattedText, decodedTestText, level) @@ -62,8 +61,7 @@ private[junit5] object JUnitHelper { val constructor = clazz.getConstructor(new Array[java.lang.Class[_]](0): _*) Modifier.isPublic(constructor.getModifiers) - } - catch { + } catch { case nsme: NoSuchMethodException => false } } diff --git a/src/main/scala/org/scalatestplus/junit5/JUnitSuite.scala b/src/main/scala/org/scalatestplus/junit5/JUnitSuite.scala index a9949e8..0619a67 100644 --- a/src/main/scala/org/scalatestplus/junit5/JUnitSuite.scala +++ b/src/main/scala/org/scalatestplus/junit5/JUnitSuite.scala @@ -15,54 +15,41 @@ */ package org.scalatestplus.junit5; -/** - * A suite of tests that can be run with either JUnit or ScalaTest. This class allows you to write JUnit 5 tests - * with ScalaTest's more concise assertion syntax as well as JUnit's assertions (assertEquals, etc.). - * You create tests by defining methods that are annotated with Test, and can create fixtures with - * methods annotated with Before and After. For example: - * - *
- * import org.junit.jupiter.api.{BeforeEach, Test}
- * import org.scalatestplus.junit5.JUnitSuite
- * import scala.collection.mutable.ListBuffer
- *
- * class TwoSuite extends JUnitSuite {
- *
- *   var sb: StringBuilder = _
- *   var lb: ListBuffer[String] = _
- *
- *   @BeforeEach def initialize() {
- *     sb = new StringBuilder("ScalaTest is ")
- *     lb = new ListBuffer[String]
- *   }
- *
- *   @Test def verifyEasy() {
- *     sb.append("easy!")
- *     assert(sb.toString === "ScalaTest is easy!")
- *     assert(lb.isEmpty)
- *     lb += "sweet"
- *   }
- *
- *   @Test def verifyFun() {
- *     sb.append("fun!")
- *     assert(sb.toString === "ScalaTest is fun!")
- *     assert(lb.isEmpty)
- *   }
- *
- * }
- * 
- * - *

- * This version of JUnitSuite was tested with JUnit version 5.13. - *

- * - *

- * Instances of this class are not thread safe. - *

- * - * @author Bill Venners - * @author Daniel Watson - * @author Joel Neely - * @author Chua Chee Seng - */ +/** A suite of tests that can be run with either JUnit or ScalaTest. This class allows you to write JUnit 5 tests with + * ScalaTest's more concise assertion syntax as well as JUnit's assertions (assertEquals, etc.). You + * create tests by defining methods that are annotated with Test, and can create fixtures with methods + * annotated with Before and After. For example: + * + *
 import org.junit.jupiter.api.{BeforeEach, Test} import org.scalatestplus.junit5.JUnitSuite
+  * import scala.collection.mutable.ListBuffer
+  *
+  * class TwoSuite extends JUnitSuite {
+  *
+  * var sb: StringBuilder = _ var lb: ListBuffer[String] = _
+  *
+  * @BeforeEach
+  *   def initialize() { sb = new StringBuilder("ScalaTest is ") lb = new ListBuffer[String] }
+  *
+  * @Test
+  *   def verifyEasy() { sb.append("easy!") assert(sb.toString === "ScalaTest is easy!") assert(lb.isEmpty) lb +=
+  *   "sweet" }
+  *
+  * @Test
+  *   def verifyFun() { sb.append("fun!") assert(sb.toString === "ScalaTest is fun!") assert(lb.isEmpty) }
+  *
+  * } 
+ * + *

This version of JUnitSuite was tested with JUnit version 5.13.

+ * + *

Instances of this class are not thread safe.

+ * + * @author + * Bill Venners + * @author + * Daniel Watson + * @author + * Joel Neely + * @author + * Chua Chee Seng + */ class JUnitSuite extends JUnitSuiteLike diff --git a/src/main/scala/org/scalatestplus/junit5/JUnitSuiteLike.scala b/src/main/scala/org/scalatestplus/junit5/JUnitSuiteLike.scala index 885f597..943f5c5 100644 --- a/src/main/scala/org/scalatestplus/junit5/JUnitSuiteLike.scala +++ b/src/main/scala/org/scalatestplus/junit5/JUnitSuiteLike.scala @@ -25,107 +25,92 @@ import org.junit.platform.engine.discovery.DiscoverySelectors.{selectClass, sele import collection.immutable.TreeSet -/** - * Implementation trait for class JUnitSuite, which represents - * a suite of tests that can be run with either JUnit 5 or ScalaTest. - * - *

- * JUnitSuite is a class, not a - * trait, to minimize compile time given there is a slight compiler overhead to - * mixing in traits compared to extending classes. If you need to mix the - * behavior of JUnitSuite into some other class, you can use this - * trait instead, because class JUnitSuite does nothing more than - * extend this trait. - *

- * - *

- * See the documentation of the class for a detailed - * overview of JUnitSuite. - *

- * - * @author Bill Venners - * @author Chua Chee Seng - */ +/** Implementation trait for class JUnitSuite, which represents a suite of tests that can be run with + * either JUnit 5 or ScalaTest. + * + *

JUnitSuite is a class, not a trait, to minimize compile time given + * there is a slight compiler overhead to mixing in traits compared to extending classes. If you need to mix the + * behavior of JUnitSuite into some other class, you can use this trait instead, because class + * JUnitSuite does nothing more than extend this trait.

+ * + *

See the documentation of the class for a detailed overview of + * JUnitSuite.

+ * + * @author + * Bill Venners + * @author + * Chua Chee Seng + */ trait JUnitSuiteLike extends Suite with AssertionsForJUnit { thisSuite => // This is volatile, because who knows what Thread JUnit will fire through this. @volatile private var theTracker = new Tracker - /** - * Throws UnsupportedOperationException, because this method is unused by this - * trait, given this trait's run method delegates to JUnit to run - * its tests. - * - *

- * The main purpose of this method implementation is to render a compiler error an attempt - * to mix in a trait that overrides runNestedSuites. Because this - * trait does not actually use runNestedSuites, the attempt to mix - * in behavior would very likely not work. - *

- * - * @param args the Args for this run - * - * @throws UnsupportedOperationException always. - */ + /** Throws UnsupportedOperationException, because this method is unused by this trait, given this trait's + * run method delegates to JUnit to run its tests. + * + *

The main purpose of this method implementation is to render a compiler error an attempt to mix in a trait that + * overrides runNestedSuites. Because this trait does not actually use runNestedSuites, the + * attempt to mix in behavior would very likely not work.

+ * + * @param args + * the Args for this run + * + * @throws UnsupportedOperationException + * always. + */ override final protected def runNestedSuites(args: Args): Status = { throw new UnsupportedOperationException } - /** - * Throws UnsupportedOperationException, because this method is unused by this - * trait, given this trait's run method delegates to JUnit to run - * its tests. - * - *

- * The main purpose of this method implementation is to render a compiler error an attempt - * to mix in a trait that overrides runTests. Because this - * trait does not actually use runTests, the attempt to mix - * in behavior would very likely not work. - *

- * - * @param testName an optional name of one test to run. If None, all relevant tests should be run. - * I.e., None acts like a wildcard that means run all relevant tests in this Suite. - * @param args the Args for this run - * - * @throws UnsupportedOperationException always. - */ + /** Throws UnsupportedOperationException, because this method is unused by this trait, given this trait's + * run method delegates to JUnit to run its tests. + * + *

The main purpose of this method implementation is to render a compiler error an attempt to mix in a trait that + * overrides runTests. Because this trait does not actually use runTests, the attempt to + * mix in behavior would very likely not work.

+ * + * @param testName + * an optional name of one test to run. If None, all relevant tests should be run. I.e., + * None acts like a wildcard that means run all relevant tests in this Suite. + * @param args + * the Args for this run + * + * @throws UnsupportedOperationException + * always. + */ override protected final def runTests(testName: Option[String], args: Args): Status = { throw new UnsupportedOperationException } - /** - * Throws UnsupportedOperationException, because this method is unused by this - * trait, given this traits's run method delegates to JUnit to run - * its tests. - * - *

- * The main purpose of this method implementation is to render a compiler error an attempt - * to mix in a trait that overrides runTest. Because this - * trait does not actually use runTest, the attempt to mix - * in behavior would very likely not work. - *

- * - * @param testName the name of one test to run. - * @param args the Args for this run - * - * @throws UnsupportedOperationException always. - */ + /** Throws UnsupportedOperationException, because this method is unused by this trait, given this + * traits's run method delegates to JUnit to run its tests. + * + *

The main purpose of this method implementation is to render a compiler error an attempt to mix in a trait that + * overrides runTest. Because this trait does not actually use runTest, the attempt to mix + * in behavior would very likely not work.

+ * + * @param testName + * the name of one test to run. + * @param args + * the Args for this run + * + * @throws UnsupportedOperationException + * always. + */ override protected final def runTest(testName: String, args: Args): Status = { throw new UnsupportedOperationException } - /** - * Returns the set of test names that will be executed by JUnit when run is invoked - * on an instance of this class, or the instance is passed directly to JUnit for running. - * - *

- * The iterator obtained by invoking elements on this - * returned Set will produce the test names in their natural order, as determined by String's - * compareTo method. Nevertheless, this method is not consulted by JUnit when it - * runs the tests, and JUnit may run the tests in any order. - *

- */ + /** Returns the set of test names that will be executed by JUnit when run is invoked on an instance of + * this class, or the instance is passed directly to JUnit for running. + * + *

The iterator obtained by invoking elements on this returned Set will produce the + * test names in their natural order, as determined by String's compareTo method. + * Nevertheless, this method is not consulted by JUnit when it runs the tests, and JUnit may run the tests in any + * order.

+ */ override def testNames: Set[String] = { // TODO: Check to see if JUnit discovers static methods, private methods, etc. @@ -150,27 +135,25 @@ trait JUnitSuiteLike extends Suite with AssertionsForJUnit { thisSuite => TreeSet[String]() ++ testNameArray } - /** - * Returns the number of tests expected to be run by JUnit when run is invoked - * on this JUnitSuite. - * - *

- * If tagsToInclude in the passed Filter is defined, this class's - * implementation of this method returns 0. Else this class's implementation of this method - * returns the size of the set returned by testNames on the current instance, - * less the number of tests that were annotated with org.junit.Ignore. - *

- * - * @param filter a Filter for test filtering - * @return number of expected test count - */ + /** Returns the number of tests expected to be run by JUnit when run is invoked on this + * JUnitSuite. + * + *

If tagsToInclude in the passed Filter is defined, this class's implementation of + * this method returns 0. Else this class's implementation of this method returns the size of the set returned by + * testNames on the current instance, less the number of tests that were annotated with + * org.junit.Ignore.

+ * + * @param filter + * a Filter for test filtering + * @return + * number of expected test count + */ override def expectedTestCount(filter: Filter) = if (filter.tagsToInclude.isDefined) 0 else (testNames.size - tags.size) - /** - * Overrides to return just tests that have org.junit.Ignore on them, but calls it org.scalatest.Ignore. - * It also auto-tags suite level annotation. - */ + /** Overrides to return just tests that have org.junit.Ignore on them, but calls it org.scalatest.Ignore. It also + * auto-tags suite level annotation. + */ override def tags: Map[String, Set[String]] = { val elements = @@ -183,15 +166,18 @@ trait JUnitSuiteLike extends Suite with AssertionsForJUnit { thisSuite => private def getMethodForJUnitTestName(testName: String) = getClass.getMethod(testName, new Array[Class[_]](0): _*) - private def hasDisabledTag(testName: String) = getMethodForJUnitTestName(testName).getAnnotation(classOf[org.junit.jupiter.api.Disabled]) != null + private def hasDisabledTag(testName: String) = + getMethodForJUnitTestName(testName).getAnnotation(classOf[org.junit.jupiter.api.Disabled]) != null - /** - * Overrides to retrieve suite and test tags from annotations. - * - * @param testName the name of the test for which to return a TestData instance - * @param theConfigMap the config map to include in the returned TestData - * @return a TestData instance for the specified test, which includes the specified config map - */ + /** Overrides to retrieve suite and test tags from annotations. + * + * @param testName + * the name of the test for which to return a TestData instance + * @param theConfigMap + * the config map to include in the returned TestData + * @return + * a TestData instance for the specified test, which includes the specified config map + */ override def testDataFor(testName: String, theConfigMap: ConfigMap = ConfigMap.empty): TestData = { val suiteTags = for { a <- this.getClass.getAnnotations @@ -204,8 +190,7 @@ trait JUnitSuiteLike extends Suite with AssertionsForJUnit { thisSuite => Set("org.scalatest.Ignore") else Set.empty[String] - } - catch { + } catch { case e: IllegalArgumentException => Set.empty[String] } new TestData { @@ -218,15 +203,17 @@ trait JUnitSuiteLike extends Suite with AssertionsForJUnit { thisSuite => } } - /** - * Overrides to use JUnit 5 to run the test(s). - * - * @param testName an optional name of one test to run. If None, all relevant tests should be run. - * I.e., None acts like a wildcard that means run all relevant tests in this Suite. - * @param args the Args for this run - * @return a Status object that indicates when all tests and nested suites started by this method have completed, and whether or not a failure occurred. - * - */ + /** Overrides to use JUnit 5 to run the test(s). + * + * @param testName + * an optional name of one test to run. If None, all relevant tests should be run. I.e., + * None acts like a wildcard that means run all relevant tests in this Suite. + * @param args + * the Args for this run + * @return + * a Status object that indicates when all tests and nested suites started by this method have + * completed, and whether or not a failure occurred. + */ override def run(testName: Option[String], args: Args): Status = { import args._ @@ -249,12 +236,15 @@ trait JUnitSuiteLike extends Suite with AssertionsForJUnit { thisSuite => val listener = new JUnitExecutionListener(reporter, configMap, tracker, status) val req = request() .selectors( - testName.map { tn => - if (testNames.contains(tn)) - selectMethod(testClass, tn) - else - throw new IllegalArgumentException(Resources.testNotFound(testName)) - }.getOrElse(selectClass(testClass))) + testName + .map { tn => + if (testNames.contains(tn)) + selectMethod(testClass, tn) + else + throw new IllegalArgumentException(Resources.testNotFound(testName)) + } + .getOrElse(selectClass(testClass)) + ) .build() val launcher = LauncherFactory.create() launcher.execute(req, listener) diff --git a/src/main/scala/org/scalatestplus/junit5/JUnitTestFailedError.scala b/src/main/scala/org/scalatestplus/junit5/JUnitTestFailedError.scala index c312fa1..e199b18 100644 --- a/src/main/scala/org/scalatestplus/junit5/JUnitTestFailedError.scala +++ b/src/main/scala/org/scalatestplus/junit5/JUnitTestFailedError.scala @@ -23,57 +23,57 @@ import org.scalactic.source import org.scalatest.exceptions.StackDepthException import org.scalactic.ArrayHelper.deep -/** - * Exception that indicates a test failed in JUnit 5. - * - *

- * The purpose of this exception is to encapsulate the same stack depth information provided by - * TestFailedException, which is used - * when running with ScalaTest, but be reported as - * a failure not an error when running with JUnit. - * The stack depth information indicates which line of test code failed, so that when running - * with ScalaTest information can be presented to - * the user that makes it quick to find the failing line of test code. (In other words, when - * running with ScalaTest the user need not scan through the stack trace to find the correct filename - * and line number of the failing test.) - *

- * - *

- * JUnit distinguishes between failures and errors. - * If a test fails because of a failed assertion, that is considered a failure in JUnit. If a test - * fails for any other reason, either the test code or the application being tested threw an unexpected - * exception, that is considered an error in JUnit. This class differs from - * TestFailedException in that it extends - * org.opentest4j.AssertionFailedError. Instances of this class are thrown by the - * assertions provided by AssertionsForJUnit. - *

- * - *

- * The way JUnit 3 (JUnit 3.8 and earlier releases) decided whether an exception represented a failure or error - * is that only thrown junit.framework.AssertionFailedErrors were considered failures. Any other - * exception type was considered an error. The exception type thrown by the JUnit 3 assertion methods declared - * in junit.framework.Assert (such as assertEquals, assertTrue, - * and fail) was, therefore, AssertionFailedError. In JUnit 4, junit.framework.AssertionFailedError - * was made to extend java.lang.AssertionError, and the distinction between failures and errors - * was essentially dropped. In JUnit 5, org.opentest4j.AssertionFailedError is used instead as common - * base class for test-related AssertionErrors. - *

- * - * @param message an optional detail message for this TestFailedException. - * @param cause an optional cause, the Throwable that caused this TestFailedException to be thrown. - * @param failedCodeStackDepth the depth in the stack trace of this exception at which the line of test code that failed resides. - * @param payload an optional payload, which ScalaTest will include in a resulting JUnitTestFailedError event - * - * @throws NullArgumentException if either message or cause is null, or Some(null). - * - * @author Bill Venners - */ +/** Exception that indicates a test failed in JUnit 5. + * + *

The purpose of this exception is to encapsulate the same stack depth information provided by TestFailedException, which is used when running with + * ScalaTest, but be reported as a failure not an error when running with JUnit. The stack depth information indicates + * which line of test code failed, so that when running with ScalaTest information can be presented to the user that + * makes it quick to find the failing line of test code. (In other words, when running with ScalaTest the user need not + * scan through the stack trace to find the correct filename and line number of the failing test.)

+ * + *

JUnit distinguishes between failures and errors. If a test fails because of a failed + * assertion, that is considered a failure in JUnit. If a test fails for any other reason, either the test + * code or the application being tested threw an unexpected exception, that is considered an error in JUnit. + * This class differs from TestFailedException in + * that it extends org.opentest4j.AssertionFailedError. Instances of this class are thrown by the + * assertions provided by AssertionsForJUnit.

+ * + *

The way JUnit 3 (JUnit 3.8 and earlier releases) decided whether an exception represented a failure or error is + * that only thrown junit.framework.AssertionFailedErrors were considered failures. Any other exception + * type was considered an error. The exception type thrown by the JUnit 3 assertion methods declared in + * junit.framework.Assert (such as assertEquals, assertTrue, and + * fail) was, therefore, AssertionFailedError. In JUnit 4, + * junit.framework.AssertionFailedError was made to extend java.lang.AssertionError, and the + * distinction between failures and errors was essentially dropped. In JUnit 5, + * org.opentest4j.AssertionFailedError is used instead as common base class for test-related + * AssertionErrors.

+ * + * @param message + * an optional detail message for this TestFailedException. + * @param cause + * an optional cause, the Throwable that caused this TestFailedException to be thrown. + * @param failedCodeStackDepth + * the depth in the stack trace of this exception at which the line of test code that failed resides. + * @param payload + * an optional payload, which ScalaTest will include in a resulting JUnitTestFailedError event + * + * @throws NullArgumentException + * if either message or cause is null, or Some(null). + * + * @author + * Bill Venners + */ class JUnitTestFailedError( - val message: Option[String], - val cause: Option[Throwable], - val posOrStackDepth: Either[source.Position, Int], - val payload: Option[Any] - ) extends AssertionFailedError(if (message.isDefined) message.get else "") with StackDepth with ModifiableMessage[JUnitTestFailedError] with PayloadField with ModifiablePayload[JUnitTestFailedError] { + val message: Option[String], + val cause: Option[Throwable], + val posOrStackDepth: Either[source.Position, Int], + val payload: Option[Any] +) extends AssertionFailedError(if (message.isDefined) message.get else "") + with StackDepth + with ModifiableMessage[JUnitTestFailedError] + with PayloadField + with ModifiablePayload[JUnitTestFailedError] { // TODO: CHange above to a message.getOrElse(""), and same in other exceptions most likely // TODO: Possibly change stack depth to stackDepthFun like in TFE, consider messageFun like in TDE @@ -81,31 +81,31 @@ class JUnitTestFailedError( requireNonNull(message, cause) message match { case Some(null) => throw new NullArgumentException("message was a Some(null)") - case _ => + case _ => } cause match { case Some(null) => throw new NullArgumentException("cause was a Some(null)") - case _ => + case _ => } if (cause.isDefined) super.initCause(cause.get) def this( - message: Option[String], - cause: Option[Throwable], - pos: source.Position, - payload: Option[Any] - ) = this(message, cause, Left(pos), payload) + message: Option[String], + cause: Option[Throwable], + pos: source.Position, + payload: Option[Any] + ) = this(message, cause, Left(pos), payload) // This is the olde general constructor def this( - message: Option[String], - cause: Option[Throwable], - failedCodeStackDepth: Int, - payload: Option[Any] - ) = this(message, cause, Right(failedCodeStackDepth), payload) + message: Option[String], + cause: Option[Throwable], + failedCodeStackDepth: Int, + payload: Option[Any] + ) = this(message, cause, Right(failedCodeStackDepth), payload) val position: Option[source.Position] = posOrStackDepth.left.toOption @@ -118,32 +118,33 @@ class JUnitTestFailedError( } /* - * Throws IllegalStateException, because StackDepthExceptions are - * always initialized with a cause passed to the constructor of superclass - */ - override final def initCause(throwable: Throwable): Throwable = { + * Throws IllegalStateException, because StackDepthExceptions are + * always initialized with a cause passed to the constructor of superclass + */ + override final def initCause(throwable: Throwable): Throwable = { if (throwable != null && cause.isDefined) throw new IllegalStateException else - super.initCause(throwable) + super.initCause(throwable) } - /** - * Create a JUnitTestFailedError with specified stack depth and no detail message or cause. - * - * @param failedCodeStackDepth the depth in the stack trace of this exception at which the line of test code that failed resides. - * - */ + /** Create a JUnitTestFailedError with specified stack depth and no detail message or cause. + * + * @param failedCodeStackDepth + * the depth in the stack trace of this exception at which the line of test code that failed resides. + */ def this(failedCodeStackDepth: Int) = this(None, None, Right(failedCodeStackDepth), None) - /** - * Create a JUnitTestFailedError with a specified stack depth and detail message. - * - * @param message A detail message for this JUnitTestFailedError. - * @param failedCodeStackDepth the depth in the stack trace of this exception at which the line of test code that failed resides. - * - * @throws NullArgumentException if message is null. - */ + /** Create a JUnitTestFailedError with a specified stack depth and detail message. + * + * @param message + * A detail message for this JUnitTestFailedError. + * @param failedCodeStackDepth + * the depth in the stack trace of this exception at which the line of test code that failed resides. + * + * @throws NullArgumentException + * if message is null. + */ def this(message: String, failedCodeStackDepth: Int) = this( { @@ -155,16 +156,18 @@ class JUnitTestFailedError( None ) - /** - * Create a JUnitTestFailedError with the specified stack depth and cause. The - * message field of this exception object will be initialized to - * if (cause.getMessage == null) "" else cause.getMessage. - * - * @param cause the cause, the Throwable that caused this JUnitTestFailedError to be thrown. - * @param failedCodeStackDepth the depth in the stack trace of this exception at which the line of test code that failed resides. - * - * @throws NullArgumentException if cause is null. - */ + /** Create a JUnitTestFailedError with the specified stack depth and cause. The message + * field of this exception object will be initialized to if (cause.getMessage == null) "" else + * cause.getMessage. + * + * @param cause + * the cause, the Throwable that caused this JUnitTestFailedError to be thrown. + * @param failedCodeStackDepth + * the depth in the stack trace of this exception at which the line of test code that failed resides. + * + * @throws NullArgumentException + * if cause is null. + */ def this(cause: Throwable, failedCodeStackDepth: Int) = this( { @@ -176,27 +179,27 @@ class JUnitTestFailedError( None ) - /** - * Create a JUnitTestFailedError with the specified stack depth, detail - * message, and cause. - * - *

Note that the detail message associated with cause is - * not automatically incorporated in this throwable's detail - * message. - * - * @param message A detail message for this JUnitTestFailedError. - * @param cause the cause, the Throwable that caused this JUnitTestFailedError to be thrown. - * @param failedCodeStackDepth the depth in the stack trace of this exception at which the line of test code that failed resides. - * - * @throws NullArgumentException if either message or cause is null. - */ + /** Create a JUnitTestFailedError with the specified stack depth, detail message, and cause. + * + *

Note that the detail message associated with cause is not automatically incorporated in this + * throwable's detail message. + * + * @param message + * A detail message for this JUnitTestFailedError. + * @param cause + * the cause, the Throwable that caused this JUnitTestFailedError to be thrown. + * @param failedCodeStackDepth + * the depth in the stack trace of this exception at which the line of test code that failed resides. + * + * @throws NullArgumentException + * if either message or cause is null. + */ def this(message: String, cause: Throwable, failedCodeStackDepth: Int) = this( { requireNonNull(message) Some(message) - }, - { + }, { requireNonNull(cause) Some(cause) }, @@ -204,12 +207,11 @@ class JUnitTestFailedError( None ) - /** - * Returns an exception of class JUnitTestFailedError with failedExceptionStackDepth set to 0 and - * all frames above this stack depth severed off. This can be useful when working with tools (such as IDEs) that do not - * directly support ScalaTest. (Tools that directly support ScalaTest can use the stack depth information delivered - * in the StackDepth exceptions.) - */ + /** Returns an exception of class JUnitTestFailedError with failedExceptionStackDepth set to + * 0 and all frames above this stack depth severed off. This can be useful when working with tools (such as IDEs) + * that do not directly support ScalaTest. (Tools that directly support ScalaTest can use the stack depth information + * delivered in the StackDepth exceptions.) + */ def severedAtStackDepth: JUnitTestFailedError = { val truncated = getStackTrace.drop(failedCodeStackDepth) val e = new JUnitTestFailedError(message, cause, posOrStackDepth, payload) @@ -217,28 +219,26 @@ class JUnitTestFailedError( e } - /** - * Returns an instance of this exception's class, identical to this exception, - * except with the detail message option string replaced with the result of passing - * the current detail message to the passed function, fun. - * - * @param fun A function that, given the current optional detail message, will produce - * the modified optional detail message for the result instance of JUnitTestFailedError. - */ + /** Returns an instance of this exception's class, identical to this exception, except with the detail message option + * string replaced with the result of passing the current detail message to the passed function, fun. + * + * @param fun + * A function that, given the current optional detail message, will produce the modified optional detail message + * for the result instance of JUnitTestFailedError. + */ def modifyMessage(fun: Option[String] => Option[String]): JUnitTestFailedError = { val mod = new JUnitTestFailedError(fun(message), cause, posOrStackDepth, payload) mod.setStackTrace(getStackTrace) mod } - /** - * Returns an instance of this exception's class, identical to this exception, - * except with the payload option replaced with the result of passing - * the current payload option to the passed function, fun. - * - * @param fun A function that, given the current optional payload, will produce - * the modified optional payload for the result instance of JUnitTestFailedError. - */ + /** Returns an instance of this exception's class, identical to this exception, except with the payload option + * replaced with the result of passing the current payload option to the passed function, fun. + * + * @param fun + * A function that, given the current optional payload, will produce the modified optional payload for the result + * instance of JUnitTestFailedError. + */ def modifyPayload(fun: Option[Any] => Option[Any]): JUnitTestFailedError = { val currentPayload = payload val mod = new JUnitTestFailedError(message, cause, posOrStackDepth, fun(currentPayload)) @@ -246,39 +246,35 @@ class JUnitTestFailedError( mod } - /** - * Indicates whether this object can be equal to the passed object. - */ + /** Indicates whether this object can be equal to the passed object. + */ def canEqual(other: Any): Boolean = other.isInstanceOf[JUnitTestFailedError] - /** - * Indicates whether this object is equal to the passed object. If the passed object is - * a JUnitTestFailedError, equality requires equal message, - * cause, and failedCodeStackDepth fields, as well as equal - * return values of getStackTrace. - */ + /** Indicates whether this object is equal to the passed object. If the passed object is a + * JUnitTestFailedError, equality requires equal message, cause, and + * failedCodeStackDepth fields, as well as equal return values of getStackTrace. + */ override def equals(other: Any): Boolean = other match { case that: JUnitTestFailedError => (that canEqual this) && - message == that.message && - cause == that.cause && - failedCodeStackDepth == that.failedCodeStackDepth && - deep(getStackTrace) == deep(that.getStackTrace) + message == that.message && + cause == that.cause && + failedCodeStackDepth == that.failedCodeStackDepth && + deep(getStackTrace) == deep(that.getStackTrace) case _ => false } - /** - * Returns a hash code value for this object. - */ + /** Returns a hash code value for this object. + */ override def hashCode: Int = 41 * ( 41 * ( 41 * ( 41 + message.hashCode - ) + cause.hashCode - ) + failedCodeStackDepth.hashCode - ) + getStackTrace.hashCode + ) + cause.hashCode + ) + failedCodeStackDepth.hashCode + ) + getStackTrace.hashCode private def isMatch(ele: StackTraceElement, pos: source.Position): Boolean = ele.getFileName == pos.fileName && ele.getLineNumber == pos.lineNumber diff --git a/src/main/scala/org/scalatestplus/junit5/ScalaTestClassDescriptor.scala b/src/main/scala/org/scalatestplus/junit5/ScalaTestClassDescriptor.scala index b6466dc..c710699 100644 --- a/src/main/scala/org/scalatestplus/junit5/ScalaTestClassDescriptor.scala +++ b/src/main/scala/org/scalatestplus/junit5/ScalaTestClassDescriptor.scala @@ -22,20 +22,27 @@ import org.scalatest.{Suite, TagAnnotation} import scala.collection.JavaConverters._ import java.util.Optional -/** - * TestDescriptor for ScalaTest suite. - * - * @param parent The parent descriptor. - * @param theUniqueId The unique ID. - * @param suiteClass The class of the ScalaTest suite. - */ -class ScalaTestClassDescriptor(parent: TestDescriptor, val theUniqueId: UniqueId, val suiteClass: Class[_], autoAddTestChildren: Boolean) extends AbstractTestDescriptor(theUniqueId, suiteClass.getName, ClassSource.from(suiteClass)) { +/** TestDescriptor for ScalaTest suite. + * + * @param parent + * The parent descriptor. + * @param theUniqueId + * The unique ID. + * @param suiteClass + * The class of the ScalaTest suite. + */ +class ScalaTestClassDescriptor( + parent: TestDescriptor, + val theUniqueId: UniqueId, + val suiteClass: Class[_], + autoAddTestChildren: Boolean +) extends AbstractTestDescriptor(theUniqueId, suiteClass.getName, ClassSource.from(suiteClass)) { - /** - * Suite instance that will be executed if this class descriptor is selected. - */ + /** Suite instance that will be executed if this class descriptor is selected. + */ lazy val suite: Suite = { - val canInstantiate = JUnitHelper.checkForPublicNoArgConstructor(suiteClass) && classOf[org.scalatest.Suite].isAssignableFrom(suiteClass) + val canInstantiate = JUnitHelper.checkForPublicNoArgConstructor(suiteClass) && classOf[org.scalatest.Suite] + .isAssignableFrom(suiteClass) require(canInstantiate, "Must pass an org.scalatest.Suite with a public no-arg constructor") suiteClass.newInstance.asInstanceOf[org.scalatest.Suite] } @@ -48,43 +55,46 @@ class ScalaTestClassDescriptor(parent: TestDescriptor, val theUniqueId: UniqueId addChild(testDesc) } - /** - * Type of this ScalaTestClassDescriptor. - * - * @return TestDescriptor.Type.CONTAINER - */ + /** Type of this ScalaTestClassDescriptor. + * + * @return + * TestDescriptor.Type.CONTAINER + */ override def getType: TestDescriptor.Type = TestDescriptor.Type.CONTAINER - /** - * Override mayRegisterTests to return true - * - * @return true - */ + /** Override mayRegisterTests to return true + * + * @return + * true + */ override def mayRegisterTests(): Boolean = true - /** - * Return ClassSource for the given suite class. - * - * @return ClassSource created from given suite class - */ + /** Return ClassSource for the given suite class. + * + * @return + * ClassSource created from given suite class + */ override def getSource: Optional[TestSource] = Optional.of(ClassSource.from(suiteClass)) - /** - * Get tags for this suite. - * - * @return Tags for this suite. - */ + /** Get tags for this suite. + * + * @return + * Tags for this suite. + */ override def getTags: java.util.Set[TestTag] = - suiteClass.getAnnotations.filter((a) => a.annotationType.isAnnotationPresent(classOf[TagAnnotation])).map((a) => TestTag.create(a.annotationType.getName)).toSet.asJava + suiteClass.getAnnotations + .filter((a) => a.annotationType.isAnnotationPresent(classOf[TagAnnotation])) + .map((a) => TestTag.create(a.annotationType.getName)) + .toSet + .asJava } -/** - * ScalaTestClassDescriptor companion object. - */ +/** ScalaTestClassDescriptor companion object. + */ object ScalaTestClassDescriptor { - /** - * Segment type for ScalaTestClassDescriptor, has the value of class - */ + + /** Segment type for ScalaTestClassDescriptor, has the value of class + */ val segmentType = "class" } diff --git a/src/main/scala/org/scalatestplus/junit5/ScalaTestDescriptor.scala b/src/main/scala/org/scalatestplus/junit5/ScalaTestDescriptor.scala index 32fefd6..3f19e21 100644 --- a/src/main/scala/org/scalatestplus/junit5/ScalaTestDescriptor.scala +++ b/src/main/scala/org/scalatestplus/junit5/ScalaTestDescriptor.scala @@ -15,7 +15,13 @@ */ package org.scalatestplus.junit5 -import org.junit.platform.engine.support.descriptor.{AbstractTestDescriptor, ClassSource, FilePosition, FileSource, MethodSource} +import org.junit.platform.engine.support.descriptor.{ + AbstractTestDescriptor, + ClassSource, + FilePosition, + FileSource, + MethodSource +} import org.junit.platform.engine.{TestDescriptor, TestSource, TestTag, UniqueId} import org.scalatest.TagAnnotation import org.scalatest.events._ @@ -25,59 +31,73 @@ import scala.collection.JavaConverters._ import java.io.File import java.util.Optional -/** - * TestDescriptor for a test in ScalaTest suite. - * - * @param theUniqueId The unique ID. - * @param suiteClass The display name for this test. - */ -class ScalaTestDescriptor(theUniqueId: UniqueId, displayName: String, locationOpt: Option[Location]) extends AbstractTestDescriptor(theUniqueId, displayName) { - /** - * Type of this ScalaTestDescriptor. - * - * @return TestDescriptor.Type.TEST - */ +/** TestDescriptor for a test in ScalaTest suite. + * + * @param theUniqueId + * The unique ID. + * @param suiteClass + * The display name for this test. + */ +class ScalaTestDescriptor(theUniqueId: UniqueId, displayName: String, locationOpt: Option[Location]) + extends AbstractTestDescriptor(theUniqueId, displayName) { + + /** Type of this ScalaTestDescriptor. + * + * @return + * TestDescriptor.Type.TEST + */ override def getType: TestDescriptor.Type = TestDescriptor.Type.TEST - /** - * Return TestSource for this test. It depends on Location reported from ScalaTest, if the location is TopOfClass, - * it returns a ClassSource. If the location is TopOfMethod, it returns a MethodSource. If the location is - * LineInFile, it returns a FileSource. If the location is SeeStackDepthException, it returns a ClassSource. - * - * @return One of TestSource subclass depending on location type. - */ + /** Return TestSource for this test. It depends on Location reported from ScalaTest, if the + * location is TopOfClass, it returns a ClassSource. If the location is + * TopOfMethod, it returns a MethodSource. If the location is LineInFile, it + * returns a FileSource. If the location is SeeStackDepthException, it returns a + * ClassSource. + * + * @return + * One of TestSource subclass depending on location type. + */ override def getSource: Optional[TestSource] = { Optional.ofNullable( - locationOpt.map { loc => - loc match { - case TopOfClass(className) => ClassSource.from(className) - case TopOfMethod(className, methodId) => - val openingBracketIdx = methodId.indexOf("(") - val closingBracketIdx = methodId.indexOf(")") - if (openingBracketIdx >= 0 && closingBracketIdx >= 0) { - val beforeOpeningBracket = methodId.substring(0, openingBracketIdx) - val lastSpaceIdx = beforeOpeningBracket.lastIndexOf(" ") - val methodNameWithClassName = if (lastSpaceIdx >= 0) beforeOpeningBracket.substring(lastSpaceIdx + 1) else beforeOpeningBracket - val methodNameLastDotIdx = methodNameWithClassName.lastIndexOf(".") - val methodName = if (methodNameLastDotIdx >= 0) methodNameWithClassName.substring(methodNameLastDotIdx + 1) else methodNameWithClassName - val methodArgs = methodId.substring(openingBracketIdx + 1, closingBracketIdx) - if (methodArgs.trim.isEmpty) MethodSource.from(className, methodName) else MethodSource.from(className, methodName, methodArgs) - } - else - MethodSource.from(className, methodId) + locationOpt + .map { loc => + loc match { + case TopOfClass(className) => ClassSource.from(className) + case TopOfMethod(className, methodId) => + val openingBracketIdx = methodId.indexOf("(") + val closingBracketIdx = methodId.indexOf(")") + if (openingBracketIdx >= 0 && closingBracketIdx >= 0) { + val beforeOpeningBracket = methodId.substring(0, openingBracketIdx) + val lastSpaceIdx = beforeOpeningBracket.lastIndexOf(" ") + val methodNameWithClassName = + if (lastSpaceIdx >= 0) beforeOpeningBracket.substring(lastSpaceIdx + 1) else beforeOpeningBracket + val methodNameLastDotIdx = methodNameWithClassName.lastIndexOf(".") + val methodName = + if (methodNameLastDotIdx >= 0) methodNameWithClassName.substring(methodNameLastDotIdx + 1) + else methodNameWithClassName + val methodArgs = methodId.substring(openingBracketIdx + 1, closingBracketIdx) + if (methodArgs.trim.isEmpty) MethodSource.from(className, methodName) + else MethodSource.from(className, methodName, methodArgs) + } else + MethodSource.from(className, methodId) - case LineInFile(lineNumber: Int, fileName: String, filePathname: Option[String]) => FileSource.from(new File(filePathname.getOrElse(fileName)), FilePosition.from(lineNumber)) - case SeeStackDepthException => ClassSource.from(theUniqueId.getSegments.get(1).getValue) // Let's just refer to the class for SeeStackDepthException + case LineInFile(lineNumber: Int, fileName: String, filePathname: Option[String]) => + FileSource.from(new File(filePathname.getOrElse(fileName)), FilePosition.from(lineNumber)) + case SeeStackDepthException => + ClassSource.from( + theUniqueId.getSegments.get(1).getValue + ) // Let's just refer to the class for SeeStackDepthException + } } - }.getOrElse(null) + .getOrElse(null) ) } - /** - * Get tags for this test. - * - * @return Tags for this test. - */ + /** Get tags for this test. + * + * @return + * Tags for this test. + */ override def getTags: java.util.Set[TestTag] = { val parentOpt = getParent if (parentOpt.isPresent) { @@ -85,11 +105,9 @@ class ScalaTestDescriptor(theUniqueId: UniqueId, displayName: String, locationOp if (parent.isInstanceOf[ScalaTestClassDescriptor]) { val parentClassDescriptor = parent.asInstanceOf[ScalaTestClassDescriptor] parentClassDescriptor.suite.tags.get(displayName).getOrElse(Set.empty).map(t => TestTag.create(t)).asJava - } - else + } else Set.empty.asJava - } - else + } else Set.empty.asJava } } diff --git a/src/main/scala/org/scalatestplus/junit5/ScalaTestEngine.scala b/src/main/scala/org/scalatestplus/junit5/ScalaTestEngine.scala index c8e74f1..05df11e 100644 --- a/src/main/scala/org/scalatestplus/junit5/ScalaTestEngine.scala +++ b/src/main/scala/org/scalatestplus/junit5/ScalaTestEngine.scala @@ -16,11 +16,23 @@ package org.scalatestplus.junit5 import org.junit.platform.commons.support.ReflectionSupport -import org.junit.platform.engine.discovery.{ClassSelector, ClasspathRootSelector, ModuleSelector, PackageSelector, UniqueIdSelector} +import org.junit.platform.engine.discovery.{ + ClassSelector, + ClasspathRootSelector, + ModuleSelector, + PackageSelector, + UniqueIdSelector +} import org.junit.platform.engine.support.descriptor.EngineDescriptor import org.junit.platform.engine.support.discovery.SelectorResolver.{Match, Resolution} import org.junit.platform.engine.support.discovery.{EngineDiscoveryRequestResolver, SelectorResolver} -import org.junit.platform.engine.{EngineDiscoveryRequest, ExecutionRequest, TestDescriptor, TestExecutionResult, UniqueId} +import org.junit.platform.engine.{ + EngineDiscoveryRequest, + ExecutionRequest, + TestDescriptor, + TestExecutionResult, + UniqueId +} import org.scalatest.{Args, ConfigMap, DynaTags, Filter, ParallelTestExecution, Stopper, Tracker} import java.lang.reflect.Modifier @@ -33,21 +45,19 @@ import scala.collection.JavaConverters._ import scala.reflect.NameTransformer import scala.util.Try -/** - * ScalaTest implementation for JUnit 5 Test Engine. - */ +/** ScalaTest implementation for JUnit 5 Test Engine. + */ class ScalaTestEngine extends org.junit.platform.engine.TestEngine { private val logger = Logger.getLogger(classOf[ScalaTestEngine].getName) - /** - * Test engine ID, return "scalatest". - */ + /** Test engine ID, return "scalatest". + */ def getId: String = "scalatest" - /** - * Discover ScalaTest suites, you can disable the discover by setting system property org.scalatestplus.junit5.ScalaTestEngine.disabled to "true". - */ + /** Discover ScalaTest suites, you can disable the discover by setting system property + * org.scalatestplus.junit5.ScalaTestEngine.disabled to "true". + */ def discover(discoveryRequest: EngineDiscoveryRequest, uniqueId: UniqueId): TestDescriptor = { // reference: https://blogs.oracle.com/javamagazine/post/junit-build-custom-test-engines-java // https://software-matters.net/posts/custom-test-engine/ @@ -64,10 +74,17 @@ class ScalaTestEngine extends org.junit.platform.engine.TestEngine { val isSuitePredicate = new java.util.function.Predicate[Class[_]]() { - def test(t: Class[_]): Boolean = - classOf[org.scalatest.Suite].isAssignableFrom(t) && - !Modifier.isAbstract(t.getModifiers) && - JUnitHelper.checkForPublicNoArgConstructor(t) + def test(t: Class[_]): Boolean = { + val isSuite = classOf[org.scalatest.Suite].isAssignableFrom(t) + val notAbstract = !Modifier.isAbstract(t.getModifiers) + val notObject = + !t.getCanonicalName.endsWith("$") // must not be an object + + val notInner = t.getEnclosingClass == null + val canInit = JUnitHelper.checkForPublicNoArgConstructor(t) + + isSuite && notAbstract && notObject && notInner && canInit + } } def classDescriptorFunction(aClass: Class[_]) = @@ -76,7 +93,7 @@ class ScalaTestEngine extends org.junit.platform.engine.TestEngine { val suiteUniqueId = parent.getUniqueId.append(ScalaTestClassDescriptor.segmentType, aClass.getName) parent.getChildren.asScala.find(_.getUniqueId == suiteUniqueId) match { case Some(_) => Optional.empty[ScalaTestClassDescriptor]() - case None => Optional.of(new ScalaTestClassDescriptor(engineDesc, suiteUniqueId, aClass, true)) + case None => Optional.of(new ScalaTestClassDescriptor(engineDesc, suiteUniqueId, aClass, true)) } } } @@ -88,12 +105,11 @@ class ScalaTestEngine extends org.junit.platform.engine.TestEngine { } } - - def addToParentFunction(context: SelectorResolver.Context) = new java.util.function.Function[Class[_], java.util.stream.Stream[Match]]() { def apply(aClass: Class[_]): java.util.stream.Stream[Match] = { - context.addToParent(classDescriptorFunction(aClass)) + context + .addToParent(classDescriptorFunction(aClass)) .map[java.util.stream.Stream[Match]](toMatch) .orElse(java.util.stream.Stream.empty()) } @@ -101,9 +117,13 @@ class ScalaTestEngine extends org.junit.platform.engine.TestEngine { val classSelectorResolver = new SelectorResolver { - override def resolve(selector: ClasspathRootSelector, context: SelectorResolver.Context): SelectorResolver.Resolution = { + override def resolve( + selector: ClasspathRootSelector, + context: SelectorResolver.Context + ): SelectorResolver.Resolution = { val matches = - ReflectionSupport.findAllClassesInClasspathRoot(selector.getClasspathRoot, isSuitePredicate, alwaysTruePredicate) + ReflectionSupport + .findAllClassesInClasspathRoot(selector.getClasspathRoot, isSuitePredicate, alwaysTruePredicate) .stream() .flatMap(addToParentFunction(context)) .collect(Collectors.toSet()) @@ -114,9 +134,13 @@ class ScalaTestEngine extends org.junit.platform.engine.TestEngine { } } - override def resolve(selector: PackageSelector, context: SelectorResolver.Context): SelectorResolver.Resolution = { + override def resolve( + selector: PackageSelector, + context: SelectorResolver.Context + ): SelectorResolver.Resolution = { val matches = - ReflectionSupport.findAllClassesInPackage(selector.getPackageName, isSuitePredicate, alwaysTruePredicate) + ReflectionSupport + .findAllClassesInPackage(selector.getPackageName, isSuitePredicate, alwaysTruePredicate) .stream() .flatMap(addToParentFunction(context)) .collect(Collectors.toSet()) @@ -127,9 +151,13 @@ class ScalaTestEngine extends org.junit.platform.engine.TestEngine { } } - override def resolve(selector: ModuleSelector, context: SelectorResolver.Context): SelectorResolver.Resolution = { + override def resolve( + selector: ModuleSelector, + context: SelectorResolver.Context + ): SelectorResolver.Resolution = { val matches = - ReflectionSupport.findAllClassesInModule(selector.getModuleName, isSuitePredicate, alwaysTruePredicate) + ReflectionSupport + .findAllClassesInModule(selector.getModuleName, isSuitePredicate, alwaysTruePredicate) .stream() .flatMap(addToParentFunction(context)) .collect(Collectors.toSet()) @@ -140,11 +168,14 @@ class ScalaTestEngine extends org.junit.platform.engine.TestEngine { } } - override def resolve(selector: ClassSelector, context: SelectorResolver.Context): SelectorResolver.Resolution = { + override def resolve( + selector: ClassSelector, + context: SelectorResolver.Context + ): SelectorResolver.Resolution = { val testClass = selector.getJavaClass if (isSuitePredicate.test(testClass)) { - context.addToParent( - new java.util.function.Function[TestDescriptor, Optional[ScalaTestClassDescriptor]]() { + context + .addToParent(new java.util.function.Function[TestDescriptor, Optional[ScalaTestClassDescriptor]]() { def apply(parent: TestDescriptor): Optional[ScalaTestClassDescriptor] = { val suiteUniqueId = parent.getUniqueId.append(ScalaTestClassDescriptor.segmentType, testClass.getName) parent.getChildren.asScala.find(_.getUniqueId == suiteUniqueId) match { @@ -153,84 +184,90 @@ class ScalaTestEngine extends org.junit.platform.engine.TestEngine { } } }) - .map[Resolution]( - new java.util.function.Function[TestDescriptor, Resolution]() { - def apply(td: TestDescriptor): Resolution = Resolution.`match`(Match.exact(td)) - } - ).orElse(Resolution.unresolved()) - } - else + .map[Resolution]( + new java.util.function.Function[TestDescriptor, Resolution]() { + def apply(td: TestDescriptor): Resolution = Resolution.`match`(Match.exact(td)) + } + ) + .orElse(Resolution.unresolved()) + } else Resolution.unresolved() } } val uniqueIdSelectorResolver = new SelectorResolver { - override def resolve(selector: UniqueIdSelector, context: SelectorResolver.Context): SelectorResolver.Resolution = { + override def resolve( + selector: UniqueIdSelector, + context: SelectorResolver.Context + ): SelectorResolver.Resolution = { selector.getUniqueId.getSegments.asScala.toList match { - case engineSeg :: suiteSeg :: testSeg :: Nil if engineSeg.getType == "engine" && engineSeg.getValue == "scalatest" && testSeg.getType == "test" && suiteSeg.getType == ScalaTestClassDescriptor.segmentType => + case engineSeg :: suiteSeg :: testSeg :: Nil + if engineSeg.getType == "engine" && engineSeg.getValue == "scalatest" && testSeg.getType == "test" && suiteSeg.getType == ScalaTestClassDescriptor.segmentType => val suiteClassName = suiteSeg.getValue val suiteClass = Class.forName(suiteClassName) if (classOf[org.scalatest.Suite].isAssignableFrom(suiteClass)) { - context.addToParent( - new java.util.function.Function[TestDescriptor, Optional[ScalaTestClassDescriptor]]() { - def apply(parent: TestDescriptor): Optional[ScalaTestClassDescriptor] = { - val children = parent.getChildren.asScala - val suiteUniqueId = uniqueId.append(ScalaTestClassDescriptor.segmentType, suiteClass.getName) - val testUniqueId = suiteUniqueId.append("test", testSeg.getValue) - val testDesc = new ScalaTestDescriptor(testUniqueId, testSeg.getValue, None) - val (suiteDesc, result) = - children.find(_.getUniqueId == suiteUniqueId) match { - case Some(suiteDesc) => - (suiteDesc, Optional.empty[ScalaTestClassDescriptor]()) - - case None => - val suiteDesc = new ScalaTestClassDescriptor(engineDesc, suiteUniqueId, suiteClass, false) - (suiteDesc, Optional.of(suiteDesc)) + context + .addToParent( + new java.util.function.Function[TestDescriptor, Optional[ScalaTestClassDescriptor]]() { + def apply(parent: TestDescriptor): Optional[ScalaTestClassDescriptor] = { + val children = parent.getChildren.asScala + val suiteUniqueId = uniqueId.append(ScalaTestClassDescriptor.segmentType, suiteClass.getName) + val testUniqueId = suiteUniqueId.append("test", testSeg.getValue) + val testDesc = new ScalaTestDescriptor(testUniqueId, testSeg.getValue, None) + val (suiteDesc, result) = + children.find(_.getUniqueId == suiteUniqueId) match { + case Some(suiteDesc) => + (suiteDesc, Optional.empty[ScalaTestClassDescriptor]()) + + case None => + val suiteDesc = new ScalaTestClassDescriptor(engineDesc, suiteUniqueId, suiteClass, false) + (suiteDesc, Optional.of(suiteDesc)) + } + + suiteDesc.getChildren.asScala.find(_.getUniqueId == testUniqueId) match { + case Some(_) => // Do nothing if the test already exists + case None => suiteDesc.addChild(testDesc) } - suiteDesc.getChildren.asScala.find(_.getUniqueId == testUniqueId) match { - case Some(_) => // Do nothing if the test already exists - case None => suiteDesc.addChild(testDesc) + result } - - result } - } - ) - .map[Resolution]( - new java.util.function.Function[TestDescriptor, Resolution]() { - def apply(td: TestDescriptor): Resolution = Resolution.`match`(Match.exact(td)) - } - ) - .orElse(Resolution.unresolved()) - } - else + ) + .map[Resolution]( + new java.util.function.Function[TestDescriptor, Resolution]() { + def apply(td: TestDescriptor): Resolution = Resolution.`match`(Match.exact(td)) + } + ) + .orElse(Resolution.unresolved()) + } else Resolution.unresolved() - case engineSeg :: suiteSeg :: Nil if engineSeg.getType == "engine" && engineSeg.getValue == "scalatest" && suiteSeg.getType == ScalaTestClassDescriptor.segmentType => + case engineSeg :: suiteSeg :: Nil + if engineSeg.getType == "engine" && engineSeg.getValue == "scalatest" && suiteSeg.getType == ScalaTestClassDescriptor.segmentType => val suiteClassName = suiteSeg.getValue val suiteClass = Class.forName(suiteClassName) if (classOf[org.scalatest.Suite].isAssignableFrom(suiteClass)) { - context.addToParent( - new java.util.function.Function[TestDescriptor, Optional[ScalaTestClassDescriptor]]() { - def apply(parent: TestDescriptor): Optional[ScalaTestClassDescriptor] = { - val children = parent.getChildren.asScala - val suiteUniqueId = uniqueId.append(ScalaTestClassDescriptor.segmentType, suiteClass.getName) - children.find(_.getUniqueId == suiteUniqueId) match { - case Some(_) => Optional.empty[ScalaTestClassDescriptor]() - case None => Optional.of(new ScalaTestClassDescriptor(engineDesc, suiteUniqueId, suiteClass, false)) + context + .addToParent( + new java.util.function.Function[TestDescriptor, Optional[ScalaTestClassDescriptor]]() { + def apply(parent: TestDescriptor): Optional[ScalaTestClassDescriptor] = { + val children = parent.getChildren.asScala + val suiteUniqueId = uniqueId.append(ScalaTestClassDescriptor.segmentType, suiteClass.getName) + children.find(_.getUniqueId == suiteUniqueId) match { + case Some(_) => Optional.empty[ScalaTestClassDescriptor]() + case None => + Optional.of(new ScalaTestClassDescriptor(engineDesc, suiteUniqueId, suiteClass, false)) + } } } - } - ) - .map[Resolution]( - new java.util.function.Function[TestDescriptor, Resolution]() { - def apply(td: TestDescriptor): Resolution = Resolution.`match`(Match.exact(td)) - } - ) - .orElse(Resolution.unresolved()) - } - else + ) + .map[Resolution]( + new java.util.function.Function[TestDescriptor, Resolution]() { + def apply(td: TestDescriptor): Resolution = Resolution.`match`(Match.exact(td)) + } + ) + .orElse(Resolution.unresolved()) + } else Resolution.unresolved() case _ => Resolution.unresolved() @@ -238,11 +275,12 @@ class ScalaTestEngine extends org.junit.platform.engine.TestEngine { } } - val resolver = EngineDiscoveryRequestResolver.builder[EngineDescriptor]() - .addClassContainerSelectorResolver(isSuitePredicate) - .addSelectorResolver(classSelectorResolver) - .addSelectorResolver(uniqueIdSelectorResolver) - .build() + val resolver = EngineDiscoveryRequestResolver + .builder[EngineDescriptor]() + .addClassContainerSelectorResolver(isSuitePredicate) + .addSelectorResolver(classSelectorResolver) + .addSelectorResolver(uniqueIdSelectorResolver) + .build() resolver.resolve(discoveryRequest, engineDesc) @@ -252,9 +290,9 @@ class ScalaTestEngine extends org.junit.platform.engine.TestEngine { engineDesc } - /** - * Execute ScalaTest suites, you can disable the ScalaTest suites execution by setting system property org.scalatestplus.junit.JUnit5TestEngine.disabled to "true". - */ + /** Execute ScalaTest suites, you can disable the ScalaTest suites execution by setting system property + * org.scalatestplus.junit.JUnit5TestEngine.disabled to "true". + */ def execute(request: ExecutionRequest): Unit = { if (System.getProperty("org.scalatestplus.junit5.ScalaTestEngine.disabled") != "true") { logger.fine("Start tests execution...") @@ -268,7 +306,8 @@ class ScalaTestEngine extends org.junit.platform.engine.TestEngine { logger.fine("Start execution of suite class " + clzDesc.suiteClass.getName + "...") listener.executionStarted(clzDesc) val suiteClass = clzDesc.suiteClass - val canInstantiate = JUnitHelper.checkForPublicNoArgConstructor(suiteClass) && classOf[org.scalatest.Suite].isAssignableFrom(suiteClass) + val canInstantiate = JUnitHelper.checkForPublicNoArgConstructor(suiteClass) && classOf[org.scalatest.Suite] + .isAssignableFrom(suiteClass) require(canInstantiate, "Must pass an org.scalatest.Suite with a public no-arg constructor") val suiteToRun = suiteClass.newInstance.asInstanceOf[org.scalatest.Suite] val reporter = new EngineExecutionListenerReporter(listener, clzDesc, engineDesc) @@ -281,16 +320,21 @@ class ScalaTestEngine extends org.junit.platform.engine.TestEngine { excludeNestedSuites = false, dynaTags = DynaTags(Map.empty, Map(suiteToRun.suiteId -> Map.empty)) ) - if (suiteToRun.testNames.size == children.size) // When testNames size is same as children size, it means all tests are selected, so no need to apply filter, this solves the issue of dynamic test names when running suite. + if ( + suiteToRun.testNames.size == children.size + ) // When testNames size is same as children size, it means all tests are selected, so no need to apply filter, this solves the issue of dynamic test names when running suite. Filter.default else { val SelectedTag = "Selected" val SelectedSet = Set(SelectedTag) val testNames = suiteToRun.testNames val desiredTests: Set[String] = - children.map(_.getDisplayName).filter { tn => - testNames.contains(tn) || testNames.contains(NameTransformer.decode(tn)) - }.toSet + children + .map(_.getDisplayName) + .filter { tn => + testNames.contains(tn) || testNames.contains(NameTransformer.decode(tn)) + } + .toSet val taggedTests: Map[String, Set[String]] = desiredTests.map(_ -> SelectedSet).toMap val suiteId = suiteToRun.suiteId Filter( @@ -324,16 +368,22 @@ class ScalaTestEngine extends org.junit.platform.engine.TestEngine { Executors.newFixedThreadPool(poolSize, threadFactory) else Executors.newCachedThreadPool(threadFactory) - val distributor = new ConcurrentDistributor(Args(reporter, Stopper.default, filter, ConfigMap.empty, None, new Tracker), execSvc) + val distributor = new ConcurrentDistributor( + Args(reporter, Stopper.default, filter, ConfigMap.empty, None, new Tracker), + execSvc + ) try { - suiteToRun.run(None, Args(reporter, Stopper.default, filter, ConfigMap.empty, Some(distributor), new Tracker)) + suiteToRun.run( + None, + Args(reporter, Stopper.default, filter, ConfigMap.empty, Some(distributor), new Tracker) + ) distributor.waitUntilDone() } finally { execSvc.shutdown() } - } - else { - val status = suiteToRun.run(None, Args(reporter, Stopper.default, filter, ConfigMap.empty, None, new Tracker)) + } else { + val status = + suiteToRun.run(None, Args(reporter, Stopper.default, filter, ConfigMap.empty, None, new Tracker)) status.waitUntilCompleted() } diff --git a/src/test/scala/org/scalatestplus/junit5/IntegrationTests.scala b/src/test/scala/org/scalatestplus/junit5/IntegrationTests.scala new file mode 100644 index 0000000..c10f29e --- /dev/null +++ b/src/test/scala/org/scalatestplus/junit5/IntegrationTests.scala @@ -0,0 +1,152 @@ +package org.scalatestplus.junit5 + +import org.junit.platform.engine.discovery.DiscoverySelectors.selectClass +import org.junit.platform.launcher.core.{LauncherDiscoveryRequestBuilder, LauncherFactory} +import org.scalatest.{BeforeAndAfterAll, BeforeAndAfterEach, Suite, funspec} +import org.scalatestplus.junit5.integration.listener.{JUnitListener, ScalaTestListener} +import org.scalatestplus.junit5.integration.NestedFixture + +import java.lang.reflect.Modifier +import java.nio.file.{Files, Path, Paths} +import java.util.jar.JarFile + +class IntegrationTests extends funspec.AnyFunSpec with BeforeAndAfterAll with BeforeAndAfterEach { + + import scala.collection.JavaConverters._ + + var scalaTestEngineProperty: Option[String] = None + + override def beforeAll(): Unit = { + scalaTestEngineProperty = Option(System.clearProperty("org.scalatestplus.junit5.ScalaTestEngine.disabled")) + } + + override def afterAll(): Unit = { + scalaTestEngineProperty.foreach(System.setProperty("org.scalatestplus.junit5.ScalaTestEngine.disabled", _)) + } + + lazy val integrationDirPath: Path = { + val classPathRoot = this.getClass.getProtectionDomain.getCodeSource.getLocation + + val root = Paths.get(classPathRoot.toURI) + + val result = root.resolve("org/scalatestplus/junit5/integration") + + println(s"integration path set to $result") + result + } + + private def findTestClasses(path: Path): Set[Class[_]] = { + val suiteClass = classOf[Suite] + + def isTestClass(cls: Class[_]): Boolean = { + suiteClass.isAssignableFrom(cls) && !Modifier.isAbstract(cls.getModifiers) && !cls.isInterface + } + + val classPathRoot = Paths.get(getClass.getProtectionDomain.getCodeSource.getLocation.toURI) + + val classNames = if (Files.isDirectory(path)) { + Files + .list(path) + .iterator() + .asScala + .filter(p => p.toString.endsWith(".class")) + .map(p => classPathRoot.relativize(p).toString.replace('/', '.').dropRight(".class".length)) + .toSet + } else if (path.toString.toLowerCase.endsWith(".jar")) { + val jarFile = new JarFile(path.toFile) + try { + jarFile + .entries() + .asScala + .filter(e => e.getName.endsWith(".class")) + .map(_.getName.replace('/', '.').dropRight(".class".length)) + .toSet + } finally { + jarFile.close() + } + } else { + Set.empty[String] + } + + classNames + .filterNot { name => + name.contains("$") + } + .flatMap { className => + try { + val classLoader = getClass.getClassLoader + val cls = Class.forName(className, false, classLoader) + val result: Option[Class[_]] = Some(cls).filter(isTestClass) + result + } catch { + case _: Throwable => None + } + } + } + + lazy val integrationTests: Set[Class[_]] = findTestClasses(integrationDirPath); + + override def beforeEach(): Unit = { + ScalaTestListener.clear() + JUnitListener.clear() + } + + describe("ScalaTest runner and JUnit runner should discovery & run identical tests") { + + describe("in classes") { + + integrationTests.foreach { clz => + val clzName = clz.getName + it(s"${clz.getSimpleName}") { + + // with ScalaTest runner + + org.scalatest.tools.Runner.run( + Array( + "-R", + integrationDirPath.toString, + "-s", + clzName, + "-C", + classOf[ScalaTestListener].getCanonicalName + // "-oN" + ) + ) + + // with JUnit 5 runner + { + val launcher = LauncherFactory.create() + + val discoveryRequest = LauncherDiscoveryRequestBuilder.request + .selectors( + selectClass(clzName) + ) + .build() + + launcher.execute(discoveryRequest, new JUnitListener()) + } + + JUnitListener.startedShouldBe(ScalaTestListener.started.mkString("\n")) + JUnitListener.finishedShouldBe(ScalaTestListener.finished.mkString("\n")) + } + } + } + + ignore("in packages") { // TODO this doesn't work + + val pkg = classOf[NestedFixture].getPackage.getName + org.scalatest.tools.Runner.run( + Array( + "-R", + integrationDirPath.toString, + "-w", + pkg, + "-C", + classOf[ScalaTestListener].getCanonicalName + // "-oN" + ) + ) + } + } + +} diff --git a/src/test/scala/org/scalatestplus/junit5/ScalaTestDescriptorSpec.scala b/src/test/scala/org/scalatestplus/junit5/ScalaTestDescriptorSpec.scala index 1985b29..f0b022b 100644 --- a/src/test/scala/org/scalatestplus/junit5/ScalaTestDescriptorSpec.scala +++ b/src/test/scala/org/scalatestplus/junit5/ScalaTestDescriptorSpec.scala @@ -39,45 +39,78 @@ class ScalaTestDescriptorSpec extends funspec.AnyFunSpec { it("should return ClassSource when location is TopOfClass") { val locationOpt = Some(TopOfClass(className)) - val descriptor1 = new ScalaTestDescriptor(uniqueId.append(ScalaTestClassDescriptor.segmentType, className).append("test", displayName), displayName, locationOpt) + val descriptor1 = new ScalaTestDescriptor( + uniqueId.append(ScalaTestClassDescriptor.segmentType, className).append("test", displayName), + displayName, + locationOpt + ) assert(descriptor1.getSource.isPresent) assert(descriptor1.getSource.get() == ClassSource.from(className)) } it("should return MethodSource when location is TopOfMethod") { - val descriptor1 = new ScalaTestDescriptor(uniqueId.append(ScalaTestClassDescriptor.segmentType, className).append("test", displayName), displayName, - Some(TopOfMethod(className, "public long java.util.concurrent.CountDownLatch.getCount()"))) + val descriptor1 = new ScalaTestDescriptor( + uniqueId.append(ScalaTestClassDescriptor.segmentType, className).append("test", displayName), + displayName, + Some(TopOfMethod(className, "public long java.util.concurrent.CountDownLatch.getCount()")) + ) assert(descriptor1.getSource.isPresent) assert(descriptor1.getSource.get() == MethodSource.from(className, "getCount")) - val descriptor2 = new ScalaTestDescriptor(uniqueId.append(ScalaTestClassDescriptor.segmentType, className).append("test", displayName), displayName, - Some(TopOfMethod(className, "public final void java.lang.Object.wait(long,int) throws java.lang.InterruptedException"))) + val descriptor2 = new ScalaTestDescriptor( + uniqueId.append(ScalaTestClassDescriptor.segmentType, className).append("test", displayName), + displayName, + Some( + TopOfMethod( + className, + "public final void java.lang.Object.wait(long,int) throws java.lang.InterruptedException" + ) + ) + ) assert(descriptor2.getSource.isPresent) assert(descriptor2.getSource.get() == MethodSource.from(className, "wait", "long,int")) - val descriptor3 = new ScalaTestDescriptor(uniqueId.append(ScalaTestClassDescriptor.segmentType, className).append("test", displayName), displayName, - Some(TopOfMethod(className, "public boolean java.lang.Object.equals(java.lang.Object)"))) + val descriptor3 = new ScalaTestDescriptor( + uniqueId.append(ScalaTestClassDescriptor.segmentType, className).append("test", displayName), + displayName, + Some(TopOfMethod(className, "public boolean java.lang.Object.equals(java.lang.Object)")) + ) assert(descriptor3.getSource.isPresent) assert(descriptor3.getSource.get() == MethodSource.from(className, "equals", "java.lang.Object")) } it("should return FileSource when location is LineInFile") { val locationOpt = Some(LineInFile(lineNumber, fileName, filePathname)) - val descriptor1 = new ScalaTestDescriptor(uniqueId.append(ScalaTestClassDescriptor.segmentType, className).append("test", displayName), displayName, locationOpt) + val descriptor1 = new ScalaTestDescriptor( + uniqueId.append(ScalaTestClassDescriptor.segmentType, className).append("test", displayName), + displayName, + locationOpt + ) assert(descriptor1.getSource.isPresent) - assert(descriptor1.getSource.get() == FileSource.from(new File(filePathname.getOrElse(fileName)), FilePosition.from(lineNumber))) + assert( + descriptor1.getSource + .get() == FileSource.from(new File(filePathname.getOrElse(fileName)), FilePosition.from(lineNumber)) + ) } it("should return ClassSource when location is SeeStackDepthException") { val locationOpt = Some(SeeStackDepthException) - val descriptor1 = new ScalaTestDescriptor(uniqueId.append(ScalaTestClassDescriptor.segmentType, className).append("test", displayName), displayName, locationOpt) + val descriptor1 = new ScalaTestDescriptor( + uniqueId.append(ScalaTestClassDescriptor.segmentType, className).append("test", displayName), + displayName, + locationOpt + ) assert(descriptor1.getSource.isPresent) assert(descriptor1.getSource.get() == ClassSource.from(className)) } it("should return Optional.empty when location is None") { val locationOpt = None - val descriptor1 = new ScalaTestDescriptor(uniqueId.append(ScalaTestClassDescriptor.segmentType, className).append("test", displayName), displayName, locationOpt) + val descriptor1 = new ScalaTestDescriptor( + uniqueId.append(ScalaTestClassDescriptor.segmentType, className).append("test", displayName), + displayName, + locationOpt + ) assert(!descriptor1.getSource.isPresent) } diff --git a/src/test/scala/org/scalatestplus/junit5/ScalaTestEngineSpec.scala b/src/test/scala/org/scalatestplus/junit5/ScalaTestEngineSpec.scala index f179c3e..6848855 100644 --- a/src/test/scala/org/scalatestplus/junit5/ScalaTestEngineSpec.scala +++ b/src/test/scala/org/scalatestplus/junit5/ScalaTestEngineSpec.scala @@ -1,11 +1,11 @@ package org.scalatestplus.junit5 import org.junit.platform.engine.UniqueId -import org.junit.platform.engine.discovery.ClasspathRootSelector import org.junit.platform.engine.discovery.DiscoverySelectors.{selectClasspathRoots, selectPackage} import org.junit.platform.launcher.core.LauncherDiscoveryRequestBuilder.request import org.scalatest.{BeforeAndAfterAll, funspec} import org.scalatestplus.junit5.helpers.HappySuite +import org.scalatestplus.junit5.integration.PlainFixture import java.nio.file.{Files, Paths} import scala.collection.JavaConverters._ @@ -26,36 +26,52 @@ class ScalaTestEngineSpec extends funspec.AnyFunSpec with BeforeAndAfterAll { describe("discover method") { it("should discover suites on classpath") { val classPathRoot = classOf[ScalaTestEngineSpec].getProtectionDomain.getCodeSource.getLocation - val discoveryRequest = request.selectors( - selectClasspathRoots(java.util.Collections.singleton(Paths.get(classPathRoot.toURI))) - ).build() + val discoveryRequest = request + .selectors( + selectClasspathRoots(java.util.Collections.singleton(Paths.get(classPathRoot.toURI))) + ) + .build() val engineDescriptor = engine.discover(discoveryRequest, UniqueId.forEngine(engine.getId())) - assert(engineDescriptor.getChildren.asScala.exists(td => td.asInstanceOf[ScalaTestClassDescriptor].suiteClass == classOf[HappySuite])) + assert( + engineDescriptor.getChildren.asScala.exists(td => + td.asInstanceOf[ScalaTestClassDescriptor].suiteClass == classOf[HappySuite] + ) + ) } it("should return unresolved for classpath without any tests") { val emptyPath = Files.createTempDirectory(null) - val discoveryRequest = request.selectors( - selectClasspathRoots(java.util.Collections.singleton(emptyPath)) - ).build() + val discoveryRequest = request + .selectors( + selectClasspathRoots(java.util.Collections.singleton(emptyPath)) + ) + .build() val engineDescriptor = engine.discover(discoveryRequest, UniqueId.forEngine(engine.getId())) assert(engineDescriptor.getChildren.asScala.isEmpty) } it("should discover suites in package") { - val discoveryRequest = request.selectors( - selectPackage("org.scalatestplus.junit5.helpers") - ).build() + val discoveryRequest = request + .selectors( + selectPackage("org.scalatestplus.junit5.helpers") + ) + .build() val engineDescriptor = engine.discover(discoveryRequest, UniqueId.forEngine(engine.getId())) - assert(engineDescriptor.getChildren.asScala.exists(td => td.asInstanceOf[ScalaTestClassDescriptor].suiteClass == classOf[HappySuite])) + assert( + engineDescriptor.getChildren.asScala.exists(td => + td.asInstanceOf[ScalaTestClassDescriptor].suiteClass == classOf[HappySuite] + ) + ) } it("should return unresolved for package without any tests") { - val discoveryRequest = request.selectors( - selectPackage("org.scalatestplus.junit5.nonexistant") - ).build() + val discoveryRequest = request + .selectors( + selectPackage("org.scalatestplus.junit5.nonexistant") + ) + .build() val engineDescriptor = engine.discover(discoveryRequest, UniqueId.forEngine(engine.getId())) assert(engineDescriptor.getChildren.asScala.isEmpty) diff --git a/src/test/scala/org/scalatestplus/junit5/helpers/JUnitSuiteSpec.scala b/src/test/scala/org/scalatestplus/junit5/helpers/JUnitSuiteSpec.scala index 4aea9f8..4c9715d 100644 --- a/src/test/scala/org/scalatestplus/junit5/helpers/JUnitSuiteSpec.scala +++ b/src/test/scala/org/scalatestplus/junit5/helpers/JUnitSuiteSpec.scala @@ -63,8 +63,10 @@ class JUnitSuiteSpec extends funspec.AnyFunSpec { assert(b.testNames === TreeSet[String]()) } - it("should return names of methods that are annotated with Test, take no params, but have a return type " + - "other than Unit from testNames") { + it( + "should return names of methods that are annotated with Test, take no params, but have a return type " + + "other than Unit from testNames" + ) { val a = new TestWithNonUnitMethod assert(a.testNames === TreeSet("doThat", "doTheOtherThing", "doThis")) @@ -145,7 +147,17 @@ class JUnitSuiteSpec extends funspec.AnyFunSpec { TestWasCalledSuite.reinitialize() val a = new TestWasCalledSuite - a.run(None, Args(SilentReporter, Stopper.default, Filter(Some(Set("org.scalatest.SlowAsMolasses")), Set()), ConfigMap.empty, None, new Tracker)) + a.run( + None, + Args( + SilentReporter, + Stopper.default, + Filter(Some(Set("org.scalatest.SlowAsMolasses")), Set()), + ConfigMap.empty, + None, + new Tracker + ) + ) assert(!TestWasCalledSuite.theDoThisCalled) assert(!TestWasCalledSuite.theDoThatCalled) } @@ -164,19 +176,29 @@ class JUnitSuiteSpec extends funspec.AnyFunSpec { val d = new DSuite assert(d.expectedTestCount(Filter(Some(Set("org.scalatestplus.junit.FastAsLight")), Set())) === 0) - assert(d.expectedTestCount(Filter(Some(Set("org.scalatestplus.junit.SlowAsMolasses")), Set("org.scalatest.FastAsLight"))) === 0) + assert( + d.expectedTestCount( + Filter(Some(Set("org.scalatestplus.junit.SlowAsMolasses")), Set("org.scalatest.FastAsLight")) + ) === 0 + ) assert(d.expectedTestCount(Filter(None, Set("org.scalatestplus.junit.SlowAsMolasses"))) === 6) assert(d.expectedTestCount(Filter()) === 6) val e = new ESuite assert(e.expectedTestCount(Filter(Some(Set("org.scalatestplus.junit.FastAsLight")), Set())) === 0) - assert(e.expectedTestCount(Filter(Some(Set("org.scalatestplus.junit.SlowAsMolasses")), Set("org.scalatest.FastAsLight"))) === 0) + assert( + e.expectedTestCount( + Filter(Some(Set("org.scalatestplus.junit.SlowAsMolasses")), Set("org.scalatest.FastAsLight")) + ) === 0 + ) assert(e.expectedTestCount(Filter(None, Set("org.scalatestplus.junit.SlowAsMolasses"))) === 1) assert(e.expectedTestCount(Filter()) === 1) } - it("should generate a test failure if a Throwable, or an Error other than direct Error subtypes " + - "known in JDK 1.5, excluding AssertionError") { + it( + "should generate a test failure if a Throwable, or an Error other than direct Error subtypes " + + "known in JDK 1.5, excluding AssertionError" + ) { val a = new ShouldFailSuite val rep = new EventRecordingReporter a.run(None, Args(rep)) diff --git a/src/test/scala/org/scalatestplus/junit5/helpers/JUnitSuiteSuite.scala b/src/test/scala/org/scalatestplus/junit5/helpers/JUnitSuiteSuite.scala index 5959edb..42c3c9b 100644 --- a/src/test/scala/org/scalatestplus/junit5/helpers/JUnitSuiteSuite.scala +++ b/src/test/scala/org/scalatestplus/junit5/helpers/JUnitSuiteSuite.scala @@ -29,7 +29,7 @@ package org.scalatestplus.junit5 { class HappySuite extends JUnitSuite { @Test def verifySomething(): Unit = () // Don't do nothin - //@Test def verifySomething2(): Unit = () // Don't do nothin// ' + // @Test def verifySomething2(): Unit = () // Don't do nothin// ' } class BitterSuite extends JUnitSuite { @@ -138,7 +138,9 @@ package org.scalatestplus.junit5 { assert(repA.testIgnoredEvent.get.suiteClassName.get === "org.scalatestplus.junit5.helpers.IgnoredSuite") } - test("A JUnitSuite with two JUnit 5 Test annotations will cause TestStarting and TestSucceeded events to be fired twice each") { + test( + "A JUnitSuite with two JUnit 5 Test annotations will cause TestStarting and TestSucceeded events to be fired twice each" + ) { val many = new ManySuite val repA = new MyReporter diff --git a/src/test/scala/org/scalatestplus/junit5/helpers/SharedHelpers.scala b/src/test/scala/org/scalatestplus/junit5/helpers/SharedHelpers.scala index 62f4c66..81f1bc0 100644 --- a/src/test/scala/org/scalatestplus/junit5/helpers/SharedHelpers.scala +++ b/src/test/scala/org/scalatestplus/junit5/helpers/SharedHelpers.scala @@ -43,10 +43,10 @@ object SharedHelpers extends Assertions { synchronized { eventsReceived filter { case event: TestSucceeded => true - case _ => false + case _ => false } map { case event: TestSucceeded => event - case _ => throw new RuntimeException("should never happen") + case _ => throw new RuntimeException("should never happen") } } } @@ -54,10 +54,10 @@ object SharedHelpers extends Assertions { synchronized { eventsReceived filter { case event: TestStarting => true - case _ => false + case _ => false } map { case event: TestStarting => event - case _ => throw new RuntimeException("should never happen") + case _ => throw new RuntimeException("should never happen") } } } @@ -67,10 +67,10 @@ object SharedHelpers extends Assertions { synchronized { eventsReceived filter { case event: InfoProvided => true - case _ => false + case _ => false } map { case event: InfoProvided => event - case _ => throw new RuntimeException("should never happen") + case _ => throw new RuntimeException("should never happen") } } } @@ -78,10 +78,10 @@ object SharedHelpers extends Assertions { synchronized { eventsReceived filter { case event: NoteProvided => true - case _ => false + case _ => false } map { case event: NoteProvided => event - case _ => throw new RuntimeException("should never happen") + case _ => throw new RuntimeException("should never happen") } } } @@ -89,10 +89,10 @@ object SharedHelpers extends Assertions { synchronized { eventsReceived filter { case event: AlertProvided => true - case _ => false + case _ => false } map { case event: AlertProvided => event - case _ => throw new RuntimeException("should never happen") + case _ => throw new RuntimeException("should never happen") } } } @@ -100,10 +100,10 @@ object SharedHelpers extends Assertions { synchronized { eventsReceived filter { case event: MarkupProvided => true - case _ => false + case _ => false } map { case event: MarkupProvided => event - case _ => throw new RuntimeException("should never happen") + case _ => throw new RuntimeException("should never happen") } } } @@ -111,10 +111,10 @@ object SharedHelpers extends Assertions { synchronized { eventsReceived filter { case event: ScopeOpened => true - case _ => false + case _ => false } map { case event: ScopeOpened => event - case _ => throw new RuntimeException("should never happen") + case _ => throw new RuntimeException("should never happen") } } } @@ -122,10 +122,10 @@ object SharedHelpers extends Assertions { synchronized { eventsReceived filter { case event: ScopeClosed => true - case _ => false + case _ => false } map { case event: ScopeClosed => event - case _ => throw new RuntimeException("should never happen") + case _ => throw new RuntimeException("should never happen") } } } @@ -133,10 +133,10 @@ object SharedHelpers extends Assertions { synchronized { eventsReceived filter { case event: ScopePending => true - case _ => false + case _ => false } map { case event: ScopePending => event - case _ => throw new RuntimeException("should never happen") + case _ => throw new RuntimeException("should never happen") } } } @@ -144,10 +144,10 @@ object SharedHelpers extends Assertions { synchronized { eventsReceived filter { case event: TestPending => true - case _ => false + case _ => false } map { case event: TestPending => event - case _ => throw new RuntimeException("should never happen") + case _ => throw new RuntimeException("should never happen") } } } @@ -155,10 +155,10 @@ object SharedHelpers extends Assertions { synchronized { eventsReceived filter { case event: TestCanceled => true - case _ => false + case _ => false } map { case event: TestCanceled => event - case _ => throw new RuntimeException("should never happen") + case _ => throw new RuntimeException("should never happen") } } } @@ -166,10 +166,10 @@ object SharedHelpers extends Assertions { synchronized { eventsReceived filter { case event: TestFailed => true - case _ => false + case _ => false } map { case event: TestFailed => event - case _ => throw new RuntimeException("should never happen") + case _ => throw new RuntimeException("should never happen") } } } @@ -177,10 +177,10 @@ object SharedHelpers extends Assertions { synchronized { eventsReceived filter { case event: TestIgnored => true - case _ => false + case _ => false } map { case event: TestIgnored => event - case _ => throw new RuntimeException("should never happen") + case _ => throw new RuntimeException("should never happen") } } } @@ -188,10 +188,10 @@ object SharedHelpers extends Assertions { synchronized { eventsReceived filter { case event: SuiteStarting => true - case _ => false + case _ => false } map { case event: SuiteStarting => event - case _ => throw new RuntimeException("should never happen") + case _ => throw new RuntimeException("should never happen") } } } @@ -199,10 +199,10 @@ object SharedHelpers extends Assertions { synchronized { eventsReceived filter { case event: SuiteCompleted => true - case _ => false + case _ => false } map { case event: SuiteCompleted => event - case _ => throw new RuntimeException("should never happen") + case _ => throw new RuntimeException("should never happen") } } } @@ -210,10 +210,10 @@ object SharedHelpers extends Assertions { synchronized { eventsReceived filter { case event: SuiteAborted => true - case _ => false + case _ => false } map { case event: SuiteAborted => event - case _ => throw new RuntimeException("should never happen") + case _ => throw new RuntimeException("should never happen") } } } @@ -224,4 +224,3 @@ object SharedHelpers extends Assertions { } } } - diff --git a/src/test/scala/org/scalatestplus/junit5/helpers/TestWasCalledSuite.scala b/src/test/scala/org/scalatestplus/junit5/helpers/TestWasCalledSuite.scala index b111f5f..95a6394 100644 --- a/src/test/scala/org/scalatestplus/junit5/helpers/TestWasCalledSuite.scala +++ b/src/test/scala/org/scalatestplus/junit5/helpers/TestWasCalledSuite.scala @@ -82,7 +82,7 @@ class DSuite extends JUnitSuite { @Test def doTwo(): Unit = () @Test def doIt(): Unit = () @Test def doFour(): String = "hi" // JUnit will not run these two because they don't - @Test def doFive(): Int = 5 // have result type Unit. + @Test def doFive(): Int = 5 // have result type Unit. } class ESuite extends JUnitSuite { diff --git a/src/test/scala/org/scalatestplus/junit5/integration/DescribeFixture.scala b/src/test/scala/org/scalatestplus/junit5/integration/DescribeFixture.scala new file mode 100644 index 0000000..24a2acb --- /dev/null +++ b/src/test/scala/org/scalatestplus/junit5/integration/DescribeFixture.scala @@ -0,0 +1,14 @@ +package org.scalatestplus.junit5.integration + +import org.scalatest.funspec.AnyFunSpec + +class DescribeFixture extends AnyFunSpec { + + describe("group") { + + it("a") {} + + it("b") {} + } + +} diff --git a/src/test/scala/org/scalatestplus/junit5/integration/NestedFixture.scala b/src/test/scala/org/scalatestplus/junit5/integration/NestedFixture.scala new file mode 100644 index 0000000..ca2f374 --- /dev/null +++ b/src/test/scala/org/scalatestplus/junit5/integration/NestedFixture.scala @@ -0,0 +1,39 @@ +package org.scalatestplus.junit5.integration + +import org.scalatest.Suite +import org.scalatest.funspec.AnyFunSpec + +import scala.collection.immutable + +object NestedFixture { + + class Inner extends AnyFunSpec { + it("a") {} + } + + object I1 extends Inner +} +class NestedFixture extends AnyFunSpec { + + val i2 = new NestedFixture.Inner { + override def suiteName: String = "i2" + } + + override def nestedSuites: immutable.IndexedSeq[Suite] = { + + val i3 = new NestedFixture.Inner { + override def suiteName: String = "i3" + } + + immutable.IndexedSeq[Suite]( + NestedFixture.I1, // object + i2, // member + i3, // local instance + new NestedFixture.Inner() { + override def suiteName: String = "i4" + } // ad-hoc + ) + } + + it("b") {} +} diff --git a/src/test/scala/org/scalatestplus/junit5/integration/PlainFixture.scala b/src/test/scala/org/scalatestplus/junit5/integration/PlainFixture.scala new file mode 100644 index 0000000..bae9ec6 --- /dev/null +++ b/src/test/scala/org/scalatestplus/junit5/integration/PlainFixture.scala @@ -0,0 +1,11 @@ +package org.scalatestplus.junit5.integration + +import org.scalatest.funspec.AnyFunSpec +import org.scalatestplus.junit5.integration.listener + +class PlainFixture extends AnyFunSpec { + + it("a") {} + + it("b") {} +} diff --git a/src/test/scala/org/scalatestplus/junit5/integration/PlainFixture_Fail.scala b/src/test/scala/org/scalatestplus/junit5/integration/PlainFixture_Fail.scala new file mode 100644 index 0000000..c18e34b --- /dev/null +++ b/src/test/scala/org/scalatestplus/junit5/integration/PlainFixture_Fail.scala @@ -0,0 +1,15 @@ +package org.scalatestplus.junit5.integration + +import org.scalatest.funspec.AnyFunSpec +import org.scalatestplus.junit5.integration.listener + +import scala.sys.error + +class PlainFixture_Fail extends AnyFunSpec { + + it("a") {} + + it("b") { + error("!") + } +} diff --git a/src/test/scala/org/scalatestplus/junit5/integration/listener/JUnitListener.scala b/src/test/scala/org/scalatestplus/junit5/integration/listener/JUnitListener.scala new file mode 100644 index 0000000..3c3c49a --- /dev/null +++ b/src/test/scala/org/scalatestplus/junit5/integration/listener/JUnitListener.scala @@ -0,0 +1,31 @@ +package org.scalatestplus.junit5.integration.listener + +import org.junit.platform.engine.TestExecutionResult +import org.junit.platform.launcher.{TestExecutionListener, TestIdentifier} + +object JUnitListener extends ExecutionOrder {} + +class JUnitListener extends TestExecutionListener { + import JUnitListener._ + + override def executionStarted(testIdentifier: TestIdentifier): Unit = { + if (testIdentifier.isTest) { + + started += List(testIdentifier.getDisplayName).mkString(" - ") + } + + } + + override def executionFinished( + testIdentifier: TestIdentifier, + testExecutionResult: TestExecutionResult + ): Unit = { + + if (testIdentifier.isTest) { + + finished.append( + testExecutionResult.getStatus -> List(testIdentifier.getDisplayName).mkString(" - ") + ) + } + } +} diff --git a/src/test/scala/org/scalatestplus/junit5/integration/listener/ScalaTestListener.scala b/src/test/scala/org/scalatestplus/junit5/integration/listener/ScalaTestListener.scala new file mode 100644 index 0000000..f9746d8 --- /dev/null +++ b/src/test/scala/org/scalatestplus/junit5/integration/listener/ScalaTestListener.scala @@ -0,0 +1,53 @@ +package org.scalatestplus.junit5.integration.listener + +import org.junit.platform.engine.TestExecutionResult +import org.scalatest.Reporter +import org.scalatest.events._ + +object ScalaTestListener extends ExecutionOrder { + + case class IR( + suiteClassName: Option[String], + suiteName: String, + testName: String + ) { + + override def toString: String = { + + (List(suiteName, testName)).mkString(" - ") + } + } +} + +// Custom reporter to capture test results +class ScalaTestListener extends Reporter { + + import ScalaTestListener._ + + def apply(event: Event): Unit = { + + event match { + case e: TestStarting => + val ir = IR(e.suiteClassName, e.suiteName, e.testName) + started.append(ir.toString) + + case e: TestSucceeded => + val ir = IR(e.suiteClassName, e.suiteName, e.testName) + finished.append( + TestExecutionResult.Status.SUCCESSFUL -> ir.toString + ) + + case e: TestCanceled => + val ir = IR(e.suiteClassName, e.suiteName, e.testName) + finished.append( + TestExecutionResult.Status.ABORTED -> ir.toString + ) + + case e: TestFailed => + val ir = IR(e.suiteClassName, e.suiteName, e.testName) + finished.append(TestExecutionResult.Status.FAILED -> ir.toString) + + case _ => + } + } +} diff --git a/src/test/scala/org/scalatestplus/junit5/integration/listener/package.scala b/src/test/scala/org/scalatestplus/junit5/integration/listener/package.scala new file mode 100644 index 0000000..0cbf8b1 --- /dev/null +++ b/src/test/scala/org/scalatestplus/junit5/integration/listener/package.scala @@ -0,0 +1,44 @@ +package org.scalatestplus.junit5.integration + +import org.junit.jupiter.api.Assertions +import org.junit.platform.engine.TestExecutionResult + +import scala.collection.mutable.ListBuffer + +package object listener { + + type QN = String + + trait ExecutionOrder { + + val started = new ListBuffer[QN] + val finished = new ListBuffer[(TestExecutionResult.Status, QN)] + + def clear(): Unit = { + started.clear() + finished.clear() + } + + // TODO: need to make OS-agnostic + def startedShouldBe(v: String = null): Unit = { + assert(started.nonEmpty, "no test started") + if (v == null) + throw new IllegalArgumentException("expecting ground truth for started:\n\n" + started.mkString("\n")) + Assertions.assertEquals( + "\n" + v.trim + "\n", + "\n" + started.mkString("\n").trim + "\n" + ) + } + + def finishedShouldBe(v: String = null): Unit = { + assert(started.nonEmpty, "no test finished") + if (v == null) + throw new IllegalArgumentException("expecting ground truth for finished:\n\n" + finished.mkString("\n")) + Assertions.assertEquals( + "\n" + v.trim + "\n", + "\n" + finished.mkString("\n").trim + "\n" + ) + } + } + +}