package io.crashbox.ci import java.io.{ByteArrayOutputStream, File, OutputStream} import scala.concurrent.{Await, Future} import scala.concurrent.duration._ import Builder._ import akka.NotUsed import akka.actor.ActorSystem import akka.stream.ActorMaterializer import akka.stream.scaladsl.{Keep, RunnableGraph, Sink, Source} import org.scalatest._ class BuildStageSpec extends FlatSpec with Matchers with BeforeAndAfterAll { implicit val system = ActorSystem("crashboxd-buildstage") implicit val materializer = ActorMaterializer() import system.dispatcher case class DummyEnv() extends Environment { val id = DummyId() } case class DummyId() extends ExecutionId { import DummyId._ private var _state: State = Starting def state = _state.synchronized { _state } def state_=(value: State) = _state.synchronized { _state = value } } object DummyId { sealed trait State case object Starting extends State case object Running extends State case object Result extends State case object Stopped extends State } class DummyExecutor( startDelay: Duration = 0.seconds, resultDelay: Duration = 0.seconds, stopDelay: Duration = 0.seconds ) extends Executor[DummyEnv, DummyId] { override def start(env: DummyEnv, script: String, dir: File, out: OutputStream) = Future { Thread.sleep(startDelay.toMillis) env.id.state = DummyId.Running env.id } override def result(id: DummyId) = Future { Thread.sleep(resultDelay.toMillis) id.state = DummyId.Result 0 } override def stop(id: DummyId) = { Thread.sleep(stopDelay.toMillis) id.state = DummyId.Stopped } } def dummySource( executor: DummyExecutor, env: DummyEnv ): Source[BuildState, NotUsed] = { val stage = new BuildSource( TaskId("dummy", 0), TaskDef(env, ""), executor, new File("nonexistant"), new ByteArrayOutputStream(0) ) Source.fromGraph(stage) } "BuildStage" should "transition states and emit in the correct order" in { val delay = 0.5.seconds val executor = new DummyExecutor(delay, delay, delay) val env = new DummyEnv() val taskId = TaskId("dummy", 0) val taskDef = TaskDef(env, "dummy script") val stage = new BuildSource( taskId, taskDef, executor, new File("nonexistant"), new ByteArrayOutputStream(0) ) val source = Source.fromGraph(stage) val eventFuture = source.toMat(Sink.seq)(Keep.right).run() Thread.sleep((delay / 2).toMillis) assert(env.id.state == DummyId.Starting) Thread.sleep(delay.toMillis) assert(env.id.state == DummyId.Running) Thread.sleep(delay.toMillis) assert(env.id.state == DummyId.Result) Thread.sleep(delay.toMillis) assert(env.id.state == DummyId.Stopped) val expectedEvents = Seq( TaskStarting(taskId, taskDef), TaskRunning(taskId, env.id), TaskFinished(taskId, 0) ) val events = Await.result(eventFuture, 10.seconds) assert(events.toList === expectedEvents.toList) } }