mago 1.47.4

A comprehensive suite of PHP tooling inspired by Rust’s approach, providing parsing, linting, formatting, and more through a unified CLI and library interface.
#![allow(clippy::expect_used, clippy::missing_panics_doc)]

use std::collections::HashSet;
use std::num::NonZeroUsize;
use std::path::Path;
use std::process::Command;
use std::sync::Arc;

use mago_extension::WorkerCommand;
use mago_extension::WorkerPool;
use mago_extension::WorkerPoolOptions;
use mago_linter::external::ExternalLinter;
use mago_php_version::PHPVersion;

#[test]
fn worker_reducers_merge_every_process_on_the_last_survivor() -> Result<(), Box<dyn std::error::Error>> {
    if std::env::var_os("MAGO_TRACE_PHP_SDK_TESTS").is_some() {
        let _subscriber = tracing_subscriber::fmt()
            .with_env_filter(tracing_subscriber::EnvFilter::from_default_env())
            .with_test_writer()
            .try_init();
    }

    let repository = Path::new(env!("CARGO_MANIFEST_DIR"));
    let sdk_available = repository.join("vendor/autoload.php").is_file()
        && Command::new("php").arg("--version").output().is_ok_and(|output| output.status.success());
    assert!(
        sdk_available || std::env::var_os("MAGO_REQUIRE_PHP_SDK_TESTS").is_none(),
        "PHP and vendor dependencies are required for the external worker-reduction test"
    );
    if !sdk_available {
        return Ok(());
    }

    let temporary = tempfile::tempdir()?;
    let command = WorkerCommand::new("php")
        .with_argument(repository.join("composer/tests/Sdk/Fixtures/worker-reducer.php"))
        .with_current_directory(repository)
        .with_environment("MAGO_REDUCTION_AUDIT_DIRECTORY", temporary.path());
    let pool = Arc::new(WorkerPool::spawn(
        command,
        NonZeroUsize::new(3).expect("three is non-zero"),
        WorkerPoolOptions::default(),
    )?);
    let linter = ExternalLinter::initialize([Arc::clone(&pool)], PHPVersion::PHP85)?;
    drop(linter);
    pool.shutdown();

    let alpha = read_audit(&temporary.path().join("alpha.txt"), "alpha")?;
    let beta = read_audit(&temporary.path().join("beta.txt"), "beta")?;
    assert_eq!(alpha.worker_ids, beta.worker_ids, "logical extensions must observe the same worker order");
    assert_eq!(alpha.worker_ids.len(), 3);
    assert_eq!(alpha.worker_ids.iter().collect::<HashSet<_>>().len(), 3);
    assert!(alpha.worker_ids.contains(&alpha.leader), "the survivor's own contribution must be included");
    assert!(beta.worker_ids.contains(&beta.leader), "the survivor's own contribution must be included");
    assert_eq!(alpha.leader, beta.leader, "all reducers must run on the same surviving process");
    assert!(pool.is_empty());

    Ok(())
}

#[test]
fn adaptive_reduction_includes_only_workers_that_were_spawned() -> Result<(), Box<dyn std::error::Error>> {
    let repository = Path::new(env!("CARGO_MANIFEST_DIR"));
    let sdk_available = repository.join("vendor/autoload.php").is_file()
        && Command::new("php").arg("--version").output().is_ok_and(|output| output.status.success());
    assert!(
        sdk_available || std::env::var_os("MAGO_REQUIRE_PHP_SDK_TESTS").is_none(),
        "PHP and vendor dependencies are required for the external worker-reduction test"
    );
    if !sdk_available {
        return Ok(());
    }

    let temporary = tempfile::tempdir()?;
    let command = WorkerCommand::new("php")
        .with_argument(repository.join("composer/tests/Sdk/Fixtures/worker-reducer.php"))
        .with_current_directory(repository)
        .with_environment("MAGO_REDUCTION_AUDIT_DIRECTORY", temporary.path());
    let pool = Arc::new(WorkerPool::spawn_adaptive(
        command,
        NonZeroUsize::new(5).expect("five is non-zero"),
        WorkerPoolOptions::default(),
    )?);
    let linter = ExternalLinter::initialize([Arc::clone(&pool)], PHPVersion::PHP85)?;
    let spawned_workers = pool.len();
    assert!(spawned_workers < 5, "the adaptive pool must retain unspawned capacity");
    drop(linter);
    pool.shutdown();

    let alpha = read_audit(&temporary.path().join("alpha.txt"), "alpha")?;
    let beta = read_audit(&temporary.path().join("beta.txt"), "beta")?;
    assert_eq!(alpha.worker_ids.len(), spawned_workers, "unspawned adaptive capacity must not contribute empty data");
    assert_eq!(alpha.worker_ids, beta.worker_ids);
    assert_eq!(alpha.leader, beta.leader);

    Ok(())
}

struct Audit {
    leader: String,
    worker_ids: Vec<String>,
}

fn read_audit(path: &Path, identifier: &str) -> Result<Audit, Box<dyn std::error::Error>> {
    let contents = std::fs::read_to_string(path)?;
    let lines = contents.lines().collect::<Vec<_>>();
    assert_eq!(lines.first(), Some(&identifier));
    assert_eq!(lines.get(2), Some(&"active"));
    let worker_ids = lines[3..]
        .iter()
        .map(|payload| {
            payload
                .strip_prefix(identifier)
                .and_then(|payload| payload.strip_prefix(':'))
                .expect("payload must retain its extension identity")
                .to_string()
        })
        .collect();

    Ok(Audit { leader: lines[1].to_string(), worker_ids })
}