mirror of
https://github.com/openai/codex.git
synced 2026-09-20 12:47:38 +00:00
## Why Daemon lifecycle commands previously required a standalone managed installation. A complete CLI package can supply the daemon executable and helpers without requiring a separate installer run. ## What changed - Let `codex app-server daemon start`, `restart`, and `bootstrap` copy the invoking package into `CODEX_HOME/packages/app-server-daemon` when no daemon installation exists. - Validate the package's platform, required helpers, executable identity, and copied contents before selecting the staged release. - Preserve existing dedicated and legacy daemon selections, reject broken selections instead of replacing them, and leave the CLI package and selection unchanged. - Preserve standalone release pins and latest-channel eligibility, and select dedicated releases using Unix symlinks or Windows junctions. ## Testing Add package preparation tests for complete copies, incomplete packages, broken selections, legacy preservation, and update-channel handling. Add Windows junction creation and retargeting coverage, plus CLI integration tests that launch the copied package through `start`, `restart`, and `bootstrap`. GitOrigin-RevId: abd2f4f82eae0885434ea30603c2c1c0ca760bef
128 lines
5.6 KiB
Rust
128 lines
5.6 KiB
Rust
use pretty_assertions::assert_eq;
|
|
|
|
#[test]
|
|
fn discovers_package_and_legacy_installs() {
|
|
let home = tempfile::TempDir::new().expect("home");
|
|
let current = home.path().join("packages/standalone/current");
|
|
let legacy = current.join(super::managed_codex_file_name());
|
|
assert_eq!(
|
|
super::managed_codex_bin(home.path()),
|
|
home.path()
|
|
.join("packages/app-server-daemon/current/bin")
|
|
.join(super::managed_codex_file_name())
|
|
);
|
|
std::fs::create_dir_all(¤t).expect("current directory");
|
|
std::fs::write(&legacy, b"legacy").expect("legacy executable");
|
|
let state = home.path().join("app-server-daemon");
|
|
std::fs::create_dir(&state).unwrap();
|
|
for name in ["settings.json", "daemon.lock", "app-server.pid.lock"] {
|
|
std::fs::write(state.join(name), b"").unwrap();
|
|
}
|
|
// A CLI install and a previous stop/status operation do not establish ownership.
|
|
assert_eq!(
|
|
super::package_root(home.path()),
|
|
home.path().join("packages/app-server-daemon")
|
|
);
|
|
std::fs::write(state.join("app-server.stderr.log"), b"").unwrap();
|
|
assert_eq!(super::managed_codex_bin(home.path()), legacy);
|
|
let packaged = current.join("bin").join(super::managed_codex_file_name());
|
|
std::fs::create_dir(current.join("bin")).expect("bin directory");
|
|
std::fs::write(&packaged, b"packaged").expect("packaged executable");
|
|
assert_eq!(super::managed_codex_bin(home.path()), packaged);
|
|
|
|
std::fs::remove_dir_all(home.path().join("packages/standalone")).unwrap();
|
|
assert_eq!(
|
|
super::package_root(home.path()),
|
|
home.path().join("packages/standalone")
|
|
);
|
|
std::fs::remove_file(state.join("app-server.stderr.log")).unwrap();
|
|
std::fs::write(state.join("app-server.pid"), b"running daemon").unwrap();
|
|
assert_eq!(
|
|
super::package_root(home.path()),
|
|
home.path().join("packages/standalone")
|
|
);
|
|
|
|
std::fs::write(state.join("daemon.pid"), b"running dedicated daemon").unwrap();
|
|
assert_eq!(
|
|
super::package_root(home.path()),
|
|
home.path().join("packages/app-server-daemon")
|
|
);
|
|
std::fs::remove_file(state.join("daemon.pid")).unwrap();
|
|
std::fs::write(state.join("daemon.stderr.log"), b"").unwrap();
|
|
assert_eq!(
|
|
super::package_root(home.path()),
|
|
home.path().join("packages/app-server-daemon")
|
|
);
|
|
|
|
#[cfg(unix)]
|
|
{
|
|
let dedicated = home.path().join("packages/app-server-daemon");
|
|
std::fs::write(&dedicated, b"not a directory").expect("unreadable selection");
|
|
assert_eq!(super::package_root(home.path()), dedicated);
|
|
}
|
|
}
|
|
|
|
#[cfg(unix)]
|
|
#[test]
|
|
fn updater_only_runs_for_stable_installer_owned_releases() {
|
|
let home = tempfile::TempDir::new().expect("home");
|
|
let state = home.path().join("app-server-daemon");
|
|
std::fs::create_dir(&state).unwrap();
|
|
std::fs::write(state.join("app-server.stderr.log"), b"").unwrap();
|
|
let standalone = home.path().join("packages/standalone");
|
|
let current = standalone.join("current");
|
|
let release = standalone.join("releases/0.150.0-aarch64-apple-darwin");
|
|
let managed = release.join("bin/codex");
|
|
std::fs::create_dir_all(managed.parent().expect("bin parent")).expect("release");
|
|
std::fs::write(&managed, b"stable").expect("managed bin");
|
|
std::os::unix::fs::symlink(&release, ¤t).expect("current release");
|
|
assert!(!super::is_stable_standalone_release(home.path(), &managed));
|
|
let marker = standalone.join("auto-update-version");
|
|
let release_name = release.file_name().expect("release name");
|
|
std::fs::write(&marker, release_name.as_encoded_bytes()).expect("latest selection");
|
|
assert!(super::is_stable_standalone_release(home.path(), &managed));
|
|
std::fs::write(&marker, b"0.149.0-aarch64-apple-darwin").expect("stale selection");
|
|
assert!(!super::is_stable_standalone_release(home.path(), &managed));
|
|
std::fs::remove_file(&marker).expect("pinned selection");
|
|
assert!(!super::is_stable_standalone_release(home.path(), &managed));
|
|
|
|
let alpha = standalone.join("releases/0.151.0-alpha.1-aarch64-apple-darwin");
|
|
let alpha_managed = alpha.join("bin/codex");
|
|
std::fs::create_dir_all(alpha_managed.parent().expect("alpha bin parent"))
|
|
.expect("alpha release");
|
|
std::fs::write(&alpha_managed, b"alpha").expect("alpha bin");
|
|
std::fs::remove_file(¤t).expect("remove current");
|
|
std::os::unix::fs::symlink(alpha, ¤t).expect("current alpha");
|
|
assert!(!super::is_stable_standalone_release(
|
|
home.path(),
|
|
&alpha_managed
|
|
));
|
|
|
|
let local = standalone.join("local-main");
|
|
let local_managed = local.join("bin/codex");
|
|
std::fs::create_dir_all(local_managed.parent().expect("local bin parent"))
|
|
.expect("local build");
|
|
std::fs::write(&local_managed, b"local").expect("local bin");
|
|
std::fs::remove_file(¤t).expect("remove current");
|
|
std::os::unix::fs::symlink(local, ¤t).expect("current local build");
|
|
assert!(!super::is_stable_standalone_release(
|
|
home.path(),
|
|
&local_managed
|
|
));
|
|
}
|
|
|
|
#[cfg(unix)]
|
|
#[tokio::test]
|
|
async fn older_managed_binary_does_not_claim_updater_support() {
|
|
use std::os::unix::fs::PermissionsExt;
|
|
|
|
let temp = tempfile::TempDir::new().expect("home");
|
|
let binary = temp.path().join("codex");
|
|
std::fs::write(&binary, b"#!/bin/sh\nexit 2\n").expect("older binary");
|
|
std::fs::set_permissions(&binary, std::fs::Permissions::from_mode(0o755))
|
|
.expect("executable binary");
|
|
assert!(!super::supports_daemon_update_loop(&binary).await);
|
|
std::fs::write(&binary, b"#!/bin/sh\nexit 0\n").expect("newer binary");
|
|
assert!(super::supports_daemon_update_loop(&binary).await);
|
|
}
|