Skip to content

Commit 629ab06

Browse files
andrasbacsaiclaude
andcommitted
test(e2e): auto-fetch coolify-stub from nightly release
stub_smoke previously required the user to prebuild the linux-x64 binary and point COOLIFY_STUB_BIN at it. Default behavior now downloads coolify-stub-linux-amd64.tar.gz from the nightly release into target/coolify-stub-cache/ and reuses it (re-fetches after 10 min for the rolling `nightly` tag, caches forever for pinned tags). Override knobs (first match wins): COOLIFY_STUB_BIN — explicit prebuilt binary path COOLIFY_STUB_SOURCE — `local` to build via bun scripts/build-binary.ts COOLIFY_STUB_TAG — pin a specific release (default `nightly`) COOLIFY_STUB_REPO — pull from a fork (default `coollabsio/coold`) Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
1 parent 76ffa8c commit 629ab06

3 files changed

Lines changed: 150 additions & 28 deletions

File tree

e2e-tests/.env.example

Lines changed: 9 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -11,10 +11,15 @@ COOLIFY_BIN=coolify
1111
# HETZNER_SERVER_TYPE=cx23
1212

1313
# ─── Stub-dashboard suite (tests/stub.rs) ────────────────────────────────────
14-
# Path to the prebuilt linux-x64 coolify-stub binary. Build via:
15-
# cd coolify-stub && bun install && (cd web && bun install)
16-
# BUN_TARGET=bun-linux-x64 bun scripts/build-binary.ts
17-
# COOLIFY_STUB_BIN=./coolify-stub/dist/coolify-stub
14+
# Default: download coolify-stub-linux-amd64.tar.gz from the `nightly`
15+
# release of coollabsio/coold into target/coolify-stub-cache/. No action
16+
# required.
17+
#
18+
# Override selection:
19+
# COOLIFY_STUB_BIN=/abs/path/to/coolify-stub # explicit binary
20+
# COOLIFY_STUB_SOURCE=local # build locally via bun
21+
# COOLIFY_STUB_TAG=nightly-20260423-a1b2c3d4 # pin a release
22+
# COOLIFY_STUB_REPO=youruser/coold # pull from a fork
1823

1924
# ─── Keep VMs + stub alive after a successful run ────────────────────────────
2025
# Set to 1 to skip EphemeralCluster teardown (all suites) and the stub pkill

e2e-tests/E2E.md

Lines changed: 24 additions & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -79,27 +79,41 @@ CONFIRM_SWEEP=1 cargo test -p e2e-tests --test install cleanup_leaked_hetzner --
7979

8080
## Suite 3 — `stub.rs` (coolify-stub dashboard smoke)
8181

82-
Provisions a single Hetzner VM, runs `coolify init apply`, scp's the prebuilt
82+
Provisions a single Hetzner VM, runs `coolify init apply`, scp's the
8383
`coolify-stub` Bun binary next to the broker, and drives a real static build
8484
through the stub's `/api/*` surface.
8585

86-
### Build the binary once
86+
### Run the suite (default — fetch from nightly release)
8787

8888
```bash
89-
cd coolify-stub
90-
bun install
91-
(cd web && bun install)
92-
BUN_TARGET=bun-linux-x64 bun scripts/build-binary.ts
93-
cd ..
89+
cargo test -p e2e-tests --test stub -- --ignored --nocapture --test-threads=1
9490
```
9591

96-
### Run the suite
92+
The harness auto-downloads `coolify-stub-linux-amd64.tar.gz` from the
93+
`nightly` release of `coollabsio/coold` into `target/coolify-stub-cache/`
94+
on first run, then reuses it (re-downloading only after 10 min for the
95+
`nightly` tag). Zero extra setup.
96+
97+
### Binary-selection knobs (first match wins)
98+
99+
| Env | Effect |
100+
|---------------------------|------------------------------------------------------------------------|
101+
| `COOLIFY_STUB_BIN=<path>` | Use this prebuilt binary as-is. |
102+
| `COOLIFY_STUB_SOURCE=local` | Build locally: `coolify-stub/scripts/build-binary.ts` (needs `bun`). |
103+
| `COOLIFY_STUB_TAG=<tag>` | Pin a specific release (default `nightly`). |
104+
| `COOLIFY_STUB_REPO=<o/r>` | Pull from a fork (default `coollabsio/coold`). |
105+
106+
### Build the binary locally (optional)
97107

98108
```bash
99-
COOLIFY_STUB_BIN=$PWD/coolify-stub/dist/coolify-stub \
100-
cargo test -p e2e-tests --test stub -- --ignored --nocapture --test-threads=1
109+
cd coolify-stub
110+
bun install && (cd web && bun install)
111+
BUN_TARGET=bun-linux-x64 bun scripts/build-binary.ts
101112
```
102113

114+
Then either set `COOLIFY_STUB_BIN=$PWD/coolify-stub/dist/coolify-stub` or
115+
run the suite with `COOLIFY_STUB_SOURCE=local`.
116+
103117
The test kills the stub process and tails its log on teardown, so a panic
104118
still surfaces the stub's stderr for triage.
105119

e2e-tests/tests/stub.rs

Lines changed: 117 additions & 14 deletions
Original file line numberDiff line numberDiff line change
@@ -3,15 +3,20 @@
33
//! prebuilt `coolify-stub` binary onto the VM, launches it next to the
44
//! broker UDS, and drives a real static build through its `/api/*` surface.
55
//!
6-
//! Requires an env var pointing at a prebuilt linux-x64 binary:
6+
//! Binary selection (first match wins):
7+
//! 1. `COOLIFY_STUB_BIN=/abs/path/to/coolify-stub` — explicit override.
8+
//! 2. `COOLIFY_STUB_SOURCE=local` — build locally via
9+
//! `coolify-stub/scripts/build-binary.ts` and use the resulting
10+
//! `coolify-stub/dist/coolify-stub`.
11+
//! 3. Default: download `coolify-stub-linux-amd64.tar.gz` from the
12+
//! `coolify-stub-tag` release (env `COOLIFY_STUB_TAG`, default
13+
//! `nightly`) on `COOLIFY_STUB_REPO` (default `coollabsio/coold`) into
14+
//! `target/coolify-stub-cache/<tag>/` and use it.
15+
//!
16+
//! Typical run:
717
//!
818
//! ```text
9-
//! cd coolify-stub
10-
//! bun install && (cd web && bun install)
11-
//! BUN_TARGET=bun-linux-x64 bun scripts/build-binary.ts
12-
//! cd ..
13-
//! COOLIFY_STUB_BIN=$PWD/coolify-stub/dist/coolify-stub \
14-
//! cargo test -p e2e-tests --test stub -- --ignored --nocapture --test-threads=1
19+
//! cargo test -p e2e-tests --test stub -- --ignored --nocapture --test-threads=1
1520
//! ```
1621
1722
use std::time::Duration;
@@ -42,13 +47,111 @@ fn ok(msg: &str) {
4247

4348
fn stub_bin_path() -> String {
4449
e2e_tests::load_dotenv();
45-
let p = std::env::var("COOLIFY_STUB_BIN")
46-
.expect("env COOLIFY_STUB_BIN required (path to prebuilt linux-x64 binary)");
47-
let meta = std::fs::metadata(&p).unwrap_or_else(|e| {
48-
panic!("COOLIFY_STUB_BIN={p} not readable: {e} — build via `bun scripts/build-binary.ts`")
49-
});
50-
assert!(meta.is_file(), "COOLIFY_STUB_BIN={p} is not a regular file");
51-
p
50+
51+
if let Ok(p) = std::env::var("COOLIFY_STUB_BIN") {
52+
let meta = std::fs::metadata(&p)
53+
.unwrap_or_else(|e| panic!("COOLIFY_STUB_BIN={p} not readable: {e}"));
54+
assert!(meta.is_file(), "COOLIFY_STUB_BIN={p} is not a regular file");
55+
return p;
56+
}
57+
58+
if std::env::var("COOLIFY_STUB_SOURCE").as_deref() == Ok("local") {
59+
return build_local_stub();
60+
}
61+
62+
fetch_stub_from_release()
63+
}
64+
65+
fn crate_root() -> std::path::PathBuf {
66+
std::path::PathBuf::from(env!("CARGO_MANIFEST_DIR"))
67+
}
68+
69+
fn repo_root() -> std::path::PathBuf {
70+
// CARGO_MANIFEST_DIR is the e2e-tests crate. Workspace root is one up.
71+
crate_root().parent().unwrap().to_path_buf()
72+
}
73+
74+
fn build_local_stub() -> String {
75+
let stub_dir = repo_root().join("coolify-stub");
76+
assert!(
77+
stub_dir.is_dir(),
78+
"COOLIFY_STUB_SOURCE=local but {stub_dir} not found",
79+
stub_dir = stub_dir.display()
80+
);
81+
eprintln!("[stub ] building coolify-stub locally (linux-x64)");
82+
let status = std::process::Command::new("bun")
83+
.args(["scripts/build-binary.ts"])
84+
.env("BUN_TARGET", "bun-linux-x64")
85+
.current_dir(&stub_dir)
86+
.status()
87+
.unwrap_or_else(|e| panic!("spawn bun (is bun installed?): {e}"));
88+
assert!(status.success(), "local stub build failed (exit {:?})", status.code());
89+
let out = stub_dir.join("dist").join("coolify-stub");
90+
assert!(out.is_file(), "expected build output at {}", out.display());
91+
out.to_string_lossy().into_owned()
92+
}
93+
94+
fn fetch_stub_from_release() -> String {
95+
let tag = std::env::var("COOLIFY_STUB_TAG").unwrap_or_else(|_| "nightly".into());
96+
let repo = std::env::var("COOLIFY_STUB_REPO").unwrap_or_else(|_| "coollabsio/coold".into());
97+
let asset = "coolify-stub-linux-amd64.tar.gz";
98+
99+
let cache_dir = repo_root()
100+
.join("target")
101+
.join("coolify-stub-cache")
102+
.join(&tag);
103+
let binary = cache_dir.join("coolify-stub");
104+
let stamp = cache_dir.join(".stamp");
105+
106+
// `nightly` is a rolling tag — re-download if older than 10 minutes so
107+
// the test picks up fresh pushes. Pinned tags (not `nightly`) are
108+
// immutable; always reuse once cached.
109+
let fresh = binary.is_file()
110+
&& stamp.is_file()
111+
&& (tag != "nightly"
112+
|| std::fs::metadata(&stamp)
113+
.and_then(|m| m.modified())
114+
.map(|t| t.elapsed().unwrap_or(Duration::from_secs(0)).as_secs() < 600)
115+
.unwrap_or(false));
116+
117+
if fresh {
118+
eprintln!("[stub ] using cached stub binary from {}", binary.display());
119+
return binary.to_string_lossy().into_owned();
120+
}
121+
122+
std::fs::create_dir_all(&cache_dir).expect("create cache dir");
123+
let tarball = cache_dir.join(asset);
124+
let url = format!("https://github.com/{repo}/releases/download/{tag}/{asset}");
125+
eprintln!("[stub ] downloading {url}");
126+
let status = std::process::Command::new("curl")
127+
.args(["-fsSL", "-o"])
128+
.arg(&tarball)
129+
.arg(&url)
130+
.status()
131+
.unwrap_or_else(|e| panic!("spawn curl: {e}"));
132+
assert!(
133+
status.success(),
134+
"download {url} failed (exit {:?}) — set COOLIFY_STUB_BIN or COOLIFY_STUB_SOURCE=local to bypass",
135+
status.code()
136+
);
137+
138+
let status = std::process::Command::new("tar")
139+
.args(["-xzf"])
140+
.arg(&tarball)
141+
.arg("-C")
142+
.arg(&cache_dir)
143+
.status()
144+
.unwrap_or_else(|e| panic!("spawn tar: {e}"));
145+
assert!(status.success(), "extract {} failed", tarball.display());
146+
147+
assert!(
148+
binary.is_file(),
149+
"expected {} after extract — archive layout changed?",
150+
binary.display()
151+
);
152+
// Leave execute bit alone; scp_upload preserves file mode.
153+
let _ = std::fs::write(&stamp, "");
154+
binary.to_string_lossy().into_owned()
52155
}
53156

54157
fn parse_body(body: &str) -> serde_json::Value {

0 commit comments

Comments
 (0)