Skip to content

Allow classes to be used as test suite #393

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
merged 2 commits into from
Jul 28, 2025
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
5 changes: 3 additions & 2 deletions readme.md
Original file line number Diff line number Diff line change
Expand Up @@ -157,7 +157,7 @@ package test.utest.examples

import utest._

object HelloTests extends TestSuite{
class HelloTests extends TestSuite{
val tests = Tests{
test("test1"){
throw new Exception("test1")
Expand Down Expand Up @@ -201,6 +201,7 @@ Tests: 3, Passed: 1, Failed: 2
The tests are run one at a time, and any tests that fail with an exception have
their stack trace printed. If the number of tests is large, a separate
results-summary and failures-summary will be shown after all tests have run.
Tests can either be inside zero-parameter `class`es (as shown above) or static `object`s.

Nesting Tests
-------------
Expand All @@ -216,7 +217,7 @@ package test.utest.examples

import utest._

object NestedTests extends TestSuite{
class NestedTests extends TestSuite{
val tests = Tests{
val x = 1
test("outer1"){
Expand Down
47 changes: 47 additions & 0 deletions utest/src-3-jvm/utest/PortableScalaReflectExcerpts.scala
Original file line number Diff line number Diff line change
Expand Up @@ -56,6 +56,25 @@ object PortableScalaReflectExcerpts {
c(clazz)
}

private def isInstantiatableClass(clazz: Class[_]): Boolean = {
/* A local class will have a non-null *enclosing* class, but a null
* *declaring* class. For a top-level class, both are null, and for an
* inner class (non-local), both are the same non-null class.
*/
def isLocalClass: Boolean =
clazz.getEnclosingClass() != clazz.getDeclaringClass()

(clazz.getModifiers() & Modifier.ABSTRACT) == 0 &&
clazz.getConstructors().length > 0 &&
!isModuleClass(clazz) &&
!isLocalClass
}

def lookupInstantiatableClass(fqcn: String,
loader: ClassLoader): Option[InstantiatableClass] = {
load(fqcn, loader).filter(isInstantiatableClass).map(new InstantiatableClass(_))
}

final class LoadableModuleClass private[PortableScalaReflectExcerpts] (val runtimeClass: Class[_]) {
/** Loads the module instance and returns it.
*
Expand All @@ -76,4 +95,32 @@ object PortableScalaReflectExcerpts {
}
}
}

/** A wrapper for a class that can be instantiated.
*
* @param runtimeClass
* The `java.lang.Class[_]` representing the class.
*/
final class InstantiatableClass (val runtimeClass: Class[_]) {

/** Instantiates this class using its zero-argument constructor.
*
* @throws java.lang.InstantiationException
* (caused by a `NoSuchMethodException`)
* If this class does not have a public zero-argument constructor.
*/
def newInstance(): Any = {
try {
runtimeClass.newInstance()
} catch {
case e: IllegalAccessException =>
/* The constructor exists but is private; make it look like it does not
* exist at all.
*/
throw new InstantiationException(runtimeClass.getName).initCause(
new NoSuchMethodException(runtimeClass.getName + ".<init>()"))
}
}

}
}
3 changes: 2 additions & 1 deletion utest/src-js/utest/PlatformShims.scala
Original file line number Diff line number Diff line change
Expand Up @@ -22,7 +22,8 @@ object PlatformShims {
def loadModule(name: String, loader: ClassLoader): Any = {
Reflect
.lookupLoadableModuleClass(name + "$", loader)
.map(_.loadModule())
.orElse(Reflect.lookupInstantiatableClass(name, loader).map(_.newInstance()))
.getOrElse(throw new ClassNotFoundException(name))
.loadModule()
}
}
3 changes: 2 additions & 1 deletion utest/src-jvm/utest/PlatformShims.scala
Original file line number Diff line number Diff line change
Expand Up @@ -12,6 +12,7 @@ object PlatformShims extends PlatformShimsVersionSpecific {
def loadModule(name: String, loader: ClassLoader): Any =
Reflect
.lookupLoadableModuleClass(name + "$", loader)
.map(_.loadModule())
.orElse(Reflect.lookupInstantiatableClass(name, loader).map(_.newInstance()))
.getOrElse(throw new ClassNotFoundException(name))
.loadModule()
}
3 changes: 2 additions & 1 deletion utest/src-native/utest/PlatformShims.scala
Original file line number Diff line number Diff line change
Expand Up @@ -18,7 +18,8 @@ object PlatformShims {
def loadModule(name: String, loader: ClassLoader): Any = {
Reflect
.lookupLoadableModuleClass(name + "$")
.map(_.loadModule())
.orElse(Reflect.lookupInstantiatableClass(name).map(_.newInstance()))
.getOrElse(throw new ClassNotFoundException(name))
.loadModule()
}
}
6 changes: 6 additions & 0 deletions utest/src/utest/runner/Fingerprint.scala
Original file line number Diff line number Diff line change
Expand Up @@ -8,3 +8,9 @@ object Fingerprint extends SubclassFingerprint {
def isModule() = true
def requireNoArgConstructor() = true
}

object ClassFingerprint extends SubclassFingerprint {
def superclassName() = "utest.TestSuite"
def isModule() = false
def requireNoArgConstructor() = true
}
2 changes: 1 addition & 1 deletion utest/src/utest/runner/Framework.scala
Original file line number Diff line number Diff line change
Expand Up @@ -56,7 +56,7 @@ class Framework extends sbt.testing.Framework with framework.Formatter {
def startHeader(path: String) = DefaultFormatters.renderBanner("Running Tests" + path)


final def fingerprints(): Array[sbt.testing.Fingerprint] = Array(Fingerprint)
final def fingerprints(): Array[sbt.testing.Fingerprint] = Array(Fingerprint, ClassFingerprint)

final def runner(args: Array[String],
remoteArgs: Array[String],
Expand Down
2 changes: 1 addition & 1 deletion utest/test/src/test/utest/AfterEachOnFailureTest.scala
Original file line number Diff line number Diff line change
Expand Up @@ -6,7 +6,7 @@ import utest.framework.ExecutionContext.RunNow
/**
* Put executor.utestAfterEach(path) into finally block to make sure it will be executed regardless of the test failing.
*/
object AfterEachOnFailureTest extends TestSuite {
class AfterEachOnFailureTest extends TestSuite{

private var res:SomeResource = _

Expand Down
2 changes: 1 addition & 1 deletion utest/test/src/test/utest/AssertsTests.scala
Original file line number Diff line number Diff line change
Expand Up @@ -8,7 +8,7 @@ import utest._
* since it is the thing that is meant to be *testing* all the fancy uTest
* asserts, we can't assume they work.
*/
object AssertsTests extends utest.TestSuite{
class AssertsTests extends utest.TestSuite{

implicit val colors: shaded.pprint.TPrintColors = shaded.pprint.TPrintColors.Colors
def tests = Tests{
Expand Down
6 changes: 3 additions & 3 deletions utest/test/src/test/utest/BeforeAfterAllFailureTest.scala
Original file line number Diff line number Diff line change
Expand Up @@ -4,11 +4,11 @@ import utest._
import utest.framework.StackMarker


object BeforeAfterAllFailureTest extends TestSuite {
class BeforeAfterAllFailureTest extends TestSuite{

// Hide this fella inside the outer object, because we don't want uTest's own
// test suite to discover him: we want to run him manually
object AfterAllFailureTest extends TestSuite {
object AfterAllFailureTest extends TestSuite{

override def utestAfterAll(): Unit = {
throw new Exception("Failed After!")
Expand All @@ -26,7 +26,7 @@ object BeforeAfterAllFailureTest extends TestSuite {

// No tests for this fella because currently, error handling of test suite
// initialization is paid done in the SBT logic, not in TestRunner
object BeforeAllFailureTest extends TestSuite {
class BeforeAllFailureTest extends TestSuite{
throw new Exception("Failed Before!")
val tests = Tests {
test("test"){
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -4,7 +4,7 @@ import utest._
import scala.concurrent.{Future, ExecutionContext}
import concurrent.duration._

object BeforeAfterEachFailureTests extends TestSuite {
class BeforeAfterEachFailureTests extends TestSuite{
implicit val ec: ExecutionContext = utest.framework.ExecutionContext.RunNow
private var failNextBeforeEach = false
private var failAfterEach = false
Expand Down
2 changes: 1 addition & 1 deletion utest/test/src/test/utest/ByNameTests.scala
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
package test.utest
import utest._
object ByNameTests extends utest.TestSuite {
class ByNameTests extends utest.TestSuite {
case class X(dummy: Int = 0, x: Int = 0)
def doAction(action: => Any): Unit = ()
val tests = Tests{
Expand Down
2 changes: 1 addition & 1 deletion utest/test/src/test/utest/DisablePrint2Tests.scala
Original file line number Diff line number Diff line change
Expand Up @@ -2,7 +2,7 @@ package test.utest
import utest._
import utest.framework.Formatter

object DisablePrint2Tests extends utest.TestSuite{
class DisablePrint2Tests extends utest.TestSuite{
override def utestFormatter = new Formatter {
override def formatSingle(path: Seq[String], res: utest.framework.Result) = None
override def formatColor = false
Expand Down
2 changes: 1 addition & 1 deletion utest/test/src/test/utest/DisablePrintTests.scala
Original file line number Diff line number Diff line change
Expand Up @@ -2,7 +2,7 @@ package test.utest
import utest._
import utest.framework.{Formatter, HTree, Result, Tree}

object DisablePrintTests extends utest.TestSuite{
class DisablePrintTests extends utest.TestSuite{
override def utestFormatter = new Formatter {
override def formatSummary(topLevelName: String, results: HTree[String, Result]) = None
override def formatColor = false
Expand Down
2 changes: 1 addition & 1 deletion utest/test/src/test/utest/FrameworkAsyncTests.scala
Original file line number Diff line number Diff line change
Expand Up @@ -4,7 +4,7 @@ import utest._
import scala.concurrent.{Future, ExecutionContext}
import concurrent.duration._

object FrameworkAsyncTests extends TestSuite {
class FrameworkAsyncTests extends TestSuite{
implicit val ec: ExecutionContext = utest.framework.ExecutionContext.RunNow
private val isNative = sys.props("java.vm.name") == "Scala Native"

Expand Down
2 changes: 1 addition & 1 deletion utest/test/src/test/utest/FrameworkTests.scala
Original file line number Diff line number Diff line change
Expand Up @@ -10,7 +10,7 @@ import scala.util.Failure
import utest.framework.ExecutionContext.RunNow


object FrameworkTests extends utest.TestSuite{
class FrameworkTests extends utest.TestSuite{

override def utestBeforeEach(path: Seq[String]): Unit = println("RUN " + path.mkString("."))
override def utestAfterEach(path: Seq[String]): Unit = println("END " + path.mkString("."))
Expand Down
2 changes: 1 addition & 1 deletion utest/test/src/test/utest/FutureCrashTest.scala
Original file line number Diff line number Diff line change
Expand Up @@ -2,7 +2,7 @@ package test.utest
import utest._
import concurrent.{Future, ExecutionContext}

object FutureCrashTest extends TestSuite {
class FutureCrashTest extends TestSuite{
def wrapping[T](f: => T):T = {
f
}
Expand Down
2 changes: 1 addition & 1 deletion utest/test/src/test/utest/FutureTest.scala
Original file line number Diff line number Diff line change
Expand Up @@ -2,7 +2,7 @@ package test.utest
import utest._
import concurrent.{Future, ExecutionContext}

object FutureTest extends TestSuite {
class FutureTest extends TestSuite{
implicit val ec: ExecutionContext = ExecutionContext.global
@volatile var flag = false

Expand Down
2 changes: 1 addition & 1 deletion utest/test/src/test/utest/MergeTestsTest.scala
Original file line number Diff line number Diff line change
Expand Up @@ -20,7 +20,7 @@ abstract class MergeSubTests2 extends TestSuite {
}
}

object MergeTestsTest extends TestSuite {
class MergeTestsTest extends TestSuite{

val x = new MergeSubTests1 {}
val y = new MergeSubTests2 {}
Expand Down
2 changes: 1 addition & 1 deletion utest/test/src/test/utest/QueryTests.scala
Original file line number Diff line number Diff line change
Expand Up @@ -6,7 +6,7 @@ import utest.TestQueryParser.parse
import utest._


object QueryTests extends utest.TestSuite{
class QueryTests extends utest.TestSuite{
def check(a: Either[String, TestQueryParser#Trees],
b: Either[String, TestQueryParser#Trees]) = {
Predef.assert(a == b, a)
Expand Down
14 changes: 7 additions & 7 deletions utest/test/src/test/utest/RetryTests.scala
Original file line number Diff line number Diff line change
Expand Up @@ -9,7 +9,7 @@ class FlakyThing{
if (runs < 2) throw new Exception("Flaky!")
}
}
object SuiteRetryTests extends TestSuite with TestSuite.Retries{
class SuiteRetryTests extends TestSuite with TestSuite.Retries{
override val utestRetryCount = 3
val flaky = new FlakyThing
def tests = Tests{
Expand All @@ -19,7 +19,7 @@ object SuiteRetryTests extends TestSuite with TestSuite.Retries{
}
}

object SuiteManualRetryTests extends utest.TestSuite{
class SuiteManualRetryTests extends utest.TestSuite{
override def utestWrap(path: Seq[String], body: => Future[Any])(implicit ec: ExecutionContext): Future[Any] = {
def rec(count: Int): Future[Any] = {
utestBeforeEach(path)
Expand All @@ -41,7 +41,7 @@ object SuiteManualRetryTests extends utest.TestSuite{
}
}

object SuiteRetryBeforeEachTests extends TestSuite with TestSuite.Retries {
class SuiteRetryBeforeEachTests extends TestSuite with TestSuite.Retries {
private var x = 0
override val utestRetryCount = 3
override def utestBeforeEach(path: Seq[String]): Unit = {
Expand All @@ -57,7 +57,7 @@ object SuiteRetryBeforeEachTests extends TestSuite with TestSuite.Retries {
}
}

object SuiteRetryBeforeAllTests extends TestSuite with TestSuite.Retries {
class SuiteRetryBeforeAllTests extends TestSuite with TestSuite.Retries {
override val utestRetryCount = 3
var x = 100
val flaky = new FlakyThing
Expand All @@ -82,7 +82,7 @@ object SuiteRetryBeforeAllTests extends TestSuite with TestSuite.Retries {
}
}

object LocalRetryTests extends utest.TestSuite{
class LocalRetryTests extends utest.TestSuite{
val flaky = new FlakyThing
def tests = Tests{
test("hello") - retry(3){
Expand All @@ -91,7 +91,7 @@ object LocalRetryTests extends utest.TestSuite{
}
}

object SuiteRetryBeforeEachFailedTests extends TestSuite with TestSuite.Retries {
class SuiteRetryBeforeEachFailedTests extends TestSuite with TestSuite.Retries {
override val utestRetryCount = 3
override def utestBeforeEach(path: Seq[String]): Unit = {
flaky.run()
Expand All @@ -104,7 +104,7 @@ object SuiteRetryBeforeEachFailedTests extends TestSuite with TestSuite.Retries
}
}

object SuiteRetryAfterEachFailedTests extends TestSuite with TestSuite.Retries {
class SuiteRetryAfterEachFailedTests extends TestSuite with TestSuite.Retries {
val flaky = new FlakyThing
override val utestRetryCount = 1
override def utestAfterEach(path: Seq[String]): Unit = {
Expand Down
4 changes: 2 additions & 2 deletions utest/test/src/test/utest/examples/BeforeAfterAllTest.scala
Original file line number Diff line number Diff line change
Expand Up @@ -3,7 +3,7 @@ package test.utest.examples
import utest._
import scala.concurrent.Future

object BeforeAfterAllSimpleTests extends TestSuite {
class BeforeAfterAllSimpleTests extends TestSuite{
println("on object body, aka: before all")

override def utestAfterAll(): Unit = {
Expand All @@ -22,7 +22,7 @@ object BeforeAfterAllSimpleTests extends TestSuite {
}
}

object BeforeAfterAllTests extends TestSuite {
class BeforeAfterAllTests extends TestSuite{
var x = 100
println(s"starting with x: $x")

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -2,7 +2,7 @@ package test.utest.examples

import utest._

object BeforeAfterEachTests extends TestSuite {
class BeforeAfterEachTests extends TestSuite{
var x = 0
override def utestBeforeEach(path: Seq[String]): Unit = {
println(s"on before each [${path.mkString("=>")}] x: $x")
Expand Down
2 changes: 1 addition & 1 deletion utest/test/src/test/utest/examples/HelloTests.scala
Original file line number Diff line number Diff line change
@@ -1,7 +1,7 @@
package test.utest.examples

import utest._
object HelloTests extends TestSuite{
class HelloTests extends TestSuite{
val tests = Tests{
test("test1"){
// throw new Exception("test1")
Expand Down
2 changes: 1 addition & 1 deletion utest/test/src/test/utest/examples/NestedTests.scala
Original file line number Diff line number Diff line change
Expand Up @@ -3,7 +3,7 @@ package test.utest.examples

import utest._

object NestedTests extends TestSuite{
class NestedTests extends TestSuite{
val tests = Tests{
val x = 1
test("outer1"){
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -2,7 +2,7 @@ package test.utest.examples

import utest._

object SeparateSetupTests extends TestSuite{
class SeparateSetupTests extends TestSuite{
val tests = Tests{
var x = 0
test("outer1"){
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -2,7 +2,7 @@ package test.utest.examples

import utest._

object SharedFixturesTests extends TestSuite{
class SharedFixturesTests extends TestSuite{
var x = 0
val tests = Tests{
test("outer1"){
Expand Down
2 changes: 1 addition & 1 deletion utest/test/src/test/utest/examples/TestPathTests.scala
Original file line number Diff line number Diff line change
Expand Up @@ -2,7 +2,7 @@ package test.utest.examples

import utest._

object TestPathTests extends TestSuite{
class TestPathTests extends TestSuite{
val tests = Tests{
test("testPath"){
test("foo"){
Expand Down
Loading