Your browser sucks, JavaScript disabled! Gunnar's console needs JavaScript and WebAssembly. Git over SSH on port 2222 does not.

nordisk/facett

xtask/tests/python_versions_track_the_workspace.rs

//! **One version, or say which.** facett's `[workspace.package] version` is the
//! single writer for all 71 crates — that is what a workspace version means. The
//! python-facing manifests are outside that mechanism and had drifted:
//!
//! ```text
//! MEASURED 2026-09-05, before this guard:
//!   Cargo.toml            [workspace.package] version = 0.1.19
//!   pkg-facet-wrapped/pyproject.toml            version = 0.1.15   (4 behind)
//!   facett-py/Cargo.toml                        version = 0.1.15   (4 behind)
//!   facett-py/pyproject.toml                    version = 0.1.13   (6 behind)
//! ```
//!
//! The last two are the interesting ones: they are the SAME artifact (a maturin
//! wheel built from that Cargo.toml) declaring two different versions of itself.
//! Whichever one a consumer reads, the other is a lie — and a `pip install
//! facett-native==0.1.15` that resolves to bytes stamped 0.1.13 is unfalsifiable
//! from the outside.
//!
//! Drift like this cannot be fixed once. A number in four files with no writer
//! between them will separate again the next time someone bumps three of them, so
//! the fix is this test, not the edit it demanded.

use std::path::{Path, PathBuf};

fn repo_root() -> PathBuf {
    Path::new(env!("CARGO_MANIFEST_DIR")).parent().unwrap().to_path_buf()
}

/// Pull `version = "x.y.z"` out of a TOML file's FIRST occurrence after the given
/// section header. Deliberately dumb: these four files are hand-written manifests,
/// and a full TOML dependency in a guard is more surface than the guard is worth.
fn version_after(path: &Path, section: &str) -> String {
    let text = std::fs::read_to_string(path)
        .unwrap_or_else(|e| panic!("read {}: {e}", path.display()));
    let mut in_section = section.is_empty();
    for line in text.lines() {
        let t = line.trim();
        if t.starts_with('[') {
            in_section = t == section;
            continue;
        }
        if in_section && let Some(rest) = t.strip_prefix("version") {
            let rest = rest.trim_start();
            if let Some(rest) = rest.strip_prefix('=') {
                let v = rest.trim().trim_matches('"').trim_matches('\'');
                // `version.workspace = true` is the inheriting form, not a number.
                if !v.is_empty() && v.chars().next().is_some_and(|c| c.is_ascii_digit()) {
                    return v.to_string();
                }
            }
        }
    }
    panic!("no `version = \"…\"` under `{section}` in {}", path.display());
}

/// The one number everything else must equal.
fn workspace_version() -> String {
    version_after(&repo_root().join("Cargo.toml"), "[workspace.package]")
}

/// The ONE version both PyPI packages carry: facett-build-thing's `PYPI_VERSION`.
///
/// 0.2.0 (owner decision 2026-10-02) split the PyPI line from the cargo workspace:
/// the two Python packages — `facett` and `facett-demo` — release together on their
/// own number, which the crates need not share. (The same day the owner put the
/// crates on 0.2.0 too, so today the two numbers COINCIDE by decision, not by
/// construction — the next crates bump does not move this one.) So the rule moved: no longer "the
/// wheel equals the workspace", but "both pyprojects equal the recipe's ONE number,
/// and the demo pins the components to that same release line". The recipe is read
/// as CODE (a dependency of this crate), not as a fourth hand-written number.
fn pypi_version() -> String {
    facett_build_thing::PYPI_VERSION.to_string()
}

#[test]
fn both_python_packages_ship_the_recipes_pypi_version() {
    let want = pypi_version();
    for (file, project) in [("pkg-facet-wrapped/pyproject.toml", "facett"), ("py/pyproject.toml", "facett-demo")] {
        let got = version_after(&repo_root().join(file), "[project]");
        assert_eq!(
            got, want,
            "{file} ships `{project} {got}` but facett-build-thing's PYPI_VERSION is {want} — \
             the two PyPI packages release together, on ONE number"
        );
    }
}

#[test]
fn the_demo_pins_the_components_to_its_own_release_line() {
    // `facett-demo 0.2.0` must install a `facett` its gallery was tested against: the
    // same minor line, with the Streamlit extra.
    let want = pypi_version();
    let text = std::fs::read_to_string(repo_root().join("py/pyproject.toml")).unwrap();
    let mut parts = want.split('.');
    let (major, minor) = (parts.next().unwrap(), parts.next().unwrap());
    let next_minor: u32 = minor.parse::<u32>().unwrap() + 1;
    let pin = format!("\"facett[streamlit]>={want},<{major}.{next_minor}\"");
    assert!(text.contains(&pin), "py/pyproject.toml must depend on {pin}");
}

#[test]
fn facett_py_agrees_with_itself_and_with_the_workspace() {
    let want = workspace_version();
    let cargo = version_after(&repo_root().join("facett-py/Cargo.toml"), "[package]");
    let py = version_after(&repo_root().join("facett-py/pyproject.toml"), "[project]");
    assert_eq!(
        cargo, py,
        "facett-py declares TWO versions of one artifact: Cargo.toml says {cargo}, \
         pyproject.toml says {py}. The maturin wheel is built from the first and \
         published under the second."
    );
    assert_eq!(cargo, want, "facett-py is {cargo}, this workspace is {want}");
}

#[test]
fn the_demo_app_is_a_release_now() {
    // Until 0.2.0 `py/` was `facett-demo-python`, an unreleased app with its own 0.1.0,
    // and this test held that exclusion. The owner made it a PyPI package
    // (`facett-demo`), so it is IN the rule now — `both_python_packages_ship_…` above —
    // and its module reads the version from metadata like the components do.
    let buildinfo = std::fs::read_to_string(repo_root().join("py/facett_demo/buildinfo.py")).unwrap();
    assert_eq!(dunder_version(&repo_root().join("py/facett_demo/buildinfo.py")), None, "a literal version in buildinfo.py");
    assert!(buildinfo.contains("_metadata.version(\"facett-demo\")"), "the demo must read its version from its distribution");
}

/// Pull `__version__ = "x.y.z"` out of a python module.
fn dunder_version(path: &Path) -> Option<String> {
    let text = std::fs::read_to_string(path).ok()?;
    for line in text.lines() {
        let t = line.trim();
        if let Some(rest) = t.strip_prefix("__version__") {
            let rest = rest.trim_start().strip_prefix('=')?.trim();
            let v = rest.trim_matches('"').trim_matches('\'');
            if v.chars().next().is_some_and(|c| c.is_ascii_digit()) {
                return Some(v.to_string());
            }
            // A non-literal (a call, a name) is the by-construction form — there is
            // no second number to drift, which is what this test wants.
            return None;
        }
    }
    None
}

#[test]
fn the_shipped_module_does_not_carry_a_hand_written_version() {
    // FOUND 2026-09-05, by the guard above missing it: `__init__.py` carried a FOURTH
    // copy of the version, `__version__ = "0.1.15"`, while the wheel it ships in says
    // 0.1.19. `build_info()["version"]` returns that string and `robot_installed.py`
    // records it into the saga tape — so the durable record of every drive was
    // stamped with a version the artifact does not have.
    //
    // Adding it to the three-file equality check would have been the wrong fix: a
    // fourth number held in place by a test is still a fourth number, and the next
    // bump has four chances to miss one. The right fix is to have NO literal here —
    // read it from the installed distribution metadata, whose single writer is
    // pyproject.toml. So this test asserts the ABSENCE of a literal rather than its
    // value.
    let init = repo_root().join("pkg-facet-wrapped/src/facett/__init__.py");
    let text = std::fs::read_to_string(&init).unwrap();
    assert!(
        text.contains("__version__"),
        "the package must still expose __version__ — build_info() and the robot read it"
    );
    assert_eq!(
        dunder_version(&init),
        None,
        "`__init__.py` hard-codes a version literal. That is a copy with no writer, and \
         it drifted once already (0.1.15 while the wheel said 0.1.19). Derive it from the \
         installed distribution metadata instead — pyproject.toml is the one writer."
    );
}

#[test]
fn the_module_reads_its_version_from_distribution_metadata() {
    // The positive half. Asserting only the absence of a literal would pass on a file
    // that deleted __version__ altogether, or set it to a bare "unknown".
    let init = repo_root().join("pkg-facet-wrapped/src/facett/__init__.py");
    let text = std::fs::read_to_string(&init).unwrap();
    assert!(
        text.contains("importlib.metadata") || text.contains("from importlib import metadata"),
        "the version must come from the installed distribution's own metadata"
    );
    assert!(
        text.contains("_metadata.version(\"facett\")"),
        "it must ask for THIS distribution (`facett`) by name"
    );
}