obj-db 1.1.2

Embedded document database. Stable file format, full ACID, single-file portability.
Documentation
//! M14 issue #117: assert the README's quickstart `rust` block
//! actually compiles against the live `obj` API.
//!
//! Mechanism:
//!
//! 1. `include_str!("../../../README.md")` pulls the workspace-root
//!    README into the test binary at compile time.
//! 2. A tiny parser finds the **first** triple-back-tick `rust`
//!    fenced block and returns the body verbatim.
//! 3. The body is written into a `*.rs` file in a tempdir alongside
//!    a hand-curated `Cargo.toml` that depends on the same `obj`,
//!    `serde`, and `tempfile` paths the rest of the test suite uses.
//! 4. `cargo build` against the generated crate must exit `0`. Any
//!    breakage in the README snippet — wrong type name, removed
//!    method, missing import — fails the test long before docs
//!    review notices.
//!
//! Power-of-ten Rule 2: the parser is a single linear pass with a
//! bounded byte budget; Rule 7: every fallible step propagates `?`
//! into the test's `Result`.
//!
//! The test is `cfg(unix)` because it shells out to `cargo` and the
//! Windows CI path runs the broader integration suite separately.

// File-level cfg: on Windows the entire test compiles to nothing,
// so imports / constants / helpers don't trip unused-* lints.
#![cfg(unix)]
// Tests use `.expect("…")` + assertion failures to surface
// mismatches; the README quickstart compile-test is not a library
// API so it carries no panic / error documentation contract.
#![allow(clippy::missing_panics_doc, clippy::missing_errors_doc)]

use std::fs;
use std::path::PathBuf;
use std::process::Command;

const README: &str = include_str!("../../../README.md");

/// Find the first triple-backtick `rust` fenced block in the README
/// and return its body. Returns `None` if no such block exists.
fn first_rust_block(src: &str) -> Option<String> {
    // The fence opens at the start of a line. We scan line by line
    // so that a literal `\`\`\`rust` inside a paragraph cannot be
    // mistaken for a fence.
    let mut lines = src.lines();
    let mut body = String::new();
    let mut inside = false;
    for line in &mut lines {
        if !inside {
            // Treat `\`\`\`rust`, `\`\`\`rust,no_run`, etc. as opening fences.
            let trimmed = line.trim_start();
            if let Some(rest) = trimmed.strip_prefix("```") {
                let lang = rest.split(|c: char| c.is_whitespace() || c == ',').next()?;
                if lang == "rust" {
                    inside = true;
                }
            }
        } else if line.trim_start().starts_with("```") {
            return Some(body);
        } else {
            body.push_str(line);
            body.push('\n');
        }
    }
    None
}

/// The first fenced rust block is the quickstart. We check it
/// compiles by writing it to a fresh tempdir crate and invoking
/// `cargo build`.
#[test]
#[cfg(unix)]
fn readme_quickstart_compiles() {
    let snippet = first_rust_block(README).expect("README must contain a ```rust block");
    assert!(
        snippet.contains("fn main"),
        "quickstart snippet must declare its own fn main; got: {snippet}"
    );

    // Build a sibling crate inside a tempdir that depends on the
    // in-tree `obj` crate. We avoid a workspace-aware path by
    // pointing `obj`, `serde`, `tempfile` at the same crates the
    // workspace already resolves; an external crate would need to
    // download from crates.io, which we do not want a unit test
    // doing.
    let tmp = tempfile::tempdir().expect("tempdir");
    let crate_dir: PathBuf = tmp.path().to_path_buf();
    fs::create_dir_all(crate_dir.join("src")).expect("mkdir src");

    let obj_crate_path = workspace_relative("crates/obj");
    let manifest = format!(
        r#"[package]
name = "obj_readme_compile_test"
version = "0.0.0"
edition = "2021"
publish = false

[dependencies]
obj-db = {{ path = "{obj}" }}
serde = {{ version = "1", features = ["derive"] }}
tempfile = "3"

[workspace]
"#,
        obj = obj_crate_path.display(),
    );
    fs::write(crate_dir.join("Cargo.toml"), manifest).expect("write manifest");
    fs::write(crate_dir.join("src").join("main.rs"), &snippet).expect("write snippet");

    let cargo = std::env::var_os("CARGO").unwrap_or_else(|| "cargo".into());
    let status = Command::new(&cargo)
        .arg("build")
        .arg("--quiet")
        .current_dir(&crate_dir)
        .env_remove("RUSTFLAGS") // workspace's `-D warnings` is paid for in the main build
        .status()
        .expect("invoke cargo build");
    assert!(
        status.success(),
        "README quickstart failed to compile; snippet was:\n---\n{snippet}\n---"
    );
}

/// Walk up from `CARGO_MANIFEST_DIR` until we hit the workspace
/// root (the directory that contains `Cargo.toml` with
/// `[workspace]`), then resolve `child` against it.
fn workspace_relative(child: &str) -> PathBuf {
    let manifest_dir = PathBuf::from(env!("CARGO_MANIFEST_DIR"));
    let mut cur: PathBuf = manifest_dir;
    // Bounded depth (Rule 2): at most 8 ancestors. A path deeper
    // than that has bigger problems than this test.
    for _ in 0..8 {
        let candidate = cur.join("Cargo.toml");
        if let Ok(text) = fs::read_to_string(&candidate) {
            if text.contains("[workspace]") {
                return cur.join(child);
            }
        }
        if !cur.pop() {
            break;
        }
    }
    panic!(
        "could not locate workspace root from {}",
        env!("CARGO_MANIFEST_DIR")
    );
}