8000 Allow to preconfigure multiple test frameworks by Gedochao · Pull Request #3653 · VirtusLab/scala-cli · GitHub
[go: up one dir, main page]
More Web Proxy on the site http://driver.im/
Skip to content

Allow to preconfigure multiple test frameworks #3653

New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
16 changes: 8 additions & 8 deletions modules/build/src/main/scala/scala/build/internal/Runner.scala
Original file line number Diff line number Diff line change
Expand Up @@ -407,7 +407,7 @@ object Runner {
entrypoint: File,
requireTests: Boolean,
args: Seq[String],
testFrameworkOpt: Option[String],
predefinedTestFrameworks: Seq[String],
logger: Logger,
jsDom: Boolean,
esModule: Boolean
Expand Down Expand Up @@ -446,9 +446,9 @@ object Runner {
logger.debug(s"JS tests class path: $classPath")

val parentInspector = new AsmTestRunner.ParentInspector(classPath)
val foundFrameworkNames: List[String] = testFrameworkOpt match {
case some @ Some(_) => some.toList
case None => value(frameworkNames(classPath, parentInspector, logger)).toList
val foundFrameworkNames: List[String] = predefinedTestFrameworks match {
case f if f.nonEmpty => f.toList
case Nil => value(frameworkNames(classPath, parentInspector, logger)).toList
}

val res =
Expand Down Expand Up @@ -485,7 +485,7 @@ object Runner {
def testNative(
classPath: Seq[Path],
launcher: File,
frameworkNameOpt: Option[String],
predefinedTestFrameworks: Seq[String],
requireTests: Boolean,
args: Seq[String],
logger: Logger
Expand All @@ -494,9 +494,9 @@ object Runner {
logger.debug(s"Native tests class path: $classPath")

val parentInspector = new AsmTestRunner.ParentInspector(classPath)
val foundFrameworkNames: List[String] = frameworkNameOpt match {
case Some(fw) => List(fw)
case None => value(frameworkNames(classPath, parentInspector, logger)).toList
val foundFrameworkNames: List[String] = predefinedTestFrameworks match {
case f if f.nonEmpty => f.toList
case Nil => value(frameworkNames(classPath, parentInspector, logger)).toList
}

val config = ScalaNativeTestAdapter.Config()
Expand Down
19 changes: 11 additions & 8 deletions modules/cli/src/main/scala/scala/cli/commands/test/Test.scala
Original file line number Diff line number Diff line change
Expand Up @@ -46,7 +46,7 @@ object Test extends ScalaCommand[TestOptions] {
sharedJava.allJavaOpts.map(JavaOpt(_)).map(Positioned.commandLine)
),
testOptions = baseOptions.testOptions.copy(
frameworkOpt = testFramework.map(_.trim).filter(_.nonEmpty),
frameworks = testFrameworks.map(_.trim).filter(_.nonEmpty).map(Positioned.commandLine),
testOnly = testOnly.map(_.trim).filter(_.nonEmpty)
),
internalDependencies = baseOptions.internalDependencies.copy(
Expand Down Expand Up @@ -192,7 +192,7 @@ object Test extends ScalaCommand[TestOptions] {
allowExecve: Boolean
): Either[BuildException, Int] = either {

val testFrameworkOpt = build.options.testOptions.frameworkOpt
val predefinedTestFrameworks = build.options.testOptions.frameworks

build.options.platform.value match {
case Platform.JS =>
Expand All @@ -215,7 +215,7 @@ object Test extends ScalaCommand[TestOptions] {
js.toIO,
requireTests,
args,
testFrameworkOpt,
predefinedTestFrameworks.map(_.value),
logger,
build.options.scalaJsOptions.dom.getOrElse(false),
esModule
Expand All @@ -232,7 +232,7 @@ object Test extends ScalaCommand[TestOptions] {
Runner.testNative(
build.fullClassPath.map(_.toNIO),
launcher.toIO,
testFrameworkOpt,
predefinedTestFrameworks.map(_.value),
requireTests,
args,
logger
Expand All @@ -242,15 +242,18 @@ object Test extends ScalaCommand[TestOptions] {
case Platform.JVM =>
val classPath = build.fullClassPathMaybeAsJar(asJar)

val testFrameworkOpt0 = testFrameworkOpt.orElse {
findTestFramework(classPath.map(_.toNIO), logger)
}
val predefinedTestFrameworks0 =
predefinedTestFrameworks match {
case f if f.nonEmpty => f
case Nil =>
findTestFramework(classPath.map(_.toNIO), logger).map(Positioned.none).toList
}
val testOnly = build.options.testOptions.testOnly

val extraArgs =
(if requireTests then Seq("--require-tests") else Nil) ++
build.options.internal.verbosity.map(v => s"--verbosity=$v") ++
testFrameworkOpt0.map(fw => s"--test-framework=$fw").toSeq ++
predefinedTestFrameworks0.map(_.value).map(fw => s"--test-framework=$fw") ++
testOnly.map(to => s"--test- ++
Seq("--") ++ args

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -7,37 +7,36 @@ import scala.cli.commands.shared._
import scala.cli.commands.tags

@HelpMessage(TestOptions.helpMessage, "", TestOptions.detailedHelpMessage)
// format: off
final case class TestOptions(
@Recurse
shared: SharedOptions = SharedOptions(),
shared: SharedOptions = SharedOptions(),
@Recurse
sharedJava: SharedJavaOptions = SharedJavaOptions(),
sharedJava: SharedJavaOptions = SharedJavaOptions(),
@Recurse
watch: SharedWatchOptions = SharedWatchOptions(),
watch: SharedWatchOptions = SharedWatchOptions(),
@Recurse
compileCross: CrossOptions = CrossOptions(),

compileCross: CrossOptions = CrossOptions(),
@Group(HelpGroup.Test.toString)
@HelpMessage("Name of the test framework's runner class to use while running tests")
@HelpMessage(
"""Names of the test frameworks' runner classes to use while running tests.
|Skips framework lookup and only runs passed frameworks.""".stripMargin
)
@ValueDescription("class-name")
@Tag(tags.should)
@Tag(tags.inShortHelp)
testFramework: Option[String] = None,

@Name("testFramework")
testFrameworks: List[String] = Nil,
@Group(HelpGroup.Test.toString)
@Tag(tags.should)
@Tag(tags.inShortHelp)
@HelpMessage("Fail if no test suites were run")
requireTests: Boolean = false,
requireTests: Boolean = false,
@Group(HelpGroup.Test.toString)
@Tag(tags.should)
@Tag(tags.inShortHelp)
@HelpMessage("Specify a glob pattern to filter the tests suite to be run.")
testOnly: Option[String] = None

testOnly: Option[String] = None
) extends HasSharedOptions
// format: on

object TestOptions {
implicit lazy val parser: Parser[TestOptions] = Parser.derive
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -148,7 +148,7 @@ final case class MillProjectDescriptor(
Seq.empty
}
val parentInspector = new AsmTestRunner.ParentInspector(testClassPath)
val frameworkName0 = options.testOptions.frameworkOpt.orElse {
val frameworkName0 = options.testOptions.frameworks.headOption.orElse {
frameworkNames(testClassPath, parentInspector, logger).toOption
.flatMap(_.headOption) // TODO: handle multiple frameworks here
}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -261,7 +261,7 @@ final case class SbtProjectDescriptor(
}

val parentInspector = new AsmTestRunner.ParentInspector(testClassPath)
val frameworkName0 = options.testOptions.frameworkOpt.orElse {
val frameworkName0 = options.testOptions.frameworks.headOption.orElse {
frameworkNames(testClassPath, parentInspector, logger).toOption
.flatMap(_.headOption) // TODO: handle multiple frameworks here
}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -8,20 +8,29 @@ import scala.cli.commands.SpecificationLevel

@DirectiveGroupName("Test framework")
@DirectiveExamples("//> using testFramework utest.runner.Framework")
@DirectiveExamples("//> using test.frameworks utest.runner.Framework munit.Framework")
@DirectiveUsage(
"using testFramework _class_name_",
"""using testFramework _class_name_
|
|using testFrameworks _class_name_ _another_class_name_
|
|using test.framework _class_name_
|
|using test.frameworks _class_name_ _another_class_name_""".stripMargin,
"`//> using testFramework` _class-name_"
)
@DirectiveDescription("Set the test framework")
@DirectiveLevel(SpecificationLevel.SHOULD)
final case class Tests(
@DirectiveName("testFramework")
@DirectiveName("test.framework")
testFramework: Option[String] = None
@DirectiveName("test.frameworks")
testFrameworks: Seq[Positioned[String]] = Nil
) extends HasBuildOptions {
def buildOptions: Either[BuildException, BuildOptions] = {
val buildOpt = BuildOptions(
testOptions = TestOptions(
frameworkOpt = testFramework
frameworks = testFrameworks
)
)
Right(buildOpt)
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -949,5 +949,62 @@ abstract class TestTestDefinitions extends ScalaCliSuite with TestScalaVersionAr
}
}
}

test(s"multiple explicitly preconfigured test frameworks$platformDescription") {
TestUtil.retryOnCi() {
val expectedMessages @ Seq(scalatestMessage, munitMessage, customMessage) =
Seq("Hello from scalatest", "Hello from Munit", "Hello from custom framework")
TestInputs(
os.rel / "project.scala" ->
s"""//> using test.dep org.scalatest::scalatest::3.2.19
|//> using dep com.lihaoyi::utest::$utestVersion
|//> using test.dep org.scalameta::munit::$munitVersion
|//> using test.frameworks org.scalatest.tools.Framework munit.Framework custom.CustomFramework
|""".stripMargin,
os.rel / "scalatest.test.scala" ->
s"""import org.scalatest.flatspec.AnyFlatSpec
|
|class ScalaTestSpec extends AnyFlatSpec {
| "example" should "work" in {
| assertResult(1)(1)
| println("$scalatestMessage")
| }
|}
|""".stripMargin,
os.rel / "munit.test.scala" ->
s"""import munit.FunSuite
|
|class Munit extends FunSuite {
| test("foo") {
| assert(2 + 2 == 4)
| println("$munitMessage")
| }
|}
|""".stripMargin,
os.rel / "custom.test.scala" ->
s"""package custom
|
|class CustomFramework extends utest.runner.Framework {
| override def setup(): Unit =
| println("$customMessage")
|}""".stripMargin
).fromRoot { root =>
val r =
os.proc(
TestUtil.cli,
"test",
extraOptions,
".",
platformOptions
).call(cwd = root)
val output = r.out.trim()
expect(output.nonEmpty)
expectedMessages.foreach { expectedMessage =>
expect(output.contains(expectedMessage))
expect(countSubStrings(output, expectedMessage) == 1)
}
}
}
}
}
}
Original file line number Diff line number Diff line change
@@ -1,7 +1,9 @@
package scala.build.options

import scala.build.Positioned

final case class TestOptions(
frameworkOpt: Option[String] = None,
frameworks: Seq[Positioned[String]] = Nil,
testOnly: Option[String] = None
)

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -28,23 +28,23 @@ object DynamicTestRunner {

def main(args: Array[String]): Unit = {

val (testFrameworkOpt, requireTests, verbosity, testOnly, args0) = {
val (testFrameworks, requireTests, verbosity, testOnly, args0) = {
@tailrec
def parse(
testFrameworkOpt: Option[String],
testFrameworks: List[String],
reverseTestArgs: List[String],
requireTests: Boolean,
verbosity: Int,
testOnly: Option[String],
args: List[String]
): (Option[String], Boolean, Int, Option[String], List[String]) =
): (List[String], Boolean, Int, Option[String], List[String]) =
args match {
case Nil => (testFrameworkOpt, requireTests, verbosity, testOnly, reverseTestArgs.reverse)
case Nil => (testFrameworks, requireTests, verbosity, testOnly, reverseTestArgs.reverse)
case "--" :: t =>
(testFrameworkOpt, requireTests, verbosity, testOnly, reverseTestArgs.reverse ::: t)
(< E377 span class="x x-first x-last">testFrameworks, requireTests, verbosity, testOnly, reverseTestArgs.reverse ::: t)
case h :: t if h.startsWith("--test-framework=") =>
parse(
Some(h.stripPrefix("--test-framework=")),
testFrameworks ++ List(h.stripPrefix("--test-framework=")),
reverseTestArgs,
requireTests,
verbosity,
Expand All @@ -53,7 +53,7 @@ object DynamicTestRunner {
)
case h :: t if h.startsWith("--test- =>
parse(
testFrameworkOpt,
testFrameworks,
reverseTestArgs,
requireTests,
verbosity,
Expand All @@ -62,41 +62,47 @@ object DynamicTestRunner {
)
case h :: t if h.startsWith("--verbosity=") =>
parse(
testFrameworkOpt,
testFrameworks,
reverseTestArgs,
requireTests,
h.stripPrefix("--verbosity=").toInt,
testOnly,
t
)
case "--require-tests" :: t =>
parse(testFrameworkOpt, reverseTestArgs, true, verbosity, testOnly, t)
parse(testFrameworks, reverseTestArgs, true, verbosity, testOnly, t)
case h :: t =>
parse(testFrameworkOpt, h :: reverseTestArgs, requireTests, verbosity, testOnly, t)
parse(testFrameworks, h :: reverseTestArgs, requireTests, verbosity, testOnly, t)
}

parse(None, Nil, false, 0, None, args.toList)
parse(Nil, Nil, false, 0, None, args.toList)
}

val logger = Logger(verbosity)

if (testFrameworks.nonEmpty) logger.debug(
s"""Directly passed ${testFrameworks.length} test frameworks:
| - ${testFrameworks.mkString("\n - ")}""".stripMargin
)

val classLoader = Thread.currentThread().getContextClassLoader
val classPath0 = TestRunner.classPath(classLoader)
val frameworks = testFrameworkOpt
.map(loadFramework(classLoader, _))
.map(Seq(_))
.getOrElse {
getFrameworksToRun(
frameworkServices = findFrameworkServices(classLoader),
frameworks = findFrameworks(classPath0, classLoader, TestRunner.commonTestFrameworks)
)(logger) match {
case f if f.nonEmpty => f
case _ if verbosity >= 2 => sys.error("No test framework found")
case _ =>
System.err.println("No test framework found")
sys.exit(1)
val frameworks =
Option(testFrameworks)
.filter(_.nonEmpty)
.map(_.map(loadFramework(classLoader, _)).toSeq)
.getOrElse {
getFrameworksToRun(
frameworkServices = findFrameworkServices(classLoader),
frameworks = findFrameworks(classPath0, classLoader, TestRunner.commonTestFrameworks)
)(logger) match {
case f if f.nonEmpty => f
case _ if verbosity >= 2 => sys.error("No test framework found")
case _ =>
System.err.println("No test framework found")
sys.exit(1)
}
}
}
def classes = {
val keepJars = false // look into dependencies, much slower
listClasses(classPath0, keepJars).map(name => classLoader.loadClass(name))
Expand Down
7 changes: 5 additions & 2 deletions website/docs/reference/cli-options.md
Original file line number Diff line number Diff line change
Expand Up @@ -1762,9 +1762,12 @@ Available in commands:

<!-- Automatically generated, DO NOT EDIT MANUALLY -->

### `--test-framework`
### `--test-frameworks`

Name of the test framework's runner class to use while running tests
Aliases: `--test-framework`

Names of the test frameworks' runner classes to use while running tests.
Skips framework lookup and only runs passed frameworks.

### `--require-tests`

Expand Down
Loading
0