#[cfg(feature = "timestamps")]
use std::time::SystemTime;
use std::{
any::Any,
hash::{Hash, Hasher},
sync::Arc,
};
use derive_more::with_trait::{
AsRef, Debug, Deref, DerefMut, Display, Error, From, Into,
};
use ref_cast::RefCast;
use crate::{step, writer::basic::coerce_error};
pub type Info = Arc<dyn Any + Send + 'static>;
#[derive(AsRef, Clone, Copy, Debug, Deref, DerefMut)]
#[non_exhaustive]
pub struct Event<T: ?Sized> {
#[cfg(feature = "timestamps")]
pub at: SystemTime,
#[as_ref]
#[deref]
#[deref_mut]
pub value: T,
}
impl<T> Event<T> {
#[cfg_attr(
not(feature = "timestamps"),
expect(clippy::missing_const_for_fn, reason = "API compliance")
)]
#[must_use]
pub fn new(value: T) -> Self {
Self {
#[cfg(feature = "timestamps")]
at: SystemTime::now(),
value,
}
}
#[must_use]
pub fn into_inner(self) -> T {
self.value
}
#[must_use]
pub fn split(self) -> (T, Metadata) {
self.replace(())
}
#[must_use]
pub fn insert<V>(self, value: V) -> Event<V> {
self.replace(value).1
}
#[must_use]
pub fn map<V>(self, f: impl FnOnce(T) -> V) -> Event<V> {
let (val, meta) = self.split();
meta.insert(f(val))
}
#[must_use]
pub fn replace<V>(self, value: V) -> (T, Event<V>) {
let event = Event {
#[cfg(feature = "timestamps")]
at: self.at,
value,
};
(self.value, event)
}
}
pub type Metadata = Event<()>;
impl Metadata {
#[must_use]
pub fn wrap<V>(self, value: V) -> Event<V> {
self.replace(value).1
}
}
#[derive(Clone, Copy, Debug, Eq, Hash, PartialEq)]
pub struct Retries {
pub current: usize,
pub left: usize,
}
impl Retries {
#[must_use]
pub const fn initial(left: usize) -> Self {
Self { left, current: 0 }
}
#[must_use]
pub fn next_try(self) -> Option<Self> {
self.left
.checked_sub(1)
.map(|left| Self { left, current: self.current + 1 })
}
}
#[derive(Debug)]
pub enum Cucumber<World> {
Started,
Feature(Source<gherkin::Feature>, Feature<World>),
ParsingFinished {
features: usize,
rules: usize,
scenarios: usize,
steps: usize,
parser_errors: usize,
},
Finished,
}
impl<World> Clone for Cucumber<World> {
fn clone(&self) -> Self {
match self {
Self::Started => Self::Started,
Self::Feature(f, ev) => Self::Feature(f.clone(), ev.clone()),
Self::ParsingFinished {
features,
rules,
scenarios,
steps,
parser_errors,
} => Self::ParsingFinished {
features: *features,
rules: *rules,
scenarios: *scenarios,
steps: *steps,
parser_errors: *parser_errors,
},
Self::Finished => Self::Finished,
}
}
}
impl<World> Cucumber<World> {
#[must_use]
pub fn feature_started(feat: impl Into<Source<gherkin::Feature>>) -> Self {
Self::Feature(feat.into(), Feature::Started)
}
#[must_use]
pub fn rule_started(
feat: impl Into<Source<gherkin::Feature>>,
rule: impl Into<Source<gherkin::Rule>>,
) -> Self {
Self::Feature(feat.into(), Feature::Rule(rule.into(), Rule::Started))
}
#[must_use]
pub fn feature_finished(feat: impl Into<Source<gherkin::Feature>>) -> Self {
Self::Feature(feat.into(), Feature::Finished)
}
#[must_use]
pub fn rule_finished(
feat: impl Into<Source<gherkin::Feature>>,
rule: impl Into<Source<gherkin::Rule>>,
) -> Self {
Self::Feature(feat.into(), Feature::Rule(rule.into(), Rule::Finished))
}
#[must_use]
pub fn scenario(
feat: impl Into<Source<gherkin::Feature>>,
rule: Option<impl Into<Source<gherkin::Rule>>>,
scenario: impl Into<Source<gherkin::Scenario>>,
event: RetryableScenario<World>,
) -> Self {
Self::Feature(
feat.into(),
if let Some(r) = rule {
Feature::Rule(r.into(), Rule::Scenario(scenario.into(), event))
} else {
Feature::Scenario(scenario.into(), event)
},
)
}
}
#[derive(Debug)]
pub enum Feature<World> {
Started,
Rule(Source<gherkin::Rule>, Rule<World>),
Scenario(Source<gherkin::Scenario>, RetryableScenario<World>),
Finished,
}
impl<World> Clone for Feature<World> {
fn clone(&self) -> Self {
match self {
Self::Started => Self::Started,
Self::Rule(r, ev) => Self::Rule(r.clone(), ev.clone()),
Self::Scenario(s, ev) => Self::Scenario(s.clone(), ev.clone()),
Self::Finished => Self::Finished,
}
}
}
#[derive(Debug)]
pub enum Rule<World> {
Started,
Scenario(Source<gherkin::Scenario>, RetryableScenario<World>),
Finished,
}
impl<World> Clone for Rule<World> {
fn clone(&self) -> Self {
match self {
Self::Started => Self::Started,
Self::Scenario(s, ev) => Self::Scenario(s.clone(), ev.clone()),
Self::Finished => Self::Finished,
}
}
}
#[derive(Debug)]
pub enum Step<World> {
Started,
Skipped,
Passed(regex::CaptureLocations, Option<step::Location>),
Failed(
Option<regex::CaptureLocations>,
Option<step::Location>,
Option<Arc<World>>,
StepError,
),
}
impl<World> Clone for Step<World> {
fn clone(&self) -> Self {
match self {
Self::Started => Self::Started,
Self::Skipped => Self::Skipped,
Self::Passed(captures, loc) => Self::Passed(captures.clone(), *loc),
Self::Failed(captures, loc, w, info) => {
Self::Failed(captures.clone(), *loc, w.clone(), info.clone())
}
}
}
}
#[derive(Clone, Debug, Display, Error, From)]
pub enum StepError {
#[display("Step doesn't match any function")]
NotFound,
#[display("Step match is ambiguous: {_0}")]
AmbiguousMatch(step::AmbiguousMatchError),
#[display("Step panicked. Captured output: {}", coerce_error(_0))]
Panic(#[error(not(source))] Info),
}
#[derive(Clone, Copy, Debug, Display)]
#[display("{self:?}")]
pub enum HookType {
Before,
After,
}
#[derive(Debug)]
pub enum Hook<World> {
Started,
Passed,
Failed(Option<Arc<World>>, Info),
}
impl<World> Clone for Hook<World> {
fn clone(&self) -> Self {
match self {
Self::Started => Self::Started,
Self::Passed => Self::Passed,
Self::Failed(w, i) => Self::Failed(w.clone(), Arc::clone(i)),
}
}
}
#[derive(Debug)]
pub enum Scenario<World> {
Started,
Hook(HookType, Hook<World>),
Background(Source<gherkin::Step>, Step<World>),
Step(Source<gherkin::Step>, Step<World>),
Log(String),
Finished,
}
impl<World> Clone for Scenario<World> {
fn clone(&self) -> Self {
match self {
Self::Started => Self::Started,
Self::Hook(ty, ev) => Self::Hook(*ty, ev.clone()),
Self::Background(bg, ev) => {
Self::Background(bg.clone(), ev.clone())
}
Self::Step(st, ev) => Self::Step(st.clone(), ev.clone()),
Self::Log(msg) => Self::Log(msg.clone()),
Self::Finished => Self::Finished,
}
}
}
impl<World> Scenario<World> {
#[must_use]
pub const fn hook_started(which: HookType) -> Self {
Self::Hook(which, Hook::Started)
}
#[must_use]
pub const fn hook_passed(which: HookType) -> Self {
Self::Hook(which, Hook::Passed)
}
#[must_use]
pub fn hook_failed(
which: HookType,
world: Option<Arc<World>>,
info: Info,
) -> Self {
Self::Hook(which, Hook::Failed(world, info))
}
#[must_use]
pub fn step_started(step: impl Into<Source<gherkin::Step>>) -> Self {
Self::Step(step.into(), Step::Started)
}
#[must_use]
pub fn background_step_started(
step: impl Into<Source<gherkin::Step>>,
) -> Self {
Self::Background(step.into(), Step::Started)
}
#[must_use]
pub fn step_passed(
step: impl Into<Source<gherkin::Step>>,
captures: regex::CaptureLocations,
loc: Option<step::Location>,
) -> Self {
Self::Step(step.into(), Step::Passed(captures, loc))
}
#[must_use]
pub fn background_step_passed(
step: impl Into<Source<gherkin::Step>>,
captures: regex::CaptureLocations,
loc: Option<step::Location>,
) -> Self {
Self::Background(step.into(), Step::Passed(captures, loc))
}
#[must_use]
pub fn step_skipped(step: impl Into<Source<gherkin::Step>>) -> Self {
Self::Step(step.into(), Step::Skipped)
}
#[must_use]
pub fn background_step_skipped(
step: impl Into<Source<gherkin::Step>>,
) -> Self {
Self::Background(step.into(), Step::Skipped)
}
#[must_use]
pub fn step_failed(
step: impl Into<Source<gherkin::Step>>,
captures: Option<regex::CaptureLocations>,
loc: Option<step::Location>,
world: Option<Arc<World>>,
info: impl Into<StepError>,
) -> Self {
Self::Step(step.into(), Step::Failed(captures, loc, world, info.into()))
}
#[must_use]
pub fn background_step_failed(
step: impl Into<Source<gherkin::Step>>,
captures: Option<regex::CaptureLocations>,
loc: Option<step::Location>,
world: Option<Arc<World>>,
info: impl Into<StepError>,
) -> Self {
Self::Background(
step.into(),
Step::Failed(captures, loc, world, info.into()),
)
}
#[must_use]
pub const fn with_retries(
self,
retries: Option<Retries>,
) -> RetryableScenario<World> {
RetryableScenario { event: self, retries }
}
}
#[derive(Debug)]
pub struct RetryableScenario<World> {
pub event: Scenario<World>,
pub retries: Option<Retries>,
}
impl<World> Clone for RetryableScenario<World> {
fn clone(&self) -> Self {
Self { event: self.event.clone(), retries: self.retries }
}
}
#[derive(Clone, Debug)]
pub enum ScenarioFinished {
BeforeHookFailed(Info),
StepPassed,
StepSkipped,
StepFailed(
Option<regex::CaptureLocations>,
Option<step::Location>,
StepError,
),
}
#[derive(AsRef, Debug, Deref, Display, From, Into, RefCast)]
#[as_ref(forward)]
#[debug("{:?}", **_0)]
#[debug(bound(T: Debug))]
#[deref(forward)]
#[repr(transparent)]
pub struct Source<T: ?Sized>(Arc<T>);
impl<T> Source<T> {
#[must_use]
pub fn new(value: T) -> Self {
Self(Arc::new(value))
}
}
impl<T> Clone for Source<T> {
fn clone(&self) -> Self {
Self(Arc::clone(&self.0))
}
}
impl<T: ?Sized> Eq for Source<T> {}
impl<T: ?Sized> PartialEq for Source<T> {
fn eq(&self, other: &Self) -> bool {
Arc::ptr_eq(&self.0, &other.0)
}
}
impl<T: ?Sized> Hash for Source<T> {
fn hash<H: Hasher>(&self, state: &mut H) {
Arc::as_ptr(&self.0).hash(state);
}
}