Skip to content
This repository has been archived by the owner on Aug 19, 2024. It is now read-only.

Commit

Permalink
Split out TestFailedException into ScalaTest env (#27)
Browse files Browse the repository at this point in the history
  • Loading branch information
ducky64 authored Mar 11, 2019
1 parent 8a2f45f commit 83f530a
Show file tree
Hide file tree
Showing 3 changed files with 79 additions and 84 deletions.
81 changes: 0 additions & 81 deletions src/main/scala/chisel3/tester/BackendInterface.scala
Original file line number Diff line number Diff line change
Expand Up @@ -4,13 +4,11 @@ package chisel3.tester

import chisel3._
import chisel3.experimental.MultiIOModule
import org.scalatest.exceptions.TestFailedException

import scala.collection.mutable

class ThreadOrderDependentException(message: String) extends Exception(message)
class TimeoutException(message: String) extends Exception(message)
class FailedExpectException(message: String, val failedCodeStackDepth: Int) extends Exception(message)

// when interfacing with the testdriver before stepping the clock after moving to an earlier region
class TemporalParadox(message: String) extends Exception(message)
Expand Down Expand Up @@ -141,82 +139,3 @@ trait BackendInstance[T <: MultiIOModule] extends BackendInterface {
*/
def run(testFn: T => Unit): Unit
}

/** Interface into the testing environment, like ScalaTest
*/
trait TestEnvInterface {

protected val batchedFailures: mutable.ArrayBuffer[Exception] = new mutable.ArrayBuffer

def topFileName: Option[String]

val useTestFailedException: Boolean = false

/** Logs a tester failure at this point.
* Failures queued until the next checkpoint.
*/
def testerFail(msg: String): Unit = {
batchedFailures += (
if(useTestFailedException) {
new TestFailedException(msg, failedCodeStackDepth = 4)
}
else {
new FailedExpectException(msg, 4)
})
}

protected def getExpectDetailedTrace(trace: Seq[StackTraceElement], inFile: String): String = {
val fullTrace = Context().backend.getParentTraceElements ++ trace

// In the threading case, this needs to be overridden to trace through parent threads
val lineNumbers = fullTrace.collect {
case ste if ste.getFileName == inFile => ste.getLineNumber
}.mkString(", ")
if (lineNumbers.isEmpty) {
s" (no lines in $inFile)"
} else {
s" (lines in $inFile: $lineNumbers)"
}
}

/** Expect a specific value on a wire, calling testerFail if the expectation isn't met.
* Failures queued until the next checkpoint.
*/
def testerExpect(expected: Any, actual: Any, signal: String, msg: Option[String]): Unit = {
if (expected != actual) {
val appendMsg = msg match {
case Some(_) => s": $msg"
case _ => ""
}

val trace = new Throwable
val expectStackDepth = trace.getStackTrace.indexWhere(ste =>
ste.getClassName == "chisel3.tester.package$testableData" && ste.getMethodName == "expect")
require(expectStackDepth != -1,
s"Failed to find expect in stack trace:\r\n${trace.getStackTrace.mkString("\r\n")}")

val trimmedTrace = trace.getStackTrace.drop(expectStackDepth + 2)
val detailedTrace = topFileName.map(getExpectDetailedTrace(trimmedTrace.toSeq, _)).getOrElse("")

val message = s"$signal=$actual did not equal expected=$expected$appendMsg$detailedTrace"
val stackIndex = expectStackDepth + 1
batchedFailures += (
if(useTestFailedException) {
new TestFailedException(message, failedCodeStackDepth = stackIndex)
}
else {
new FailedExpectException(message, stackIndex)
})
}
}

/** If there are any failures, reports them and end the test now.
*/
def checkpoint(): Unit = {
// TODO: report multiple exceptions simultaneously
for (failure <- batchedFailures) {
throw failure
}
}

}
12 changes: 9 additions & 3 deletions src/main/scala/chisel3/tester/ChiselScalatestTester.scala
Original file line number Diff line number Diff line change
Expand Up @@ -20,8 +20,6 @@ trait ChiselScalatestTester extends Assertions with TestSuiteMixin with TestEnvI
}
}

override val useTestFailedException = true

// Stack trace data to help generate more informative (and localizable) failure messages
var topFileName: Option[String] = None // best guess at the testdriver top filename

Expand All @@ -38,7 +36,15 @@ trait ChiselScalatestTester extends Assertions with TestSuiteMixin with TestEnvI

batchedFailures.clear()

Context.run(tester, this, testFn)
try {
Context.run(tester, this, testFn)
} catch {
// Translate testers2's FailedExpectException into ScalaTest TestFailedException that is more readable
case exc: FailedExpectException =>
val newExc = new TestFailedException(exc, exc.failedCodeStackDepth)
newExc.setStackTrace(exc.getStackTrace)
throw newExc
}
}

def getTestOptions: TesterOptions = {
Expand Down
70 changes: 70 additions & 0 deletions src/main/scala/chisel3/tester/TestEnvInterface.scala
Original file line number Diff line number Diff line change
@@ -0,0 +1,70 @@
// See LICENSE for license details.

package chisel3.tester

import scala.collection.mutable

class FailedExpectException(val message: String, val failedCodeStackDepth: Int) extends Exception(message)

/** Interface into the testing environment, like ScalaTest
*/
trait TestEnvInterface {
protected val batchedFailures: mutable.ArrayBuffer[Exception] = new mutable.ArrayBuffer

def topFileName: Option[String]

/** Logs a tester failure at this point.
* Failures queued until the next checkpoint.
*/
def testerFail(msg: String): Unit = {
batchedFailures += new FailedExpectException(msg, 4)
}

protected def getExpectDetailedTrace(trace: Seq[StackTraceElement], inFile: String): String = {
val fullTrace = Context().backend.getParentTraceElements ++ trace

// In the threading case, this needs to be overridden to trace through parent threads
val lineNumbers = fullTrace.collect {
case ste if ste.getFileName == inFile => ste.getLineNumber
}.mkString(", ")
if (lineNumbers.isEmpty) {
s" (no lines in $inFile)"
} else {
s" (lines in $inFile: $lineNumbers)"
}
}

/** Expect a specific value on a wire, calling testerFail if the expectation isn't met.
* Failures queued until the next checkpoint.
*/
def testerExpect(expected: Any, actual: Any, signal: String, msg: Option[String]): Unit = {
if (expected != actual) {
val appendMsg = msg match {
case Some(_) => s": $msg"
case _ => ""
}

val trace = new Throwable
val expectStackDepth = trace.getStackTrace.indexWhere(ste =>
ste.getClassName == "chisel3.tester.package$testableData" && ste.getMethodName == "expect")
require(expectStackDepth != -1,
s"Failed to find expect in stack trace:\r\n${trace.getStackTrace.mkString("\r\n")}")

val trimmedTrace = trace.getStackTrace.drop(expectStackDepth + 2)
val detailedTrace = topFileName.map(getExpectDetailedTrace(trimmedTrace.toSeq, _)).getOrElse("")

val message = s"$signal=$actual did not equal expected=$expected$appendMsg$detailedTrace"
val stackIndex = expectStackDepth + 1
batchedFailures += new FailedExpectException(message, stackIndex)
}
}

/** If there are any failures, reports them and end the test now.
*/
def checkpoint(): Unit = {
// TODO: report multiple exceptions simultaneously
for (failure <- batchedFailures) {
throw failure
}
}
}

0 comments on commit 83f530a

Please sign in to comment.