use std::{env, fs, io};
use std::fs::File;
use std::io::{BufRead, BufReader, ErrorKind};
use std::path::Path;
use std::process::{Command, Stdio};
fn main() -> io::Result<()> {
println!("`flowsample` version {}", env!("CARGO_PKG_VERSION"));
println!(
"Current Working Directory: `{}`",
std::env::current_dir().expect("Could not get working directory").display()
);
let samples_root = env!("CARGO_MANIFEST_DIR");
let samples_dir = Path::new(samples_root);
let root_dir = samples_dir.parent().expect("Could not get parent directory");
let samples_out_dir = root_dir.join("target/flowsamples");
println!("Samples Root Directory: `{}`", samples_root);
let args: Vec<String> = env::args().collect();
match args.len() {
1 => {
for entry in fs::read_dir(samples_root)? {
let e = entry?;
if e.file_type()?.is_dir() && e.path().join("root.toml").exists() {
run_sample(&e.path(), &samples_out_dir.join(e.file_name()))?
}
}
}
2 => {
run_sample(&samples_dir.join(&args[1]), &samples_out_dir.join(&args[1]))?
}
_ => eprintln!("Usage: {} <optional_sample_directory_name>", args[0]),
}
Ok(())
}
fn get_context_root() -> Result<String, String> {
let samples_dir = Path::new(env!("CARGO_MANIFEST_DIR")).parent()
.ok_or("Could not get parent dir")?;
Ok(samples_dir.join("flowr/src/context").to_str()
.expect("Could not convert path to String").to_string())
}
fn run_sample(sample_dir: &Path, output_dir: &Path) -> io::Result<()> {
let _ = fs::remove_file(output_dir.join("test.err"));
let _ = fs::remove_file(output_dir.join("test.file"));
let _ = fs::remove_file(output_dir.join("test.output"));
let manifest_path = output_dir.join("manifest.json");
println!("\n\tRunning Sample: {:?}", sample_dir.file_name());
assert!(manifest_path.exists(), "Manifest not found at '{}'", manifest_path.display());
println!("\tSTDIN is read from test.input, Arguments are read from test.arguments");
println!("\tSTDOUT is sent to test.output, STDERR to test.err and file output to test.file");
let mut command_args: Vec<String> = vec!["--native".into(), manifest_path.display().to_string()];
command_args.push("-C".into());
let context_root = get_context_root().expect("Could not get context root directory");
command_args.push(context_root);
command_args.append(&mut args(sample_dir)?);
let output = File::create(output_dir.join("test.output"))
.expect("Could not get directory as string");
let error = File::create(output_dir.join("test.err"))
.expect("Could not get directory as string");
println!("Command line: 'flowr {}'", command_args.join(" "));
match Command::new("flowr")
.args(command_args)
.current_dir(output_dir.canonicalize()?)
.stdin(Stdio::piped())
.stdout(Stdio::from(output))
.stderr(Stdio::from(error))
.spawn()
{
Ok(mut flowr_child) => {
let _ = Command::new("cat")
.args(vec![sample_dir.join("test.input")])
.stdout(flowr_child.stdin.take().ok_or_else(|| {
io::Error::new(
io::ErrorKind::Other,
"Could not take STDIN of `flowr` process",
)
})?)
.spawn();
flowr_child.wait_with_output()?;
Ok(())
}
Err(e) => match e.kind() {
ErrorKind::NotFound => Err(io::Error::new(
io::ErrorKind::Other,
format!("`flowc` was not found! Check your $PATH. {}", e),
)),
_ => Err(io::Error::new(
io::ErrorKind::Other,
format!("Unexpected error running `flowc`: {}", e),
)),
},
}
}
fn args(sample_dir: &Path) -> io::Result<Vec<String>> {
let args_file = sample_dir.join("test.arguments");
let f = File::open(&args_file)?;
let f = BufReader::new(f);
let mut args = Vec::new();
for line in f.lines() {
args.push(line?);
}
Ok(args)
}
#[cfg(test)]
mod test {
use std::fs;
use std::path::{Path, PathBuf};
use std::process::{Command, Stdio};
use serial_test::serial;
fn test_sample(name: &str) {
let samples_root = env!("CARGO_MANIFEST_DIR");
let samples_dir = Path::new(samples_root);
let sample_dir = samples_dir.join(name);
let root_dir = samples_dir.parent().expect("Could not get parent directory");
let samples_out_dir = root_dir.join("target/flowsamples");
let output_dir = samples_out_dir.join(name);
super::run_sample(&sample_dir, &output_dir).expect("Running of test sample failed");
check_test_output(&sample_dir, &output_dir);
let _ = fs::remove_file(output_dir.join("test.err"));
let _ = fs::remove_file(output_dir.join("test.file"));
let _ = fs::remove_file(output_dir.join("test.output"));
}
fn compare_and_fail(expected_path: PathBuf, actual_path: PathBuf) {
if expected_path.exists() {
let diff = Command::new("diff")
.args(vec![&expected_path, &actual_path])
.stdin(Stdio::inherit())
.stderr(Stdio::inherit())
.stdout(Stdio::inherit())
.spawn()
.expect("Could not get child process");
let output = diff.wait_with_output().expect("Could not get child process output");
if output.status.success() {
return;
}
panic!("Contents of '{}' doesn't match the expected contents in '{}'",
actual_path.display(), expected_path.display());
}
}
fn check_test_output(sample_dir: &Path, output_dir: &Path) {
let error_output = output_dir.join("test.err");
if error_output.exists() {
let contents = fs::read_to_string(&error_output).expect("Could not read from 'test.err' file");
if !contents.is_empty() {
panic!(
"Sample {:?} produced output to STDERR written to '{}'\n{}",
sample_dir.file_name().expect("Could not get directory file name"),
error_output.display(), contents
);
}
}
compare_and_fail(sample_dir.join("expected.output"), output_dir.join("test.output"));
compare_and_fail(sample_dir.join("expected.file"), output_dir.join("test.file"));
}
#[test]
#[serial]
fn test_args() {
test_sample("args");
}
#[test]
#[serial]
fn test_arrays() {
test_sample("arrays");
}
#[test]
#[serial]
fn test_factorial() {
test_sample("factorial");
}
#[test]
#[serial]
fn test_fibonacci() {
test_sample("fibonacci");
}
#[test]
#[serial]
fn test_hello_world() {
test_sample("hello-world");
}
#[test]
#[serial]
fn test_matrix_mult() {
test_sample("matrix_mult");
}
#[test]
#[serial]
fn test_pipeline() {
test_sample("pipeline");
}
#[test]
#[serial]
fn test_primitives() {
test_sample("primitives");
}
#[test]
#[serial]
fn test_sequence() {
test_sample("sequence");
}
#[test]
#[serial]
fn test_sequence_of_sequences() {
test_sample("sequence-of-sequences");
}
#[test]
#[serial]
fn test_router() {
test_sample("router");
}
#[test]
#[serial]
fn test_tokenizer() {
test_sample("tokenizer");
}
#[test]
#[serial]
fn test_reverse_echo() {
test_sample("reverse-echo");
}
#[test]
#[serial]
fn test_mandlebrot() {
test_sample("mandlebrot");
}
#[test]
#[serial]
fn test_prime() {
test_sample("prime");
}
}