From 8e42c6f340b2a35bfb5c08df116a1e88de052eb7 Mon Sep 17 00:00:00 2001 From: Jakob Odersky Date: Sat, 3 Dec 2016 22:36:23 -0800 Subject: Add test suite --- build.sbt | 1 + flow-core/build.sbt | 2 + .../scala/ch/jodersky/flow/PseudoTerminal.scala | 43 ++++++++++++++++++ .../scala/ch/jodersky/flow/SerialManagerSpec.scala | 38 ++++++++++++++++ .../ch/jodersky/flow/SerialOperatorSpec.scala | 53 ++++++++++++++++++++++ flow-stream/build.sbt | 2 +- .../scala/ch/jodersky/flow/stream/SerialSpec.scala | 51 +++++++++++++++++++++ project/Dependencies.scala | 4 +- project/FlowBuild.scala | 5 +- 9 files changed, 195 insertions(+), 4 deletions(-) create mode 100644 flow-core/src/test/scala/ch/jodersky/flow/PseudoTerminal.scala create mode 100644 flow-core/src/test/scala/ch/jodersky/flow/SerialManagerSpec.scala create mode 100644 flow-core/src/test/scala/ch/jodersky/flow/SerialOperatorSpec.scala create mode 100644 flow-stream/src/test/scala/ch/jodersky/flow/stream/SerialSpec.scala diff --git a/build.sbt b/build.sbt index 91efbd3..2f895fb 100644 --- a/build.sbt +++ b/build.sbt @@ -37,3 +37,4 @@ scalacOptions in (ScalaUnidoc, doc) ++= Seq( siteMappings ++= (mappings in (ScalaUnidoc, packageDoc)).value.map{ case (file, path) => (file, "api/" + path) } +fork := true diff --git a/flow-core/build.sbt b/flow-core/build.sbt index 487afe0..40eb978 100644 --- a/flow-core/build.sbt +++ b/flow-core/build.sbt @@ -3,5 +3,7 @@ import flow.{FlowBuild, Dependencies} FlowBuild.commonSettings libraryDependencies += Dependencies.akkaActor +libraryDependencies += Dependencies.akkaTestKit +libraryDependencies += Dependencies.scalatest target in javah := (baseDirectory in ThisBuild).value / "flow-native" / "src" / "include" diff --git a/flow-core/src/test/scala/ch/jodersky/flow/PseudoTerminal.scala b/flow-core/src/test/scala/ch/jodersky/flow/PseudoTerminal.scala new file mode 100644 index 0000000..8e891ae --- /dev/null +++ b/flow-core/src/test/scala/ch/jodersky/flow/PseudoTerminal.scala @@ -0,0 +1,43 @@ +package ch.jodersky.flow + +import java.io.{File, IOException} +import java.nio.file.Files + +import scala.concurrent.duration._ +import scala.sys.process._ +import scala.util.control.NonFatal + +trait PseudoTerminal { + + final val SetupTimeout = 100.milliseconds + + def withEcho[A](action: (String, SerialSettings) => A): A = { + val dir = Files.createTempDirectory("flow-pty").toFile + val pty = new File(dir, "pty") + + val socat = try { + val s = Seq( + "socat", + "-d -d", + s"exec:cat,pty,raw,b115200,echo=0", + s"pty,raw,b115200,echo=0,link=${pty.getAbsolutePath}" + ).run(ProcessLogger(println(_)), false) + Thread.sleep(SetupTimeout.toMillis) // allow ptys to set up + s + } catch { + case NonFatal(ex) => + throw new IOException( + "Error running echo service, make sure the program 'socat' is installed", ex) + } + + try { + val result = action(pty.getAbsolutePath, SerialSettings(baud = 115200)) + Thread.sleep(SetupTimeout.toMillis) // allow for async cleanup before destroying ptys + result + } finally { + socat.destroy() + dir.delete() + } + } + +} diff --git a/flow-core/src/test/scala/ch/jodersky/flow/SerialManagerSpec.scala b/flow-core/src/test/scala/ch/jodersky/flow/SerialManagerSpec.scala new file mode 100644 index 0000000..59af305 --- /dev/null +++ b/flow-core/src/test/scala/ch/jodersky/flow/SerialManagerSpec.scala @@ -0,0 +1,38 @@ +package ch.jodersky.flow + +import akka.actor.ActorSystem +import akka.io.IO +import akka.testkit.{ImplicitSender, TestKit} +import org.scalatest._ + +class SerialManagerSpec + extends TestKit(ActorSystem("serial-manager")) + with ImplicitSender + with WordSpecLike + with Matchers + with BeforeAndAfterAll + with PseudoTerminal { + + override def afterAll { + TestKit.shutdownActorSystem(system) + } + + "Serial manager" should { + val manager = IO(Serial) + + "open an existing port" in { + withEcho{ case (port, settings) => + manager ! Serial.Open(port, settings) + expectMsgType[Serial.Opened] + } + } + + "fail opening a non-existing port" in { + val cmd = Serial.Open("nonexistent", SerialSettings(115200)) + manager ! cmd + assert(expectMsgType[Serial.CommandFailed].command == cmd) + } + + } + +} diff --git a/flow-core/src/test/scala/ch/jodersky/flow/SerialOperatorSpec.scala b/flow-core/src/test/scala/ch/jodersky/flow/SerialOperatorSpec.scala new file mode 100644 index 0000000..4a6cf1e --- /dev/null +++ b/flow-core/src/test/scala/ch/jodersky/flow/SerialOperatorSpec.scala @@ -0,0 +1,53 @@ +package ch.jodersky.flow + +import scala.concurrent.duration._ + +import akka.actor.{ActorRef, ActorSystem} +import akka.testkit.{ImplicitSender, TestKit} +import akka.util.ByteString +import org.scalatest._ + +case class Ack(n: Int) extends Serial.Event + +class SerialOperatorSpec + extends TestKit(ActorSystem("serial-operator")) + with ImplicitSender + with WordSpecLike + with Matchers + with BeforeAndAfterAll + with SequentialNestedSuiteExecution + with PseudoTerminal { + + override def afterAll { + TestKit.shutdownActorSystem(system) + } + + def withEchoOp[A](action: ActorRef => A): A = { + withEcho { case (port, settings) => + val connection = SerialConnection.open(port, settings) + val operator = system.actorOf(SerialOperator.apply(connection, 1024, testActor)) + action(operator) + } + } + + "Serial operator" should { + + "follow the correct protocol" in withEchoOp { op => + expectMsgType[Serial.Opened] + + val data = ByteString("hello world".getBytes("utf-8")) + op ! Serial.Write(data) + expectMsg(Serial.Received(data)) + + op ! Serial.Write(data, n => Ack(n)) + expectMsg(Serial.Received(data)) + expectMsg(Ack(data.length)) + + op ! Serial.Close + expectMsg(Serial.Closed) + + } + + } + +} diff --git a/flow-stream/build.sbt b/flow-stream/build.sbt index 183c20f..f14190f 100644 --- a/flow-stream/build.sbt +++ b/flow-stream/build.sbt @@ -4,4 +4,4 @@ FlowBuild.commonSettings libraryDependencies += Dependencies.akkaActor libraryDependencies += Dependencies.akkaStream - +libraryDependencies += Dependencies.scalatest diff --git a/flow-stream/src/test/scala/ch/jodersky/flow/stream/SerialSpec.scala b/flow-stream/src/test/scala/ch/jodersky/flow/stream/SerialSpec.scala new file mode 100644 index 0000000..1a1ebdc --- /dev/null +++ b/flow-stream/src/test/scala/ch/jodersky/flow/stream/SerialSpec.scala @@ -0,0 +1,51 @@ +package ch.jodersky.flow +package stream + +import scala.concurrent.Await +import scala.concurrent.duration._ + +import akka.actor.ActorSystem +import akka.stream.ActorMaterializer +import akka.stream.scaladsl.{Keep, Sink, Source} +import akka.util.ByteString +import org.scalatest._ + +class SerialSpec extends WordSpec with BeforeAndAfterAll with PseudoTerminal { + + implicit val system = ActorSystem("flow-test") + implicit val materializer = ActorMaterializer() + + override def afterAll { + system.terminate() + } + + "Serial stream" should { + val data = ByteString(("hello world").getBytes("utf-8")) + + "receive the same data it sends in an echo test" in { + withEcho { case (port, settings) => + val graph = Source.single(data) + .via(Serial().open(port, settings)) // send to echo pty + .scan(ByteString.empty)(_ ++ _) // received elements could potentially be split by OS + .dropWhile(_ != data) + .toMat(Sink.head)(Keep.right) + + Await.result(graph.run(), 2.seconds) + } + } + + "fail if the underlying pty fails" in { + val result = withEcho { case (port, settings) => + Source.single(data) + .via(Serial().open(port, settings)) + .toMat(Sink.last)(Keep.right) + .run()} + + intercept[StreamSerialException] { + Await.result(result, 10.seconds) + } + } + + } + +} diff --git a/project/Dependencies.scala b/project/Dependencies.scala index b697f6d..f02b039 100644 --- a/project/Dependencies.scala +++ b/project/Dependencies.scala @@ -5,7 +5,9 @@ import sbt._ object Dependencies { val akkaActor = "com.typesafe.akka" %% "akka-actor" % "2.4.14" - val akkaStream ="com.typesafe.akka" %% "akka-stream" % "2.4.14" + val akkaTestKit = "com.typesafe.akka" %% "akka-testkit" % "2.4.14" % "test" + val scalatest = "org.scalatest" %% "scalatest" % "3.0.1" % "test" + } diff --git a/project/FlowBuild.scala b/project/FlowBuild.scala index dcd4cdd..19042b6 100644 --- a/project/FlowBuild.scala +++ b/project/FlowBuild.scala @@ -42,14 +42,15 @@ object FlowBuild extends Build { aggregate(core, native, stream) lazy val core = (project in file("flow-core")). - settings(name:= "flow-core") + settings(name:= "flow-core"). + dependsOn(native % "test->runtime") lazy val native = (project in file("flow-native")). settings(name:= "flow-native") lazy val stream = (project in file("flow-stream")). settings(name:= "flow-stream"). - dependsOn(core) + dependsOn(core, core % "test->test", native % "test->runtime") lazy val samplesTerminal = (project in file("flow-samples") / "terminal"). dependsOn(core, native % Runtime) -- cgit v1.2.3