修复Windows Runner固定锁路径校验
允许私有AppData中的单实例锁与GUI owner锁。 继续拒绝其他任意锁文件名并补齐回归测试。 让Runner测试使用经过私有DACL初始化的AppData夹具。
This commit is contained in:
@@ -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() {
|
||||
|
||||
Reference in New Issue
Block a user