Skip to main content

provola_googletest/
lib.rs

1use provola_core::test_runners::{Only, TestRunnerOpt};
2use provola_core::{AvailableTests, CoreReport, Error, Executable};
3use std::fs::File;
4use std::io::BufReader;
5use std::time::Duration;
6use subprocess::Popen;
7use subprocess::PopenConfig;
8use subprocess::Redirection;
9
10mod report;
11
12fn add_list_argv(mut argv: Vec<String>) -> Vec<String> {
13    argv.push("--gtest_list_tests".to_string());
14    argv.push("--gtest_color=no".to_string());
15    argv
16}
17
18fn add_run_argv(mut argv: Vec<String>, report_path: &str) -> Vec<String> {
19    argv.push(format!("--gtest_output=json:{}", report_path));
20    argv.push("--gtest_color=no".to_string());
21    argv
22}
23
24fn run_exec_with_argv(argv: Vec<String>) -> Result<String, Error> {
25    let mut p = Popen::create(
26        &argv,
27        PopenConfig {
28            stdin: Redirection::None,
29            stdout: Redirection::Pipe,
30            stderr: Redirection::Pipe,
31            ..Default::default()
32        },
33    )?;
34
35    let (out, _err) = p.communicate(None)?;
36
37    // TODO Timeout from configuration
38    let timeout = Duration::from_secs(3600);
39
40    if let Some(_exit_status) = p.wait_timeout(timeout)? {
41        log::trace!("done");
42    } else {
43        log::warn!("Terminate subprocess");
44        p.terminate()?;
45    }
46
47    Ok(out.unwrap_or_default())
48}
49
50fn extract_test_suite_name(s: &str) -> String {
51    s.chars().take_while(|&x| x != '.').collect()
52}
53
54fn extract_test_case_name(s: &str) -> String {
55    s.chars().skip(2).collect()
56}
57
58fn parse_available_tests(s: &str) -> Result<AvailableTests, Error> {
59    let lines = s.lines().skip(1);
60
61    let mut test_suite: Option<String> = None;
62    let mut tests = AvailableTests::default();
63
64    for line in lines {
65        // At least 2 chars, one for the name, one for dot or space
66        // "X." is the shortest test suite name
67        // "  X" is the shortest test case name
68        match line.chars().next() {
69            Some(' ') => {
70                if let Some(test_suite) = &test_suite {
71                    tests.push(test_suite.clone(), extract_test_case_name(line));
72                }
73            }
74            Some(_) => {
75                test_suite = Some(extract_test_suite_name(line));
76            }
77            None => {
78                // invalid
79            }
80        }
81    }
82
83    Ok(tests)
84}
85
86fn generate_available_tests(executable: &Executable) -> Result<AvailableTests, Error> {
87    let argv = add_list_argv(executable.into());
88    let out = run_exec_with_argv(argv)?;
89    parse_available_tests(&out)
90}
91
92fn generate_report(executable: &Executable, test_filter: &TestFilter) -> Result<CoreReport, Error> {
93    let report_path = "googletest_report.json";
94    let executable = executable.into();
95
96    let mut argv = add_run_argv(executable, report_path);
97
98    if let Some(test_filter_s) = &test_filter.0 {
99        argv.push(format!("--gtest_filter={}", test_filter_s));
100    }
101
102    run_exec_with_argv(argv)?;
103
104    let file = File::open(report_path).unwrap();
105    let reader = BufReader::new(file);
106    let gtest_rep: report::UnitTest = serde_json::from_reader(reader).unwrap();
107    let core_rep = CoreReport::from(gtest_rep);
108    Ok(core_rep)
109}
110
111pub struct TestRunner {
112    executable: Executable,
113    available_tests: AvailableTests,
114}
115
116#[derive(Default)]
117struct TestFilter(Option<String>);
118
119fn make_test_filter(opt: &TestRunnerOpt, tests: &AvailableTests) -> Result<TestFilter, Error> {
120    let fqtn = match opt.only {
121        Only::SingleByIndex(index) => tests.get(index),
122        Only::SingleByFqtc(fqtc) => tests.get_by_id(fqtc),
123        Only::All => None,
124    };
125
126    let s = fqtn.map(|x| format!("{}.{}", x.test_suite.0, x.test_case.0));
127
128    Ok(TestFilter(s))
129}
130
131impl TestRunner {
132    fn generate_report(&self, opt: &TestRunnerOpt) -> Result<CoreReport, Error> {
133        let test_filter = make_test_filter(opt, &self.available_tests);
134        generate_report(&self.executable, &test_filter?)
135    }
136}
137
138impl From<Executable> for TestRunner {
139    fn from(executable: Executable) -> Self {
140        // TODO Fix unwrap
141        let available_tests = generate_available_tests(&executable).unwrap();
142        TestRunner {
143            executable,
144            available_tests,
145        }
146    }
147}
148
149impl provola_core::test_runners::TestRunner for TestRunner {
150    fn run(&self, opt: &TestRunnerOpt) -> Result<provola_core::TestResult, provola_core::Error> {
151        let report = self.generate_report(opt)?;
152        let result = report.into();
153        Ok(result)
154    }
155
156    fn list(&self, _opt: &TestRunnerOpt) -> Result<AvailableTests, Error> {
157        generate_available_tests(&self.executable)
158    }
159}
160
161#[cfg(test)]
162mod tests {
163    use std::path::PathBuf;
164
165    use super::*;
166
167    fn make_exec() -> Executable {
168        let path = PathBuf::from("./examples/data/build/example");
169        Executable::from(path)
170    }
171
172    // Ignored because example must be built first
173    #[ignore]
174    #[test]
175    fn run_valid_executable() {
176        let exec = make_exec();
177        let test_filter = TestFilter::default();
178        assert!(generate_report(&exec, &test_filter).is_ok());
179    }
180
181    // Ignored because example must be built first
182    #[ignore]
183    #[test]
184    fn from_valid_executable() {
185        let exec = make_exec();
186        let tr = TestRunner::from(exec);
187        let tr: Box<dyn provola_core::test_runners::TestRunner> = Box::new(tr);
188        let tr_opt = TestRunnerOpt::default();
189        assert!(tr.run(&tr_opt).is_ok());
190    }
191
192    // Ignored because example must be built first
193    #[ignore]
194    #[test]
195    fn generate_available_tests_from_valid_executable() {
196        let exec = make_exec();
197        let list = generate_available_tests(&exec).unwrap();
198        assert_eq!(list.len(), 4);
199    }
200
201    #[test]
202    fn parse_gtest_list_tests_output() {
203        let s = r#"Running main() from provola-googletest/examples/data/googletest/googletest/src/gtest_main.cc
204Foo.
205  Foo1
206  Foo2
207Bar.
208  Bar1
209  Bar2"#;
210        let list = parse_available_tests(s).unwrap();
211        insta::assert_debug_snapshot!(&list);
212    }
213}