aboutsummaryrefslogtreecommitdiffstats
path: root/src/main/scala/com/iinteractive/test/ExternalTest.scala
diff options
context:
space:
mode:
Diffstat (limited to 'src/main/scala/com/iinteractive/test/ExternalTest.scala')
-rw-r--r--src/main/scala/com/iinteractive/test/ExternalTest.scala73
1 files changed, 73 insertions, 0 deletions
diff --git a/src/main/scala/com/iinteractive/test/ExternalTest.scala b/src/main/scala/com/iinteractive/test/ExternalTest.scala
new file mode 100644
index 0000000..7855aa8
--- /dev/null
+++ b/src/main/scala/com/iinteractive/test/ExternalTest.scala
@@ -0,0 +1,73 @@
+package com.iinteractive.test
+
+import scala.concurrent.Await
+import scala.concurrent.duration.Duration
+import scala.concurrent.ExecutionContext.Implicits.global
+import scala.concurrent.Future
+import scala.concurrent.Future._
+import scala.annotation.tailrec
+
+/** Runs an external process which emits TAP, and parses it as a test.
+ *
+ * This test class can be used if you have existing tests that you would like
+ * to be able to include in a test suite using this framework. You just need
+ * to write a test class for each external test that looks like this:
+ *
+ * {{{
+ * class MyTest1 extends ExternalTest("perl", "t/basic.t")
+ * }}}
+ *
+ * This will run your external process, and use its TAP stream as its output.
+ * This will allow it to, for instance, be a part of the test suite that runs
+ * via `sbt test`. As with any other test class, its stdout and stderr will
+ * be sent to `Console.out` and `Console.err`, where they can be overridden
+ * as needed.
+ */
+class ExternalTest (cmdLine: String*) extends Test {
+ protected def runTests (raw: Boolean): Int = {
+ val processBuilder = new ProcessBuilder(cmdLine: _*)
+
+ // Ensure that if stdout and stderr are both pointing to the same place (a
+ // terminal or file or something) that they remain synchronized. This is
+ // only possible if the endpoint they are streaming to is the same place
+ // underneath, with no extra processing in between. (This is safe because
+ // stdout is typically line-buffered, and we only ever output a line at a
+ // time when writing TAP, so in theory, buffering of the underlying file
+ // descriptor shouldn't make a difference here)
+ if (Console.out eq System.out) {
+ processBuilder.redirectOutput(ProcessBuilder.Redirect.INHERIT)
+ }
+ if (Console.err eq System.err) {
+ processBuilder.redirectError(ProcessBuilder.Redirect.INHERIT)
+ }
+
+ val process = processBuilder.start
+
+ val streams = Seq(
+ Console.out -> process.getInputStream,
+ Console.err -> process.getErrorStream
+ )
+
+ val listeners = streams.map { case (out, in) =>
+ Future {
+ val buf = new Array[Byte](1024)
+
+ @tailrec
+ def read {
+ val bytes = in.read(buf)
+ if (bytes >= 0) {
+ out.print(new String(buf.take(bytes)))
+ read
+ }
+ }
+
+ read
+ true
+ }
+ }
+
+ val exitCode = process.waitFor
+ Await.ready(Future.sequence(listeners), Duration.Inf)
+ exitCode
+ }
+}