aboutsummaryrefslogtreecommitdiff
path: root/src/test/scala/firrtlTests/options
diff options
context:
space:
mode:
Diffstat (limited to 'src/test/scala/firrtlTests/options')
-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
4 files changed, 280 insertions, 37 deletions
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
+}