fd/tests/tests.rs
2018-03-26 08:28:22 +02:00

1003 lines
24 KiB
Rust
Raw Blame History

// 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.
//! Integration tests for the CLI interface of fd.
extern crate regex;
mod testenv;
use testenv::TestEnv;
use regex::escape;
use std::fs;
use std::io::Write;
static DEFAULT_DIRS: &'static [&'static str] = &["one/two/three", "one/two/three/directory_foo"];
static DEFAULT_FILES: &'static [&'static str] = &[
"a.foo",
"one/b.foo",
"one/two/c.foo",
"one/two/C.Foo2",
"one/two/three/d.foo",
"fdignored.foo",
"gitignored.foo",
".hidden.foo",
"e1 e2",
];
fn get_absolute_root_path(env: &TestEnv) -> String {
let path = env.test_root()
.canonicalize()
.expect("absolute path")
.to_str()
.expect("string")
.to_string();
#[cfg(windows)]
let path = path.trim_left_matches(r"\\?\").to_string();
path
}
#[cfg(test)]
fn get_test_env_with_abs_path(dirs: &[&'static str], files: &[&'static str]) -> (TestEnv, String) {
let env = TestEnv::new(dirs, files);
let root_path = get_absolute_root_path(&env);
(env, root_path)
}
/// Simple tests
#[test]
fn test_simple() {
let te = TestEnv::new(DEFAULT_DIRS, DEFAULT_FILES);
te.assert_output(&["a.foo"], "a.foo");
te.assert_output(&["b.foo"], "one/b.foo");
te.assert_output(&["d.foo"], "one/two/three/d.foo");
te.assert_output(
&["foo"],
"a.foo
one/b.foo
one/two/c.foo
one/two/C.Foo2
one/two/three/d.foo
one/two/three/directory_foo",
);
te.assert_output(
&[],
"a.foo
e1 e2
one
one/b.foo
one/two
one/two/c.foo
one/two/C.Foo2
one/two/three
one/two/three/d.foo
one/two/three/directory_foo
symlink",
);
}
/// Test multiple directory searches
#[test]
fn test_multi_file() {
let dirs = &["test1", "test2"];
let files = &["test1/a.foo", "test1/b.foo", "test2/a.foo"];
let te = TestEnv::new(dirs, files);
te.assert_output(
&["a.foo", "test1", "test2"],
"test1/a.foo
test2/a.foo",
);
te.assert_output(
&["", "test1", "test2"],
"test1/a.foo
test2/a.foo
test1/b.foo",
);
te.assert_output(&["a.foo", "test1"], "test1/a.foo");
te.assert_output(&["b.foo", "test1", "test2"], "test1/b.foo");
}
/// Explicit root path
#[test]
fn test_explicit_root_path() {
let te = TestEnv::new(DEFAULT_DIRS, DEFAULT_FILES);
te.assert_output(
&["foo", "one"],
"one/b.foo
one/two/c.foo
one/two/C.Foo2
one/two/three/d.foo
one/two/three/directory_foo",
);
te.assert_output(
&["foo", "one/two/three"],
"one/two/three/d.foo
one/two/three/directory_foo",
);
te.assert_output_subdirectory(
"one/two",
&["foo", "../../"],
"../../a.foo
../../one/b.foo
../../one/two/c.foo
../../one/two/C.Foo2
../../one/two/three/d.foo
../../one/two/three/directory_foo",
);
te.assert_output_subdirectory(
"one/two/three",
&["", ".."],
"../c.foo
../C.Foo2
../three
../three/d.foo
../three/directory_foo",
);
}
/// Regex searches
#[test]
fn test_regex_searches() {
let te = TestEnv::new(DEFAULT_DIRS, DEFAULT_FILES);
te.assert_output(
&["[a-c].foo"],
"a.foo
one/b.foo
one/two/c.foo
one/two/C.Foo2",
);
te.assert_output(
&["--case-sensitive", "[a-c].foo"],
"a.foo
one/b.foo
one/two/c.foo",
);
}
/// Smart case
#[test]
fn test_smart_case() {
let te = TestEnv::new(DEFAULT_DIRS, DEFAULT_FILES);
te.assert_output(
&["c.foo"],
"one/two/c.foo
one/two/C.Foo2",
);
te.assert_output(&["C.Foo"], "one/two/C.Foo2");
te.assert_output(&["Foo"], "one/two/C.Foo2");
// Only literal uppercase chars should trigger case sensitivity.
te.assert_output(
&["\\Ac"],
"one/two/c.foo
one/two/C.Foo2",
);
te.assert_output(&["\\AC"], "one/two/C.Foo2");
}
/// Case sensitivity (--case-sensitive)
#[test]
fn test_case_sensitive() {
let te = TestEnv::new(DEFAULT_DIRS, DEFAULT_FILES);
te.assert_output(&["--case-sensitive", "c.foo"], "one/two/c.foo");
te.assert_output(&["--case-sensitive", "C.Foo"], "one/two/C.Foo2");
te.assert_output(
&["--ignore-case", "--case-sensitive", "C.Foo"],
"one/two/C.Foo2",
);
}
/// Case insensitivity (--ignore-case)
#[test]
fn test_case_insensitive() {
let te = TestEnv::new(DEFAULT_DIRS, DEFAULT_FILES);
te.assert_output(
&["--ignore-case", "C.Foo"],
"one/two/c.foo
one/two/C.Foo2",
);
te.assert_output(
&["--case-sensitive", "--ignore-case", "C.Foo"],
"one/two/c.foo
one/two/C.Foo2",
);
}
/// Full path search (--full-path)
#[test]
fn test_full_path() {
let te = TestEnv::new(DEFAULT_DIRS, DEFAULT_FILES);
let root = te.system_root();
let prefix = escape(&root.to_string_lossy());
te.assert_output(
&[
"--full-path",
&format!("^{prefix}.*three.*foo$", prefix = prefix),
],
"one/two/three/d.foo
one/two/three/directory_foo",
);
}
/// Hidden files (--hidden)
#[test]
fn test_hidden() {
let te = TestEnv::new(DEFAULT_DIRS, DEFAULT_FILES);
te.assert_output(
&["--hidden", "foo"],
".hidden.foo
a.foo
one/b.foo
one/two/c.foo
one/two/C.Foo2
one/two/three/d.foo
one/two/three/directory_foo",
);
}
/// Ignored files (--no-ignore)
#[test]
fn test_no_ignore() {
let te = TestEnv::new(DEFAULT_DIRS, DEFAULT_FILES);
te.assert_output(
&["--no-ignore", "foo"],
"a.foo
fdignored.foo
gitignored.foo
one/b.foo
one/two/c.foo
one/two/C.Foo2
one/two/three/d.foo
one/two/three/directory_foo",
);
te.assert_output(
&["--hidden", "--no-ignore", "foo"],
".hidden.foo
a.foo
fdignored.foo
gitignored.foo
one/b.foo
one/two/c.foo
one/two/C.Foo2
one/two/three/d.foo
one/two/three/directory_foo",
);
}
/// .gitignore and .fdignore
#[test]
fn test_gitignore_and_fdignore() {
let files = &[
"ignored-by-nothing",
"ignored-by-fdignore",
"ignored-by-gitignore",
"ignored-by-both",
];
let te = TestEnv::new(&[], files);
fs::File::create(te.test_root().join(".fdignore"))
.unwrap()
.write_all(b"ignored-by-fdignore\nignored-by-both")
.unwrap();
fs::File::create(te.test_root().join(".gitignore"))
.unwrap()
.write_all(b"ignored-by-gitignore\nignored-by-both")
.unwrap();
te.assert_output(&["ignored"], "ignored-by-nothing");
te.assert_output(
&["--no-ignore-vcs", "ignored"],
"ignored-by-nothing
ignored-by-gitignore",
);
te.assert_output(
&["--no-ignore", "ignored"],
"ignored-by-nothing
ignored-by-fdignore
ignored-by-gitignore
ignored-by-both",
);
}
/// Precedence of .fdignore files
#[test]
fn test_custom_ignore_precedence() {
let dirs = &["inner"];
let files = &["inner/foo"];
let te = TestEnv::new(dirs, files);
// Ignore 'foo' via .gitignore
fs::File::create(te.test_root().join("inner/.gitignore"))
.unwrap()
.write_all(b"foo")
.unwrap();
// Whitelist 'foo' via .fdignore
fs::File::create(te.test_root().join(".fdignore"))
.unwrap()
.write_all(b"!foo")
.unwrap();
te.assert_output(&["foo"], "inner/foo");
te.assert_output(&["--no-ignore-vcs", "foo"], "inner/foo");
te.assert_output(&["--no-ignore", "foo"], "inner/foo");
}
/// VCS ignored files (--no-ignore-vcs)
#[test]
fn test_no_ignore_vcs() {
let te = TestEnv::new(DEFAULT_DIRS, DEFAULT_FILES);
te.assert_output(
&["--no-ignore-vcs", "foo"],
"a.foo
gitignored.foo
one/b.foo
one/two/c.foo
one/two/C.Foo2
one/two/three/d.foo
one/two/three/directory_foo",
);
}
/// Custom ignore files (--ignore-file)
#[test]
fn test_custom_ignore_files() {
let te = TestEnv::new(DEFAULT_DIRS, DEFAULT_FILES);
// Ignore 'C.Foo2' and everything in 'three'.
fs::File::create(te.test_root().join("custom.ignore"))
.unwrap()
.write_all(b"C.Foo2\nthree")
.unwrap();
te.assert_output(
&["--ignore-file", "custom.ignore", "foo"],
"a.foo
one/b.foo
one/two/c.foo",
);
}
/// Ignored files with ripgrep aliases (-u / -uu)
#[test]
fn test_no_ignore_aliases() {
let te = TestEnv::new(DEFAULT_DIRS, DEFAULT_FILES);
te.assert_output(
&["-u", "foo"],
"a.foo
fdignored.foo
gitignored.foo
one/b.foo
one/two/c.foo
one/two/C.Foo2
one/two/three/d.foo
one/two/three/directory_foo",
);
te.assert_output(
&["-uu", "foo"],
".hidden.foo
a.foo
fdignored.foo
gitignored.foo
one/b.foo
one/two/c.foo
one/two/C.Foo2
one/two/three/d.foo
one/two/three/directory_foo",
);
}
/// Symlinks (--follow)
#[test]
fn test_follow() {
let te = TestEnv::new(DEFAULT_DIRS, DEFAULT_FILES);
te.assert_output(
&["--follow", "c.foo"],
"one/two/c.foo
one/two/C.Foo2
symlink/c.foo
symlink/C.Foo2",
);
}
/// Null separator (--print0)
#[test]
fn test_print0() {
let te = TestEnv::new(DEFAULT_DIRS, DEFAULT_FILES);
te.assert_output(
&["--print0", "foo"],
"a.fooNULL
one/b.fooNULL
one/two/C.Foo2NULL
one/two/c.fooNULL
one/two/three/d.fooNULL
one/two/three/directory_fooNULL",
);
}
/// Maximum depth (--max-depth)
#[test]
fn test_max_depth() {
let te = TestEnv::new(DEFAULT_DIRS, DEFAULT_FILES);
te.assert_output(
&["--max-depth", "3"],
"a.foo
e1 e2
one
one/b.foo
one/two
one/two/c.foo
one/two/C.Foo2
one/two/three
symlink",
);
te.assert_output(
&["--max-depth", "2"],
"a.foo
e1 e2
one
one/b.foo
one/two
symlink",
);
te.assert_output(
&["--max-depth", "1"],
"a.foo
e1 e2
one
symlink",
);
}
/// Absolute paths (--absolute-path)
#[test]
fn test_absolute_path() {
let (te, abs_path) = get_test_env_with_abs_path(DEFAULT_DIRS, DEFAULT_FILES);
te.assert_output(
&["--absolute-path"],
&format!(
"{abs_path}/a.foo
{abs_path}/e1 e2
{abs_path}/one
{abs_path}/one/b.foo
{abs_path}/one/two
{abs_path}/one/two/c.foo
{abs_path}/one/two/C.Foo2
{abs_path}/one/two/three
{abs_path}/one/two/three/d.foo
{abs_path}/one/two/three/directory_foo
{abs_path}/symlink",
abs_path = &abs_path
),
);
te.assert_output(
&["--absolute-path", "foo"],
&format!(
"{abs_path}/a.foo
{abs_path}/one/b.foo
{abs_path}/one/two/c.foo
{abs_path}/one/two/C.Foo2
{abs_path}/one/two/three/d.foo
{abs_path}/one/two/three/directory_foo",
abs_path = &abs_path
),
);
}
/// Show absolute paths if the path argument is absolute
#[test]
fn test_implicit_absolute_path() {
let (te, abs_path) = get_test_env_with_abs_path(DEFAULT_DIRS, DEFAULT_FILES);
te.assert_output(
&["foo", &abs_path],
&format!(
"{abs_path}/a.foo
{abs_path}/one/b.foo
{abs_path}/one/two/c.foo
{abs_path}/one/two/C.Foo2
{abs_path}/one/two/three/d.foo
{abs_path}/one/two/three/directory_foo",
abs_path = &abs_path
),
);
}
/// Absolute paths should be normalized
#[test]
fn test_normalized_absolute_path() {
let (te, abs_path) = get_test_env_with_abs_path(DEFAULT_DIRS, DEFAULT_FILES);
te.assert_output_subdirectory(
"one",
&["--absolute-path", "foo", ".."],
&format!(
"{abs_path}/a.foo
{abs_path}/one/b.foo
{abs_path}/one/two/c.foo
{abs_path}/one/two/C.Foo2
{abs_path}/one/two/three/d.foo
{abs_path}/one/two/three/directory_foo",
abs_path = &abs_path
),
);
}
/// File type filter (--type)
#[test]
fn test_type() {
let te = TestEnv::new(DEFAULT_DIRS, DEFAULT_FILES);
te.assert_output(
&["--type", "f"],
"a.foo
e1 e2
one/b.foo
one/two/c.foo
one/two/C.Foo2
one/two/three/d.foo",
);
te.assert_output(&["--type", "f", "e1"], "e1 e2");
te.assert_output(
&["--type", "d"],
"one
one/two
one/two/three
one/two/three/directory_foo",
);
te.assert_output(
&["--type", "d", "--type", "l"],
"one
one/two
one/two/three
one/two/three/directory_foo
symlink",
);
te.assert_output(&["--type", "l"], "symlink");
}
/// Test `--type executable`
#[cfg(unix)]
#[test]
fn test_type_executable() {
use std::os::unix::fs::OpenOptionsExt;
let te = TestEnv::new(DEFAULT_DIRS, DEFAULT_FILES);
fs::OpenOptions::new()
.create(true)
.write(true)
.mode(0o777)
.open(te.test_root().join("executable-file.sh"))
.unwrap();
te.assert_output(&["--type", "executable"], "executable-file.sh");
te.assert_output(
&["--type", "executable", "--type", "directory"],
"executable-file.sh
one
one/two
one/two/three
one/two/three/directory_foo",
);
}
/// File extension (--extension)
#[test]
fn test_extension() {
let te = TestEnv::new(DEFAULT_DIRS, DEFAULT_FILES);
te.assert_output(
&["--extension", "foo"],
"a.foo
one/b.foo
one/two/c.foo
one/two/three/d.foo",
);
te.assert_output(
&["--extension", ".foo"],
"a.foo
one/b.foo
one/two/c.foo
one/two/three/d.foo",
);
te.assert_output(
&["--extension", ".foo", "--extension", "foo2"],
"a.foo
one/b.foo
one/two/c.foo
one/two/three/d.foo
one/two/C.Foo2",
);
te.assert_output(&["--extension", ".foo", "a"], "a.foo");
te.assert_output(&["--extension", "foo2"], "one/two/C.Foo2");
let te2 = TestEnv::new(&[], &["spam.bar.baz", "egg.bar.baz", "yolk.bar.baz.sig"]);
te2.assert_output(
&["--extension", ".bar.baz"],
"spam.bar.baz
egg.bar.baz",
);
te2.assert_output(&["--extension", "sig"], "yolk.bar.baz.sig");
te2.assert_output(&["--extension", "bar.baz.sig"], "yolk.bar.baz.sig");
let te3 = TestEnv::new(&[], &["latin1.e\u{301}xt", "smiley.☻"]);
te3.assert_output(&["--extension", ""], "smiley.☻");
te3.assert_output(&["--extension", ".e\u{301}xt"], "latin1.e\u{301}xt");
let te4 = TestEnv::new(&[], &[".hidden", "test.hidden"]);
te4.assert_output(&["--hidden", "--extension", ".hidden"], "test.hidden");
}
/// Symlink as search directory
#[test]
fn test_symlink_as_root() {
let te = TestEnv::new(DEFAULT_DIRS, DEFAULT_FILES);
// From: http://pubs.opengroup.org/onlinepubs/9699919799/functions/getcwd.html
// The getcwd() function shall place an absolute pathname of the current working directory in
// the array pointed to by buf, and return buf. The pathname shall contain no components that
// are dot or dot-dot, or are symbolic links.
//
// Key points:
// 1. The path of the current working directory of a Unix process cannot contain symlinks.
// 2. The path of the current working directory of a Windows process can contain symlinks.
//
// More:
// 1. On Windows, symlinks are resolved after the ".." component.
// 2. On Unix, symlinks are resolved immediately as encountered.
let parent_parent = if cfg!(windows) { ".." } else { "../.." };
te.assert_output_subdirectory(
"symlink",
&["", parent_parent],
&format!(
"{dir}/a.foo
{dir}/e1 e2
{dir}/one
{dir}/one/b.foo
{dir}/one/two
{dir}/one/two/c.foo
{dir}/one/two/C.Foo2
{dir}/one/two/three
{dir}/one/two/three/d.foo
{dir}/one/two/three/directory_foo
{dir}/symlink",
dir = &parent_parent
),
);
}
#[test]
fn test_symlink_and_absolute_path() {
let (te, abs_path) = get_test_env_with_abs_path(DEFAULT_DIRS, DEFAULT_FILES);
te.assert_output_subdirectory(
"symlink",
&["--absolute-path"],
&format!(
"{abs_path}/one/two/c.foo
{abs_path}/one/two/C.Foo2
{abs_path}/one/two/three
{abs_path}/one/two/three/d.foo
{abs_path}/one/two/three/directory_foo",
abs_path = &abs_path
),
);
}
#[test]
fn test_symlink_as_absolute_root() {
let (te, abs_path) = get_test_env_with_abs_path(DEFAULT_DIRS, DEFAULT_FILES);
te.assert_output(
&["", &format!("{abs_path}/symlink", abs_path = abs_path)],
&format!(
"{abs_path}/symlink/c.foo
{abs_path}/symlink/C.Foo2
{abs_path}/symlink/three
{abs_path}/symlink/three/d.foo
{abs_path}/symlink/three/directory_foo",
abs_path = &abs_path
),
);
}
#[test]
fn test_symlink_and_full_path() {
let (te, abs_path) = get_test_env_with_abs_path(DEFAULT_DIRS, DEFAULT_FILES);
let root = te.system_root();
let prefix = escape(&root.to_string_lossy());
te.assert_output_subdirectory(
"symlink",
&[
"--absolute-path",
"--full-path",
&format!("^{prefix}.*three", prefix = prefix),
],
&format!(
"{abs_path}/one/two/three
{abs_path}/one/two/three/d.foo
{abs_path}/one/two/three/directory_foo",
abs_path = &abs_path
),
);
}
#[test]
fn test_symlink_and_full_path_abs_path() {
let (te, abs_path) = get_test_env_with_abs_path(DEFAULT_DIRS, DEFAULT_FILES);
let root = te.system_root();
let prefix = escape(&root.to_string_lossy());
te.assert_output(
&[
"--full-path",
&format!("^{prefix}.*symlink.*three", prefix = prefix),
&format!("{abs_path}/symlink", abs_path = abs_path),
],
&format!(
"{abs_path}/symlink/three
{abs_path}/symlink/three/d.foo
{abs_path}/symlink/three/directory_foo",
abs_path = &abs_path
),
);
}
/// Exclude patterns (--exclude)
#[test]
fn test_excludes() {
let te = TestEnv::new(DEFAULT_DIRS, DEFAULT_FILES);
te.assert_output(
&["--exclude", "*.foo"],
"one
one/two
one/two/C.Foo2
one/two/three
one/two/three/directory_foo
e1 e2
symlink",
);
te.assert_output(
&["--exclude", "*.foo", "--exclude", "*.Foo2"],
"one
one/two
one/two/three
one/two/three/directory_foo
e1 e2
symlink",
);
te.assert_output(
&["--exclude", "*.foo", "--exclude", "*.Foo2", "foo"],
"one/two/three/directory_foo",
);
te.assert_output(
&["--exclude", "one/two", "foo"],
"a.foo
one/b.foo",
);
te.assert_output(
&["--exclude", "one/**/*.foo"],
"a.foo
e1 e2
one
one/two
one/two/C.Foo2
one/two/three
one/two/three/directory_foo
symlink",
);
}
/// Shell script execution (--exec)
#[test]
fn test_exec() {
assert_exec_output("--exec");
}
/// Shell script execution using -exec
#[test]
fn test_exec_substitution() {
assert_exec_output("-exec");
}
// Shell script execution using -x
#[test]
fn test_exec_short_arg() {
assert_exec_output("-x");
}
#[cfg(test)]
fn assert_exec_output(exec_style: &str) {
let (te, abs_path) = get_test_env_with_abs_path(DEFAULT_DIRS, DEFAULT_FILES);
// TODO Windows tests: D:file.txt \file.txt \\server\share\file.txt ...
if !cfg!(windows) {
te.assert_output(
&["--absolute-path", "foo", exec_style, "echo"],
&format!(
"{abs_path}/a.foo
{abs_path}/one/b.foo
{abs_path}/one/two/C.Foo2
{abs_path}/one/two/c.foo
{abs_path}/one/two/three/d.foo
{abs_path}/one/two/three/directory_foo",
abs_path = &abs_path
),
);
te.assert_output(
&["foo", exec_style, "echo", "{}"],
"a.foo
one/b.foo
one/two/C.Foo2
one/two/c.foo
one/two/three/d.foo
one/two/three/directory_foo",
);
te.assert_output(
&["foo", exec_style, "echo", "{.}"],
"a
one/b
one/two/C
one/two/c
one/two/three/d
one/two/three/directory_foo",
);
te.assert_output(
&["foo", exec_style, "echo", "{/}"],
"a.foo
b.foo
C.Foo2
c.foo
d.foo
directory_foo",
);
te.assert_output(
&["foo", exec_style, "echo", "{/.}"],
"a
b
C
c
d
directory_foo",
);
te.assert_output(
&["foo", exec_style, "echo", "{//}"],
".
one
one/two
one/two
one/two/three
one/two/three",
);
te.assert_output(&["e1", exec_style, "printf", "%s.%s\n"], "e1 e2.");
}
}
/// Literal search (--fixed-strings)
#[test]
fn test_fixed_strings() {
let dirs = &["test1", "test2"];
let files = &["test1/a.foo", "test1/a_foo", "test2/Download (1).tar.gz"];
let te = TestEnv::new(dirs, files);
// Regex search, dot is treated as "any character"
te.assert_output(
&["a.foo"],
"test1/a.foo
test1/a_foo",
);
// Literal search, dot is treated as character
te.assert_output(&["--fixed-strings", "a.foo"], "test1/a.foo");
// Regex search, parens are treated as group
te.assert_output(&["download (1)"], "");
// Literal search, parens are treated as characters
te.assert_output(
&["--fixed-strings", "download (1)"],
"test2/Download (1).tar.gz",
);
// Combine with --case-sensitive
te.assert_output(&["--fixed-strings", "--case-sensitive", "download (1)"], "");
}
/// Filenames with invalid UTF-8 sequences
#[cfg(target_os = "linux")]
#[test]
fn test_invalid_utf8() {
use std::ffi::OsStr;
use std::os::unix::ffi::OsStrExt;
let dirs = &["test1"];
let files = &[];
let te = TestEnv::new(dirs, files);
fs::File::create(
te.test_root()
.join(OsStr::from_bytes(b"test1/test_\xFEinvalid.txt")),
).unwrap();
te.assert_output(&["", "test1/"], "test1/test_<74>invalid.txt");
te.assert_output(&["invalid", "test1/"], "test1/test_<74>invalid.txt");
// Should not be found under a different extension
te.assert_output(&["-e", "zip", "", "test1/"], "");
}