From 26aa8adc30a84d983d020e34b488ac22a31cb544 Mon Sep 17 00:00:00 2001 From: Jakob Odersky Date: Sat, 1 Apr 2017 13:21:15 -0700 Subject: Add yaml parser and docker executor --- .../scala/io/crashbox/ci/DockerExecutorSpec.scala | 122 +++++++++++++++++++++ .../src/test/scala/io/crashbox/ci/ParserSpec.scala | 19 ++++ .../io/crashbox/ci/yaml/CompositeReadersSpec.scala | 26 +++++ .../io/crashbox/ci/yaml/SimpleReadersSpec.scala | 23 ++++ .../test/scala/io/crashbox/ci/yaml/YamlSpec.scala | 40 +++++++ 5 files changed, 230 insertions(+) create mode 100644 crashboxd/src/test/scala/io/crashbox/ci/DockerExecutorSpec.scala create mode 100644 crashboxd/src/test/scala/io/crashbox/ci/ParserSpec.scala create mode 100644 crashboxd/src/test/scala/io/crashbox/ci/yaml/CompositeReadersSpec.scala create mode 100644 crashboxd/src/test/scala/io/crashbox/ci/yaml/SimpleReadersSpec.scala create mode 100644 crashboxd/src/test/scala/io/crashbox/ci/yaml/YamlSpec.scala (limited to 'crashboxd/src/test/scala') diff --git a/crashboxd/src/test/scala/io/crashbox/ci/DockerExecutorSpec.scala b/crashboxd/src/test/scala/io/crashbox/ci/DockerExecutorSpec.scala new file mode 100644 index 0000000..2b6dce7 --- /dev/null +++ b/crashboxd/src/test/scala/io/crashbox/ci/DockerExecutorSpec.scala @@ -0,0 +1,122 @@ +package io.crashbox.ci + +import java.io.{ByteArrayOutputStream, File} +import java.nio.file.Files + +import scala.collection.JavaConverters._ +import scala.concurrent.Await +import scala.concurrent.duration._ + +import akka.actor.ActorSystem +import org.scalatest._ + +class DockerExecutorSpec + extends FlatSpec + with Matchers + with BeforeAndAfterAll { + + val image = "crashbox" + + val timeout = 30.seconds + + implicit val system = ActorSystem("docker-test") + import system.dispatcher + val exec = new DockerExecutor + + override def beforeAll: Unit = { + println("Pulling base docker image for running docker tests") + val base = "debian:jessie-backports" + exec.dockerClient.pull(base) + + withTmp { dir => + println("Adapting base image for tests") + val modifications = s"""|FROM $base + |RUN adduser crashbox + |USER crashbox + |""".stripMargin + Files.write((new File(dir, "Dockerfile")).toPath, modifications.getBytes) + exec.dockerClient.build(dir.toPath, image) + } + + } + + override def afterAll: Unit = { + system.terminate() + } + + def withTmp[A](action: File => A): A = { + val dir = Files.createTempDirectory("crashbox-docker-test").toFile + try action(dir) + finally dir.delete() + } + + def run[A](script: String)(tests: (Int, File, String) => A): A = withTmp { + dir => + val out = new ByteArrayOutputStream(1024) + val awaitable = for (id <- exec.start(image, script, dir, out); + status <- exec.result(id)) yield { + status + } + val status = Await.result(awaitable, timeout) + tests(status, dir, new String(out.toByteArray()).trim()) + } + + "DockerExecutor" should "return expected exit codes" in { + run("true") { + case (status, _, _) => + assert(status == 0) + } + run("false") { + case (status, _, _) => + assert(status == 1) + } + run("nonexistant") { + case (status, _, _) => + assert(status == 127) + } + } + + it should "print the expected output" in { + run("echo hello world") { + case (_, _, out) => + assert(out == "hello world") + } + run("echo hello world >&2") { + case (_, _, out) => + assert(out == "hello world") + } + run("echo hello world > /dev/null") { + case (_, _, out) => + assert(out == "") + } + } + + it should "create expected files" in { + run("echo hello world > data") { + case (_, dir, _) => + val data = Files + .lines((new File(dir, "data")).toPath) + .iterator() + .asScala + .mkString("\n") + assert(data == "hello world") + } + } + + it should "allow cancellation" in { + withTmp { dir => + val script = "while true; do sleep 1; echo sleeping; done" + val out = new ByteArrayOutputStream(1024) + + val id = Await.result(exec.start(image, script, dir, out), timeout) + val check = exec.result(id).map { res => + assert(res == 137) + } + exec.stop(id) + //TODO check if resoruces were cleaned up properly + + Await.result(check, timeout) + } + } + +} diff --git a/crashboxd/src/test/scala/io/crashbox/ci/ParserSpec.scala b/crashboxd/src/test/scala/io/crashbox/ci/ParserSpec.scala new file mode 100644 index 0000000..8834af9 --- /dev/null +++ b/crashboxd/src/test/scala/io/crashbox/ci/ParserSpec.scala @@ -0,0 +1,19 @@ +package io.crashbox.ci + +import org.scalatest._ + +class ParserSpec extends FlatSpec with Matchers { + + val build = """|tasks: + | main: + | image: foo/bar + | script: echo "hello world" + |""".stripMargin + + val parsed = BuildDef( + Seq(TaskDef(DockerEnvironment("foo/bar"), "echo \"hello world\""))) + + "Parser" should "parse build definitions" in { + assert(Parser.parse(build) == Parser.Success(parsed)) + } +} diff --git a/crashboxd/src/test/scala/io/crashbox/ci/yaml/CompositeReadersSpec.scala b/crashboxd/src/test/scala/io/crashbox/ci/yaml/CompositeReadersSpec.scala new file mode 100644 index 0000000..02a13ca --- /dev/null +++ b/crashboxd/src/test/scala/io/crashbox/ci/yaml/CompositeReadersSpec.scala @@ -0,0 +1,26 @@ +package io.crashbox.ci +package yaml + +import org.scalatest._ + +class CompositeReadersSpec + extends FlatSpec + with Matchers + with CompositeReaders + with SimpleReaders { + + "CompositeReaders" should "convert yaml" in { + assert( + Yaml.parse("hello: world").convertTo[Map[String, String]] == Map( + "hello" -> "world")) + assert( + Yaml.parse("hello: 42").convertTo[Map[String, Int]] == Map( + "hello" -> 42)) + + assert(Yaml.parse("- 42").convertTo[Seq[Int]] == Seq(42)) + assert( + Yaml.parse("hello:\n - 42").convertTo[Map[String, Seq[Int]]] == Map( + "hello" -> Seq(42))) + } + +} diff --git a/crashboxd/src/test/scala/io/crashbox/ci/yaml/SimpleReadersSpec.scala b/crashboxd/src/test/scala/io/crashbox/ci/yaml/SimpleReadersSpec.scala new file mode 100644 index 0000000..198e921 --- /dev/null +++ b/crashboxd/src/test/scala/io/crashbox/ci/yaml/SimpleReadersSpec.scala @@ -0,0 +1,23 @@ +package io.crashbox.ci +package yaml + +import org.scalatest._ + +class SimpleReadersSpec extends FlatSpec with Matchers with SimpleReaders { + + "SimpleReaders" should "convert yaml" in { + assert(Yaml.parse("hello").convertTo[String] == "hello") + assert(Yaml.parse("42").convertTo[Byte] == 42.toByte) + assert(Yaml.parse("42").convertTo[Short] == 42.toShort) + assert(Yaml.parse("42").convertTo[Int] == 42) + assert(Yaml.parse("42").convertTo[Long] == 42l) + assert(Yaml.parse("42.0").convertTo[Float] == 42f) + assert(Yaml.parse("42.0").convertTo[Double] == 42.0) + assert(Yaml.parse("true").convertTo[Boolean] == true) + assert(Yaml.parse("false").convertTo[Boolean] == false) + } + + "SimpleReaders" should "fail to convert invalid yaml" in { + intercept[YamlFormatException](Yaml.parse("foo").convertTo[Boolean]) + } +} diff --git a/crashboxd/src/test/scala/io/crashbox/ci/yaml/YamlSpec.scala b/crashboxd/src/test/scala/io/crashbox/ci/yaml/YamlSpec.scala new file mode 100644 index 0000000..6d1de66 --- /dev/null +++ b/crashboxd/src/test/scala/io/crashbox/ci/yaml/YamlSpec.scala @@ -0,0 +1,40 @@ +package io.crashbox.ci +package yaml + +import org.scalatest._ + +class YamlSpec extends FlatSpec with Matchers { + + val yml = """|--- + |foo: bar + |buz: qux + |list: + | - elem1 + | - elem2 + |map: + | elem1: foo + | elem2: bar + | elem3: + |""".stripMargin + + val tree = YamlMap( + Map( + "foo" -> YamlString("bar"), + "buz" -> YamlString("qux"), + "list" -> YamlSeq( + Seq( + YamlString("elem1"), + YamlString("elem2") + )), + "map" -> YamlMap( + Map( + "elem1" -> YamlString("foo"), + "elem2" -> YamlString("bar"), + "elem3" -> YamlString("") + )) + )) + + "Yaml" should "parse valid yaml" in { + assert(Yaml.parse(yml) == tree) + } +} -- cgit v1.2.3