mirror of
https://github.com/actionbook/actionbook.git
synced 2026-09-19 03:28:06 +08:00
11e71d9d03
* test(daemon): add Windows daemon red coverage (ACT-897) Add tests and CI configuration for Windows daemon TCP transport: - config.rs: test_actionbook_home_uses_userprofile_when_home_unset — cross-platform red test; fails until USERPROFILE fallback is implemented - server.rs: port_path() stub (path-only) + #[cfg(all(test,windows))] unit tests for is_daemon_running, send_sigterm, is_pid_alive Windows behaviour - tests/e2e/windows_daemon.rs: 3 e2e tests ignored on non-Windows, run on Windows CI - .github/workflows/e2e-windows.yml: windows-latest CI job for unit + e2e tests * feat(daemon): add Windows TCP localhost IPC support (ACT-897) Unix behaviour is unchanged (UnixListener/UnixStream, flock). Windows now uses TCP localhost instead of the missing Unix socket: server.rs: - run_daemon(): bind TcpListener on 127.0.0.1:0, write ephemeral port to daemon.port; use fs2::FileExt for cross-platform exclusive file locking - is_daemon_running(): TCP connect-probe against daemon.port (Windows) - send_sigterm(): taskkill /F /PID (Windows) - is_pid_alive(): delegates to is_daemon_running() TCP probe (Windows) - handle_connection_inner(): generic AsyncRead+AsyncWrite handler shared by both platforms; Unix/Windows handle_connection() call it with the appropriate stream type - port_path(): daemon.port path (used both platforms for path derivation) client.rs: - DaemonClient (Windows): TcpStream-based connect/send_action - auto_start_daemon_windows(): spawns daemon with DETACHED_PROCESS | CREATE_NEW_PROCESS_GROUP flags so it survives parent exit - wait/restart/cleanup helpers mirroring the Unix equivalents config.rs: - actionbook_home(): fall back to USERPROFILE when HOME is not set (standard Windows home directory convention) Cargo.toml: - Add fs2 = "0.4" for cross-platform LockFileEx file locking * style: apply cargo fmt to windows_daemon tests * fix: serialize USERPROFILE test with config test_lock to prevent env var race The test_actionbook_home_uses_userprofile_when_home_unset test manipulates ACTIONBOOK_HOME and HOME env vars without acquiring the shared test_lock, causing intermittent failures in other config tests that run concurrently. * fix: gate Unix-only kill() FFI behind #[cfg(unix)] for Windows build - start.rs: wrap orphan Chrome kill(pid, 0)/kill(pid, 9) in #[cfg(unix)], add #[cfg(windows)] equivalent using taskkill - chrome_reaper.rs: gate Duration/Instant imports behind #[cfg(unix)] since they're only used in the Unix SIGTERM graceful shutdown path * fix: make drop_session_entry test cross-platform for Windows CI The test used Unix-only `sleep` and `kill -0` commands. Use `ping` for a long-lived process on Windows and `tasklist` for liveness checks. * ci: limit Windows e2e to lifecycle, tab management, and daemon tests Run only windows_daemon, browser_lifecycle, and tab_management test modules on Windows CI to avoid timeouts from tests not yet adapted for Windows. Run sequentially with --test-threads=1. * ci: narrow Windows e2e to windows_daemon + tab_management browser_lifecycle tests hang on Windows (close_kills_chrome_process stuck for 30min). These tests exercise daemon lifecycle edge cases not yet adapted for Windows. Narrow scope to windows_daemon and tab_management which cover core IPC and browser operations. * ci: narrow Windows e2e to windows_daemon tests only tab_management tests require full Chrome browser interaction and hang on Windows CI. Only run our Windows-specific daemon IPC tests which validate the TCP localhost approach without Chrome dependencies. * fix: avoid pipe-inheritance hang in e2e harness on Windows On Windows, assert_cmd pipes stdout/stderr. When the CLI spawns a detached daemon, the daemon inherits those pipe handles via CreateProcess(bInheritHandles=TRUE). Even after the CLI exits, the daemon still holds write handles, blocking the test's read_to_end() forever. Fix: redirect stdout/stderr to temp files on Windows instead of pipes. Also make SoloEnv::drop() cross-platform (taskkill on Windows). * fix: add wait-timeout dev-dependency for Windows e2e harness The Windows-specific timeout code in the e2e harness uses wait_timeout::ChildExt directly. It was previously only available transitively via assert_cmd, which isn't sufficient on Windows. * fix: add Windows Chrome discovery and fix e2e test subcommand - find_chrome(): Add Windows paths (Program Files, LOCALAPPDATA) and use `where` instead of `which` for PATH lookup on Windows - Fix session_status_works_over_tcp test: use 'status' subcommand (not 'session-status' which doesn't exist) * fix: use test env's ACTIONBOOK_HOME for daemon.port path assertion The port file test was checking the global ~/.actionbook/daemon.port but the test daemon writes to SoloEnv's isolated temp directory.
96 lines
2.9 KiB
Rust
96 lines
2.9 KiB
Rust
//! Windows daemon e2e tests — skipped on non-Windows platforms.
|
|
//!
|
|
//! These tests verify that `browser start` and `browser close` work correctly
|
|
//! using TCP localhost transport (the Windows IPC path).
|
|
//!
|
|
//! Run on Windows CI with:
|
|
//! set RUN_E2E_TESTS=true
|
|
//! cargo test --test e2e windows_daemon -- --test-threads=1 --nocapture
|
|
|
|
use crate::harness;
|
|
|
|
/// On Windows, the daemon must start using TCP localhost (no Unix sockets).
|
|
/// Verify that a browser session can be created and destroyed.
|
|
#[test]
|
|
#[cfg_attr(
|
|
not(windows),
|
|
ignore = "Windows daemon transport tests only run on Windows"
|
|
)]
|
|
fn daemon_starts_and_connects_on_windows() {
|
|
if harness::skip() {
|
|
return;
|
|
}
|
|
|
|
let env = harness::SoloEnv::new();
|
|
|
|
let out = env.headless(&["browser", "start", "--set-session-id", "win-test"], 15);
|
|
harness::assert_success(&out, "browser start on Windows");
|
|
|
|
let out = env.headless(&["browser", "close", "--session", "win-test"], 10);
|
|
harness::assert_success(&out, "browser close on Windows");
|
|
}
|
|
|
|
/// The daemon port file (`daemon.port`) must exist after starting a session on Windows.
|
|
#[test]
|
|
#[cfg_attr(
|
|
not(windows),
|
|
ignore = "Windows daemon transport tests only run on Windows"
|
|
)]
|
|
fn daemon_port_file_exists_after_start() {
|
|
if harness::skip() {
|
|
return;
|
|
}
|
|
|
|
let env = harness::SoloEnv::new();
|
|
|
|
let out = env.headless(
|
|
&["browser", "start", "--set-session-id", "win-portfile"],
|
|
15,
|
|
);
|
|
harness::assert_success(&out, "browser start");
|
|
|
|
let port_path = std::path::Path::new(&env.actionbook_home).join("daemon.port");
|
|
assert!(
|
|
port_path.exists(),
|
|
"daemon.port file must exist after daemon start, path={}",
|
|
port_path.display()
|
|
);
|
|
|
|
let port_str = std::fs::read_to_string(&port_path).unwrap();
|
|
let port: u16 = port_str
|
|
.trim()
|
|
.parse()
|
|
.expect("daemon.port must contain a valid port number");
|
|
assert!(port > 0, "port must be non-zero");
|
|
|
|
// Verify daemon actually responds on that port
|
|
let addr = std::net::SocketAddr::from(([127, 0, 0, 1], port));
|
|
assert!(
|
|
std::net::TcpStream::connect_timeout(&addr, std::time::Duration::from_millis(200)).is_ok(),
|
|
"daemon must accept TCP connections on port {port}"
|
|
);
|
|
|
|
env.headless(&["browser", "close", "--session", "win-portfile"], 10);
|
|
}
|
|
|
|
/// Verify that `browser session-status` works on Windows (round-trip IPC via TCP).
|
|
#[test]
|
|
#[cfg_attr(
|
|
not(windows),
|
|
ignore = "Windows daemon transport tests only run on Windows"
|
|
)]
|
|
fn session_status_works_over_tcp() {
|
|
if harness::skip() {
|
|
return;
|
|
}
|
|
|
|
let env = harness::SoloEnv::new();
|
|
|
|
env.headless(&["browser", "start", "--set-session-id", "win-status"], 15);
|
|
|
|
let out = env.headless(&["browser", "status", "--session", "win-status"], 10);
|
|
harness::assert_success(&out, "status over TCP");
|
|
|
|
env.headless(&["browser", "close", "--session", "win-status"], 10);
|
|
}
|