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

nordisk/Skidbladnir

tests/live_tests_are_ignored.rs

//! **A test that needs the caller's session bus, or writes under the caller's real
//! `~/.config`, must be `#[ignore]`d and must FAIL without its bus (stinky #23).**
//!
//! Two lib tests used `let Ok(sd) = connect_scoped(Scope::User) else { return }`, so
//! a plain `cargo test` in CI reported them green without running anything, and on a
//! developer's machine they wrote units into the real `~/.config/systemd/user`. The
//! live tests in `tests/*_live.rs` already follow the rule: `#[ignore]`, run with
//! `--ignored`, and `.expect(...)` the bus.
//!
//! This guard reads every `#[test]` in `src/` and `tests/` and refuses one that
//! touches the user bus or the real home without `#[ignore]`, and one that quietly
//! returns when the bus is missing.

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

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

/// `(attributes, body)` for every `#[test]` fn in `src`: the attribute lines
/// directly above it, and its text up to the next `#[test]`.
fn tests_in(src: &str) -> Vec<(String, String)> {
    let parts: Vec<&str> = src.split("#[test]").collect();
    let mut out = Vec::new();
    for i in 1..parts.len() {
        let attrs_before: String = parts[i - 1]
            .lines()
            .rev()
            .take_while(|l| l.trim_start().starts_with("#[") || l.trim_start().starts_with("///"))
            .collect::<Vec<_>>()
            .join("\n");
        let body = parts[i];
        let head_end = body.find("fn ").unwrap_or(0);
        out.push((format!("{attrs_before}\n{}", &body[..head_end]), body.to_string()));
    }
    out
}

/// Markers of a live test: dialling the caller's user bus, or joining the unit dir
/// onto a real home (a pure test spells the whole fake path instead).
const LIVE: [&str; 2] = ["connect_scoped(Scope::User)", ".join(\".config/systemd/user\")"];
const THIS_FILE: &str = "live_tests_are_ignored.rs";

#[test]
fn live_user_bus_tests_are_ignored_and_fail_without_a_bus() {
    let root = Path::new(env!("CARGO_MANIFEST_DIR"));
    let mut files = Vec::new();
    rust_files(&root.join("src"), &mut files);
    rust_files(&root.join("tests"), &mut files);
    let mut live = 0;
    let mut bad = Vec::new();
    for f in files.iter().filter(|f| !f.ends_with(THIS_FILE)) {
        let text = std::fs::read_to_string(f).unwrap();
        for (attrs, body) in tests_in(&text) {
            if !LIVE.iter().any(|m| body.contains(m)) {
                continue;
            }
            live += 1;
            let name = body
                .split("fn ")
                .nth(1)
                .and_then(|r| r.split('(').next())
                .unwrap_or("?")
                .to_string();
            if !attrs.contains("#[ignore") {
                bad.push(format!("{}: `{name}` is not #[ignore]", f.display()));
            }
            if body.contains("connect_scoped(Scope::User) else") {
                bad.push(format!("{}: `{name}` returns green when the bus is missing", f.display()));
            }
        }
    }
    assert!(live > 0, "found no live test at all; the scan is looking at nothing");
    assert!(bad.is_empty(), "{}", bad.join("\n"));
}