fix(core): prevent startup failures after core updates

Atomically replace core files, recover from macOS SIGKILLs, and capture startup output for diagnostics.

Fixes #224
This commit is contained in:
lzt404 committed 2026-09-08 13:35:08 +08:00
1 parent 08ee8ca8d3
commit 53ef4dc264
4 files changed
+338 -35

No files matched your search

+96
View File
@@ -307,6 +307,9 @@ fn replacing_a_core_preserves_only_regular_bundled_assets() {
#[test]
fn overlaying_a_core_updates_packaged_files_and_preserves_plugins() {
#[cfg(unix)]
use std::os::unix::fs::MetadataExt;
let root = agent_test_home("core-overlay-preserves-plugins");
let install_dir = root.join("cpa-core");
let staging_dir = root.join("cpa-core.staging");
@@ -329,6 +332,10 @@ fn overlaying_a_core_updates_packaged_files_and_preserves_plugins() {
)
.unwrap();
fs::write(staging_dir.join("runtime/default.json"), b"new runtime").unwrap();
#[cfg(unix)]
let original_binary_inode = fs::metadata(install_dir.join(core_binary_name()))
.unwrap()
.ino();
overlay_install_dir(&install_dir, &staging_dir).unwrap();
@@ -356,6 +363,14 @@ fn overlaying_a_core_updates_packaged_files_and_preserves_plugins() {
fs::read(install_dir.join("user-data.json")).unwrap(),
b"user data"
);
#[cfg(unix)]
assert_ne!(
fs::metadata(install_dir.join(core_binary_name()))
.unwrap()
.ino(),
original_binary_inode,
"更新后的内核必须使用新 inode"
);
assert!(!staging_dir.exists());
fs::remove_dir_all(root).unwrap();
}
@@ -537,3 +552,84 @@ fn release_page_assets_parse_download_links_and_sha256() {
.browser_download_url
.ends_with("/releases/download/v1.2.3/CLIProxyAPI_1.2.3_linux_amd64.tar.gz"));
}
#[test]
fn rematerializing_core_binary_preserves_bytes_and_cleans_up_temporary_file() {
#[cfg(unix)]
use std::os::unix::fs::MetadataExt;
let root = agent_test_home("core-rematerialize");
fs::create_dir_all(&root).unwrap();
let binary_path = root.join(core_binary_name());
fs::write(&binary_path, b"signed core bytes").unwrap();
#[cfg(unix)]
let original_inode = fs::metadata(&binary_path).unwrap().ino();
rematerialize_core_binary(&binary_path).unwrap();
assert_eq!(fs::read(&binary_path).unwrap(), b"signed core bytes");
#[cfg(unix)]
assert_ne!(fs::metadata(&binary_path).unwrap().ino(), original_inode);
assert_eq!(fs::read_dir(&root).unwrap().count(), 1);
fs::remove_dir_all(root).unwrap();
}
#[test]
fn core_start_log_path_follows_the_managed_logs_directory() {
let base_dir = PathBuf::from("test-base");
let install_dir = base_dir.join("cpa-core");
assert_eq!(
core_start_log_path(&install_dir, DEFAULT_AUTH_DIR),
base_dir
.join("oauth")
.join("logs")
.join("core-start-output.log")
);
assert_eq!(
core_start_log_path(&install_dir, "custom-auth"),
install_dir
.join("custom-auth")
.join("logs")
.join("core-start-output.log")
);
}
#[test]
fn core_start_output_helper() {
if env::var_os("EASYCLIPROXYAPI_CORE_OUTPUT_TEST_HELPER").is_none() {
return;
}
println!("core stdout marker");
eprintln!("core stderr marker");
}
#[test]
fn core_start_log_captures_stdout_and_stderr() {
let root = agent_test_home("core-start-output");
let log_path = root.join("logs").join("core-start-output.log");
fs::create_dir_all(log_path.parent().unwrap()).unwrap();
fs::write(&log_path, "stale startup output").unwrap();
let (stdout, stderr) = core_start_stdio(&log_path).unwrap();
let mut command = Command::new(env::current_exe().unwrap());
command
.args([
"--exact",
"tests::core_runtime::core_start_output_helper",
"--nocapture",
])
.env("EASYCLIPROXYAPI_CORE_OUTPUT_TEST_HELPER", "1")
.stdin(Stdio::null())
.stdout(stdout)
.stderr(stderr);
configure_background_command(&mut command);
assert!(command.status().unwrap().success());
let output = fs::read_to_string(&log_path).unwrap();
assert!(output.contains("===== CPA 内核启动"));
assert!(output.contains("core stdout marker"));
assert!(output.contains("core stderr marker"));
assert!(!output.contains("stale startup output"));
fs::remove_dir_all(root).unwrap();
}