Repository: reubeno/brush
Pull request #317 (2025-01-10 22:50:48)

fix: correct issues with `!` extglobs and compgen -X
Fixes multiple issues with extglob pattern matching support, particularly with `!(...)` patterns. Additional changes for use of patterns as filter expressions in completion (notably, fixes to inversion).

### Updates to pattern parsing:

* [`brush-parser/src/pattern.rs`](diffhunk://#diff-6d3d15c993f8181f7e8182d68c72fca77a48114f71a359773d73da6745bff401L95-R107): Refactored the `extended_glob_body` rule to simplify the parsing of extended glob patterns and adjusted how negative lookaheads are handled in the regex generation. [[1]](diffhunk://#diff-6d3d15c993f8181f7e8182d68c72fca77a48114f71a359773d73da6745bff401L95-R107) [[2]](diffhunk://#diff-6d3d15c993f8181f7e8182d68c72fca77a48114f71a359773d73da6745bff401L111-L113) [[3]](diffhunk://#diff-6d3d15c993f8181f7e8182d68c72fca77a48114f71a359773d73da6745bff401L127-R135)

### Addition of test cases:

* [`brush-core/src/patterns.rs`](diffhunk://#diff-587f52b950161b074c8f0d6fdfc109f08e3219129404be142ff7044a80e054e7R645-R814): Added extensive test cases to verify the behavior of pattern matching with and without extended globbing, covering various scenarios including basic, advanced, and degenerate cases.
* [`brush-shell/tests/cases/builtins/compgen.yaml`](diffhunk://#diff-ac8a33134165ee0507555d187652a2598ed84d27c738103ea229aca7ac3918b0R140-R165): Added new test cases for the `compgen` command with extended globbing enabled.
* [`brush-shell/tests/cases/patterns.yaml`](diffhunk://#diff-ef3eac8b325b892c963ee64fa69882c13500bca63f5dd27ef9fa995b45a804d6L214-R226): Added new test cases for pathname expansion with extended globbing, including degenerate inverted patterns. [[1]](diffhunk://#diff-ef3eac8b325b892c963ee64fa69882c13500bca63f5dd27ef9fa995b45a804d6L214-R226) [[2]](diffhunk://#diff-ef3eac8b325b892c963ee64fa69882c13500bca63f5dd27ef9fa995b45a804d6L223-R237)

### Other changes:

* [`brush-shell/tests/completion_tests.rs`](diffhunk://#diff-858dab8c31079d18f10be31944b5aac5a7869a059bb68a16593fa12f0aac465eL327-R327): Adjusted the expected results in the `complete_path_args_to_well_known_programs` test to reflect the updated completion logic.

github-actions[bot]: ## Test Results
    2 files      9 suites   1m 17s ⏱️
  585 tests   585 ✅ 0 💤 0 ❌
1 156 runs  1 156 ✅ 0 💤 0 ❌

Results for commit e573b850.

[test-results]:data:application/gzip;base64,H4sIALSmgWcC/13MSw7CIBSF4a00jB0AzQV0M4bHJSG2xfAYGfcuJa22Dr//JOdFfJgwk9vALwPJNZSOa4OrSZcQl0Ypm9tS1g0U7Lrnau1/eoRnS/QbvA7TKWBKMW0l1WX9ZAzEpv3zlH6f3YfL7uOjjfMcSgNBkKNRQB1nBhGd0JJrKQC9cqMfBdXGWwuKvD9bFElRBgEAAA==

github-actions[bot]: # Performance Benchmark Report
| Benchmark name                         | Baseline (μs) |  Test/PR (μs) |    Delta (μs) | Delta %         |
| -------------------------------------- | ------------- | ------------- | ------------- | --------------- |
| `clone_shell_object                  ` | `   16.76 μs` | `   16.40 μs` | `   -0.36 μs` | `🟢 -2.16%    ` |
| `eval_arithmetic                     ` | `    0.18 μs` | `    0.19 μs` | `    0.00 μs` | `⚪  Unchanged` |
| `expand_one_string                   ` | `    1.71 μs` | `    1.61 μs` | `   -0.09 μs` | `⚪  Unchanged` |
| `for_loop                            ` | `   33.02 μs` | `   32.32 μs` | `   -0.70 μs` | `🟢 -2.12%    ` |
| `function_call                       ` | `    3.35 μs` | `    3.27 μs` | `   -0.08 μs` | `⚪  Unchanged` |
| `instantiate_shell                   ` | `   47.53 μs` | `   48.41 μs` | `    0.89 μs` | `⚪  Unchanged` |
| `instantiate_shell_with_init_scripts ` | `22405.01 μs` | `22252.38 μs` | ` -152.63 μs` | `⚪  Unchanged` |
| `parse_bash_completion               ` | ` 1686.54 μs` | ` 1681.00 μs` | `   -5.54 μs` | `⚪  Unchanged` |
| `parse_sample_script                 ` | `    2.22 μs` | `    2.17 μs` | `   -0.05 μs` | `🟢 -2.34%    ` |
| `run_echo_builtin_command            ` | `   16.20 μs` | `   16.03 μs` | `   -0.16 μs` | `⚪  Unchanged` |
| `run_one_external_command            ` | ` 1982.57 μs` | ` 2048.34 μs` | `   65.77 μs` | `🟠 +3.32%    ` |

# Code Coverage Report: Only Changed Files listed

| Package | Base Coverage | New Coverage | Difference |
| ------- | ------------- | ------------ | ---------- |
| brush-core/src/builtins/complete.rs | 🟠 75% | 🟢 75.58% | 🟢 0.58% |
| brush-core/src/completion.rs | 🟠 63.1% | 🟠 63.18% | 🟢 0.08% |
| brush-core/src/jobs.rs | 🔴 43.1% | 🔴 37.07% | 🔴 -6.03% |
| brush-core/src/patterns.rs | 🟢 97.95% | 🟢 98.46% | 🟢 0.51% |
| brush-core/src/shell.rs | 🟢 82.27% | 🟢 82.4% | 🟢 0.13% |
| **Overall Coverage** | **🟢 76.83%** | **🟢 76.96%** | **🟢 0.13%** |

_Minimum allowed coverage is_ `70%`_, this run produced_ `76.96%`
<!-- Sticky Pull Request Comment -->

Patch:
diff --git a/brush-core/src/builtins/complete.rs b/brush-core/src/builtins/complete.rs
index a38169b..1329dcd 100644
--- a/brush-core/src/builtins/complete.rs
+++ b/brush-core/src/builtins/complete.rs
@@ -94,12 +94,19 @@ pub(crate) struct CommonCompleteCommandArgs {
 }
 
 impl CommonCompleteCommandArgs {
-    fn create_spec(&self) -> completion::Spec {
+    fn create_spec(&self, extglob_enabled: bool) -> completion::Spec {
         let filter_pattern_excludes;
         let filter_pattern = if let Some(filter_pattern) = self.filter_pattern.as_ref() {
-            if let Some(filter_pattern) = filter_pattern.strip_prefix('!') {
-                filter_pattern_excludes = false;
-                Some(filter_pattern.to_owned())
+            // If the pattern starts with a '!' that's not the start of an extglob pattern,
+            // then we invert.
+            if let Some(remaining_pattern) = filter_pattern.strip_prefix('!') {
+                if !extglob_enabled || !remaining_pattern.starts_with('(') {
+                    filter_pattern_excludes = false;
+                    Some(remaining_pattern.to_owned())
+                } else {
+                    filter_pattern_excludes = true;
+                    Some(filter_pattern.to_owned())
+                }
             } else {
                 filter_pattern_excludes = true;
                 Some(filter_pattern.clone())
@@ -280,7 +287,10 @@ impl CompleteCommand {
             }
         } else {
             if let Some(target_spec) = target_spec {
-                let mut new_spec = Some(self.common_args.create_spec());
+                let mut new_spec = Some(
+                    self.common_args
+                        .create_spec(context.shell.options.extended_globbing),
+                );
                 std::mem::swap(&mut new_spec, target_spec);
             } else {
                 return error::unimp("set unspecified spec");
@@ -443,7 +453,9 @@ impl CompleteCommand {
             return Ok(true);
         }
 
-        let config = self.common_args.create_spec();
+        let config = self
+            .common_args
+            .create_spec(context.shell.options.extended_globbing);
 
         context.shell.completion_config.set(name, config);
 
@@ -466,7 +478,9 @@ impl builtins::Command for CompGenCommand {
         &self,
         context: commands::ExecutionContext<'_>,
     ) -> Result<crate::builtins::ExitCode, crate::error::Error> {
-        let mut spec = self.common_args.create_spec();
+        let mut spec = self
+            .common_args
+            .create_spec(context.shell.options.extended_globbing);
         spec.options.no_sort = true;
 
         let token_to_complete = self.word.as_deref().unwrap_or_default();
diff --git a/brush-core/src/completion.rs b/brush-core/src/completion.rs
index 1b497dd..dbcfbd4 100644
--- a/brush-core/src/completion.rs
+++ b/brush-core/src/completion.rs
@@ -291,12 +291,14 @@ impl Spec {
                 let mut updated = IndexSet::new();
 
                 for candidate in candidates {
-                    if !completion_filter_pattern_matches(
+                    let matches = completion_filter_pattern_matches(
                         filter_pattern.as_str(),
                         candidate.as_str(),
                         context.token_to_complete,
                         shell,
-                    )? {
+                    )?;
+
+                    if self.filter_pattern_excludes != matches {
                         updated.insert(candidate);
                     }
                 }
@@ -1184,15 +1186,6 @@ fn completion_filter_pattern_matches(
     token_being_completed: &str,
     shell: &mut Shell,
 ) -> Result<bool, error::Error> {
-    let mut pattern = pattern;
-
-    let invert = if let Some(remaining_pattern) = pattern.strip_prefix('!') {
-        pattern = remaining_pattern;
-        true
-    } else {
-        false
-    };
-
     let pattern = replace_unescaped_ampersands(pattern, token_being_completed);
 
     //
@@ -1205,7 +1198,7 @@ fn completion_filter_pattern_matches(
 
     let matches = pattern.exactly_matches(candidate)?;
 
-    Ok(if invert { !matches } else { matches })
+    Ok(matches)
 }
 
 fn replace_unescaped_ampersands<'a>(pattern: &'a str, replacement: &str) -> Cow<'a, str> {
diff --git a/brush-core/src/patterns.rs b/brush-core/src/patterns.rs
index 5c1a9de..9ecbeff 100644
--- a/brush-core/src/patterns.rs
+++ b/brush-core/src/patterns.rs
@@ -642,4 +642,174 @@ mod tests {
         );
         Ok(())
     }
+
+    #[test]
+    fn test_matching() -> Result<()> {
+        assert!(Pattern::from("abc").exactly_matches("abc")?);
+
+        assert!(!Pattern::from("abc").exactly_matches("ABC")?);
+        assert!(!Pattern::from("abc").exactly_matches("xabcx")?);
+        assert!(!Pattern::from("abc").exactly_matches("")?);
+        assert!(!Pattern::from("abc").exactly_matches("abcd")?);
+        assert!(!Pattern::from("abc").exactly_matches("def")?);
+
+        assert!(Pattern::from("*").exactly_matches("")?);
+        assert!(Pattern::from("*").exactly_matches("abc")?);
+        assert!(Pattern::from("*").exactly_matches(" ")?);
+
+        assert!(Pattern::from("a*").exactly_matches("a")?);
+        assert!(Pattern::from("a*").exactly_matches("ab")?);
+        assert!(Pattern::from("a*").exactly_matches("a ")?);
+
+        assert!(!Pattern::from("a*").exactly_matches("A")?);
+        assert!(!Pattern::from("a*").exactly_matches("")?);
+        assert!(!Pattern::from("a*").exactly_matches("bc")?);
+        assert!(!Pattern::from("a*").exactly_matches("xax")?);
+        assert!(!Pattern::from("a*").exactly_matches(" a")?);
+
+        assert!(Pattern::from("*a").exactly_matches("a")?);
+        assert!(Pattern::from("*a").exactly_matches("ba")?);
+        assert!(Pattern::from("*a").exactly_matches("aa")?);
+        assert!(Pattern::from("*a").exactly_matches(" a")?);
+
+        assert!(!Pattern::from("*a").exactly_matches("BA")?);
+        assert!(!Pattern::from("*a").exactly_matches("")?);
+        assert!(!Pattern::from("*a").exactly_matches("ab")?);
+        assert!(!Pattern::from("*a").exactly_matches("xax")?);
+
+        Ok(())
+    }
+
+    fn make_extglob(s: &str) -> Pattern {
+        let pattern = Pattern::from(s).set_extended_globbing(true);
+        let regex_str = pattern.to_regex_str(true, true).unwrap();
+        eprintln!("pattern: '{s}' => regex: '{regex_str}'");
+
+        pattern
+    }
+
+    #[test]
+    fn test_extglob_or_matching() -> Result<()> {
+        assert!(make_extglob("@(a|b)").exactly_matches("a")?);
+        assert!(make_extglob("@(a|b)").exactly_matches("b")?);
+
+        assert!(!make_extglob("@(a|b)").exactly_matches("")?);
+        assert!(!make_extglob("@(a|b)").exactly_matches("c")?);
+        assert!(!make_extglob("@(a|b)").exactly_matches("ab")?);
+
+        assert!(!make_extglob("@(a|b)").exactly_matches("")?);
+        assert!(make_extglob("@(a*b|b)").exactly_matches("ab")?);
+        assert!(make_extglob("@(a*b|b)").exactly_matches("axb")?);
+        assert!(make_extglob("@(a*b|b)").exactly_matches("b")?);
+
+        assert!(!make_extglob("@(a*b|b)").exactly_matches("a")?);
+
+        Ok(())
+    }
+
+    #[test]
+    fn test_extglob_not_matching() -> Result<()> {
+        // Basic cases.
+        assert!(make_extglob("!(a)").exactly_matches("")?);
+        assert!(make_extglob("!(a)").exactly_matches(" ")?);
+        assert!(make_extglob("!(a)").exactly_matches("x")?);
+        assert!(make_extglob("!(a)").exactly_matches(" a ")?);
+        assert!(make_extglob("!(a)").exactly_matches("a ")?);
+        assert!(make_extglob("!(a)").exactly_matches("aa")?);
+        assert!(!make_extglob("!(a)").exactly_matches("a")?);
+
+        assert!(make_extglob("a!(a)a").exactly_matches("aa")?);
+        assert!(make_extglob("a!(a)a").exactly_matches("aaaa")?);
+        assert!(make_extglob("a!(a)a").exactly_matches("aba")?);
+        assert!(!make_extglob("a!(a)a").exactly_matches("a")?);
+        assert!(!make_extglob("a!(a)a").exactly_matches("aaa")?);
+        assert!(!make_extglob("a!(a)a").exactly_matches("baaa")?);
+
+        // Alternates.
+        assert!(make_extglob("!(a|b)").exactly_matches("c")?);
+        assert!(make_extglob("!(a|b)").exactly_matches("ab")?);
+        assert!(make_extglob("!(a|b)").exactly_matches("aa")?);
+        assert!(make_extglob("!(a|b)").exactly_matches("bb")?);
+        assert!(!make_extglob("!(a|b)").exactly_matches("a")?);
+        assert!(!make_extglob("!(a|b)").exactly_matches("b")?);
+
+        Ok(())
+    }
+
+    #[test]
+    fn test_extglob_advanced_not_matching() -> Result<()> {
+        assert!(make_extglob("!(a*)").exactly_matches("b")?);
+        assert!(make_extglob("!(a*)").exactly_matches("")?);
+        assert!(!make_extglob("!(a*)").exactly_matches("a")?);
+        assert!(!make_extglob("!(a*)").exactly_matches("abc")?);
+        assert!(!make_extglob("!(a*)").exactly_matches("aabc")?);
+
+        Ok(())
+    }
+
+    #[test]
+    fn test_extglob_not_degenerate_matching() -> Result<()> {
+        // Degenerate case.
+        assert!(make_extglob("!()").exactly_matches("a")?);
+        assert!(!make_extglob("!()").exactly_matches("")?);
+
+        Ok(())
+    }
+
+    #[test]
+    fn test_extglob_zero_or_more_matching() -> Result<()> {
+        assert!(make_extglob("x*(a)x").exactly_matches("xx")?);
+        assert!(make_extglob("x*(a)x").exactly_matches("xax")?);
+        assert!(make_extglob("x*(a)x").exactly_matches("xaax")?);
+
+        assert!(!make_extglob("x*(a)x").exactly_matches("x")?);
+        assert!(!make_extglob("x*(a)x").exactly_matches("xa")?);
+        assert!(!make_extglob("x*(a)x").exactly_matches("xxx")?);
+
+        assert!(make_extglob("*(a|b)").exactly_matches("")?);
+        assert!(make_extglob("*(a|b)").exactly_matches("a")?);
+        assert!(make_extglob("*(a|b)").exactly_matches("b")?);
+        assert!(make_extglob("*(a|b)").exactly_matches("aba")?);
+        assert!(make_extglob("*(a|b)").exactly_matches("aaa")?);
+
+        assert!(!make_extglob("*(a|b)").exactly_matches("c")?);
+        assert!(!make_extglob("*(a|b)").exactly_matches("ca")?);
+
+        Ok(())
+    }
+
+    #[test]
+    fn test_extglob_one_or_more_matching() -> Result<()> {
+        fn make_extglob(s: &str) -> Pattern {
+            Pattern::from(s).set_extended_globbing(true)
+        }
+
+        assert!(make_extglob("x+(a)x").exactly_matches("xax")?);
+        assert!(make_extglob("x+(a)x").exactly_matches("xaax")?);
+
+        assert!(!make_extglob("x+(a)x").exactly_matches("xx")?);
+        assert!(!make_extglob("x+(a)x").exactly_matches("x")?);
+        assert!(!make_extglob("x+(a)x").exactly_matches("xa")?);
+        assert!(!make_extglob("x+(a)x").exactly_matches("xxx")?);
+
+        assert!(make_extglob("+(a|b)").exactly_matches("a")?);
+        assert!(make_extglob("+(a|b)").exactly_matches("b")?);
+        assert!(make_extglob("+(a|b)").exactly_matches("aba")?);
+        assert!(make_extglob("+(a|b)").exactly_matches("aaa")?);
+
+        assert!(!make_extglob("+(a|b)").exactly_matches("")?);
+        assert!(!make_extglob("+(a|b)").exactly_matches("c")?);
+        assert!(!make_extglob("+(a|b)").exactly_matches("ca")?);
+
+        assert!(make_extglob("+(x+(ab)y)").exactly_matches("xaby")?);
+        assert!(make_extglob("+(x+(ab)y)").exactly_matches("xababy")?);
+        assert!(make_extglob("+(x+(ab)y)").exactly_matches("xabababy")?);
+        assert!(make_extglob("+(x+(ab)y)").exactly_matches("xabababyxabababyxabababy")?);
+
+        assert!(!make_extglob("+(x+(ab)y)").exactly_matches("xy")?);
+        assert!(!make_extglob("+(x+(ab)y)").exactly_matches("xay")?);
+        assert!(!make_extglob("+(x+(ab)y)").exactly_matches("xyxy")?);
+
+        Ok(())
+    }
 }
diff --git a/brush-parser/src/pattern.rs b/brush-parser/src/pattern.rs
index cfce493..5ec4287 100644
--- a/brush-parser/src/pattern.rs
+++ b/brush-parser/src/pattern.rs
@@ -92,25 +92,28 @@ peg::parser! {
             kind:extended_glob_prefix() "(" branches:extended_glob_body() ")" {
                 let mut s = String::new();
 
-                s.push('(');
-
                 // fancy_regex uses ?! to indicate a negative lookahead.
                 if matches!(kind, ExtendedGlobKind::Exclamation) {
-                    s.push_str("(?!");
-                }
-
-                s.push_str(&branches.join("|"));
-                s.push(')');
-
-                match kind {
-                    ExtendedGlobKind::Plus => s.push('+'),
-                    ExtendedGlobKind::Question => s.push('?'),
-                    ExtendedGlobKind::Star => s.push('*'),
-                    ExtendedGlobKind::At | ExtendedGlobKind::Exclamation => (),
-                }
-
-                if matches!(kind, ExtendedGlobKind::Exclamation) {
-                    s.push_str(".)*?");
+                    if !branches.is_empty() {
+                        s.push_str("(?:(?!");
+                        s.push_str(&branches.join("|"));
+                        s.push_str(").*|(?>");
+                        s.push_str(&branches.join("|"));
+                        s.push_str(").+?|)");
+                    } else {
+                        s.push_str("(?:.+)")
+                    }
+                } else {
+                    s.push('(');
+                    s.push_str(&branches.join("|"));
+                    s.push(')');
+
+                    match kind {
+                        ExtendedGlobKind::Plus => s.push('+'),
+                        ExtendedGlobKind::Question => s.push('?'),
+                        ExtendedGlobKind::Star => s.push('*'),
+                        ExtendedGlobKind::At | ExtendedGlobKind::Exclamation => (),
+                    }
                 }
 
                 s
@@ -124,14 +127,12 @@ peg::parser! {
             "*" { ExtendedGlobKind::Star }
 
         pub(crate) rule extended_glob_body() -> Vec<String> =
-            first_branches:((b:extended_glob_branch() "|" { b })*) last_branch:extended_glob_branch() {
-                let mut branches = first_branches;
-                branches.push(last_branch);
-                branches
-            }
+            extended_glob_branch() ** "|"
 
         rule extended_glob_branch() -> String =
-            pieces:(!['|' | ')'] piece:pattern_piece() { piece })* { pieces.join("") }
+            pieces:(!['|' | ')'] piece:pattern_piece() { piece })+ {
+                pieces.join("")
+            }
     }
 }
 
@@ -185,6 +186,11 @@ mod tests {
             "(ab|ac)*"
         );
 
+        assert_eq!(
+            pattern_to_regex_translator::extended_glob_body("", true)?,
+            Vec::<String>::new(),
+        );
+
         Ok(())
     }
 }


Test patch:
diff --git a/brush-shell/tests/cases/builtins/compgen.yaml b/brush-shell/tests/cases/builtins/compgen.yaml
index 0fe1f32..a838a8d 100644
--- a/brush-shell/tests/cases/builtins/compgen.yaml
+++ b/brush-shell/tests/cases/builtins/compgen.yaml
@@ -137,6 +137,32 @@ cases:
       echo "[Take 1]"
       compgen -W 'somebody something' -X '&b*' some
 
+  - name: "compgen -X with extglob"
+    stdin: |
+      touch README
+      shopt -s extglob
+
+      echo "[Take 1]"
+      compgen -f READ
+
+      echo "[Take 2]"
+      compgen -f -X "READ" READ
+
+      echo "[Take 3]"
+      compgen -f -X "README" READ
+
+      echo "[Take 4]"
+      compgen -f -X "!(READ)" READ
+
+      echo "[Take 5]"
+      compgen -f -X "!(README)" READ
+
+      echo "[Take 6]"
+      compgen -f -X "!!(READ)" READ
+
+      echo "[Take 7]"
+      compgen -f -X "!!(README)" READ
+
   - name: "compgen -o dirnames"
     stdin: |
       echo "[Take 1]"
diff --git a/brush-shell/tests/cases/patterns.yaml b/brush-shell/tests/cases/patterns.yaml
index b960680..afe7a88 100644
--- a/brush-shell/tests/cases/patterns.yaml
+++ b/brush-shell/tests/cases/patterns.yaml
@@ -211,7 +211,19 @@ cases:
       - path: "def.txt"
     stdin: |
       shopt -s extglob
-      echo !(a*)
+      echo "1: " !(a*)
+      echo "2: " !(abc.txt)
+      echo "3: " !(abc)
+      echo "4: " !(*)
+
+  - name: "Pathname expansion: Degenerate inverted pattern"
+    test_files:
+      - path: "abc.txt"
+      - path: "abd.txt"
+      - path: "def.txt"
+    stdin: |
+      shopt -s extglob
+      echo !()
 
   - name: "Pathname expansion: Extended patterns"
     ignore_stderr: true
@@ -220,7 +232,9 @@ cases:
       - path: "abd.txt"
     stdin: |
       shopt -s extglob
-      echo @(abc|abd).txt
+      echo "1: " @(abc|abd).txt
+      echo "2: " @(abc.txt)
+      echo "3: " @(abc)
 
   - name: "Pathname expansion: Optional patterns"
     ignore_stderr: true
diff --git a/brush-shell/tests/completion_tests.rs b/brush-shell/tests/completion_tests.rs
index c00d9c7..b7b6e39 100644
--- a/brush-shell/tests/completion_tests.rs
+++ b/brush-shell/tests/completion_tests.rs
@@ -324,7 +324,7 @@ async fn complete_path_args_to_well_known_programs() -> Result<()> {
     // Complete.
     let results = test_shell.complete_end_of_line("tar tvf ./item").await?;
 
-    assert_eq!(results, ["./item1", "./item2"]);
+    assert_eq!(results, ["./item2"]);
 
     Ok(())
 }