windjammer 0.48.0

A simple language inspired by Go, Ruby, and Elixir that transpiles to Rust - 80% of Rust's power with 20% of the complexity
Documentation
//! Auto-generate `tests/lib.rs` and wire it into the crate root for Rust tests that use `crate::`.

use anyhow::{Context, Result};
use std::fs;
use std::path::{Path, PathBuf};

const LIB_RS_MARKER_BEGIN: &str = "// WINDJAMMER_AUTO_RUST_TESTS_BEGIN";
const LIB_RS_MARKER_END: &str = "// WINDJAMMER_AUTO_RUST_TESTS_END";

const GENERATED_HEADER: &str = "// Auto-generated by Windjammer — do not edit manually.\n\
// Rust tests in `tests/*.rs` are discovered automatically (no manual `tests/lib.rs` registration).\n\n";

/// Resolve project root: prefer directory containing `tests/` and `Cargo.toml` or `lib.rs`.
pub fn find_project_root_with_tests(start: &Path) -> Option<PathBuf> {
    let mut candidates = Vec::new();
    if start.is_dir() {
        candidates.push(start.to_path_buf());
        if let Some(parent) = start.parent() {
            candidates.push(parent.to_path_buf());
        }
    } else if let Some(parent) = start.parent() {
        candidates.push(parent.to_path_buf());
        if let Some(grand) = parent.parent() {
            candidates.push(grand.to_path_buf());
        }
    }
    candidates
        .into_iter()
        .find(|root| root.join("tests").is_dir())
}

fn is_excluded_test_root_file(name: &str) -> bool {
    name == "lib.rs" || name == "mod.rs" || name == "all.rs"
}

/// True when this project uses the consolidated `tests/all.rs` binary (build.rs auto-discovery).
/// Those crates must not also generate `tests/lib.rs` — it is redundant and confusing.
fn uses_consolidated_test_binary(project_root: &Path) -> bool {
    project_root.join("tests").join("all.rs").exists()
}

fn is_helper_module(stem: &str, contents: &str) -> bool {
    if stem.ends_with("_test") || stem.starts_with("test_") || stem.ends_with("_tests") {
        return false;
    }
    // Heuristic: helper modules (e.g. gpu_layout_assertions) expose items for other tests.
    !contents.contains("#[test]")
}

/// Scan `tests/*.rs` and write `tests/lib.rs`.
pub fn generate_tests_lib_rs(project_root: &Path) -> Result<Option<PathBuf>> {
    if uses_consolidated_test_binary(project_root) {
        return Ok(None);
    }
    let tests_dir = project_root.join("tests");
    if !tests_dir.is_dir() {
        return Ok(None);
    }

    let mut modules: Vec<(String, bool)> = Vec::new();
    for entry in fs::read_dir(&tests_dir)? {
        let entry = entry?;
        let path = entry.path();
        if !path.is_file() {
            continue;
        }
        let Some(name) = path.file_name().and_then(|n| n.to_str()) else {
            continue;
        };
        if !name.ends_with(".rs") || is_excluded_test_root_file(name) {
            continue;
        }
        let stem = name.strip_suffix(".rs").unwrap_or(name);
        let contents =
            fs::read_to_string(&path).with_context(|| format!("read {}", path.display()))?;
        let helper = is_helper_module(stem, &contents);
        modules.push((stem.to_string(), helper));
    }

    if modules.is_empty() {
        return Ok(None);
    }

    modules.sort_by(|a, b| a.0.cmp(&b.0));

    let mut out = String::from(GENERATED_HEADER);
    for (stem, helper) in &modules {
        if *helper {
            out.push_str(&format!("pub mod {stem};\n"));
        } else {
            out.push_str("#[cfg(test)]\n");
            out.push_str(&format!("pub mod {stem};\n"));
        }
    }

    let helpers: Vec<_> = modules.iter().filter(|(_, h)| *h).collect();
    if !helpers.is_empty() {
        out.push_str("\n// Helper re-exports for test modules\n");
        for (stem, _) in helpers {
            out.push_str(&format!("pub use {stem}::*;\n"));
        }
    }

    let lib_path = tests_dir.join("lib.rs");
    fs::write(&lib_path, out).with_context(|| format!("write {}", lib_path.display()))?;

    // mod.rs duplicate causes a stray integration-test crate named `mod`; remove if auto-generated.
    let mod_rs = tests_dir.join("mod.rs");
    if mod_rs.exists() {
        if let Ok(text) = fs::read_to_string(&mod_rs) {
            if text.contains("Auto-generated mod.rs by Windjammer") {
                let _ = fs::remove_file(&mod_rs);
            }
        }
    }

    Ok(Some(lib_path))
}

/// Append or refresh `#[path = "tests/lib.rs"]` hook in the crate root `lib.rs`.
pub fn wire_tests_lib_into_crate_root(project_root: &Path) -> Result<()> {
    let tests_lib = project_root.join("tests").join("lib.rs");
    if !tests_lib.exists() {
        return Ok(());
    }

    let lib_rs_path = project_root.join("lib.rs");
    if !lib_rs_path.exists() {
        return Ok(());
    }

    let hook = format!(
        "{LIB_RS_MARKER_BEGIN}\n\
         #[cfg(test)]\n\
         #[path = \"tests/lib.rs\"]\n\
         mod windjammer_rust_integration_tests;\n\
         {LIB_RS_MARKER_END}\n"
    );

    let existing = fs::read_to_string(&lib_rs_path)?;
    let updated = if existing.contains(LIB_RS_MARKER_BEGIN) {
        replace_between_markers(&existing, LIB_RS_MARKER_BEGIN, LIB_RS_MARKER_END, &hook)
    } else {
        format!("{existing}\n{hook}")
    };

    if updated != existing {
        fs::write(&lib_rs_path, updated)?;
    }

    ensure_autotests_disabled(&project_root.join("Cargo.toml"))?;
    Ok(())
}

fn replace_between_markers(content: &str, begin: &str, end: &str, replacement: &str) -> String {
    if let (Some(start), Some(end_pos)) = (content.find(begin), content.find(end)) {
        if end_pos >= start {
            let after = end_pos + end.len();
            let mut out = String::new();
            out.push_str(&content[..start]);
            out.push_str(replacement);
            if after < content.len() {
                out.push_str(&content[after..]);
            }
            return out;
        }
    }
    content.to_string()
}

/// Prevent duplicate test binaries: `tests/*.rs` are compiled via the lib hook, not as standalone integration crates.
fn ensure_autotests_disabled(cargo_toml_path: &Path) -> Result<()> {
    if !cargo_toml_path.exists() {
        return Ok(());
    }
    let content = fs::read_to_string(cargo_toml_path)?;
    if content.contains("autotests") {
        return Ok(());
    }
    let updated = if let Some(idx) = content.find("[package]") {
        let insert_at = content[idx..]
            .find('\n')
            .map(|off| idx + off + 1)
            .unwrap_or(content.len());
        format!(
            "{}{}{}",
            &content[..insert_at],
            "autotests = false\n",
            &content[insert_at..]
        )
    } else {
        format!("{content}\n[package]\nautotests = false\n")
    };
    fs::write(cargo_toml_path, updated)?;
    Ok(())
}

/// Full sync: generate `tests/lib.rs` + wire crate root + disable autotests.
pub fn sync_rust_integration_tests(project_root: &Path) -> Result<()> {
    if uses_consolidated_test_binary(project_root) {
        return Ok(());
    }
    if generate_tests_lib_rs(project_root)?.is_some() {
        wire_tests_lib_into_crate_root(project_root)?;
    }
    Ok(())
}

#[cfg(test)]
mod tests {
    use super::*;
    use std::fs;

    #[test]
    fn skips_lib_rs_when_consolidated_all_binary_present() {
        let tmp = tempfile::tempdir().unwrap();
        let root = tmp.path();
        fs::create_dir_all(root.join("tests")).unwrap();
        fs::write(root.join("tests/all.rs"), "// consolidated\n").unwrap();
        fs::write(
            root.join("tests/feature_test.rs"),
            "#[test]\nfn t() { assert!(true); }\n",
        )
        .unwrap();

        assert!(generate_tests_lib_rs(root).unwrap().is_none());
        assert!(!root.join("tests/lib.rs").exists());
    }

    #[test]
    fn generates_lib_rs_from_test_files() {
        let tmp = tempfile::tempdir().unwrap();
        let root = tmp.path();
        fs::create_dir_all(root.join("tests")).unwrap();
        fs::write(
            root.join("tests/shader_file_registry_test.rs"),
            "#[test]\nfn t() { assert!(true); }\n",
        )
        .unwrap();
        fs::write(
            root.join("tests/gpu_layout_assertions.rs"),
            "pub fn helper() -> i32 { 1 }\n",
        )
        .unwrap();
        fs::write(root.join("lib.rs"), "pub fn api() {}\n").unwrap();
        fs::write(root.join("Cargo.toml"), "[package]\nname = \"demo\"\n").unwrap();

        generate_tests_lib_rs(root).unwrap();
        wire_tests_lib_into_crate_root(root).unwrap();

        let lib = fs::read_to_string(root.join("tests/lib.rs")).unwrap();
        assert!(lib.contains("pub mod shader_file_registry_test;"));
        assert!(lib.contains("pub mod gpu_layout_assertions;"));
        assert!(lib.contains("pub use gpu_layout_assertions::*;"));

        let crate_lib = fs::read_to_string(root.join("lib.rs")).unwrap();
        assert!(crate_lib.contains("WINDJAMMER_AUTO_RUST_TESTS_BEGIN"));
        assert!(crate_lib.contains("tests/lib.rs"));

        let cargo = fs::read_to_string(root.join("Cargo.toml")).unwrap();
        assert!(cargo.contains("autotests = false"));
    }
}