Skip to main content

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"
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/// Combine multiple `dev_report::Producer` results into a single
82/// `MultiReport` keyed by `subject`/`version`.
83///
84/// Pure composition: no new types, no new logic. Each producer is
85/// invoked once via `Producer::produce()` and pushed into the
86/// returned [`dev_report::MultiReport`].
87///
88/// # Example
89///
90/// ```
91/// use dev_tools::full_run;
92/// use dev_tools::report::{CheckResult, Producer, Report, Verdict};
93///
94/// struct A;
95/// impl Producer for A {
96///     fn produce(&self) -> Report {
97///         let mut r = Report::new("crate", "0.1.0").with_producer("a");
98///         r.push(CheckResult::pass("ok"));
99///         r.finish();
100///         r
101///     }
102/// }
103/// struct B;
104/// impl Producer for B {
105///     fn produce(&self) -> Report {
106///         let mut r = Report::new("crate", "0.1.0").with_producer("b");
107///         r.push(CheckResult::pass("ok"));
108///         r.finish();
109///         r
110///     }
111/// }
112///
113/// let multi = full_run!("crate", "0.1.0"; A, B);
114/// assert_eq!(multi.reports.len(), 2);
115/// assert_eq!(multi.overall_verdict(), Verdict::Pass);
116/// ```
117#[macro_export]
118macro_rules! full_run {
119    ($subject:expr, $version:expr; $($producer:expr),* $(,)?) => {{
120        let mut multi = $crate::report::MultiReport::new($subject, $version);
121        $(
122            multi.push(<_ as $crate::report::Producer>::produce(&$producer));
123        )*
124        multi.finish();
125        multi
126    }};
127}
128
129#[cfg(test)]
130mod tests {
131    use super::*;
132
133    #[test]
134    fn report_module_is_always_available() {
135        let r = report::Report::new("self", "0.1.0");
136        assert_eq!(r.subject, "self");
137    }
138
139    #[cfg(feature = "fixtures")]
140    #[test]
141    fn fixtures_module_is_available_with_feature() {
142        let _ = fixtures::TempProject::new();
143    }
144
145    #[cfg(feature = "bench")]
146    #[test]
147    fn bench_module_is_available_with_feature() {
148        let _ = bench::Benchmark::new("x");
149    }
150
151    #[test]
152    fn full_run_combines_zero_producers() {
153        let multi = full_run!("crate", "0.1.0";);
154        assert_eq!(multi.reports.len(), 0);
155        assert_eq!(multi.overall_verdict(), report::Verdict::Skip);
156    }
157
158    #[test]
159    fn full_run_combines_two_producers() {
160        struct OkProducer(&'static str);
161        impl report::Producer for OkProducer {
162            fn produce(&self) -> report::Report {
163                let mut r = report::Report::new("c", "0.1.0").with_producer(self.0);
164                r.push(report::CheckResult::pass("x"));
165                r.finish();
166                r
167            }
168        }
169        let multi = full_run!("c", "0.1.0"; OkProducer("a"), OkProducer("b"));
170        assert_eq!(multi.reports.len(), 2);
171        assert_eq!(multi.overall_verdict(), report::Verdict::Pass);
172    }
173
174    #[test]
175    fn full_run_propagates_failures() {
176        struct OkProducer;
177        impl report::Producer for OkProducer {
178            fn produce(&self) -> report::Report {
179                let mut r = report::Report::new("c", "0.1.0").with_producer("ok");
180                r.push(report::CheckResult::pass("x"));
181                r.finish();
182                r
183            }
184        }
185        struct FailProducer;
186        impl report::Producer for FailProducer {
187            fn produce(&self) -> report::Report {
188                let mut r = report::Report::new("c", "0.1.0").with_producer("fail");
189                r.push(report::CheckResult::fail("y", report::Severity::Error));
190                r.finish();
191                r
192            }
193        }
194        let multi = full_run!("c", "0.1.0"; OkProducer, FailProducer);
195        assert_eq!(multi.overall_verdict(), report::Verdict::Fail);
196    }
197
198    #[cfg(all(feature = "fixtures", feature = "bench"))]
199    #[test]
200    fn full_run_with_real_producers() {
201        // fixtures: a self-test of TempProject lifecycle.
202        let fixture_producer =
203            fixtures::FixtureProducer::new("temp_project_lifecycle", "0.1.0", || {
204                let _p = fixtures::TempProject::new()
205                    .with_file("README.md", "hi")
206                    .build()?;
207                Ok(())
208            });
209        // bench: a tiny benchmark with no baseline.
210        let bench_producer = bench::BenchProducer::new(
211            || {
212                let mut b = bench::Benchmark::new("hot");
213                for _ in 0..5 {
214                    b.iter(|| std::hint::black_box(1 + 1));
215                }
216                b.finish()
217            },
218            "0.1.0",
219            None,
220            bench::Threshold::regression_pct(20.0),
221        );
222        let multi = full_run!("crate", "0.1.0"; fixture_producer, bench_producer);
223        assert_eq!(multi.reports.len(), 2);
224    }
225}