aboutsummaryrefslogtreecommitdiff
path: root/src
diff options
context:
space:
mode:
authorSchuyler Eldridge2018-12-04 01:35:48 -0500
committerSchuyler Eldridge2019-04-25 16:24:15 -0400
commit254e7909f6c9d155f514664584f142566f0a6799 (patch)
treefb2cdc32f8a37f8fc74cbf92ef79ee33240cc96c /src
parentb2dd0eb845081609d0aec4a873587ab3f22fe3f7 (diff)
Add tests for Annotations/Options refactor
- Add tests for DriverCompatibility.AddImplicitEmitter - Add tests for DriverCompatibility.AddImplicitOutputFile - Use a different top name in DriverSpec emit circuit tests for better coverage - Add tests for DriverCompatibility.WriteEmitted - Add catchWrites firrtlTests utility to intercept file writes - Add tests for WriteOutputAnnotations - Add tests for --custom-transforms reversing Signed-off-by: Schuyler Eldridge <schuyler.eldridge@ibm.com>
Diffstat (limited to 'src')
-rw-r--r--src/test/scala/firrtl/stage/phases/tests/DriverCompatibilitySpec.scala219
-rw-r--r--src/test/scala/firrtlTests/DriverSpec.scala38
-rw-r--r--src/test/scala/firrtlTests/FirrtlSpec.scala84
-rw-r--r--src/test/scala/firrtlTests/options/OptionParserSpec.scala65
-rw-r--r--src/test/scala/firrtlTests/options/phases/ChecksSpec.scala48
-rw-r--r--src/test/scala/firrtlTests/options/phases/GetIncludesSpec.scala96
-rw-r--r--src/test/scala/firrtlTests/options/phases/WriteOutputAnnotationsSpec.scala108
-rw-r--r--src/test/scala/firrtlTests/stage/FirrtlCliSpec.scala34
-rw-r--r--src/test/scala/firrtlTests/stage/FirrtlMainSpec.scala421
-rw-r--r--src/test/scala/firrtlTests/stage/FirrtlOptionsViewSpec.scala70
-rw-r--r--src/test/scala/firrtlTests/stage/phases/AddCircuitSpec.scala101
-rw-r--r--src/test/scala/firrtlTests/stage/phases/AddDefaultsSpec.scala37
-rw-r--r--src/test/scala/firrtlTests/stage/phases/AddImplicitEmitterSpec.scala45
-rw-r--r--src/test/scala/firrtlTests/stage/phases/AddImplicitOutputFileSpec.scala46
-rw-r--r--src/test/scala/firrtlTests/stage/phases/ChecksSpec.scala89
-rw-r--r--src/test/scala/firrtlTests/stage/phases/CompilerSpec.scala143
-rw-r--r--src/test/scala/firrtlTests/stage/phases/WriteEmittedSpec.scala79
17 files changed, 1673 insertions, 50 deletions
diff --git a/src/test/scala/firrtl/stage/phases/tests/DriverCompatibilitySpec.scala b/src/test/scala/firrtl/stage/phases/tests/DriverCompatibilitySpec.scala
new file mode 100644
index 00000000..77316feb
--- /dev/null
+++ b/src/test/scala/firrtl/stage/phases/tests/DriverCompatibilitySpec.scala
@@ -0,0 +1,219 @@
+// See LICENSE for license details.
+
+package firrtl.stage.phases.tests
+
+import org.scalatest.{FlatSpec, Matchers, PrivateMethodTester}
+
+import scala.io.Source
+
+import java.io.File
+
+import firrtl._
+import firrtl.stage._
+import firrtl.stage.phases.DriverCompatibility._
+
+import firrtl.options.{InputAnnotationFileAnnotation, Phase, TargetDirAnnotation}
+import firrtl.stage.phases.DriverCompatibility
+
+class DriverCompatibilitySpec extends FlatSpec with Matchers with PrivateMethodTester {
+
+ class PhaseFixture(val phase: Phase)
+
+ /* This method wraps some magic that lets you use the private method DriverCompatibility.topName */
+ def topName(annotations: AnnotationSeq): Option[String] = {
+ val topName = PrivateMethod[Option[String]]('topName)
+ DriverCompatibility invokePrivate topName(annotations)
+ }
+
+ def simpleCircuit(main: String): String = s"""|circuit $main:
+ | module $main:
+ | node x = UInt<1>("h0")
+ |""".stripMargin
+
+ /* This is a tuple holding an annotation that can be used to derive a top name and the expected top name for that
+ * annotation. If these annotations are presented here in the same order that DriverCompatibility.topName uses to
+ * discern a top name. E.g., a TopNameAnnotation is always used, even in the presence of a FirrtlSourceAnnotation.
+ * Note: the last two FirrtlFileAnnotations have equal precedence, but the first one in the AnnotationSeq wins.
+ */
+ val annosWithTops = Seq(
+ (TopNameAnnotation("foo"), "foo"),
+ (FirrtlCircuitAnnotation(Parser.parse(simpleCircuit("bar"))), "bar"),
+ (FirrtlSourceAnnotation(simpleCircuit("baz")), "baz"),
+ (FirrtlFileAnnotation("src/test/resources/integration/PipeTester.fir"), "PipeTester"),
+ (FirrtlFileAnnotation("src/test/resources/integration/GCDTester.pb"), "GCDTester")
+ )
+
+ behavior of s"${DriverCompatibility.getClass.getName}.topName (private method)"
+
+ /* This iterates over the tails of annosWithTops. Using the ordering of annosWithTops, if this AnnotationSeq is fed to
+ * DriverCompatibility.topName, the head annotation will be used to determine the top name. This test ensures that
+ * topName behaves as expected.
+ */
+ for ( t <- annosWithTops.tails ) t match {
+ case Nil =>
+ it should "return None on an empty AnnotationSeq" in {
+ topName(Seq.empty) should be (None)
+ }
+ case x =>
+ val annotations = x.map(_._1)
+ val top = x.head._2
+ it should s"determine a top name ('$top') from a ${annotations.head.getClass.getName}" in {
+ topName(annotations).get should be (top)
+ }
+ }
+
+ def createFile(name: String): Unit = {
+ val file = new File(name)
+ file.getParentFile.getCanonicalFile.mkdirs()
+ file.createNewFile()
+ }
+
+ behavior of classOf[AddImplicitAnnotationFile].toString
+
+ val testDir = "test_run_dir/DriverCompatibilitySpec"
+
+ it should "not modify the annotations if an InputAnnotationFile already exists" in
+ new PhaseFixture(new AddImplicitAnnotationFile) {
+
+ createFile(testDir + "/foo.anno")
+ val annotations = Seq(
+ InputAnnotationFileAnnotation("bar.anno"),
+ TargetDirAnnotation(testDir),
+ TopNameAnnotation("foo") )
+
+ phase.transform(annotations).toSeq should be (annotations)
+ }
+
+ it should "add an InputAnnotationFile based on a derived topName" in
+ new PhaseFixture(new AddImplicitAnnotationFile) {
+ createFile(testDir + "/bar.anno")
+ val annotations = Seq(
+ TargetDirAnnotation(testDir),
+ TopNameAnnotation("bar") )
+
+ val expected = annotations.toSet +
+ InputAnnotationFileAnnotation(testDir + "/bar.anno")
+
+ phase.transform(annotations).toSet should be (expected)
+ }
+
+ it should "not add an InputAnnotationFile for .anno.json annotations" in
+ new PhaseFixture(new AddImplicitAnnotationFile) {
+ createFile(testDir + "/baz.anno.json")
+ val annotations = Seq(
+ TargetDirAnnotation(testDir),
+ TopNameAnnotation("baz") )
+
+ phase.transform(annotations).toSeq should be (annotations)
+ }
+
+ it should "not add an InputAnnotationFile if it cannot determine the topName" in
+ new PhaseFixture(new AddImplicitAnnotationFile) {
+ val annotations = Seq( TargetDirAnnotation(testDir) )
+
+ phase.transform(annotations).toSeq should be (annotations)
+ }
+
+ behavior of classOf[AddImplicitFirrtlFile].toString
+
+ it should "not modify the annotations if a CircuitOption is present" in
+ new PhaseFixture(new AddImplicitFirrtlFile) {
+ val annotations = Seq( FirrtlFileAnnotation("foo"), TopNameAnnotation("bar") )
+
+ phase.transform(annotations).toSeq should be (annotations)
+ }
+
+ it should "add an FirrtlFileAnnotation if a TopNameAnnotation is present" in
+ new PhaseFixture(new AddImplicitFirrtlFile) {
+ val annotations = Seq( TopNameAnnotation("foo") )
+ val expected = annotations.toSet +
+ FirrtlFileAnnotation(new File("foo.fir").getCanonicalPath)
+
+ phase.transform(annotations).toSet should be (expected)
+ }
+
+ it should "do nothing if no TopNameAnnotation is present" in
+ new PhaseFixture(new AddImplicitFirrtlFile) {
+ val annotations = Seq( TargetDirAnnotation("foo") )
+
+ phase.transform(annotations).toSeq should be (annotations)
+ }
+
+ behavior of classOf[AddImplicitEmitter].toString
+
+ val (nc, hfc, mfc, lfc, vc, svc) = ( new NoneCompiler,
+ new HighFirrtlCompiler,
+ new MiddleFirrtlCompiler,
+ new LowFirrtlCompiler,
+ new VerilogCompiler,
+ new SystemVerilogCompiler )
+
+ it should "convert CompilerAnnotations into EmitCircuitAnnotations without EmitOneFilePerModuleAnnotation" in
+ new PhaseFixture(new AddImplicitEmitter) {
+ val annotations = Seq(
+ CompilerAnnotation(nc),
+ CompilerAnnotation(hfc),
+ CompilerAnnotation(mfc),
+ CompilerAnnotation(lfc),
+ CompilerAnnotation(vc),
+ CompilerAnnotation(svc)
+ )
+ val expected = annotations
+ .flatMap( a => Seq(a,
+ RunFirrtlTransformAnnotation(a.compiler.emitter),
+ EmitCircuitAnnotation(a.compiler.emitter.getClass)) )
+
+ phase.transform(annotations).toSeq should be (expected)
+ }
+
+ it should "convert CompilerAnnotations into EmitAllodulesAnnotation with EmitOneFilePerModuleAnnotation" in
+ new PhaseFixture(new AddImplicitEmitter) {
+ val annotations = Seq(
+ EmitOneFilePerModuleAnnotation,
+ CompilerAnnotation(nc),
+ CompilerAnnotation(hfc),
+ CompilerAnnotation(mfc),
+ CompilerAnnotation(lfc),
+ CompilerAnnotation(vc),
+ CompilerAnnotation(svc)
+ )
+ val expected = annotations
+ .flatMap{
+ case a: CompilerAnnotation => Seq(a,
+ RunFirrtlTransformAnnotation(a.compiler.emitter),
+ EmitAllModulesAnnotation(a.compiler.emitter.getClass))
+ case a => Seq(a)
+ }
+
+ phase.transform(annotations).toSeq should be (expected)
+ }
+
+ behavior of classOf[AddImplicitOutputFile].toString
+
+ it should "add an OutputFileAnnotation derived from a TopNameAnnotation if no OutputFileAnnotation exists" in
+ new PhaseFixture(new AddImplicitOutputFile) {
+ val annotations = Seq( TopNameAnnotation("foo") )
+ val expected = Seq(
+ OutputFileAnnotation("foo"),
+ TopNameAnnotation("foo")
+ )
+ phase.transform(annotations).toSeq should be (expected)
+ }
+
+ it should "do nothing if an OutputFileannotation already exists" in
+ new PhaseFixture(new AddImplicitOutputFile) {
+ val annotations = Seq(
+ TopNameAnnotation("foo"),
+ OutputFileAnnotation("bar") )
+ val expected = annotations
+ phase.transform(annotations).toSeq should be (expected)
+ }
+
+ it should "do nothing if no TopNameAnnotation exists" in
+ new PhaseFixture(new AddImplicitOutputFile) {
+ val annotations = Seq.empty
+ val expected = annotations
+ phase.transform(annotations).toSeq should be (expected)
+ }
+
+}
diff --git a/src/test/scala/firrtlTests/DriverSpec.scala b/src/test/scala/firrtlTests/DriverSpec.scala
index d26aadf2..1cb991c8 100644
--- a/src/test/scala/firrtlTests/DriverSpec.scala
+++ b/src/test/scala/firrtlTests/DriverSpec.scala
@@ -218,6 +218,7 @@ class DriverSpec extends FreeSpec with Matchers with BackendCompilationUtilities
firrtlOptions = firrtlOptions.copy(firrtlSource = Some(input))
}
val annoFile = new File(optionsManager.commonOptions.targetDirName, top + ".anno")
+ val vFile = new File(optionsManager.commonOptions.targetDirName, top + ".v")
"Using Driver.getAnnotations" in {
copyResourceToFile("/annotations/SampleAnnotations.anno", annoFile)
optionsManager.firrtlOptions.annotations.length should be(0)
@@ -225,6 +226,7 @@ class DriverSpec extends FreeSpec with Matchers with BackendCompilationUtilities
annos.length should be(12) // 9 from circuit plus 3 general purpose
annos.count(_.isInstanceOf[InlineAnnotation]) should be(9)
annoFile.delete()
+ vFile.delete()
}
"Using Driver.execute" in {
copyResourceToFile("/annotations/SampleAnnotations.anno", annoFile)
@@ -234,6 +236,7 @@ class DriverSpec extends FreeSpec with Matchers with BackendCompilationUtilities
annos.count(_.isInstanceOf[InlineAnnotation]) should be(9)
}
annoFile.delete()
+ vFile.delete()
}
}
@@ -384,20 +387,29 @@ class DriverSpec extends FreeSpec with Matchers with BackendCompilationUtilities
"To a single file with file extension depending on the compiler by default" in {
Seq(
- "none" -> "./Top.fir",
- "low" -> "./Top.lo.fir",
- "high" -> "./Top.hi.fir",
- "middle" -> "./Top.mid.fir",
- "verilog" -> "./Top.v",
- "mverilog" -> "./Top.v",
- "sverilog" -> "./Top.sv"
+ "none" -> "./Foo.fir",
+ "low" -> "./Foo.lo.fir",
+ "high" -> "./Foo.hi.fir",
+ "middle" -> "./Foo.mid.fir",
+ "verilog" -> "./Foo.v",
+ "mverilog" -> "./Foo.v",
+ "sverilog" -> "./Foo.sv"
).foreach { case (compilerName, expectedOutputFileName) =>
+ info(s"$compilerName -> $expectedOutputFileName")
val manager = new ExecutionOptionsManager("test") with HasFirrtlOptions {
- commonOptions = CommonOptions(topName = "Top")
+ commonOptions = CommonOptions(topName = "Foo")
firrtlOptions = FirrtlExecutionOptions(firrtlSource = Some(input), compilerName = compilerName)
}
- firrtl.Driver.execute(manager)
+ firrtl.Driver.execute(manager) match {
+ case success: FirrtlExecutionSuccess =>
+ success.emitted.size should not be (0)
+ success.circuitState.annotations.length should be > (0)
+ case a: FirrtlExecutionFailure =>
+ fail(s"Got a FirrtlExecutionFailure! Expected FirrtlExecutionSuccess. Full message:\n${a.message}")
+ }
+
+
val file = new File(expectedOutputFileName)
file.exists() should be(true)
@@ -414,9 +426,8 @@ class DriverSpec extends FreeSpec with Matchers with BackendCompilationUtilities
"mverilog" -> Seq("./Top.v", "./Child.v"),
"sverilog" -> Seq("./Top.sv", "./Child.sv")
).foreach { case (compilerName, expectedOutputFileNames) =>
- println(s"$compilerName -> $expectedOutputFileNames")
+ info(s"$compilerName -> $expectedOutputFileNames")
val manager = new ExecutionOptionsManager("test") with HasFirrtlOptions {
- commonOptions = CommonOptions(topName = "Top")
firrtlOptions = FirrtlExecutionOptions(firrtlSource = Some(input),
compilerName = compilerName,
emitOneFilePerModule = true)
@@ -424,9 +435,10 @@ class DriverSpec extends FreeSpec with Matchers with BackendCompilationUtilities
firrtl.Driver.execute(manager) match {
case success: FirrtlExecutionSuccess =>
+ success.emitted.size should not be (0)
success.circuitState.annotations.length should be > (0)
- case _ =>
-
+ case failure: FirrtlExecutionFailure =>
+ fail(s"Got a FirrtlExecutionFailure! Expected FirrtlExecutionSuccess. Full message:\n${failure.message}")
}
for (name <- expectedOutputFileNames) {
diff --git a/src/test/scala/firrtlTests/FirrtlSpec.scala b/src/test/scala/firrtlTests/FirrtlSpec.scala
index c3c25cd5..c110109c 100644
--- a/src/test/scala/firrtlTests/FirrtlSpec.scala
+++ b/src/test/scala/firrtlTests/FirrtlSpec.scala
@@ -3,6 +3,7 @@
package firrtlTests
import java.io._
+import java.security.Permission
import com.typesafe.scalalogging.LazyLogging
@@ -320,3 +321,86 @@ abstract class CompilationTest(name: String, dir: String) extends FirrtlPropSpec
compileFirrtlTest(name, dir)
}
}
+
+trait Utils {
+
+ /** Run some Scala thunk and return STDOUT and STDERR as strings.
+ * @param thunk some Scala code
+ * @return a tuple containing STDOUT, STDERR, and what the thunk returns
+ */
+ def grabStdOutErr[T](thunk: => T): (String, String, T) = {
+ val stdout, stderr = new ByteArrayOutputStream()
+ val ret = scala.Console.withOut(stdout) { scala.Console.withErr(stderr) { thunk } }
+ (stdout.toString, stderr.toString, ret)
+ }
+
+ /** Encodes a System.exit exit code
+ * @param status the exit code
+ */
+ private case class ExitException(status: Int) extends SecurityException(s"Found a sys.exit with code $status")
+
+ /** A security manager that converts calls to System.exit into [[ExitException]]s by explicitly disabling the ability of
+ * a thread to actually exit. For more information, see:
+ * - https://docs.oracle.com/javase/tutorial/essential/environment/security.html
+ */
+ private class ExceptOnExit extends SecurityManager {
+ override def checkPermission(perm: Permission): Unit = {}
+ override def checkPermission(perm: Permission, context: Object): Unit = {}
+ override def checkExit(status: Int): Unit = {
+ super.checkExit(status)
+ throw ExitException(status)
+ }
+ }
+
+ /** Encodes a file that some code tries to write to
+ * @param the file name
+ */
+ private case class WriteException(file: String) extends SecurityException(s"Tried to write to file $file")
+
+ /** A security manager that converts writes to any file into [[WriteException]]s.
+ */
+ private class ExceptOnWrite extends SecurityManager {
+ override def checkPermission(perm: Permission): Unit = {}
+ override def checkPermission(perm: Permission, context: Object): Unit = {}
+ override def checkWrite(file: String): Unit = {
+ super.checkWrite(file)
+ throw WriteException(file)
+ }
+ }
+
+ /** Run some Scala code (a thunk) in an environment where all System.exit are caught and returned. This avoids a
+ * situation where a test results in something actually exiting and killing the entire test. This is necessary if you
+ * want to test a command line program, e.g., the `main` method of [[firrtl.options.Stage Stage]].
+ *
+ * NOTE: THIS WILL NOT WORK IN SITUATIONS WHERE THE THUNK IS CATCHING ALL [[Exception]]s OR [[Throwable]]s, E.G.,
+ * SCOPT. IF THIS IS HAPPENING THIS WILL NOT WORK. REPEAT THIS WILL NOT WORK.
+ * @param thunk some Scala code
+ * @return either the output of the thunk (`Right[T]`) or an exit code (`Left[Int]`)
+ */
+ def catchStatus[T](thunk: => T): Either[Int, T] = {
+ try {
+ System.setSecurityManager(new ExceptOnExit())
+ Right(thunk)
+ } catch {
+ case ExitException(a) => Left(a)
+ } finally {
+ System.setSecurityManager(null)
+ }
+ }
+
+ /** Run some Scala code (a thunk) in an environment where file writes are caught and the file that a program tries to
+ * write to is returned. This is useful if you want to test that some thunk either tries to write to a specific file
+ * or doesn't try to write at all.
+ */
+ def catchWrites[T](thunk: => T): Either[String, T] = {
+ try {
+ System.setSecurityManager(new ExceptOnWrite())
+ Right(thunk)
+ } catch {
+ case WriteException(a) => Left(a)
+ } finally {
+ System.setSecurityManager(null)
+ }
+ }
+
+}
diff --git a/src/test/scala/firrtlTests/options/OptionParserSpec.scala b/src/test/scala/firrtlTests/options/OptionParserSpec.scala
index ae4899d4..0a9ac2d5 100644
--- a/src/test/scala/firrtlTests/options/OptionParserSpec.scala
+++ b/src/test/scala/firrtlTests/options/OptionParserSpec.scala
@@ -4,15 +4,13 @@ package firrtlTests.options
import firrtl.{AnnotationSeq, FIRRTLException}
import firrtl.annotations.{Annotation, NoTargetAnnotation}
-import firrtl.options.{DoNotTerminateOnExit, DuplicateHandling, OptionsException}
+import firrtl.options.{DoNotTerminateOnExit, DuplicateHandling, ExceptOnError, OptionsException}
import scopt.OptionParser
import org.scalatest.{FlatSpec, Matchers}
-import java.security.Permission
-
-class OptionParserSpec extends FlatSpec with Matchers {
+class OptionParserSpec extends FlatSpec with Matchers with firrtlTests.Utils {
case class IntAnnotation(x: Int) extends NoTargetAnnotation {
def extract: Int = x
@@ -32,50 +30,35 @@ class OptionParserSpec extends FlatSpec with Matchers {
opt[Int]("integer").abbr("m").unbounded.action( (x, c) => IntAnnotation(x) +: c )
}
- case class ExitException(status: Option[Int]) extends SecurityException("Found a sys.exit")
+ trait WithIntParser { val parser = new IntParser }
- /* Security manager that disallows calls to sys.exit */
- class ExceptOnExit extends SecurityManager {
- override def checkPermission(perm: Permission): Unit = {}
- override def checkPermission(perm: Permission, context: Object): Unit = {}
- override def checkExit(status: Int): Unit = {
- super.checkExit(status)
- throw ExitException(Some(status))
- }
- }
+ behavior of "A default OptionsParser"
- /* Tell a parser to terminate in an environment where sys.exit throws an exception */
- def catchStatus(parser: OptionParser[_], exitState: Either[String, Unit]): Option[Int] = {
- System.setSecurityManager(new ExceptOnExit())
- val status = try {
- parser.terminate(exitState)
- throw new ExitException(None)
- } catch {
- case ExitException(s) => s
- }
- System.setSecurityManager(null)
- status
- }
+ it should "call sys.exit if terminate is called" in new WithIntParser {
+ info("exit status of 1 for failure")
+ catchStatus { parser.terminate(Left("some message")) } should be (Left(1))
- behavior of "default OptionsParser"
-
- it should "terminate on exit" in {
- val parser = new IntParser
+ info("exit status of 0 for success")
+ catchStatus { parser.terminate(Right(Unit)) } should be (Left(0))
+ }
- info("By default, exit statuses are reported")
- catchStatus(parser, Left("some message")) should be (Some(1))
- catchStatus(parser, Right(Unit)) should be (Some(0))
+ it should "print to stderr on an invalid option" in new WithIntParser {
+ grabStdOutErr{ parser.parse(Array("--foo"), Seq[Annotation]()) }._2 should include ("Unknown option --foo")
}
- behavior of "DoNotTerminateOnExit"
+ behavior of "An OptionParser with DoNotTerminateOnExit mixed in"
it should "disable sys.exit for terminate method" in {
val parser = new IntParser with DoNotTerminateOnExit
- catchStatus(parser, Left("some message")) should be (None)
- catchStatus(parser, Right(Unit)) should be (None)
+
+ info("no exit for failure")
+ catchStatus { parser.terminate(Left("some message")) } should be (Right(()))
+
+ info("no exit for success")
+ catchStatus { parser.terminate(Right(Unit)) } should be (Right(()))
}
- behavior of "DuplicateHandling"
+ behavior of "An OptionParser with DuplicateHandling mixed in"
it should "detect short duplicates" in {
val parser = new IntParser with DuplicateHandling with DuplicateShortOption
@@ -89,4 +72,12 @@ class OptionParserSpec extends FlatSpec with Matchers {
.getMessage should startWith ("Duplicate long option")
}
+ behavior of "An OptionParser with ExceptOnError mixed in"
+
+ it should "cause an OptionsException on an invalid option" in {
+ val parser = new IntParser with ExceptOnError
+ intercept[OptionsException] { parser.parse(Array("--foo"), Seq[Annotation]()) }
+ .getMessage should include ("Unknown option")
+ }
+
}
diff --git a/src/test/scala/firrtlTests/options/phases/ChecksSpec.scala b/src/test/scala/firrtlTests/options/phases/ChecksSpec.scala
new file mode 100644
index 00000000..e04ba554
--- /dev/null
+++ b/src/test/scala/firrtlTests/options/phases/ChecksSpec.scala
@@ -0,0 +1,48 @@
+// See LICENSE for license details.
+
+package firrtlTests.options.phases
+
+import org.scalatest.{FlatSpec, Matchers}
+
+import firrtl.AnnotationSeq
+import firrtl.options.{OptionsException, OutputAnnotationFileAnnotation, Phase, TargetDirAnnotation}
+import firrtl.options.phases.Checks
+
+class ChecksSpec extends FlatSpec with Matchers {
+
+ val targetDir = TargetDirAnnotation("foo")
+ val annoOut = OutputAnnotationFileAnnotation("bar")
+
+ /* A minimum annotation Seq that will pass [[Checks]] */
+ val min = Seq(targetDir)
+
+ def checkExceptionMessage(phase: Phase, annotations: AnnotationSeq, messageStart: String): Unit =
+ intercept[OptionsException]{ phase.transform(annotations) }.getMessage should startWith(messageStart)
+
+ class Fixture { val phase: Phase = new Checks }
+
+ behavior of classOf[Checks].toString
+
+ it should "enforce exactly one TargetDirAnnotation" in new Fixture {
+ info("0 target directories throws an exception")
+ checkExceptionMessage(phase, Seq.empty, "Exactly one target directory must be specified")
+
+ info("2 target directories throws an exception")
+ checkExceptionMessage(phase, Seq(targetDir, targetDir), "Exactly one target directory must be specified")
+ }
+
+ it should "enforce zero or one output annotation files" in new Fixture {
+ info("0 output annotation files is okay")
+ phase.transform(Seq(targetDir))
+
+ info("2 output annotation files throws an exception")
+ val in = Seq(targetDir, annoOut, annoOut)
+ checkExceptionMessage(phase, in, "At most one output annotation file can be specified")
+ }
+
+ it should "pass if the minimum annotations are specified" in new Fixture {
+ info(s"""Minimum required: ${min.map(_.getClass.getSimpleName).mkString(", ")}""")
+ phase.transform(min)
+ }
+
+}
diff --git a/src/test/scala/firrtlTests/options/phases/GetIncludesSpec.scala b/src/test/scala/firrtlTests/options/phases/GetIncludesSpec.scala
new file mode 100644
index 00000000..5b07d0e0
--- /dev/null
+++ b/src/test/scala/firrtlTests/options/phases/GetIncludesSpec.scala
@@ -0,0 +1,96 @@
+// See LICENSE for license details.
+
+package firrtlTests.options.phases
+
+import org.scalatest.{FlatSpec, Matchers}
+
+import java.io.{File, PrintWriter}
+
+import firrtl.AnnotationSeq
+import firrtl.annotations.{Annotation, AnnotationFileNotFoundException, JsonProtocol,
+ NoTargetAnnotation}
+import firrtl.options.phases.GetIncludes
+import firrtl.options.{InputAnnotationFileAnnotation, Phase}
+import firrtl.util.BackendCompilationUtilities
+
+case object A extends NoTargetAnnotation
+case object B extends NoTargetAnnotation
+case object C extends NoTargetAnnotation
+case object D extends NoTargetAnnotation
+case object E extends NoTargetAnnotation
+
+class GetIncludesSpec extends FlatSpec with Matchers with BackendCompilationUtilities with firrtlTests.Utils {
+
+
+ val dir = new File("test_run_dir/GetIncludesSpec")
+ dir.mkdirs()
+
+ def ref(filename: String): InputAnnotationFileAnnotation = InputAnnotationFileAnnotation(s"$dir/$filename.anno.json")
+
+ def checkAnnos(a: AnnotationSeq, b: AnnotationSeq): Unit = {
+ info("read the expected number of annotations")
+ a.size should be (b.size)
+
+ info("annotations match exact order")
+ a.zip(b).foreach{ case (ax, bx) => ax should be (bx) }
+ }
+
+ val files = Seq(
+ new File(dir + "/a.anno.json") -> Seq(A, ref("b")),
+ new File(dir + "/b.anno.json") -> Seq(B, ref("c"), ref("a")),
+ new File(dir + "/c.anno.json") -> Seq(C, ref("d"), ref("e")),
+ new File(dir + "/d.anno.json") -> Seq(D),
+ new File(dir + "/e.anno.json") -> Seq(E)
+ )
+
+ files.foreach{ case (file, annotations) =>
+ val pw = new PrintWriter(file)
+ pw.write(JsonProtocol.serialize(annotations))
+ pw.close()
+ }
+
+ class Fixture { val phase: Phase = new GetIncludes }
+
+ behavior of classOf[GetIncludes].toString
+
+ it should "throw an exception if the annotation file doesn't exit" in new Fixture {
+ intercept[AnnotationFileNotFoundException]{ phase.transform(Seq(ref("f"))) }
+ .getMessage should startWith("Annotation file")
+ }
+
+ it should "read annotations from a file" in new Fixture {
+ val e = ref("e")
+ val in = Seq(e)
+ val expect = Seq(E)
+ val out = phase.transform(in)
+
+ checkAnnos(out, expect)
+ }
+
+ it should "read annotations from multiple files, but not reading duplicates" in new Fixture {
+ val Seq(d, e) = Seq("d", "e").map(ref)
+ val in = Seq(d, e, e, d)
+ val expect = Seq(D, E)
+ val (stdout, _, out) = grabStdOutErr { phase.transform(in) }
+
+ checkAnnos(out, expect)
+
+ Seq("d", "e").foreach{ x =>
+ info(s"a warning about '$x.anno.json' was printed")
+ stdout should include (s"Warning: Annotation file ($dir/$x.anno.json) already included!")
+ }
+ }
+
+ it should "handle recursive references gracefully, but show a warning" in new Fixture {
+ val Seq(a, b, c, d, e) = Seq("a", "b", "c", "d", "e").map(ref)
+ val in = Seq(a)
+ val expect = Seq(A, B, C, D, E)
+ val (stdout, _, out) = grabStdOutErr { phase.transform(in) }
+
+ checkAnnos(out, expect)
+
+ info("a warning about 'a.anno.json' was printed")
+ stdout should include (s"Warning: Annotation file ($dir/a.anno.json)")
+ }
+
+}
diff --git a/src/test/scala/firrtlTests/options/phases/WriteOutputAnnotationsSpec.scala b/src/test/scala/firrtlTests/options/phases/WriteOutputAnnotationsSpec.scala
new file mode 100644
index 00000000..07aad53f
--- /dev/null
+++ b/src/test/scala/firrtlTests/options/phases/WriteOutputAnnotationsSpec.scala
@@ -0,0 +1,108 @@
+// See LICENSE for license details.
+
+package firrtlTests.options.phases
+
+import org.scalatest.{FlatSpec, Matchers}
+
+import java.io.File
+
+import firrtl.{AnnotationSeq, EmittedFirrtlCircuitAnnotation, EmittedFirrtlCircuit}
+import firrtl.annotations.{DeletedAnnotation, NoTargetAnnotation}
+import firrtl.options.{InputAnnotationFileAnnotation, OutputAnnotationFileAnnotation, Phase, WriteDeletedAnnotation}
+import firrtl.options.phases.{GetIncludes, WriteOutputAnnotations}
+import firrtl.stage.FirrtlFileAnnotation
+
+class WriteOutputAnnotationsSpec extends FlatSpec with Matchers with firrtlTests.Utils {
+
+ val dir = "test_run_dir/WriteOutputAnnotationSpec"
+
+ /** Check if the annotations contained by a [[File]] and the same, and in the same order, as a reference
+ * [[AnnotationSeq]]. This uses [[GetIncludes]] as that already knows how to read annotation files.
+ * @param f a file to read
+ * @param a the expected annotations
+ */
+ private def fileContainsAnnotations(f: File, a: AnnotationSeq): Unit = {
+ info(s"output file '$f' exists")
+ f should (exist)
+
+ info(s"reading '$f' works")
+ val read = (new GetIncludes)
+ .transform(Seq(InputAnnotationFileAnnotation(f.toString)))
+ .filterNot{
+ case a @ DeletedAnnotation(_, _: InputAnnotationFileAnnotation) => true
+ case _ => false }
+
+ info(s"annotations in file are expected size")
+ read.size should be (a.size)
+
+ read
+ .zip(a)
+ .foreach{ case (read, expected) =>
+ info(s"$read matches")
+ read should be (expected) }
+
+ f.delete()
+ }
+
+ class Fixture { val phase: Phase = new WriteOutputAnnotations }
+
+ behavior of classOf[WriteOutputAnnotations].toString
+
+ it should "write annotations to a file (excluding DeletedAnnotations)" in new Fixture {
+ val file = new File(dir + "/should-write-annotations-to-a-file.anno.json")
+ val annotations = Seq( OutputAnnotationFileAnnotation(file.toString),
+ WriteOutputAnnotationsSpec.FooAnnotation,
+ WriteOutputAnnotationsSpec.BarAnnotation(0),
+ WriteOutputAnnotationsSpec.BarAnnotation(1),
+ DeletedAnnotation("foo", WriteOutputAnnotationsSpec.FooAnnotation) )
+ val expected = annotations.filter {
+ case a: DeletedAnnotation => false
+ case a => true
+ }
+ val out = phase.transform(annotations)
+
+ info("annotations are unmodified")
+ out.toSeq should be (annotations)
+
+ fileContainsAnnotations(file, expected)
+ }
+
+ it should "include DeletedAnnotations if a WriteDeletedAnnotation is present" in new Fixture {
+ val file = new File(dir + "should-include-deleted.anno.json")
+ val annotations = Seq( OutputAnnotationFileAnnotation(file.toString),
+ WriteOutputAnnotationsSpec.FooAnnotation,
+ WriteOutputAnnotationsSpec.BarAnnotation(0),
+ WriteOutputAnnotationsSpec.BarAnnotation(1),
+ DeletedAnnotation("foo", WriteOutputAnnotationsSpec.FooAnnotation),
+ WriteDeletedAnnotation )
+ val out = phase.transform(annotations)
+
+ info("annotations are unmodified")
+ out.toSeq should be (annotations)
+
+ fileContainsAnnotations(file, annotations)
+ }
+
+ it should "do nothing if no output annotation file is specified" in new Fixture {
+ val annotations = Seq( WriteOutputAnnotationsSpec.FooAnnotation,
+ WriteOutputAnnotationsSpec.BarAnnotation(0),
+ WriteOutputAnnotationsSpec.BarAnnotation(1) )
+
+ val out = catchWrites { phase.transform(annotations) } match {
+ case Right(a) =>
+ info("no file writes occurred")
+ a
+ case Left(a) =>
+ fail(s"No file writes expected, but a write to '$a' ocurred!")
+ }
+
+ info("annotations are unmodified")
+ out.toSeq should be (annotations)
+ }
+
+}
+
+private object WriteOutputAnnotationsSpec {
+ case object FooAnnotation extends NoTargetAnnotation
+ case class BarAnnotation(x: Int) extends NoTargetAnnotation
+}
diff --git a/src/test/scala/firrtlTests/stage/FirrtlCliSpec.scala b/src/test/scala/firrtlTests/stage/FirrtlCliSpec.scala
new file mode 100644
index 00000000..f13a498f
--- /dev/null
+++ b/src/test/scala/firrtlTests/stage/FirrtlCliSpec.scala
@@ -0,0 +1,34 @@
+// See LICENSE for license details.
+
+package firrtlTests.stage
+
+import org.scalatest.{FlatSpec, Matchers}
+
+import firrtl.stage.RunFirrtlTransformAnnotation
+import firrtl.options.Shell
+import firrtl.stage.FirrtlCli
+
+class FirrtlCliSpec extends FlatSpec with Matchers {
+
+ behavior of "FirrtlCli for RunFirrtlTransformAnnotation / -fct / --custom-transforms"
+
+ it should "preserver transform order" in {
+ val shell = new Shell("foo") with FirrtlCli
+ val args = Array(
+ "--custom-transforms", "firrtl.transforms.BlackBoxSourceHelper,firrtl.transforms.CheckCombLoops",
+ "--custom-transforms", "firrtl.transforms.CombineCats",
+ "--custom-transforms", "firrtl.transforms.ConstantPropagation" )
+ val expected = Seq(
+ classOf[firrtl.transforms.BlackBoxSourceHelper],
+ classOf[firrtl.transforms.CheckCombLoops],
+ classOf[firrtl.transforms.CombineCats],
+ classOf[firrtl.transforms.ConstantPropagation] )
+
+ shell
+ .parse(args)
+ .collect{ case a: RunFirrtlTransformAnnotation => a }
+ .zip(expected)
+ .map{ case (RunFirrtlTransformAnnotation(a), b) => a.getClass should be (b) }
+ }
+
+}
diff --git a/src/test/scala/firrtlTests/stage/FirrtlMainSpec.scala b/src/test/scala/firrtlTests/stage/FirrtlMainSpec.scala
new file mode 100644
index 00000000..ca766d52
--- /dev/null
+++ b/src/test/scala/firrtlTests/stage/FirrtlMainSpec.scala
@@ -0,0 +1,421 @@
+// See LICENSE for license details.
+
+package firrtlTests.stage
+
+import org.scalatest.{FeatureSpec, GivenWhenThen, Matchers}
+
+import java.io.{File, PrintWriter}
+
+import scala.io.Source
+
+import firrtl.stage.FirrtlMain
+import firrtl.util.BackendCompilationUtilities
+
+/** Testing for the top-level [[FirrtlStage]] via [[FirrtlMain]].
+ *
+ * This test uses the [[org.scalatest.FeatureSpec FeatureSpec]] intentionally as this test exercises the top-level
+ * interface and is more suitable to an Acceptance Testing style.
+ */
+class FirrtlMainSpec extends FeatureSpec with GivenWhenThen with Matchers with firrtlTests.Utils
+ with BackendCompilationUtilities {
+
+ /** Parameterizes one test of [[FirrtlMain]]. Running the [[FirrtlMain]] `main` with certain args should produce
+ * certain files.
+ * @param args arguments to pass
+ * @param circuit a [[FirrtlCircuitFixture]] to use. This will generate an appropriate '-i $targetDir/$main.fi'
+ * argument.
+ * @param files expected files that will be created
+ * @param stdout expected stdout string, None if no output expected
+ * @param stderr expected stderr string, None if no output expected
+ * @param result expected exit code
+ */
+ case class FirrtlMainTest(
+ args: Array[String],
+ circuit: Option[FirrtlCircuitFixture] = Some(new SimpleFirrtlCircuitFixture),
+ files: Seq[String] = Seq.empty,
+ stdout: Option[String] = None,
+ stderr: Option[String] = None,
+ result: Int = 0) {
+ /** Generate a name for the test based on the arguments */
+ def testName: String = "args" + args.mkString("_")
+
+ /** Print the arguments as a single string */
+ def argsString: String = args.mkString(" ")
+ }
+
+ /** Run the FIRRTL stage with some command line arguments expecting output files to be created. The target directory is
+ * implied, but will be created by the stage.
+ * @param p some test parameters
+ */
+ def runStageExpectFiles(p: FirrtlMainTest): Unit = {
+ scenario(s"""User runs FIRRTL Stage with '${p.argsString}'""") {
+ val f = new FirrtlMainFixture
+ val td = new TargetDirectoryFixture(p.testName)
+
+ val inputFile: Array[String] = p.circuit match {
+ case Some(c) =>
+ And("some input FIRRTL IR")
+ val in = new File(td.dir, c.main)
+ val pw = new PrintWriter(in)
+ pw.write(c.input)
+ pw.close()
+ Array("-i", in.toString)
+ case None => Array.empty
+ }
+
+ p.files.foreach( f => new File(td.buildDir + s"/$f").delete() )
+
+ When(s"""the user tries to compile with '${p.argsString}'""")
+ val (stdout, stderr, result) =
+ grabStdOutErr { catchStatus { f.stage.main(inputFile ++ Array("-td", td.buildDir.toString) ++ p.args) } }
+
+ p.stdout match {
+ case Some(a) =>
+ Then(s"""STDOUT should include "$a"""")
+ stdout should include (a)
+ case None =>
+ Then(s"nothing should print to STDOUT")
+ stdout should be (empty)
+ }
+
+ p.stderr match {
+ case Some(a) =>
+ And(s"""STDERR should include "$a"""")
+ stderr should include (a)
+ case None =>
+ And(s"nothing should print to STDERR")
+ stderr should be (empty)
+ }
+
+ p.result match {
+ case 0 =>
+ And(s"the exit code should be 0")
+ result shouldBe a [Right[_,_]]
+ case a =>
+ And(s"the exit code should be $a")
+ result shouldBe (Left(a))
+ }
+
+ p.files.foreach { f =>
+ And(s"file '$f' should be emitted in the target directory")
+ val out = new File(td.buildDir + s"/$f")
+ out should (exist)
+ }
+ }
+ }
+
+
+ /** Test fixture that links to the [[FirrtlMain]] object. This could be done without, but its use matches the
+ * Given/When/Then style more accurately.
+ */
+ class FirrtlMainFixture {
+ Given("the FIRRTL stage")
+ val stage = FirrtlMain
+ }
+
+ /** Test fixture that creates a build directory
+ * @param dirName the name of the base directory; a `build` directory is created under this
+ */
+ class TargetDirectoryFixture(dirName: String) {
+ val dir = new File(s"test_run_dir/FirrtlMainSpec/$dirName")
+ val buildDir = new File(dir + "/build")
+ dir.mkdirs()
+ }
+
+ trait FirrtlCircuitFixture {
+ val main: String
+ val input: String
+ }
+
+ /** Test fixture defining a simple FIRRTL circuit that will emit differently with and without `--split-modules`. */
+ class SimpleFirrtlCircuitFixture extends FirrtlCircuitFixture {
+ val main: String = "Top"
+ val input: String =
+ """|circuit Top:
+ | module Top:
+ | output foo: UInt<32>
+ | inst c of Child
+ | inst e of External
+ | foo <= tail(add(c.foo, e.foo), 1)
+ | module Child:
+ | output foo: UInt<32>
+ | inst e of External
+ | foo <= e.foo
+ | extmodule External:
+ | output foo: UInt<32>
+ |""".stripMargin
+ }
+
+ info("As a FIRRTL command line user")
+ info("I want to compile some FIRRTL")
+ feature("FirrtlMain command line interface") {
+ scenario("User tries to discover available options") {
+ val f = new FirrtlMainFixture
+
+ When("the user passes '--help'")
+ /* Note: THIS CANNOT CATCH THE STATUS BECAUSE SCOPT CATCHES ALL THROWABLE!!! The catchStatus is only used to prevent
+ * sys.exit from killing the test. However, this should additionally return an exit code of 0 and not print an
+ * error. The nature of running this through catchStatus causes scopt to intercept the custom SecurityException
+ * and then use that as evidence to exit with a code of 1.
+ */
+ val (out, _, result) = grabStdOutErr { catchStatus { f.stage.main(Array("--help")) } }
+
+ Then("the usage text should be shown")
+ out should include ("Usage: firrtl")
+
+ And("usage text should show known registered transforms")
+ out should include ("--no-dce")
+
+ And("usage text should show known registered libraries")
+ out should include ("MemLib Options")
+
+ info("""And the exit code should be 0, but scopt catches all throwable, so we can't check this... ¯\_(ツ)_/¯""")
+ // And("the exit code should be zero")
+ // out should be (Left(0))
+ }
+
+ Seq(
+ /* Test all standard emitters with and without annotation file outputs */
+ FirrtlMainTest(args = Array("-X", "none", "-E", "chirrtl"),
+ files = Seq("Top.fir")),
+ FirrtlMainTest(args = Array("-X", "high", "-E", "high"),
+ files = Seq("Top.hi.fir")),
+ FirrtlMainTest(args = Array("-X", "middle", "-E", "middle", "-foaf", "Top"),
+ files = Seq("Top.mid.fir", "Top.anno.json")),
+ FirrtlMainTest(args = Array("-X", "low", "-E", "low", "-foaf", "annotations.anno.json"),
+ files = Seq("Top.lo.fir", "annotations.anno.json")),
+ FirrtlMainTest(args = Array("-X", "verilog", "-E", "verilog", "-foaf", "foo.anno"),
+ files = Seq("Top.v", "foo.anno.anno.json")),
+ FirrtlMainTest(args = Array("-X", "sverilog", "-E", "sverilog", "-foaf", "foo.json"),
+ files = Seq("Top.sv", "foo.json.anno.json"),
+ stdout = Some("SystemVerilog Compiler behaves the same as the Verilog Compiler!")),
+
+ /* Test all one file per module emitters */
+ FirrtlMainTest(args = Array("-X", "none", "-e", "chirrtl"),
+ files = Seq("Top.fir", "Child.fir")),
+ FirrtlMainTest(args = Array("-X", "high", "-e", "high"),
+ files = Seq("Top.hi.fir", "Child.hi.fir")),
+ FirrtlMainTest(args = Array("-X", "middle", "-e", "middle"),
+ files = Seq("Top.mid.fir", "Child.mid.fir")),
+ FirrtlMainTest(args = Array("-X", "low", "-e", "low"),
+ files = Seq("Top.lo.fir", "Child.lo.fir")),
+ FirrtlMainTest(args = Array("-X", "verilog", "-e", "verilog"),
+ files = Seq("Top.v", "Child.v")),
+ FirrtlMainTest(args = Array("-X", "sverilog", "-e", "sverilog"),
+ files = Seq("Top.sv", "Child.sv"),
+ stdout = Some("SystemVerilog Compiler behaves the same as the Verilog Compiler!")),
+
+ /* Test changes to output file name */
+ FirrtlMainTest(args = Array("-X", "none", "-E", "chirrtl", "-o", "foo"),
+ files = Seq("foo.fir")),
+ FirrtlMainTest(args = Array("-X", "high", "-E", "high", "-o", "foo"),
+ files = Seq("foo.hi.fir")),
+ FirrtlMainTest(args = Array("-X", "middle", "-E", "middle", "-o", "foo.middle"),
+ files = Seq("foo.middle.mid.fir")),
+ FirrtlMainTest(args = Array("-X", "low", "-E", "low", "-o", "foo.lo.fir"),
+ files = Seq("foo.lo.fir")),
+ FirrtlMainTest(args = Array("-X", "verilog", "-E", "verilog", "-o", "foo.sv"),
+ files = Seq("foo.sv.v")),
+ FirrtlMainTest(args = Array("-X", "sverilog", "-E", "sverilog", "-o", "Foo"),
+ files = Seq("Foo.sv"),
+ stdout = Some("SystemVerilog Compiler behaves the same as the Verilog Compiler!"))
+ )
+ .foreach(runStageExpectFiles)
+
+ scenario("User doesn't specify a target directory") {
+ val f = new FirrtlMainFixture
+
+ When("the user doesn't specify a target directory")
+ val outName = "FirrtlMainSpecNoTargetDirectory"
+ val out = new File(s"$outName.hi.fir")
+ out.delete()
+ val result = catchStatus {
+ f.stage.main(Array("-i", "src/test/resources/integration/GCDTester.fir", "-o", outName, "-X", "high",
+ "-E", "high")) }
+
+ Then("outputs should be written to current directory")
+ out should (exist)
+ out.delete()
+ }
+
+ scenario("User provides Protocol Buffer input") {
+ val f = new FirrtlMainFixture
+ val td = new TargetDirectoryFixture("protobuf-works")
+
+ And("some Protocol Buffer input")
+ val protobufIn = new File(td.dir + "/Foo.pb")
+ copyResourceToFile("/integration/GCDTester.pb", protobufIn)
+
+ When("the user tries to compile to High FIRRTL")
+ f.stage.main(Array("-i", protobufIn.toString, "-X", "high", "-E", "high", "-td", td.buildDir.toString,
+ "-o", "Foo"))
+
+ Then("the output should be the same as using FIRRTL input")
+ new File(td.buildDir + "/Foo.hi.fir") should (exist)
+ }
+
+ }
+
+ info("As a FIRRTL command line user")
+ info("I want to receive error messages when I do not specify mandatory inputs")
+ feature("FirrtlMain input validation of mandatory options") {
+ scenario("User gives no command line options (no input circuit specified)") {
+ val f = new FirrtlMainFixture
+
+ When("the user passes no arguments")
+ val (out, err, result) = grabStdOutErr { catchStatus { f.stage.main(Array.empty) } }
+
+ Then("an error should be printed on stdout")
+ out should include (s"Error: Unable to determine FIRRTL source to read")
+
+ And("no usage text should be shown")
+ out should not include ("Usage: firrtl")
+
+ And("nothing should print to stderr")
+ err should be (empty)
+
+ And("the exit code should be 1")
+ result should be (Left(1))
+ }
+ }
+
+ info("As a FIRRTL command line user")
+ info("I want to receive helpful error and warnings message")
+ feature("FirrtlMain input validation") {
+ /* Note: most input validation occurs inside firrtl.stage.phases.Checks. This seeks to validate command line
+ * behavior.
+ */
+
+ scenario("User tries to use an implicit annotation file") {
+ val f = new FirrtlMainFixture
+ val td = new TargetDirectoryFixture("implict-annotation-file")
+ val circuit = new SimpleFirrtlCircuitFixture
+
+ And("implicit legacy and extant annotation files")
+ val annoFiles = Array( (new File(td.dir + "/Top.anno"), "/annotations/SampleAnnotations.anno"),
+ (new File(td.dir + "/Top.anno.json"), "/annotations/SampleAnnotations.anno.json") )
+ annoFiles.foreach{ case (file, source) => copyResourceToFile(source, file) }
+
+ When("the user implies an annotation file (an annotation file has the same base name as an input file)")
+ val in = new File(td.dir + "/Top.fir")
+ val pw = new PrintWriter(in)
+ pw.write(circuit.input)
+ pw.close()
+ val (out, _, result) = grabStdOutErr{ catchStatus { f.stage.main(Array("-td", td.dir.toString,
+ "-i", in.toString,
+ "-foaf", "Top.out",
+ "-X", "high",
+ "-E", "high")) } }
+
+ Then("the implicit annotation file should NOT be read")
+ val annoFileOut = new File(td.dir + "/Top.out.anno.json")
+ val annotationJson = Source.fromFile(annoFileOut).mkString
+ annotationJson should not include ("InlineInstances")
+
+ And("no warning should be printed")
+ out should not include ("Warning:")
+
+ And("no error should be printed")
+ out should not include ("Error:")
+
+ And("the exit code should be 0")
+ result shouldBe a [Right[_,_]]
+ }
+
+ scenario("User provides unsupported legacy annotations") {
+ val f = new FirrtlMainFixture
+ val td = new TargetDirectoryFixture("legacy-annotation-file")
+ val circuit = new SimpleFirrtlCircuitFixture
+
+ And("a legacy annotation file")
+ val annoFile = new File(td.dir + "/legacy.anno")
+ copyResourceToFile("/annotations/SampleAnnotations.anno", annoFile)
+
+ When("the user provides legacy annotations")
+ val in = new File(td.dir + "/Top.fir")
+ val pw = new PrintWriter(in)
+ pw.write(circuit.input)
+ pw.close()
+ val (out, _, result) = grabStdOutErr{ catchStatus { f.stage.main(Array("-td", td.dir.toString,
+ "-i", in.toString,
+ "-faf", annoFile.toString,
+ "-foaf", "Top",
+ "-X", "high")) } }
+
+ Then("a warning should be printed")
+ out should include ("YAML Annotation files are deprecated")
+
+ And("the exit code should be 0")
+ result shouldBe a [Right[_,_]]
+ }
+
+ Seq(
+ /* Erroneous inputs */
+ FirrtlMainTest(args = Array("--thisIsNotASupportedOption"),
+ circuit = None,
+ stdout = Some("Error: Unknown option"),
+ result = 1),
+ FirrtlMainTest(args = Array("-i", "foo", "--info-mode", "Use"),
+ circuit = None,
+ stdout = Some("Unknown info mode 'Use'! (Did you misspell it?)"),
+ result = 1),
+ FirrtlMainTest(args = Array("-i", "test_run_dir/I-DO-NOT-EXIST"),
+ circuit = None,
+ stdout = Some("Input file 'test_run_dir/I-DO-NOT-EXIST' not found!"),
+ result = 1),
+ FirrtlMainTest(args = Array("-i", "foo", "-X", "Verilog"),
+ circuit = None,
+ stdout = Some("Unknown compiler name 'Verilog'! (Did you misspell it?)"),
+ result = 1)
+ )
+ .foreach(runStageExpectFiles)
+
+ }
+
+ info("As a FIRRTL transform developer")
+ info("I want to register my custom transforms with FIRRTL")
+ feature("FirrtlMain transform registration") {
+ scenario("User doesn't know if their transforms were registered") {
+ val f = new FirrtlMainFixture
+
+ When("the user passes '--show-registrations'")
+ val (out, _, result) = grabStdOutErr { catchStatus { f.stage.main(Array("--show-registrations")) } }
+
+ Then("stdout should show registered transforms")
+ out should include ("firrtl.passes.InlineInstances")
+
+ And("stdout should show registered libraries")
+ out should include("firrtl.passes.memlib.MemLibOptions")
+
+ And("the exit code should be 1")
+ result should be (Left(1))
+ }
+ }
+
+ info("As a longtime FIRRTL user")
+ info("I migrate from Driver to FirrtlMain")
+ feature("FirrtlMain migration helpers") {
+ def optionRemoved(a: String): Option[String] = Some(s"Option '$a' was removed as part of the FIRRTL Stage refactor")
+ Seq(
+ /* Removed --top-name/-tn handling */
+ FirrtlMainTest(args = Array("--top-name", "foo"),
+ circuit = None,
+ stdout = optionRemoved("--top-name/-tn"),
+ result = 1),
+ FirrtlMainTest(args = Array("-tn"),
+ circuit = None,
+ stdout = optionRemoved("--top-name/-tn"),
+ result = 1),
+ /* Removed --split-modules/-fsm handling */
+ FirrtlMainTest(args = Array("--split-modules"),
+ circuit = None,
+ stdout = optionRemoved("--split-modules/-fsm"),
+ result = 1),
+ FirrtlMainTest(args = Array("-fsm"),
+ circuit = None,
+ stdout = optionRemoved("--split-modules/-fsm"),
+ result = 1)
+ )
+ .foreach(runStageExpectFiles)
+ }
+}
diff --git a/src/test/scala/firrtlTests/stage/FirrtlOptionsViewSpec.scala b/src/test/scala/firrtlTests/stage/FirrtlOptionsViewSpec.scala
new file mode 100644
index 00000000..cf8bfb26
--- /dev/null
+++ b/src/test/scala/firrtlTests/stage/FirrtlOptionsViewSpec.scala
@@ -0,0 +1,70 @@
+// See LICENSE for license details.
+
+package firrtlTests.stage
+
+import org.scalatest.{FlatSpec, Matchers}
+
+import firrtl.options._
+import firrtl.stage._
+
+import firrtl.{CircuitForm, CircuitState, ir, NoneCompiler, Parser, UnknownForm}
+import firrtl.options.Viewer.view
+import firrtl.stage.{FirrtlOptions, FirrtlOptionsView}
+
+class BazCompiler extends NoneCompiler
+
+class Baz_Compiler extends NoneCompiler
+
+class FirrtlOptionsViewSpec extends FlatSpec with Matchers {
+
+ behavior of FirrtlOptionsView.getClass.getName
+
+ def circuitString(main: String): String = s"""|circuit $main:
+ | module $main:
+ | node x = UInt<1>("h0")
+ |""".stripMargin
+
+ val corge: String = circuitString("corge")
+
+ val grault: ir.Circuit = Parser.parse(circuitString("grault"))
+
+ val annotations = Seq(
+ /* FirrtlOptions */
+ OutputFileAnnotation("bar"),
+ CompilerAnnotation(new BazCompiler()),
+ InfoModeAnnotation("use"),
+ FirrtlCircuitAnnotation(grault)
+ )
+
+ it should "construct a view from an AnnotationSeq" in {
+ val out = view[FirrtlOptions](annotations)
+
+ out.outputFileName should be (Some("bar"))
+ out.compiler.getClass should be (classOf[BazCompiler])
+ out.infoModeName should be ("use")
+ out.firrtlCircuit should be (Some(grault))
+ }
+
+ /* This test only exists to catch changes to existing behavior. This test does not indicate that this is the correct
+ * behavior, only that modifications to existing code will not change behavior that people may expect.
+ */
+ it should "overwrite or append to earlier annotation information with later annotation information" in {
+ val corge_ = circuitString("xyzzy_")
+ val grault_ = Parser.parse(circuitString("thud_"))
+
+ val overwrites = Seq(
+ OutputFileAnnotation("bar_"),
+ CompilerAnnotation(new Baz_Compiler()),
+ InfoModeAnnotation("gen"),
+ FirrtlCircuitAnnotation(grault_)
+ )
+
+ val out = view[FirrtlOptions](annotations ++ overwrites)
+
+ out.outputFileName should be (Some("bar_"))
+ out.compiler.getClass should be (classOf[Baz_Compiler])
+ out.infoModeName should be ("gen")
+ out.firrtlCircuit should be (Some(grault_))
+ }
+
+}
diff --git a/src/test/scala/firrtlTests/stage/phases/AddCircuitSpec.scala b/src/test/scala/firrtlTests/stage/phases/AddCircuitSpec.scala
new file mode 100644
index 00000000..a86c5f12
--- /dev/null
+++ b/src/test/scala/firrtlTests/stage/phases/AddCircuitSpec.scala
@@ -0,0 +1,101 @@
+// See LICENSE for license details.
+
+package firrtlTests.stage.phases
+
+import org.scalatest.{FlatSpec, Matchers}
+
+import firrtl.{AnnotationSeq, Parser}
+import firrtl.annotations.NoTargetAnnotation
+import firrtl.options.{OptionsException, Phase, PhasePrerequisiteException}
+import firrtl.stage.{CircuitOption, FirrtlCircuitAnnotation, FirrtlSourceAnnotation, InfoModeAnnotation,
+ FirrtlFileAnnotation}
+import firrtl.stage.phases.AddCircuit
+
+import java.io.{File, FileWriter}
+
+class AddCircuitSpec extends FlatSpec with Matchers {
+
+ case class FooAnnotation(x: Int) extends NoTargetAnnotation
+ case class BarAnnotation(x: String) extends NoTargetAnnotation
+
+ class Fixture { val phase: Phase = new AddCircuit }
+
+ behavior of classOf[AddCircuit].toString
+
+ def firrtlSource(name: String): String =
+ s"""|circuit $name:
+ | module $name:
+ | input a: UInt<1>
+ | output b: UInt<1>
+ | b <= not(a)
+ |""".stripMargin
+
+ it should "throw a PhasePrerequisiteException if a CircuitOption exists without an InfoModeAnnotation" in
+ new Fixture {
+ {the [PhasePrerequisiteException] thrownBy phase.transform(Seq(FirrtlSourceAnnotation("foo")))}
+ .message should startWith ("An InfoModeAnnotation must be present")
+ }
+
+ it should "do nothing if no CircuitOption annotations are present" in new Fixture {
+ val annotations = (1 to 10).map(FooAnnotation) ++
+ ('a' to 'm').map(_.toString).map(BarAnnotation) :+ InfoModeAnnotation("ignore")
+ phase.transform(annotations).toSeq should be (annotations.toSeq)
+ }
+
+ val (file, fileCircuit) = {
+ val source = firrtlSource("foo")
+ val fileName = "test_run_dir/AddCircuitSpec.fir"
+ val fw = new FileWriter(new File(fileName))
+ fw.write(source)
+ fw.close()
+ (fileName, Parser.parse(source))
+ }
+
+ val (source, sourceCircuit) = {
+ val source = firrtlSource("bar")
+ (source, Parser.parse(source))
+ }
+
+ it should "transform and remove CircuitOption annotations" in new Fixture {
+ val circuit = Parser.parse(firrtlSource("baz"))
+
+ val annotations = Seq(
+ FirrtlFileAnnotation(file),
+ FirrtlSourceAnnotation(source),
+ FirrtlCircuitAnnotation(circuit),
+ InfoModeAnnotation("ignore") )
+
+ val annotationsExpected = Set(
+ FirrtlCircuitAnnotation(fileCircuit),
+ FirrtlCircuitAnnotation(sourceCircuit),
+ FirrtlCircuitAnnotation(circuit) )
+
+ val out = phase.transform(annotations).toSeq
+
+ info("generated expected FirrtlCircuitAnnotations")
+ out.collect{ case a: FirrtlCircuitAnnotation => a}.toSet should be (annotationsExpected)
+
+ info("all CircuitOptions were removed")
+ out.collect{ case a: CircuitOption => a } should be (empty)
+ }
+
+ it should """add info for a FirrtlFileAnnotation with a "gen" info mode""" in new Fixture {
+ phase.transform(Seq(InfoModeAnnotation("gen"), FirrtlFileAnnotation(file)))
+ .collectFirst{ case a: FirrtlCircuitAnnotation => a.circuit.serialize }
+ .get should include ("AddCircuitSpec")
+ }
+
+ it should """add info for a FirrtlSourceAnnotation with an "append" info mode""" in new Fixture {
+ phase.transform(Seq(InfoModeAnnotation("append"), FirrtlSourceAnnotation(source)))
+ .collectFirst{ case a: FirrtlCircuitAnnotation => a.circuit.serialize }
+ .get should include ("anonymous source")
+ }
+
+ it should "throw an OptionsException if the specified file doesn't exist" in new Fixture {
+ val a = Seq(InfoModeAnnotation("ignore"), FirrtlFileAnnotation("test_run_dir/I-DO-NOT-EXIST"))
+
+ {the [OptionsException] thrownBy phase.transform(a)}
+ .message should startWith (s"Input file 'test_run_dir/I-DO-NOT-EXIST' not found")
+ }
+
+}
diff --git a/src/test/scala/firrtlTests/stage/phases/AddDefaultsSpec.scala b/src/test/scala/firrtlTests/stage/phases/AddDefaultsSpec.scala
new file mode 100644
index 00000000..c4566dd5
--- /dev/null
+++ b/src/test/scala/firrtlTests/stage/phases/AddDefaultsSpec.scala
@@ -0,0 +1,37 @@
+// See LICENSE for license details.
+
+package firrtlTests.stage.phases
+
+import org.scalatest.{FlatSpec, Matchers}
+
+import firrtl.NoneCompiler
+import firrtl.annotations.Annotation
+import firrtl.stage.phases.AddDefaults
+import firrtl.transforms.BlackBoxTargetDirAnno
+import firrtl.stage.{CompilerAnnotation, InfoModeAnnotation}
+import firrtl.options.{Phase, TargetDirAnnotation}
+
+class AddDefaultsSpec extends FlatSpec with Matchers {
+
+ class Fixture { val phase: Phase = new AddDefaults }
+
+ behavior of classOf[AddDefaults].toString
+
+ it should "add expected default annotations and nothing else" in new Fixture {
+ val expected = Seq(
+ (a: Annotation) => a match { case BlackBoxTargetDirAnno(b) => b == TargetDirAnnotation().directory },
+ (a: Annotation) => a match { case CompilerAnnotation(b) => b.getClass == CompilerAnnotation().compiler.getClass },
+ (a: Annotation) => a match { case InfoModeAnnotation(b) => b == InfoModeAnnotation().modeName } )
+
+ phase.transform(Seq.empty).zip(expected).map { case (x, f) => f(x) should be (true) }
+ }
+
+ it should "not overwrite existing annotations" in new Fixture {
+ val input = Seq(
+ BlackBoxTargetDirAnno("foo"),
+ CompilerAnnotation(new NoneCompiler()),
+ InfoModeAnnotation("ignore"))
+
+ phase.transform(input).toSeq should be (input)
+ }
+}
diff --git a/src/test/scala/firrtlTests/stage/phases/AddImplicitEmitterSpec.scala b/src/test/scala/firrtlTests/stage/phases/AddImplicitEmitterSpec.scala
new file mode 100644
index 00000000..117e9c5f
--- /dev/null
+++ b/src/test/scala/firrtlTests/stage/phases/AddImplicitEmitterSpec.scala
@@ -0,0 +1,45 @@
+// See LICENSE for license details.
+
+package firrtlTests.stage.phases
+
+import org.scalatest.{FlatSpec, Matchers}
+
+import firrtl.{EmitAllModulesAnnotation, EmitCircuitAnnotation, HighFirrtlEmitter, VerilogCompiler}
+import firrtl.annotations.NoTargetAnnotation
+import firrtl.options.Phase
+import firrtl.stage.{CompilerAnnotation, RunFirrtlTransformAnnotation}
+import firrtl.stage.phases.AddImplicitEmitter
+
+class AddImplicitEmitterSpec extends FlatSpec with Matchers {
+
+ case class FooAnnotation(x: Int) extends NoTargetAnnotation
+ case class BarAnnotation(x: String) extends NoTargetAnnotation
+
+ class Fixture { val phase: Phase = new AddImplicitEmitter }
+
+ val someAnnos = Seq(FooAnnotation(1), FooAnnotation(2), BarAnnotation("bar"))
+
+ behavior of classOf[AddImplicitEmitter].toString
+
+ it should "do nothing if no CompilerAnnotation is present" in new Fixture {
+ phase.transform(someAnnos).toSeq should be (someAnnos)
+ }
+
+ it should "add an EmitCircuitAnnotation derived from a CompilerAnnotation" in new Fixture {
+ val input = CompilerAnnotation(new VerilogCompiler) +: someAnnos
+ val expected = input.flatMap{
+ case a@ CompilerAnnotation(b) => Seq(a,
+ RunFirrtlTransformAnnotation(b.emitter),
+ EmitCircuitAnnotation(b.emitter.getClass))
+ case a => Some(a)
+ }
+ phase.transform(input).toSeq should be (expected)
+ }
+
+ it should "not add an EmitCircuitAnnotation if an EmitAnnotation already exists" in new Fixture {
+ val input = Seq(CompilerAnnotation(new VerilogCompiler),
+ EmitAllModulesAnnotation(classOf[HighFirrtlEmitter])) ++ someAnnos
+ phase.transform(input).toSeq should be (input)
+ }
+
+}
diff --git a/src/test/scala/firrtlTests/stage/phases/AddImplicitOutputFileSpec.scala b/src/test/scala/firrtlTests/stage/phases/AddImplicitOutputFileSpec.scala
new file mode 100644
index 00000000..72d186b5
--- /dev/null
+++ b/src/test/scala/firrtlTests/stage/phases/AddImplicitOutputFileSpec.scala
@@ -0,0 +1,46 @@
+// See LICENSE for license details.
+
+package firrtlTests.stage.phases
+
+import org.scalatest.{FlatSpec, Matchers}
+
+import firrtl.{ChirrtlEmitter, EmitAllModulesAnnotation, Parser}
+import firrtl.options.Phase
+import firrtl.stage.{FirrtlCircuitAnnotation, OutputFileAnnotation}
+import firrtl.stage.phases.AddImplicitOutputFile
+
+class AddImplicitOutputFileSpec extends FlatSpec with Matchers {
+
+ class Fixture { val phase: Phase = new AddImplicitOutputFile }
+
+ val foo = """|circuit Foo:
+ | module Foo:
+ | node a = UInt<1>("h0")
+ |""".stripMargin
+
+ val circuit = Parser.parse(foo)
+
+ behavior of classOf[AddImplicitOutputFile].toString
+
+ it should "default to an output file named 'a'" in new Fixture {
+ phase.transform(Seq.empty).toSeq should be (Seq(OutputFileAnnotation("a")))
+ }
+
+ it should "set the output file based on a FirrtlCircuitAnnotation's main" in new Fixture {
+ val in = Seq(FirrtlCircuitAnnotation(circuit))
+ val out = OutputFileAnnotation(circuit.main) +: in
+ phase.transform(in).toSeq should be (out)
+ }
+
+ it should "do nothing if an OutputFileAnnotation or EmitAllModulesAnnotation already exists" in new Fixture {
+
+ info("OutputFileAnnotation works")
+ val outputFile = Seq(OutputFileAnnotation("Bar"), FirrtlCircuitAnnotation(circuit))
+ phase.transform(outputFile).toSeq should be (outputFile)
+
+ info("EmitAllModulesAnnotation works")
+ val eam = Seq(EmitAllModulesAnnotation(classOf[ChirrtlEmitter]), FirrtlCircuitAnnotation(circuit))
+ phase.transform(eam).toSeq should be (eam)
+ }
+
+}
diff --git a/src/test/scala/firrtlTests/stage/phases/ChecksSpec.scala b/src/test/scala/firrtlTests/stage/phases/ChecksSpec.scala
new file mode 100644
index 00000000..47aa9b5b
--- /dev/null
+++ b/src/test/scala/firrtlTests/stage/phases/ChecksSpec.scala
@@ -0,0 +1,89 @@
+// See LICENSE for license details.
+
+package firrtlTests.stage.phases
+
+import org.scalatest.{FlatSpec, Matchers}
+
+import firrtl.stage._
+
+import firrtl.{AnnotationSeq, ChirrtlEmitter, EmitAllModulesAnnotation, NoneCompiler}
+import firrtl.options.{OptionsException, OutputAnnotationFileAnnotation, Phase}
+import firrtl.stage.phases.{AddImplicitOutputFile, Checks}
+
+class ChecksSpec extends FlatSpec with Matchers {
+
+ class Fixture { val phase: Phase = new Checks }
+
+ val inputFile = FirrtlFileAnnotation("foo")
+ val outputFile = OutputFileAnnotation("bar")
+ val emitAllModules = EmitAllModulesAnnotation(classOf[ChirrtlEmitter])
+ val outputAnnotationFile = OutputAnnotationFileAnnotation("baz")
+ val goodCompiler = CompilerAnnotation(new NoneCompiler())
+ val infoMode = InfoModeAnnotation("ignore")
+
+ val min = Seq(inputFile, goodCompiler, infoMode)
+
+ def checkExceptionMessage(phase: Phase, annotations: AnnotationSeq, messageStart: String): Unit =
+ intercept[OptionsException]{ phase.transform(annotations) }.getMessage should startWith(messageStart)
+
+ behavior of classOf[Checks].toString
+
+ it should "require exactly one input source" in new Fixture {
+ info("0 input source causes an exception")
+ checkExceptionMessage(phase, Seq.empty, "Unable to determine FIRRTL source to read")
+
+ info("2 input sources causes an exception")
+ val in = min :+ FirrtlSourceAnnotation("circuit Foo:")
+ checkExceptionMessage(phase, in, "Multiply defined input FIRRTL sources")
+ }
+
+ it should "require mutual exclusivity of OutputFileAnnotation and EmitAllModulesAnnotation" in new Fixture {
+ info("OutputFileAnnotation alone works")
+ phase.transform(min :+ outputFile)
+
+ info("OneFilePerModuleAnnotation alone works")
+ phase.transform(min :+ emitAllModules)
+
+ info("Together, they throw an exception")
+ val in = min ++ Seq(outputFile, emitAllModules)
+ checkExceptionMessage(phase, in, "Output file is incompatible with emit all modules annotation")
+ }
+
+ it should "enforce zero or one output files" in new Fixture {
+ val in = min ++ Seq(outputFile, outputFile)
+ checkExceptionMessage(phase, in, "No more than one output file can be specified")
+ }
+
+ it should "enforce exactly one compiler" in new Fixture {
+ info("0 compilers should throw an exception")
+ val inZero = Seq(inputFile, infoMode)
+ checkExceptionMessage(phase, inZero, "Exactly one compiler must be specified, but none found")
+
+ info("2 compilers should throw an exception")
+ val c = goodCompiler.compiler
+ val inTwo = min :+ goodCompiler
+ checkExceptionMessage(phase, inTwo, s"Exactly one compiler must be specified, but found '$c, $c'")
+ }
+
+ it should "validate info mode names" in new Fixture {
+ info("Good info mode names should work")
+ Seq("ignore", "use", "gen", "append")
+ .map(info => phase.transform(Seq(inputFile, goodCompiler, InfoModeAnnotation(info))))
+ }
+
+ it should "enforce exactly one info mode" in new Fixture {
+ info("0 info modes should throw an exception")
+ checkExceptionMessage(phase, Seq(inputFile, goodCompiler),
+ "Exactly one info mode must be specified, but none found")
+
+ info("2 info modes should throw an exception")
+ val i = infoMode.modeName
+ checkExceptionMessage(phase, min :+ infoMode, s"Exactly one info mode must be specified, but found '$i, $i'")
+ }
+
+ it should "pass if the minimum annotations are specified" in new Fixture {
+ info(s"""Minimum required: ${min.map(_.getClass.getSimpleName).mkString(", ")}""")
+ phase.transform(min)
+ }
+
+}
diff --git a/src/test/scala/firrtlTests/stage/phases/CompilerSpec.scala b/src/test/scala/firrtlTests/stage/phases/CompilerSpec.scala
new file mode 100644
index 00000000..7a3ab6b7
--- /dev/null
+++ b/src/test/scala/firrtlTests/stage/phases/CompilerSpec.scala
@@ -0,0 +1,143 @@
+// See LICENSE for license details.
+
+package firrtlTests.stage.phases
+
+import org.scalatest.{FlatSpec, Matchers}
+
+import firrtl.{Compiler => _, _}
+import firrtl.options.{Phase, PhasePrerequisiteException}
+import firrtl.stage.{CompilerAnnotation, FirrtlCircuitAnnotation, RunFirrtlTransformAnnotation}
+import firrtl.stage.phases.Compiler
+
+class CompilerSpec extends FlatSpec with Matchers {
+
+ class Fixture { val phase: Phase = new Compiler }
+
+ behavior of classOf[Compiler].toString
+
+ it should "do nothing for an empty AnnotationSeq" in new Fixture {
+ phase.transform(Seq.empty).toSeq should be (empty)
+ }
+
+ /** A circuit with a parameterized main (top name) that is different at High, Mid, and Low FIRRTL forms. */
+ private def chirrtl(main: String): String =
+ s"""|circuit $main:
+ | module $main:
+ | output foo: {bar: UInt}
+ |
+ | foo.bar <= UInt<4>("h0")
+ |""".stripMargin
+
+ it should "compile a circuit to Low FIRRTL when using the Verilog compiler" in new Fixture {
+ val compiler = new VerilogCompiler
+
+ val circuitIn = Parser.parse(chirrtl("top"))
+ val circuitOut = compiler.compile(CircuitState(circuitIn, ChirrtlForm), Seq.empty).circuit
+
+ val input = Seq(
+ FirrtlCircuitAnnotation(circuitIn),
+ CompilerAnnotation(compiler) )
+
+ val expected = Seq(FirrtlCircuitAnnotation(circuitOut))
+
+ phase.transform(input).toSeq should be (expected)
+ }
+
+ it should "compile multiple FirrtlCircuitAnnotations" in new Fixture {
+ val (nc, hfc, mfc, lfc, vc, svc) = (
+ new NoneCompiler,
+ new HighFirrtlCompiler,
+ new MiddleFirrtlCompiler,
+ new LowFirrtlCompiler,
+ new VerilogCompiler,
+ new SystemVerilogCompiler )
+ val (ce, hfe, mfe, lfe, ve, sve) = (
+ new ChirrtlEmitter,
+ new HighFirrtlEmitter,
+ new MiddleFirrtlEmitter,
+ new LowFirrtlEmitter,
+ new VerilogEmitter,
+ new SystemVerilogEmitter )
+
+ val a = Seq(
+ /* Default Compiler is HighFirrtlCompiler */
+ CompilerAnnotation(hfc),
+
+ /* First compiler group, use NoneCompiler */
+ FirrtlCircuitAnnotation(Parser.parse(chirrtl("a"))),
+ CompilerAnnotation(nc),
+ RunFirrtlTransformAnnotation(ce),
+ EmitCircuitAnnotation(ce.getClass),
+
+ /* Second compiler group, use default HighFirrtlCompiler */
+ FirrtlCircuitAnnotation(Parser.parse(chirrtl("b"))),
+ RunFirrtlTransformAnnotation(ce),
+ EmitCircuitAnnotation(ce.getClass),
+ RunFirrtlTransformAnnotation(hfe),
+ EmitCircuitAnnotation(hfe.getClass),
+
+ /* Third compiler group, use MiddleFirrtlCompiler */
+ FirrtlCircuitAnnotation(Parser.parse(chirrtl("c"))),
+ CompilerAnnotation(mfc),
+ RunFirrtlTransformAnnotation(ce),
+ EmitCircuitAnnotation(ce.getClass),
+ RunFirrtlTransformAnnotation(hfe),
+ EmitCircuitAnnotation(hfe.getClass),
+ RunFirrtlTransformAnnotation(mfe),
+ EmitCircuitAnnotation(mfe.getClass),
+
+ /* Fourth compiler group, use LowFirrtlCompiler*/
+ FirrtlCircuitAnnotation(Parser.parse(chirrtl("d"))),
+ CompilerAnnotation(lfc),
+ RunFirrtlTransformAnnotation(ce),
+ EmitCircuitAnnotation(ce.getClass),
+ RunFirrtlTransformAnnotation(hfe),
+ EmitCircuitAnnotation(hfe.getClass),
+ RunFirrtlTransformAnnotation(mfe),
+ EmitCircuitAnnotation(mfe.getClass),
+ RunFirrtlTransformAnnotation(lfe),
+ EmitCircuitAnnotation(lfe.getClass),
+
+ /* Fifth compiler group, use VerilogCompiler */
+ FirrtlCircuitAnnotation(Parser.parse(chirrtl("e"))),
+ CompilerAnnotation(vc),
+ RunFirrtlTransformAnnotation(ce),
+ EmitCircuitAnnotation(ce.getClass),
+ RunFirrtlTransformAnnotation(hfe),
+ EmitCircuitAnnotation(hfe.getClass),
+ RunFirrtlTransformAnnotation(mfe),
+ EmitCircuitAnnotation(mfe.getClass),
+ RunFirrtlTransformAnnotation(lfe),
+ EmitCircuitAnnotation(lfe.getClass),
+ RunFirrtlTransformAnnotation(ve),
+ EmitCircuitAnnotation(ve.getClass),
+
+ /* Sixth compiler group, use SystemVerilogCompiler */
+ FirrtlCircuitAnnotation(Parser.parse(chirrtl("f"))),
+ CompilerAnnotation(svc),
+ RunFirrtlTransformAnnotation(ce),
+ EmitCircuitAnnotation(ce.getClass),
+ RunFirrtlTransformAnnotation(hfe),
+ EmitCircuitAnnotation(hfe.getClass),
+ RunFirrtlTransformAnnotation(mfe),
+ EmitCircuitAnnotation(mfe.getClass),
+ RunFirrtlTransformAnnotation(lfe),
+ EmitCircuitAnnotation(lfe.getClass),
+ RunFirrtlTransformAnnotation(sve),
+ EmitCircuitAnnotation(sve.getClass)
+ )
+
+ val output = phase.transform(a)
+
+ info("with the same number of output FirrtlCircuitAnnotations")
+ output
+ .collect{ case a: FirrtlCircuitAnnotation => a }
+ .size should be (6)
+
+ info("and all expected EmittedAnnotations should be generated")
+ output
+ .collect{ case a: EmittedAnnotation[_] => a }
+ .size should be (20)
+ }
+
+}
diff --git a/src/test/scala/firrtlTests/stage/phases/WriteEmittedSpec.scala b/src/test/scala/firrtlTests/stage/phases/WriteEmittedSpec.scala
new file mode 100644
index 00000000..4f7ad92a
--- /dev/null
+++ b/src/test/scala/firrtlTests/stage/phases/WriteEmittedSpec.scala
@@ -0,0 +1,79 @@
+// See LICENSE for license details.
+
+package firrtlTests.stage.phases
+
+import org.scalatest.{FlatSpec, Matchers}
+
+import java.io.File
+
+import firrtl._
+
+import firrtl.options.{Phase, TargetDirAnnotation}
+import firrtl.stage.OutputFileAnnotation
+import firrtl.stage.phases.WriteEmitted
+
+class WriteEmittedSpec extends FlatSpec with Matchers {
+
+ def removeEmitted(a: AnnotationSeq): AnnotationSeq = a.flatMap {
+ case a: EmittedAnnotation[_] => None
+ case a => Some(a)
+ }
+
+ class Fixture { val phase: Phase = new WriteEmitted }
+
+ behavior of classOf[WriteEmitted].toString
+
+ it should "write emitted circuits" in new Fixture {
+ val annotations = Seq(
+ TargetDirAnnotation("test_run_dir/WriteEmittedSpec"),
+ EmittedFirrtlCircuitAnnotation(EmittedFirrtlCircuit("foo", "", ".foocircuit")),
+ EmittedFirrtlCircuitAnnotation(EmittedFirrtlCircuit("bar", "", ".barcircuit")),
+ EmittedVerilogCircuitAnnotation(EmittedVerilogCircuit("baz", "", ".bazcircuit")) )
+ val expected = Seq("foo.foocircuit", "bar.barcircuit", "baz.bazcircuit")
+ .map(a => new File(s"test_run_dir/WriteEmittedSpec/$a"))
+
+ info("annotations are unmodified")
+ phase.transform(annotations).toSeq should be (removeEmitted(annotations).toSeq)
+
+ expected.foreach{ a =>
+ info(s"$a was written")
+ a should (exist)
+ a.delete()
+ }
+ }
+
+ it should "default to the output file name if one exists" in new Fixture {
+ val annotations = Seq(
+ TargetDirAnnotation("test_run_dir/WriteEmittedSpec"),
+ OutputFileAnnotation("quux"),
+ EmittedFirrtlCircuitAnnotation(EmittedFirrtlCircuit("qux", "", ".quxcircuit")) )
+ val expected = new File("test_run_dir/WriteEmittedSpec/quux.quxcircuit")
+
+ info("annotations are unmodified")
+ phase.transform(annotations).toSeq should be (removeEmitted(annotations).toSeq)
+
+ info(s"$expected was written")
+ expected should (exist)
+ expected.delete()
+ }
+
+ it should "write emitted modules" in new Fixture {
+ val annotations = Seq(
+ TargetDirAnnotation("test_run_dir/WriteEmittedSpec"),
+ EmittedFirrtlModuleAnnotation(EmittedFirrtlModule("foo", "", ".foomodule")),
+ EmittedFirrtlModuleAnnotation(EmittedFirrtlModule("bar", "", ".barmodule")),
+ EmittedVerilogModuleAnnotation(EmittedVerilogModule("baz", "", ".bazmodule")) )
+ val expected = Seq("foo.foomodule", "bar.barmodule", "baz.bazmodule")
+ .map(a => new File(s"test_run_dir/WriteEmittedSpec/$a"))
+
+ info("EmittedComponent annotations are deleted")
+ phase.transform(annotations).toSeq should be (removeEmitted(annotations).toSeq)
+
+ expected.foreach{ a =>
+ info(s"$a was written")
+ a should (exist)
+ a.delete()
+ }
+ }
+
+}