#[cfg(test)]
mod tests {
use std::fs;
use std::path::{Path, PathBuf};
fn repo_root() -> PathBuf {
PathBuf::from(env!("CARGO_MANIFEST_DIR"))
}
fn read(path: impl AsRef<Path>) -> String {
let path = repo_root().join(path);
fs::read_to_string(&path).unwrap_or_else(|e| panic!("cannot read {}: {e}", path.display()))
}
fn cargo_field(key: &str) -> String {
let manifest = read("Cargo.toml");
for line in manifest.lines() {
let line = line.trim();
if line.starts_with('[') && line != "[package]" {
break;
}
let Some((name, value)) = line.split_once('=') else { continue };
if name.trim() != key {
continue;
}
return value.trim().trim_matches('"').to_string();
}
panic!("`{key}` not found in the [package] block of Cargo.toml");
}
fn json_field(file: &str, key: &str) -> String {
let text = read(file);
let needle = format!("\"{key}\"");
let start = text.find(&needle).unwrap_or_else(|| panic!("`{key}` not found in {file}"));
let after = &text[start + needle.len()..];
let after = after.trim_start().trim_start_matches(':').trim_start();
let after = after.strip_prefix('"').unwrap_or_else(|| panic!("`{key}` in {file} is not a string"));
after[..after.find('"').expect("unterminated string")].to_string()
}
#[test]
fn npm_package_version_matches_the_crate() {
let crate_version = cargo_field("version");
let npm_version = json_field("npm/package.json", "version");
assert_eq!(
npm_version, crate_version,
"npm/package.json version ({npm_version}) differs from Cargo.toml ({crate_version}); \
the npm page would advertise a version that was never released"
);
}
#[test]
fn npm_wrapper_downloads_the_matching_binary() {
let crate_version = cargo_field("version");
let binary_tag = json_field("npm/package.json", "binary");
assert_eq!(
binary_tag,
format!("v{crate_version}"),
"the npm wrapper points at release {binary_tag} while this is {crate_version}; \
installing from npm would fetch the wrong binary"
);
}
#[test]
fn the_product_is_described_the_same_way_everywhere() {
let crate_description = cargo_field("description");
let npm_description = json_field("npm/package.json", "description");
assert_eq!(
npm_description, crate_description,
"crates.io and npm describe the product differently; \
Cargo.toml `description` is the single source"
);
}
#[test]
fn readme_is_shared_rather_than_duplicated() {
let duplicate = repo_root().join("npm/README.md");
assert!(
!duplicate.exists(),
"npm/README.md exists again; it will drift from the root README. \
The publish workflow copies the root one into npm/ instead."
);
}
#[test]
fn readme_links_resolve_off_github() {
let readme = read("README.md");
for (line_no, line) in readme.lines().enumerate() {
for (marker, kind) in [("src=\"", "image"), ("](", "link")] {
let mut rest = line;
while let Some(at) = rest.find(marker) {
let target = &rest[at + marker.len()..];
let end = if marker == "](" { ')' } else { '"' };
let target = &target[..target.find(end).unwrap_or(target.len())];
let relative = !target.starts_with("http") && !target.starts_with('#') && !target.is_empty();
assert!(
!relative,
"README line {}: relative {kind} `{target}` breaks on crates.io and npm; use an absolute URL",
line_no + 1
);
rest = &rest[at + marker.len()..];
}
}
}
}
#[test]
fn the_unix_installer_redirects_windows_shells() {
let installer = read("tools/install.sh");
for shell in ["MINGW*", "MSYS*", "CYGWIN*"] {
assert!(
installer.contains(shell),
"install.sh does not recognise {shell}; Windows shells fall through to the generic 'no prebuilt binary' message"
);
}
assert!(
installer.contains("install.ps1"),
"install.sh does not name the PowerShell installer, leaving Windows users at a dead end"
);
}
#[test]
fn installers_name_the_crate_that_actually_exists() {
let crate_name = cargo_field("name");
for file in ["tools/install.sh", "tools/install.ps1"] {
let text = read(file);
for (line_no, line) in text.lines().enumerate() {
let Some(at) = line.find("cargo install ") else { continue };
let named = line[at + "cargo install ".len()..]
.split_whitespace()
.next()
.unwrap_or("")
.trim_matches(|c: char| !c.is_ascii_alphanumeric() && c != '-' && c != '_');
assert_eq!(
named,
crate_name,
"{file} line {} suggests `cargo install {named}`, but the crate is `{crate_name}`",
line_no + 1
);
}
}
}
#[test]
fn the_windows_installer_keeps_the_path_type() {
let windows = read("tools/install.ps1");
assert!(
!windows.contains("SetEnvironmentVariable"),
"install.ps1 must not call [Environment]::SetEnvironmentVariable - it stores the user PATH as REG_SZ and breaks %VAR% entries"
);
assert!(
windows.contains("DoNotExpandEnvironmentNames") && windows.contains("-Type ExpandString"),
"install.ps1 must read the raw PATH and write it back as ExpandString"
);
assert!(
windows.contains("0x1A"),
"install.ps1 must broadcast WM_SETTINGCHANGE so open shells see the new PATH"
);
}
fn ico_images(ico: &[u8]) -> Vec<(u32, &[u8])> {
let count = u16::from_le_bytes([ico[4], ico[5]]) as usize;
(0..count)
.map(|index| {
let at = 6 + index * 16;
let entry = &ico[at..at + 16];
let size = if entry[0] == 0 { 256 } else { entry[0] as u32 };
let length = u32::from_le_bytes([entry[8], entry[9], entry[10], entry[11]]) as usize;
let offset = u32::from_le_bytes([entry[12], entry[13], entry[14], entry[15]]) as usize;
(size, &ico[offset..offset + length])
})
.collect()
}
fn is_filled_tile(png: &[u8]) -> (bool, u32) {
let decoder = png::Decoder::new(std::io::Cursor::new(png));
let mut reader = decoder.read_info().expect("an icon image is not a PNG");
let mut buf = vec![0; reader.output_buffer_size().expect("png buffer size")];
let info = reader.next_frame(&mut buf).expect("cannot decode an icon image");
let channels = info.color_type.samples();
assert_eq!(info.bit_depth, png::BitDepth::Eight, "icon images are exported as 8-bit RGBA");
let (x, y) = (info.width as usize / 4, info.height as usize / 2);
let at = (y * info.width as usize + x) * channels;
let pixel = &buf[at..at + channels];
if channels == 4 {
assert!(pixel[3] > 40, "the {}px image is transparent where the tile should be", info.width);
}
let brightness = u32::from(pixel[0]) + u32::from(pixel[1]) + u32::from(pixel[2]);
(brightness > 180, info.width)
}
#[test]
fn every_icon_size_carries_the_level_that_reads_at_it() {
let ico = std::fs::read(repo_root().join("assets/icon.ico")).expect("assets/icon.ico is missing");
let images = ico_images(&ico);
assert!(!images.is_empty(), "the .ico has no images");
for (size, png) in images {
let (filled, width) = is_filled_tile(png);
assert_eq!(width, size, "the {size}px entry holds a {width}px image");
if size <= 27 {
assert!(
filled,
"the {size}px image is not the filled S tile; below 28px the outline collapses into noise"
);
} else {
assert!(
!filled,
"the {size}px image is the filled S tile, not the plated mark - the level rule puts S at 27px and below"
);
}
}
let touch = std::fs::read(repo_root().join("assets/apple-touch-icon.png")).expect("apple-touch-icon.png is missing");
let (filled, width) = is_filled_tile(&touch);
assert_eq!(width, 180);
assert!(!filled, "apple-touch-icon.png is the filled S tile at 180px");
let docs_copy = std::fs::read(repo_root().join("docs/public/apple-touch-icon.png")).expect("docs copy is missing");
assert_eq!(touch, docs_copy, "docs/public/apple-touch-icon.png drifted from assets/");
}
#[test]
fn the_largest_icon_image_comes_first() {
let ico = std::fs::read(repo_root().join("assets/icon.ico")).expect("assets/icon.ico is missing");
let sizes: Vec<u32> = ico_images(&ico).into_iter().map(|(size, _)| size).collect();
let mut sorted = sizes.clone();
sorted.sort_unstable_by(|a, b| b.cmp(a));
assert_eq!(sizes, sorted, "the images are not ordered largest first");
}
#[test]
fn the_alias_is_a_link_rather_than_a_second_binary() {
let unix = read("tools/install.sh");
assert!(
unix.contains("ln -sf turnout"),
"install.sh must symlink the alias; a copy goes stale on the next self-update"
);
let windows = read("tools/install.ps1");
assert!(
windows.contains("-ItemType HardLink"),
"install.ps1 must hard-link the alias (symlinks need elevation on Windows); a plain copy goes stale on the next self-update"
);
let link_at = windows.find("-ItemType HardLink").expect("checked above");
let copy_at = windows.find("Copy-Item (Join-Path $dir \"turnout.exe\") $alias");
if let Some(copy_at) = copy_at {
assert!(
link_at < copy_at,
"install.ps1 copies the alias before trying to link it - the copy would always win"
);
}
}
#[test]
fn the_alias_is_not_packaged_as_a_second_binary() {
let manifest = read("Cargo.toml");
assert!(
!manifest.contains("[[bin]]"),
"Cargo.toml declares an extra binary; the `tn` alias is a link made at install time, not a packaged copy"
);
let workflow = read(".github/workflows/release.yml");
assert!(
!workflow.contains("tn.exe") && !workflow.contains("/tn "),
"release.yml packages a `tn` binary; the alias is created by the installer as a link"
);
}
#[test]
fn readme_only_shows_commands_that_exist() {
let readme = read("README.md");
let help = String::from_utf8(
std::process::Command::new(env!("CARGO_BIN_EXE_turnout"))
.arg("--help")
.output()
.expect("cannot run turnout --help")
.stdout,
)
.expect("help output is not utf-8");
let known: Vec<String> = help
.lines()
.skip_while(|l| !l.starts_with("Commands:"))
.skip(1)
.take_while(|l| l.starts_with(" ") && !l.trim().is_empty())
.filter_map(|l| l.split_whitespace().next())
.map(str::to_string)
.collect();
assert!(!known.is_empty(), "could not parse subcommands out of --help");
for line in readme.lines() {
let line = line.trim();
let Some(rest) = line.strip_prefix("$ turnout ") else { continue };
let Some(word) = rest.split_whitespace().next() else { continue };
if word.starts_with('-') {
continue; }
assert!(
known.contains(&word.to_string()),
"README shows `turnout {word}`, which is not a command; known: {known:?}"
);
}
}
#[test]
fn the_readme_sample_shows_the_current_version() {
let readme = read("README.md");
let version = cargo_field("version");
let stale: Vec<&str> = readme
.lines()
.map(str::trim)
.filter(|line| line.starts_with("turnout 0.") && !line.contains(&version))
.collect();
assert!(
stale.is_empty(),
"the README shows an old version in a sample: {stale:?} (this release is {version})"
);
}
}