Skip to content

Commit

Permalink
Add out dir lock test
Browse files Browse the repository at this point in the history
  • Loading branch information
alexarchambault committed Oct 7, 2024
1 parent 80acc92 commit d7c0a73
Show file tree
Hide file tree
Showing 3 changed files with 142 additions and 0 deletions.
12 changes: 12 additions & 0 deletions integration/feature/output-directory/resources/build.mill
Original file line number Diff line number Diff line change
Expand Up @@ -5,4 +5,16 @@ import mill.scalalib._

object `package` extends RootModule with ScalaModule {
def scalaVersion = scala.util.Properties.versionNumberString

def hello = Task {
"Hello from hello task"
}

def blockWhileExists(path: os.Path) = Task.Command[String] {
if (!os.exists(path))
os.write(path, Array.emptyByteArray)
while (os.exists(path))
Thread.sleep(100L)
"Blocking command done"
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,82 @@
package mill.integration

import mill.testkit.UtestIntegrationTestSuite
import utest._

import java.io.ByteArrayOutputStream
import java.util.concurrent.CountDownLatch

import scala.concurrent.Await
import scala.concurrent.duration.Duration

object OutputDirectoryLockTests extends UtestIntegrationTestSuite {

def tests: Tests = Tests {
test("basic") - integrationTest { tester =>
import tester._
val signalFile = workspacePath / "do-wait"
System.err.println("Spawning blocking task")
val blocksFuture = evalAsync(("show", "blockWhileExists", "--path", signalFile), check = true)
while (!os.exists(signalFile) && !blocksFuture.isCompleted)
Thread.sleep(100L)
if (os.exists(signalFile))
System.err.println("Blocking task is running")
else {
System.err.println("Failed to run blocking task")
Predef.assert(blocksFuture.isCompleted)
blocksFuture.value.get.get
}

val testCommand: os.Shellable = ("show", "hello")
val testMessage = "Hello from hello task"

System.err.println("Evaluating task without lock")
val noLockRes = eval(("--no-build-lock", testCommand), check = true)
assert(noLockRes.out.contains(testMessage))

System.err.println("Evaluating task without waiting for lock (should fail)")
val noWaitRes = eval(("--no-wait-for-build-lock", testCommand))
assert(noWaitRes.err.contains("Cannot proceed, another Mill process is running tasks"))

System.err.println("Evaluating task waiting for the lock")

val lock = new CountDownLatch(1)
val stderr = new ByteArrayOutputStream
var success = false
val futureWaitingRes = evalAsync(
testCommand,
stderr = os.ProcessOutput {
val expectedMessage =
"Another Mill process is running tasks, waiting for it to be done..."

(bytes, len) =>
stderr.write(bytes, 0, len)
val output = new String(stderr.toByteArray)
if (output.contains(expectedMessage))
lock.countDown()
},
check = true
)
try {
lock.await()
success = true
} finally {
if (!success) {
System.err.println("Waiting task output:")
System.err.write(stderr.toByteArray)
}
}

System.err.println("Task is waiting for the lock, unblocking it")
os.remove(signalFile)

System.err.println("Blocking task should exit")
val blockingRes = Await.result(blocksFuture, Duration.Inf)
assert(blockingRes.out.contains("Blocking command done"))

System.err.println("Waiting task should be free to proceed")
val waitingRes = Await.result(futureWaitingRes, Duration.Inf)
assert(waitingRes.out.contains(testMessage))
}
}
}
48 changes: 48 additions & 0 deletions testkit/src/mill/testkit/IntegrationTester.scala
Original file line number Diff line number Diff line change
Expand Up @@ -5,6 +5,11 @@ import mill.eval.Evaluator
import mill.resolve.SelectMode
import ujson.Value

import java.util.concurrent.atomic.AtomicInteger

import scala.concurrent.{Future, Promise}
import scala.util.Try

/**
* Helper meant for executing Mill integration tests, which runs Mill in a subprocess
* against a folder with a `build.mill` and project files. Provides APIs such as [[eval]]
Expand Down Expand Up @@ -91,6 +96,49 @@ object IntegrationTester {
)
}

private val evalAsyncCounter = new AtomicInteger
def evalAsync(
cmd: os.Shellable,
env: Map[String, String] = millTestSuiteEnv,
cwd: os.Path = workspacePath,
stdin: os.ProcessInput = os.Pipe,
stdout: os.ProcessOutput = os.Pipe,
stderr: os.ProcessOutput = os.Pipe,
mergeErrIntoOut: Boolean = false,
timeout: Long = -1,
check: Boolean = false,
propagateEnv: Boolean = true,
timeoutGracePeriod: Long = 100
): Future[IntegrationTester.EvalResult] = {

val promise = Promise[IntegrationTester.EvalResult]()

val thread = new Thread(s"mill-test-background-eval-${evalAsyncCounter.incrementAndGet()}") {
setDaemon(true)
override def run(): Unit =
promise.complete {
Try {
eval(
cmd = cmd,
env = env,
cwd = cwd,
stdin = stdin,
stdout = stdout,
stderr = stderr,
mergeErrIntoOut = mergeErrIntoOut,
timeout = timeout,
check = check,
propagateEnv = propagateEnv,
timeoutGracePeriod = timeoutGracePeriod
)
}
}
}
thread.start()

promise.future
}

def millTestSuiteEnv: Map[String, String] = Map("MILL_TEST_SUITE" -> this.getClass().toString())

/**
Expand Down

0 comments on commit d7c0a73

Please sign in to comment.