folk-runtime-embed 0.1.17

Embedded PHP runtime for Folk — PHP interpreter runs in-process via FFI
Documentation
//! Extension compatibility test.
//!
//! Loads the PHP test script and runs all extension tests.
//! Reports results as structured JSON.
//!
//! Run with Docker (requires all extensions):
//!   docker build -f crates/folk-runtime-embed/compat/Dockerfile -t folk-compat .
//!   docker run --rm folk-compat
//!
//! Or locally (tests whatever extensions are available):
//!   cargo test -p folk-runtime-embed --test extension_compat -- --test-threads=1 --nocapture

use folk_runtime_embed::php::{PhpInstance, RequestContext};

#[test]
fn run_extension_compatibility_tests() {
    let mut php = PhpInstance::boot_custom_sapi().expect("boot failed");

    let mut ctx = RequestContext::new("GET", "/");
    php.set_request_context(&mut ctx);
    php.request_startup().expect("startup failed");

    // Load the test script
    let test_script = include_str!("../compat/test_extensions.php");
    php.eval(test_script).expect("failed to load test script");

    // Run all tests
    let result = php
        .eval("echo folk_test_all_extensions_json();")
        .expect("failed to run tests");

    println!("\n=== Extension Compatibility Results ===\n");
    println!("{}", result.output);

    // Parse results and check for failures
    let results: Vec<serde_json::Value> =
        serde_json::from_str(&result.output).expect("invalid JSON from test script");

    let mut loaded = 0;
    let mut missing = 0;
    let mut passed = 0;
    let mut failed = 0;

    println!("\n| Extension | Load | Basic | Notes |");
    println!("|-----------|------|-------|-------|");

    for r in &results {
        let name = r["name"].as_str().unwrap_or("?");
        let load = r["load"].as_str().unwrap_or("?");
        let basic = r["basic"].as_str().unwrap_or("?");
        let error = r["error"].as_str().unwrap_or("");

        let load_icon = match load {
            "ok" => "",
            "missing" => "",
            _ => "",
        };
        let basic_icon = match basic {
            "ok" => "",
            "skip" => "",
            _ => "",
        };

        println!("| {name} | {load_icon} | {basic_icon} | {error} |");

        match load {
            "ok" => loaded += 1,
            "missing" => missing += 1,
            _ => {},
        }
        match basic {
            "ok" => passed += 1,
            "fail" => failed += 1,
            _ => {},
        }
    }

    println!("\n=== Summary ===");
    println!("Total: {}", results.len());
    println!("Loaded: {loaded}");
    println!("Missing: {missing}");
    println!("Passed: {passed}");
    println!("Failed: {failed}");

    // Don't assert on failures — this is informational.
    // In CI, we can set thresholds.
}

#[test]
fn extension_multi_request_stability() {
    let mut php = PhpInstance::boot_custom_sapi().expect("boot failed");

    let mut ctx = RequestContext::new("GET", "/");
    php.set_request_context(&mut ctx);
    php.request_startup().expect("startup failed");

    // Load test script
    let test_script = include_str!("../compat/test_extensions.php");
    php.eval(test_script).expect("failed to load test script");
    php.request_shutdown();

    // Run extension tests across multiple requests to check stability
    for i in 0..10 {
        let mut ctx = RequestContext::new("GET", &format!("/req/{i}"));
        php.set_request_context(&mut ctx);
        php.request_startup().expect("startup failed");

        let result = php
            .eval("echo folk_test_all_extensions_json();")
            .expect("test run failed");

        // Just verify it returns valid JSON each time
        let _: Vec<serde_json::Value> = serde_json::from_str(&result.output)
            .unwrap_or_else(|_| panic!("invalid JSON on request {i}"));

        php.request_shutdown();
    }
}