use crate::Config;
use std::collections::HashMap;
use std::{env, fs, process};
use regex::Regex;
use crate::model::*;
use crate::{parse, vars};
use std;
pub struct TestEvaluator
{
pub invocation: Invocation,
}
struct Checker
{
lines: Lines,
variables: HashMap<String, String>,
}
struct Lines {
lines: Vec<String>,
current: usize,
}
impl TestEvaluator
{
pub fn new(invocation: Invocation) -> Self {
TestEvaluator { invocation: invocation }
}
pub fn execute_tests(self, test_file: &TestFile, config: &Config) -> TestResultKind {
let mut cmd = self.build_command(test_file, config);
let output = match cmd.output() {
Ok(o) => o,
Err(e) => match e.kind() {
std::io::ErrorKind::NotFound => {
return TestResultKind::Error(
format!("shell '{}' does not exist", &config.shell).into(),
);
},
_ => return TestResultKind::Error(e.into()),
},
};
if !output.status.success() {
let stderr = String::from_utf8(output.stderr).unwrap();
return TestResultKind::Fail {
message: format!(
"exited with code {}", output.status.code().unwrap()),
reason: unimplemented!(),
};
}
let stdout = String::from_utf8(output.stdout).unwrap();
let stdout_lines: Vec<_> = stdout.lines().map(|l| l.trim().to_owned()).collect();
let stdout: String = stdout_lines.join("\n");
Checker::new(stdout).run(config, &test_file)
}
fn build_command(&self,
test_file: &TestFile,
config: &Config) -> process::Command {
let mut variables = config.constants.clone();
variables.extend(test_file.variables());
let command_line: String = vars::resolve::invocation(&self.invocation, &config, &mut variables);
let mut cmd = process::Command::new(&config.shell);
cmd.args(&["-c", &command_line]);
if let Ok(current_exe) = env::current_exe() {
if let Some(parent) = current_exe.parent() {
let current_path = env::var("PATH").unwrap_or(String::new());
cmd.env("PATH", format!("{}:{}", parent.to_str().unwrap(), current_path));
}
}
cmd
}
}
impl Checker
{
fn new(stdout: String) -> Self {
Checker {
lines: stdout.into(),
variables: HashMap::new(),
}
}
fn run(&mut self, config: &Config, test_file: &TestFile) -> TestResultKind {
let mut expect_test_pass = true;
let result = self.run_expecting_pass(config, test_file, &mut expect_test_pass);
if expect_test_pass {
result
} else { match result {
TestResultKind::Pass => TestResultKind::UnexpectedPass,
TestResultKind::Error(_) |
TestResultKind::Fail { .. } => TestResultKind::ExpectedFailure,
TestResultKind::Skip => TestResultKind::Skip,
TestResultKind::UnexpectedPass |
TestResultKind::ExpectedFailure => unreachable!(),
}
}
}
fn run_expecting_pass(&mut self,
config: &Config,
test_file: &TestFile,
expect_test_pass: &mut bool) -> TestResultKind {
for command in test_file.commands.iter() {
match command.kind {
CommandKind::XFail => *expect_test_pass = false,
CommandKind::Run(..) => (),
CommandKind::Check(ref text_pattern) => {
let regex = vars::resolve::text_pattern(&text_pattern, config, &mut self.variables);
let beginning_line = self.lines.peek().unwrap_or_else(|| "".to_owned());
let matched_line = self.lines.find(|l| regex.is_match(l));
if let Some(matched_line) = matched_line {
self.process_captures(®ex, &matched_line);
} else {
let message = format_check_error(test_file,
command,
&format!("could not find match: '{}'", text_pattern),
&beginning_line);
return TestResultKind::Fail { message, reason: unimplemented!() };
}
},
CommandKind::CheckNext(ref text_pattern) => {
let regex = vars::resolve::text_pattern(&text_pattern, config, &mut self.variables);
if let Some(next_line) = self.lines.next() {
if regex.is_match(&next_line) {
self.process_captures(®ex, &next_line);
} else {
let message = format_check_error(test_file,
command,
&format!("could not find pattern: '{}'", text_pattern),
&next_line);
return TestResultKind::Fail { message, reason: unimplemented!() };
}
} else {
return TestResultKind::Fail {
message: format!("check-next reached the end of file unexpectedly"),
reason: unimplemented!(),
};
}
},
}
}
if config.cleanup_temporary_files {
let tempfiles = self.variables.iter()
.filter(|(k,_)| k.contains("tempfile"))
.map(|(_,v)| v);
for tempfile in tempfiles {
fs::remove_file(tempfile).ok();
}
}
TestResultKind::Pass
}
pub fn process_captures(&mut self, regex: &Regex, line: &str) {
debug_assert_eq!(regex.is_match(line), true);
let captures = if let Some(captures) = regex.captures(line) {
captures
} else {
return;
};
for capture_name in regex.capture_names() {
if let Some(name) = capture_name {
let captured_value = captures.name(name).unwrap();
self.variables.insert(name.to_owned(), captured_value.as_str().to_owned());
}
}
}
}
impl Lines {
pub fn new(lines: Vec<String>) -> Self {
Lines { lines: lines, current: 0 }
}
fn peek(&self) -> Option<<Self as Iterator>::Item> {
self.next_index().map(|idx| self.lines[idx].clone())
}
fn next_index(&self) -> Option<usize> {
if self.current > self.lines.len() { return None; }
self.lines[self.current..].iter()
.position(|l| parse::possible_command(l, 0).is_none())
.map(|offset| self.current + offset)
}
}
impl Iterator for Lines
{
type Item = String;
fn next(&mut self) -> Option<String> {
if let Some(next_index) = self.next_index() {
self.current = next_index + 1;
Some(self.lines[next_index].clone())
} else {
None
}
}
}
impl From<String> for Lines
{
fn from(s: String) -> Lines {
Lines::new(s.split("\n").map(ToOwned::to_owned).collect())
}
}
fn format_check_error(test_file: &TestFile,
command: &Command,
msg: &str,
next_line: &str) -> String {
self::format_error(test_file, command, msg, next_line)
}
fn format_error(test_file: &TestFile,
command: &Command,
msg: &str,
next_line: &str) -> String {
format!("{}:{}: {}\nnext line: '{}'", test_file.path.display(), command.line_number, msg, next_line)
}
#[cfg(test)]
mod test {
use super::*;
fn lines(s: &str) -> Vec<String> {
let lines: Lines = s.to_owned().into();
lines.collect()
}
#[test]
fn trivial_lines_works_correctly() {
assert_eq!(lines("hello\nworld\nfoo"), &["hello", "world", "foo"]);
}
#[test]
fn lines_ignores_commands() {
assert_eq!(lines("; RUN: cat %file\nhello\n; CHECK: foo\nfoo"),
&["hello", "foo"]);
}
#[test]
fn lines_can_peek() {
let mut lines: Lines = "hello\nworld\nfoo".to_owned().into();
assert_eq!(lines.next(), Some("hello".to_owned()));
assert_eq!(lines.peek(), Some("world".to_owned()));
assert_eq!(lines.next(), Some("world".to_owned()));
assert_eq!(lines.peek(), Some("foo".to_owned()));
assert_eq!(lines.next(), Some("foo".to_owned()));
}
}