microsandbox 0.7.7

`microsandbox` is the core library for the microsandbox project.
#[test]
fn creation_futures_remain_small_for_concurrent_callers() {
    let future = microsandbox::Sandbox::builder("stack-probe").create();
    let detached = microsandbox::Sandbox::builder("stack-probe").create_detached();
    for bytes in [
        std::mem::size_of_val(&future),
        std::mem::size_of_val(&detached),
    ] {
        assert!(
            bytes < 16 * 1024,
            "create state must not inflate every caller's async stack: {bytes} bytes"
        );
    }
}

#[test]
#[cfg(feature = "local")]
fn rust_root_compat_exports_stay_available() {
    // Compile-time tripwire for public root exports restored after the
    // backend-routing refactor. The function items are not invoked.
    let _ = microsandbox::Image::get;
    let _ = microsandbox::Image::list;
    let _ = microsandbox::Image::inspect;
    let _ = microsandbox::Image::remove;
    let _ = microsandbox::Image::prune;
    let _ = microsandbox::all_sandbox_metrics;

    let _: Option<microsandbox::ImagePruneReport> = None;
    let _: Option<microsandbox::SandboxMetrics> = None;
}

#[test]
#[cfg(feature = "local")]
fn rust_config_and_runtime_setup_surface_stays_available() {
    let _ = microsandbox::setup::resolve_runtime;
    let _ = microsandbox::setup::install_runtime;
    let _ = microsandbox::setup::ensure_runtime;
    let _: Option<microsandbox::setup::ResolvedRuntime> = None;
    let _: Option<microsandbox::setup::InstallOptions> = None;
    let _ = microsandbox::config::config;
    let _ = microsandbox::config::resolve_msb_path;
    let _ = microsandbox::config::resolve_libkrunfw_path;
    let _ = microsandbox::config::GlobalConfig::resolve_msb_path;
    let _ = microsandbox::config::GlobalConfig::resolve_libkrunfw_path;

    #[allow(deprecated)]
    let _: Option<microsandbox::config::LocalConfig> = None;
}

#[cfg(feature = "ssh")]
#[test]
fn rust_ssh_compat_export_stays_available() {
    let _: Option<microsandbox::SandboxSshOps> = None;
}

#[test]
fn rust_identity_and_generated_patch_surface_is_backend_neutral() {
    use microsandbox::sandbox::{DestroyOptions, RestartOptions, SandboxHandle, SandboxId};

    let _: Option<SandboxId> = None;
    let _ = SandboxHandle::id;
    let _ = SandboxHandle::connect_or_start;
    let _ = SandboxHandle::wait_for_status;
    let _ = SandboxHandle::restart;
    let _ = SandboxHandle::destroy;
    let _ = (RestartOptions::default(), DestroyOptions::default());
    let _ = microsandbox::SandboxConfigPatch::new().spec(
        microsandbox::SandboxSpecPatch::new()
            .resources(microsandbox::SandboxResourcesPatch::new().cpus(2)),
    );
}

#[allow(dead_code)]
async fn rust_sandbox_fs_handle_api_stays_available(
    fs: &microsandbox::sandbox::SandboxFsOps<'_>,
    entry: microsandbox::sandbox::FsEntry,
    metadata: microsandbox::sandbox::FsMetadata,
) -> microsandbox::MicrosandboxResult<()> {
    use microsandbox::sandbox::{FsHandle, FsOpenOptions, FsSetAttrs};

    let _: Option<FsHandle> = None;
    let file = fs.open_file("/tmp/file", FsOpenOptions::default()).await?;
    let dir = fs.open_dir("/tmp").await?;

    let _ = fs.read_handle(file, 0, None).await?;
    let mut read_stream = fs.read_handle_stream(file, 0, Some(1)).await?;
    let _ = read_stream.recv().await?;

    fs.write_handle(file, 0, b"hello").await?;
    let write_stream = fs.write_handle_stream(file, 0, None).await?;
    write_stream.close().await?;

    let _ = fs.read_dir_handle(dir, None).await?;
    let _ = fs.read_dir(dir, None).await?;

    let _ = fs.stat_handle(file).await?;
    let _ = fs.fstat(file).await?;
    fs.set_stat_handle(file, FsSetAttrs::default()).await?;
    fs.fset_stat(file, FsSetAttrs::default()).await?;

    let _ = fs.real_path(".").await?;
    fs.remove_empty_dir("/tmp/empty").await?;
    fs.close_handle(file).await?;
    fs.close_handle(dir).await?;

    let _ = (entry.uid, entry.gid, entry.accessed);
    let _ = (metadata.uid, metadata.gid, metadata.accessed);

    Ok(())
}

#[test]
fn boot_error_reason_is_preserved_in_every_backend_build() {
    use microsandbox::logs::{BootError, InitFailureReason};

    for (wire, expected) in [
        (
            Some("user_not_found"),
            Some(InitFailureReason::UserNotFound),
        ),
        (
            Some("group_not_found"),
            Some(InitFailureReason::GroupNotFound),
        ),
        (Some("future_reason"), Some(InitFailureReason::Unknown)),
        (None, None),
    ] {
        let mut record = serde_json::json!({
            "t": "2026-10-03T00:00:00Z",
            "stage": "config",
            "errno": null,
            "message": "guest initialization failed",
        });
        if let Some(wire) = wire {
            record["reason"] = wire.into();
        }
        let error: BootError = serde_json::from_value(record).unwrap();
        assert_eq!(error.reason, expected);
        let encoded = serde_json::to_value(&error).unwrap();
        if wire.is_none() {
            assert!(encoded.get("reason").is_none());
        } else {
            assert_eq!(encoded["reason"], serde_json::to_value(expected).unwrap());
        }
    }
}