vivacity-core 0.12.0

Manifests, content hash, platform checks, dist fetching, content-addressed store and installation for vivacity
Documentation
//! Differential property test: php_json_encode(v) must be byte-identical to
//! PHP's `json_encode(json_decode($json, true), 0)` for arbitrary JSON
//! values. The oracle is real PHP; the generator hunts for the cases we did
//! not think of (the example-by-example differential proves the known
//! cases).

use proptest::prelude::*;
use serde_json::Value;
use std::io::Write as _;
use std::process::{Command, Stdio};

fn arb_json(depth: u32) -> impl Strategy<Value = Value> {
    let leaf = prop_oneof![
        Just(Value::Null),
        any::<bool>().prop_map(Value::Bool),
        any::<i64>().prop_map(|i| Value::Number(i.into())),
        any::<f64>()
            .prop_filter("finite", |f| f.is_finite())
            .prop_map(|f| serde_json::Number::from_f64(f)
                .map(Value::Number)
                .unwrap_or(Value::Null)),
        // Strings with hostile content: unicode, controls, quotes, slashes.
        "[\\PC\\n\\t/\"\\\\]{0,12}".prop_map(Value::String),
    ];
    leaf.prop_recursive(depth, 24, 6, |inner| {
        prop_oneof![
            prop::collection::vec(inner.clone(), 0..5).prop_map(Value::Array),
            prop::collection::vec(("[a-z0-9/\\-]{0,6}", inner), 0..5).prop_map(|kvs| {
                let mut map = serde_json::Map::new();
                for (k, v) in kvs {
                    map.insert(k, v);
                }
                Value::Object(map)
            }),
        ]
    })
}

fn php_oracle_encode(json_text: &str) -> String {
    let mut child = Command::new("php")
        .args([
            "-d",
            "error_reporting=0",
            "-r",
            "echo json_encode(json_decode(stream_get_contents(STDIN), true), 0);",
        ])
        .stdin(Stdio::piped())
        .stdout(Stdio::piped())
        .spawn()
        .expect("php must be installed");
    child
        .stdin
        .take()
        .expect("stdin")
        .write_all(json_text.as_bytes())
        .expect("write");
    let out = child.wait_with_output().expect("php exit");
    assert!(out.status.success(), "PHP oracle failed");
    String::from_utf8(out.stdout).expect("utf8")
}

proptest! {
    // The oracle forks one PHP process per case: we cap the number of cases
    // to keep the test under a few seconds (raise PROPTEST_CASES locally).
    #![proptest_config(ProptestConfig { cases: 64, ..ProptestConfig::default() })]
    #[test]
    fn matches_php_json_encode(v in arb_json(3)) {
        // Go through the JSON text so that both sides decode the same thing
        // (serde and PHP each normalise their own parsing).
        let text = serde_json::to_string(&v).expect("serde encode");
        let reparsed: Value = serde_json::from_str(&text).expect("reparse");
        let ours = vivacity_core::phpjson::php_json_encode(&reparsed).expect("encode");
        let theirs = php_oracle_encode(&text);
        prop_assert_eq!(ours, theirs);
    }
}

/// Exact-tie cases found by the property test (CI 2026-09-11): the value
/// sits halfway between two shortest strings, dtoa rounds to the even
/// digit. Frozen as a deterministic regression.
#[test]
fn exact_ties_round_to_even_like_dtoa() {
    for text in [
        "{\"\":-2124202659384827.2}",
        "[2124202659384827.25]",
        "[4503599627370497.5]",
        "[1125899906842624.5]",
        "[-1.0287745609898322e+201]",
        "[2.5,3.5,0.125,1e23,9.5e-5,5e-324,1.7976931348623157e308]",
    ] {
        let v: Value = serde_json::from_str(text).expect("parse");
        let ours = vivacity_core::phpjson::php_json_encode(&v).expect("encode");
        assert_eq!(ours, php_oracle_encode(text), "divergence on {text}");
    }
}