dev_tools/lib.rs
1//! # dev-tools
2//!
3//! Modular verification toolkit for AI-assisted Rust development.
4//! Umbrella crate over the `dev-*` suite.
5//!
6//! `dev-tools` is the convenient one-import entry point. Pick the
7//! features you need and pull them in with one line.
8//!
9//! ## Default features
10//!
11//! By default, you get:
12//!
13//! - [`mod@report`]: structured machine-readable verdicts (always enabled).
14//! - [`mod@fixtures`]: deterministic test environments.
15//! - [`mod@bench`]: performance measurement and regression detection.
16//!
17//! ## Opt-in features
18//!
19//! Enable with `features = ["..."]`:
20//!
21//! - `async`: async-specific validation (deadlocks, hung futures, leaks).
22//! - `stress`: high-load stress testing (concurrency, volume).
23//! - `chaos`: failure injection and recovery testing.
24//! - `full`: all of the above.
25//!
26//! ## Quick example
27//!
28//! ```toml
29//! [dependencies]
30//! dev-tools = "0.9.1"
31//! ```
32//!
33//! ```rust
34//! use dev_tools::report::{Report, Verdict};
35//!
36//! let mut r = Report::new("my-crate", "0.1.0");
37//! // ... use r ...
38//! ```
39//!
40//! ## See also
41//!
42//! - [`dev-report`](https://crates.io/crates/dev-report) - schema only
43//! - [`dev-fixtures`](https://crates.io/crates/dev-fixtures) - test environments
44//! - [`dev-bench`](https://crates.io/crates/dev-bench) - performance
45//! - [`dev-async`](https://crates.io/crates/dev-async) - async validation
46//! - [`dev-stress`](https://crates.io/crates/dev-stress) - load testing
47//! - [`dev-chaos`](https://crates.io/crates/dev-chaos) - failure injection
48
49#![cfg_attr(docsrs, feature(doc_cfg))]
50#![warn(missing_docs)]
51#![warn(rust_2018_idioms)]
52
53/// Re-export of [`dev_report`]. Always available.
54pub use dev_report as report;
55
56/// Re-export of [`dev_fixtures`]. Available with the `fixtures` feature.
57#[cfg(feature = "fixtures")]
58#[cfg_attr(docsrs, doc(cfg(feature = "fixtures")))]
59pub use dev_fixtures as fixtures;
60
61/// Re-export of [`dev_bench`]. Available with the `bench` feature.
62#[cfg(feature = "bench")]
63#[cfg_attr(docsrs, doc(cfg(feature = "bench")))]
64pub use dev_bench as bench;
65
66/// Re-export of [`dev_async`]. Available with the `async` feature.
67#[cfg(feature = "async")]
68#[cfg_attr(docsrs, doc(cfg(feature = "async")))]
69pub use dev_async as r#async;
70
71/// Re-export of [`dev_stress`]. Available with the `stress` feature.
72#[cfg(feature = "stress")]
73#[cfg_attr(docsrs, doc(cfg(feature = "stress")))]
74pub use dev_stress as stress;
75
76/// Re-export of [`dev_chaos`]. Available with the `chaos` feature.
77#[cfg(feature = "chaos")]
78#[cfg_attr(docsrs, doc(cfg(feature = "chaos")))]
79pub use dev_chaos as chaos;
80
81/// Convenience re-exports for the most common items across the suite.
82///
83/// `use dev_tools::prelude::*;` to pull in the schema types
84/// ([`Report`], [`CheckResult`], [`Verdict`], [`Severity`], [`Evidence`],
85/// the [`Producer`] trait) plus `MultiReport` and `Diff`. Optional
86/// per-feature items (`fixtures::TempProject`, `bench::Benchmark`,
87/// etc.) are NOT in the prelude — pull them in directly via the
88/// re-exported sub-crate modules.
89///
90/// # Example
91///
92/// ```
93/// use dev_tools::prelude::*;
94///
95/// let mut r = Report::new("my-crate", "0.1.0");
96/// r.push(CheckResult::pass("compile"));
97/// r.finish();
98/// assert!(r.passed());
99/// ```
100///
101/// [`Report`]: dev_report::Report
102/// [`CheckResult`]: dev_report::CheckResult
103/// [`Verdict`]: dev_report::Verdict
104/// [`Severity`]: dev_report::Severity
105/// [`Evidence`]: dev_report::Evidence
106/// [`Producer`]: dev_report::Producer
107pub mod prelude {
108 pub use dev_report::{
109 CheckResult, Diff, DiffOptions, Evidence, EvidenceData, EvidenceKind, FileRef, MultiReport,
110 Producer, Report, Severity, Verdict,
111 };
112}
113
114/// Combine multiple `dev_report::Producer` results into a single
115/// `MultiReport` keyed by `subject`/`version`.
116///
117/// Pure composition: no new types, no new logic. Each producer is
118/// invoked once via `Producer::produce()` and pushed into the
119/// returned [`dev_report::MultiReport`].
120///
121/// # Example
122///
123/// ```
124/// use dev_tools::full_run;
125/// use dev_tools::report::{CheckResult, Producer, Report, Verdict};
126///
127/// struct A;
128/// impl Producer for A {
129/// fn produce(&self) -> Report {
130/// let mut r = Report::new("crate", "0.1.0").with_producer("a");
131/// r.push(CheckResult::pass("ok"));
132/// r.finish();
133/// r
134/// }
135/// }
136/// struct B;
137/// impl Producer for B {
138/// fn produce(&self) -> Report {
139/// let mut r = Report::new("crate", "0.1.0").with_producer("b");
140/// r.push(CheckResult::pass("ok"));
141/// r.finish();
142/// r
143/// }
144/// }
145///
146/// let multi = full_run!("crate", "0.1.0"; A, B);
147/// assert_eq!(multi.reports.len(), 2);
148/// assert_eq!(multi.overall_verdict(), Verdict::Pass);
149/// ```
150#[macro_export]
151macro_rules! full_run {
152 ($subject:expr, $version:expr; $($producer:expr),* $(,)?) => {{
153 let mut multi = $crate::report::MultiReport::new($subject, $version);
154 $(
155 multi.push(<_ as $crate::report::Producer>::produce(&$producer));
156 )*
157 multi.finish();
158 multi
159 }};
160}
161
162#[cfg(test)]
163mod tests {
164 use super::*;
165
166 #[test]
167 fn report_module_is_always_available() {
168 let r = report::Report::new("self", "0.1.0");
169 assert_eq!(r.subject, "self");
170 }
171
172 #[test]
173 fn prelude_pulls_core_types() {
174 // The prelude should make these immediately accessible
175 // without further imports.
176 use crate::prelude::*;
177
178 let mut r = Report::new("c", "0.1.0");
179 r.push(CheckResult::pass("ok"));
180 r.finish();
181 assert_eq!(r.overall_verdict(), Verdict::Pass);
182 assert!(r.passed());
183
184 let _ev = Evidence::numeric_int("count", 42);
185 let _opts = DiffOptions::default();
186 let _multi = MultiReport::new("c", "0.1.0");
187
188 // Sanity-check that Severity and Producer/Diff/etc. are in scope.
189 let _sev = Severity::Error;
190 fn _takes_producer(_p: &dyn Producer) {}
191 fn _takes_diff(_d: &Diff) {}
192 }
193
194 #[cfg(feature = "fixtures")]
195 #[test]
196 fn fixtures_module_is_available_with_feature() {
197 let _ = fixtures::TempProject::new();
198 }
199
200 #[cfg(feature = "bench")]
201 #[test]
202 fn bench_module_is_available_with_feature() {
203 let _ = bench::Benchmark::new("x");
204 }
205
206 #[test]
207 fn full_run_combines_zero_producers() {
208 let multi = full_run!("crate", "0.1.0";);
209 assert_eq!(multi.reports.len(), 0);
210 assert_eq!(multi.overall_verdict(), report::Verdict::Skip);
211 }
212
213 #[test]
214 fn full_run_combines_two_producers() {
215 struct OkProducer(&'static str);
216 impl report::Producer for OkProducer {
217 fn produce(&self) -> report::Report {
218 let mut r = report::Report::new("c", "0.1.0").with_producer(self.0);
219 r.push(report::CheckResult::pass("x"));
220 r.finish();
221 r
222 }
223 }
224 let multi = full_run!("c", "0.1.0"; OkProducer("a"), OkProducer("b"));
225 assert_eq!(multi.reports.len(), 2);
226 assert_eq!(multi.overall_verdict(), report::Verdict::Pass);
227 }
228
229 #[test]
230 fn full_run_propagates_failures() {
231 struct OkProducer;
232 impl report::Producer for OkProducer {
233 fn produce(&self) -> report::Report {
234 let mut r = report::Report::new("c", "0.1.0").with_producer("ok");
235 r.push(report::CheckResult::pass("x"));
236 r.finish();
237 r
238 }
239 }
240 struct FailProducer;
241 impl report::Producer for FailProducer {
242 fn produce(&self) -> report::Report {
243 let mut r = report::Report::new("c", "0.1.0").with_producer("fail");
244 r.push(report::CheckResult::fail("y", report::Severity::Error));
245 r.finish();
246 r
247 }
248 }
249 let multi = full_run!("c", "0.1.0"; OkProducer, FailProducer);
250 assert_eq!(multi.overall_verdict(), report::Verdict::Fail);
251 }
252
253 #[cfg(all(feature = "fixtures", feature = "bench"))]
254 #[test]
255 fn full_run_with_real_producers() {
256 // fixtures: a self-test of TempProject lifecycle.
257 let fixture_producer =
258 fixtures::FixtureProducer::new("temp_project_lifecycle", "0.1.0", || {
259 let _p = fixtures::TempProject::new()
260 .with_file("README.md", "hi")
261 .build()?;
262 Ok(())
263 });
264 // bench: a tiny benchmark with no baseline.
265 let bench_producer = bench::BenchProducer::new(
266 || {
267 let mut b = bench::Benchmark::new("hot");
268 for _ in 0..5 {
269 b.iter(|| std::hint::black_box(1 + 1));
270 }
271 b.finish()
272 },
273 "0.1.0",
274 None,
275 bench::Threshold::regression_pct(20.0),
276 );
277 let multi = full_run!("crate", "0.1.0"; fixture_producer, bench_producer);
278 assert_eq!(multi.reports.len(), 2);
279 }
280}