jayver 1.0.0

A calendar versioning scheme for binaries developed by Emmett Jayhart
Documentation
//! Property-based tests for invariants with randomly generated inputs
use jayver::{Version, VersionReq};
use proptest::prelude::*;

/// Strategy for generating valid years
fn years() -> impl Strategy<Value = i32> {
    prop_oneof![
        1..=99i32, // Post-2000 short years (1-99)
        -50..0i32  // Pre-2000 short years (-50 to -1)
    ]
}

/// Strategy for generating valid weeks
fn weeks() -> impl Strategy<Value = u8> {
    1..=53u8
}

/// Strategy for generating valid patches
fn patches() -> impl Strategy<Value = u32> {
    0..1000u32
}

/// Strategy for generating valid versions
fn valid_versions() -> impl Strategy<Value = Version> {
    // Generate components and filter to only valid combinations
    (years(), weeks(), patches())
        .prop_filter_map("valid calendar combinations", |(year, week, patch)| {
            Version::new(year, week, patch).ok()
        })
}

/// Operators strategy
fn operators() -> impl Strategy<Value = &'static str> {
    prop::sample::select(&["=", ">", ">=", "<", "<=", "~>"])
}

/// Strategy for generating valid requirements
fn valid_requirements() -> impl Strategy<Value = VersionReq> {
    prop::collection::vec(
        (operators(), valid_versions()).prop_map(|(op, ver)| format!("{op}{ver}")),
        1..3,
    )
    .prop_filter_map("valid requirements", |comps| {
        VersionReq::parse(comps.join(",")).ok()
    })
}

proptest! {
    // Property: parsing then stringifying equals original string
    #[test]
    fn parse_display_roundtrip(v in valid_versions()) {
        let string = v.to_string();
        let parsed = Version::parse(&string).unwrap();
        prop_assert_eq!(v, parsed);
    }

    // Property: version ordering is transitive
    #[test]
    fn version_ordering_is_transitive(
        v1 in valid_versions(),
        v2 in valid_versions(),
        v3 in valid_versions()
    ) {
        // If v1 < v2 and v2 < v3, then v1 < v3
        if v1 < v2 && v2 < v3 {
            prop_assert!(v1 < v3);
        }
    }

    // Property: requirements maintain consistency
    #[test]
    fn requirement_consistency(
        req in valid_requirements(),
        v1 in valid_versions(),
        v2 in valid_versions()
    ) {
        // If v1 == v2, then req.matches(v1) == req.matches(v2)
        if v1 == v2 {
            prop_assert_eq!(req.matches(&v1), req.matches(&v2));
        }
    }

    // Property: compatible operator works as expected
    #[test]
    fn compatible_operator_behavior(
        year in years(),
        week in weeks(),
        base_patch in 0..50u32,
        test_patch in 0..100u32
    ) {
        // Skip invalid combinations
        if let Ok(base) = Version::new(year, week, base_patch) {
            if let Ok(test) = Version::new(year, week, test_patch) {
                let req = VersionReq::parse(format!("~>{base}")).unwrap();

                // Compatible means same year and week, patch >= base
                prop_assert_eq!(req.matches(&test), test_patch >= base_patch);

                // Different week should never match
                if let Ok(other_week) = Version::new(year, if week < 53 { week + 1 } else { 1 }, test_patch) {
                    prop_assert!(!req.matches(&other_week));
                }
            }
        }
    }
}