use std::fs;
use tempfile::TempDir;
use zlayer_builder::{detect_runtime, resolve_runtime, Dockerfile, Instruction, Runtime};
fn tmpdir() -> TempDir {
TempDir::new().expect("create temp dir for detection test")
}
#[test]
fn windows_nanoserver_template_parses() {
let template = Runtime::WindowsNanoserver.template();
let dockerfile = Dockerfile::parse(template).expect("nanoserver template should parse");
assert_eq!(
dockerfile.stages.len(),
1,
"nanoserver template is single-stage"
);
let stage = &dockerfile.stages[0];
let base = stage.base_image.to_string();
assert_eq!(
base, "mcr.microsoft.com/windows/nanoserver:ltsc2022",
"nanoserver template must FROM the pinned ltsc2022 base, got {base}"
);
let has_windows_workdir = stage
.instructions
.iter()
.any(|i| matches!(i, Instruction::Workdir(w) if w.trim().starts_with("C:")));
assert!(
has_windows_workdir,
"nanoserver template must set a Windows WORKDIR (C:\\...)"
);
let has_cmd = stage
.instructions
.iter()
.any(|i| matches!(i, Instruction::Cmd(_)));
assert!(has_cmd, "nanoserver template must define a CMD");
}
#[test]
fn windows_servercore_template_parses() {
let template = Runtime::WindowsServerCore.template();
let dockerfile = Dockerfile::parse(template).expect("servercore template should parse");
assert_eq!(
dockerfile.stages.len(),
1,
"servercore template is single-stage"
);
let stage = &dockerfile.stages[0];
let base = stage.base_image.to_string();
assert_eq!(
base, "mcr.microsoft.com/windows/servercore:ltsc2022",
"servercore template must FROM the pinned ltsc2022 base, got {base}"
);
let has_powershell_shell = stage.instructions.iter().any(|i| {
matches!(
i,
Instruction::Shell(argv)
if argv.len() >= 2
&& argv[0] == "powershell"
&& argv[1] == "-Command"
)
});
assert!(
has_powershell_shell,
"servercore template must switch SHELL to [\"powershell\", \"-Command\"]"
);
let has_workdir = stage
.instructions
.iter()
.any(|i| matches!(i, Instruction::Workdir(w) if w.trim().starts_with("C:")));
assert!(
has_workdir,
"servercore template must set a Windows WORKDIR (C:\\...)"
);
let has_cmd = stage
.instructions
.iter()
.any(|i| matches!(i, Instruction::Cmd(_)));
assert!(has_cmd, "servercore template must define a CMD");
}
#[test]
fn detect_runtime_sln_is_servercore() {
let dir = tmpdir();
fs::write(dir.path().join("MyApp.sln"), "").unwrap();
let runtime = detect_runtime(dir.path());
assert_eq!(
runtime,
Some(Runtime::WindowsServerCore),
"a bare .sln should map to WindowsServerCore"
);
}
#[test]
fn detect_runtime_csproj_is_servercore() {
let dir = tmpdir();
fs::write(
dir.path().join("MyApp.csproj"),
r#"<Project Sdk="Microsoft.NET.Sdk"></Project>"#,
)
.unwrap();
let runtime = detect_runtime(dir.path());
assert_eq!(
runtime,
Some(Runtime::WindowsServerCore),
"a bare .csproj should map to WindowsServerCore"
);
}
#[test]
fn detect_runtime_vcxproj_is_servercore() {
let dir = tmpdir();
fs::write(dir.path().join("Native.vcxproj"), "").unwrap();
let runtime = detect_runtime(dir.path());
assert_eq!(
runtime,
Some(Runtime::WindowsServerCore),
"a bare .vcxproj should map to WindowsServerCore"
);
}
#[test]
fn detect_runtime_standalone_exe_is_nanoserver() {
let dir = tmpdir();
fs::write(dir.path().join("app.exe"), b"MZ\x90\x00").unwrap();
let runtime = detect_runtime(dir.path());
assert_eq!(
runtime,
Some(Runtime::WindowsNanoserver),
"a bare .exe with no Linux-ecosystem indicators should map to \
WindowsNanoserver (smallest self-contained wrapper)"
);
}
#[test]
fn detect_runtime_exe_does_not_override_cargo_toml() {
let dir = tmpdir();
fs::write(dir.path().join("Cargo.toml"), "[package]\nname = \"demo\"").unwrap();
fs::write(dir.path().join("helper.exe"), b"MZ").unwrap();
let runtime = detect_runtime(dir.path());
assert_eq!(
runtime,
Some(Runtime::Rust),
"Rust (Cargo.toml) must outrank a stray .exe at the context root"
);
}
#[test]
fn resolve_runtime_explicit_nanoserver_overrides_detect() {
let dir = tmpdir();
fs::write(dir.path().join("Cargo.toml"), "[package]\nname = \"demo\"").unwrap();
let resolved = resolve_runtime(Some("windows-nanoserver"), dir.path(), false);
assert_eq!(
resolved,
Some(Runtime::WindowsNanoserver),
"explicit --runtime windows-nanoserver must win over Cargo.toml detection"
);
let detected = resolve_runtime(None, dir.path(), false);
assert_eq!(
detected,
Some(Runtime::Rust),
"with no explicit runtime, detection still picks Rust"
);
}