mod common;
use boxlite::BoxliteRuntime;
use boxlite::runtime::options::{BoxOptions, BoxliteOptions, RootfsSpec};
#[tokio::test]
async fn shutdown_is_idempotent() {
let home = boxlite_test_utils::home::PerTestBoxHome::isolated_in("/tmp");
let runtime = BoxliteRuntime::new(BoxliteOptions {
home_dir: home.path.clone(),
image_registries: common::test_registries(),
})
.expect("create runtime");
let result1 = runtime.shutdown(None).await;
assert!(result1.is_ok());
let result2 = runtime.shutdown(None).await;
assert!(result2.is_ok());
}
#[tokio::test]
async fn shutdown_with_timeout() {
let home = boxlite_test_utils::home::PerTestBoxHome::isolated_in("/tmp");
let runtime = BoxliteRuntime::new(BoxliteOptions {
home_dir: home.path.clone(),
image_registries: common::test_registries(),
})
.expect("create runtime");
let result = runtime.shutdown(Some(5)).await;
assert!(result.is_ok());
}
#[tokio::test]
async fn shutdown_empty_runtime() {
let home = boxlite_test_utils::home::PerTestBoxHome::isolated_in("/tmp");
let runtime = BoxliteRuntime::new(BoxliteOptions {
home_dir: home.path.clone(),
image_registries: common::test_registries(),
})
.expect("create runtime");
let result = runtime.shutdown(None).await;
assert!(result.is_ok());
}
#[tokio::test]
async fn shutdown_does_not_affect_other_runtimes() {
let home1 = boxlite_test_utils::home::PerTestBoxHome::isolated_in("/tmp");
let runtime1 = BoxliteRuntime::new(BoxliteOptions {
home_dir: home1.path.clone(),
image_registries: common::test_registries(),
})
.expect("create runtime");
let home2 = boxlite_test_utils::home::PerTestBoxHome::isolated_in("/tmp");
let runtime2 = BoxliteRuntime::new(BoxliteOptions {
home_dir: home2.path.clone(),
image_registries: common::test_registries(),
})
.expect("create runtime");
runtime1.shutdown(None).await.unwrap();
let result = runtime2.list_info().await;
assert!(result.is_ok());
assert!(result.unwrap().is_empty());
}
#[tokio::test]
async fn read_operations_work_after_shutdown() {
let home = boxlite_test_utils::home::PerTestBoxHome::isolated_in("/tmp");
let runtime = BoxliteRuntime::new(BoxliteOptions {
home_dir: home.path.clone(),
image_registries: common::test_registries(),
})
.expect("create runtime");
runtime.shutdown(None).await.unwrap();
let result = runtime.list_info().await;
assert!(result.is_ok());
assert!(result.unwrap().is_empty());
}
#[test]
fn drop_releases_lock() {
let home = boxlite_test_utils::home::PerTestBoxHome::isolated_in("/tmp");
{
let options = BoxliteOptions {
home_dir: home.path.clone(),
image_registries: common::test_registries(),
};
let _rt = BoxliteRuntime::new(options).unwrap();
}
let options2 = BoxliteOptions {
home_dir: home.path.clone(),
image_registries: common::test_registries(),
};
let _rt2 = BoxliteRuntime::new(options2).unwrap();
}
#[tokio::test]
async fn cloned_runtime_shares_shutdown_state() {
let home = boxlite_test_utils::home::PerTestBoxHome::isolated_in("/tmp");
let runtime = BoxliteRuntime::new(BoxliteOptions {
home_dir: home.path.clone(),
image_registries: common::test_registries(),
})
.expect("create runtime");
let clone = runtime.clone();
clone.shutdown(None).await.unwrap();
let result = runtime.shutdown(None).await;
assert!(
result.is_ok(),
"Second shutdown via original should succeed as no-op"
);
}
#[tokio::test]
async fn shutdown_timeout_edge_values() {
let home = boxlite_test_utils::home::PerTestBoxHome::isolated_in("/tmp");
let runtime = BoxliteRuntime::new(BoxliteOptions {
home_dir: home.path.clone(),
image_registries: common::test_registries(),
})
.expect("create runtime");
assert!(runtime.shutdown(Some(0)).await.is_ok());
let home = boxlite_test_utils::home::PerTestBoxHome::isolated_in("/tmp");
let runtime = BoxliteRuntime::new(BoxliteOptions {
home_dir: home.path.clone(),
image_registries: common::test_registries(),
})
.expect("create runtime");
assert!(runtime.shutdown(Some(-1)).await.is_ok());
let home = boxlite_test_utils::home::PerTestBoxHome::isolated_in("/tmp");
let runtime = BoxliteRuntime::new(BoxliteOptions {
home_dir: home.path.clone(),
image_registries: common::test_registries(),
})
.expect("create runtime");
assert!(runtime.shutdown(Some(30)).await.is_ok());
let home = boxlite_test_utils::home::PerTestBoxHome::isolated_in("/tmp");
let runtime = BoxliteRuntime::new(BoxliteOptions {
home_dir: home.path.clone(),
image_registries: common::test_registries(),
})
.expect("create runtime");
assert!(runtime.shutdown(Some(-5)).await.is_ok());
}
#[tokio::test]
async fn concurrent_shutdown_is_safe() {
let home = boxlite_test_utils::home::PerTestBoxHome::isolated_in("/tmp");
let runtime = BoxliteRuntime::new(BoxliteOptions {
home_dir: home.path.clone(),
image_registries: common::test_registries(),
})
.expect("create runtime");
let handles: Vec<_> = (0..4)
.map(|_| {
let rt = runtime.clone();
tokio::spawn(async move { rt.shutdown(None).await })
})
.collect();
let results = futures::future::join_all(handles).await;
for (i, result) in results.iter().enumerate() {
let inner = result.as_ref().expect("task should not panic");
assert!(inner.is_ok(), "shutdown #{i} should succeed: {:?}", inner);
}
}
#[tokio::test]
async fn create_after_shutdown_is_rejected() {
let home = boxlite_test_utils::home::PerTestBoxHome::isolated_in("/tmp");
let runtime = BoxliteRuntime::new(BoxliteOptions {
home_dir: home.path.clone(),
image_registries: common::test_registries(),
})
.expect("create runtime");
runtime.shutdown(None).await.unwrap();
let result = runtime
.create(
BoxOptions {
rootfs: RootfsSpec::Image("test:latest".into()),
..Default::default()
},
Some("test-box".into()),
)
.await;
match result {
Err(e) => {
let err_msg = e.to_string();
assert!(
err_msg.contains("shut down"),
"Error should mention 'shut down': {err_msg}"
);
}
Ok(_) => panic!("create should fail after shutdown"),
}
}