From 7efb2caf7d8832a7d3a9d2ac55862e43267a3eb2 Mon Sep 17 00:00:00 2001 From: Jesse Luehrs Date: Wed, 6 Mar 2013 15:32:26 -0600 Subject: move the directory structure too --- .../scala/com/iinteractive/test/ExternalTest.scala | 73 ++++++++++++++++++++++ 1 file changed, 73 insertions(+) create mode 100644 src/main/scala/com/iinteractive/test/ExternalTest.scala (limited to 'src/main/scala/com/iinteractive/test/ExternalTest.scala') 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 + } +} -- cgit v1.2.3-54-g00ecf