summaryrefslogtreecommitdiff
path: root/crashboxd/src/test/scala/io/crashbox/ci/DockerExecutorSpec.scala
blob: 5ec95cd19ac7045f39b149705f5dc3fa260b7672 (plain) (blame)
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
package io.crashbox.ci

import com.spotify.docker.client.DockerClient
import com.spotify.docker.client.DockerClient.ListContainersParam
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._
import scala.util.Random


object DockerUtil {
  import TestUtil._

  val defaultImage = "crashbox"

  def ensureImage(client: DockerClient): Unit = {
    println("Pulling base docker image for running docker tests")
    val baseImage = "debian:jessie-backports"
    client.pull(baseImage)

    withTempFile { dir =>
      println("Adapting base image for tests")
      val modifications = s"""|FROM $baseImage
                              |RUN adduser crashbox
                              |USER crashbox
                              |""".stripMargin
      Files.write((new File(dir, "Dockerfile")).toPath, modifications.getBytes)
      client.build(dir.toPath, defaultImage)
    }
  }

}

class DockerExecutorSpec
    extends FlatSpec
    with Matchers
    with BeforeAndAfterAll
    with BeforeAndAfterEach {

  import TestUtil._

  val image = "crashbox"

  val timeout = 30.seconds

  implicit val system = ActorSystem("docker-test")
  import system.dispatcher
  val exec = new DockerExecutor

  override def beforeAll: Unit = {
    sys.addShutdownHook {
      println("------------------- fooooo")
      exec.clean()
    }
    DockerUtil.ensureImage(exec.dockerClient)
  }

  override def afterAll: Unit = {
    system.terminate()
  }

  override def afterEach: Unit = {
    assert(exec.clean(), "Spawned containers were not removed")
  }

  def run[A](script: String)(tests: (Int, File, String) => A): A = withTempFile {
    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 cancellations" in {
    withTempFile { 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)
      Await.result(check, timeout)
    }
  }

}