alef 0.115.5

Opinionated polyglot binding generator for Rust libraries
Documentation
#![allow(clippy::print_stderr)]

use super::*;
use crate::backends::dart::DartBackend;
use crate::core::backend::Backend;
use crate::core::config::{NewAlefConfig, ResolvedCrateConfig};
use std::path::Path;

#[path = "native_pruning.rs"]
mod pruning;

fn task(root: &Path, name: &str, arguments: &[&str]) -> std::process::Output {
    let output = std::process::Command::new("task")
        .arg(name)
        .args(arguments)
        .current_dir(root)
        .env("RUSTC_WRAPPER", "")
        .env("RUSTC_WORKSPACE_WRAPPER", "")
        .env("CARGO_TARGET_DIR", root.join("frb-target"))
        .output()
        .expect("execute Dart error display fixture task");
    std::fs::write(
        root.join(format!("proof-{name}.log")),
        [output.stdout.as_slice(), output.stderr.as_slice()].concat(),
    )
    .unwrap();
    output
}

fn successful(root: &Path, name: &str, arguments: &[&str]) -> std::process::Output {
    let output = task(root, name, arguments);
    assert!(
        output.status.success(),
        "{name}:\n{}\n{}",
        String::from_utf8_lossy(&output.stdout),
        String::from_utf8_lossy(&output.stderr)
    );
    output
}

fn prepare(root: &Path) -> ResolvedCrateConfig {
    let core = include_str!("../../../../tests/fixtures/dart_error_display/core.rs");
    std::fs::create_dir_all(root.join("src")).unwrap();
    std::fs::write(root.join("src/lib.rs"), core).unwrap();
    std::fs::write(root.join("Cargo.toml"), "[package]\nname = \"display-sample\"\nversion = \"1.0.0\"\nedition = \"2024\"\n[dependencies]\nthiserror = \"2\"\n").unwrap();
    let api = crate::extract::extractor::extract(&[&root.join("src/lib.rs")], "display-sample", "1.0.0", None).unwrap();
    assert_eq!(api.errors.len(), 2);
    assert_eq!(api.errors[0].variants.len(), 7);
    assert_eq!(
        records(&api.errors.iter().collect::<Vec<_>>()).len(),
        6,
        "{:#?}",
        api.errors
    );
    let config: NewAlefConfig = toml::from_str(
        "[workspace]\nlanguages = [\"dart\"]\n[[crates]]\nname = \"display-sample\"\nsources = [\"src/lib.rs\"]\n",
    )
    .unwrap();
    let config = config.resolve().unwrap().remove(0);
    let files = DartBackend.generate_bindings(&api, &config).unwrap();
    assert!(
        files.iter().any(|file| file.content.contains("alef-error-display:")),
        "generated facade missing metadata"
    );
    let scaffolds = crate::scaffold::scaffold(&api, &config, &[crate::core::config::Language::Dart]).unwrap();
    for file in files.into_iter().chain(scaffolds) {
        let path = root.join(file.path);
        std::fs::create_dir_all(path.parent().unwrap()).unwrap();
        std::fs::write(path, file.content).unwrap();
    }
    std::fs::write(
        root.join("Taskfile.yml"),
        include_str!("../../../../tests/fixtures/dart_error_display/Taskfile.yml"),
    )
    .unwrap();
    std::fs::write(
        root.join("packages/dart/check.dart"),
        include_str!("../../../../tests/fixtures/dart_error_display/check.dart"),
    )
    .unwrap();
    config
}

#[test]
#[ignore = "requires Dart, flutter_rust_bridge_codegen, Freezed, and a native Rust toolchain"]
fn native_error_display_survives_actual_frb_freezed_and_rust_errors() {
    let directory = tempfile::Builder::new().prefix("alef-dart-display-").tempdir().unwrap();
    let preserved = directory.keep();
    let root = preserved.as_path();
    eprintln!("dart_error_display_proof={}", root.display());
    let config = prepare(root);
    successful(root, "setup", &[]);
    pruning::verify(root, &config);
    successful(root, "generate", &[]);
    successful(root, "build", &[]);
    let binary = root.join("native-target/debug").join(format!(
        "{}display_sample_dart{}",
        std::env::consts::DLL_PREFIX,
        std::env::consts::DLL_SUFFIX
    ));
    assert!(binary.metadata().unwrap().len() > 0);
    let library = binary.to_str().unwrap();
    rejected(root, "test", &["--", library]);
    repair(root, &config);
    successful(root, "analyze", &[]);
    let positive = successful(root, "test", &["--", library]);
    assert!(String::from_utf8_lossy(&positive.stdout).contains("error_display_cases=9"));
    rejected(root, "wrong-expectation", &["--", library]);
    std::fs::write(
        root.join("proof-summary.json"),
        r#"{"metadata_records":6,"runtime_cases":9,"negative_control":"exact Display mismatch","idempotent":true}"#,
    )
    .unwrap();
}

fn repair(root: &Path, config: &ResolvedCrateConfig) {
    let facade = root.join(crate::backends::dart::frb_rust_facade_paths(config).unwrap().0);
    let facade = std::fs::read_to_string(facade).unwrap();
    assert_eq!(read_records(&facade).unwrap().len(), 6);
    let bridge = root.join(crate::backends::dart::frb_dart_bridge_path(config).unwrap());
    let source = std::fs::read_to_string(&bridge).unwrap();
    let path = bridge.with_file_name("lib.freezed.dart");
    let before = std::fs::read_to_string(&path).unwrap();
    assert!(before.len() > 1000);
    let repaired = rewrite(&before, &source, &facade).unwrap();
    assert_ne!(repaired, before);
    assert_eq!(rewrite(&repaired, &source, &facade).unwrap(), repaired);
    assert_eq!(
        std::fs::read_to_string(&bridge).unwrap(),
        source,
        "factory constructors must remain byte-identical"
    );
    std::fs::write(path, repaired).unwrap();
}

fn rejected(root: &Path, name: &str, arguments: &[&str]) {
    let output = task(root, name, arguments);
    assert!(!output.status.success(), "incorrect Display assertion must fail");
    assert!(
        String::from_utf8_lossy(&output.stderr).contains("Bad state: expected display 0:"),
        "{}",
        String::from_utf8_lossy(&output.stderr)
    );
}