gopackagesdriver: ignore bazel analysis errors (#4567)

**What type of PR is this?**

> Bug fix

**What does this PR do? Why is it needed?**

Bazel exits with code 7 (ANALYSIS_FAILURE) when querying a file that's
not mentioned in BUILD. gopackagesdriver can returns partial results for
code 1 (BUILD_FAILURE), but not 7.

gopackagesdriver often hits this for a new file not added to BUILD. A
file= query may still fail in this case because the query will match no
labels, but at least that's no longer a bazel error.

**Which issues(s) does this PR fix?**

Fixes #4565

**Other notes for review**
diff --git a/go/tools/gopackagesdriver/bazel.go b/go/tools/gopackagesdriver/bazel.go
index 323ac15..9ef3254 100644
--- a/go/tools/gopackagesdriver/bazel.go
+++ b/go/tools/gopackagesdriver/bazel.go
@@ -133,14 +133,8 @@
 		"--build_event_json_file=" + jsonFile.Name(),
 		"--build_event_json_file_path_conversion=no",
 	}, args...)
-	if _, err := b.run(ctx, "build", args...); err != nil {
-		// Ignore a regular build failure to get partial data.
-		// See https://docs.bazel.build/versions/main/guide.html#what-exit-code-will-i-get on
-		// exit codes.
-		var exerr *exec.ExitError
-		if !errors.As(err, &exerr) || exerr.ExitCode() != 1 {
-			return nil, fmt.Errorf("bazel build failed: %w", err)
-		}
+	if _, err := b.run(ctx, "build", args...); err != nil && !isAllowedBazelError(err) {
+		return nil, fmt.Errorf("bazel build failed: %w", err)
 	}
 
 	files := make([]string, 0)
@@ -169,7 +163,7 @@
 
 func (b *Bazel) Query(ctx context.Context, args ...string) ([]string, error) {
 	output, err := b.run(ctx, "query", args...)
-	if err != nil {
+	if err != nil && !isAllowedBazelError(err) {
 		return nil, fmt.Errorf("bazel query failed: %w", err)
 	}
 
@@ -202,6 +196,26 @@
 	return b.info["output_base"]
 }
 
+// isAllowedBazelError if err is an *exec.ExitError with an expected exit code
+// for a bazel command. gopackagesdriver is often invoked when the user
+// is editing code, and the build isn't expected to work. When we get one of
+// these codes, we should suppress the error and return partial results.
+func isAllowedBazelError(err error) bool {
+	var exitErr *exec.ExitError
+	if !errors.As(err, &exitErr) {
+		return false
+	}
+	switch exitErr.ExitCode() {
+	// Refer to https://github.com/bazelbuild/bazel/blob/master/src/main/java/com/google/devtools/build/lib/util/ExitCode.java.
+	case 1, // BUILD_FAILURE
+		3, // PARTIAL_ANALYSIS_FAILURE
+		7: // ANALYSIS_FAILURE
+		return true
+	default:
+		return false
+	}
+}
+
 type bazelVersion [3]int
 
 func parseBazelVersion(raw string) (bazelVersion, bool) {
diff --git a/go/tools/gopackagesdriver/gopackagesdriver_test.go b/go/tools/gopackagesdriver/gopackagesdriver_test.go
index 44094ac..06798b9 100644
--- a/go/tools/gopackagesdriver/gopackagesdriver_test.go
+++ b/go/tools/gopackagesdriver/gopackagesdriver_test.go
@@ -109,6 +109,11 @@
 func main() {
 	fmt.Fprintln(os.Stderr, "Subdirectory Hello World!")
 }
+
+-- unattached.go --
+package unattached
+
+// not mentioned in any target
 `,
 	})
 }
@@ -141,9 +146,9 @@
 		}
 
 		wantCompiledGoFiles := map[string]struct{}{
-			"hello.go": {},
-			"_cgo_gotypes.go": {},
-			"_cgo_imports.go": {},
+			"hello.go":         {},
+			"_cgo_gotypes.go":  {},
+			"_cgo_imports.go":  {},
 			"hellocgo.cgo1.go": {},
 		}
 		for _, file := range pkg.CompiledGoFiles {
@@ -159,7 +164,7 @@
 		}
 
 		wantGoFiles := map[string]struct{}{
-			"hello.go": {},
+			"hello.go":    {},
 			"hellocgo.go": {},
 		}
 		for _, file := range pkg.GoFiles {
@@ -376,7 +381,25 @@
 	}
 }
 
-func runForTest(t *testing.T, driverRequest packages.DriverRequest, relativeWorkingDir string, args ...string) packages.DriverResponse {
+func TestUnattached(t *testing.T) {
+	runForTestExpectError(t, "found no labels matching the requests", packages.DriverRequest{}, ".", "file=unattached.go")
+}
+
+func runForTest(
+	t *testing.T,
+	driverRequest packages.DriverRequest,
+	relativeWorkingDir string,
+	args ...string) packages.DriverResponse {
+	t.Helper()
+	return runForTestExpectError(t, "", driverRequest, relativeWorkingDir, args...)
+}
+
+func runForTestExpectError(
+	t *testing.T,
+	wantError string,
+	driverRequest packages.DriverRequest,
+	relativeWorkingDir string,
+	args ...string) packages.DriverResponse {
 	t.Helper()
 
 	// Remove most environment variables, other than those on an allowlist.
@@ -441,8 +464,17 @@
 	}
 	in := bytes.NewReader(driverRequestJson)
 	out := &bytes.Buffer{}
-	if err := run(context.Background(), in, out, args); err != nil {
-		t.Fatalf("running gopackagesdriver: %v", err)
+	err = run(context.Background(), in, out, args)
+	if err == nil && wantError != "" {
+		t.Fatal("unexpected success")
+	} else if err != nil {
+		errMsg := err.Error()
+		if wantError == "" {
+			t.Fatalf("running gopackagesdriver: %s", errMsg)
+		} else if !strings.Contains(errMsg, wantError) {
+			t.Fatalf("running gopackagesdriver: %s; error did not contain %q", errMsg, wantError)
+		}
+		return packages.DriverResponse{}
 	}
 	var resp packages.DriverResponse
 	if err := json.Unmarshal(out.Bytes(), &resp); err != nil {