//! The `emacs --batch` ground-truth oracle (spec §5, Phase 0). //! //! Every other test in this suite checks orgo against orgo: a snapshot says our //! output has not *changed*, never that it is *right*. Those two questions are different, //! and only one of them matters to someone whose site is currently published by Emacs. //! This file answers the second by exporting the same fixture with org's own HTML //! exporter — the exporter weblorg wraps to publish the target corpus — and diffing the //! two. //! //! **What is compared.** Byte equality is not a useful goal: org wraps every section in //! `outline-container` divs keyed by generated ids, and no amount of agreement on //! semantics would survive that. Both sides are reduced to a *semantic skeleton* — the //! sequence of element opens, closes, and text runs, with `
`s and all attributes //! except `href`/`src` dropped, whitespace collapsed, and entities decoded. What remains //! is the question worth asking: does org think this is a `

`, and do we? //! //! **What the result means.** These tests do not assert agreement — they *snapshot the //! disagreement*. A divergence report that is checked in and reviewed is worth more than //! a red test nobody can act on, and it makes any new divergence show up as a diff in //! code review. A few invariants that must never break are asserted outright. //! //! The suite skips cleanly when Emacs is absent, so it never blocks a machine or CI //! runner that has no Emacs. use std::process::Command; use camino::Utf8PathBuf; use orgo::parser::parse; use orgo::render::{render, Html, SyntectHighlighter}; use orgo::resolve::ResolvedDoc; use orgo::skeleton::{decode_entities, skeleton}; fn manifest_dir() -> Utf8PathBuf { Utf8PathBuf::from(env!("CARGO_MANIFEST_DIR")) } /// Is a usable Emacs on PATH? The oracle is a development instrument, not a build /// dependency, so its absence skips rather than fails. fn emacs_available() -> bool { Command::new("emacs") .arg("--version") .output() .map(|o| o.status.success()) .unwrap_or(false) } /// Emacs and org, as the `+` side of every report below. The ground truth moves: org /// 9.8 began wrapping source blocks in `

`, which orgo already did, so three
/// of these snapshots gained several points without orgo changing. Recorded so that
/// shows in the diff as what it is rather than reading as a regression.
fn oracle_version() -> &'static str {
    static VERSION: std::sync::OnceLock = std::sync::OnceLock::new();
    VERSION.get_or_init(|| {
        let output = Command::new("emacs")
            .args([
                "-Q",
                "--batch",
                "--eval",
                "(progn (require 'org) \
                 (princ (format \"emacs %s, org %s\" emacs-version (org-version))))",
            ])
            .output()
            .expect("run emacs");
        String::from_utf8_lossy(&output.stdout).trim().to_string()
    })
}

/// Export a fixture with org's own HTML exporter.
fn org_export(fixture: &str) -> String {
    let root = manifest_dir();
    let output = Command::new("emacs")
        .args(["-Q", "--batch", "-l"])
        .arg(root.join("tests/oracle.el"))
        .env("ORG_ORACLE_INPUT", root.join("fixtures").join(fixture))
        .current_dir(&root)
        .output()
        .expect("run emacs");
    assert!(
        output.status.success(),
        "emacs export of {fixture} failed:\n{}",
        String::from_utf8_lossy(&output.stderr)
    );
    String::from_utf8(output.stdout).expect("emacs emits UTF-8")
}

/// Render a fixture with orgo.
fn our_export(fixture: &str) -> String {
    let path = manifest_dir().join("fixtures").join(fixture);
    let source = std::fs::read_to_string(&path).expect("read fixture");
    let document = parse(Utf8PathBuf::from(fixture).as_path(), &source).expect("parse");
    let Html(html) = render(&ResolvedDoc { document }, &SyntectHighlighter::new());
    html
}

// ---------------------------------------------------------------------------
// Divergence report
// ---------------------------------------------------------------------------

/// One divergence orgo makes on purpose, so the report can separate "we chose this"
/// from "we got this wrong".
///
/// Without this split the agreement percentage is noise: the timestamps fixture sat at
/// 40% while being entirely correct, because org writes `<2024-01-15 Mon>` as text and
/// orgo writes a `