qex 0.15.0

Queued EXecutor — a resource-aware local job queue for long-running tasks
//! The version that this build reports, and the rule for a development build.
//!
//! # Why the version does not come from Cargo.toml
//!
//! `main` holds `version = "0.0.0-dev"` for ever. The number of a release lives
//! on the tag, and on the one commit that the tag names. `build.rs` calculates
//! the number of THIS build and gives it here; read `build.rs` for the rules
//! and for the form.
//!
//! A build thus reports one of these:
//!
//!     0.7.3                       the number in Cargo.toml: a release
//!     0.0.0-dev+g98513e2          the commit that this build holds
//!     0.0.0-dev+g98513e2.dirty    the same, with changes that are not committed
//!     0.0.0-dev+unknown           a build that could not learn its commit
//!
//! Only the first of those is a release.

/// The version of this build.
pub const VERSION: &str = env!("QEX_BUILD_VERSION");

/// Says whether a version names a development build.
///
/// THIS IS THE ONE PLACE THAT DECIDES IT. `capabilities::check_floor` treats
/// such a coordinator differently, and a second rule in a second file would let
/// the two disagree about one build.
///
/// A development version is `0.0.0-dev`, and then build metadata that a `+`
/// starts. `build.rs` writes that form and no other, so this test names it
/// exactly.
///
/// # Why the test is exact, and not a family of shapes
///
/// The answer decides which of two things happens to a coordinator below the
/// capability floor: a development build gets a WARNING, and everything else gets
/// a REFUSAL. A test that accepts a family of shapes therefore hands the gentle
/// answer to a program that qex knows nothing about — `0.0.0-devil` is below
/// the floor, and it is not a build of qex.
///
/// Nothing forces the test to be loose. `build.rs` writes one form, so the test
/// is that form. A change to the form that `build.rs` writes must change this
/// line as well, and the tests below hold the two together.
///
/// Text that this function cannot read is NOT a development build. A version
/// that qex cannot read comes from a program that qex knows nothing about, and
/// the safe answer for such a program is the strict one.
pub fn is_development(version: &str) -> bool {
    let version = version.trim();
    version == DEVELOPMENT || version.starts_with(concat!("0.0.0-dev", "+"))
}

/// The version that `main` holds, and that a build with no release number
/// reports. `Cargo.toml` holds this same text.
pub const DEVELOPMENT: &str = "0.0.0-dev";

#[cfg(test)]
mod tests {
    use super::*;

    #[test]
    fn a_release_is_not_a_development_build() {
        assert!(!is_development("0.7.3"));
        assert!(!is_development("1.0.0"));
        // Build metadata alone does not make a development build. A release
        // takes its number from Cargo.toml, and nothing adds `-dev` to it.
        assert!(!is_development("0.7.3+g98513e2"));
    }

    #[test]
    fn the_forms_that_build_rs_writes_are_development_builds() {
        assert!(is_development("0.0.0-dev"));
        assert!(is_development("0.0.0-dev+g98513e2"));
        assert!(is_development("0.0.0-dev+g98513e2.dirty"));
        // A build that could not learn its commit is still a development build,
        // so qex warns about it and never refuses it.
        assert!(is_development("0.0.0-dev+unknown"));
    }

    /// Text that qex cannot read must never pass as a development build. Such a
    /// coordinator comes from a program that qex knows nothing about.
    #[test]
    fn text_that_is_not_a_version_is_not_a_development_build() {
        assert!(!is_development(""));
        assert!(!is_development("not-a-version"));
        assert!(!is_development("-dev"));
        assert!(!is_development("0.0-dev"), "a version has three numbers");
        assert!(
            !is_development("0.0.0.0-dev"),
            "a version has three numbers"
        );
        assert!(!is_development("0.0.x-dev"));
        assert!(!is_development("0.7.3-rc1"), "a candidate is not a build");
        assert!(!is_development("0.7.3-alpha-5"), "qex writes no alpha");
    }

    /// A word that STARTS with `dev` is not `dev`.
    ///
    /// A version below the capability floor that reads as a development build gets
    /// a warning, and every other version below the floor gets a refusal. A
    /// test that accepts any word beginning with `dev` therefore gives the
    /// gentle answer to a program that qex knows nothing about, and each of the
    /// versions below is such a program.
    #[test]
    fn a_word_that_starts_with_dev_is_not_a_development_build() {
        assert!(!is_development("0.0.0-devil"));
        assert!(!is_development("0.0.0-development"));
        assert!(!is_development("0.0.0-devel"));
        assert!(!is_development("0.0.0-dev.1"), "qex writes no such form");
        assert!(!is_development("0.0.0-dev-1"), "qex writes no such form");
        // A different number with the same word is not the form that qex
        // writes. `build.rs` writes `0.0.0-dev` and nothing else.
        assert!(!is_development("0.1.0-dev"));
        assert!(!is_development("1.0.0-dev"));
    }

    /// `Cargo.toml` holds this development version or a release number, and
    /// never a third thing.
    ///
    /// # THIS TEST RUNS WHERE TWO DIFFERENT NUMBERS ARE CORRECT
    ///
    /// On `main`, `Cargo.toml` holds `0.0.0-dev`. On the commit that a tag
    /// names, `set-version.sh` has written the release number into it, and the
    /// release BUILDS AND TESTS THAT COMMIT. A test that demands `0.0.0-dev`
    /// thus passes on `main` and stops every release.
    ///
    /// The rule below holds in both places: the number is this development
    /// version exactly, or it is three numbers and nothing else.
    ///
    /// # What it catches
    ///
    /// A third form in `Cargo.toml`, such as `0.0.0-devel` or `0.1.0-dev`,
    /// while `DEVELOPMENT` stays as it is. `is_development` names one form
    /// exactly, so a build of such a tree reports a version that this file does
    /// not recognise. Its numbers are below the capability floor, so
    /// `capabilities::check_floor` REFUSES every such coordinator in place of
    /// warning about it, and a person cannot use a build of their own tree.
    ///
    /// # THIS TEST IS THE SECOND NET, AND NOT THE FIRST
    ///
    /// `build.rs::refuse_below_the_floor` stops such a build before a test
    /// runs, so this test does not see the tree and cannot fail for that
    /// reason. It accepts a wider set than `build.rs` does — `0.0.0` and
    /// `0.5.9` pass here and `build.rs` refuses both.
    ///
    /// It stays because the two guards answer to different rules. `build.rs`
    /// holds its own copy of the capability floor and reads only the number;
    /// this test reads `DEVELOPMENT`, which is the rule that `is_development`
    /// uses. A change to either one leaves the other in place.
    #[test]
    fn cargo_toml_holds_this_development_version_or_a_release() {
        assert!(
            is_development(DEVELOPMENT),
            "the constant must name a development build"
        );

        // Read the file as TOML, and take `[package] version`.
        //
        // A search for a line is not sufficient. `version = "..."` appears in a
        // dependency as well, so a search finds the first one in the FILE and
        // not the one that names this package; a comment or a space after the
        // value defeats it; and the answer then changes when somebody moves a
        // table. `toml` is already a dependency of qex, so the file costs
        // nothing to read correctly.
        let cargo: toml::Value =
            toml::from_str(include_str!("../Cargo.toml")).expect("Cargo.toml must be TOML");
        let found = cargo
            .get("package")
            .and_then(|p| p.get("version"))
            .and_then(|v| v.as_str())
            .expect("Cargo.toml must hold `[package] version`");

        let release = found.split('.').count() == 3
            && found
                .split('.')
                .all(|p| !p.is_empty() && p.bytes().all(|b| b.is_ascii_digit()));

        assert!(
            found == DEVELOPMENT || release,
            "Cargo.toml holds `{found}`. It must hold `{DEVELOPMENT}`, which is what \
             `main` holds, or three numbers, which is what the commit that a tag names \
             holds. Any other text gives a build that qex refuses in place of warning \
             about it."
        );
    }

    /// This build must report a version that a reader can use.
    #[test]
    fn this_build_names_itself() {
        assert!(!VERSION.is_empty(), "a build must report a version");
    }
}