From 14fe8555b4c01a9bd6f5a997e2bae8141bedb231 Mon Sep 17 00:00:00 2001 From: Valentin Rothberg Date: Fri, 17 May 2024 12:40:11 +0200 Subject: [PATCH] handle *grep errors Try parsing the stdout of *grep even in case of an error. This allows to return output when hitting, for instance, eperms on certain files. The stderr of *grep is now logged directly while the exit code is being recorded and used by vgrep on exit. Fixes: #216 Signed-off-by: Valentin Rothberg --- test/errors.bats | 39 +++++++++++++++++++++++++++++++++++++++ test/helpers.bash | 2 ++ vgrep.go | 35 +++++++++++++++++++++++------------ 3 files changed, 64 insertions(+), 12 deletions(-) create mode 100644 test/errors.bats diff --git a/test/errors.bats b/test/errors.bats new file mode 100644 index 0000000..5aa35c1 --- /dev/null +++ b/test/errors.bats @@ -0,0 +1,39 @@ +#!/usr/bin/env bats -t + +load helpers + +# Create a write-only file which causes the *grep implementations to fail. In +# that case, vgrep should still parse the stdout of *grep and return the error +# reported on stdout along with the exit code. + +WONLY_FILE=test/search_files/wonly.txt + +function setup() { + touch $WONLY_FILE + chmod 200 $WONLY_FILE +} + +function teardown() { + rm $WONLY_FILE +} + +@test "Search with permission error" { + run_vgrep --no-header foo test/search_files + [ "$status" -eq 2 ] + [[ ${lines[0]} =~ "test/search_files/wonly.txt: Permission denied (os error 13)" ]] + [[ ${lines[1]} =~ "bar baz" ]] +} + +@test "Search with permission error (--no-ripgrep)" { + # Since the file isn't under version control, git will not try to read it + run_vgrep --no-header --no-ripgrep foo test/search_files + [ "$status" -eq 0 ] + [[ ${lines[0]} =~ "bar baz" ]] +} + +@test "Search with permission error (--no-git --no-ripgrep)" { + run_vgrep --no-header --no-git --no-ripgrep foo test/search_files + [ "$status" -eq 2 ] + [[ ${lines[0]} =~ "test/search_files/wonly.txt: Permission denied" ]] + [[ ${lines[1]} =~ "bar baz" ]] +} diff --git a/test/helpers.bash b/test/helpers.bash index d7ab3e3..cca900b 100644 --- a/test/helpers.bash +++ b/test/helpers.bash @@ -15,7 +15,9 @@ function run_vgrep() { fi run $VGREP $args "$@" if [ "$status" -ne 0 ]; then + echo "-------------" echo "CLI: $VGREP $args $*" echo "OUT: $output" + echo "-------------" fi } diff --git a/vgrep.go b/vgrep.go index b542651..eb5e7b8 100644 --- a/vgrep.go +++ b/vgrep.go @@ -54,10 +54,11 @@ type cliArgs struct { // vgrep stores state and the user-specified command-line arguments. type vgrep struct { cliArgs - matches [][]string - workDir string - lock lockfile.Lockfile - waiter sync.WaitGroup + exitCode int + matches [][]string + workDir string + lock lockfile.Lockfile + waiter sync.WaitGroup } // the type of underlying grep program @@ -169,6 +170,9 @@ func main() { } if len(v.matches) == 0 { + if v.exitCode != 0 { + os.Exit(v.exitCode) + } os.Exit(1) } @@ -178,7 +182,7 @@ func main() { v.commandPrintMatches([]int{}) } v.waiter.Wait() - os.Exit(0) + os.Exit(v.exitCode) } v.waiter.Add(1) @@ -187,13 +191,16 @@ func main() { if len(v.matches) == 0 { v.waiter.Wait() + if v.exitCode != 0 { + os.Exit(v.exitCode) + } os.Exit(1) } // Last resort, print all matches. v.commandPrintMatches([]int{}) v.waiter.Wait() - os.Exit(0) + os.Exit(v.exitCode) } // runCommand executes the program specified in args and returns the stdout as @@ -212,13 +219,17 @@ func (v *vgrep) runCommand(args []string, env string) ([]string, error) { err := cmd.Run() if err != nil { logrus.Debugf("error running command: %v", err) - errStr := err.Error() - if errStr == "exit status 1" { - logrus.Debug("ignoring error (no matches found)") + + if exitError, ok := err.(*exec.ExitError); ok { + exitCode := exitError.ExitCode() + switch exitCode { + case 1: + logrus.Debug("ignoring error (no matches found)") + default: + logrus.Errorf("%s", strings.TrimSuffix(serr.String(), "\n")) + v.exitCode = exitCode + } err = nil - } else { - spl := strings.Split(serr.String(), "\n") - err = fmt.Errorf("%s [%s]", spl[0], args[0]) } }