"Fossies" - the Fresh Open Source Software Archive

Member "fd-8.1.1/tests/tests.rs" (25 May 2020, 41744 Bytes) of package /linux/privat/fd-8.1.1.tar.gz:


As a special service "Fossies" has tried to format the requested source page into HTML format using (guessed) Rust source code syntax highlighting (style: standard) with prefixed line numbers and code folding option. Alternatively you can here view or download the uninterpreted source code file. See also the last Fossies "Diffs" side-by-side code changes report for "tests.rs": 8.0.0_vs_8.1.0.

    1 mod testenv;
    2 
    3 use std::fs;
    4 use std::io::Write;
    5 use std::path::Path;
    6 use std::time::{Duration, SystemTime};
    7 
    8 use regex::escape;
    9 
   10 use crate::testenv::TestEnv;
   11 
   12 static DEFAULT_DIRS: &[&str] = &["one/two/three", "one/two/three/directory_foo"];
   13 
   14 static DEFAULT_FILES: &[&str] = &[
   15     "a.foo",
   16     "one/b.foo",
   17     "one/two/c.foo",
   18     "one/two/C.Foo2",
   19     "one/two/three/d.foo",
   20     "fdignored.foo",
   21     "gitignored.foo",
   22     ".hidden.foo",
   23     "e1 e2",
   24 ];
   25 
   26 fn get_absolute_root_path(env: &TestEnv) -> String {
   27     let path = env
   28         .test_root()
   29         .canonicalize()
   30         .expect("absolute path")
   31         .to_str()
   32         .expect("string")
   33         .to_string();
   34 
   35     #[cfg(windows)]
   36     let path = path.trim_start_matches(r"\\?\").to_string();
   37 
   38     path
   39 }
   40 
   41 #[cfg(test)]
   42 fn get_test_env_with_abs_path(dirs: &[&'static str], files: &[&'static str]) -> (TestEnv, String) {
   43     let env = TestEnv::new(dirs, files);
   44     let root_path = get_absolute_root_path(&env);
   45     (env, root_path)
   46 }
   47 
   48 #[cfg(test)]
   49 fn create_file_with_size<P: AsRef<Path>>(path: P, size_in_bytes: usize) {
   50     let content = "#".repeat(size_in_bytes);
   51     let mut f = fs::File::create::<P>(path).unwrap();
   52     f.write_all(content.as_bytes()).unwrap();
   53 }
   54 
   55 /// Simple test
   56 #[test]
   57 fn test_simple() {
   58     let te = TestEnv::new(DEFAULT_DIRS, DEFAULT_FILES);
   59 
   60     te.assert_output(&["a.foo"], "a.foo");
   61     te.assert_output(&["b.foo"], "one/b.foo");
   62     te.assert_output(&["d.foo"], "one/two/three/d.foo");
   63 
   64     te.assert_output(
   65         &["foo"],
   66         "a.foo
   67         one/b.foo
   68         one/two/c.foo
   69         one/two/C.Foo2
   70         one/two/three/d.foo
   71         one/two/three/directory_foo",
   72     );
   73 }
   74 
   75 /// Test each pattern type with an empty pattern.
   76 #[test]
   77 fn test_empty_pattern() {
   78     let te = TestEnv::new(DEFAULT_DIRS, DEFAULT_FILES);
   79     let expected = "a.foo
   80     e1 e2
   81     one
   82     one/b.foo
   83     one/two
   84     one/two/c.foo
   85     one/two/C.Foo2
   86     one/two/three
   87     one/two/three/d.foo
   88     one/two/three/directory_foo
   89     symlink";
   90 
   91     te.assert_output(&["--regex"], expected);
   92     te.assert_output(&["--fixed-strings"], expected);
   93     te.assert_output(&["--glob"], expected);
   94 }
   95 
   96 /// Test multiple directory searches
   97 #[test]
   98 fn test_multi_file() {
   99     let dirs = &["test1", "test2"];
  100     let files = &["test1/a.foo", "test1/b.foo", "test2/a.foo"];
  101     let te = TestEnv::new(dirs, files);
  102     te.assert_output(
  103         &["a.foo", "test1", "test2"],
  104         "test1/a.foo
  105         test2/a.foo",
  106     );
  107 
  108     te.assert_output(
  109         &["", "test1", "test2"],
  110         "test1/a.foo
  111         test2/a.foo
  112         test1/b.foo",
  113     );
  114 
  115     te.assert_output(&["a.foo", "test1"], "test1/a.foo");
  116 
  117     te.assert_output(&["b.foo", "test1", "test2"], "test1/b.foo");
  118 }
  119 
  120 /// Test search over multiple directory with missing
  121 #[test]
  122 fn test_multi_file_with_missing() {
  123     let dirs = &["real"];
  124     let files = &["real/a.foo", "real/b.foo"];
  125     let te = TestEnv::new(dirs, files);
  126     te.assert_output(&["a.foo", "real", "fake"], "real/a.foo");
  127 
  128     te.assert_error(
  129         &["a.foo", "real", "fake"],
  130         "[fd error]: Search path 'fake' is not a directory.",
  131     );
  132 
  133     te.assert_output(
  134         &["", "real", "fake"],
  135         "real/a.foo
  136         real/b.foo",
  137     );
  138 
  139     te.assert_output(
  140         &["", "real", "fake1", "fake2"],
  141         "real/a.foo
  142         real/b.foo",
  143     );
  144 
  145     te.assert_error(
  146         &["", "real", "fake1", "fake2"],
  147         "[fd error]: Search path 'fake1' is not a directory.
  148         [fd error]: Search path 'fake2' is not a directory.",
  149     );
  150 
  151     te.assert_failure_with_error(
  152         &["", "fake1", "fake2"],
  153         "[fd error]: Search path 'fake1' is not a directory.
  154         [fd error]: Search path 'fake2' is not a directory.
  155         [fd error]: No valid search paths given.",
  156     );
  157 }
  158 
  159 /// Explicit root path
  160 #[test]
  161 fn test_explicit_root_path() {
  162     let te = TestEnv::new(DEFAULT_DIRS, DEFAULT_FILES);
  163 
  164     te.assert_output(
  165         &["foo", "one"],
  166         "one/b.foo
  167         one/two/c.foo
  168         one/two/C.Foo2
  169         one/two/three/d.foo
  170         one/two/three/directory_foo",
  171     );
  172 
  173     te.assert_output(
  174         &["foo", "one/two/three"],
  175         "one/two/three/d.foo
  176         one/two/three/directory_foo",
  177     );
  178 
  179     te.assert_output_subdirectory(
  180         "one/two",
  181         &["foo", "../../"],
  182         "../../a.foo
  183         ../../one/b.foo
  184         ../../one/two/c.foo
  185         ../../one/two/C.Foo2
  186         ../../one/two/three/d.foo
  187         ../../one/two/three/directory_foo",
  188     );
  189 
  190     te.assert_output_subdirectory(
  191         "one/two/three",
  192         &["", ".."],
  193         "../c.foo
  194         ../C.Foo2
  195         ../three
  196         ../three/d.foo
  197         ../three/directory_foo",
  198     );
  199 }
  200 
  201 /// Regex searches
  202 #[test]
  203 fn test_regex_searches() {
  204     let te = TestEnv::new(DEFAULT_DIRS, DEFAULT_FILES);
  205 
  206     te.assert_output(
  207         &["[a-c].foo"],
  208         "a.foo
  209         one/b.foo
  210         one/two/c.foo
  211         one/two/C.Foo2",
  212     );
  213 
  214     te.assert_output(
  215         &["--case-sensitive", "[a-c].foo"],
  216         "a.foo
  217         one/b.foo
  218         one/two/c.foo",
  219     );
  220 }
  221 
  222 /// Smart case
  223 #[test]
  224 fn test_smart_case() {
  225     let te = TestEnv::new(DEFAULT_DIRS, DEFAULT_FILES);
  226 
  227     te.assert_output(
  228         &["c.foo"],
  229         "one/two/c.foo
  230         one/two/C.Foo2",
  231     );
  232 
  233     te.assert_output(&["C.Foo"], "one/two/C.Foo2");
  234 
  235     te.assert_output(&["Foo"], "one/two/C.Foo2");
  236 
  237     // Only literal uppercase chars should trigger case sensitivity.
  238     te.assert_output(
  239         &["\\Ac"],
  240         "one/two/c.foo
  241         one/two/C.Foo2",
  242     );
  243     te.assert_output(&["\\AC"], "one/two/C.Foo2");
  244 }
  245 
  246 /// Case sensitivity (--case-sensitive)
  247 #[test]
  248 fn test_case_sensitive() {
  249     let te = TestEnv::new(DEFAULT_DIRS, DEFAULT_FILES);
  250 
  251     te.assert_output(&["--case-sensitive", "c.foo"], "one/two/c.foo");
  252 
  253     te.assert_output(&["--case-sensitive", "C.Foo"], "one/two/C.Foo2");
  254 
  255     te.assert_output(
  256         &["--ignore-case", "--case-sensitive", "C.Foo"],
  257         "one/two/C.Foo2",
  258     );
  259 }
  260 
  261 /// Case insensitivity (--ignore-case)
  262 #[test]
  263 fn test_case_insensitive() {
  264     let te = TestEnv::new(DEFAULT_DIRS, DEFAULT_FILES);
  265 
  266     te.assert_output(
  267         &["--ignore-case", "C.Foo"],
  268         "one/two/c.foo
  269         one/two/C.Foo2",
  270     );
  271 
  272     te.assert_output(
  273         &["--case-sensitive", "--ignore-case", "C.Foo"],
  274         "one/two/c.foo
  275         one/two/C.Foo2",
  276     );
  277 }
  278 
  279 /// Glob-based searches (--glob)
  280 #[test]
  281 fn test_glob_searches() {
  282     let te = TestEnv::new(DEFAULT_DIRS, DEFAULT_FILES);
  283 
  284     te.assert_output(
  285         &["--glob", "*.foo"],
  286         "a.foo
  287         one/b.foo
  288         one/two/c.foo
  289         one/two/three/d.foo",
  290     );
  291 
  292     te.assert_output(
  293         &["--glob", "[a-c].foo"],
  294         "a.foo
  295         one/b.foo
  296         one/two/c.foo",
  297     );
  298 
  299     te.assert_output(
  300         &["--glob", "[a-c].foo*"],
  301         "a.foo
  302         one/b.foo
  303         one/two/C.Foo2
  304         one/two/c.foo",
  305     );
  306 }
  307 
  308 /// Glob-based searches (--glob) in combination with full path searches (--full-path)
  309 #[cfg(not(windows))] // TODO: make this work on Windows
  310 #[test]
  311 fn test_full_path_glob_searches() {
  312     let te = TestEnv::new(DEFAULT_DIRS, DEFAULT_FILES);
  313 
  314     te.assert_output(
  315         &["--glob", "--full-path", "**/one/**/*.foo"],
  316         "one/b.foo
  317         one/two/c.foo
  318         one/two/three/d.foo",
  319     );
  320 
  321     te.assert_output(
  322         &["--glob", "--full-path", "**/one/*/*.foo"],
  323         " one/two/c.foo",
  324     );
  325 
  326     te.assert_output(
  327         &["--glob", "--full-path", "**/one/*/*/*.foo"],
  328         " one/two/three/d.foo",
  329     );
  330 }
  331 
  332 #[test]
  333 fn test_smart_case_glob_searches() {
  334     let te = TestEnv::new(DEFAULT_DIRS, DEFAULT_FILES);
  335 
  336     te.assert_output(
  337         &["--glob", "c.foo*"],
  338         "one/two/C.Foo2
  339         one/two/c.foo",
  340     );
  341 
  342     te.assert_output(&["--glob", "C.Foo*"], "one/two/C.Foo2");
  343 }
  344 
  345 /// Glob-based searches (--glob) in combination with --case-sensitive
  346 #[test]
  347 fn test_case_sensitive_glob_searches() {
  348     let te = TestEnv::new(DEFAULT_DIRS, DEFAULT_FILES);
  349 
  350     te.assert_output(&["--glob", "--case-sensitive", "c.foo*"], "one/two/c.foo");
  351 }
  352 
  353 /// Glob-based searches (--glob) in combination with --extension
  354 #[test]
  355 fn test_glob_searches_with_extension() {
  356     let te = TestEnv::new(DEFAULT_DIRS, DEFAULT_FILES);
  357 
  358     te.assert_output(
  359         &["--glob", "--extension", "foo2", "[a-z].*"],
  360         "one/two/C.Foo2",
  361     );
  362 }
  363 
  364 /// Make sure that --regex overrides --glob
  365 #[test]
  366 fn test_regex_overrides_glob() {
  367     let te = TestEnv::new(DEFAULT_DIRS, DEFAULT_FILES);
  368 
  369     te.assert_output(&["--glob", "--regex", "Foo2$"], "one/two/C.Foo2");
  370 }
  371 
  372 /// Full path search (--full-path)
  373 #[test]
  374 fn test_full_path() {
  375     let te = TestEnv::new(DEFAULT_DIRS, DEFAULT_FILES);
  376 
  377     let root = te.system_root();
  378     let prefix = escape(&root.to_string_lossy());
  379 
  380     te.assert_output(
  381         &[
  382             "--full-path",
  383             &format!("^{prefix}.*three.*foo$", prefix = prefix),
  384         ],
  385         "one/two/three/d.foo
  386         one/two/three/directory_foo",
  387     );
  388 }
  389 
  390 /// Hidden files (--hidden)
  391 #[test]
  392 fn test_hidden() {
  393     let te = TestEnv::new(DEFAULT_DIRS, DEFAULT_FILES);
  394 
  395     te.assert_output(
  396         &["--hidden", "foo"],
  397         ".hidden.foo
  398         a.foo
  399         one/b.foo
  400         one/two/c.foo
  401         one/two/C.Foo2
  402         one/two/three/d.foo
  403         one/two/three/directory_foo",
  404     );
  405 }
  406 
  407 /// Hidden file attribute on Windows
  408 #[cfg(windows)]
  409 #[test]
  410 fn test_hidden_file_attribute() {
  411     use std::os::windows::fs::OpenOptionsExt;
  412 
  413     let te = TestEnv::new(DEFAULT_DIRS, DEFAULT_FILES);
  414 
  415     // https://docs.microsoft.com/en-us/windows/win32/api/fileapi/nf-fileapi-setfileattributesa
  416     const FILE_ATTRIBUTE_HIDDEN: u32 = 2;
  417 
  418     fs::OpenOptions::new()
  419         .create(true)
  420         .write(true)
  421         .attributes(FILE_ATTRIBUTE_HIDDEN)
  422         .open(te.test_root().join("hidden-file.txt"))
  423         .unwrap();
  424 
  425     te.assert_output(&["--hidden", "hidden-file.txt"], "hidden-file.txt");
  426     te.assert_output(&["hidden-file.txt"], "");
  427 }
  428 
  429 /// Ignored files (--no-ignore)
  430 #[test]
  431 fn test_no_ignore() {
  432     let te = TestEnv::new(DEFAULT_DIRS, DEFAULT_FILES);
  433 
  434     te.assert_output(
  435         &["--no-ignore", "foo"],
  436         "a.foo
  437         fdignored.foo
  438         gitignored.foo
  439         one/b.foo
  440         one/two/c.foo
  441         one/two/C.Foo2
  442         one/two/three/d.foo
  443         one/two/three/directory_foo",
  444     );
  445 
  446     te.assert_output(
  447         &["--hidden", "--no-ignore", "foo"],
  448         ".hidden.foo
  449         a.foo
  450         fdignored.foo
  451         gitignored.foo
  452         one/b.foo
  453         one/two/c.foo
  454         one/two/C.Foo2
  455         one/two/three/d.foo
  456         one/two/three/directory_foo",
  457     );
  458 }
  459 
  460 /// .gitignore and .fdignore
  461 #[test]
  462 fn test_gitignore_and_fdignore() {
  463     let files = &[
  464         "ignored-by-nothing",
  465         "ignored-by-fdignore",
  466         "ignored-by-gitignore",
  467         "ignored-by-both",
  468     ];
  469     let te = TestEnv::new(&[], files);
  470 
  471     fs::File::create(te.test_root().join(".fdignore"))
  472         .unwrap()
  473         .write_all(b"ignored-by-fdignore\nignored-by-both")
  474         .unwrap();
  475 
  476     fs::File::create(te.test_root().join(".gitignore"))
  477         .unwrap()
  478         .write_all(b"ignored-by-gitignore\nignored-by-both")
  479         .unwrap();
  480 
  481     te.assert_output(&["ignored"], "ignored-by-nothing");
  482 
  483     te.assert_output(
  484         &["--no-ignore-vcs", "ignored"],
  485         "ignored-by-nothing
  486         ignored-by-gitignore",
  487     );
  488 
  489     te.assert_output(
  490         &["--no-ignore", "ignored"],
  491         "ignored-by-nothing
  492         ignored-by-fdignore
  493         ignored-by-gitignore
  494         ignored-by-both",
  495     );
  496 }
  497 
  498 /// Precedence of .fdignore files
  499 #[test]
  500 fn test_custom_ignore_precedence() {
  501     let dirs = &["inner"];
  502     let files = &["inner/foo"];
  503     let te = TestEnv::new(dirs, files);
  504 
  505     // Ignore 'foo' via .gitignore
  506     fs::File::create(te.test_root().join("inner/.gitignore"))
  507         .unwrap()
  508         .write_all(b"foo")
  509         .unwrap();
  510 
  511     // Whitelist 'foo' via .fdignore
  512     fs::File::create(te.test_root().join(".fdignore"))
  513         .unwrap()
  514         .write_all(b"!foo")
  515         .unwrap();
  516 
  517     te.assert_output(&["foo"], "inner/foo");
  518 
  519     te.assert_output(&["--no-ignore-vcs", "foo"], "inner/foo");
  520 
  521     te.assert_output(&["--no-ignore", "foo"], "inner/foo");
  522 }
  523 
  524 /// VCS ignored files (--no-ignore-vcs)
  525 #[test]
  526 fn test_no_ignore_vcs() {
  527     let te = TestEnv::new(DEFAULT_DIRS, DEFAULT_FILES);
  528 
  529     te.assert_output(
  530         &["--no-ignore-vcs", "foo"],
  531         "a.foo
  532         gitignored.foo
  533         one/b.foo
  534         one/two/c.foo
  535         one/two/C.Foo2
  536         one/two/three/d.foo
  537         one/two/three/directory_foo",
  538     );
  539 }
  540 
  541 /// Custom ignore files (--ignore-file)
  542 #[test]
  543 fn test_custom_ignore_files() {
  544     let te = TestEnv::new(DEFAULT_DIRS, DEFAULT_FILES);
  545 
  546     // Ignore 'C.Foo2' and everything in 'three'.
  547     fs::File::create(te.test_root().join("custom.ignore"))
  548         .unwrap()
  549         .write_all(b"C.Foo2\nthree")
  550         .unwrap();
  551 
  552     te.assert_output(
  553         &["--ignore-file", "custom.ignore", "foo"],
  554         "a.foo
  555         one/b.foo
  556         one/two/c.foo",
  557     );
  558 }
  559 
  560 /// Ignored files with ripgrep aliases (-u / -uu)
  561 #[test]
  562 fn test_no_ignore_aliases() {
  563     let te = TestEnv::new(DEFAULT_DIRS, DEFAULT_FILES);
  564 
  565     te.assert_output(
  566         &["-u", "foo"],
  567         "a.foo
  568         fdignored.foo
  569         gitignored.foo
  570         one/b.foo
  571         one/two/c.foo
  572         one/two/C.Foo2
  573         one/two/three/d.foo
  574         one/two/three/directory_foo",
  575     );
  576 
  577     te.assert_output(
  578         &["-uu", "foo"],
  579         ".hidden.foo
  580         a.foo
  581         fdignored.foo
  582         gitignored.foo
  583         one/b.foo
  584         one/two/c.foo
  585         one/two/C.Foo2
  586         one/two/three/d.foo
  587         one/two/three/directory_foo",
  588     );
  589 }
  590 
  591 /// Symlinks (--follow)
  592 #[test]
  593 fn test_follow() {
  594     let te = TestEnv::new(DEFAULT_DIRS, DEFAULT_FILES);
  595 
  596     te.assert_output(
  597         &["--follow", "c.foo"],
  598         "one/two/c.foo
  599         one/two/C.Foo2
  600         symlink/c.foo
  601         symlink/C.Foo2",
  602     );
  603 }
  604 
  605 // File system boundaries (--one-file-system)
  606 // Limited to Unix because, to the best of my knowledge, there is no easy way to test a use case
  607 // file systems mounted into the tree on Windows.
  608 // Not limiting depth causes massive delay under Darwin, see BurntSushi/ripgrep#1429
  609 #[test]
  610 #[cfg(unix)]
  611 fn test_file_system_boundaries() {
  612     // Helper function to get the device ID for a given path
  613     // Inspired by https://github.com/BurntSushi/ripgrep/blob/8892bf648cfec111e6e7ddd9f30e932b0371db68/ignore/src/walk.rs#L1693
  614     fn device_num(path: impl AsRef<Path>) -> u64 {
  615         use std::os::unix::fs::MetadataExt;
  616 
  617         path.as_ref().metadata().map(|md| md.dev()).unwrap()
  618     }
  619 
  620     // Can't simulate file system boundaries
  621     let te = TestEnv::new(&[], &[]);
  622 
  623     let dev_null = Path::new("/dev/null");
  624 
  625     // /dev/null should exist in all sane Unixes. Skip if it doesn't exist for some reason.
  626     // Also skip should it be on the same device as the root partition for some reason.
  627     if !dev_null.is_file() || device_num(dev_null) == device_num("/") {
  628         return;
  629     }
  630 
  631     te.assert_output(
  632         &["--full-path", "--max-depth", "2", "^/dev/null$", "/"],
  633         "/dev/null",
  634     );
  635     te.assert_output(
  636         &[
  637             "--one-file-system",
  638             "--full-path",
  639             "--max-depth",
  640             "2",
  641             "^/dev/null$",
  642             "/",
  643         ],
  644         "",
  645     );
  646 }
  647 
  648 #[test]
  649 fn test_follow_broken_symlink() {
  650     let mut te = TestEnv::new(DEFAULT_DIRS, DEFAULT_FILES);
  651     te.create_broken_symlink("broken_symlink")
  652         .expect("Failed to create broken symlink.");
  653 
  654     te.assert_output(
  655         &["symlink"],
  656         "broken_symlink
  657         symlink",
  658     );
  659     te.assert_output(
  660         &["--type", "symlink", "symlink"],
  661         "broken_symlink
  662         symlink",
  663     );
  664 
  665     te.assert_output(&["--type", "file", "symlink"], "");
  666 
  667     te.assert_output(
  668         &["--follow", "--type", "symlink", "symlink"],
  669         "broken_symlink",
  670     );
  671     te.assert_output(&["--follow", "--type", "file", "symlink"], "");
  672 }
  673 
  674 /// Null separator (--print0)
  675 #[test]
  676 fn test_print0() {
  677     let te = TestEnv::new(DEFAULT_DIRS, DEFAULT_FILES);
  678 
  679     te.assert_output(
  680         &["--print0", "foo"],
  681         "a.fooNULL
  682         one/b.fooNULL
  683         one/two/C.Foo2NULL
  684         one/two/c.fooNULL
  685         one/two/three/d.fooNULL
  686         one/two/three/directory_fooNULL",
  687     );
  688 }
  689 
  690 /// Maximum depth (--max-depth)
  691 #[test]
  692 fn test_max_depth() {
  693     let te = TestEnv::new(DEFAULT_DIRS, DEFAULT_FILES);
  694 
  695     te.assert_output(
  696         &["--max-depth", "3"],
  697         "a.foo
  698         e1 e2
  699         one
  700         one/b.foo
  701         one/two
  702         one/two/c.foo
  703         one/two/C.Foo2
  704         one/two/three
  705         symlink",
  706     );
  707 
  708     te.assert_output(
  709         &["--max-depth", "2"],
  710         "a.foo
  711         e1 e2
  712         one
  713         one/b.foo
  714         one/two
  715         symlink",
  716     );
  717 
  718     te.assert_output(
  719         &["--max-depth", "1"],
  720         "a.foo
  721         e1 e2
  722         one
  723         symlink",
  724     );
  725 }
  726 
  727 /// Minimum depth (--min-depth)
  728 #[test]
  729 fn test_min_depth() {
  730     let te = TestEnv::new(DEFAULT_DIRS, DEFAULT_FILES);
  731 
  732     te.assert_output(
  733         &["--min-depth", "3"],
  734         "one/two/c.foo
  735         one/two/C.Foo2
  736         one/two/three
  737         one/two/three/d.foo
  738         one/two/three/directory_foo",
  739     );
  740 
  741     te.assert_output(
  742         &["--min-depth", "4"],
  743         "one/two/three/d.foo
  744         one/two/three/directory_foo",
  745     );
  746 }
  747 
  748 /// Exact depth (--exact-depth)
  749 #[test]
  750 fn test_exact_depth() {
  751     let te = TestEnv::new(DEFAULT_DIRS, DEFAULT_FILES);
  752 
  753     te.assert_output(
  754         &["--exact-depth", "3"],
  755         "one/two/c.foo
  756         one/two/C.Foo2
  757         one/two/three",
  758     );
  759 }
  760 
  761 /// Absolute paths (--absolute-path)
  762 #[test]
  763 fn test_absolute_path() {
  764     let (te, abs_path) = get_test_env_with_abs_path(DEFAULT_DIRS, DEFAULT_FILES);
  765 
  766     te.assert_output(
  767         &["--absolute-path"],
  768         &format!(
  769             "{abs_path}/a.foo
  770             {abs_path}/e1 e2
  771             {abs_path}/one
  772             {abs_path}/one/b.foo
  773             {abs_path}/one/two
  774             {abs_path}/one/two/c.foo
  775             {abs_path}/one/two/C.Foo2
  776             {abs_path}/one/two/three
  777             {abs_path}/one/two/three/d.foo
  778             {abs_path}/one/two/three/directory_foo
  779             {abs_path}/symlink",
  780             abs_path = &abs_path
  781         ),
  782     );
  783 
  784     te.assert_output(
  785         &["--absolute-path", "foo"],
  786         &format!(
  787             "{abs_path}/a.foo
  788             {abs_path}/one/b.foo
  789             {abs_path}/one/two/c.foo
  790             {abs_path}/one/two/C.Foo2
  791             {abs_path}/one/two/three/d.foo
  792             {abs_path}/one/two/three/directory_foo",
  793             abs_path = &abs_path
  794         ),
  795     );
  796 }
  797 
  798 /// Show absolute paths if the path argument is absolute
  799 #[test]
  800 fn test_implicit_absolute_path() {
  801     let (te, abs_path) = get_test_env_with_abs_path(DEFAULT_DIRS, DEFAULT_FILES);
  802 
  803     te.assert_output(
  804         &["foo", &abs_path],
  805         &format!(
  806             "{abs_path}/a.foo
  807             {abs_path}/one/b.foo
  808             {abs_path}/one/two/c.foo
  809             {abs_path}/one/two/C.Foo2
  810             {abs_path}/one/two/three/d.foo
  811             {abs_path}/one/two/three/directory_foo",
  812             abs_path = &abs_path
  813         ),
  814     );
  815 }
  816 
  817 /// Absolute paths should be normalized
  818 #[test]
  819 fn test_normalized_absolute_path() {
  820     let (te, abs_path) = get_test_env_with_abs_path(DEFAULT_DIRS, DEFAULT_FILES);
  821 
  822     te.assert_output_subdirectory(
  823         "one",
  824         &["--absolute-path", "foo", ".."],
  825         &format!(
  826             "{abs_path}/a.foo
  827             {abs_path}/one/b.foo
  828             {abs_path}/one/two/c.foo
  829             {abs_path}/one/two/C.Foo2
  830             {abs_path}/one/two/three/d.foo
  831             {abs_path}/one/two/three/directory_foo",
  832             abs_path = &abs_path
  833         ),
  834     );
  835 }
  836 
  837 /// File type filter (--type)
  838 #[test]
  839 fn test_type() {
  840     let te = TestEnv::new(DEFAULT_DIRS, DEFAULT_FILES);
  841 
  842     te.assert_output(
  843         &["--type", "f"],
  844         "a.foo
  845         e1 e2
  846         one/b.foo
  847         one/two/c.foo
  848         one/two/C.Foo2
  849         one/two/three/d.foo",
  850     );
  851 
  852     te.assert_output(&["--type", "f", "e1"], "e1 e2");
  853 
  854     te.assert_output(
  855         &["--type", "d"],
  856         "one
  857         one/two
  858         one/two/three
  859         one/two/three/directory_foo",
  860     );
  861 
  862     te.assert_output(
  863         &["--type", "d", "--type", "l"],
  864         "one
  865         one/two
  866         one/two/three
  867         one/two/three/directory_foo
  868         symlink",
  869     );
  870 
  871     te.assert_output(&["--type", "l"], "symlink");
  872 }
  873 
  874 /// Test `--type executable`
  875 #[cfg(unix)]
  876 #[test]
  877 fn test_type_executable() {
  878     use std::os::unix::fs::OpenOptionsExt;
  879 
  880     let te = TestEnv::new(DEFAULT_DIRS, DEFAULT_FILES);
  881 
  882     fs::OpenOptions::new()
  883         .create(true)
  884         .write(true)
  885         .mode(0o777)
  886         .open(te.test_root().join("executable-file.sh"))
  887         .unwrap();
  888 
  889     te.assert_output(&["--type", "executable"], "executable-file.sh");
  890 
  891     te.assert_output(
  892         &["--type", "executable", "--type", "directory"],
  893         "executable-file.sh
  894         one
  895         one/two
  896         one/two/three
  897         one/two/three/directory_foo",
  898     );
  899 }
  900 
  901 /// Test `--type empty`
  902 #[test]
  903 fn test_type_empty() {
  904     let te = TestEnv::new(&["dir_empty", "dir_nonempty"], &[]);
  905 
  906     create_file_with_size(te.test_root().join("0_bytes.foo"), 0);
  907     create_file_with_size(te.test_root().join("5_bytes.foo"), 5);
  908 
  909     create_file_with_size(te.test_root().join("dir_nonempty").join("2_bytes.foo"), 2);
  910 
  911     te.assert_output(
  912         &["--type", "empty"],
  913         "0_bytes.foo
  914         dir_empty",
  915     );
  916 
  917     te.assert_output(
  918         &["--type", "empty", "--type", "file", "--type", "directory"],
  919         "0_bytes.foo
  920         dir_empty",
  921     );
  922 
  923     te.assert_output(&["--type", "empty", "--type", "file"], "0_bytes.foo");
  924 
  925     te.assert_output(&["--type", "empty", "--type", "directory"], "dir_empty");
  926 }
  927 
  928 /// File extension (--extension)
  929 #[test]
  930 fn test_extension() {
  931     let te = TestEnv::new(DEFAULT_DIRS, DEFAULT_FILES);
  932 
  933     te.assert_output(
  934         &["--extension", "foo"],
  935         "a.foo
  936         one/b.foo
  937         one/two/c.foo
  938         one/two/three/d.foo",
  939     );
  940 
  941     te.assert_output(
  942         &["--extension", ".foo"],
  943         "a.foo
  944         one/b.foo
  945         one/two/c.foo
  946         one/two/three/d.foo",
  947     );
  948 
  949     te.assert_output(
  950         &["--extension", ".foo", "--extension", "foo2"],
  951         "a.foo
  952         one/b.foo
  953         one/two/c.foo
  954         one/two/three/d.foo
  955         one/two/C.Foo2",
  956     );
  957 
  958     te.assert_output(&["--extension", ".foo", "a"], "a.foo");
  959 
  960     te.assert_output(&["--extension", "foo2"], "one/two/C.Foo2");
  961 
  962     let te2 = TestEnv::new(&[], &["spam.bar.baz", "egg.bar.baz", "yolk.bar.baz.sig"]);
  963 
  964     te2.assert_output(
  965         &["--extension", ".bar.baz"],
  966         "spam.bar.baz
  967         egg.bar.baz",
  968     );
  969 
  970     te2.assert_output(&["--extension", "sig"], "yolk.bar.baz.sig");
  971 
  972     te2.assert_output(&["--extension", "bar.baz.sig"], "yolk.bar.baz.sig");
  973 
  974     let te3 = TestEnv::new(&[], &["latin1.e\u{301}xt", "smiley.☻"]);
  975 
  976     te3.assert_output(&["--extension", "☻"], "smiley.☻");
  977 
  978     te3.assert_output(&["--extension", ".e\u{301}xt"], "latin1.e\u{301}xt");
  979 
  980     let te4 = TestEnv::new(&[], &[".hidden", "test.hidden"]);
  981 
  982     te4.assert_output(&["--hidden", "--extension", ".hidden"], "test.hidden");
  983 }
  984 
  985 /// Symlink as search directory
  986 #[test]
  987 fn test_symlink_as_root() {
  988     let mut te = TestEnv::new(DEFAULT_DIRS, DEFAULT_FILES);
  989     te.create_broken_symlink("broken_symlink")
  990         .expect("Failed to create broken symlink.");
  991 
  992     // From: http://pubs.opengroup.org/onlinepubs/9699919799/functions/getcwd.html
  993     // The getcwd() function shall place an absolute pathname of the current working directory in
  994     // the array pointed to by buf, and return buf. The pathname shall contain no components that
  995     // are dot or dot-dot, or are symbolic links.
  996     //
  997     // Key points:
  998     // 1. The path of the current working directory of a Unix process cannot contain symlinks.
  999     // 2. The path of the current working directory of a Windows process can contain symlinks.
 1000     //
 1001     // More:
 1002     // 1. On Windows, symlinks are resolved after the ".." component.
 1003     // 2. On Unix, symlinks are resolved immediately as encountered.
 1004 
 1005     let parent_parent = if cfg!(windows) { ".." } else { "../.." };
 1006     te.assert_output_subdirectory(
 1007         "symlink",
 1008         &["", parent_parent],
 1009         &format!(
 1010             "{dir}/a.foo
 1011             {dir}/broken_symlink
 1012             {dir}/e1 e2
 1013             {dir}/one
 1014             {dir}/one/b.foo
 1015             {dir}/one/two
 1016             {dir}/one/two/c.foo
 1017             {dir}/one/two/C.Foo2
 1018             {dir}/one/two/three
 1019             {dir}/one/two/three/d.foo
 1020             {dir}/one/two/three/directory_foo
 1021             {dir}/symlink",
 1022             dir = &parent_parent
 1023         ),
 1024     );
 1025 }
 1026 
 1027 #[test]
 1028 fn test_symlink_and_absolute_path() {
 1029     let (te, abs_path) = get_test_env_with_abs_path(DEFAULT_DIRS, DEFAULT_FILES);
 1030 
 1031     te.assert_output_subdirectory(
 1032         "symlink",
 1033         &["--absolute-path"],
 1034         &format!(
 1035             "{abs_path}/one/two/c.foo
 1036             {abs_path}/one/two/C.Foo2
 1037             {abs_path}/one/two/three
 1038             {abs_path}/one/two/three/d.foo
 1039             {abs_path}/one/two/three/directory_foo",
 1040             abs_path = &abs_path
 1041         ),
 1042     );
 1043 }
 1044 
 1045 #[test]
 1046 fn test_symlink_as_absolute_root() {
 1047     let (te, abs_path) = get_test_env_with_abs_path(DEFAULT_DIRS, DEFAULT_FILES);
 1048 
 1049     te.assert_output(
 1050         &["", &format!("{abs_path}/symlink", abs_path = abs_path)],
 1051         &format!(
 1052             "{abs_path}/symlink/c.foo
 1053             {abs_path}/symlink/C.Foo2
 1054             {abs_path}/symlink/three
 1055             {abs_path}/symlink/three/d.foo
 1056             {abs_path}/symlink/three/directory_foo",
 1057             abs_path = &abs_path
 1058         ),
 1059     );
 1060 }
 1061 
 1062 #[test]
 1063 fn test_symlink_and_full_path() {
 1064     let (te, abs_path) = get_test_env_with_abs_path(DEFAULT_DIRS, DEFAULT_FILES);
 1065     let root = te.system_root();
 1066     let prefix = escape(&root.to_string_lossy());
 1067 
 1068     te.assert_output_subdirectory(
 1069         "symlink",
 1070         &[
 1071             "--absolute-path",
 1072             "--full-path",
 1073             &format!("^{prefix}.*three", prefix = prefix),
 1074         ],
 1075         &format!(
 1076             "{abs_path}/one/two/three
 1077             {abs_path}/one/two/three/d.foo
 1078             {abs_path}/one/two/three/directory_foo",
 1079             abs_path = &abs_path
 1080         ),
 1081     );
 1082 }
 1083 
 1084 #[test]
 1085 fn test_symlink_and_full_path_abs_path() {
 1086     let (te, abs_path) = get_test_env_with_abs_path(DEFAULT_DIRS, DEFAULT_FILES);
 1087     let root = te.system_root();
 1088     let prefix = escape(&root.to_string_lossy());
 1089     te.assert_output(
 1090         &[
 1091             "--full-path",
 1092             &format!("^{prefix}.*symlink.*three", prefix = prefix),
 1093             &format!("{abs_path}/symlink", abs_path = abs_path),
 1094         ],
 1095         &format!(
 1096             "{abs_path}/symlink/three
 1097             {abs_path}/symlink/three/d.foo
 1098             {abs_path}/symlink/three/directory_foo",
 1099             abs_path = &abs_path
 1100         ),
 1101     );
 1102 }
 1103 /// Exclude patterns (--exclude)
 1104 #[test]
 1105 fn test_excludes() {
 1106     let te = TestEnv::new(DEFAULT_DIRS, DEFAULT_FILES);
 1107 
 1108     te.assert_output(
 1109         &["--exclude", "*.foo"],
 1110         "one
 1111         one/two
 1112         one/two/C.Foo2
 1113         one/two/three
 1114         one/two/three/directory_foo
 1115         e1 e2
 1116         symlink",
 1117     );
 1118 
 1119     te.assert_output(
 1120         &["--exclude", "*.foo", "--exclude", "*.Foo2"],
 1121         "one
 1122         one/two
 1123         one/two/three
 1124         one/two/three/directory_foo
 1125         e1 e2
 1126         symlink",
 1127     );
 1128 
 1129     te.assert_output(
 1130         &["--exclude", "*.foo", "--exclude", "*.Foo2", "foo"],
 1131         "one/two/three/directory_foo",
 1132     );
 1133 
 1134     te.assert_output(
 1135         &["--exclude", "one/two", "foo"],
 1136         "a.foo
 1137         one/b.foo",
 1138     );
 1139 
 1140     te.assert_output(
 1141         &["--exclude", "one/**/*.foo"],
 1142         "a.foo
 1143         e1 e2
 1144         one
 1145         one/two
 1146         one/two/C.Foo2
 1147         one/two/three
 1148         one/two/three/directory_foo
 1149         symlink",
 1150     );
 1151 }
 1152 
 1153 /// Shell script execution (--exec)
 1154 #[test]
 1155 fn test_exec() {
 1156     let (te, abs_path) = get_test_env_with_abs_path(DEFAULT_DIRS, DEFAULT_FILES);
 1157     // TODO Windows tests: D:file.txt \file.txt \\server\share\file.txt ...
 1158     if !cfg!(windows) {
 1159         te.assert_output(
 1160             &["--absolute-path", "foo", "--exec", "echo"],
 1161             &format!(
 1162                 "{abs_path}/a.foo
 1163                 {abs_path}/one/b.foo
 1164                 {abs_path}/one/two/C.Foo2
 1165                 {abs_path}/one/two/c.foo
 1166                 {abs_path}/one/two/three/d.foo
 1167                 {abs_path}/one/two/three/directory_foo",
 1168                 abs_path = &abs_path
 1169             ),
 1170         );
 1171 
 1172         te.assert_output(
 1173             &["foo", "--exec", "echo", "{}"],
 1174             "a.foo
 1175             one/b.foo
 1176             one/two/C.Foo2
 1177             one/two/c.foo
 1178             one/two/three/d.foo
 1179             one/two/three/directory_foo",
 1180         );
 1181 
 1182         te.assert_output(
 1183             &["foo", "--exec", "echo", "{.}"],
 1184             "a
 1185             one/b
 1186             one/two/C
 1187             one/two/c
 1188             one/two/three/d
 1189             one/two/three/directory_foo",
 1190         );
 1191 
 1192         te.assert_output(
 1193             &["foo", "--exec", "echo", "{/}"],
 1194             "a.foo
 1195             b.foo
 1196             C.Foo2
 1197             c.foo
 1198             d.foo
 1199             directory_foo",
 1200         );
 1201 
 1202         te.assert_output(
 1203             &["foo", "--exec", "echo", "{/.}"],
 1204             "a
 1205             b
 1206             C
 1207             c
 1208             d
 1209             directory_foo",
 1210         );
 1211 
 1212         te.assert_output(
 1213             &["foo", "--exec", "echo", "{//}"],
 1214             ".
 1215             one
 1216             one/two
 1217             one/two
 1218             one/two/three
 1219             one/two/three",
 1220         );
 1221 
 1222         te.assert_output(&["e1", "--exec", "printf", "%s.%s\n"], "e1 e2.");
 1223     }
 1224 }
 1225 
 1226 #[test]
 1227 fn test_exec_batch() {
 1228     let (te, abs_path) = get_test_env_with_abs_path(DEFAULT_DIRS, DEFAULT_FILES);
 1229     let te = te.normalize_line(true);
 1230 
 1231     // TODO Test for windows
 1232     if !cfg!(windows) {
 1233         te.assert_output(
 1234             &["--absolute-path", "foo", "--exec-batch", "echo"],
 1235             &format!(
 1236                 "{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",
 1237                 abs_path = &abs_path
 1238             ),
 1239         );
 1240 
 1241         te.assert_output(
 1242             &["foo", "--exec-batch", "echo", "{}"],
 1243             "a.foo one/b.foo one/two/C.Foo2 one/two/c.foo one/two/three/d.foo one/two/three/directory_foo",
 1244         );
 1245 
 1246         te.assert_output(
 1247             &["foo", "--exec-batch", "echo", "{/}"],
 1248             "a.foo b.foo C.Foo2 c.foo d.foo directory_foo",
 1249         );
 1250 
 1251         te.assert_output(
 1252             &["no_match", "--exec-batch", "echo", "Matched: ", "{/}"],
 1253             "",
 1254         );
 1255 
 1256         te.assert_failure_with_error(
 1257             &["foo", "--exec-batch", "echo", "{}", "{}"],
 1258             "[fd error]: Only one placeholder allowed for batch commands",
 1259         );
 1260 
 1261         te.assert_failure_with_error(
 1262             &["foo", "--exec-batch", "echo", "{/}", ";", "-x", "echo"],
 1263             "error: The argument '--exec <cmd>' cannot be used with '--exec-batch <cmd>'",
 1264         );
 1265 
 1266         te.assert_failure_with_error(
 1267             &["foo", "--exec-batch"],
 1268             "error: The argument '--exec-batch <cmd>' requires a value but none was supplied",
 1269         );
 1270 
 1271         te.assert_failure_with_error(
 1272             &["foo", "--exec-batch", "echo {}"],
 1273             "[fd error]: First argument of exec-batch is expected to be a fixed executable",
 1274         );
 1275     }
 1276 }
 1277 
 1278 /// Literal search (--fixed-strings)
 1279 #[test]
 1280 fn test_fixed_strings() {
 1281     let dirs = &["test1", "test2"];
 1282     let files = &["test1/a.foo", "test1/a_foo", "test2/Download (1).tar.gz"];
 1283     let te = TestEnv::new(dirs, files);
 1284 
 1285     // Regex search, dot is treated as "any character"
 1286     te.assert_output(
 1287         &["a.foo"],
 1288         "test1/a.foo
 1289          test1/a_foo",
 1290     );
 1291 
 1292     // Literal search, dot is treated as character
 1293     te.assert_output(&["--fixed-strings", "a.foo"], "test1/a.foo");
 1294 
 1295     // Regex search, parens are treated as group
 1296     te.assert_output(&["download (1)"], "");
 1297 
 1298     // Literal search, parens are treated as characters
 1299     te.assert_output(
 1300         &["--fixed-strings", "download (1)"],
 1301         "test2/Download (1).tar.gz",
 1302     );
 1303 
 1304     // Combine with --case-sensitive
 1305     te.assert_output(&["--fixed-strings", "--case-sensitive", "download (1)"], "");
 1306 }
 1307 
 1308 /// Filenames with invalid UTF-8 sequences
 1309 #[cfg(target_os = "linux")]
 1310 #[test]
 1311 fn test_invalid_utf8() {
 1312     use std::ffi::OsStr;
 1313     use std::os::unix::ffi::OsStrExt;
 1314 
 1315     let dirs = &["test1"];
 1316     let files = &[];
 1317     let te = TestEnv::new(dirs, files);
 1318 
 1319     fs::File::create(
 1320         te.test_root()
 1321             .join(OsStr::from_bytes(b"test1/test_\xFEinvalid.txt")),
 1322     )
 1323     .unwrap();
 1324 
 1325     te.assert_output(&["", "test1/"], "test1/test_�invalid.txt");
 1326 
 1327     te.assert_output(&["invalid", "test1/"], "test1/test_�invalid.txt");
 1328 
 1329     // Should not be found under a different extension
 1330     te.assert_output(&["-e", "zip", "", "test1/"], "");
 1331 }
 1332 
 1333 /// Filtering for file size (--size)
 1334 #[test]
 1335 fn test_size() {
 1336     let te = TestEnv::new(&[], &[]);
 1337 
 1338     create_file_with_size(te.test_root().join("0_bytes.foo"), 0);
 1339     create_file_with_size(te.test_root().join("11_bytes.foo"), 11);
 1340     create_file_with_size(te.test_root().join("30_bytes.foo"), 30);
 1341     create_file_with_size(te.test_root().join("3_kilobytes.foo"), 3 * 1000);
 1342     create_file_with_size(te.test_root().join("4_kibibytes.foo"), 4 * 1024);
 1343 
 1344     // Zero and non-zero sized files.
 1345     te.assert_output(
 1346         &["", "--size", "+0B"],
 1347         "0_bytes.foo
 1348         11_bytes.foo
 1349         30_bytes.foo
 1350         3_kilobytes.foo
 1351         4_kibibytes.foo",
 1352     );
 1353 
 1354     // Zero sized files.
 1355     te.assert_output(&["", "--size", "-0B"], "0_bytes.foo");
 1356 
 1357     // Files with 2 bytes or more.
 1358     te.assert_output(
 1359         &["", "--size", "+2B"],
 1360         "11_bytes.foo
 1361         30_bytes.foo
 1362         3_kilobytes.foo
 1363         4_kibibytes.foo",
 1364     );
 1365 
 1366     // Files with 2 bytes or less.
 1367     te.assert_output(&["", "--size", "-2B"], "0_bytes.foo");
 1368 
 1369     // Files with size between 1 byte and 11 bytes.
 1370     te.assert_output(&["", "--size", "+1B", "--size", "-11B"], "11_bytes.foo");
 1371 
 1372     // Files with size between 1 byte and 30 bytes.
 1373     te.assert_output(
 1374         &["", "--size", "+1B", "--size", "-30B"],
 1375         "11_bytes.foo
 1376         30_bytes.foo",
 1377     );
 1378 
 1379     // Combine with a search pattern
 1380     te.assert_output(&["^11_", "--size", "+1B", "--size", "-30B"], "11_bytes.foo");
 1381 
 1382     // Files with size between 12 and 30 bytes.
 1383     te.assert_output(&["", "--size", "+12B", "--size", "-30B"], "30_bytes.foo");
 1384 
 1385     // Files with size between 31 and 100 bytes.
 1386     te.assert_output(&["", "--size", "+31B", "--size", "-100B"], "");
 1387 
 1388     // Files with size between 3 kibibytes and 5 kibibytes.
 1389     te.assert_output(&["", "--size", "+3ki", "--size", "-5ki"], "4_kibibytes.foo");
 1390 
 1391     // Files with size between 3 kilobytes and 5 kilobytes.
 1392     te.assert_output(
 1393         &["", "--size", "+3k", "--size", "-5k"],
 1394         "3_kilobytes.foo
 1395         4_kibibytes.foo",
 1396     );
 1397 
 1398     // Files with size greater than 3 kilobytes and less than 3 kibibytes.
 1399     te.assert_output(&["", "--size", "+3k", "--size", "-3ki"], "3_kilobytes.foo");
 1400 
 1401     // Files with size equal 4 kibibytes.
 1402     te.assert_output(&["", "--size", "+4ki", "--size", "-4ki"], "4_kibibytes.foo");
 1403 }
 1404 
 1405 #[cfg(test)]
 1406 fn create_file_with_modified<P: AsRef<Path>>(path: P, duration_in_secs: u64) {
 1407     let st = SystemTime::now() - Duration::from_secs(duration_in_secs);
 1408     let ft = filetime::FileTime::from_system_time(st);
 1409     fs::File::create(&path).expect("creation failed");
 1410     filetime::set_file_times(&path, ft, ft).expect("time modification failed");
 1411 }
 1412 
 1413 #[test]
 1414 fn test_modified_relative() {
 1415     let te = TestEnv::new(&[], &[]);
 1416     create_file_with_modified(te.test_root().join("foo_0_now"), 0);
 1417     create_file_with_modified(te.test_root().join("bar_1_min"), 60);
 1418     create_file_with_modified(te.test_root().join("foo_10_min"), 600);
 1419     create_file_with_modified(te.test_root().join("bar_1_h"), 60 * 60);
 1420     create_file_with_modified(te.test_root().join("foo_2_h"), 2 * 60 * 60);
 1421     create_file_with_modified(te.test_root().join("bar_1_day"), 24 * 60 * 60);
 1422 
 1423     te.assert_output(
 1424         &["", "--changed-within", "15min"],
 1425         "foo_0_now
 1426         bar_1_min
 1427         foo_10_min",
 1428     );
 1429 
 1430     te.assert_output(
 1431         &["", "--change-older-than", "15min"],
 1432         "bar_1_h
 1433         foo_2_h
 1434         bar_1_day",
 1435     );
 1436 
 1437     te.assert_output(
 1438         &["foo", "--changed-within", "12h"],
 1439         "foo_0_now
 1440         foo_10_min
 1441         foo_2_h",
 1442     );
 1443 }
 1444 
 1445 #[cfg(test)]
 1446 fn change_file_modified<P: AsRef<Path>>(path: P, iso_date: &str) {
 1447     let st = humantime::parse_rfc3339(iso_date).expect("invalid date");
 1448     let ft = filetime::FileTime::from_system_time(st);
 1449     filetime::set_file_times(path, ft, ft).expect("time modification failde");
 1450 }
 1451 
 1452 #[test]
 1453 fn test_modified_asolute() {
 1454     let te = TestEnv::new(&[], &["15mar2018", "30dec2017"]);
 1455     change_file_modified(te.test_root().join("15mar2018"), "2018-03-15T12:00:00Z");
 1456     change_file_modified(te.test_root().join("30dec2017"), "2017-12-30T23:59:00Z");
 1457 
 1458     te.assert_output(
 1459         &["", "--change-newer-than", "2018-01-01 00:00:00"],
 1460         "15mar2018",
 1461     );
 1462     te.assert_output(
 1463         &["", "--changed-before", "2018-01-01 00:00:00"],
 1464         "30dec2017",
 1465     );
 1466 }
 1467 
 1468 #[test]
 1469 fn test_custom_path_separator() {
 1470     let te = TestEnv::new(DEFAULT_DIRS, DEFAULT_FILES);
 1471 
 1472     te.assert_output(
 1473         &["foo", "one", "--path-separator", "="],
 1474         "one=b.foo
 1475         one=two=c.foo
 1476         one=two=C.Foo2
 1477         one=two=three=d.foo
 1478         one=two=three=directory_foo",
 1479     );
 1480 }
 1481 
 1482 #[test]
 1483 fn test_base_directory() {
 1484     let te = TestEnv::new(DEFAULT_DIRS, DEFAULT_FILES);
 1485 
 1486     te.assert_output(
 1487         &["--base-directory", "one"],
 1488         "b.foo
 1489         two
 1490         two/c.foo
 1491         two/C.Foo2
 1492         two/three
 1493         two/three/d.foo
 1494         two/three/directory_foo",
 1495     );
 1496 
 1497     te.assert_output(
 1498         &["--base-directory", "one/two", "foo"],
 1499         "c.foo
 1500         C.Foo2
 1501         three/d.foo
 1502         three/directory_foo",
 1503     );
 1504 
 1505     // Explicit root path
 1506     te.assert_output(
 1507         &["--base-directory", "one", "foo", "two"],
 1508         "two/c.foo
 1509         two/C.Foo2
 1510         two/three/d.foo
 1511         two/three/directory_foo",
 1512     );
 1513 
 1514     // Ignore base directory when absolute path is used
 1515     let (te, abs_path) = get_test_env_with_abs_path(DEFAULT_DIRS, DEFAULT_FILES);
 1516     let abs_base_dir = &format!("{abs_path}/one/two", abs_path = &abs_path);
 1517     te.assert_output(
 1518         &["--base-directory", &abs_base_dir, "foo", &abs_path],
 1519         &format!(
 1520             "{abs_path}/a.foo
 1521             {abs_path}/one/b.foo
 1522             {abs_path}/one/two/c.foo
 1523             {abs_path}/one/two/C.Foo2
 1524             {abs_path}/one/two/three/d.foo
 1525             {abs_path}/one/two/three/directory_foo",
 1526             abs_path = &abs_path
 1527         ),
 1528     );
 1529 }
 1530 
 1531 #[test]
 1532 fn test_max_results() {
 1533     let te = TestEnv::new(DEFAULT_DIRS, DEFAULT_FILES);
 1534 
 1535     // Unrestricted
 1536     te.assert_output(
 1537         &["--max-results=0", "c.foo"],
 1538         "one/two/C.Foo2
 1539          one/two/c.foo",
 1540     );
 1541 
 1542     // Limited to two results
 1543     te.assert_output(
 1544         &["--max-results=2", "c.foo"],
 1545         "one/two/C.Foo2
 1546          one/two/c.foo",
 1547     );
 1548 
 1549     // Limited to one result. We could find either C.Foo2 or c.foo
 1550     let assert_just_one_result_with_option = |option| {
 1551         let output = te.assert_success_and_get_output(".", &[option, "c.foo"]);
 1552         let stdout = String::from_utf8_lossy(&output.stdout)
 1553             .trim()
 1554             .replace(&std::path::MAIN_SEPARATOR.to_string(), "/");
 1555         assert!(stdout == "one/two/C.Foo2" || stdout == "one/two/c.foo");
 1556     };
 1557     assert_just_one_result_with_option("--max-results=1");
 1558     assert_just_one_result_with_option("-1");
 1559 }
 1560 
 1561 /// Filenames with non-utf8 paths are passed to the executed program unchanged
 1562 ///
 1563 /// Note:
 1564 /// - the test is disabled on Darwin/OSX, since it coerces file names to UTF-8,
 1565 ///   even when the requested file name is not valid UTF-8.
 1566 /// - the test is currently disabled on Windows because I'm not sure how to create
 1567 ///   invalid UTF-8 files on Windows
 1568 #[cfg(all(unix, not(target_os = "macos")))]
 1569 #[test]
 1570 fn test_exec_invalid_utf8() {
 1571     use std::ffi::OsStr;
 1572     use std::os::unix::ffi::OsStrExt;
 1573 
 1574     let dirs = &["test1"];
 1575     let files = &[];
 1576     let te = TestEnv::new(dirs, files);
 1577 
 1578     fs::File::create(
 1579         te.test_root()
 1580             .join(OsStr::from_bytes(b"test1/test_\xFEinvalid.txt")),
 1581     )
 1582     .unwrap();
 1583 
 1584     te.assert_output_raw(
 1585         &["", "test1/", "--exec", "echo", "{}"],
 1586         b"test1/test_\xFEinvalid.txt\n",
 1587     );
 1588 
 1589     te.assert_output_raw(
 1590         &["", "test1/", "--exec", "echo", "{/}"],
 1591         b"test_\xFEinvalid.txt\n",
 1592     );
 1593 
 1594     te.assert_output_raw(&["", "test1/", "--exec", "echo", "{//}"], b"test1\n");
 1595 
 1596     te.assert_output_raw(
 1597         &["", "test1/", "--exec", "echo", "{.}"],
 1598         b"test1/test_\xFEinvalid\n",
 1599     );
 1600 
 1601     te.assert_output_raw(
 1602         &["", "test1/", "--exec", "echo", "{/.}"],
 1603         b"test_\xFEinvalid\n",
 1604     );
 1605 }
 1606 
 1607 #[test]
 1608 fn test_list_details() {
 1609     let te = TestEnv::new(DEFAULT_DIRS, DEFAULT_FILES);
 1610 
 1611     // Make sure we can execute 'fd --list-details' without any errors.
 1612     te.assert_success_and_get_output(".", &["--list-details"]);
 1613 }