Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
46 changes: 46 additions & 0 deletions docs/developers-guide.md
Original file line number Diff line number Diff line change
Expand Up @@ -659,6 +659,52 @@ Table: Instrumented functions and their logging levels and fields.
| `find_footnote_end` | trace | `idx` (in), `skip(text)`, return value (out) |
| `parse_rows` | trace, debug | `skip(trimmed)`; `row_index`, `cell_count`, and `error_category` events |

### Tracing-event snapshot tests

The stable structured fields above are pinned by `insta` snapshots so that
accidental changes to a tracing event's level, target, message, or field set
are caught in review. These tests live next to the instrumented code:

- `src/wrap/inline/fragment_tracing_snapshots.rs` – `fragment classified`.
- `src/wrap/inline/span_helper_tracing_tests.rs` – date-span events.
- `src/wrap/tokenize/parsing_tracing_snapshots.rs` – link, image, and footnote
events.

Each is wired into its owning module as a `#[cfg(test)]` `#[path = "…"]`
submodule so the snapshot test sits beside the code it pins while keeping the
production module within the 400-line limit. The `.snap` fixtures live under the
neighbouring `snapshots/` directory.

A test captures events with `tracing-test`'s `#[traced_test]`, then normalizes
the captured lines through the shared
`crate::wrap::tracing_snapshot_support::normalise_event_lines` helper before
asserting the snapshot.

#### `normalise_event_lines` helper

`tracing-test` prefixes every captured line with a volatile timestamp and span
context, which would make raw snapshots non-deterministic.
`normalise_event_lines(lines, message)` retains only the lines containing
`message`, strips the volatile prefix up to the event level (`TRACE`/`DEBUG`),
trims trailing whitespace, and joins the survivors. The level, target, message,
and structured fields are preserved verbatim, so the snapshot still fails if any
of those change.

Re-use policy for this helper:

- **Ownership.** Owned by the wrap module (`src/wrap/tracing_snapshot_support.rs`)
and gated behind `#[cfg(test)]`; it is `pub(crate)` test-support code, not part
of any public or runtime API.
- **Permitted call-sites.** Only tracing-event snapshot tests. Call it from
inside a `#[traced_test]` function through the injected `logs_assert` closure,
copying the normalized result into an owned buffer before asserting the
snapshot after the closure returns (see the modules listed above for the
canonical shape).
- **Composition.** Do not layer additional normalization on top; if a new event
needs different masking, extend the helper (and this section) rather than
post-processing its output at the call-site, so every snapshot shares one
normalization contract.

## Fences module

The `fences` module in [src/fences.rs](../src/fences.rs) is responsible for
Expand Down
2 changes: 2 additions & 0 deletions src/wrap.rs
Original file line number Diff line number Diff line change
Expand Up @@ -20,6 +20,8 @@ mod inline;
mod link_reference;
mod paragraph;
mod tokenize;
#[cfg(test)]
pub(crate) mod tracing_snapshot_support;
use block::{BULLET_RE, FOOTNOTE_RE};
pub(crate) use block::{BlockKind, classify_block, leading_indent};
pub use blockquote::BlockquotePrefix;
Expand Down
3 changes: 3 additions & 0 deletions src/wrap/inline/fragment.rs
Original file line number Diff line number Diff line change
Expand Up @@ -395,3 +395,6 @@ mod proptests {
}
}
}
#[cfg(test)]
#[path = "fragment_tracing_snapshots.rs"]
mod fragment_tracing_snapshots;
30 changes: 30 additions & 0 deletions src/wrap/inline/fragment_tracing_snapshots.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,30 @@
//! Snapshot test for fragment-classification tracing events.

use std::cell::RefCell;

use tracing_test::traced_test;

use super::InlineFragment;
use crate::wrap::tracing_snapshot_support::normalise_event_lines;

#[traced_test]
#[test]
fn snapshots_fragment_classified_event() {
let captured = RefCell::new(String::new());
let fragment = InlineFragment::new("plain".to_string());
// Assert the observable classification the traced event mirrors; the
// snapshot below is supplementary evidence of the emitted event shape.
assert!(fragment.is_plain(), "a bare word must classify as Plain");
assert_eq!(fragment.text, "plain");
logs_assert(|lines| {
captured.replace(normalise_event_lines(lines, "fragment classified"));
(!captured.borrow().is_empty())
.then_some(())
.ok_or_else(|| "expected fragment classification event".to_string())
});
let event = captured.into_inner();

insta::with_settings!({prepend_module_to_snapshot => false}, {
insta::assert_snapshot!("fragment-classified-event", event);
});
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,5 @@
---
source: src/wrap/inline/span_helper_tracing_tests.rs
expression: event
---
TRACE snapshots_grouped_date_sequence_event: mdtablefix::wrap::inline: determine_token_span grouped date sequence start=0 end=5 width=18
5 changes: 5 additions & 0 deletions src/wrap/inline/snapshots/fragment-classified-event.snap
Original file line number Diff line number Diff line change
@@ -0,0 +1,5 @@
---
source: src/wrap/inline/fragment_tracing_snapshots.rs
expression: event
---
DEBUG snapshots_fragment_classified_event: mdtablefix::wrap::inline::fragment: fragment classified token=plain truncated=false kind=Plain
5 changes: 5 additions & 0 deletions src/wrap/inline/snapshots/matched-date-sequence-event.snap
Original file line number Diff line number Diff line change
@@ -0,0 +1,5 @@
---
source: src/wrap/inline/span_helper_tracing_tests.rs
expression: event
---
DEBUG snapshots_matched_date_sequence_event:date_token_span{start=0}:try_match_date_sequence{start=0}: mdtablefix::wrap::inline::span_helpers: matched date sequence start=0 end=5 pattern="ordinal_day_month_year"
74 changes: 69 additions & 5 deletions src/wrap/inline/span_helper_tracing_tests.rs
Original file line number Diff line number Diff line change
@@ -1,10 +1,12 @@
//! Traced-event tests for inline span helper instrumentation.

use std::cell::RefCell;

use rstest::{fixture, rstest};
use tracing_test::traced_test;

use super::{date_token_span, try_match_date_sequence};
use crate::wrap::inline::determine_token_span;
use crate::wrap::{inline::determine_token_span, tracing_snapshot_support::normalise_event_lines};

#[fixture]
fn date_tokens() -> Vec<String> {
Expand All @@ -30,21 +32,24 @@ fn colon_footnote_tokens() -> Vec<String> {
#[traced_test]
#[rstest]
fn try_match_date_sequence_emits_trace_event(date_tokens: Vec<String>) {
let _ = try_match_date_sequence(&date_tokens, 0);
// Observable result: all five date tokens form one sequence (exclusive end).
assert_eq!(try_match_date_sequence(&date_tokens, 0), Some(5));
assert!(logs_contain("try_match_date_sequence"));
}

#[traced_test]
#[rstest]
fn try_match_date_sequence_logs_matched_pattern(date_tokens: Vec<String>) {
let _ = try_match_date_sequence(&date_tokens, 0);
assert_eq!(try_match_date_sequence(&date_tokens, 0), Some(5));
assert!(logs_contain("ordinal_day_month_year"));
}

#[traced_test]
#[rstest]
fn date_token_span_emits_trace_event(date_tokens: Vec<String>) {
let _ = date_token_span(&date_tokens, 0);
// Observable result: the span covers all five tokens with display width 18
// ("25th December 2025"); no footnote coupling applies here.
assert_eq!(date_token_span(&date_tokens, 0), Some((5, 18)));
assert!(logs_contain("date_token_span"));
}

Expand Down Expand Up @@ -81,9 +86,68 @@ fn grouping_boundary_logs_declined_footnote_coupling(
.iter()
.map(|token| (*token).to_string())
.collect::<Vec<_>>();
let _ = determine_token_span(&tokens, 0);
let (end, width) = determine_token_span(&tokens, 0);
// Observable edge-case result: a declined coupling groups only the leading
// "word" span (end == 1, never 0), leaving the footnote outside the span.
assert_eq!(
end, 1,
"declined coupling must retain the leading word span"
);
let grouped = tokens[..end].join("");
assert_eq!(grouped, "word");
assert_eq!(width, 4);
assert!(
!grouped.contains("[^note]"),
"declined coupling must not group the footnote reference",
);
assert!(logs_contain(&format!(
"error_category=\"{error_category}\""
)));
assert!(!logs_contain("[^note]"));
}

#[traced_test]
#[rstest]
fn snapshots_grouped_date_sequence_event(date_tokens: Vec<String>) {
let captured = RefCell::new(String::new());
// Observable grouping result: the date tokens form one atomic span of
// width 18. The snapshot below supplements this behavioural assertion.
let (end, width) = determine_token_span(&date_tokens, 0);
assert_eq!(date_tokens[..end].join(""), "25th December 2025");
assert_eq!(width, 18);
logs_assert(|lines| {
captured.replace(normalise_event_lines(
lines,
"determine_token_span grouped date sequence",
));
(!captured.borrow().is_empty())
.then_some(())
.ok_or_else(|| "expected grouped date sequence event".to_string())
});
let event = captured.into_inner();

insta::with_settings!({prepend_module_to_snapshot => false}, {
insta::assert_snapshot!("determine-token-span-grouped-date-sequence-event", event);
});
}

#[traced_test]
#[rstest]
fn snapshots_matched_date_sequence_event(date_tokens: Vec<String>) {
let captured = RefCell::new(String::new());
// Observable grouping result backing the supplementary snapshot.
let (end, width) = determine_token_span(&date_tokens, 0);
assert_eq!(date_tokens[..end].join(""), "25th December 2025");
assert_eq!(width, 18);
logs_assert(|lines| {
captured.replace(normalise_event_lines(lines, "matched date sequence"));
(!captured.borrow().is_empty())
.then_some(())
.ok_or_else(|| "expected matched date sequence event".to_string())
});
let event = captured.into_inner();

insta::with_settings!({prepend_module_to_snapshot => false}, {
insta::assert_snapshot!("matched-date-sequence-event", event);
});
}
4 changes: 4 additions & 0 deletions src/wrap/tokenize/parsing.rs
Original file line number Diff line number Diff line change
Expand Up @@ -221,3 +221,7 @@ pub(super) fn handle_backtick_fence(text: &str, start_idx: usize) -> (String, us
#[cfg(test)]
#[path = "parsing_tests.rs"]
mod tests;

#[cfg(test)]
#[path = "parsing_tracing_snapshots.rs"]
mod parsing_tracing_snapshots;
52 changes: 52 additions & 0 deletions src/wrap/tokenize/parsing_tracing_snapshots.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,52 @@
//! Snapshot tests for parsing tracing events.

use std::cell::RefCell;

use tracing_test::traced_test;

use super::parse_link_or_image;
use crate::wrap::tracing_snapshot_support::normalise_event_lines;

#[traced_test]
#[test]
fn snapshots_footnote_reference_parsed_event() {
let captured = RefCell::new(String::new());
// Observable parse result: the footnote reference is kept atomic and the
// cursor advances past it. The snapshot is supplementary.
let (token, idx) = parse_link_or_image("[^4] tail", 0);
assert_eq!(token, "[^4]");
assert_eq!(idx, token.len());
logs_assert(|lines| {
captured.replace(normalise_event_lines(lines, "footnote reference parsed"));
(!captured.borrow().is_empty())
.then_some(())
.ok_or_else(|| "expected footnote reference parsed event".to_string())
});
let event = captured.into_inner();

insta::with_settings!({prepend_module_to_snapshot => false}, {
insta::assert_snapshot!("footnote-reference-parsed-event", event);
});
}

#[traced_test]
#[test]
fn snapshots_link_or_image_parsed_event() {
let captured = RefCell::new(String::new());
// Observable parse result: the whole link is kept atomic. Supplementary
// snapshot follows.
let (token, idx) = parse_link_or_image("[link](url)", 0);
assert_eq!(token, "[link](url)");
assert_eq!(idx, token.len());
logs_assert(|lines| {
captured.replace(normalise_event_lines(lines, "link or image parsed"));
(!captured.borrow().is_empty())
.then_some(())
.ok_or_else(|| "expected link or image parsed event".to_string())
});
let event = captured.into_inner();

insta::with_settings!({prepend_module_to_snapshot => false}, {
insta::assert_snapshot!("link-or-image-parsed-event", event);
});
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,5 @@
---
source: src/wrap/tokenize/parsing_tracing_snapshots.rs
expression: event
---
DEBUG snapshots_footnote_reference_parsed_event:parse_link_or_image{idx=0}: mdtablefix::wrap::tokenize::parsing: footnote reference parsed token_length=4
5 changes: 5 additions & 0 deletions src/wrap/tokenize/snapshots/link-or-image-parsed-event.snap
Original file line number Diff line number Diff line change
@@ -0,0 +1,5 @@
---
source: src/wrap/tokenize/parsing_tracing_snapshots.rs
expression: event
---
DEBUG snapshots_link_or_image_parsed_event:parse_link_or_image{idx=0}: mdtablefix::wrap::tokenize::parsing: link or image parsed token_length=11 is_image=false
Loading
Loading