1use std::ffi::OsStr;
2use std::io::{Read, Write};
3use std::process::{Child, Stdio};
4
5#[macro_export]
8macro_rules! run_current_example {
9 () => {
10 $crate::run_current_example!(::std::iter::empty::<&str>())
11 };
12 ($args:literal) => {
13 $crate::run_current_example!(str::split_whitespace($args))
14 };
15 ($args:expr $(,)?) => {
16 $crate::ExampleChild::run_new(
17 &::std::env::var("CARGO_PKG_NAME").unwrap(),
18 &$crate::extract_example_name(file!()).expect("Failed to determine example name."),
19 $args,
20 )
21 };
22}
23
24pub struct ExampleChild {
28 child: Child,
29 output_buffer: Vec<u8>,
30 output_len: usize,
31}
32impl ExampleChild {
33 pub fn run_new(
34 pkg_name: &str,
35 test_name: &str,
36 args: impl IntoIterator<Item = impl AsRef<OsStr>>,
37 ) -> Self {
38 let mut cargo_cmd = std::process::Command::new("cargo");
39 cargo_cmd
40 .args(["run", "--frozen", "--no-default-features"])
41 .args(["-p", pkg_name, "--example", test_name]);
42 if let Some(features) = trybuild_internals_api::features::find()
43 && !features.is_empty()
44 {
45 cargo_cmd.args(["--features", &features.join(",")]);
46 }
47 cargo_cmd
48 .arg("--")
49 .args(args)
50 .env("RUNNING_AS_EXAMPLE_TEST", "1");
51
52 log::info!("Running cargo command: {:?}", cargo_cmd);
53
54 let child = cargo_cmd
55 .stdin(Stdio::piped())
56 .stdout(Stdio::piped())
57 .spawn()
58 .unwrap();
59 Self {
60 child,
61 output_buffer: vec![0; 1024],
62 output_len: 0,
63 }
64 }
65
66 pub fn read_string(&mut self, wait_for_string: &str) {
74 self.read_regex(®ex::escape(wait_for_string));
75 }
76
77 pub fn read_regex(&mut self, wait_for_regex: &str) {
79 let stdout = self.child.stdout.as_mut().unwrap();
80 let re = regex::Regex::new(wait_for_regex).unwrap();
81
82 while !re.is_match(&String::from_utf8_lossy(
83 &self.output_buffer[0..self.output_len],
84 )) {
85 eprintln!(
86 "waiting ({}):\n{}",
87 wait_for_regex,
88 String::from_utf8_lossy(&self.output_buffer[0..self.output_len])
89 );
90
91 while self.output_buffer.len() - self.output_len < 1024 {
92 self.output_buffer
93 .resize(self.output_buffer.len() + 1024, 0);
94 }
95
96 let bytes_read = stdout
97 .read(&mut self.output_buffer[self.output_len..])
98 .unwrap();
99 self.output_len += bytes_read;
100
101 if 0 == bytes_read {
102 panic!("Child process exited before a match was found.");
103 }
104 }
105 }
106
107 pub fn write_line(&mut self, line: &str) {
109 let stdin = self.child.stdin.as_mut().unwrap();
110 stdin.write_all(line.as_bytes()).unwrap();
111 stdin.write_all(b"\n").unwrap();
112 stdin.flush().unwrap();
113 }
114}
115
116impl Drop for ExampleChild {
121 fn drop(&mut self) {
122 #[cfg(target_family = "windows")]
123 let _ = self.child.kill(); #[cfg(not(target_family = "windows"))]
125 self.child.kill().unwrap();
126
127 self.child.wait().unwrap();
128 }
129}
130
131pub fn extract_example_name(file: &str) -> Option<String> {
133 let pathbuf = std::path::PathBuf::from(file);
134 let mut path = pathbuf.as_path();
135 while path.parent()?.file_name()? != "examples" {
136 path = path.parent().unwrap();
137 }
138 Some(path.file_stem()?.to_string_lossy().into_owned())
139}