provola_googletest/
lib.rs1use 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 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 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 }
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 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 #[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 #[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 #[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}