修复Windows Runner固定锁路径校验
Project CI / Frontend tests (push) Failing after 16s
Project CI / Repository checks (push) Failing after 49s
Project CI / Backend tests (push) Successful in 2m58s
Project CI / Native shell tests (push) Failing after 2m46s

允许私有AppData中的单实例锁与GUI owner锁。
继续拒绝其他任意锁文件名并补齐回归测试。
让Runner测试使用经过私有DACL初始化的AppData夹具。
This commit is contained in:
2026-07-31 13:58:39 +08:00
parent dcd8901246
commit 66341fe7a5
2 changed files with 43 additions and 15 deletions
@@ -810,11 +810,13 @@ pub(super) fn try_open_external_agent_runner_lock(
.parent()
.ok_or_else(|| format!("{label} 缺少 AppData 父目录:{}", path.display()))?;
let private_parent = crate::inspect_game_creator_runtime_config_dir(parent)?;
let expected_path = private_parent.join(EXTERNAL_AGENT_RUNNER_LOCK_FILE_NAME);
if path != expected_path {
let runner_lock_path = private_parent.join(EXTERNAL_AGENT_RUNNER_LOCK_FILE_NAME);
let gui_owner_lock_path = private_parent.join(EXTERNAL_AGENT_RUNNER_GUI_OWNER_LOCK_FILE_NAME);
if path != runner_lock_path && path != gui_owner_lock_path {
return Err(format!(
"{label} 必须位于已验证的私有 AppData 固定路径:{}",
expected_path.display()
"{label} 必须位于已验证的私有 AppData 固定路径:{}{}",
runner_lock_path.display(),
gui_owner_lock_path.display()
));
}
@@ -33,6 +33,11 @@ fn unique_test_directory() -> TestDirectoryGuard {
TestDirectoryGuard(path)
}
fn private_runner_test_config_dir(directory: &TestDirectoryGuard) -> PathBuf {
crate::prepare_game_creator_runtime_config_dir(&directory.0.join("appdata"))
.expect("prepare private runner AppData")
}
fn acquire_project_owner_after_release(
root: &Path,
boot_id: &str,
@@ -273,21 +278,23 @@ fn endpoint_reuse_requires_current_protocol_and_executable_identity() {
#[test]
fn idle_runner_shutdown_treats_missing_endpoint_as_already_stopped() {
let directory = unique_test_directory();
assert!(shutdown_external_agent_runner_if_idle_at(&directory.0)
let config_dir = private_runner_test_config_dir(&directory);
assert!(shutdown_external_agent_runner_if_idle_at(&config_dir)
.expect("missing endpoint should already be stopped"));
}
#[test]
fn gui_runner_shutdown_rejects_missing_endpoint_while_runner_lock_is_held() {
let directory = unique_test_directory();
let config_dir = private_runner_test_config_dir(&directory);
let _lock = acquire_external_agent_runner_instance_lock(
&external_agent_runner_lock_path(&directory.0),
&external_agent_runner_lock_path(&config_dir),
"gui-shutdown-held-lock",
)
.expect("hold runner lock without endpoint");
let started = Instant::now();
let error = shutdown_external_agent_runner_at(&directory.0)
let error = shutdown_external_agent_runner_at(&config_dir)
.expect_err("held Runner lock means missing endpoint is not proof of shutdown");
assert!(error.contains("实例锁仍被占用"));
@@ -300,6 +307,7 @@ fn gui_runner_shutdown_rejects_missing_endpoint_while_runner_lock_is_held() {
#[test]
fn gui_runner_shutdown_has_a_short_hard_timeout_for_an_unresponsive_endpoint() {
let directory = unique_test_directory();
let config_dir = private_runner_test_config_dir(&directory);
let listener = TcpListener::bind(SocketAddrV4::new(Ipv4Addr::LOCALHOST, 0))
.expect("bind unresponsive runner fixture");
let port = listener.local_addr().expect("fixture address").port();
@@ -309,7 +317,7 @@ fn gui_runner_shutdown_has_a_short_hard_timeout_for_an_unresponsive_endpoint() {
port,
);
write_external_agent_runner_endpoint_atomic(
&external_agent_runner_endpoint_path(&directory.0),
&external_agent_runner_endpoint_path(&config_dir),
&endpoint,
)
.expect("write unresponsive endpoint");
@@ -319,7 +327,7 @@ fn gui_runner_shutdown_has_a_short_hard_timeout_for_an_unresponsive_endpoint() {
});
let started = Instant::now();
let error = shutdown_external_agent_runner_at(&directory.0)
let error = shutdown_external_agent_runner_at(&config_dir)
.expect_err("unresponsive runner must hit the GUI shutdown deadline");
assert!(error.contains("读取 Agent Runner 响应失败"));
@@ -461,27 +469,29 @@ fn runner_endpoint_rejects_hard_links() {
#[test]
fn gui_owner_lock_allows_only_one_frontend_process_per_appdata() {
let directory = unique_test_directory();
let config_dir = private_runner_test_config_dir(&directory);
let first =
acquire_external_agent_runner_gui_owner_lock(&directory.0).expect("first GUI owns AppData");
let error = acquire_external_agent_runner_gui_owner_lock(&directory.0)
acquire_external_agent_runner_gui_owner_lock(&config_dir).expect("first GUI owns AppData");
let error = acquire_external_agent_runner_gui_owner_lock(&config_dir)
.expect_err("second GUI must not share the same Runner owner");
assert!(error.contains("其他进程运行"));
drop(first);
acquire_external_agent_runner_gui_owner_lock(&directory.0)
acquire_external_agent_runner_gui_owner_lock(&config_dir)
.expect("GUI owner lock is recoverable after the first frontend exits");
}
#[test]
fn attached_gui_owner_loss_forces_runner_shutdown() {
let directory = unique_test_directory();
let config_dir = private_runner_test_config_dir(&directory);
let token = "gui-owner-monitor-token-gui-owner-monitor-token";
let state = ExternalAgentRunnerServerState::new(
directory.0.join(EXTERNAL_AGENT_RUNNER_ENDPOINT_FILE_NAME),
config_dir.join(EXTERNAL_AGENT_RUNNER_ENDPOINT_FILE_NAME),
test_endpoint(token, "gui-owner-monitor-boot", 31319),
);
let owner =
acquire_external_agent_runner_gui_owner_lock(&directory.0).expect("acquire GUI owner lock");
acquire_external_agent_runner_gui_owner_lock(&config_dir).expect("acquire GUI owner lock");
let attached = handle_external_agent_runner_request(
ExternalAgentRunnerRequest {
protocol_version: EXTERNAL_AGENT_RUNNER_PROTOCOL_VERSION,
@@ -509,9 +519,10 @@ fn attached_gui_owner_loss_forces_runner_shutdown() {
#[test]
fn gui_owner_attach_rejects_missing_owner_lock() {
let directory = unique_test_directory();
let config_dir = private_runner_test_config_dir(&directory);
let token = "gui-owner-missing-token-gui-owner-missing-token";
let state = ExternalAgentRunnerServerState::new(
directory.0.join(EXTERNAL_AGENT_RUNNER_ENDPOINT_FILE_NAME),
config_dir.join(EXTERNAL_AGENT_RUNNER_ENDPOINT_FILE_NAME),
test_endpoint(token, "gui-owner-missing-boot", 31320),
);
let response = handle_external_agent_runner_request(
@@ -1684,6 +1695,21 @@ fn active_runner_lock_is_not_repaired_or_truncated() {
assert_eq!(diagnostic["bootId"], "first-active-boot");
}
#[cfg(windows)]
#[test]
fn windows_runner_lock_rejects_unrecognized_fixed_name() {
let directory = unique_test_directory();
let config_dir = crate::prepare_game_creator_runtime_config_dir(&directory.0.join("appdata"))
.expect("prepare private runner AppData");
let unexpected_path = config_dir.join("unexpected-runner.lock");
let error = try_open_external_agent_runner_lock(&unexpected_path, "unexpected Runner lock")
.expect_err("only the instance and GUI owner lock names are allowed");
assert!(error.contains("固定锁路径"));
assert!(!unexpected_path.exists());
}
#[cfg(windows)]
#[test]
fn windows_stale_runner_lock_is_reowned_for_token_user() {