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"));
}