80 lines
4.6 KiB
Clojure
80 lines
4.6 KiB
Clojure
(ns integration-tasks
|
|
"Shared implementation for the dockerized integration bb tasks (see bb.edn)."
|
|
(:require [babashka.process :refer [shell]]))
|
|
|
|
(defn run-integration
|
|
"Builds the app image, starts it on a random port, runs the integration suite against
|
|
it, then tears everything down and exits with the suite's exit code.
|
|
|
|
With :coverage? the app runs under Cloverage (docker-compose.coverage.yml); after the
|
|
suite finishes the app is stopped gracefully so it writes its report, which is copied
|
|
to target/coverage/integration."
|
|
[{:keys [coverage?]}]
|
|
(let [free-port-in-range
|
|
;; Picks a random port in [lo, hi] that's currently bindable on this
|
|
;; host; retries on collision since another process could grab it first.
|
|
(fn [lo hi]
|
|
(loop [attempts 20]
|
|
(let [port (+ lo (rand-int (inc (- hi lo))))]
|
|
(if (try
|
|
(.close (java.net.ServerSocket. port))
|
|
true
|
|
(catch java.io.IOException _ false))
|
|
port
|
|
(if (pos? attempts)
|
|
(recur (dec attempts))
|
|
(throw (ex-info "no free port found in range" {:lo lo :hi hi})))))))
|
|
port (free-port-in-range 30000 40000)
|
|
_ (println (format "[test] starting vibe-app on random port %d%s" port
|
|
(if coverage? " (under coverage instrumentation)" "")))
|
|
builder-name "vibe-builder"
|
|
compose (into ["docker" "compose"]
|
|
(when coverage?
|
|
["-f" "docker-compose.yml" "-f" "docker-compose.coverage.yml"]))
|
|
;; Works around an LXC/AppArmor bug in runc (opencontainers/runc#5007) that
|
|
;; fails RUN steps during image build; a docker-container builder with
|
|
;; apparmor disabled sidesteps it.
|
|
_ (let [{:keys [exit]} (shell {:continue true} "docker" "buildx" "inspect" builder-name)]
|
|
(when-not (zero? exit)
|
|
(shell "docker" "buildx" "create" "--name" builder-name
|
|
"--driver" "docker-container"
|
|
"--buildkitd-config" "buildkitd.toml")))
|
|
_ (shell "docker" "buildx" "use" builder-name)
|
|
;; Runs from a JVM shutdown hook (normal exit and Ctrl-C), where
|
|
;; babashka.process/shell fails: it registers its own shutdown hook,
|
|
;; which the JVM rejects with "Shutdown in progress". Plain
|
|
;; ProcessBuilder has no such hook and lets us wait for `down` to finish.
|
|
teardown (fn []
|
|
(-> (doto (ProcessBuilder. ^java.util.List (vec (concat compose ["down" "-v"])))
|
|
(.directory (java.io.File. "integration-tests"))
|
|
(.inheritIO))
|
|
.start
|
|
.waitFor))
|
|
_ (.addShutdownHook (Runtime/getRuntime) (Thread. teardown))
|
|
{:keys [exit]} (apply shell {:dir "integration-tests" :continue true
|
|
:extra-env {"VIBE_PORT" (str port)}}
|
|
;; `run` returns the test container's own exit code; `up --abort-on-container-exit`
|
|
;; can leak the app's SIGTERM exit (143) during teardown instead.
|
|
(concat compose ["--ansi" "never" "--progress" "plain"
|
|
"run" "--build" "--rm" "integration-test"]))]
|
|
(when coverage?
|
|
;; SIGTERM is what makes Cloverage write its report, so stop (not kill) the app
|
|
;; and pull the report out of the stopped container before `down` removes it.
|
|
(let [out-dir (java.io.File. "target/coverage")
|
|
dest (java.io.File. out-dir "integration")]
|
|
(println "[test] stopping vibe-app to flush coverage report")
|
|
(apply shell {:dir "integration-tests" :continue true}
|
|
(concat compose ["stop" "-t" "120" "vibe-app"]))
|
|
(when (.exists dest)
|
|
(run! (fn [^java.io.File f] (.delete f)) (reverse (file-seq dest))))
|
|
(.mkdirs out-dir)
|
|
(let [{cp-exit :exit} (apply shell {:dir "integration-tests" :continue true}
|
|
(concat compose ["cp" "vibe-app:/app/target/coverage/integration"
|
|
(.getAbsolutePath out-dir)]))]
|
|
(when-not (and (zero? cp-exit) (.exists (java.io.File. dest "lcov.info")))
|
|
(println "[test] WARNING: no integration coverage report was produced")))))
|
|
;; Only exit early on failure so chained tasks (`bb coverage`) can carry on after a pass;
|
|
;; the teardown hook still runs when bb exits.
|
|
(when-not (zero? exit)
|
|
(System/exit exit))))
|