1use alloy_json_abi::Function;
4use alloy_primitives::Bytes;
5use alloy_sol_types::SolError;
6use std::{fmt, path::Path};
7
8pub trait TestFilter: Send + Sync {
10 fn matches_test(&self, test_signature: &str) -> bool;
12
13 fn matches_contract(&self, contract_name: &str) -> bool;
15
16 fn matches_path(&self, path: &Path) -> bool;
18
19 fn matches_test_function_kind_in_contract(
23 &self,
24 _contract_id: &str,
25 func: &Function,
26 kind: TestFunctionKind,
27 ) -> bool {
28 kind.is_any_test() && self.matches_test(&func.signature())
29 }
30
31 fn matches_test_function_in_contract(&self, contract_id: &str, func: &Function) -> bool {
35 self.matches_test_function_kind_in_contract(contract_id, func, func.test_function_kind())
36 }
37}
38
39impl<'a> dyn TestFilter + 'a {
40 pub fn matches_test_function_kind(&self, func: &Function, kind: TestFunctionKind) -> bool {
42 kind.is_any_test() && self.matches_test(&func.signature())
43 }
44
45 pub fn matches_test_function(&self, func: &Function) -> bool {
47 self.matches_test_function_kind(func, func.test_function_kind())
48 }
49}
50
51#[derive(Clone, Debug, Default)]
53pub struct EmptyTestFilter(());
54impl TestFilter for EmptyTestFilter {
55 fn matches_test(&self, _test_signature: &str) -> bool {
56 true
57 }
58
59 fn matches_contract(&self, _contract_name: &str) -> bool {
60 true
61 }
62
63 fn matches_path(&self, _path: &Path) -> bool {
64 true
65 }
66}
67
68pub trait TestFunctionExt {
70 fn test_function_kind(&self) -> TestFunctionKind {
72 TestFunctionKind::classify(self.tfe_as_str(), self.tfe_has_inputs(), false)
73 }
74
75 fn is_setup(&self) -> bool {
77 self.test_function_kind().is_setup()
78 }
79
80 fn is_any_test(&self) -> bool {
82 self.test_function_kind().is_any_test()
83 }
84
85 fn is_any_test_fail(&self) -> bool {
87 self.test_function_kind().is_any_test_fail()
88 }
89
90 fn is_unit_test(&self) -> bool {
92 matches!(self.test_function_kind(), TestFunctionKind::UnitTest { .. })
93 }
94
95 fn is_before_test_setup(&self) -> bool {
97 self.tfe_as_str().eq_ignore_ascii_case("beforetestsetup")
98 }
99
100 fn is_fuzz_test(&self) -> bool {
102 self.test_function_kind().is_fuzz_test()
103 }
104
105 fn is_invariant_test(&self) -> bool {
107 self.test_function_kind().is_invariant_test()
108 }
109
110 fn is_symbolic_test(&self) -> bool {
112 self.test_function_kind().is_symbolic_test()
113 }
114
115 fn is_after_invariant(&self) -> bool {
117 self.test_function_kind().is_after_invariant()
118 }
119
120 fn is_fixture(&self) -> bool {
122 self.test_function_kind().is_fixture()
123 }
124
125 fn is_reserved(&self) -> bool {
127 self.is_any_test()
128 || self.is_setup()
129 || self.is_before_test_setup()
130 || self.is_after_invariant()
131 || self.is_fixture()
132 }
133
134 #[doc(hidden)]
135 fn tfe_as_str(&self) -> &str;
136 #[doc(hidden)]
137 fn tfe_has_inputs(&self) -> bool;
138}
139
140impl TestFunctionExt for Function {
141 fn tfe_as_str(&self) -> &str {
142 self.name.as_str()
143 }
144
145 fn tfe_has_inputs(&self) -> bool {
146 !self.inputs.is_empty()
147 }
148}
149
150impl TestFunctionExt for String {
151 fn tfe_as_str(&self) -> &str {
152 self
153 }
154
155 fn tfe_has_inputs(&self) -> bool {
156 false
157 }
158}
159
160impl TestFunctionExt for str {
161 fn tfe_as_str(&self) -> &str {
162 self
163 }
164
165 fn tfe_has_inputs(&self) -> bool {
166 false
167 }
168}
169
170#[derive(Clone, Copy, Debug, PartialEq, Eq, Hash)]
172pub enum TestFunctionKind {
173 Setup,
175 UnitTest { should_fail: bool },
177 FuzzTest { should_fail: bool },
179 InvariantTest,
181 TableTest,
183 SymbolicTest,
185 AfterInvariant,
187 Fixture,
189 Unknown,
191}
192
193impl TestFunctionKind {
194 pub fn classify(name: &str, has_inputs: bool, symbolic_entrypoints: bool) -> Self {
196 match () {
197 _ if name.starts_with("test") => {
198 let should_fail = name.starts_with("testFail");
199 if has_inputs {
200 Self::FuzzTest { should_fail }
201 } else {
202 Self::UnitTest { should_fail }
203 }
204 }
205 _ if name.starts_with("invariant") || name.starts_with("statefulFuzz") => {
206 Self::InvariantTest
207 }
208 _ if name.starts_with("table") => Self::TableTest,
209 _ if symbolic_entrypoints
210 && (name.starts_with("check") || name.starts_with("prove")) =>
211 {
212 Self::SymbolicTest
213 }
214 _ if name.eq_ignore_ascii_case("setup") && !has_inputs => Self::Setup,
215 _ if name.eq_ignore_ascii_case("afterinvariant") => Self::AfterInvariant,
216 _ if name.starts_with("fixture") => Self::Fixture,
217 _ => Self::Unknown,
218 }
219 }
220
221 pub const fn name(&self) -> &'static str {
223 match self {
224 Self::Setup => "setUp",
225 Self::UnitTest { should_fail: false } => "test",
226 Self::UnitTest { should_fail: true } => "testFail",
227 Self::FuzzTest { should_fail: false } => "fuzz",
228 Self::FuzzTest { should_fail: true } => "fuzz fail",
229 Self::InvariantTest => "invariant",
230 Self::TableTest => "table",
231 Self::SymbolicTest => "symbolic",
232 Self::AfterInvariant => "afterInvariant",
233 Self::Fixture => "fixture",
234 Self::Unknown => "unknown",
235 }
236 }
237
238 #[inline]
240 pub const fn is_setup(&self) -> bool {
241 matches!(self, Self::Setup)
242 }
243
244 #[inline]
246 pub const fn is_any_test(&self) -> bool {
247 matches!(
248 self,
249 Self::UnitTest { .. }
250 | Self::FuzzTest { .. }
251 | Self::TableTest
252 | Self::InvariantTest
253 | Self::SymbolicTest
254 )
255 }
256
257 #[inline]
259 pub const fn is_any_test_fail(&self) -> bool {
260 matches!(self, Self::UnitTest { should_fail: true } | Self::FuzzTest { should_fail: true })
261 }
262
263 #[inline]
265 pub const fn is_unit_test(&self) -> bool {
266 matches!(self, Self::UnitTest { .. })
267 }
268
269 #[inline]
271 pub const fn is_fuzz_test(&self) -> bool {
272 matches!(self, Self::FuzzTest { .. })
273 }
274
275 #[inline]
277 pub const fn is_invariant_test(&self) -> bool {
278 matches!(self, Self::InvariantTest)
279 }
280
281 #[inline]
283 pub const fn is_table_test(&self) -> bool {
284 matches!(self, Self::TableTest)
285 }
286
287 #[inline]
289 pub const fn is_symbolic_test(&self) -> bool {
290 matches!(self, Self::SymbolicTest)
291 }
292
293 #[inline]
295 pub const fn is_after_invariant(&self) -> bool {
296 matches!(self, Self::AfterInvariant)
297 }
298
299 #[inline]
301 pub const fn is_fixture(&self) -> bool {
302 matches!(self, Self::Fixture)
303 }
304
305 #[inline]
307 pub const fn is_known(&self) -> bool {
308 !matches!(self, Self::Unknown)
309 }
310
311 #[inline]
313 pub const fn is_unknown(&self) -> bool {
314 matches!(self, Self::Unknown)
315 }
316}
317
318impl fmt::Display for TestFunctionKind {
319 fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
320 self.name().fmt(f)
321 }
322}
323
324pub trait ErrorExt: std::error::Error {
326 fn abi_encode_revert(&self) -> Bytes;
328}
329
330impl<T: std::error::Error> ErrorExt for T {
331 fn abi_encode_revert(&self) -> Bytes {
332 alloy_sol_types::Revert::from(self.to_string()).abi_encode().into()
333 }
334}
335
336#[cfg(test)]
337mod tests {
338 use super::*;
339
340 #[test]
341 fn test_setup_classification() {
342 assert_eq!(TestFunctionKind::classify("setUp", false, false), TestFunctionKind::Setup);
344
345 assert_eq!(TestFunctionKind::classify("setUp", true, false), TestFunctionKind::Unknown);
348 }
349
350 #[test]
351 fn test_symbolic_classification() {
352 assert_eq!(
353 TestFunctionKind::classify("checkSymbolic", true, true),
354 TestFunctionKind::SymbolicTest
355 );
356 assert_eq!(
357 TestFunctionKind::classify("proveSymbolic", false, true),
358 TestFunctionKind::SymbolicTest
359 );
360 assert_eq!(
361 TestFunctionKind::classify("checkSymbolic", true, false),
362 TestFunctionKind::Unknown
363 );
364 }
365}