#![warn(anonymous_parameters, bad_style, missing_docs)]
#![warn(
unused,
unused_extern_crates,
unused_import_braces,
unused_qualifications
)]
#![warn(unsafe_code)]
use std::env;
use std::fs::File;
use std::io::Read;
use std::path::{Path, PathBuf};
use std::process;
fn get_test_dir() -> PathBuf {
let self_exe = env::current_exe().expect("Cannot get self's executable path");
let dir = self_exe.parent().expect("Cannot get self's directory");
assert!(dir.ends_with("target/debug/deps") || dir.ends_with("target/release/deps"));
dir.to_owned()
}
fn get_bin(bin: &str) -> PathBuf {
let test_dir = get_test_dir();
let debug_or_release_dir = test_dir.parent().expect("Failed to get parent directory");
debug_or_release_dir
.join(bin)
.with_extension(env::consts::EXE_EXTENSION)
}
fn get_src_dir() -> PathBuf {
let test_dir = get_test_dir();
let debug_or_release_dir = test_dir.parent().expect("Failed to get parent directory");
let target_dir = debug_or_release_dir
.parent()
.expect("Failed to get parent directory");
let dir = target_dir.parent().expect("Failed to get parent directory");
assert!(dir.join("Cargo.toml").exists());
dir.to_owned()
}
fn check_program<I: Into<process::Stdio>>(
bin: &Path,
args: &[&str],
exp_code: i32,
golden_stdin: I,
exp_stdout: &str,
exp_stderr: &str,
) {
let result = process::Command::new(bin)
.args(args)
.stdin(golden_stdin)
.output()
.expect("Failed to execute subprocess");
let code = result
.status
.code()
.expect("Subprocess didn't exit cleanly");
let stdout = String::from_utf8(result.stdout).expect("Actual stdout not is not valid UTF-8");
let stderr = String::from_utf8(result.stderr).expect("Actual stderr not is not valid UTF-8");
if exp_code != code || exp_stdout != stdout || exp_stderr != stderr {
eprintln!("Exit code: {}", code);
eprintln!("stdout:\n{}", stdout);
eprintln!("stderr:\n{}", stderr);
assert_eq!(exp_code, code);
assert_eq!(exp_stdout, stdout);
assert_eq!(exp_stderr, stderr);
}
}
fn check(args: &[&str], exp_code: i32, exp_stdout: &str, exp_stderr: &str) {
let bin = get_bin("endbasic");
check_program(
&bin,
args,
exp_code,
process::Stdio::null(),
exp_stdout,
exp_stderr,
)
}
fn read_golden(path: &Path) -> String {
let mut f = File::open(path).expect("Failed to open golden data file");
let mut golden = vec![];
f.read_to_end(&mut golden)
.expect("Failed to read golden data file");
let raw = String::from_utf8(golden).expect("Golden data file is not valid UTF-8");
if cfg!(target_os = "windows") {
raw.replace("\r\n", "\n")
} else {
raw
}
}
fn do_golden_test(name: &str) {
let bas_path = get_src_dir().join("tests").join(name).with_extension("bas");
let in_path = get_src_dir().join("tests").join(name).with_extension("in");
let out_path = get_src_dir().join("tests").join(name).with_extension("out");
let err_path = get_src_dir().join("tests").join(name).with_extension("err");
let input = if in_path.exists() {
File::open(in_path).unwrap().into()
} else {
process::Stdio::null()
};
let golden_code = if err_path.exists() { 1 } else { 0 };
let golden_stdout = if out_path.exists() {
read_golden(&out_path)
} else {
"".to_owned()
};
let golden_stderr = if err_path.exists() {
read_golden(&err_path)
} else {
"".to_owned()
};
let bin = get_bin("endbasic");
check_program(
&bin,
&[bas_path.to_str().unwrap()],
golden_code,
input,
&golden_stdout,
&golden_stderr,
)
}
#[test]
fn test_golden_control_flow() {
do_golden_test("control-flow");
}
#[test]
fn test_golden_exec_error() {
do_golden_test("exec-error");
}
#[test]
fn test_golden_hello() {
do_golden_test("hello");
}
#[test]
fn test_golden_lexer_error() {
do_golden_test("lexer-error");
}
#[test]
fn test_golden_parser_error() {
do_golden_test("parser-error");
}
#[test]
fn test_golden_types() {
do_golden_test("types");
}
#[test]
fn test_golden_utf8() {
do_golden_test("utf8");
}
#[test]
fn test_no_args() {
check(&[], 1, "", "endbasic: E: No program specified\n");
}
#[test]
fn test_too_many_args() {
check(&["foo", "bar"], 1, "", "endbasic: E: Too many arguments\n");
}
#[cfg(not(target_os = "linux"))]
#[test]
fn test_program_name_uses_arg0() {
use std::fs;
struct DeleteOnDrop<'a> {
path: &'a Path,
}
impl<'a> Drop for DeleteOnDrop<'a> {
fn drop(&mut self) {
let _best_effort_removal = fs::remove_file(self.path);
}
}
let original = get_bin("endbasic");
let custom = get_test_dir()
.join("custom-name")
.with_extension(env::consts::EXE_EXTENSION);
let _delete_custom = DeleteOnDrop { path: &custom };
fs::copy(&original, &custom).unwrap();
check_program(
&custom,
&[],
1,
process::Stdio::null(),
"",
"custom-name: E: No program specified\n",
);
}