Your browser sucks, JavaScript disabled! Gunnar's console needs JavaScript and WebAssembly. Git over SSH on port 2222 does not.

nordisk/holger

holger-estate-front/tests/the_front_compiles_only_its_own_files.rs

//! **The front compiles only its own files** (A28 L12): a `#[path]` to another crate's source
//! (holger-server's `server/cli/src/dev_narrate.rs` was one) is a dependency the manifest does
//! not name: its features, its tests and its deps are the other crate's, and a move there breaks
//! this build. Shared code is a crate (`holger-dev-narrate`), named in Cargo.toml.

use std::path::{Path, PathBuf};

fn rust_files(dir: &Path, out: &mut Vec<PathBuf>) {
    for e in std::fs::read_dir(dir).unwrap_or_else(|e| panic!("{}: {e}", dir.display())) {
        let p = e.unwrap().path();
        if p.is_dir() {
            rust_files(&p, out);
        } else if p.extension().is_some_and(|x| x == "rs") {
            out.push(p);
        }
    }
}

/// Pure: every `#[path = "…"]` in `text` whose target leaves `crate_dir` (resolved from `file`'s
/// directory), as `(line, target)`.
fn escaping_paths(crate_dir: &Path, file: &Path, text: &str) -> Vec<(usize, String)> {
    let base = file.parent().unwrap();
    let crate_dir = normalize(crate_dir);
    text.lines()
        .enumerate()
        .filter_map(|(n, l)| {
            let l = l.trim_start();
            let rest = l
                .strip_prefix("#[path")?
                .trim_start()
                .strip_prefix('=')?
                .trim_start()
                .strip_prefix('"')?;
            let target = &rest[..rest.find('"')?];
            (!normalize(&base.join(target)).starts_with(&crate_dir))
                .then(|| (n + 1, target.to_string()))
        })
        .collect()
}

fn normalize(p: &Path) -> PathBuf {
    let mut out = PathBuf::new();
    for c in p.components() {
        match c {
            std::path::Component::ParentDir => {
                out.pop();
            }
            std::path::Component::CurDir => {}
            c => out.push(c),
        }
    }
    out
}

#[test]
fn no_source_of_another_crate_is_compiled_in_by_path() {
    let dir = Path::new(env!("CARGO_MANIFEST_DIR"));
    let mut files = Vec::new();
    for sub in ["src", "tests"] {
        rust_files(&dir.join(sub), &mut files);
    }
    assert!(
        files.len() > 5,
        "the scan read the front's sources: {files:?}"
    );
    let found: Vec<String> = files
        .iter()
        .flat_map(|f| {
            let text = std::fs::read_to_string(f).unwrap();
            escaping_paths(dir, f, &text)
                .into_iter()
                .map(move |(n, t)| format!("{}:{n} #[path = {t:?}]", f.display()))
        })
        .collect();
    assert!(
        found.is_empty(),
        "the front compiles another crate's file; depend on the crate instead:\n{}",
        found.join("\n")
    );
}

#[test]
fn a_path_inside_the_crate_is_fine_and_one_outside_is_found() {
    let dir = Path::new("/r/holger-estate-front");
    let f = dir.join("src/main.rs");
    assert!(escaping_paths(dir, &f, "#[path = \"inner/x.rs\"]\nmod x;").is_empty());
    assert_eq!(
        escaping_paths(
            dir,
            &f,
            "use a;\n#[path = \"../../server/cli/src/dev_narrate.rs\"]\nmod dev_narrate;"
        ),
        vec![(2, "../../server/cli/src/dev_narrate.rs".to_string())]
    );
}