making franz happy
This commit is contained in:
@@ -0,0 +1,77 @@
|
|||||||
|
name: CI
|
||||||
|
|
||||||
|
on:
|
||||||
|
push:
|
||||||
|
branches: [main]
|
||||||
|
pull_request:
|
||||||
|
|
||||||
|
jobs:
|
||||||
|
unit:
|
||||||
|
name: Unit tests
|
||||||
|
runs-on: ubuntu-latest
|
||||||
|
steps:
|
||||||
|
- uses: actions/checkout@v4
|
||||||
|
|
||||||
|
- uses: actions/setup-java@v4
|
||||||
|
with:
|
||||||
|
distribution: temurin
|
||||||
|
java-version: "11"
|
||||||
|
|
||||||
|
- uses: DeLaGuardo/setup-clojure@13.4.0
|
||||||
|
with:
|
||||||
|
cli: latest
|
||||||
|
|
||||||
|
- uses: actions/cache@v4
|
||||||
|
with:
|
||||||
|
path: |
|
||||||
|
~/.m2/repository
|
||||||
|
~/.gitlibs
|
||||||
|
key: deps-${{ runner.os }}-${{ hashFiles('deps.edn') }}
|
||||||
|
restore-keys: deps-${{ runner.os }}-
|
||||||
|
|
||||||
|
- run: script/unit-test
|
||||||
|
|
||||||
|
integration:
|
||||||
|
name: Integration tests + coverage
|
||||||
|
runs-on: ubuntu-latest
|
||||||
|
needs: unit
|
||||||
|
steps:
|
||||||
|
- uses: actions/checkout@v4
|
||||||
|
|
||||||
|
- uses: actions/setup-java@v4
|
||||||
|
with:
|
||||||
|
distribution: temurin
|
||||||
|
java-version: "11"
|
||||||
|
|
||||||
|
- uses: DeLaGuardo/setup-clojure@13.4.0
|
||||||
|
with:
|
||||||
|
cli: latest
|
||||||
|
bb: latest
|
||||||
|
|
||||||
|
- uses: actions/cache@v4
|
||||||
|
with:
|
||||||
|
path: |
|
||||||
|
~/.m2/repository
|
||||||
|
~/.gitlibs
|
||||||
|
key: deps-${{ runner.os }}-${{ hashFiles('deps.edn') }}
|
||||||
|
restore-keys: deps-${{ runner.os }}-
|
||||||
|
|
||||||
|
# The integration suite drives `docker compose` and `docker buildx`, so the
|
||||||
|
# runner must expose a Docker daemon (e.g. /var/run/docker.sock mounted into
|
||||||
|
# job containers, or a host-mode runner). Fail fast with a clear message.
|
||||||
|
- name: Check Docker
|
||||||
|
run: |
|
||||||
|
docker version
|
||||||
|
docker compose version
|
||||||
|
docker buildx version
|
||||||
|
|
||||||
|
# Unit + integration under Cloverage, then the merged report.
|
||||||
|
- run: script/coverage
|
||||||
|
|
||||||
|
# Gitea Actions only supports upload-artifact v3 (v4+ needs GitHub's artifact backend).
|
||||||
|
- uses: actions/upload-artifact@v3
|
||||||
|
if: always()
|
||||||
|
with:
|
||||||
|
name: coverage
|
||||||
|
path: target/coverage/
|
||||||
|
if-no-files-found: ignore
|
||||||
@@ -0,0 +1,92 @@
|
|||||||
|
# vibe
|
||||||
|
|
||||||
|
A small Clojure HTTP API built on [Ring](https://github.com/ring-clojure/ring) (Jetty) and
|
||||||
|
[reitit](https://github.com/metosin/reitit), with Swagger UI docs, unit tests, dockerized
|
||||||
|
integration tests, and joint line coverage.
|
||||||
|
|
||||||
|
## Requirements
|
||||||
|
|
||||||
|
- [Clojure CLI](https://clojure.org/guides/install_clojure) (tools.deps) and JDK 11+
|
||||||
|
- [Babashka](https://babashka.org/) for merging coverage reports (`script/coverage-report`)
|
||||||
|
- Docker, for the integration tests only
|
||||||
|
|
||||||
|
## Running
|
||||||
|
|
||||||
|
```bash
|
||||||
|
clojure -X:run
|
||||||
|
```
|
||||||
|
|
||||||
|
The server listens on port `3000` by default; set `PORT` to change it:
|
||||||
|
|
||||||
|
```bash
|
||||||
|
PORT=8080 clojure -X:run
|
||||||
|
```
|
||||||
|
|
||||||
|
Or build and run the container:
|
||||||
|
|
||||||
|
```bash
|
||||||
|
docker build -t vibe .
|
||||||
|
docker run --rm -p 3000:3000 vibe
|
||||||
|
```
|
||||||
|
|
||||||
|
## API
|
||||||
|
|
||||||
|
| Method | Path | Description |
|
||||||
|
|--------|------|-------------|
|
||||||
|
| `GET` | `/` | Plain-text landing message |
|
||||||
|
| `GET` | `/api/ping` | Health check, returns `pong` |
|
||||||
|
| `GET` | `/api/add?x=2&y=3` | Adds two numbers, returns EDN: `{:result 5}` (400 if either is missing or not a number) |
|
||||||
|
| `GET` | `/api-docs` | Swagger UI |
|
||||||
|
| `GET` | `/swagger.json` | OpenAPI/Swagger spec |
|
||||||
|
|
||||||
|
```bash
|
||||||
|
curl localhost:3000/api/ping # pong
|
||||||
|
curl 'localhost:3000/api/add?x=2&y=3' # {:result 5}
|
||||||
|
```
|
||||||
|
|
||||||
|
## Development
|
||||||
|
|
||||||
|
Every task is a bash script in `script/`. Start an nREPL server (with CIDER middleware) on the port in `.nrepl-port`, or `3000` if that
|
||||||
|
file doesn't exist:
|
||||||
|
|
||||||
|
```bash
|
||||||
|
script/repl
|
||||||
|
```
|
||||||
|
|
||||||
|
### Tests
|
||||||
|
|
||||||
|
| Task | What it does |
|
||||||
|
|------|--------------|
|
||||||
|
| `script/unit-test` | Runs the Clojure tests in `test/` |
|
||||||
|
| `script/integration-test` | Builds the app image, starts it on a random port, runs the [integration suite](integration-test/README.md) against it, then tears it down |
|
||||||
|
| `script/test` | Unit tests, then integration tests (stops at the first failure) |
|
||||||
|
|
||||||
|
### Coverage
|
||||||
|
|
||||||
|
Line coverage via [Cloverage](https://github.com/cloverage/cloverage):
|
||||||
|
|
||||||
|
| Task | Output |
|
||||||
|
|------|--------|
|
||||||
|
| `script/unit-coverage` | `target/coverage/unit/` |
|
||||||
|
| `script/integration-coverage` | `target/coverage/integration/` |
|
||||||
|
| `script/coverage` | Runs both, then writes the merged report to `target/coverage/merged/` |
|
||||||
|
|
||||||
|
Each report directory has an `index.html` and an `lcov.info`. See
|
||||||
|
[integration-test/README.md](integration-test/README.md) for how integration coverage is
|
||||||
|
collected.
|
||||||
|
|
||||||
|
|
||||||
|
## Project layout
|
||||||
|
|
||||||
|
```
|
||||||
|
src/vibe/
|
||||||
|
core.clj Router, Ring app, and server startup
|
||||||
|
<group>/routes.clj Each route group (health, math, ...) owns its route subtree
|
||||||
|
<group>/core.clj Business logic for the group, with no HTTP concerns
|
||||||
|
test/ Unit tests
|
||||||
|
integration-test/ Dockerized integration tests and coverage harness
|
||||||
|
script/ Bash entry points (test, coverage, repl, ...) and coverage helpers
|
||||||
|
```
|
||||||
|
|
||||||
|
To add a new group, create `vibe.<group>.routes` with a `routes` fn that returns a reitit route
|
||||||
|
vector, then add it to the router in `src/vibe/core.clj`.
|
||||||
@@ -1,27 +0,0 @@
|
|||||||
{:paths ["integration-tests" "scripts"]
|
|
||||||
:override-builtin true
|
|
||||||
:tasks
|
|
||||||
{:requires ([integration-tasks :as it])
|
|
||||||
unit-test {:doc "Run the Clojure unit tests (test/ directory)."
|
|
||||||
:task (shell "clojure" "-M:test")}
|
|
||||||
test {:doc "Run the unit tests, then the integration tests (stops at the first failure)."
|
|
||||||
:task (do (run 'unit-test)
|
|
||||||
(run 'integration-test))}
|
|
||||||
repl {:doc "Start an nREPL server with dev dependencies"
|
|
||||||
:task (shell "clojure" "-A:dev" "-M" "-m" "nrepl.cmdline"
|
|
||||||
"--middleware" "[cider.nrepl/cider-middleware]"
|
|
||||||
"--port" (try (slurp ".nrepl-port") (catch Exception _ "3000")))}
|
|
||||||
integration-test {:doc "Run the dockerized integration tests in isolation (builds the app image, starts it on a random port, runs the test suite against it, then tears everything down)."
|
|
||||||
:task (it/run-integration {})}
|
|
||||||
unit-coverage {:doc "Run the unit tests under Cloverage; reports go to target/coverage/unit."
|
|
||||||
:task (shell "clojure" "-M:cov" "-m" "cloverage.coverage"
|
|
||||||
"--src-ns-path" "src" "--test-ns-path" "test"
|
|
||||||
"--lcov" "--html" "--output" "target/coverage/unit")}
|
|
||||||
integration-coverage {:doc "Run the dockerized integration tests against a Cloverage-instrumented app; reports go to target/coverage/integration."
|
|
||||||
:task (it/run-integration {:coverage? true})}
|
|
||||||
coverage-report {:doc "Merge the unit and integration line-coverage results into target/coverage/merged and print a summary."
|
|
||||||
:task (shell "bb" "scripts/coverage_report.clj")}
|
|
||||||
coverage {:doc "Run the full suite (unit + integration) with line coverage and write the joint report to target/coverage/merged."
|
|
||||||
:task (do (run 'unit-coverage)
|
|
||||||
(run 'integration-coverage)
|
|
||||||
(run 'coverage-report))}}}
|
|
||||||
@@ -1,4 +1,4 @@
|
|||||||
{:paths ["src" "resources" "integration-tests"]
|
{:paths ["src" "resources" "integration-test"]
|
||||||
:deps
|
:deps
|
||||||
{org.clojure/clojure {:mvn/version "1.12.0"}
|
{org.clojure/clojure {:mvn/version "1.12.0"}
|
||||||
ring/ring-jetty-adapter {:mvn/version "1.12.2"}
|
ring/ring-jetty-adapter {:mvn/version "1.12.2"}
|
||||||
@@ -16,7 +16,7 @@
|
|||||||
:extra-deps {io.github.cognitect-labs/test-runner {:git/tag "v0.5.1" :git/sha "dfb30dd"}}
|
:extra-deps {io.github.cognitect-labs/test-runner {:git/tag "v0.5.1" :git/sha "dfb30dd"}}
|
||||||
:main-opts ["-m" "cognitect.test-runner"]}
|
:main-opts ["-m" "cognitect.test-runner"]}
|
||||||
;; Line coverage via Cloverage. Only adds the dep + test path; the entry points
|
;; Line coverage via Cloverage. Only adds the dep + test path; the entry points
|
||||||
;; (unit: cloverage.coverage, integration: vibe.integration.coverage) are chosen in bb.edn.
|
;; (unit: cloverage.coverage, integration: vibe.integration.coverage) are chosen in script/unit-coverage and script/integration-coverage.
|
||||||
:cov
|
:cov
|
||||||
{:extra-paths ["test"]
|
{:extra-paths ["test"]
|
||||||
:extra-deps {cloverage/cloverage {:mvn/version "1.2.4"}}}
|
:extra-deps {cloverage/cloverage {:mvn/version "1.2.4"}}}
|
||||||
|
|||||||
@@ -7,7 +7,7 @@ Dockerized integration tests for the vibe server.
|
|||||||
Run the full test suite (builds the app, starts it, runs tests):
|
Run the full test suite (builds the app, starts it, runs tests):
|
||||||
|
|
||||||
```bash
|
```bash
|
||||||
cd integration-tests
|
cd integration-test
|
||||||
docker-compose up --abort-on-container-exit
|
docker-compose up --abort-on-container-exit
|
||||||
```
|
```
|
||||||
|
|
||||||
@@ -16,21 +16,21 @@ docker-compose up --abort-on-container-exit
|
|||||||
If your vibe app is running locally on port 3000:
|
If your vibe app is running locally on port 3000:
|
||||||
|
|
||||||
```bash
|
```bash
|
||||||
cd integration-tests
|
cd integration-test
|
||||||
docker run --rm -v "$(pwd)/integration_test.bb:/integration_test.bb:ro" babashka/babashka:latest bb /integration_test.bb http://host.docker.internal:3000
|
docker run --rm -v "$(pwd)/integration_test.bb:/integration_test.bb:ro" babashka/babashka:latest bb /integration_test.bb http://host.docker.internal:3000
|
||||||
```
|
```
|
||||||
|
|
||||||
Or run directly on a host with babashka installed:
|
Or run directly on a host with babashka installed:
|
||||||
|
|
||||||
```bash
|
```bash
|
||||||
cd integration-tests
|
cd integration-test
|
||||||
bb integration_test.bb http://localhost:3000
|
bb integration_test.bb http://localhost:3000
|
||||||
```
|
```
|
||||||
|
|
||||||
Or from the repo root, via the `bb.edn` task:
|
Or from the repo root, via the script:
|
||||||
|
|
||||||
```bash
|
```bash
|
||||||
bb integration-test
|
script/integration-test
|
||||||
```
|
```
|
||||||
|
|
||||||
### Options
|
### Options
|
||||||
@@ -44,7 +44,7 @@ bb integration-test
|
|||||||
Example — watch a local server every 2 seconds:
|
Example — watch a local server every 2 seconds:
|
||||||
|
|
||||||
```bash
|
```bash
|
||||||
bb integration-test http://localhost:3000 --watch --interval 2000
|
bb integration-test/integration_test.bb http://localhost:3000 --watch --interval 2000
|
||||||
```
|
```
|
||||||
|
|
||||||
## What's Tested
|
## What's Tested
|
||||||
@@ -64,10 +64,10 @@ unit tests, the integration tests, and both combined. From the repo root:
|
|||||||
|
|
||||||
| Task | What it does | Output |
|
| Task | What it does | Output |
|
||||||
|------|--------------|--------|
|
|------|--------------|--------|
|
||||||
| `bb unit-coverage` | Runs `test/` under Cloverage | `target/coverage/unit/` |
|
| `script/unit-coverage` | Runs `test/` under Cloverage | `target/coverage/unit/` |
|
||||||
| `bb integration-coverage` | Runs the integration suite against a Cloverage-instrumented `vibe-app` container | `target/coverage/integration/` |
|
| `script/integration-coverage` | Runs the integration suite against a Cloverage-instrumented `vibe-app` container | `target/coverage/integration/` |
|
||||||
| `bb coverage-report` | Merges the two `lcov.info` files, prints a per-file table | `target/coverage/merged/` |
|
| `script/coverage-report` | Merges the two `lcov.info` files, prints a per-file table | `target/coverage/merged/` |
|
||||||
| `bb coverage` | All three, in order (the full suite with a joint report) | all of the above |
|
| `script/coverage` | All three, in order (the full suite with a joint report) | all of the above |
|
||||||
|
|
||||||
Each directory contains an `index.html` (open it in a browser) and an `lcov.info`. In the
|
Each directory contains an `index.html` (open it in a browser) and an `lcov.info`. In the
|
||||||
merged report a line counts as covered if either suite hit it; the HTML shows per-line hit
|
merged report a line counts as covered if either suite hit it; the HTML shows per-line hit
|
||||||
@@ -75,6 +75,6 @@ counts per suite (`u:` unit, `i:` integration).
|
|||||||
|
|
||||||
How the integration numbers are collected: `docker-compose.coverage.yml` starts the app via
|
How the integration numbers are collected: `docker-compose.coverage.yml` starts the app via
|
||||||
`vibe.integration.coverage`, which lets Cloverage instrument `src/` and then serves real
|
`vibe.integration.coverage`, which lets Cloverage instrument `src/` and then serves real
|
||||||
traffic until it receives SIGTERM. `bb integration-coverage` stops the container
|
traffic until it receives SIGTERM. `script/integration-coverage` stops the container
|
||||||
(`docker compose stop`) so the report gets written, then copies it out with `docker compose cp`.
|
(`docker compose stop`) so the report gets written, then copies it out with `docker compose cp`.
|
||||||
Plain `bb integration-test` / `bb test` are unchanged and run the uninstrumented app.
|
Plain `script/integration-test` / `script/test` are unchanged and run the uninstrumented app.
|
||||||
+2
-2
@@ -1,5 +1,5 @@
|
|||||||
# Overlay for `bb integration-coverage`: runs vibe-app under Cloverage instead of plainly.
|
# Overlay for `script/integration-coverage`: runs vibe-app under Cloverage instead of plainly.
|
||||||
# Reports land in /app/target/coverage/integration inside the container; bb.edn copies
|
# Reports land in /app/target/coverage/integration inside the container; script/integration-test copies
|
||||||
# them out after the app is stopped (SIGTERM is what triggers the report).
|
# them out after the app is stopped (SIGTERM is what triggers the report).
|
||||||
services:
|
services:
|
||||||
vibe-app:
|
vibe-app:
|
||||||
Executable
+9
@@ -0,0 +1,9 @@
|
|||||||
|
#!/usr/bin/env bash
|
||||||
|
# Run the full suite (unit + integration) with line coverage and write the joint
|
||||||
|
# report to target/coverage/merged.
|
||||||
|
set -euo pipefail
|
||||||
|
cd "$(dirname "$0")/.."
|
||||||
|
|
||||||
|
script/unit-coverage
|
||||||
|
script/integration-coverage
|
||||||
|
script/coverage-report
|
||||||
Executable
+7
@@ -0,0 +1,7 @@
|
|||||||
|
#!/usr/bin/env bash
|
||||||
|
# Merge the unit and integration line-coverage results into target/coverage/merged
|
||||||
|
# and print a summary. Requires Babashka.
|
||||||
|
set -euo pipefail
|
||||||
|
cd "$(dirname "$0")/.."
|
||||||
|
|
||||||
|
exec bb script/coverage_report.clj "$@"
|
||||||
@@ -1,7 +1,7 @@
|
|||||||
#!/usr/bin/env bb
|
#!/usr/bin/env bb
|
||||||
;; Merges the unit and integration LCOV files into one line-coverage report.
|
;; Merges the unit and integration LCOV files into one line-coverage report.
|
||||||
;;
|
;;
|
||||||
;; bb scripts/coverage_report.clj [--out target/coverage/merged]
|
;; bb script/coverage_report.clj [--out target/coverage/merged]
|
||||||
;;
|
;;
|
||||||
;; Reads target/coverage/{unit,integration}/lcov.info (either may be missing), prints a
|
;; Reads target/coverage/{unit,integration}/lcov.info (either may be missing), prints a
|
||||||
;; per-file table (unit / integration / merged) and writes the merged result to
|
;; per-file table (unit / integration / merged) and writes the merged result to
|
||||||
@@ -40,7 +40,7 @@
|
|||||||
;; Cloverage's SF: paths are usually project-relative already (src/vibe/core.clj); fall
|
;; Cloverage's SF: paths are usually project-relative already (src/vibe/core.clj); fall
|
||||||
;; back to the source roots in case they are classpath-relative.
|
;; back to the source roots in case they are classpath-relative.
|
||||||
(some (fn [dir] (let [f (io/file dir file)] (when (.isFile f) (slurp f))))
|
(some (fn [dir] (let [f (io/file dir file)] (when (.isFile f) (slurp f))))
|
||||||
[nil "src" "test" "integration-tests"]))
|
[nil "src" "test" "integration-test"]))
|
||||||
|
|
||||||
(defn esc [s]
|
(defn esc [s]
|
||||||
(-> s (str/replace "&" "&") (str/replace "<" "<") (str/replace ">" ">")))
|
(-> s (str/replace "&" "&") (str/replace "<" "<") (str/replace ">" ">")))
|
||||||
@@ -104,7 +104,7 @@ details{margin:1em 0}summary{cursor:pointer;font-weight:600}
|
|||||||
:when (.exists (io/file path))]
|
:when (.exists (io/file path))]
|
||||||
[k (parse-lcov (slurp path))]))]
|
[k (parse-lcov (slurp path))]))]
|
||||||
(when (empty? per-suite)
|
(when (empty? per-suite)
|
||||||
(println "No lcov.info found; run `bb unit-coverage` and/or `bb integration-coverage` first.")
|
(println "No lcov.info found; run `script/unit-coverage` and/or `script/integration-coverage` first.")
|
||||||
(System/exit 1))
|
(System/exit 1))
|
||||||
(let [merged (apply merge-with (fn [a b] (merge-with + a b)) (vals per-suite))
|
(let [merged (apply merge-with (fn [a b] (merge-with + a b)) (vals per-suite))
|
||||||
cols (keys per-suite)
|
cols (keys per-suite)
|
||||||
Executable
+6
@@ -0,0 +1,6 @@
|
|||||||
|
#!/usr/bin/env bash
|
||||||
|
# Run the dockerized integration tests against a Cloverage-instrumented app;
|
||||||
|
# reports go to target/coverage/integration.
|
||||||
|
set -euo pipefail
|
||||||
|
|
||||||
|
COVERAGE=1 exec "$(dirname "$0")/integration-test"
|
||||||
Executable
+79
@@ -0,0 +1,79 @@
|
|||||||
|
#!/usr/bin/env bash
|
||||||
|
# Run the dockerized integration tests in isolation: builds the app image, starts it on a
|
||||||
|
# random port, runs the suite against it, then tears everything down and exits with the
|
||||||
|
# suite's exit code.
|
||||||
|
#
|
||||||
|
# With COVERAGE=1 (see script/integration-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.
|
||||||
|
set -euo pipefail
|
||||||
|
cd "$(dirname "$0")/.."
|
||||||
|
root=$PWD
|
||||||
|
|
||||||
|
# Picks a random port in [lo, hi] that's currently free on this host; retries on
|
||||||
|
# collision since another process could grab it first.
|
||||||
|
free_port_in_range() {
|
||||||
|
local lo=$1 hi=$2 port attempt
|
||||||
|
for attempt in $(seq 20); do
|
||||||
|
port=$((lo + RANDOM % (hi - lo + 1)))
|
||||||
|
# A successful connect means something is already listening.
|
||||||
|
if ! (exec 3<>"/dev/tcp/127.0.0.1/$port") 2>/dev/null; then
|
||||||
|
echo "$port"
|
||||||
|
return 0
|
||||||
|
fi
|
||||||
|
done
|
||||||
|
echo "no free port found in range $lo-$hi" >&2
|
||||||
|
return 1
|
||||||
|
}
|
||||||
|
|
||||||
|
port=$(free_port_in_range 30000 40000)
|
||||||
|
echo "[test] starting vibe-app on random port $port${COVERAGE:+ (under coverage instrumentation)}"
|
||||||
|
|
||||||
|
compose=(docker compose)
|
||||||
|
if [[ -n "${COVERAGE:-}" ]]; then
|
||||||
|
compose+=(-f docker-compose.yml -f docker-compose.coverage.yml)
|
||||||
|
fi
|
||||||
|
|
||||||
|
# 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.
|
||||||
|
builder=vibe-builder
|
||||||
|
if ! docker buildx inspect "$builder" >/dev/null 2>&1; then
|
||||||
|
docker buildx create --name "$builder" \
|
||||||
|
--driver docker-container \
|
||||||
|
--buildkitd-config buildkitd.toml
|
||||||
|
fi
|
||||||
|
docker buildx use "$builder"
|
||||||
|
|
||||||
|
cd integration-test
|
||||||
|
export VIBE_PORT=$port
|
||||||
|
|
||||||
|
teardown() {
|
||||||
|
local status=$?
|
||||||
|
trap - EXIT
|
||||||
|
"${compose[@]}" down -v || true
|
||||||
|
exit "$status"
|
||||||
|
}
|
||||||
|
trap teardown EXIT
|
||||||
|
trap 'exit 130' INT
|
||||||
|
trap 'exit 143' TERM
|
||||||
|
|
||||||
|
# `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.
|
||||||
|
status=0
|
||||||
|
"${compose[@]}" --ansi never --progress plain run --build --rm integration-test || status=$?
|
||||||
|
|
||||||
|
if [[ -n "${COVERAGE:-}" ]]; then
|
||||||
|
# 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.
|
||||||
|
out_dir=$root/target/coverage
|
||||||
|
echo "[test] stopping vibe-app to flush coverage report"
|
||||||
|
"${compose[@]}" stop -t 120 vibe-app || true
|
||||||
|
rm -rf "$out_dir/integration"
|
||||||
|
mkdir -p "$out_dir"
|
||||||
|
if ! "${compose[@]}" cp vibe-app:/app/target/coverage/integration "$out_dir" \
|
||||||
|
|| [[ ! -f $out_dir/integration/lcov.info ]]; then
|
||||||
|
echo "[test] WARNING: no integration coverage report was produced"
|
||||||
|
fi
|
||||||
|
fi
|
||||||
|
|
||||||
|
exit "$status"
|
||||||
Executable
+13
@@ -0,0 +1,13 @@
|
|||||||
|
#!/usr/bin/env bash
|
||||||
|
# Start an nREPL server with dev dependencies, on the port in .nrepl-port (default 3000).
|
||||||
|
set -euo pipefail
|
||||||
|
cd "$(dirname "$0")/.."
|
||||||
|
|
||||||
|
port=3000
|
||||||
|
if [[ -f .nrepl-port ]]; then
|
||||||
|
port=$(tr -d '[:space:]' < .nrepl-port)
|
||||||
|
fi
|
||||||
|
|
||||||
|
exec clojure -A:dev -M -m nrepl.cmdline \
|
||||||
|
--middleware "[cider.nrepl/cider-middleware]" \
|
||||||
|
--port "$port"
|
||||||
Executable
+7
@@ -0,0 +1,7 @@
|
|||||||
|
#!/usr/bin/env bash
|
||||||
|
# Run the unit tests, then the integration tests (stops at the first failure).
|
||||||
|
set -euo pipefail
|
||||||
|
cd "$(dirname "$0")/.."
|
||||||
|
|
||||||
|
script/unit-test
|
||||||
|
script/integration-test
|
||||||
Executable
+8
@@ -0,0 +1,8 @@
|
|||||||
|
#!/usr/bin/env bash
|
||||||
|
# Run the unit tests under Cloverage; reports go to target/coverage/unit.
|
||||||
|
set -euo pipefail
|
||||||
|
cd "$(dirname "$0")/.."
|
||||||
|
|
||||||
|
exec clojure -M:cov -m cloverage.coverage \
|
||||||
|
--src-ns-path src --test-ns-path test \
|
||||||
|
--lcov --html --output target/coverage/unit
|
||||||
Executable
+6
@@ -0,0 +1,6 @@
|
|||||||
|
#!/usr/bin/env bash
|
||||||
|
# Run the Clojure unit tests (test/ directory). Extra args go to the test runner.
|
||||||
|
set -euo pipefail
|
||||||
|
cd "$(dirname "$0")/.."
|
||||||
|
|
||||||
|
exec clojure -M:test "$@"
|
||||||
@@ -1,79 +0,0 @@
|
|||||||
(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))))
|
|
||||||
Reference in New Issue
Block a user