2017-10-21 10:16:03 +02:00
|
|
|
// Copyright (c) 2017 fd developers
|
|
|
|
// Licensed under the Apache License, Version 2.0
|
|
|
|
// <LICENSE-APACHE or http://www.apache.org/licenses/LICENSE-2.0>
|
|
|
|
// or the MIT license <LICENSE-MIT or http://opensource.org/licenses/MIT>,
|
|
|
|
// at your option. All files in the project carrying such
|
|
|
|
// notice may not be copied, modified, or distributed except
|
|
|
|
// according to those terms.
|
|
|
|
|
2017-10-04 23:19:30 +02:00
|
|
|
use std;
|
|
|
|
use std::env;
|
|
|
|
use std::fs;
|
|
|
|
use std::io;
|
|
|
|
use std::io::Write;
|
|
|
|
use std::path::{Path, PathBuf};
|
|
|
|
use std::process;
|
|
|
|
|
|
|
|
#[cfg(unix)]
|
|
|
|
use std::os::unix;
|
|
|
|
|
|
|
|
#[cfg(windows)]
|
|
|
|
use std::os::windows;
|
|
|
|
|
2019-01-05 18:24:33 +01:00
|
|
|
use tempdir::TempDir;
|
2017-10-04 23:19:30 +02:00
|
|
|
|
|
|
|
/// Environment for the integration tests.
|
|
|
|
pub struct TestEnv {
|
|
|
|
/// Temporary working directory.
|
|
|
|
temp_dir: TempDir,
|
|
|
|
|
|
|
|
/// Path to the *fd* executable.
|
|
|
|
fd_exe: PathBuf,
|
2018-11-11 18:00:01 +01:00
|
|
|
|
2018-11-12 18:43:40 +01:00
|
|
|
/// Normalize each line by sorting the whitespace-separated words
|
2018-11-11 18:00:01 +01:00
|
|
|
normalize_line: bool,
|
2017-10-04 23:19:30 +02:00
|
|
|
}
|
|
|
|
|
|
|
|
/// Create the working directory and the test files.
|
2017-12-06 23:52:23 +01:00
|
|
|
fn create_working_directory(
|
|
|
|
directories: &[&'static str],
|
|
|
|
files: &[&'static str],
|
|
|
|
) -> Result<TempDir, io::Error> {
|
2017-10-04 23:19:30 +02:00
|
|
|
let temp_dir = TempDir::new("fd-tests")?;
|
|
|
|
|
|
|
|
{
|
|
|
|
let root = temp_dir.path();
|
|
|
|
|
2018-07-30 17:19:37 +02:00
|
|
|
// Pretend that this is a Git repository in order for `.gitignore` files to be respected
|
|
|
|
fs::create_dir_all(root.join(".git"))?;
|
|
|
|
|
2017-12-06 23:52:23 +01:00
|
|
|
for directory in directories {
|
|
|
|
fs::create_dir_all(root.join(directory))?;
|
|
|
|
}
|
2017-10-04 23:19:30 +02:00
|
|
|
|
2017-12-06 23:52:23 +01:00
|
|
|
for file in files {
|
|
|
|
fs::File::create(root.join(file))?;
|
|
|
|
}
|
2017-10-04 23:19:30 +02:00
|
|
|
|
2018-01-01 12:16:43 +01:00
|
|
|
#[cfg(unix)]
|
|
|
|
unix::fs::symlink(root.join("one/two"), root.join("symlink"))?;
|
2017-10-04 23:19:30 +02:00
|
|
|
|
2017-10-07 09:40:44 +02:00
|
|
|
// Note: creating symlinks on Windows requires the `SeCreateSymbolicLinkPrivilege` which
|
|
|
|
// is by default only granted for administrators.
|
2018-01-01 12:16:43 +01:00
|
|
|
#[cfg(windows)]
|
|
|
|
windows::fs::symlink_dir(root.join("one/two"), root.join("symlink"))?;
|
2017-10-04 23:19:30 +02:00
|
|
|
|
2018-02-21 21:41:52 +01:00
|
|
|
fs::File::create(root.join(".fdignore"))?.write_all(b"fdignored.foo")?;
|
2017-11-21 22:54:00 +01:00
|
|
|
|
2018-01-01 12:16:43 +01:00
|
|
|
fs::File::create(root.join(".gitignore"))?.write_all(b"gitignored.foo")?;
|
2017-10-04 23:19:30 +02:00
|
|
|
}
|
|
|
|
|
|
|
|
Ok(temp_dir)
|
|
|
|
}
|
|
|
|
|
|
|
|
/// Find the *fd* executable.
|
|
|
|
fn find_fd_exe() -> PathBuf {
|
|
|
|
// Tests exe is in target/debug/deps, the *fd* exe is in target/debug
|
2017-10-12 08:01:51 +02:00
|
|
|
let root = env::current_exe()
|
|
|
|
.expect("tests executable")
|
|
|
|
.parent()
|
|
|
|
.expect("tests executable directory")
|
|
|
|
.parent()
|
|
|
|
.expect("fd executable directory")
|
2017-10-04 23:19:30 +02:00
|
|
|
.to_path_buf();
|
|
|
|
|
|
|
|
let exe_name = if cfg!(windows) { "fd.exe" } else { "fd" };
|
|
|
|
|
|
|
|
root.join(exe_name)
|
|
|
|
}
|
|
|
|
|
|
|
|
/// Format an error message for when *fd* did not exit successfully.
|
|
|
|
fn format_exit_error(args: &[&str], output: &process::Output) -> String {
|
|
|
|
format!(
|
|
|
|
"`fd {}` did not exit successfully.\nstdout:\n---\n{}---\nstderr:\n---\n{}---",
|
|
|
|
args.join(" "),
|
|
|
|
String::from_utf8_lossy(&output.stdout),
|
2017-10-12 08:01:51 +02:00
|
|
|
String::from_utf8_lossy(&output.stderr)
|
|
|
|
)
|
2017-10-04 23:19:30 +02:00
|
|
|
}
|
|
|
|
|
|
|
|
/// Format an error message for when the output of *fd* did not match the expected output.
|
|
|
|
fn format_output_error(args: &[&str], expected: &str, actual: &str) -> String {
|
|
|
|
// Generate diff text.
|
2017-10-12 08:01:51 +02:00
|
|
|
let diff_text = diff::lines(expected, actual)
|
|
|
|
.into_iter()
|
|
|
|
.map(|diff| match diff {
|
|
|
|
diff::Result::Left(l) => format!("-{}", l),
|
|
|
|
diff::Result::Both(l, _) => format!(" {}", l),
|
|
|
|
diff::Result::Right(r) => format!("+{}", r),
|
2018-09-27 23:01:38 +02:00
|
|
|
})
|
|
|
|
.collect::<Vec<_>>()
|
2017-10-12 08:01:51 +02:00
|
|
|
.join("\n");
|
2017-10-04 23:19:30 +02:00
|
|
|
|
|
|
|
format!(
|
|
|
|
concat!(
|
|
|
|
"`fd {}` did not produce the expected output.\n",
|
2017-10-12 08:01:51 +02:00
|
|
|
"Showing diff between expected and actual:\n{}\n"
|
|
|
|
),
|
2017-10-04 23:19:30 +02:00
|
|
|
args.join(" "),
|
2017-10-12 08:01:51 +02:00
|
|
|
diff_text
|
|
|
|
)
|
2017-10-04 23:19:30 +02:00
|
|
|
}
|
|
|
|
|
|
|
|
/// Normalize the output for comparison.
|
2019-03-02 01:19:47 +01:00
|
|
|
fn normalize_output(s: &str, trim_start: bool, normalize_line: bool) -> String {
|
2017-10-04 23:19:30 +02:00
|
|
|
// Split into lines and normalize separators.
|
2018-05-14 18:39:47 +02:00
|
|
|
let mut lines = s
|
|
|
|
.replace('\0', "NULL\n")
|
2017-10-04 23:19:30 +02:00
|
|
|
.lines()
|
|
|
|
.map(|line| {
|
2019-03-02 01:19:47 +01:00
|
|
|
let line = if trim_start { line.trim_start() } else { line };
|
2018-11-11 18:00:01 +01:00
|
|
|
let line = line.replace('/', &std::path::MAIN_SEPARATOR.to_string());
|
|
|
|
if normalize_line {
|
2018-11-12 18:43:40 +01:00
|
|
|
let mut words: Vec<_> = line.split_whitespace().collect();
|
|
|
|
words.sort();
|
|
|
|
return words.join(" ");
|
2018-11-11 18:00:01 +01:00
|
|
|
}
|
|
|
|
line
|
2018-09-27 23:01:38 +02:00
|
|
|
})
|
|
|
|
.collect::<Vec<_>>();
|
2017-10-04 23:19:30 +02:00
|
|
|
|
2018-11-11 18:00:01 +01:00
|
|
|
lines.sort();
|
2017-10-04 23:19:30 +02:00
|
|
|
lines.join("\n")
|
|
|
|
}
|
|
|
|
|
|
|
|
impl TestEnv {
|
2017-12-06 23:52:23 +01:00
|
|
|
pub fn new(directories: &[&'static str], files: &[&'static str]) -> TestEnv {
|
|
|
|
let temp_dir = create_working_directory(directories, files).expect("working directory");
|
2017-10-04 23:19:30 +02:00
|
|
|
let fd_exe = find_fd_exe();
|
|
|
|
|
|
|
|
TestEnv {
|
2018-11-11 18:00:01 +01:00
|
|
|
temp_dir,
|
|
|
|
fd_exe,
|
|
|
|
normalize_line: false,
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
pub fn normalize_line(self, normalize: bool) -> TestEnv {
|
|
|
|
TestEnv {
|
|
|
|
temp_dir: self.temp_dir,
|
|
|
|
fd_exe: self.fd_exe,
|
|
|
|
normalize_line: normalize,
|
2017-10-04 23:19:30 +02:00
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
/// Get the root directory for the tests.
|
2017-10-18 20:04:34 +02:00
|
|
|
pub fn test_root(&self) -> PathBuf {
|
2017-10-04 23:19:30 +02:00
|
|
|
self.temp_dir.path().to_path_buf()
|
|
|
|
}
|
|
|
|
|
2017-10-18 20:04:34 +02:00
|
|
|
/// Get the root directory of the file system.
|
|
|
|
pub fn system_root(&self) -> PathBuf {
|
|
|
|
let mut components = self.temp_dir.path().components();
|
|
|
|
PathBuf::from(components.next().expect("root directory").as_os_str())
|
|
|
|
}
|
|
|
|
|
2017-10-04 23:19:30 +02:00
|
|
|
/// Assert that calling *fd* with the specified arguments produces the expected output.
|
|
|
|
pub fn assert_output(&self, args: &[&str], expected: &str) {
|
|
|
|
self.assert_output_subdirectory(".", args, expected)
|
|
|
|
}
|
|
|
|
|
|
|
|
/// Assert that calling *fd* in the specified path under the root working directory,
|
|
|
|
/// and with the specified arguments produces the expected output.
|
2017-10-12 08:01:51 +02:00
|
|
|
pub fn assert_output_subdirectory<P: AsRef<Path>>(
|
|
|
|
&self,
|
|
|
|
path: P,
|
|
|
|
args: &[&str],
|
|
|
|
expected: &str,
|
|
|
|
) {
|
2017-10-04 23:19:30 +02:00
|
|
|
// Setup *fd* command.
|
|
|
|
let mut cmd = process::Command::new(&self.fd_exe);
|
|
|
|
cmd.current_dir(self.temp_dir.path().join(path));
|
|
|
|
cmd.args(args);
|
|
|
|
|
|
|
|
// Run *fd*.
|
|
|
|
let output = cmd.output().expect("fd output");
|
|
|
|
|
|
|
|
// Check for exit status.
|
|
|
|
if !output.status.success() {
|
|
|
|
panic!(format_exit_error(args, &output));
|
|
|
|
}
|
|
|
|
|
|
|
|
// Normalize both expected and actual output.
|
2018-11-11 18:00:01 +01:00
|
|
|
let expected = normalize_output(expected, true, self.normalize_line);
|
|
|
|
let actual = normalize_output(
|
|
|
|
&String::from_utf8_lossy(&output.stdout),
|
|
|
|
false,
|
|
|
|
self.normalize_line,
|
|
|
|
);
|
2017-10-04 23:19:30 +02:00
|
|
|
|
|
|
|
// Compare actual output to expected output.
|
|
|
|
if expected != actual {
|
|
|
|
panic!(format_output_error(args, &expected, &actual));
|
|
|
|
}
|
|
|
|
}
|
2018-11-11 18:00:01 +01:00
|
|
|
|
|
|
|
/// Assert that calling *fd* with the specified arguments produces the expected error.
|
|
|
|
pub fn assert_error(&self, args: &[&str], expected: &str) {
|
|
|
|
self.assert_error_subdirectory(".", args, expected)
|
|
|
|
}
|
|
|
|
|
|
|
|
/// Assert that calling *fd* in the specified path under the root working directory,
|
|
|
|
/// and with the specified arguments produces an error with the expected message.
|
|
|
|
fn assert_error_subdirectory<P: AsRef<Path>>(&self, path: P, args: &[&str], expected: &str) {
|
|
|
|
// Setup *fd* command.
|
|
|
|
let mut cmd = process::Command::new(&self.fd_exe);
|
|
|
|
cmd.current_dir(self.temp_dir.path().join(path));
|
|
|
|
cmd.args(args);
|
|
|
|
|
|
|
|
// Run *fd*.
|
|
|
|
let output = cmd.output().expect("fd output");
|
|
|
|
|
|
|
|
// Check for exit status.
|
|
|
|
if output.status.success() {
|
2018-11-12 18:43:40 +01:00
|
|
|
panic!("error '{}' did not occur.", expected);
|
2018-11-11 18:00:01 +01:00
|
|
|
}
|
|
|
|
|
|
|
|
// Compare actual output to expected output.
|
|
|
|
let actual = String::from_utf8_lossy(&output.stderr);
|
2018-11-12 18:43:40 +01:00
|
|
|
if !actual.starts_with(expected) {
|
2018-11-11 18:00:01 +01:00
|
|
|
panic!(format_output_error(args, &expected, &actual));
|
|
|
|
}
|
|
|
|
}
|
2017-10-04 23:19:30 +02:00
|
|
|
}
|