use std::fs;
use std::path::{Path, PathBuf};
use std::process::Command;
const MARKER: &[u8] = b"SIGNED\n";
fn tmp(name: &str) -> PathBuf {
let dir = Path::new(env!("CARGO_TARGET_TMPDIR")).join(name);
let _ = fs::remove_dir_all(&dir);
fs::create_dir_all(&dir).unwrap();
dir
}
fn write_fixture(dir: &Path, sign_block: &str) -> PathBuf {
fs::write(dir.join("hello.exe"), b"MZ dummy payload").unwrap();
let config = format!(
r#"
[package]
name = "hello"
display-name = "Embala Hello"
version = "0.1.0"
identifier = "ca.hrndz.embala.hello"
publisher = "Carlos Hernandez"
description = "sign integration fixture"
[msi]
arch = "x86_64"
main-executable = "hello.exe"
files = [{{ src = "hello.exe", dest = "hello.exe" }}]
[setup]
arch = "x86_64"
main-executable = "hello.exe"
files = [{{ src = "hello.exe", dest = "hello.exe" }}]
{sign_block}
"#
);
let path = dir.join("embala.toml");
fs::write(&path, config).unwrap();
path
}
fn write_sign_script(dir: &Path, log: &Path) -> PathBuf {
let script = dir.join("sign.sh");
fs::write(
&script,
format!(
"#!/bin/sh\nprintf '%s\\n' \"$1\" >> '{}'\nprintf 'SIGNED\\n' >> \"$1\"\n",
log.display()
),
)
.unwrap();
script
}
fn build(config: &Path, out_dir: &Path, formats: &str) -> std::process::Output {
build_with(config, out_dir, formats, &[])
}
fn build_with(
config: &Path,
out_dir: &Path,
formats: &str,
extra: &[&str],
) -> std::process::Output {
Command::new(env!("CARGO_BIN_EXE_embala"))
.args(["build", "--config"])
.arg(config)
.args(["--formats", formats, "--out-dir"])
.arg(out_dir)
.args(extra)
.output()
.expect("run embala")
}
#[test]
fn windows_sign_runs_on_stub_then_setup_and_msi_in_order() {
let dir = tmp("sign-order");
let log = dir.join("sign.log");
let script = write_sign_script(&dir, &log);
let sign_block = format!("[sign.windows]\ncommand = \"sh {} $f\"\n", script.display());
let config = write_fixture(&dir, &sign_block);
let out = dir.join("dist");
let result = build(&config, &out, "msi,setup");
assert!(
result.status.success(),
"build failed: {}",
String::from_utf8_lossy(&result.stderr)
);
let setup_exe = out.join("hello-0.1.0-x86_64-setup.exe");
let msi = out.join("hello-0.1.0-x86_64.msi");
for artifact in [&setup_exe, &msi] {
let bytes = fs::read(artifact).unwrap();
assert!(
bytes.ends_with(MARKER),
"{} missing sign marker",
artifact.display()
);
}
let bytes = fs::read(&setup_exe).unwrap();
let trailer_end = bytes.len() - MARKER.len();
let trailer = embala_setup_overlay::Trailer::parse(
&bytes[trailer_end - embala_setup_overlay::TRAILER_LEN..trailer_end],
)
.expect("valid overlay trailer before the sign marker");
let sec = trailer.signed_stub;
assert!(sec.len > 0, "signed_stub section must be non-empty");
let stub = &bytes[sec.offset as usize..(sec.offset + sec.len) as usize];
assert!(
stub.ends_with(MARKER),
"embedded signed_stub must carry the sign marker"
);
let log = fs::read_to_string(&log).unwrap();
let lines: Vec<&str> = log.lines().collect();
let stub_idx = lines
.iter()
.position(|l| l.contains(".embala-stub-"))
.expect("stub was signed");
let setup_idx = lines
.iter()
.position(|l| l.ends_with("setup.exe"))
.expect("setup.exe was signed");
assert!(
stub_idx < setup_idx,
"stub must be signed before setup.exe (log: {log:?})"
);
assert!(lines.iter().any(|l| l.ends_with(".msi")), "msi was signed");
}
#[test]
fn failing_sign_command_fails_the_build() {
let dir = tmp("sign-fail");
let config = write_fixture(&dir, "[sign.windows]\ncommand = \"false $f\"\n");
let result = build(&config, &dir.join("dist"), "setup");
assert!(
!result.status.success(),
"a failing sign command must fail the build"
);
let stderr = String::from_utf8_lossy(&result.stderr);
assert!(stderr.contains("sign command failed"), "stderr: {stderr}");
}
#[test]
fn no_sign_section_is_byte_reproducible() {
let dir = tmp("sign-repro");
let config = write_fixture(&dir, "");
let out1 = dir.join("dist1");
let out2 = dir.join("dist2");
assert!(build(&config, &out1, "msi,setup").status.success());
assert!(build(&config, &out2, "msi,setup").status.success());
for name in ["hello-0.1.0-x86_64-setup.exe", "hello-0.1.0-x86_64.msi"] {
assert_eq!(
fs::read(out1.join(name)).unwrap(),
fs::read(out2.join(name)).unwrap(),
"{name} must be byte-identical without [sign]"
);
}
}
#[test]
fn no_sign_flag_builds_as_if_sign_were_absent() {
let signed_dir = tmp("no-sign-flag");
let signed = write_fixture(&signed_dir, "[sign.windows]\ncommand = \"false $f\"\n");
let flagged = signed_dir.join("dist");
let result = build_with(&signed, &flagged, "msi,setup", &["--no-sign"]);
assert!(
result.status.success(),
"--no-sign must skip the failing sign command: {}",
String::from_utf8_lossy(&result.stderr)
);
let plain_dir = tmp("no-sign-plain");
let plain = write_fixture(&plain_dir, "");
let unsigned = plain_dir.join("dist");
assert!(build(&plain, &unsigned, "msi,setup").status.success());
for name in ["hello-0.1.0-x86_64-setup.exe", "hello-0.1.0-x86_64.msi"] {
assert_eq!(
fs::read(flagged.join(name)).unwrap(),
fs::read(unsigned.join(name)).unwrap(),
"{name} with --no-sign must match a build without [sign]"
);
}
}