diff options
9 files changed, 301 insertions, 97 deletions
diff --git a/core/src/main/scala/chisel3/experimental/Analog.scala b/core/src/main/scala/chisel3/experimental/Analog.scala index 2ce2c86d..6cca81f5 100644 --- a/core/src/main/scala/chisel3/experimental/Analog.scala +++ b/core/src/main/scala/chisel3/experimental/Analog.scala @@ -56,6 +56,7 @@ final class Analog private (private[chisel3] val width: Width) extends Element { case ChildBinding(parent) => parent.topBinding // See https://github.com/freechipsproject/chisel3/pull/946 case SampleElementBinding(parent) => parent.topBinding + case a: MemTypeBinding[_] => a } targetTopBinding match { @@ -83,4 +84,3 @@ final class Analog private (private[chisel3] val width: Width) extends Element { object Analog { def apply(width: Width): Analog = new Analog(width) } - diff --git a/core/src/main/scala/chisel3/internal/Error.scala b/core/src/main/scala/chisel3/internal/Error.scala index 60bb6e2b..134f4c87 100644 --- a/core/src/main/scala/chisel3/internal/Error.scala +++ b/core/src/main/scala/chisel3/internal/Error.scala @@ -5,12 +5,82 @@ package chisel3.internal import scala.annotation.tailrec import scala.collection.mutable.{ArrayBuffer, LinkedHashMap} -class ChiselException(message: String, cause: Throwable = null) extends Exception(message, cause) { +object ExceptionHelpers { + + /** Root packages that are not typically relevant to Chisel user code. */ + final val packageTrimlist: Set[String] = Set("chisel3", "scala", "java", "jdk", "sun", "sbt") + + /** The object name of Chisel's internal `Builder`. */ + final val builderName: String = chisel3.internal.Builder.getClass.getName + + /** Return a stack trace element that looks like `... (someMessage)`. + * @param message an optional message to include + */ + def ellipsis(message: Option[String] = None): StackTraceElement = + new StackTraceElement("..", " ", message.getOrElse(""), -1) + + /** Utility methods that can be added to exceptions. + */ + implicit class ThrowableHelpers(throwable: Throwable) { + + /** For an exception, mutably trim a stack trace to user code only. + * + * This does the following actions to the stack trace: + * + * 1. From the top, remove elements while the (root) package matches the packageTrimlist + * 2. Optionally, from the bottom, remove elements until the class matches an anchor + * 3. From the anchor (or the bottom), remove elements while the (root) package matches the packageTrimlist + * + * @param packageTrimlist packages that should be removed from the stack trace + * @param anchor an optional class name at which user execution might begin, e.g., a main object + * @return nothing as this mutates the exception directly + */ + def trimStackTraceToUserCode( + packageTrimlist: Set[String] = packageTrimlist, + anchor: Option[String] = Some(builderName) + ): Unit = { + def inTrimlist(ste: StackTraceElement) = { + val packageName = ste.getClassName().takeWhile(_ != '.') + packageTrimlist.contains(packageName) + } + + // Step 1: Remove elements from the top in the package trimlist + ((a: Array[StackTraceElement]) => a.view.dropWhile(inTrimlist)) + // Step 2: Optionally remove elements from the bottom until the anchor + .andThen(_.reverse) + .andThen( a => + anchor match { + case Some(b) => a.dropWhile(ste => !ste.getClassName.startsWith(b)) + case None => a + } + ) + // Step 3: Remove elements from the bottom in the package trimlist + .andThen(_.dropWhile(inTrimlist)) + // Step 4: Reverse back to the original order + .andThen(_.reverse.toArray) + // Step 5: Add ellipsis stack trace elements and "--full-stacktrace" info + .andThen(a => + ellipsis() +: + a :+ + ellipsis() :+ + ellipsis(Some("Stack trace trimmed to user code only. Rerun with --full-stacktrace to see the full stack trace"))) + // Step 5: Mutate the stack trace in this exception + .andThen(throwable.setStackTrace(_)) + .apply(throwable.getStackTrace) + } + + } + +} + +class ChiselException(message: String, cause: Throwable = null) extends Exception(message, cause, true, true) { /** Package names whose stack trace elements should be trimmed when generating a trimmed stack trace */ + @deprecated("Use ExceptionHelpers.packageTrimlist. This will be removed in Chisel 3.6", "3.5") val blacklistPackages: Set[String] = Set("chisel3", "scala", "java", "sun", "sbt") /** The object name of Chisel's internal `Builder`. Everything stack trace element after this will be trimmed. */ + @deprecated("Use ExceptionHelpers.builderName. This will be removed in Chisel 3.6", "3.5") val builderName: String = chisel3.internal.Builder.getClass.getName /** Examine a [[Throwable]], to extract all its causes. Innermost cause is first. @@ -27,7 +97,7 @@ class ChiselException(message: String, cause: Throwable = null) extends Exceptio /** Returns true if an exception contains */ private def containsBuilder(throwable: Throwable): Boolean = throwable.getStackTrace().collectFirst { - case ste if ste.getClassName().startsWith(builderName) => throwable + case ste if ste.getClassName().startsWith(ExceptionHelpers.builderName) => throwable }.isDefined /** Examine this [[ChiselException]] and it's causes for the first [[Throwable]] that contains a stack trace including @@ -142,7 +212,8 @@ private[chisel3] class ErrorLog { } if (!allErrors.isEmpty) { - throwException("Fatal errors during hardware elaboration") + throw new ChiselException("Fatal errors during hardware elaboration. Look above for error list.") + with scala.util.control.NoStackTrace } else { // No fatal errors, clear accumulated warnings since they've been reported errors.clear() diff --git a/src/main/scala/chisel3/stage/ChiselAnnotations.scala b/src/main/scala/chisel3/stage/ChiselAnnotations.scala index bbe86ab4..04246fc5 100644 --- a/src/main/scala/chisel3/stage/ChiselAnnotations.scala +++ b/src/main/scala/chisel3/stage/ChiselAnnotations.scala @@ -56,13 +56,9 @@ case class ChiselGeneratorAnnotation(gen: () => RawModule) extends NoTargetAnnot /** Run elaboration on the Chisel module generator function stored by this [[firrtl.annotations.Annotation]] */ - def elaborate: AnnotationSeq = try { + def elaborate: AnnotationSeq = { val (circuit, dut) = Builder.build(Module(gen())) Seq(ChiselCircuitAnnotation(circuit), DesignAnnotation(dut)) - } catch { - case e @ (_: OptionsException | _: ChiselException) => throw e - case e: Throwable => - throw new ChiselException(s"Exception thrown when elaborating ChiselGeneratorAnnotation", e) } } diff --git a/src/main/scala/chisel3/stage/ChiselStage.scala b/src/main/scala/chisel3/stage/ChiselStage.scala index aae7ad8d..1bcf5124 100644 --- a/src/main/scala/chisel3/stage/ChiselStage.scala +++ b/src/main/scala/chisel3/stage/ChiselStage.scala @@ -13,7 +13,7 @@ import firrtl.{ VerilogEmitter, SystemVerilogEmitter } -import firrtl.options.{Dependency, Phase, PhaseManager, Shell, Stage, StageError, StageMain} +import firrtl.options.{Dependency, Phase, PhaseManager, Shell, Stage, StageMain} import firrtl.options.phases.DeletedWrapper import firrtl.stage.{FirrtlCircuitAnnotation, FirrtlCli, RunFirrtlTransformAnnotation} import firrtl.options.Viewer.view @@ -28,7 +28,7 @@ class ChiselStage extends Stage { override def prerequisites = Seq.empty override def optionalPrerequisites = Seq.empty - override def optionalPrerequisiteOf = Seq.empty + override def optionalPrerequisiteOf = Seq(Dependency[firrtl.stage.FirrtlStage]) override def invalidates(a: Phase) = false val shell: Shell = new Shell("chisel") with ChiselCli with FirrtlCli @@ -42,23 +42,7 @@ class ChiselStage extends Stage { } } - def run(annotations: AnnotationSeq): AnnotationSeq = try { - phaseManager.transform(annotations) - } catch { - case ce: ChiselException => - val stackTrace = if (!view[ChiselOptions](annotations).printFullStackTrace) { - ce.chiselStackTrace - } else { - val sw = new StringWriter - ce.printStackTrace(new PrintWriter(sw)) - sw.toString - } - Predef - .augmentString(stackTrace) - .lines - .foreach(line => println(s"${ErrorLog.errTag} $line")) - throw new StageError(cause=ce) - } + def run(annotations: AnnotationSeq): AnnotationSeq = phaseManager.transform(annotations) /** Convert a Chisel module to a CHIRRTL string * @param gen a call-by-name Chisel module diff --git a/src/main/scala/chisel3/stage/phases/Elaborate.scala b/src/main/scala/chisel3/stage/phases/Elaborate.scala index 04cfc33e..df39b66b 100644 --- a/src/main/scala/chisel3/stage/phases/Elaborate.scala +++ b/src/main/scala/chisel3/stage/phases/Elaborate.scala @@ -6,6 +6,7 @@ import java.io.{PrintWriter, StringWriter} import chisel3.ChiselException import chisel3.internal.ErrorLog +import chisel3.internal.ExceptionHelpers.ThrowableHelpers import chisel3.stage.{ChiselGeneratorAnnotation, ChiselOptions} import firrtl.AnnotationSeq import firrtl.options.Viewer.view @@ -21,8 +22,18 @@ class Elaborate extends Phase { override def invalidates(a: Phase) = false def transform(annotations: AnnotationSeq): AnnotationSeq = annotations.flatMap { - case a: ChiselGeneratorAnnotation => a.elaborate - case a => Some(a) + case a: ChiselGeneratorAnnotation => try { + a.elaborate + } catch { + /* if any throwable comes back and we're in "stack trace trimming" mode, then print an error and trim the stack trace + */ + case scala.util.control.NonFatal(a) => + if (!view[ChiselOptions](annotations).printFullStackTrace) { + a.trimStackTraceToUserCode() + } + throw(a) + } + case a => Some(a) } } diff --git a/src/test/scala/chiselTests/ChiselTestUtilitiesSpec.scala b/src/test/scala/chiselTests/ChiselTestUtilitiesSpec.scala index 6d3d526c..40358d11 100644 --- a/src/test/scala/chiselTests/ChiselTestUtilitiesSpec.scala +++ b/src/test/scala/chiselTests/ChiselTestUtilitiesSpec.scala @@ -8,16 +8,15 @@ import org.scalatest.exceptions.TestFailedException class ChiselTestUtilitiesSpec extends ChiselFlatSpec { // Who tests the testers? "assertKnownWidth" should "error when the expected width is wrong" in { - val caught = intercept[ChiselException] { + intercept[TestFailedException] { assertKnownWidth(7) { Wire(UInt(8.W)) } } - assert(caught.getCause.isInstanceOf[TestFailedException]) } it should "error when the width is unknown" in { - a [ChiselException] shouldBe thrownBy { + intercept[ChiselException] { assertKnownWidth(7) { Wire(UInt()) } @@ -31,12 +30,11 @@ class ChiselTestUtilitiesSpec extends ChiselFlatSpec { } "assertInferredWidth" should "error if the width is known" in { - val caught = intercept[ChiselException] { + intercept[TestFailedException] { assertInferredWidth(8) { Wire(UInt(8.W)) } } - assert(caught.getCause.isInstanceOf[TestFailedException]) } it should "error if the expected width is wrong" in { @@ -57,4 +55,3 @@ class ChiselTestUtilitiesSpec extends ChiselFlatSpec { } } } - diff --git a/src/test/scala/chiselTests/OneHotMuxSpec.scala b/src/test/scala/chiselTests/OneHotMuxSpec.scala index 887843d4..7608a3e7 100644 --- a/src/test/scala/chiselTests/OneHotMuxSpec.scala +++ b/src/test/scala/chiselTests/OneHotMuxSpec.scala @@ -37,26 +37,18 @@ class OneHotMuxSpec extends AnyFreeSpec with Matchers with ChiselRunners { } } "simple one hot mux with all fixed width bundles but with different bundles should Not work" in { - try { + intercept[IllegalArgumentException] { assertTesterPasses(new DifferentBundleOneHotTester) - } catch { - case a: ChiselException => a.getCause match { - case _: IllegalArgumentException => - } } } "UIntToOH with output width greater than 2^(input width)" in { assertTesterPasses(new UIntToOHTester) } "UIntToOH should not accept width of zero (until zero-width wires are fixed" in { - try { + intercept[IllegalArgumentException] { assertTesterPasses(new BasicTester { val out = UIntToOH(0.U, 0) }) - } catch { - case a: ChiselException => a.getCause match { - case _: IllegalArgumentException => - } } } diff --git a/src/test/scala/chiselTests/stage/ChiselMainSpec.scala b/src/test/scala/chiselTests/stage/ChiselMainSpec.scala index 0fc42fc6..1634e765 100644 --- a/src/test/scala/chiselTests/stage/ChiselMainSpec.scala +++ b/src/test/scala/chiselTests/stage/ChiselMainSpec.scala @@ -10,6 +10,8 @@ import chisel3.aop.inspecting.{InspectingAspect, InspectorAspect} import org.scalatest.GivenWhenThen import org.scalatest.featurespec.AnyFeatureSpec import org.scalatest.matchers.should.Matchers +import org.scalatest.Inside._ +import org.scalatest.EitherValues._ import scala.io.Source import firrtl.Parser @@ -32,7 +34,7 @@ object ChiselMainSpec { /** A module that fails a requirement */ class FailingRequirementModule extends RawModule { - require(false) + require(false, "the user wrote a failing requirement") } /** A module that triggers a Builder.error (as opposed to exception) */ @@ -69,14 +71,35 @@ class ChiselMainSpec extends AnyFeatureSpec with GivenWhenThen with Matchers wit args: Array[String], generator: Option[Class[_ <: RawModule]] = None, files: Seq[String] = Seq.empty, - stdout: Option[String] = None, - stderr: Option[String] = None, + stdout: Seq[Either[String, String]] = Seq.empty, + stderr: Seq[Either[String, String]] = Seq.empty, result: Int = 0, fileChecks: Map[String, File => Unit] = Map.empty) { def testName: String = "args" + args.mkString("_") def argsString: String = args.mkString(" ") } + /** A test of ChiselMain that is going to involve catching an exception. + * @param args command line arguments (excluding --module) to pass in + * @param generator the module to build (used to generate --module) + * @param message snippets of text that should appear (Right) or not appear (Left) in the exception message + * @param stdout snippets of text that should appear (Right) or not appear (Left) in STDOUT + * @param stderr snippets of text that should appear (Right) or not appear (Left) in STDERR + * @param stackTrace snippets of text that should appear (Right) or not appear (Left) in the stack trace + * @tparam the type of exception that should occur + */ + case class ChiselMainExceptionTest[A <: Throwable]( + args: Array[String], + generator: Option[Class[_ <: RawModule]] = None, + message: Seq[Either[String, String]] = Seq.empty, + stdout: Seq[Either[String, String]] = Seq.empty, + stderr: Seq[Either[String, String]] = Seq.empty, + stackTrace: Seq[Either[String, String]] = Seq.empty + ) { + def testName: String = "args" + args.mkString("_") + def argsString: String = args.mkString(" ") + } + def runStageExpectFiles(p: ChiselMainTest): Unit = { Scenario(s"""User runs Chisel Stage with '${p.argsString}'""") { val f = new ChiselMainFixture @@ -85,32 +108,33 @@ class ChiselMainSpec extends AnyFeatureSpec with GivenWhenThen with Matchers wit p.files.foreach( f => new File(td.buildDir + s"/$f").delete() ) When(s"""the user tries to compile with '${p.argsString}'""") + val module: Array[String] = + (if (p.generator.nonEmpty) { Array("--module", p.generator.get.getName) } + else { Array.empty[String] }) + f.stage.main(Array("-td", td.buildDir.toString) ++ module ++ p.args) val (stdout, stderr, result) = grabStdOutErr { catchStatus { - val module: Array[String] = Array("foo") ++ - (if (p.generator.nonEmpty) { Array("--module", p.generator.get.getName) } - else { Array.empty[String] }) f.stage.main(Array("-td", td.buildDir.toString) ++ module ++ p.args) } } - p.stdout match { - case Some(a) => + p.stdout.foreach { + case Right(a) => Then(s"""STDOUT should include "$a"""") stdout should include (a) - case None => - Then(s"nothing should print to STDOUT") - stdout should be (empty) + case Left(a) => + Then(s"""STDOUT should not include "$a"""") + stdout should not include (a) } - p.stderr match { - case Some(a) => - And(s"""STDERR should include "$a"""") + p.stderr.foreach { + case Right(a) => + Then(s"""STDERR should include "$a"""") stderr should include (a) - case None => - And(s"nothing should print to STDERR") - stderr should be (empty) + case Left(a) => + Then(s"""STDERR should not include "$a"""") + stderr should not include (a) } p.result match { @@ -131,56 +155,128 @@ class ChiselMainSpec extends AnyFeatureSpec with GivenWhenThen with Matchers wit } } + /** Run a ChiselMainExceptionTest and verify that all the properties it spells out hold. + * @param p the test to run + * @tparam the type of the exception to catch (you shouldn't have to explicitly provide this) + */ + def runStageExpectException[A <: Throwable: scala.reflect.ClassTag](p: ChiselMainExceptionTest[A]): Unit = { + Scenario(s"""User runs Chisel Stage with '${p.argsString}'""") { + val f = new ChiselMainFixture + val td = new TargetDirectoryFixture(p.testName) + + When(s"""the user tries to compile with '${p.argsString}'""") + val module: Array[String] = + (if (p.generator.nonEmpty) { Array("--module", p.generator.get.getName) } + else { Array.empty[String] }) + val (stdout, stderr, result) = + grabStdOutErr { + catchStatus { + intercept[A] { + f.stage.main(Array("-td", td.buildDir.toString) ++ module ++ p.args) + } + } + } + + Then("the expected exception was thrown") + result should be a ('right) + val exception = result.right.get + info(s""" - Exception was a "${exception.getClass.getName}"""") + + val message = exception.getMessage + p.message.foreach { + case Right(a) => + Then(s"""STDOUT should include "$a"""") + message should include (a) + case Left(a) => + Then(s"""STDOUT should not include "$a"""") + message should not include (a) + } + + p.stdout.foreach { + case Right(a) => + Then(s"""STDOUT should include "$a"""") + stdout should include (a) + case Left(a) => + Then(s"""STDOUT should not include "$a"""") + stdout should not include (a) + } + + p.stderr.foreach { + case Right(a) => + Then(s"""STDERR should include "$a"""") + stderr should include (a) + case Left(a) => + Then(s"""STDERR should not include "$a"""") + stderr should not include (a) + } + + val stackTraceString = exception.getStackTrace.mkString("\n") + p.stackTrace.foreach { + case Left(a) => + And(s"""the stack does not include "$a"""") + stackTraceString should not include (a) + case Right(a) => + And(s"""the stack trace includes "$a"""") + stackTraceString should include (a) + } + + } + } + info("As a Chisel user") info("I compile a design") Feature("show elaborating message") { runStageExpectFiles( ChiselMainTest(args = Array("-X", "high"), - generator = Some(classOf[SameTypesModule]), - stdout = Some("Done elaborating.") + generator = Some(classOf[SameTypesModule]) ) ) } info("I screw up and compile some bad code") - Feature("Stack trace trimming") { + Feature("Stack trace trimming of ChiselException") { Seq( - ChiselMainTest(args = Array("-X", "low"), - generator = Some(classOf[DifferentTypesModule]), - stdout = Some("Stack trace trimmed to user code only"), - result = 1), - ChiselMainTest(args = Array("-X", "high", "--full-stacktrace"), - generator = Some(classOf[DifferentTypesModule]), - stdout = Some("org.scalatest"), - result = 1) - ).foreach(runStageExpectFiles) + ChiselMainExceptionTest[chisel3.internal.ChiselException]( + args = Array("-X", "low"), + generator = Some(classOf[DifferentTypesModule]), + stackTrace = Seq(Left("java"), Right(classOf[DifferentTypesModule].getName)) + ), + ChiselMainExceptionTest[chisel3.internal.ChiselException]( + args = Array("-X", "low", "--full-stacktrace"), + generator = Some(classOf[DifferentTypesModule]), + stackTrace = Seq(Right("java"), Right(classOf[DifferentTypesModule].getName)) + ) + ).foreach(runStageExpectException) } - Feature("Report properly trimmed stack traces") { + Feature("Stack trace trimming of user exceptions") { Seq( - ChiselMainTest(args = Array("-X", "low"), - generator = Some(classOf[FailingRequirementModule]), - stdout = Some("requirement failed"), - result = 1), - ChiselMainTest(args = Array("-X", "low", "--full-stacktrace"), - generator = Some(classOf[FailingRequirementModule]), - stdout = Some("chisel3.internal.ChiselException"), - result = 1) - ).foreach(runStageExpectFiles) + ChiselMainExceptionTest[java.lang.IllegalArgumentException]( + args = Array("-X", "low"), + generator = Some(classOf[FailingRequirementModule]), + stackTrace = Seq(Right(classOf[FailingRequirementModule].getName), Left("java")) + ), + ChiselMainExceptionTest[java.lang.IllegalArgumentException]( + args = Array("-X", "low", "--full-stacktrace"), + generator = Some(classOf[FailingRequirementModule]), + stackTrace = Seq(Right(classOf[FailingRequirementModule].getName), Right("java")) + ) + ).foreach(runStageExpectException) } - Feature("Builder.error source locator") { + Feature("Stack trace trimming and Builder.error errors") { Seq( - ChiselMainTest(args = Array("-X", "none"), + ChiselMainExceptionTest[chisel3.internal.ChiselException]( + args = Array("-X", "low"), generator = Some(classOf[BuilderErrorModule]), - stdout = Some("ChiselMainSpec.scala:41: Invalid bit range (3,-1) in class chiselTests.stage.ChiselMainSpec$BuilderErrorModule"), - result = 1) - ).foreach(runStageExpectFiles) + message = Seq(Right("Fatal errors during hardware elaboration")), + stdout = Seq(Right("ChiselMainSpec.scala:43: Invalid bit range (3,-1) in class chiselTests.stage.ChiselMainSpec$BuilderErrorModule")) + ) + ).foreach(runStageExpectException) } Feature("Specifying a custom output file") { runStageExpectFiles(ChiselMainTest( args = Array("--chisel-output-file", "Foo", "--no-run-firrtl"), generator = Some(classOf[SameTypesModule]), - stdout = Some(""), files = Seq("Foo.fir"), fileChecks = Map( "Foo.fir" -> { file => @@ -192,7 +288,6 @@ class ChiselMainSpec extends AnyFeatureSpec with GivenWhenThen with Matchers wit runStageExpectFiles(ChiselMainTest( args = Array("--chisel-output-file", "Foo.pb", "--no-run-firrtl"), generator = Some(classOf[SameTypesModule]), - stdout = Some(""), files = Seq("Foo.pb"), fileChecks = Map( "Foo.pb" -> { file => @@ -209,10 +304,10 @@ class ChiselMainSpec extends AnyFeatureSpec with GivenWhenThen with Matchers wit Seq( ChiselMainTest(args = Array( "-X", "high", "--with-aspect", "chiselTests.stage.TestClassAspect" ), generator = Some(classOf[SameTypesModule]), - stdout = Some("Ran inspectingAspect")), + stdout = Seq(Right("Ran inspectingAspect"))), ChiselMainTest(args = Array( "-X", "high", "--with-aspect", "chiselTests.stage.TestObjectAspect" ), generator = Some(classOf[SameTypesModule]), - stdout = Some("Ran inspectingAspect")) + stdout = Seq(Right("Ran inspectingAspect"))) ).foreach(runStageExpectFiles) } diff --git a/src/test/scala/chiselTests/stage/ChiselStageSpec.scala b/src/test/scala/chiselTests/stage/ChiselStageSpec.scala index 98bbb2ea..167e414b 100644 --- a/src/test/scala/chiselTests/stage/ChiselStageSpec.scala +++ b/src/test/scala/chiselTests/stage/ChiselStageSpec.scala @@ -30,6 +30,10 @@ object ChiselStageSpec { out := memory(bar.out) } + class UserExceptionModule extends RawModule { + assert(false, "User threw an exception") + } + } class ChiselStageSpec extends AnyFlatSpec with Matchers with Utils { @@ -40,13 +44,13 @@ class ChiselStageSpec extends AnyFlatSpec with Matchers with Utils { val stage = new ChiselStage } - behavior of "ChiselStage.emitChirrtl" + behavior of "ChiselStage$.emitChirrtl" it should "return a CHIRRTL string" in { ChiselStage.emitChirrtl(new Foo) should include ("infer mport") } - behavior of "ChiselStage.emitFirrtl" + behavior of "ChiselStage$.emitFirrtl" it should "return a High FIRRTL string" in { ChiselStage.emitFirrtl(new Foo) should include ("mem memory") @@ -58,7 +62,7 @@ class ChiselStageSpec extends AnyFlatSpec with Matchers with Utils { .emitFirrtl(new Foo, args) should include ("module Bar") } - behavior of "ChiselStage.emitVerilog" + behavior of "ChiselStage$.emitVerilog" it should "return a Verilog string" in { ChiselStage.emitVerilog(new Foo) should include ("endmodule") @@ -142,4 +146,58 @@ class ChiselStageSpec extends AnyFlatSpec with Matchers with Utils { exactly (1, order) should be (Dependency[chisel3.stage.phases.Elaborate]) } + behavior of "ChiselStage$ exception handling" + + it should "truncate a user exception" in { + info("The user's java.lang.AssertionError was thrown") + val exception = intercept[java.lang.AssertionError] { + ChiselStage.emitChirrtl(new UserExceptionModule) + } + + val message = exception.getMessage + info("The exception includes the user's message") + message should include ("User threw an exception") + + info("The stack trace is trimmed") + exception.getStackTrace.mkString("\n") should not include ("java") + } + + behavior of "ChiselStage exception handling" + + it should "truncate a user exception" in { + info("The user's java.lang.AssertionError was thrown") + val exception = intercept[java.lang.AssertionError] { + (new ChiselStage).emitChirrtl(new UserExceptionModule) + } + + info(s""" - Exception was a ${exception.getClass.getName}""") + + val message = exception.getMessage + info("The exception includes the user's message") + message should include ("User threw an exception") + + val stackTrace = exception.getStackTrace.mkString("\n") + info("The stack trace is trimmed") + stackTrace should not include ("java") + + info("The stack trace include information about running --full-stacktrace") + stackTrace should include ("--full-stacktrace") + } + + it should """not truncate a user exception with "--full-stacktrace"""" in { + info("The user's java.lang.AssertionError was thrown") + val exception = intercept[java.lang.AssertionError] { + (new ChiselStage).emitChirrtl(new UserExceptionModule, Array("--full-stacktrace")) + } + + info(s""" - Exception was a ${exception.getClass.getName}""") + + val message = exception.getMessage + info("The exception includes the user's message") + message should include ("User threw an exception") + + info("The stack trace is not trimmed") + exception.getStackTrace.mkString("\n") should include ("java") + } + } |
