1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
//! Phase 2B (issue #7) — exercises the opt-in `tracing` feature.
//!
//! Builds only when the feature is enabled; the entire file is
//! gated at the module level so `cargo test` without
//! `--features tracing` skips it cleanly.
//!
//! The test is a wire-up smoke check, not a tracing-semantics
//! freeze: it attaches a capturing [`tracing_subscriber::Layer`]
//! that records every `on_new_span` event into a shared
//! `Vec<String>`, exercises the four obj-db entry points
//! (`Db::open` → `Db::memory`, `Db::transaction`, `Db::query::fetch`,
//! `Db::integrity_check`), and asserts the expected span names
//! show up. The exact event count + ordering is intentionally NOT
//! frozen — future revisions are free to add events inside a span
//! without breaking the test.
#![cfg(feature = "tracing")]
#![forbid(unsafe_code)]
use std::sync::{Arc, Mutex};
use obj::{Db, Document};
use serde::{Deserialize, Serialize};
use tracing::subscriber::with_default;
use tracing_subscriber::layer::{Context, SubscriberExt};
use tracing_subscriber::registry::LookupSpan;
use tracing_subscriber::Layer;
use tracing_subscriber::Registry;
/// Toy document with no indexes — keeps the test focused on the
/// tracing wire-up rather than the M7 index machinery.
#[derive(Debug, Clone, Serialize, Deserialize)]
struct Tick {
/// The single payload byte. Not captured by any span.
n: u64,
}
impl Document for Tick {
const COLLECTION: &'static str = "ticks";
const VERSION: u32 = 1;
}
/// `Layer` that captures every span's `name()` into a shared `Vec`.
/// Power-of-ten Rule 5: the `Arc<Mutex<Vec<String>>>` is the
/// minimal invariant boundary — the only mutation is `push`, and
/// every lock acquisition is checked for poisoning.
struct CaptureLayer {
spans: Arc<Mutex<Vec<String>>>,
}
impl<S> Layer<S> for CaptureLayer
where
S: tracing::Subscriber + for<'a> LookupSpan<'a>,
{
fn on_new_span(
&self,
attrs: &tracing::span::Attributes<'_>,
_id: &tracing::Id,
_ctx: Context<'_, S>,
) {
// `metadata().name()` is the static span-name token — exactly
// what we want to assert on. The Mutex `lock()` only fails on
// poisoning; the test thread does not panic mid-push so the
// happy path is one acquisition per span.
if let Ok(mut guard) = self.spans.lock() {
guard.push(attrs.metadata().name().to_owned());
}
}
}
#[test]
fn tracing_feature_emits_expected_spans() -> obj::Result<()> {
let captured: Arc<Mutex<Vec<String>>> = Arc::new(Mutex::new(Vec::new()));
let layer = CaptureLayer {
spans: Arc::clone(&captured),
};
let subscriber = Registry::default().with(layer);
with_default(subscriber, || -> obj::Result<()> {
// `Db::memory` routes through `from_parts` without going
// through `Db::open`, so we directly exercise the txn /
// query / integrity spans here. The `db.open` span is
// exercised by the second `Db::open` call below against
// a tempfile-backed database.
let db = Db::memory()?;
db.transaction(|tx| {
tx.collection::<Tick>()?.insert(Tick { n: 1 })?;
Ok(())
})?;
// `Query::fetch` opens a `read_transaction` inside; both
// the `query.execute` and `db.read_transaction` spans
// should appear.
let _ = db.query::<Tick>().fetch()?;
let _ = db.integrity_check()?;
// `Db::open` against a real path — the only entry point
// that records the `path` field.
let dir = tempfile::tempdir()?;
let path = dir.path().join("trace.obj");
let _ondisk = Db::open(&path)?;
Ok(())
})?;
let names = captured.lock().expect("capture mutex not poisoned");
let snapshot: Vec<&str> = names.iter().map(String::as_str).collect();
for expected in [
"db.open",
"db.transaction",
"db.read_transaction",
"db.integrity_check",
"query.execute",
] {
assert!(
snapshot.contains(&expected),
"expected span `{expected}` in capture; saw {snapshot:?}",
);
}
Ok(())
}