mirror of
https://github.com/openai/codex.git
synced 2026-10-20 18:30:37 +00:00
## Why Daemon package tests copy full debug CLI packages. Running these I/O-heavy cases alongside other tests can increase shared-disk writeback pressure. ## What changed - Move package installation and replacement coverage into the `app_server_daemon_package` test binary, sharing daemon fixtures with the lifecycle tests. - Reserve all nextest test threads for each package test and add an exclusive Bazel target that runs the cases serially on Linux and macOS. - Hard-link the prepared fixture executable during package setup, while retaining real independent package copies in the installer. GitOrigin-RevId: 255ac8caf75d9eb61bafe68f1f9bcfa99ee72484
220 lines
7.9 KiB
Rust
220 lines
7.9 KiB
Rust
//! Real package installation and replacement through the daemon CLI.
|
|
//! Keep these I/O-heavy cases separate from ordinary lifecycle coverage.
|
|
#![cfg(unix)]
|
|
|
|
mod support;
|
|
|
|
use anyhow::Context;
|
|
use anyhow::Result;
|
|
use anyhow::ensure;
|
|
use pretty_assertions::assert_eq;
|
|
use serde_json::Value;
|
|
use support::TestDaemon;
|
|
use support::wait_for_exit;
|
|
|
|
#[derive(Clone, Copy, PartialEq, Eq)]
|
|
enum InitialDaemon {
|
|
Missing,
|
|
Legacy,
|
|
}
|
|
|
|
fn packaged_daemon_launch(action: &str, initial: InitialDaemon) -> Result<()> {
|
|
let mut daemon = TestDaemon::new()?;
|
|
let standalone = daemon.home.path().join("packages/standalone");
|
|
let package = if action == "start" && initial == InitialDaemon::Missing {
|
|
standalone.join("releases/caller")
|
|
} else {
|
|
daemon.home.path().join("cli-package")
|
|
};
|
|
for directory in ["bin", "codex-path", "codex-resources"] {
|
|
std::fs::create_dir_all(package.join(directory))?;
|
|
}
|
|
// Both paths are in this test's temp home and therefore on the same filesystem.
|
|
// Reuse its prepared (and on Intel macOS, translated) executable. The installer
|
|
// still makes a real independent package copy; only fixture setup shares this inode.
|
|
std::fs::hard_link(
|
|
standalone.join("current/bin/codex"),
|
|
package.join("bin/codex"),
|
|
)?;
|
|
daemon.codex = package.join("bin/codex");
|
|
for helper in [
|
|
"bin/codex-code-mode-host",
|
|
"codex-path/rg",
|
|
"codex-resources/bwrap",
|
|
] {
|
|
codex_utils_cargo_bin::write_executable(&package.join(helper), "runtime fixture")?;
|
|
}
|
|
let target = format!(
|
|
"{}-{}",
|
|
std::env::consts::ARCH,
|
|
if cfg!(target_os = "macos") {
|
|
"apple-darwin"
|
|
} else if cfg!(target_env = "gnu") {
|
|
"unknown-linux-gnu"
|
|
} else {
|
|
"unknown-linux-musl"
|
|
}
|
|
);
|
|
std::fs::write(
|
|
package.join("codex-package.json"),
|
|
serde_json::to_vec(&serde_json::json!({
|
|
"version": env!("CARGO_PKG_VERSION"), "target": target, "entrypoint": "bin/codex"
|
|
}))?,
|
|
)?;
|
|
if action == "start" && initial == InitialDaemon::Missing {
|
|
std::fs::remove_file(standalone.join("current"))?;
|
|
std::os::unix::fs::symlink(&package, standalone.join("current"))?;
|
|
}
|
|
let cli_selection = standalone.join("current").canonicalize()?;
|
|
let state = daemon.home.path().join("app-server-daemon");
|
|
if initial == InitialDaemon::Missing {
|
|
std::fs::remove_file(state.join("app-server.stderr.log"))?;
|
|
}
|
|
std::fs::write(
|
|
state.join("settings.json"),
|
|
br#"{"shutdownGraceSeconds":0}"#,
|
|
)?;
|
|
let cli_before = daemon.codex.canonicalize()?;
|
|
let result = daemon
|
|
.command()
|
|
.args(["app-server", "daemon", action])
|
|
.output()
|
|
.context("failed to launch packaged daemon fixture")?;
|
|
ensure!(
|
|
result.status.success(),
|
|
"{}",
|
|
String::from_utf8_lossy(&result.stderr)
|
|
);
|
|
assert_eq!(
|
|
String::from_utf8_lossy(&result.stderr).contains("Installing daemon from CLI version"),
|
|
initial == InitialDaemon::Missing
|
|
);
|
|
let output: Value = serde_json::from_slice(&result.stdout)?;
|
|
let dedicated = daemon
|
|
.home
|
|
.path()
|
|
.canonicalize()?
|
|
.join("packages/app-server-daemon");
|
|
let (initial_root, initial_pid_file) = match initial {
|
|
InitialDaemon::Missing => (&dedicated, "daemon.pid"),
|
|
InitialDaemon::Legacy => (&standalone, "app-server.pid"),
|
|
};
|
|
assert_eq!(
|
|
output["managedCodexPath"],
|
|
initial_root
|
|
.canonicalize()?
|
|
.join("current/bin/codex")
|
|
.to_str()
|
|
.context("managed path is not UTF-8")?
|
|
);
|
|
assert_eq!(daemon.codex.canonicalize()?, cli_before);
|
|
assert_eq!(standalone.join("current").canonicalize()?, cli_selection);
|
|
assert!(state.join(initial_pid_file).exists());
|
|
let legacy_updater = match initial {
|
|
InitialDaemon::Missing => {
|
|
assert!(!state.join("app-server-updater.pid").exists());
|
|
None
|
|
}
|
|
InitialDaemon::Legacy => Some(daemon.pid("app-server-updater.pid")?),
|
|
};
|
|
if action == "start" {
|
|
let initial_current = initial_root.join("current");
|
|
let current = dedicated.join("current");
|
|
let original = initial_current.canonicalize()?;
|
|
let original_pid = daemon.pid(initial_pid_file)?;
|
|
let settings_before = std::fs::read(state.join("settings.json"))?;
|
|
let refused = daemon
|
|
.command()
|
|
.args(["app-server", "daemon", "update", "--from-cli"])
|
|
.output()?;
|
|
assert!(!refused.status.success());
|
|
assert!(String::from_utf8_lossy(&refused.stderr).contains("rerun with --yes"));
|
|
assert_eq!(daemon.pid(initial_pid_file)?, original_pid);
|
|
assert_eq!(initial_current.canonicalize()?, original);
|
|
if initial == InitialDaemon::Legacy {
|
|
assert!(!current.exists());
|
|
}
|
|
|
|
std::fs::remove_file(package.join("bin/codex-code-mode-host"))?;
|
|
let invalid = daemon
|
|
.command()
|
|
.args(["app-server", "daemon", "update", "--from-cli", "--yes"])
|
|
.output()?;
|
|
assert!(!invalid.status.success());
|
|
assert_eq!(daemon.pid(initial_pid_file)?, original_pid);
|
|
assert_eq!(initial_current.canonicalize()?, original);
|
|
if initial == InitialDaemon::Legacy {
|
|
assert!(!current.exists());
|
|
}
|
|
codex_utils_cargo_bin::write_executable(
|
|
&package.join("bin/codex-code-mode-host"),
|
|
"replacement helper",
|
|
)?;
|
|
let replaced = daemon
|
|
.command()
|
|
.args(["app-server", "daemon", "update", "--from-cli", "--yes"])
|
|
.output()?;
|
|
ensure!(
|
|
replaced.status.success(),
|
|
"{}",
|
|
String::from_utf8_lossy(&replaced.stderr)
|
|
);
|
|
let output: Value = serde_json::from_slice(&replaced.stdout)?;
|
|
assert_eq!(output["status"], "updated");
|
|
assert_ne!(daemon.pid("daemon.pid")?, original_pid);
|
|
wait_for_exit(original_pid)?;
|
|
if let Some(pid) = legacy_updater {
|
|
wait_for_exit(pid)?;
|
|
}
|
|
assert!(!state.join("app-server.pid").exists());
|
|
assert!(!state.join("app-server-updater.pid").exists());
|
|
assert_eq!(
|
|
output["managedCodexPath"],
|
|
current
|
|
.join("bin/codex")
|
|
.to_str()
|
|
.context("managed path is not UTF-8")?
|
|
);
|
|
assert_ne!(current.canonicalize()?, original);
|
|
assert!(!dedicated.join("auto-update-version").exists());
|
|
assert_eq!(std::fs::read(state.join("settings.json"))?, settings_before);
|
|
assert_eq!(
|
|
std::fs::read(current.join("bin/codex-code-mode-host"))?,
|
|
b"replacement helper"
|
|
);
|
|
if initial == InitialDaemon::Missing {
|
|
assert_eq!(
|
|
std::fs::read(original.join("bin/codex-code-mode-host"))?,
|
|
b"runtime fixture"
|
|
);
|
|
}
|
|
assert_eq!(daemon.lifecycle("start")?["status"], "alreadyRunning");
|
|
assert_eq!(daemon.lifecycle("stop")?["status"], "stopped");
|
|
assert_eq!(standalone.join("current").canonicalize()?, cli_selection);
|
|
}
|
|
if action == "bootstrap" {
|
|
assert_eq!(output["autoUpdateEnabled"], false);
|
|
}
|
|
Ok(())
|
|
}
|
|
|
|
#[test]
|
|
fn packaged_daemon_start_and_explicit_replacement() -> Result<()> {
|
|
packaged_daemon_launch("start", InitialDaemon::Missing)
|
|
}
|
|
|
|
#[test]
|
|
fn packaged_daemon_restart_seeds_local_package() -> Result<()> {
|
|
packaged_daemon_launch("restart", InitialDaemon::Missing)
|
|
}
|
|
|
|
#[test]
|
|
fn packaged_daemon_bootstrap_seeds_local_package() -> Result<()> {
|
|
packaged_daemon_launch("bootstrap", InitialDaemon::Missing)
|
|
}
|
|
|
|
#[test]
|
|
fn packaged_daemon_explicit_replacement_migrates_running_legacy() -> Result<()> {
|
|
packaged_daemon_launch("start", InitialDaemon::Legacy)
|
|
}
|