1use std::{env, fs, io};
2use std::fmt::Write;
3use std::fs::File;
4use std::io::{BufRead, BufReader};
5use std::io::Read;
6use std::path::{Path, PathBuf};
7use std::process::{Command, Stdio};
8
9const TEST_STDOUT_FILENAME: &str = "test.stdout";
11
12const EXPECTED_STDOUT_FILENAME : &str = "expected.stdout";
14
15const TEST_STDIN_FILENAME : &str = "test.stdin";
17
18const TEST_STDERR_FILENAME : &str = "test.stderr";
20
21const TEST_FILE_FILENAME: &str = "test.file";
23
24const EXPECTED_FILE_FILENAME : &str = "expected.file";
26
27const TEST_ARGS_FILENAME: &str = "test.args";
29
30pub fn run_example(source_file: &str, runner: &str, flowrex: bool, native: bool) {
32 let mut sample_dir = PathBuf::from(source_file);
33 sample_dir.pop();
34
35 compile_example(&sample_dir, runner);
36
37 println!("\n\tRunning example: {}", sample_dir.display());
38 println!("\t\tRunner: {}", runner);
39 println!("\t\tSTDIN is read from {TEST_STDIN_FILENAME}");
40 println!("\t\tArguments are read from {TEST_ARGS_FILENAME}");
41 println!("\t\tSTDOUT is saved in {TEST_STDOUT_FILENAME}");
42 println!("\t\tSTDERR is saved in {TEST_STDERR_FILENAME}");
43 println!("\t\tFile output is saved in {TEST_FILE_FILENAME}");
44
45 let _ = fs::remove_file(sample_dir.join(TEST_STDERR_FILENAME));
47 let _ = fs::remove_file(sample_dir.join(TEST_FILE_FILENAME));
48 let _ = fs::remove_file(sample_dir.join(TEST_STDOUT_FILENAME));
49
50 let mut runner_args: Vec<String> = if native {
51 vec!["--native".into()]
52 } else {
53 vec![]
54 };
55
56 if runner == "flowrgui" {
57 runner_args.push("--auto".into());
58 }
59
60 let flowrex_child = if flowrex {
61 runner_args.push("--threads".into());
63 runner_args.push("0".into());
64 Some(Command::new("flowrex").spawn().expect("Could not spawn flowrex"))
65 } else {
66 None
67 };
68
69 runner_args.push( "manifest.json".into());
70 runner_args.append(&mut args(&sample_dir).expect("Could not get flow args"));
71
72 let output = File::create(sample_dir.join(TEST_STDOUT_FILENAME))
73 .expect("Could not create Test StdOutput File");
74 let error = File::create(sample_dir.join(TEST_STDERR_FILENAME))
75 .expect("Could not create Test StdError File ");
76
77 println!("\tCommand line: '{} {}'", runner, runner_args.join(" "));
78 let mut runner_child = Command::new(runner)
79 .args(runner_args)
80 .current_dir(sample_dir.canonicalize().expect("Could not canonicalize path"))
81 .stdin(Stdio::piped())
82 .stdout(Stdio::from(output))
83 .stderr(Stdio::from(error))
84 .spawn().expect("Could not spawn runner");
85
86 let stdin_file = sample_dir.join(TEST_STDIN_FILENAME);
87 if stdin_file.exists() {
88 let _ = Command::new("cat")
89 .args(vec![stdin_file])
90 .stdout(runner_child.stdin.take().expect("Could not take STDIN"))
91 .spawn();
92 }
93
94 runner_child.wait_with_output().expect("Could not get sub process output");
95
96 if let Some(mut child) = flowrex_child {
98 println!("Killing 'flowrex'");
99 child.kill().expect("Failed to kill server child process");
100 }
101}
102
103pub fn test_example(source_file: &str, runner: &str, flowrex: bool, native: bool) {
105 let _ = env::set_current_dir(PathBuf::from(env!("CARGO_MANIFEST_DIR"))
106 .parent().expect("Could not cd into flowr directory")
107 .parent().expect("Could not cd into flow directory"));
108
109 run_example(source_file, runner, flowrex, native);
110 check_test_output(source_file);
111}
112
113fn args(sample_dir: &Path) -> io::Result<Vec<String>> {
115 let args_file = sample_dir.join(TEST_ARGS_FILENAME);
116
117 let mut args = Vec::new();
118
119 if let Ok(f) = File::open(args_file) {
121 let f = BufReader::new(f);
122
123 for line in f.lines() {
124 args.push(line?);
125 }
126 }
127
128 Ok(args)
129}
130
131pub fn compile_example(sample_path: &Path, runner: &str) {
133 let sample_dir = sample_path.to_string_lossy();
134
135 let mut command = Command::new("flowc");
136 let command_args = vec!["-d", "-g", "-c", "-O", "-r", runner, &sample_dir];
143
144 let stat = command
145 .args(&command_args)
146 .status().expect("Could not get status of 'flowc' execution");
147
148 if !stat.success() {
149 panic!("Error building example, command line\n flowc {}", command_args.join(" "));
150 }
151}
152
153pub fn check_test_output(source_file: &str) {
154 let mut sample_dir = PathBuf::from(source_file);
155 sample_dir.pop();
156
157 let error_output = sample_dir.join(TEST_STDERR_FILENAME);
158 if error_output.exists() {
159 let contents = fs::read_to_string(&error_output).expect("Could not read from {STDERR_FILENAME} file");
160
161 if !contents.is_empty() {
162 panic!(
163 "Sample {:?} produced output to STDERR written to '{}'\n{contents}",
164 sample_dir.file_name().expect("Could not get directory file name"),
165 error_output.display());
166 }
167 }
168
169 compare_and_fail(sample_dir.join(EXPECTED_STDOUT_FILENAME), sample_dir.join(TEST_STDOUT_FILENAME));
170 compare_and_fail(sample_dir.join(EXPECTED_FILE_FILENAME), sample_dir.join(TEST_FILE_FILENAME));
171}
172
173fn compare_and_fail(expected_path: PathBuf, actual_path: PathBuf) {
174 if expected_path.exists() {
175 let diff = Command::new("diff")
176 .args(vec![&expected_path, &actual_path])
177 .stdin(Stdio::inherit())
178 .stderr(Stdio::inherit())
179 .stdout(Stdio::inherit())
180 .spawn()
181 .expect("Could not get child process");
182 let output = diff.wait_with_output().expect("Could not get diff output");
183 if output.status.success() {
184 return;
185 }
186 panic!("Contents of '{}' doesn't match the expected contents in '{}'",
187 actual_path.display(), expected_path.display());
188 }
189}
190
191pub fn execute_flow_client_server(example_name: &str, manifest: PathBuf) {
193 let crate_dir = PathBuf::from(env!("CARGO_MANIFEST_DIR"));
194 let root_dir = crate_dir.parent().expect("Could not go to parent flowr dir");
195 let samples_dir = root_dir.join("examples").join(example_name);
196
197 let mut server_command = Command::new("flowrcli");
198
199 let server_args = vec!["-n", "-s"];
201
202 println!("Starting 'flowrcli' as server with command line: 'flowrcli {}'", server_args.join(" "));
203
204 let mut server = server_command
206 .args(server_args)
207 .stdin(Stdio::piped())
208 .stdout(Stdio::piped())
209 .stderr(Stdio::piped())
210 .spawn()
211 .expect("Failed to spawn flowrcli");
212
213 let stdout = server.stdout.as_mut().expect("Could not read stdout of server");
215 let mut reader = BufReader::new(stdout);
216 let mut discovery_port = String::new();
217 reader.read_line(&mut discovery_port).expect("Could not read line");
218
219 let mut client = Command::new("flowrcli");
220 let manifest_str = manifest.to_string_lossy();
221 let client_args = vec!["-c", discovery_port.trim(), &manifest_str];
222 println!("Starting 'flowrcli' client with command line: 'flowr {}'", client_args.join(" "));
223
224 let mut runner = client
226 .args(client_args)
227 .stdin(Stdio::piped())
228 .stdout(Stdio::piped())
229 .stderr(Stdio::piped())
230 .spawn()
231 .expect("Could not spawn flowrcli process");
232
233 let mut actual_stderr = String::new();
235 if let Some(ref mut stderr) = runner.stderr {
236 for line in BufReader::new(stderr).lines() {
237 let _ = writeln!(actual_stderr, "{}", &line.expect("Could not read line"));
238 }
239 }
240
241 let mut actual_stdout = String::new();
243 if let Some(ref mut stdout) = runner.stdout {
244 for line in BufReader::new(stdout).lines() {
245 let _ = writeln!(actual_stdout, "{}", &line.expect("Could not read line"));
246 }
247 }
248
249 println!("Killing 'flowr' server");
250 server.kill().expect("Failed to kill server child process");
251
252 if !actual_stderr.is_empty() {
253 eprintln!("STDERR: {actual_stderr}");
254 panic!("Failed due to STDERR output")
255 }
256
257 let expected_stdout = read_file(&samples_dir, "expected.stdout");
258 if expected_stdout != actual_stdout {
259 println!("Expected STDOUT:\n{expected_stdout}");
260 println!("Actual STDOUT:\n{actual_stdout}");
261 panic!("Actual STDOUT did not match expected.stdout");
262 }
263}
264
265fn read_file(test_dir: &Path, file_name: &str) -> String {
266 let expected_file = test_dir.join(file_name);
267 if !expected_file.exists() {
268 return "".into();
269 }
270
271 let mut f = File::open(&expected_file).expect("Could not open file");
272 let mut buffer = Vec::new();
273 f.read_to_end(&mut buffer).expect("Could not read from file");
274 String::from_utf8(buffer).expect("Could not convert to String")
275}