From f9cc5e3f8478eb9f19cca093b8019e579f9c87e6 Mon Sep 17 00:00:00 2001 From: Jakob Odersky Date: Thu, 2 Mar 2017 23:35:46 -0800 Subject: Implement basic worker functionality --- build.sbt | 4 +- crashbox-worker/build.sbt | 7 +++ .../main/scala/io/crashbox/ci/build/Builders.scala | 15 ++++++ .../scala/io/crashbox/ci/build/ShellBuilders.scala | 33 +++++++++++++ .../scala/io/crashbox/ci/source/Fetchers.scala | 12 +++++ .../scala/io/crashbox/ci/source/GitFetchers.scala | 19 ++++++++ .../src/test/scala/io/crashbox/ci/TestUtil.scala | 13 +++++ .../io/crashbox/ci/build/ShellBuildersSpec.scala | 55 ++++++++++++++++++++++ .../io/crashbox/ci/source/GitFetchersSpec.scala | 36 ++++++++++++++ 9 files changed, 192 insertions(+), 2 deletions(-) create mode 100644 crashbox-worker/build.sbt create mode 100644 crashbox-worker/src/main/scala/io/crashbox/ci/build/Builders.scala create mode 100644 crashbox-worker/src/main/scala/io/crashbox/ci/build/ShellBuilders.scala create mode 100644 crashbox-worker/src/main/scala/io/crashbox/ci/source/Fetchers.scala create mode 100644 crashbox-worker/src/main/scala/io/crashbox/ci/source/GitFetchers.scala create mode 100644 crashbox-worker/src/test/scala/io/crashbox/ci/TestUtil.scala create mode 100644 crashbox-worker/src/test/scala/io/crashbox/ci/build/ShellBuildersSpec.scala create mode 100644 crashbox-worker/src/test/scala/io/crashbox/ci/source/GitFetchersSpec.scala diff --git a/build.sbt b/build.sbt index 23ac49e..aa14fd3 100644 --- a/build.sbt +++ b/build.sbt @@ -5,13 +5,13 @@ scalaVersion in ThisBuild := (crossScalaVersions in ThisBuild).value.head scalacOptions in ThisBuild ++= Seq( "-deprecation", "-feature", - "-Xfatal-warnings", + //"-Xfatal-warnings", "-Xlint" ) fork in ThisBuild := true cancelable in Global := true -lazy val root = (project in file(".")).aggregate(http) +lazy val root = (project in file(".")).aggregate(http, worker) lazy val http = (project in file("crashbox-http")) diff --git a/crashbox-worker/build.sbt b/crashbox-worker/build.sbt new file mode 100644 index 0000000..2b9cf08 --- /dev/null +++ b/crashbox-worker/build.sbt @@ -0,0 +1,7 @@ +import crashbox.Dependencies + +libraryDependencies ++= Seq( + Dependencies.jgitArchive, + Dependencies.jgitServer, + Dependencies.scalatest % Test +) diff --git a/crashbox-worker/src/main/scala/io/crashbox/ci/build/Builders.scala b/crashbox-worker/src/main/scala/io/crashbox/ci/build/Builders.scala new file mode 100644 index 0000000..14b43fe --- /dev/null +++ b/crashbox-worker/src/main/scala/io/crashbox/ci/build/Builders.scala @@ -0,0 +1,15 @@ +package io.crashbox.ci +package build + +import java.io.{ File, OutputStream } +import scala.concurrent.Future + +trait Builders { + + def build( + workdir: File, + stdout: OutputStream, + stderr: OutputStream + ): Future[Int] + +} diff --git a/crashbox-worker/src/main/scala/io/crashbox/ci/build/ShellBuilders.scala b/crashbox-worker/src/main/scala/io/crashbox/ci/build/ShellBuilders.scala new file mode 100644 index 0000000..71bd683 --- /dev/null +++ b/crashbox-worker/src/main/scala/io/crashbox/ci/build/ShellBuilders.scala @@ -0,0 +1,33 @@ +package io.crashbox.ci +package build + +import java.io.OutputStream +import scala.concurrent.Future +import scala.sys.process.{ Process, _ } +import java.io.{ File, InputStream } +import scala.concurrent.Future + +trait ShellBuilders extends Builders { + + def pipe(is: InputStream, os: OutputStream) = { + var n = 0 + val buffer = new Array[Byte](1024); + while ({n = is.read(buffer); n > -1}) { + os.write(buffer, 0, n); + } + os.close() + } + + @deprecated("use git-specific execution context", "todo") + implicit private val ec = scala.concurrent.ExecutionContext.global + + override def build(workdir: File, stdout: OutputStream, stderr: OutputStream): Future[Int] = { + def ignore(in: OutputStream): Unit = () + val io = new ProcessIO(ignore, pipe(_, stdout), pipe(_, stderr)) + + Future{ + Process("./crashbox", Some(workdir)).run(io).exitValue() + } + } + +} diff --git a/crashbox-worker/src/main/scala/io/crashbox/ci/source/Fetchers.scala b/crashbox-worker/src/main/scala/io/crashbox/ci/source/Fetchers.scala new file mode 100644 index 0000000..0fd4f38 --- /dev/null +++ b/crashbox-worker/src/main/scala/io/crashbox/ci/source/Fetchers.scala @@ -0,0 +1,12 @@ +package io.crashbox.ci +package source + +import java.io.File +import java.net.URL +import scala.concurrent.Future + +trait Fetchers { + + def fetch(from: URL, to: File): Future[File] + +} diff --git a/crashbox-worker/src/main/scala/io/crashbox/ci/source/GitFetchers.scala b/crashbox-worker/src/main/scala/io/crashbox/ci/source/GitFetchers.scala new file mode 100644 index 0000000..9b603e2 --- /dev/null +++ b/crashbox-worker/src/main/scala/io/crashbox/ci/source/GitFetchers.scala @@ -0,0 +1,19 @@ +package io.crashbox.ci +package source + +import java.io.File +import java.net.URL +import org.eclipse.jgit.api.Git +import scala.concurrent.Future + +trait GitFetchers extends Fetchers { + + @deprecated("use git-specific execution context", "todo") + implicit private val ec = scala.concurrent.ExecutionContext.global + + def fetch(from: URL, to: File): Future[File] = Future { + Git.cloneRepository.setURI(from.toURI.toString).setDirectory(to).call() + to + } + +} diff --git a/crashbox-worker/src/test/scala/io/crashbox/ci/TestUtil.scala b/crashbox-worker/src/test/scala/io/crashbox/ci/TestUtil.scala new file mode 100644 index 0000000..eb177f8 --- /dev/null +++ b/crashbox-worker/src/test/scala/io/crashbox/ci/TestUtil.scala @@ -0,0 +1,13 @@ +package io.crashbox.ci + +import java.io.{ File, OutputStream } +import java.nio.file.Files + +object TestUtil { + + def withTempDir[A](f: File => A): A = { + val dir = Files.createTempDirectory("crashbox-test").toFile + try f(dir) finally dir.delete() + } + +} diff --git a/crashbox-worker/src/test/scala/io/crashbox/ci/build/ShellBuildersSpec.scala b/crashbox-worker/src/test/scala/io/crashbox/ci/build/ShellBuildersSpec.scala new file mode 100644 index 0000000..9419804 --- /dev/null +++ b/crashbox-worker/src/test/scala/io/crashbox/ci/build/ShellBuildersSpec.scala @@ -0,0 +1,55 @@ +package io.crashbox.ci +package build + +import java.io.{ BufferedOutputStream, ByteArrayOutputStream, File } +import java.nio.file.Files +import java.net.URL +import org.eclipse.jgit.util.Paths +import scala.concurrent.duration._ +import scala.concurrent.Await +import org.scalatest._ + +class ShellBuildersSpec extends FlatSpec with Matchers with ShellBuilders { + + val Timeout = 10.seconds + + def runScript(script: String): (Int, String, String) = { + val stdout = new ByteArrayOutputStream(4096) + val stderr = new ByteArrayOutputStream(4096) + + val result = TestUtil.withTempDir{ dir => + val exec = new File(dir, "crashbox") + exec.createNewFile() + Files.write(exec.toPath, script.getBytes) + exec.setExecutable(true) + + Await.result(build(dir, stdout, stderr), Timeout) + } + stdout.close() + stderr.close() + + (result, new String(stdout.toByteArray(), "utf-8"), new String(stderr.toByteArray(), "utf-8")) + } + + "ShellBuilders" should "run a shell script" in { + val script = """|#!/bin/sh + |echo "hello world" + |echo "foo" >&2 + |""".stripMargin + val (res, stdout, stderr) = runScript(script: String) + + assert(res == 0) + assert(stdout == "hello world\n") + assert(stderr == "foo\n") + } + + it should "report a failed script" in { + val script = """|#!/bin/sh + |exit 1 + |""".stripMargin + val (res, _, _) = runScript(script: String) + + assert(res == 1) + } + +} diff --git a/crashbox-worker/src/test/scala/io/crashbox/ci/source/GitFetchersSpec.scala b/crashbox-worker/src/test/scala/io/crashbox/ci/source/GitFetchersSpec.scala new file mode 100644 index 0000000..a0886a5 --- /dev/null +++ b/crashbox-worker/src/test/scala/io/crashbox/ci/source/GitFetchersSpec.scala @@ -0,0 +1,36 @@ +package io.crashbox.ci +package source + +import java.io.File +import java.nio.file.Files +import java.net.URL +import org.eclipse.jgit.api.Git +import scala.concurrent.duration._ +import scala.concurrent.Await +import org.scalatest._ + +class GitFetchersSpec extends FlatSpec with Matchers with GitFetchers { + + val Timeout = 10.seconds + + def makeRepo(dir: File): Unit = { + Git.init().setDirectory(dir).call() + val file1 = new File(dir, "file1") + file1.createNewFile() + val file2 = new File(dir, "file2") + file2.createNewFile() + Git.open(dir).add().addFilepattern(".").call() + Git.open(dir).commit().setMessage("initial commit").call() + } + + "GitFetchers" should "be able to clone a local repository" in { + TestUtil.withTempDir{ remote => + makeRepo(remote) + TestUtil.withTempDir { local => + val cloned = Await.result(fetch(remote.toURI().toURL(), local), Timeout) + assert(cloned.listFiles().length == 3) + } + } + } + +} -- cgit v1.2.3