1

my e2e test task sends some http requests to the server. i want to start that server (Play framework based) on a separate jvm, then start the test which hits the server and let it finish, then stop the server.

i looked through many SO threads so far found these options:

  1. use sbt-sequential
  2. use sbt-revolver
  3. use alias

but in my experiments setting fork doesn't work, i.e. it still blocks execution when server is started

fork := true
fork in run := true
fork in Test := true
fork in IntegrationTest := true

The startServer/stopServer examples in sbt docs are also blocking it seems

I also tried just starting the server in background from shell but server is quickly shut down, similar to this question

nohup sbt -Djline.terminal=jline.UnsupportedTerminal web/run  < /dev/null > /tmp/sbt.log 2>&1 &

related questions:

alex
  • 1,757
  • 4
  • 21
  • 32

2 Answers2

2

fork doesn't run task in parallel - it just makes sure that tests are run in a separate JVM which helps with things like shutdown webhooks or disconnecting from services that doesn't handle resource release properly (e.g. DB connection that never calls disconnect).

If you want to use the same sbt to start server AND run test against that instance (which sounds like easily breakable antipattern BTW) you can use somethings like:

  • reStart
  • it:test
  • reStop

However that would be tricky because reStart yields immediately so tests would start when the server setup started but not necessarily completed. Race condition, failing tests, or blocking all tests until server finishes starting.

This is why nobody does it. Much easier to handle solution is to:

  • start the server in test in some beforeAll method and make this method complete only after server is responding to queries
  • shutdown it in some afterAll method (or somehow handle both of these using something like cats.effect.Resource or similar)
  • depending on situation:
    • running tests sequentially to avoid starting two instances at the same time or
    • generating config for each test so that they could be run in parallel without clashing on ports allocations

Anything else is just a hack that is going to fail sooner rather than later.

Mateusz Kubuszok
  • 24,995
  • 4
  • 42
  • 64
0

answering my own question, what we ended up doing is

  1. use "sbt stage" to create standalone server jar & run script for the Play web app (in web/target/universal/stage/bin/)
  2. create run_integration_tests.sh shell script that starts the server, waits 30 sec and starts test
  3. add runIntegrationTests task in build.sbt which calls run_integration_tests.sh, and add it to it:test

run_integration_tests.sh:

#! /bin/bash
CURDIR=$(pwd)
echo "Starting integration/e2e test runner"
date >runner.log

export JAVA_OPTS="-Dplay.server.http.port=9195 -Dconfig.file=$CURDIR/web/conf/application_test.conf  -Xmx2G" 

rm -f "$CURDIR/web/target/universal/stage/RUNNING_PID"

echo "Starting server"
nohup web/target/universal/stage/bin/myapp >>runner.log 2>&1 &

echo "Webserver PID is $pid"
echo "Waiting for server start"
sleep 30

echo "Running the tests"
sbt "service/test:run-main com.blah.myapp.E2ETest"

ERR="$?"
echo "Tests Done at $(date), killing server"
kill $pid
echo "Waiting for server exit"
wait $pid
echo "All done"
if [ $ERR -ne 0 ]; then
    cat runner.log
    exit "$ERR"
fi

build.sbt:

lazy val runIntegrationTests = taskKey[Unit]("Run integration tests")
runIntegrationTests := {
    val s: TaskStreams = streams.value
    s.log.info("Running integration tests...")
    val shell: Seq[String] = Seq("bash", "-c")
    val runTests: Seq[String] = shell :+ "./run_integration_tests.sh"
    if ((runTests !) == 0) {
        s.log.success("Integration tests successful!")
    } else {
        s.log.error("Integration tests failed!")
        throw new IllegalStateException("Integration tests failed!")
    }
}

lazy val root = project.in(file("."))
  .aggregate(service, web, tools)
  .configs(IntegrationTest)
  .settings(Defaults.itSettings)
  .settings(
    publishLocal := {},
    publish := {},
    (test in IntegrationTest) := (runIntegrationTests dependsOn (test in IntegrationTest)).value
  )

calling sbt in CI/jenkins:

  sh 'sbt clean coverage test stage it:test'
alex
  • 1,757
  • 4
  • 21
  • 32