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
20impl<'a> dyn TestFilter + 'a {
21 pub fn matches_test_function(&self, func: &Function) -> bool {
23 func.is_any_test() && self.matches_test(&func.signature())
24 }
25}
26
27#[derive(Clone, Debug, Default)]
29pub struct EmptyTestFilter(());
30impl TestFilter for EmptyTestFilter {
31 fn matches_test(&self, _test_signature: &str) -> bool {
32 true
33 }
34
35 fn matches_contract(&self, _contract_name: &str) -> bool {
36 true
37 }
38
39 fn matches_path(&self, _path: &Path) -> bool {
40 true
41 }
42}
43
44pub trait TestFunctionExt {
46 fn test_function_kind(&self) -> TestFunctionKind {
48 TestFunctionKind::classify(self.tfe_as_str(), self.tfe_has_inputs())
49 }
50
51 fn is_setup(&self) -> bool {
53 self.test_function_kind().is_setup()
54 }
55
56 fn is_any_test(&self) -> bool {
58 self.test_function_kind().is_any_test()
59 }
60
61 fn is_any_test_fail(&self) -> bool {
63 self.test_function_kind().is_any_test_fail()
64 }
65
66 fn is_unit_test(&self) -> bool {
68 matches!(self.test_function_kind(), TestFunctionKind::UnitTest { .. })
69 }
70
71 fn is_before_test_setup(&self) -> bool {
73 self.tfe_as_str().eq_ignore_ascii_case("beforetestsetup")
74 }
75
76 fn is_fuzz_test(&self) -> bool {
78 self.test_function_kind().is_fuzz_test()
79 }
80
81 fn is_invariant_test(&self) -> bool {
83 self.test_function_kind().is_invariant_test()
84 }
85
86 fn is_after_invariant(&self) -> bool {
88 self.test_function_kind().is_after_invariant()
89 }
90
91 fn is_fixture(&self) -> bool {
93 self.test_function_kind().is_fixture()
94 }
95
96 fn is_reserved(&self) -> bool {
98 self.is_any_test()
99 || self.is_setup()
100 || self.is_before_test_setup()
101 || self.is_after_invariant()
102 || self.is_fixture()
103 }
104
105 #[doc(hidden)]
106 fn tfe_as_str(&self) -> &str;
107 #[doc(hidden)]
108 fn tfe_has_inputs(&self) -> bool;
109}
110
111impl TestFunctionExt for Function {
112 fn tfe_as_str(&self) -> &str {
113 self.name.as_str()
114 }
115
116 fn tfe_has_inputs(&self) -> bool {
117 !self.inputs.is_empty()
118 }
119}
120
121impl TestFunctionExt for String {
122 fn tfe_as_str(&self) -> &str {
123 self
124 }
125
126 fn tfe_has_inputs(&self) -> bool {
127 false
128 }
129}
130
131impl TestFunctionExt for str {
132 fn tfe_as_str(&self) -> &str {
133 self
134 }
135
136 fn tfe_has_inputs(&self) -> bool {
137 false
138 }
139}
140
141#[derive(Clone, Copy, Debug, PartialEq, Eq, Hash)]
143pub enum TestFunctionKind {
144 Setup,
146 UnitTest { should_fail: bool },
148 FuzzTest { should_fail: bool },
150 InvariantTest,
152 TableTest,
154 AfterInvariant,
156 Fixture,
158 Unknown,
160}
161
162impl TestFunctionKind {
163 pub fn classify(name: &str, has_inputs: bool) -> Self {
165 match () {
166 _ if name.starts_with("test") => {
167 let should_fail = name.starts_with("testFail");
168 if has_inputs {
169 Self::FuzzTest { should_fail }
170 } else {
171 Self::UnitTest { should_fail }
172 }
173 }
174 _ if name.starts_with("invariant") || name.starts_with("statefulFuzz") => {
175 Self::InvariantTest
176 }
177 _ if name.starts_with("table") => Self::TableTest,
178 _ if name.eq_ignore_ascii_case("setup") => Self::Setup,
179 _ if name.eq_ignore_ascii_case("afterinvariant") => Self::AfterInvariant,
180 _ if name.starts_with("fixture") => Self::Fixture,
181 _ => Self::Unknown,
182 }
183 }
184
185 pub const fn name(&self) -> &'static str {
187 match self {
188 Self::Setup => "setUp",
189 Self::UnitTest { should_fail: false } => "test",
190 Self::UnitTest { should_fail: true } => "testFail",
191 Self::FuzzTest { should_fail: false } => "fuzz",
192 Self::FuzzTest { should_fail: true } => "fuzz fail",
193 Self::InvariantTest => "invariant",
194 Self::TableTest => "table",
195 Self::AfterInvariant => "afterInvariant",
196 Self::Fixture => "fixture",
197 Self::Unknown => "unknown",
198 }
199 }
200
201 #[inline]
203 pub const fn is_setup(&self) -> bool {
204 matches!(self, Self::Setup)
205 }
206
207 #[inline]
209 pub const fn is_any_test(&self) -> bool {
210 matches!(
211 self,
212 Self::UnitTest { .. } | Self::FuzzTest { .. } | Self::TableTest | Self::InvariantTest
213 )
214 }
215
216 #[inline]
218 pub const fn is_any_test_fail(&self) -> bool {
219 matches!(self, Self::UnitTest { should_fail: true } | Self::FuzzTest { should_fail: true })
220 }
221
222 #[inline]
224 pub fn is_unit_test(&self) -> bool {
225 matches!(self, Self::UnitTest { .. })
226 }
227
228 #[inline]
230 pub const fn is_fuzz_test(&self) -> bool {
231 matches!(self, Self::FuzzTest { .. })
232 }
233
234 #[inline]
236 pub const fn is_invariant_test(&self) -> bool {
237 matches!(self, Self::InvariantTest)
238 }
239
240 #[inline]
242 pub const fn is_table_test(&self) -> bool {
243 matches!(self, Self::TableTest)
244 }
245
246 #[inline]
248 pub const fn is_after_invariant(&self) -> bool {
249 matches!(self, Self::AfterInvariant)
250 }
251
252 #[inline]
254 pub const fn is_fixture(&self) -> bool {
255 matches!(self, Self::Fixture)
256 }
257
258 #[inline]
260 pub const fn is_known(&self) -> bool {
261 !matches!(self, Self::Unknown)
262 }
263
264 #[inline]
266 pub const fn is_unknown(&self) -> bool {
267 matches!(self, Self::Unknown)
268 }
269}
270
271impl fmt::Display for TestFunctionKind {
272 fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
273 self.name().fmt(f)
274 }
275}
276
277pub trait ErrorExt: std::error::Error {
279 fn abi_encode_revert(&self) -> Bytes;
281}
282
283impl<T: std::error::Error> ErrorExt for T {
284 fn abi_encode_revert(&self) -> Bytes {
285 alloy_sol_types::Revert::from(self.to_string()).abi_encode().into()
286 }
287}