#![cfg(target_os = "windows")]
use zlayer_builder::backend::hcs::HcsBackend;
use zlayer_builder::backend::BuildBackend;
use zlayer_builder::windows::deps::{validate_dockerfile, DepsError};
use zlayer_builder::{BuildError, BuildOptions, Dockerfile};
fn scratch_storage_root(slot: &str) -> std::path::PathBuf {
std::env::temp_dir().join(format!("zlayer-builder-e2e-{slot}"))
}
#[tokio::test]
#[ignore = "requires Windows host with HCS + MCR network access + SeBackupPrivilege"]
async fn hcs_backend_round_trip_nanoserver_copy() {
let tmp = tempfile::tempdir().expect("tempdir for build context");
let context = tmp.path();
std::fs::write(
context.join("hello.txt"),
b"hello from zlayer-builder L-8 e2e\n",
)
.expect("write COPY source");
let dockerfile = Dockerfile::parse(
r#"
FROM mcr.microsoft.com/windows/nanoserver:ltsc2022
COPY hello.txt C:\hello.txt
WORKDIR C:\app
CMD ["cmd", "/c", "type", "C:\\hello.txt"]
"#,
)
.expect("parse Dockerfile");
let backend = HcsBackend::with_storage_root(scratch_storage_root("round-trip"))
.await
.expect("construct HCS backend");
let opts = BuildOptions {
tags: vec!["zlayer-windows-e2e:round-trip".to_string()],
..BuildOptions::default()
};
let built = backend
.build_image(context, &dockerfile, &opts, None)
.await
.expect("HCS build to succeed");
assert!(
built.image_id.starts_with("sha256:"),
"image_id should be a sha256 reference, got {}",
built.image_id
);
assert!(
built.layer_count >= 2,
"expected at least base + new diff layer, got {}",
built.layer_count
);
assert!(
built.size > 0,
"final OCI layout should occupy non-zero bytes"
);
assert!(
built.build_time_ms > 0,
"build_time_ms should be populated, got {}",
built.build_time_ms
);
assert_eq!(
built.tags,
vec!["zlayer-windows-e2e:round-trip".to_string()],
"tag list should survive the build"
);
}
#[tokio::test]
#[ignore = "requires Windows host to construct the HcsBackend (no network needed once constructed)"]
async fn hcs_backend_rejects_multi_stage() {
let dockerfile = Dockerfile::parse(
r"
FROM mcr.microsoft.com/windows/servercore:ltsc2022 AS builder
RUN echo built > C:\artifact.txt
FROM mcr.microsoft.com/windows/nanoserver:ltsc2022
COPY --from=builder C:\artifact.txt C:\artifact.txt
",
)
.expect("parse multi-stage Dockerfile");
assert_eq!(
dockerfile.stages.len(),
2,
"sanity: parser produces two stages"
);
let tmp = tempfile::tempdir().expect("tempdir for build context");
let backend = HcsBackend::with_storage_root(scratch_storage_root("reject-multi-stage"))
.await
.expect("construct HCS backend");
let err = backend
.build_image(tmp.path(), &dockerfile, &BuildOptions::default(), None)
.await
.expect_err("multi-stage Windows builds are deferred — must error");
match err {
BuildError::NotSupported { operation } => {
assert!(
operation.contains("multi-stage"),
"error message should name the deferred capability, got: {operation}"
);
assert!(
operation.contains("HCS"),
"error message should identify the HCS backend, got: {operation}"
);
}
other => panic!("expected BuildError::NotSupported, got {other:?}"),
}
}
#[test]
#[ignore = "kept under the Windows e2e gate for symmetry; the validator itself is cross-platform and already unit-tested in windows::deps"]
fn hcs_backend_rejects_choco_on_nanoserver() {
let dockerfile = Dockerfile::parse(
r"
FROM mcr.microsoft.com/windows/nanoserver:ltsc2022
RUN choco install nginx -y
",
)
.expect("parse nanoserver+choco Dockerfile");
let err =
validate_dockerfile(&dockerfile).expect_err("choco on nanoserver must be rejected early");
let DepsError::ChocoOnNanoserver {
instruction_index,
package_manager,
} = err;
assert_eq!(package_manager, "choco", "detected pm should be `choco`");
assert_eq!(
instruction_index, 0,
"offending instruction is the first RUN in the stage"
);
}