interal/derrors, internal/worker: update error handling

Updates error handling to more accurately categorize the errors that
may happen when scanning with govulncheck.
Specifically, we now accurately catch instances where there is no go.mod
file, as well as isolate a common os/file error.

Change-Id: I06483d7a15c339653611f20cf288768f943cdf3f
Reviewed-on: https://go-review.googlesource.com/c/pkgsite-metrics/+/479976
Run-TryBot: Maceo Thompson <maceothompson@google.com>
TryBot-Result: Gopher Robot <gobot@golang.org>
Reviewed-by: Jonathan Amsterdam <jba@google.com>
diff --git a/internal/derrors/derrors.go b/internal/derrors/derrors.go
index f5bdaeb..eea4d74 100644
--- a/internal/derrors/derrors.go
+++ b/internal/derrors/derrors.go
@@ -94,6 +94,9 @@
 
 	// ScanModuleMemoryLimitExceeded occurs when scanning uses too much memory.
 	ScanModuleMemoryLimitExceeded = errors.New("scan module memory limit exceeded")
+
+	// ScanModuleTooManyOpenFiles occurs when there are too many files open while scanning.
+	ScanModuleTooManyOpenFiles = errors.New("scan module too many open files")
 )
 
 // Wrap adds context to the error and allows
@@ -195,6 +198,8 @@
 		return "PANIC"
 	case errors.Is(err, ScanModuleMemoryLimitExceeded):
 		return "MEM LIMIT EXCEEDED"
+	case errors.Is(err, ScanModuleTooManyOpenFiles):
+		return "TOO MANY OPEN FILES"
 	case errors.Is(err, ProxyError):
 		return "PROXY"
 	case errors.Is(err, BigQueryError):
diff --git a/internal/worker/govulncheck_scan.go b/internal/worker/govulncheck_scan.go
index e310871..f975eac 100644
--- a/internal/worker/govulncheck_scan.go
+++ b/internal/worker/govulncheck_scan.go
@@ -212,11 +212,13 @@
 		case errors.Is(err, derrors.LoadPackagesNoGoModError) ||
 			errors.Is(err, derrors.LoadPackagesNoGoSumError):
 			// errors already classified by package loading.
-		case isMissingGoMod(err):
-			// specific for govulncheck
+		case isMissingGoMod(err) || isNoModulesSpecified(err):
+			// Covers the missing go.mod file cases when running govulncheck in the sandbox
 			err = fmt.Errorf("%v: %w", err, derrors.LoadPackagesNoGoModError)
 		case isNoRequiredModule(err):
 			err = fmt.Errorf("%v: %w", err, derrors.LoadPackagesNoRequiredModuleError)
+		case isTooManyFiles(err):
+			err = fmt.Errorf("%v: %w", err, derrors.ScanModuleTooManyOpenFiles)
 		case isMissingGoSumEntry(err):
 			err = fmt.Errorf("%v: %w", err, derrors.LoadPackagesMissingGoSumEntryError)
 		case errors.Is(err, derrors.LoadPackagesError):
@@ -437,6 +439,7 @@
 	output, err := govulncheckCmd.CombinedOutput()
 	if err != nil {
 		if e := (&exec.ExitError{}); !errors.As(err, &e) || e.ProcessState.ExitCode() != 3 {
+			// TODO: error processing here
 			return nil, fmt.Errorf("govulncheck error: err=%v out=%s", err, output)
 		}
 	}
@@ -446,10 +449,19 @@
 	res, err := govulncheck.UnmarshalGovulncheckResult(output)
 	if err != nil {
 		return nil, err
+		//TODO:
 	}
 	return res.Vulns, nil
 }
 
+func isNoModulesSpecified(err error) bool {
+	return strings.Contains(err.Error(), "no modules specified")
+}
+
+func isTooManyFiles(err error) bool {
+	return strings.Contains(err.Error(), "too many open files")
+}
+
 func isNoRequiredModule(err error) bool {
 	return strings.Contains(err.Error(), "no required module")
 }