xtask/tests/licenses_ship_with_every_crate.rs
//! **The license text travels with every published artifact.**
//!
//! facett is `MIT` from 0.2.1 (owner decision 2026-10-03; everything published up to
//! and including 0.2.0 stays `MIT OR Apache-2.0`, which `NOTICE` says). MIT expects
//! its text to accompany every copy. The copyright holder is the company, Ignalina ApS.
//!
//! Where the texts must be, and why each place:
//!
//! * the repository root — `LICENSE` (MIT) and `NOTICE`, the ONE writer of each;
//! * every crate cargo may publish — `cargo package` packs only files under the
//! crate's own directory (an `include` cannot reach `..`), so each publishable
//! crate carries byte-identical copies (copies, not symlinks);
//! * both PyPI packages — `license-files` in each pyproject; the wheel/sdist build
//! stages the root texts beside the pyproject (`facett-ops` `pip::LICENSE_TEXTS`).
use std::path::{Path, PathBuf};
fn repo_root() -> PathBuf {
Path::new(env!("CARGO_MANIFEST_DIR")).parent().unwrap().to_path_buf()
}
const TEXTS: [&str; 2] = ["LICENSE", "NOTICE"];
fn read(p: &Path) -> Vec<u8> {
std::fs::read(p).unwrap_or_else(|e| panic!("{}: {e}", p.display()))
}
fn version_free_license_line(path: &str) -> String {
let text = std::fs::read_to_string(repo_root().join(path)).unwrap();
text.lines().find(|l| l.trim_start().starts_with("license =")).unwrap_or_default().trim().to_string()
}
#[test]
fn the_root_texts_are_mit_by_the_company_and_say_what_changed() {
let root = repo_root();
let mit = String::from_utf8(read(&root.join("LICENSE"))).unwrap();
assert!(mit.starts_with("MIT License\n"), "LICENSE is not the MIT text");
assert!(
mit.lines().any(|l| l.starts_with("Copyright (c) 2026 Ignalina ApS")),
"LICENSE's copyright line must name the company"
);
assert!(mit.contains("THE SOFTWARE IS PROVIDED \"AS IS\""), "LICENSE is missing the MIT warranty disclaimer");
assert!(!root.join("LICENSE-APACHE").exists() && !root.join("LICENSE-MIT").exists(), "one license file: LICENSE");
let notice = String::from_utf8(read(&root.join("NOTICE"))).unwrap();
assert!(notice.contains("up to and including 0.2.0") && notice.contains("MIT OR Apache-2.0"), "{notice}");
assert!(notice.contains("From 0.2.1 facett is MIT only"), "{notice}");
}
#[test]
fn every_license_declaration_says_mit() {
assert_eq!(version_free_license_line("Cargo.toml"), "license = \"MIT\"", "[workspace.package]");
for file in ["pkg-facet-wrapped/pyproject.toml", "py/pyproject.toml", "facett-build-thing/Cargo.toml"] {
assert_eq!(version_free_license_line(file), "license = \"MIT\"", "{file}");
}
}
#[test]
fn every_publishable_crate_carries_byte_identical_copies() {
let root = repo_root();
let want: Vec<Vec<u8>> = TEXTS.iter().map(|t| read(&root.join(t))).collect();
let mut checked = 0;
let mut bad = Vec::new();
for entry in std::fs::read_dir(&root).unwrap() {
let dir = entry.unwrap().path();
let manifest = dir.join("Cargo.toml");
if !manifest.is_file() || dir == root {
continue;
}
let text = std::fs::read_to_string(&manifest).unwrap();
if text.lines().any(|l| l.trim().replace(' ', "") == "publish=false") {
continue;
}
checked += 1;
for (t, bytes) in TEXTS.iter().zip(&want) {
let p = dir.join(t);
match std::fs::symlink_metadata(&p) {
Ok(m) if m.file_type().is_symlink() => bad.push(format!("{} is a symlink — cargo needs a copy", p.display())),
Ok(_) if read(&p) != *bytes => bad.push(format!("{} differs from the root's", p.display())),
Ok(_) => {}
Err(_) => bad.push(format!("{} is missing", p.display())),
}
}
for gone in ["LICENSE-MIT", "LICENSE-APACHE"] {
if dir.join(gone).exists() {
bad.push(format!("{} — one license file per crate: LICENSE", dir.join(gone).display()));
}
}
}
assert!(checked >= 70, "only {checked} publishable crates found — the walk is wrong");
assert!(bad.is_empty(), "{} problem(s):\n {}", bad.len(), bad.join("\n "));
}
#[test]
fn both_pyprojects_ship_the_texts() {
for file in ["pkg-facet-wrapped/pyproject.toml", "py/pyproject.toml"] {
let text = std::fs::read_to_string(repo_root().join(file)).unwrap();
assert!(
text.contains("license-files = [\"LICENSE\", \"NOTICE\"]"),
"{file} must list LICENSE and NOTICE in [project] license-files"
);
}
}