mirror of
https://github.com/openai/codex.git
synced 2026-10-28 18:58:46 +00:00
## What changed - Run the daemon update loop on Windows using the non-interactive PowerShell installer. - Restart the managed app server after updates and hand updater ownership to a changed managed executable through a readiness handshake. - Contain installer descendants in a Windows job while allowing app-server and successor updater processes to break away. - Preserve or restore PID ownership when successor startup fails, without terminating a process that reused the PID. ## Testing - Cover non-interactive installer success and script failure on Windows. - Cover updater handoff, readiness, rollback, inaccessible processes, and PID reuse. GitOrigin-RevId: 5178c01124b4dd86b507b53f5093ec9a9d48e12c
116 lines
3.2 KiB
Rust
116 lines
3.2 KiB
Rust
use std::sync::Mutex;
|
|
|
|
use pretty_assertions::assert_eq;
|
|
|
|
use super::INSTALL_URL;
|
|
use super::InstallerHttp;
|
|
use super::InstallerResponse;
|
|
use super::fetch_installer_script;
|
|
use super::update_modes_for_identities;
|
|
use crate::RestartMode;
|
|
use crate::UpdaterRefreshMode;
|
|
use crate::managed_install::executable_identity_from_bytes;
|
|
|
|
#[test]
|
|
fn unchanged_updater_uses_version_based_restart() {
|
|
assert_eq!(
|
|
update_modes_for_identities(
|
|
&executable_identity_from_bytes(b"same"),
|
|
&executable_identity_from_bytes(b"same"),
|
|
),
|
|
(RestartMode::IfVersionChanged, UpdaterRefreshMode::None)
|
|
);
|
|
}
|
|
|
|
#[test]
|
|
fn changed_updater_forces_refresh_even_when_version_may_match() {
|
|
assert_eq!(
|
|
update_modes_for_identities(
|
|
&executable_identity_from_bytes(b"old"),
|
|
&executable_identity_from_bytes(b"new"),
|
|
),
|
|
(
|
|
RestartMode::Always,
|
|
UpdaterRefreshMode::ReexecIfManagedBinaryChanged,
|
|
)
|
|
);
|
|
}
|
|
|
|
#[tokio::test]
|
|
async fn installer_fetch_uses_exact_url_and_preserves_bytes() {
|
|
let script = b"#!/bin/sh\nprintf 'update bytes'\n".to_vec();
|
|
let http = FakeInstallerHttp::new(InstallerResponse::Success(script.clone()));
|
|
|
|
assert_eq!(
|
|
fetch_installer_script(&http)
|
|
.await
|
|
.expect("installer fetch should succeed"),
|
|
script
|
|
);
|
|
assert_eq!(http.requested_urls(), vec![INSTALL_URL.to_string()]);
|
|
}
|
|
|
|
#[tokio::test]
|
|
async fn installer_fetch_rejects_non_success_status() {
|
|
let http = FakeInstallerHttp::new(InstallerResponse::Unsuccessful { status: 503 });
|
|
|
|
let error = fetch_installer_script(&http)
|
|
.await
|
|
.expect_err("non-success response should fail");
|
|
|
|
assert!(error.to_string().contains("503"));
|
|
assert_eq!(http.requested_urls(), vec![INSTALL_URL.to_string()]);
|
|
}
|
|
|
|
struct FakeInstallerHttp {
|
|
response: InstallerResponse,
|
|
requested_urls: Mutex<Vec<String>>,
|
|
}
|
|
|
|
impl FakeInstallerHttp {
|
|
fn new(response: InstallerResponse) -> Self {
|
|
Self {
|
|
response,
|
|
requested_urls: Mutex::new(Vec::new()),
|
|
}
|
|
}
|
|
|
|
fn requested_urls(&self) -> Vec<String> {
|
|
self.requested_urls
|
|
.lock()
|
|
.unwrap_or_else(std::sync::PoisonError::into_inner)
|
|
.clone()
|
|
}
|
|
}
|
|
|
|
impl InstallerHttp for FakeInstallerHttp {
|
|
async fn get(&self, url: &str) -> anyhow::Result<InstallerResponse> {
|
|
self.requested_urls
|
|
.lock()
|
|
.unwrap_or_else(std::sync::PoisonError::into_inner)
|
|
.push(url.to_string());
|
|
Ok(self.response.clone())
|
|
}
|
|
}
|
|
|
|
#[cfg(windows)]
|
|
#[tokio::test]
|
|
async fn powershell_installer_is_noninteractive_and_reports_script_failure() {
|
|
let valid = FakeInstallerHttp::new(InstallerResponse::Success(
|
|
br#"
|
|
function Test-Installer {
|
|
if ($env:CODEX_NON_INTERACTIVE -ne '1') { throw 'interactive installer' }
|
|
}
|
|
Test-Installer
|
|
"#
|
|
.to_vec(),
|
|
));
|
|
super::install_latest_standalone(&valid)
|
|
.await
|
|
.expect("installer succeeds");
|
|
let failing = FakeInstallerHttp::new(InstallerResponse::Success(
|
|
b"throw 'installer failed'".to_vec(),
|
|
));
|
|
assert!(super::install_latest_standalone(&failing).await.is_err());
|
|
}
|