Skip to main content

test_lang/
lib.rs

1//! # test_lang
2//!
3//! A snapshot test harness for language front-ends: give it source, run that
4//! source through a stage, and assert the rendered result — the token stream,
5//! the syntax tree, or the diagnostics — against an expected block of text.
6//!
7//! ## Why a snapshot harness
8//!
9//! The output of a lexer, parser, or diagnostics renderer is a *shape*: a list
10//! of tokens, a tree of nodes, a set of caret-annotated errors. Asserting that
11//! shape field by field is verbose and brittle — one added node and a dozen
12//! index-based assertions shift. A snapshot test instead captures the whole
13//! rendered shape as text and compares it against a known-good block. When the
14//! output changes, you get a line-level diff pointing at exactly what moved, and
15//! accepting the new behavior is a copy-paste.
16//!
17//! This crate owns no grammar and depends on no other front-end crate. It works
18//! on anything that can render itself to text — a
19//! [`Display`](core::fmt::Display) value, a [`Debug`](core::fmt::Debug) tree, or
20//! an iterator of displayable items — so the same harness serves a hand-written
21//! lexer, a generated parser, or a diagnostics layer without coupling to any of
22//! them.
23//!
24//! ## Stability
25//!
26//! As of 1.0.0 the public surface — the four exported types and their inherent
27//! methods and trait impls — is stable and frozen. It will not change in a
28//! breaking way before a 2.0. New capability arrives additively in 1.x releases.
29//!
30//! ## The two types
31//!
32//! - [`Snapshot`] — a normalized, comparable rendering of some output. Build one
33//!   with [`Snapshot::display`], [`Snapshot::debug`], [`Snapshot::per_line`], or
34//!   [`Snapshot::new`], then call [`Snapshot::check`].
35//! - [`Diff`] — the line-level difference reported when a check fails, rendered
36//!   as a unified `-expected`/`+actual` diff. A failed check hands it back inside
37//!   a [`Mismatch`].
38//!
39//! ## Example
40//!
41//! Snapshot a token stream and assert it:
42//!
43//! ```
44//! use test_lang::Snapshot;
45//!
46//! // Whatever your lexer produces — here, a stand-in that yields display strings.
47//! fn lex(source: &str) -> Vec<String> {
48//!     source.split_whitespace().map(str::to_string).collect()
49//! }
50//!
51//! let tokens = lex("let x = 1");
52//! let snapshot = Snapshot::per_line(&tokens);
53//!
54//! snapshot.check("let\nx\n=\n1").expect("token stream matches");
55//! ```
56//!
57//! When the output drifts, the returned [`Mismatch`] shows precisely what
58//! changed:
59//!
60//! ```
61//! use test_lang::Snapshot;
62//!
63//! let snapshot = Snapshot::per_line(["let", "y", "=", "1"]);
64//! let err = snapshot.check("let\nx\n=\n1").unwrap_err();
65//!
66//! // `-x` was expected; `+y` was produced in its place.
67//! assert!(err.to_string().contains("-x"));
68//! assert!(err.to_string().contains("+y"));
69//! ```
70//!
71//! ## `no_std`
72//!
73//! The crate is `no_std` + `alloc` when the default `std` feature is disabled.
74//! Every type — including the [`Error`](core::error::Error) impl on
75//! [`Mismatch`], which is anchored to `core::error::Error` in both modes — is
76//! available with or without `std`.
77
78#![cfg_attr(not(feature = "std"), no_std)]
79#![cfg_attr(docsrs, feature(doc_cfg))]
80#![deny(missing_docs)]
81#![forbid(unsafe_code)]
82#![deny(clippy::unwrap_used)]
83#![deny(clippy::expect_used)]
84
85extern crate alloc;
86
87mod diff;
88mod snapshot;
89
90pub use diff::{Change, Diff};
91pub use snapshot::{Mismatch, Snapshot};