cucumber_trellis/
trellis.rs

1use super::{result::TestResult, CucumberTest};
2use futures::future::join_all;
3use path_absolutize::Absolutize;
4use std::{
5    path::{PathBuf, Path},
6    env::current_dir,
7};
8
9pub struct CucumberTrellis {
10    path_base: PathBuf,
11    lib_test: bool,
12    tests: Vec<TestResult>,
13}
14
15impl CucumberTrellis {
16    /// Create a new CucumberTrellis with the given path base for feature files.
17    ///
18    /// If the path base is not provided, the current directory is used,
19    /// and the path base used will be `./tests/features`.
20    pub fn new(feature_path_base: Option<&Path>) -> Self {
21        let path_base = match feature_path_base {
22            None => current_dir().unwrap().join("tests").join("features"),
23            Some(p) => p.absolutize().unwrap().to_path_buf(),
24        };
25
26        if !path_base.exists() {
27            panic!("Path does not exist: {}", path_base.display());
28        }
29
30        if !path_base.is_dir() {
31            panic!("Path is not a directory: {}", path_base.display());
32        }
33
34        Self {
35            path_base,
36            lib_test: false,
37            tests: Vec::new(),
38            // no-coverage:start
39        }
40        // no-coverage:stop
41    }
42
43    /// Add a test to the trellis.
44    pub fn add_test<T: CucumberTest>(&mut self) {
45        let name = format!("{}.feature", T::NAME);
46        let feature_path = self.path_base.join(&name);
47
48        if !feature_path.exists() {
49            panic!("Feature file does not exist: {}", feature_path.display());
50        }
51
52        #[cfg(feature = "libtest")]
53        let result = {
54            use cucumber::{
55                parser::basic::Cli as Parser,
56                runner::basic::Cli as Runner,
57                writer::{
58                    libtest::{ReportTime, Cli as Writer},
59                    Libtest,
60                },
61                cli::Opts,
62                WriterExt,
63            };
64
65            use std::io::stdout;
66
67            let mut cli = Opts::<Parser, Runner, Writer>::parsed();
68            cli.writer.report_time = Some(ReportTime::Plain);
69
70            let mut cucumber = T::cucumber()
71                .with_writer(Libtest::new(stdout()).normalized())
72                .with_cli(cli);
73
74            T::config(&mut cucumber);
75            TestResult::new(async {
76                drop(cucumber.run(feature_path).await);
77            })
78        };
79
80        #[cfg(not(feature = "libtest"))]
81        let result = {
82            let mut cucumber = T::cucumber();
83
84            T::config(&mut cucumber);
85            TestResult::new(async {
86                drop(cucumber.run(feature_path).await);
87            })
88        };
89
90        self.tests.push(result);
91    }
92
93    /// Run all tests in the trellis.
94    pub async fn run_tests(self) {
95        join_all(self.tests).await;
96    }
97}
98
99// no-coverage:start
100#[cfg(test)]
101mod tests {
102    use super::*;
103    use cucumber::World;
104
105    #[test]
106    fn test_new_trellis_with_current_path() {
107        let path_base = current_dir().unwrap().join("tests").join("features");
108        let trellis = CucumberTrellis::new(None);
109
110        assert_eq!(trellis.path_base, path_base, "path base should be `./tests/features`");
111        assert!(trellis.tests.is_empty(), "no tests should be added");
112    }
113
114    #[test]
115    fn test_new_trellis_with_any_path() {
116        let path_base = current_dir().unwrap().join("tests").join("features");
117        let trellis = CucumberTrellis::new(Some(&path_base));
118
119        assert_eq!(trellis.path_base, path_base, "path base should be `./tests/features`");
120        assert!(trellis.tests.is_empty(), "no tests should be added");
121    }
122
123    #[test]
124    #[should_panic]
125    fn test_new_trellis_with_nonexistent_path() {
126        CucumberTrellis::new(Some(&PathBuf::from("!existent")));
127    }
128
129    #[test]
130    #[should_panic]
131    fn test_new_trellis_with_path_on_file() {
132        CucumberTrellis::new(Some(&PathBuf::from("Cargo.toml")));
133    }
134
135    #[test]
136    fn test_add_test() {
137        #[derive(World, Debug, Default)]
138        pub(in super::super) struct SimpleTest;
139
140        impl CucumberTest for SimpleTest {
141            const NAME: &'static str = "simple-test";
142        }
143
144        let mut trellis = CucumberTrellis::new(None);
145        trellis.add_test::<SimpleTest>();
146    }
147
148    #[test]
149    #[should_panic]
150    fn test_add_test_with_nonexistent_feature_file() {
151        #[derive(World, Debug, Default)]
152        pub(in super::super) struct NoFeatureTest;
153
154        impl CucumberTest for NoFeatureTest {
155            const NAME: &'static str = "!existent";
156        }
157
158        let mut trellis = CucumberTrellis::new(None);
159        trellis.add_test::<NoFeatureTest>();
160    }
161}
162// no-coverage:stop