CtrlK
BlogDocsLog inGet started
Tessl Logo

tessl/maven-org-scala-js--scalajs-test-interface

Scala.js version of the sbt testing interface that provides a standardized API for test frameworks to integrate with SBT and run tests in a Scala.js (JavaScript) environment

Pending
Overview
Eval results
Files

events.mddocs/

Event Handling

Event system for reporting test results, progress, and metadata with support for different test selection patterns and structured error reporting.

Capabilities

Event Interface

Core interface for test events fired during test execution.

/**
 * An event fired by the test framework during a run.
 * Contains complete information about test execution results.
 */
trait Event {
  /**
   * The fully qualified name of a class that can rerun the suite or test.
   * @return class name for rerunning this test/suite
   */
  def fullyQualifiedName(): String
  
  /**
   * The fingerprint of the test class whose fully qualified name is returned.
   * If fingerprint.isModule indicates module, fullyQualifiedName excludes trailing $.
   * @return fingerprint used to identify the test class
   */
  def fingerprint(): Fingerprint
  
  /**
   * Additional information identifying the suite or test about which an event was fired.
   * @return selector providing specific test/suite identification
   */
  def selector(): Selector
  
  /**
   * Indicates the test result: success, failure, error, skipped, ignored, canceled, pending.
   * @return status representing the test outcome
   */
  def status(): Status
  
  /**
   * An OptionalThrowable associated with this Event.
   * Contains exception information for failures and errors.
   * @return optional throwable with error details
   */
  def throwable(): OptionalThrowable
  
  /**
   * Execution time in milliseconds, or -1 if no duration available.
   * @return duration in milliseconds or -1
   */
  def duration(): Long
}

Usage Examples:

// Create success event for test method
class MyEvent(
  className: String,
  fp: Fingerprint,
  sel: Selector,
  stat: Status,
  opt: OptionalThrowable,
  dur: Long
) extends Event {
  def fullyQualifiedName() = className
  def fingerprint() = fp
  def selector() = sel
  def status() = stat
  def throwable() = opt
  def duration() = dur
}

// Create events for different outcomes
val successEvent = new MyEvent(
  className = "com.example.MathTest",
  fp = testFingerprint,
  sel = new TestSelector("shouldAddNumbers"),
  stat = Status.Success,
  opt = new OptionalThrowable(), // empty
  dur = 150L
)

val failureEvent = new MyEvent(
  className = "com.example.MathTest", 
  fp = testFingerprint,
  sel = new TestSelector("shouldDivideByZero"),
  stat = Status.Failure,
  opt = new OptionalThrowable(new AssertionError("Division by zero should throw")),
  dur = 75L
)

val suiteEvent = new MyEvent(
  className = "com.example.MathTest",
  fp = testFingerprint,
  sel = new SuiteSelector(),
  stat = Status.Success,
  opt = new OptionalThrowable(),
  dur = 1200L
)

EventHandler Interface

Interface for handling events fired by test frameworks during execution.

/**
 * Interface implemented by clients that handle events fired by test frameworks.
 * An event handler is passed to test framework via Task.execute method.
 */
trait EventHandler {
  /**
   * Handle an event fired by the test framework.
   * @param event the event to handle
   */
  def handle(event: Event): Unit
}

Usage Examples:

// Simple logging event handler
class LoggingEventHandler(logger: Logger) extends EventHandler {
  def handle(event: Event): Unit = {
    val testName = event.selector() match {
      case suite: SuiteSelector => s"Suite: ${event.fullyQualifiedName()}"
      case test: TestSelector => s"Test: ${event.fullyQualifiedName()}.${test.testName()}"
      case nested: NestedTestSelector => 
        s"Nested: ${event.fullyQualifiedName()}.${nested.suiteId()}.${nested.testName()}"
      case _ => s"Unknown: ${event.fullyQualifiedName()}"
    }
    
    event.status() match {
      case Status.Success => 
        logger.info(s"✓ $testName (${event.duration()}ms)")
      case Status.Failure =>
        logger.error(s"✗ $testName (${event.duration()}ms)")
        if (event.throwable().isDefined()) {
          logger.trace(event.throwable().get())
        }
      case Status.Error =>
        logger.error(s"⚠ $testName - ERROR (${event.duration()}ms)")
        if (event.throwable().isDefined()) {
          logger.trace(event.throwable().get())
        }
      case Status.Skipped => 
        logger.warn(s"⚬ $testName - SKIPPED")
      case Status.Ignored =>
        logger.warn(s"⚬ $testName - IGNORED") 
      case Status.Canceled =>
        logger.warn(s"⚬ $testName - CANCELED")
      case Status.Pending =>
        logger.warn(s"⚬ $testName - PENDING")
    }
  }
}

// Aggregating event handler
class TestResultCollector extends EventHandler {
  private val results = mutable.Map[String, TestResult]()
  
  def handle(event: Event): Unit = {
    val key = s"${event.fullyQualifiedName()}.${selectorKey(event.selector())}"
    results(key) = TestResult(
      className = event.fullyQualifiedName(),
      selector = event.selector(),
      status = event.status(),
      duration = event.duration(),
      throwable = if (event.throwable().isDefined()) Some(event.throwable().get()) else None
    )
  }
  
  def getResults(): Map[String, TestResult] = results.toMap
  
  def getSummary(): TestSummary = {
    val allResults = results.values.toSeq
    TestSummary(
      total = allResults.size,
      passed = allResults.count(_.status == Status.Success),
      failed = allResults.count(_.status == Status.Failure),
      errors = allResults.count(_.status == Status.Error),
      skipped = allResults.count(r => 
        r.status == Status.Skipped || r.status == Status.Ignored || r.status == Status.Canceled),
      pending = allResults.count(_.status == Status.Pending),
      duration = allResults.map(_.duration).sum
    )
  }
}

Status Enumeration

Represents the outcome of test execution with comprehensive status types.

/**
 * Represents the status of running a test.
 * Test frameworks can decide which statuses to use and their meanings.
 */
class Status private (name: String, ordinal: Int) extends Enum[Status](name, ordinal)

object Status {
  /** Indicates a test succeeded. */
  final val Success: Status
  
  /** Indicates an "error" occurred during test execution. */
  final val Error: Status
  
  /** Indicates a "failure" occurred during test execution. */
  final val Failure: Status
  
  /** Indicates a test was skipped for any reason. */
  final val Skipped: Status
  
  /** Indicates a test was ignored (temporarily disabled with intention to fix). */
  final val Ignored: Status
  
  /** 
   * Indicates a test was canceled (unable to complete due to unmet precondition,
   * such as database being offline).
   */
  final val Canceled: Status
  
  /** 
   * Indicates a test was declared as pending (with test code and/or 
   * production code as yet unimplemented).
   */
  final val Pending: Status
  
  /** Returns array of all status values. */
  def values(): Array[Status]
  
  /** 
   * Returns status by name.
   * @param name status name
   * @return Status instance
   * @throws IllegalArgumentException if name not found
   */
  def valueOf(name: String): Status
}

Usage Examples:

// Check status types
def handleTestResult(event: Event): Unit = {
  event.status() match {
    case Status.Success => 
      println("Test passed!")
    case Status.Failure =>
      println(s"Test failed: ${event.throwable().get().getMessage}")
    case Status.Error =>
      println(s"Test error: ${event.throwable().get().getMessage}")
    case Status.Skipped | Status.Ignored | Status.Canceled =>
      println("Test was not executed")
    case Status.Pending =>
      println("Test is pending implementation")
  }
}

// Create events with different statuses
val statuses = Status.values()
println(s"Available statuses: ${statuses.map(_.name).mkString(", ")}")

// Status by name lookup
val failureStatus = Status.valueOf("Failure")
assert(failureStatus == Status.Failure)

OptionalThrowable

Container for optional exception information associated with test events.

/**
 * An optional Throwable container.
 * Used to carry exception information with test events.
 */
final class OptionalThrowable(exception: Throwable) extends Serializable {
  /** Creates empty OptionalThrowable with no exception. */
  def this()
  
  /**
   * Indicates whether this OptionalThrowable contains a Throwable.
   * @return true if contains a Throwable
   */
  def isDefined(): Boolean
  
  /**
   * Indicates whether this OptionalThrowable is empty.
   * @return true if contains no Throwable
   */
  def isEmpty(): Boolean
  
  /**
   * Returns the contained Throwable if defined.
   * @return contained Throwable
   * @throws IllegalStateException if not defined
   */
  def get(): Throwable
}

Usage Examples:

// Create OptionalThrowable with exception
val withException = new OptionalThrowable(new RuntimeException("Test failed"))
if (withException.isDefined()) {
  println(s"Exception: ${withException.get().getMessage}")
}

// Create empty OptionalThrowable
val empty = new OptionalThrowable()
assert(empty.isEmpty())

// Safe exception handling
def safeGetException(opt: OptionalThrowable): Option[Throwable] = {
  if (opt.isDefined()) Some(opt.get()) else None
}

// Create events with optional exceptions
val successEvent = createEvent(Status.Success, new OptionalThrowable())
val failureEvent = createEvent(
  Status.Failure, 
  new OptionalThrowable(new AssertionError("Expected 5 but was 3"))
)

Event Patterns

Event Creation in Tasks

Tasks should create and fire events for all test outcomes:

class MyTask(taskDef: TaskDef) extends Task {
  def execute(eventHandler: EventHandler, loggers: Array[Logger]): Array[Task] = {
    val startTime = System.currentTimeMillis()
    
    try {
      // Execute test logic
      val result = runTest(taskDef.fullyQualifiedName(), taskDef.selectors())
      val duration = System.currentTimeMillis() - startTime
      
      result match {
        case TestSuccess =>
          val event = new MyEvent(
            taskDef.fullyQualifiedName(),
            taskDef.fingerprint(),
            new SuiteSelector(),
            Status.Success,
            new OptionalThrowable(),
            duration
          )
          eventHandler.handle(event)
          
        case TestFailure(assertion) =>
          val event = new MyEvent(
            taskDef.fullyQualifiedName(),
            taskDef.fingerprint(), 
            new SuiteSelector(),
            Status.Failure,
            new OptionalThrowable(assertion),
            duration
          )
          eventHandler.handle(event)
      }
      
      Array.empty // No nested tasks
      
    } catch {
      case t: Throwable =>
        val duration = System.currentTimeMillis() - startTime
        val errorEvent = new MyEvent(
          taskDef.fullyQualifiedName(),
          taskDef.fingerprint(),
          new SuiteSelector(),
          Status.Error,
          new OptionalThrowable(t),
          duration
        )
        eventHandler.handle(errorEvent)
        Array.empty
    }
  }
}

Selector-Specific Events

Different selector types should fire appropriate events:

def fireEventsForSelectors(
  selectors: Array[Selector],
  className: String,
  fingerprint: Fingerprint,
  eventHandler: EventHandler
): Unit = {
  
  selectors.foreach {
    case _: SuiteSelector =>
      // Fire suite-level event
      val event = createSuiteEvent(className, fingerprint, Status.Success)
      eventHandler.handle(event)
      
    case testSel: TestSelector =>
      // Fire individual test event
      val event = createTestEvent(className, fingerprint, testSel, Status.Success)
      eventHandler.handle(event)
      
    case nestedSel: NestedTestSelector =>
      // Fire nested test event
      val event = createNestedTestEvent(className, fingerprint, nestedSel, Status.Success)
      eventHandler.handle(event)
      
    case wildcardSel: TestWildcardSelector =>
      // Fire events for matching tests
      val matchingTests = findTestsMatching(wildcardSel.testWildcard())
      matchingTests.foreach { testName =>
        val testSelector = new TestSelector(testName)
        val event = createTestEvent(className, fingerprint, testSelector, Status.Success)
        eventHandler.handle(event)
      }
  }
}

Error Handling

Exception Information

Events should include detailed exception information for failures and errors:

def createFailureEvent(
  className: String,
  fingerprint: Fingerprint,
  selector: Selector,
  exception: Throwable,
  duration: Long
): Event = {
  
  val status = exception match {
    case _: AssertionError => Status.Failure
    case _: Exception => Status.Error
    case _ => Status.Error
  }
  
  new MyEvent(
    className,
    fingerprint,
    selector,
    status,
    new OptionalThrowable(exception),
    duration
  )
}

Event Handler Error Recovery

Event handlers should handle processing errors gracefully:

class RobustEventHandler extends EventHandler {
  def handle(event: Event): Unit = {
    try {
      processEvent(event)
    } catch {
      case t: Throwable =>
        // Log error but don't propagate to avoid breaking test execution
        System.err.println(s"Event handler error: ${t.getMessage}")
        t.printStackTrace()
    }
  }
  
  private def processEvent(event: Event): Unit = {
    // Event processing logic that might throw
  }
}

Install with Tessl CLI

npx tessl i tessl/maven-org-scala-js--scalajs-test-interface

docs

discovery.md

events.md

execution.md

framework.md

index.md

logging.md

tile.json