1pub use allure_rust_commons::{Status, StatusDetails};
2pub use allure_test_macros::{allure_test, step};
7
8use std::{
9 any::Any,
10 cell::RefCell,
11 panic::{catch_unwind, AssertUnwindSafe},
12 path::Path,
13};
14
15mod labels;
16mod testplan;
17
18pub use testplan::{TestPlan, TestPlanEntry};
19
20use allure_rust_commons::{AllureFacade, AllureRuntime, FileSystemResultsWriter};
21
22thread_local! {
23 static CURRENT_ALLURE: RefCell<Option<AllureFacade>> = const { RefCell::new(None) };
24}
25
26pub mod __private {
27 use super::{AllureFacade, CURRENT_ALLURE};
28
29 pub struct CurrentAllureGuard {
30 previous: Option<AllureFacade>,
31 }
32
33 pub fn push_current_allure(allure: &AllureFacade) -> CurrentAllureGuard {
34 let previous = CURRENT_ALLURE.with(|current| current.replace(Some(allure.clone())));
35 CurrentAllureGuard { previous }
36 }
37
38 pub fn current_allure() -> Option<AllureFacade> {
39 CURRENT_ALLURE.with(|current| current.borrow().clone())
40 }
41
42 impl Drop for CurrentAllureGuard {
43 fn drop(&mut self) {
44 CURRENT_ALLURE.with(|current| {
45 current.replace(self.previous.take());
46 });
47 }
48 }
49}
50
51#[derive(Debug)]
52pub enum ReporterError {
53 Io(std::io::Error),
54}
55
56impl std::fmt::Display for ReporterError {
57 fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
58 match self {
59 Self::Io(err) => write!(f, "io error: {err}"),
60 }
61 }
62}
63
64impl std::error::Error for ReporterError {}
65
66impl From<std::io::Error> for ReporterError {
67 fn from(value: std::io::Error) -> Self {
68 Self::Io(value)
69 }
70}
71
72#[derive(Clone)]
73pub struct CargoTestReporter {
74 allure: AllureFacade,
75 test_plan: Option<TestPlan>,
76}
77
78impl CargoTestReporter {
79 pub fn new<P: AsRef<Path>>(results_dir: P) -> Result<Self, ReporterError> {
80 let writer = FileSystemResultsWriter::new(results_dir)?;
81 let runtime = AllureRuntime::new(writer);
82 Ok(Self {
83 allure: AllureFacade::with_lifecycle(runtime.lifecycle()),
84 test_plan: TestPlan::from_env(),
85 })
86 }
87
88 pub fn allure(&self) -> &AllureFacade {
89 &self.allure
90 }
91
92 pub fn run_test<F>(&self, name: &str, f: F)
93 where
94 F: FnOnce(&AllureFacade),
95 {
96 self.run_test_with_metadata(name, Some(name), None, None, f);
97 }
98
99 pub fn run_test_with_metadata<F>(
100 &self,
101 test_name: &str,
102 full_name: Option<&str>,
103 allure_id: Option<&str>,
104 tags: Option<&[&str]>,
105 f: F,
106 ) where
107 F: FnOnce(&AllureFacade),
108 {
109 if !self.is_selected(test_name, full_name, allure_id, tags) {
110 return;
111 }
112
113 if let Some(full_name) = full_name {
114 self.allure.start_test_with_full_name(test_name, full_name);
115 } else {
116 self.allure.start_test(test_name);
117 }
118 labels::add_default_and_global_labels(&self.allure);
119 labels::add_synthetic_suite_labels(&self.allure, full_name);
120 let _current_allure = __private::push_current_allure(&self.allure);
121 let result = catch_unwind(AssertUnwindSafe(|| f(&self.allure)));
122 match result {
123 Ok(_) => self.allure.end_test(Status::Passed, None),
124 Err(payload) => {
125 let msg = if let Some(msg) = payload.downcast_ref::<&str>() {
126 (*msg).to_string()
127 } else if let Some(msg) = payload.downcast_ref::<String>() {
128 msg.clone()
129 } else {
130 "panic without string payload".to_string()
131 };
132 self.allure.end_test(
133 Status::Failed,
134 Some(StatusDetails {
135 message: Some(msg),
136 trace: None,
137 actual: None,
138 expected: None,
139 }),
140 );
141 std::panic::resume_unwind(payload);
142 }
143 }
144 }
145
146 pub fn is_selected(
147 &self,
148 _test_name: &str,
149 full_name: Option<&str>,
150 allure_id: Option<&str>,
151 tags: Option<&[&str]>,
152 ) -> bool {
153 match &self.test_plan {
154 Some(plan) => plan.is_selected(full_name, allure_id, tags),
155 None => true,
156 }
157 }
158
159 pub fn run_test_with_result<F>(&self, name: &str, f: F)
160 where
161 F: FnOnce(&AllureFacade) -> (Status, Option<StatusDetails>, Option<Box<dyn Any + Send>>),
162 {
163 self.allure.start_test(name);
164 labels::add_default_and_global_labels(&self.allure);
165 let _current_allure = __private::push_current_allure(&self.allure);
166 let (status, details, panic_payload) = f(&self.allure);
167 self.allure.end_test(status, details);
168 if let Some(payload) = panic_payload {
169 std::panic::resume_unwind(payload);
170 }
171 }
172}
173
174#[macro_export]
175macro_rules! allure_wrap_test {
176 ($reporter:expr, $name:expr, $body:block) => {{
177 $reporter.run_test($name, |_| $body)
178 }};
179}