cucumber/
lib.rs

1// Copyright (c) 2018-2024  Brendan Molloy <brendan@bbqsrc.net>,
2//                          Ilya Solovyiov <ilya.solovyiov@gmail.com>,
3//                          Kai Ren <tyranron@gmail.com>
4//
5// Licensed under the Apache License, Version 2.0 <LICENSE-APACHE or
6// http://www.apache.org/licenses/LICENSE-2.0> or the MIT license
7// <LICENSE-MIT or http://opensource.org/licenses/MIT>, at your
8// option. This file may not be copied, modified, or distributed
9// except according to those terms.
10
11#![doc(
12    html_logo_url = "https://avatars.githubusercontent.com/u/91469139?s=128",
13    html_favicon_url = "https://avatars.githubusercontent.com/u/91469139?s=256"
14)]
15#![doc = include_str!("../README.md")]
16#![cfg_attr(docsrs, feature(doc_auto_cfg))]
17#![deny(
18    macro_use_extern_crate,
19    nonstandard_style,
20    rust_2018_idioms,
21    rustdoc::all,
22    trivial_casts,
23    trivial_numeric_casts
24)]
25#![forbid(non_ascii_idents, unsafe_code)]
26#![warn(
27    clippy::absolute_paths,
28    clippy::as_conversions,
29    clippy::as_ptr_cast_mut,
30    clippy::assertions_on_result_states,
31    clippy::branches_sharing_code,
32    clippy::clear_with_drain,
33    clippy::clone_on_ref_ptr,
34    clippy::collection_is_never_read,
35    clippy::create_dir,
36    clippy::dbg_macro,
37    clippy::debug_assert_with_mut_call,
38    clippy::decimal_literal_representation,
39    clippy::default_union_representation,
40    clippy::derive_partial_eq_without_eq,
41    clippy::else_if_without_else,
42    clippy::empty_drop,
43    clippy::empty_line_after_outer_attr,
44    clippy::empty_structs_with_brackets,
45    clippy::equatable_if_let,
46    clippy::empty_enum_variants_with_brackets,
47    clippy::exit,
48    clippy::expect_used,
49    clippy::fallible_impl_from,
50    clippy::filetype_is_file,
51    clippy::float_cmp_const,
52    clippy::fn_to_numeric_cast,
53    clippy::fn_to_numeric_cast_any,
54    clippy::format_push_string,
55    clippy::get_unwrap,
56    clippy::if_then_some_else_none,
57    clippy::imprecise_flops,
58    clippy::index_refutable_slice,
59    clippy::infinite_loop,
60    clippy::iter_on_empty_collections,
61    clippy::iter_on_single_items,
62    clippy::iter_over_hash_type,
63    clippy::iter_with_drain,
64    clippy::large_include_file,
65    clippy::large_stack_frames,
66    clippy::let_underscore_untyped,
67    clippy::lossy_float_literal,
68    clippy::manual_c_str_literals,
69    clippy::map_err_ignore,
70    clippy::mem_forget,
71    clippy::missing_assert_message,
72    clippy::missing_asserts_for_indexing,
73    clippy::missing_const_for_fn,
74    clippy::missing_docs_in_private_items,
75    clippy::multiple_inherent_impl,
76    clippy::multiple_unsafe_ops_per_block,
77    clippy::mutex_atomic,
78    clippy::mutex_integer,
79    clippy::needless_collect,
80    clippy::needless_pass_by_ref_mut,
81    clippy::needless_raw_strings,
82    clippy::nonstandard_macro_braces,
83    clippy::option_if_let_else,
84    clippy::or_fun_call,
85    clippy::panic_in_result_fn,
86    clippy::partial_pub_fields,
87    clippy::pedantic,
88    clippy::print_stderr,
89    clippy::print_stdout,
90    clippy::pub_without_shorthand,
91    clippy::ref_as_ptr,
92    clippy::rc_buffer,
93    clippy::rc_mutex,
94    clippy::read_zero_byte_vec,
95    clippy::redundant_clone,
96    clippy::redundant_type_annotations,
97    clippy::ref_patterns,
98    clippy::rest_pat_in_fully_bound_structs,
99    clippy::same_name_method,
100    clippy::semicolon_inside_block,
101    clippy::shadow_unrelated,
102    clippy::significant_drop_in_scrutinee,
103    clippy::significant_drop_tightening,
104    clippy::str_to_string,
105    clippy::string_add,
106    clippy::string_lit_as_bytes,
107    clippy::string_lit_chars_any,
108    clippy::string_slice,
109    clippy::string_to_string,
110    clippy::suboptimal_flops,
111    clippy::suspicious_operation_groupings,
112    clippy::suspicious_xor_used_as_pow,
113    clippy::tests_outside_test_module,
114    clippy::todo,
115    clippy::trailing_empty_array,
116    clippy::transmute_undefined_repr,
117    clippy::trivial_regex,
118    clippy::try_err,
119    clippy::undocumented_unsafe_blocks,
120    clippy::unimplemented,
121    clippy::uninhabited_references,
122    clippy::unnecessary_safety_comment,
123    clippy::unnecessary_safety_doc,
124    clippy::unnecessary_self_imports,
125    clippy::unnecessary_struct_initialization,
126    clippy::unneeded_field_pattern,
127    clippy::unused_peekable,
128    clippy::unwrap_in_result,
129    clippy::unwrap_used,
130    clippy::use_debug,
131    clippy::use_self,
132    clippy::useless_let_if_seq,
133    clippy::verbose_file_reads,
134    clippy::wildcard_enum_match_arm,
135    explicit_outlives_requirements,
136    future_incompatible,
137    let_underscore_drop,
138    meta_variable_misuse,
139    missing_abi,
140    missing_copy_implementations,
141    missing_debug_implementations,
142    missing_docs,
143    redundant_lifetimes,
144    semicolon_in_expressions_from_macros,
145    single_use_lifetimes,
146    unit_bindings,
147    unnameable_types,
148    unreachable_pub,
149    unsafe_op_in_unsafe_fn,
150    unstable_features,
151    unused_crate_dependencies,
152    unused_extern_crates,
153    unused_import_braces,
154    unused_lifetimes,
155    unused_macro_rules,
156    unused_qualifications,
157    unused_results,
158    variant_size_differences
159)]
160// TODO: Remove on next `derive_more` major version.
161#![allow(clippy::uninlined_format_args)]
162
163pub mod cli;
164mod cucumber;
165pub mod event;
166pub mod feature;
167pub(crate) mod future;
168pub mod parser;
169pub mod runner;
170pub mod step;
171pub mod tag;
172pub mod writer;
173
174#[cfg(feature = "macros")]
175pub mod codegen;
176#[cfg(feature = "tracing")]
177pub mod tracing;
178
179// TODO: Remove once tests run without complains about it.
180#[cfg(test)]
181mod actually_used_crates_in_tests_and_book {
182    use rand as _;
183    use tempfile as _;
184    use tokio as _;
185}
186
187#[cfg(feature = "macros")]
188use std::{fmt::Debug, path::Path};
189use std::{fmt::Display, future::Future};
190
191#[cfg(feature = "macros")]
192use self::{
193    codegen::{StepConstructor as _, WorldInventory},
194    cucumber::DefaultCucumber,
195};
196
197pub use gherkin;
198
199#[cfg(feature = "macros")]
200#[doc(inline)]
201pub use self::codegen::Parameter;
202#[cfg(feature = "macros")]
203#[doc(inline)]
204pub use cucumber_codegen::{given, then, when, Parameter, World};
205
206#[doc(inline)]
207pub use self::{
208    cucumber::Cucumber,
209    event::Event,
210    parser::Parser,
211    runner::{Runner, ScenarioType},
212    step::Step,
213    writer::{
214        Arbitrary as ArbitraryWriter, Ext as WriterExt, Stats as StatsWriter,
215        Writer,
216    },
217};
218
219/// Represents a shared user-defined state for a [Cucumber] run.
220/// It lives on per-[scenario][0] basis.
221///
222/// This crate doesn't provide out-of-box solution for managing state shared
223/// across [scenarios][0], because we want some friction there to avoid tests
224/// being dependent on each other. If your workflow needs a way to share state
225/// between [scenarios][0] (ex. database connection pool), we recommend using
226/// [`once_cell`][1] crate or organize it other way via [shared state][2].
227///
228/// [0]: https://cucumber.io/docs/gherkin/reference#descriptions
229/// [1]: https://docs.rs/once_cell
230/// [2]: https://doc.rust-lang.org/book/ch16-03-shared-state.html
231/// [Cucumber]: https://cucumber.io
232pub trait World: Sized + 'static {
233    /// Error of creating a new [`World`] instance.
234    type Error: Display;
235
236    /// Creates a new [`World`] instance.
237    fn new() -> impl Future<Output = Result<Self, Self::Error>>;
238
239    #[cfg(feature = "macros")]
240    /// Returns runner for tests with auto-wired steps marked by [`given`],
241    /// [`when`] and [`then`] attributes.
242    #[must_use]
243    fn collection() -> step::Collection<Self>
244    where
245        Self: Debug + WorldInventory,
246    {
247        let mut out = step::Collection::new();
248
249        for given in inventory::iter::<Self::Given> {
250            let (loc, regex, fun) = given.inner();
251            out = out.given(Some(loc), regex(), fun);
252        }
253
254        for when in inventory::iter::<Self::When> {
255            let (loc, regex, fun) = when.inner();
256            out = out.when(Some(loc), regex(), fun);
257        }
258
259        for then in inventory::iter::<Self::Then> {
260            let (loc, regex, fun) = then.inner();
261            out = out.then(Some(loc), regex(), fun);
262        }
263
264        out
265    }
266
267    #[cfg(feature = "macros")]
268    /// Returns default [`Cucumber`] with all the auto-wired [`Step`]s.
269    #[must_use]
270    fn cucumber<I: AsRef<Path>>() -> DefaultCucumber<Self, I>
271    where
272        Self: Debug + WorldInventory,
273    {
274        Cucumber::new().steps(Self::collection())
275    }
276
277    #[cfg(feature = "macros")]
278    /// Runs [`Cucumber`].
279    ///
280    /// [`Feature`]s sourced by [`Parser`] are fed into [`Runner`] where the
281    /// later produces events handled by [`Writer`].
282    ///
283    /// # Panics
284    ///
285    /// If encountered errors while parsing [`Feature`]s or at least one
286    /// [`Step`] panicked.
287    ///
288    /// [`Feature`]: gherkin::Feature
289    fn run<I: AsRef<Path>>(input: I) -> impl Future<Output = ()>
290    where
291        Self: Debug + WorldInventory,
292    {
293        Self::cucumber().run_and_exit(input)
294    }
295
296    #[cfg(feature = "macros")]
297    /// Runs [`Cucumber`] with [`Scenario`]s filter.
298    ///
299    /// [`Feature`]s sourced by [`Parser`] are fed into [`Runner`] where the
300    /// later produces events handled by [`Writer`].
301    ///
302    /// # Panics
303    ///
304    /// If encountered errors while parsing [`Feature`]s or at least one
305    /// [`Step`] panicked.
306    ///
307    /// [`Feature`]: gherkin::Feature
308    /// [`Scenario`]: gherkin::Scenario
309    /// [`Step`]: gherkin::Step
310    fn filter_run<I, F>(input: I, filter: F) -> impl Future<Output = ()>
311    where
312        Self: Debug + WorldInventory,
313        I: AsRef<Path>,
314        F: Fn(
315                &gherkin::Feature,
316                Option<&gherkin::Rule>,
317                &gherkin::Scenario,
318            ) -> bool
319            + 'static,
320    {
321        Self::cucumber().filter_run_and_exit(input, filter)
322    }
323}