1use derive_more::with_trait::Deref;
18
19use crate::{
20 Event, World, Writer,
21 event::{self, Source},
22 parser, writer,
23};
24
25#[derive(Clone, Copy, Debug, Deref)]
31pub struct FailOnSkipped<W, F = SkipFn> {
32 #[deref]
34 writer: W,
35
36 should_fail: F,
42}
43
44pub type SkipFn =
50 fn(&gherkin::Feature, Option<&gherkin::Rule>, &gherkin::Scenario) -> bool;
51
52impl<W, Wr, F> Writer<W> for FailOnSkipped<Wr, F>
53where
54 W: World,
55 F: Fn(
56 &gherkin::Feature,
57 Option<&gherkin::Rule>,
58 &gherkin::Scenario,
59 ) -> bool,
60 Wr: Writer<W>,
61{
62 type Cli = Wr::Cli;
63
64 async fn handle_event(
65 &mut self,
66 event: parser::Result<Event<event::Cucumber<W>>>,
67 cli: &Self::Cli,
68 ) {
69 use event::{
70 Cucumber, Feature, RetryableScenario, Rule, Scenario, Step,
71 StepError::NotFound,
72 };
73
74 let map_failed = |f: &Source<_>, r: &Option<_>, sc: &Source<_>| {
75 if (self.should_fail)(f, r.as_deref(), sc) {
76 Step::Failed(None, None, None, NotFound)
77 } else {
78 Step::Skipped
79 }
80 };
81 let map_failed_bg =
82 |f: Source<_>, r: Option<_>, sc: Source<_>, st: _, ret| {
83 let ev = map_failed(&f, &r, &sc);
84 let ev = Scenario::Background(st, ev).with_retries(ret);
85 Cucumber::scenario(f, r, sc, ev)
86 };
87 let map_failed_step =
88 |f: Source<_>, r: Option<_>, sc: Source<_>, st: _, ret| {
89 let ev = map_failed(&f, &r, &sc);
90 let ev = Scenario::Step(st, ev).with_retries(ret);
91 Cucumber::scenario(f, r, sc, ev)
92 };
93
94 let event = event.map(|outer| {
95 outer.map(|ev| match ev {
96 Cucumber::Feature(
97 f,
98 Feature::Rule(
99 r,
100 Rule::Scenario(
101 sc,
102 RetryableScenario {
103 event: Scenario::Background(st, Step::Skipped),
104 retries,
105 },
106 ),
107 ),
108 ) => map_failed_bg(f, Some(r), sc, st, retries),
109 Cucumber::Feature(
110 f,
111 Feature::Scenario(
112 sc,
113 RetryableScenario {
114 event: Scenario::Background(st, Step::Skipped),
115 retries,
116 },
117 ),
118 ) => map_failed_bg(f, None, sc, st, retries),
119 Cucumber::Feature(
120 f,
121 Feature::Rule(
122 r,
123 Rule::Scenario(
124 sc,
125 RetryableScenario {
126 event: Scenario::Step(st, Step::Skipped),
127 retries,
128 },
129 ),
130 ),
131 ) => map_failed_step(f, Some(r), sc, st, retries),
132 Cucumber::Feature(
133 f,
134 Feature::Scenario(
135 sc,
136 RetryableScenario {
137 event: Scenario::Step(st, Step::Skipped),
138 retries,
139 },
140 ..,
141 ),
142 ) => map_failed_step(f, None, sc, st, retries),
143 Cucumber::Started
144 | Cucumber::Feature(..)
145 | Cucumber::ParsingFinished { .. }
146 | Cucumber::Finished => ev,
147 })
148 });
149
150 self.writer.handle_event(event, cli).await;
151 }
152}
153
154#[warn(clippy::missing_trait_methods)]
155impl<W, Wr, Val, F> writer::Arbitrary<W, Val> for FailOnSkipped<Wr, F>
156where
157 W: World,
158 Self: Writer<W>,
159 Wr: writer::Arbitrary<W, Val>,
160{
161 async fn write(&mut self, val: Val) {
162 self.writer.write(val).await;
163 }
164}
165
166#[warn(clippy::missing_trait_methods)]
167impl<W, Wr, F> writer::Stats<W> for FailOnSkipped<Wr, F>
168where
169 Wr: writer::Stats<W>,
170 Self: Writer<W>,
171{
172 fn passed_steps(&self) -> usize {
173 self.writer.passed_steps()
174 }
175
176 fn skipped_steps(&self) -> usize {
177 self.writer.skipped_steps()
178 }
179
180 fn failed_steps(&self) -> usize {
181 self.writer.failed_steps()
182 }
183
184 fn retried_steps(&self) -> usize {
185 self.writer.retried_steps()
186 }
187
188 fn parsing_errors(&self) -> usize {
189 self.writer.parsing_errors()
190 }
191
192 fn hook_errors(&self) -> usize {
193 self.writer.hook_errors()
194 }
195
196 fn execution_has_failed(&self) -> bool {
197 self.writer.execution_has_failed()
198 }
199}
200
201#[warn(clippy::missing_trait_methods)]
202impl<Wr: writer::Normalized, F> writer::Normalized for FailOnSkipped<Wr, F> {}
203
204impl<Writer> From<Writer> for FailOnSkipped<Writer> {
205 fn from(writer: Writer) -> Self {
206 Self {
207 writer,
208 should_fail: |feat, rule, sc| {
209 !sc.tags
210 .iter()
211 .chain(rule.iter().flat_map(|r| &r.tags))
212 .chain(&feat.tags)
213 .any(|t| t == "allow.skipped")
214 },
215 }
216 }
217}
218
219impl<Writer> FailOnSkipped<Writer> {
220 #[must_use]
222 pub fn new(writer: Writer) -> Self {
223 Self::from(writer)
224 }
225
226 #[must_use]
234 pub const fn with<P>(
235 writer: Writer,
236 predicate: P,
237 ) -> FailOnSkipped<Writer, P>
238 where
239 P: Fn(
240 &gherkin::Feature,
241 Option<&gherkin::Rule>,
242 &gherkin::Scenario,
243 ) -> bool,
244 {
245 FailOnSkipped { writer, should_fail: predicate }
246 }
247
248 #[must_use]
250 pub fn inner_writer(&self) -> &Writer {
251 &self.writer
252 }
253}