//! 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)
}
/// 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 `