test(e2e): isolate worker server runtime environments

This commit is contained in:
2026-09-22 22:26:02 +09:00
parent 043cb7bf22
commit 0869ef0cc6
16 changed files with 1568 additions and 134 deletions
+1 -1
View File
@@ -41,7 +41,7 @@ reqwest = { version = "0.13", optional = true, default-features = false, feature
ring.workspace = true
tar.workspace = true
thiserror = { workspace = true }
tokio = { workspace = true, features = ["net", "process", "rt", "sync", "time"] }
tokio = { workspace = true, features = ["net", "process", "rt", "signal", "sync", "time"] }
tracing.workspace = true
tracing-subscriber.workspace = true
toml.workspace = true
+57 -7
View File
@@ -123,8 +123,8 @@ impl FsRuntimeStore {
})?;
let owner_lock = acquire_runtime_store_owner_lock(&root)?;
recover_runtime_store_migration(&root)?;
let existed = root.exists();
if existed && !root.is_dir() {
let root_existed = root.exists();
if root_existed && !root.is_dir() {
return Err(RuntimeError::StoreCorrupt {
operation: "open runtime store",
path: root,
@@ -132,17 +132,36 @@ impl FsRuntimeStore {
});
}
if !existed {
if !root_existed {
fs::create_dir_all(&root).map_err(|source| RuntimeError::StoreIo {
operation: "create runtime store root",
path: root.clone(),
source,
})?;
}
let snapshot_exists = root.join(RUNTIME_FILE).is_file();
let workers_dir = root.join(WORKERS_DIR);
if !snapshot_exists
&& workers_dir.is_dir()
&& fs::read_dir(&workers_dir)
.map_err(|source| RuntimeError::StoreIo {
operation: "inspect uninitialized runtime store",
path: workers_dir.clone(),
source,
})?
.next()
.is_some()
{
return Err(RuntimeError::StoreCorrupt {
operation: "open runtime store",
path: root,
message: "Worker records exist without a Runtime snapshot".to_string(),
});
}
fs::create_dir_all(root.join(WORKERS_DIR)).map_err(|source| RuntimeError::StoreIo {
fs::create_dir_all(&workers_dir).map_err(|source| RuntimeError::StoreIo {
operation: "create runtime store",
path: root.join(WORKERS_DIR),
path: workers_dir,
source,
})?;
let legacy_events = root.join("events.jsonl");
@@ -158,14 +177,14 @@ impl FsRuntimeStore {
}
}
if existed {
if snapshot_exists {
migrate_runtime_store(&root, runtime_id)?;
}
let store = Self {
root,
_owner_lock: Some(owner_lock),
};
let state = if existed {
let state = if snapshot_exists {
Some(store.load_runtime_state()?)
} else {
None
@@ -2137,6 +2156,37 @@ mod tests {
document["schema_version"].as_u64().unwrap()
}
#[test]
fn auth_initialized_directory_without_snapshot_is_a_fresh_store() {
let parent = tempfile::tempdir().unwrap();
let root = parent.path().join("runtime");
fs::create_dir_all(&root).unwrap();
fs::write(root.join("auth.toml"), b"fixture auth material").unwrap();
let opened = FsRuntimeStore::open_or_create(root.clone(), "runtime-test").unwrap();
assert!(opened.state.is_none());
assert!(root.join(WORKERS_DIR).is_dir());
assert_eq!(
fs::read(root.join("auth.toml")).unwrap(),
b"fixture auth material"
);
}
#[test]
fn worker_records_without_runtime_snapshot_are_rejected() {
let parent = tempfile::tempdir().unwrap();
let root = parent.path().join("runtime");
fs::create_dir_all(root.join(WORKERS_DIR).join("orphan")).unwrap();
let error = FsRuntimeStore::open_or_create(root.clone(), "runtime-test").unwrap_err();
assert!(matches!(
error,
RuntimeError::StoreCorrupt { path, .. } if path == root
));
}
#[test]
fn runtime_store_owner_lock_child_probe() {
let Some(root) = std::env::var_os("YOI_TEST_RUNTIME_STORE_LOCK_ROOT") else {
+35
View File
@@ -139,6 +139,22 @@ pub enum RuntimeHttpStoreSelection {
},
}
pub async fn serve_runtime_http_with_shutdown<F>(
runtime: Runtime,
listener: TcpListener,
local_token: Option<String>,
shutdown: F,
) -> Result<(), RuntimeHttpServerError>
where
F: std::future::Future<Output = ()> + Send + 'static,
{
let local_token = local_token.ok_or(RuntimeHttpServerError::AuthRequired)?;
axum::serve(listener, runtime_http_router(runtime, local_token))
.with_graceful_shutdown(shutdown)
.await?;
Ok(())
}
/// Serve an existing Runtime on a pre-bound listener.
pub async fn serve_runtime_http(
runtime: Runtime,
@@ -150,6 +166,25 @@ pub async fn serve_runtime_http(
Ok(())
}
pub async fn serve_runtime_http_with_workspace_auth_shutdown<F>(
runtime: Runtime,
listener: TcpListener,
local_token: Option<String>,
workspace_auth: WorkspaceRuntimeHttpAuth,
shutdown: F,
) -> Result<(), RuntimeHttpServerError>
where
F: std::future::Future<Output = ()> + Send + 'static,
{
axum::serve(
listener,
runtime_http_router_with_optional_auth(runtime, local_token, Some(workspace_auth)),
)
.with_graceful_shutdown(shutdown)
.await?;
Ok(())
}
pub async fn serve_runtime_http_with_workspace_auth(
runtime: Runtime,
listener: TcpListener,
+21 -2
View File
@@ -75,6 +75,23 @@ fn init_serve_tracing() {
.try_init();
}
async fn shutdown_signal() {
#[cfg(unix)]
{
let mut terminate =
tokio::signal::unix::signal(tokio::signal::unix::SignalKind::terminate())
.expect("install SIGTERM handler");
tokio::select! {
_ = tokio::signal::ctrl_c() => {},
_ = terminate.recv() => {},
}
}
#[cfg(not(unix))]
{
let _ = tokio::signal::ctrl_c().await;
}
}
fn run() -> Result<(), ProcessError> {
let args = env::args().skip(1).collect::<Vec<_>>();
if matches!(args.first().map(String::as_str), Some("migrate")) {
@@ -104,11 +121,12 @@ fn run() -> Result<(), ProcessError> {
"yoi-runtime listening on {local_addr}; intended client is a trusted backend/proxy, not a browser"
);
let server = if let Some(workspace_auth) = workspace_http_auth {
worker_runtime::http_server::serve_runtime_http_with_workspace_auth(
worker_runtime::http_server::serve_runtime_http_with_workspace_auth_shutdown(
worker_runtime,
listener,
config.http.local_token,
workspace_auth,
shutdown_signal(),
)
.await
} else {
@@ -117,10 +135,11 @@ fn run() -> Result<(), ProcessError> {
"Runtime HTTP server requires Workspace issuer auth or --local-token".to_owned(),
)
})?;
worker_runtime::http_server::serve_runtime_http(
worker_runtime::http_server::serve_runtime_http_with_shutdown(
worker_runtime,
listener,
Some(local_token),
shutdown_signal(),
)
.await
};