Skip to content

Commit 2abfe08

Browse files
committed
gopackagesdriver: move and simplify test
Moved //tests/integration/gopackagesdriver:gopackagesdriver_test to //go/tools/gopackagesdriver:gopackagesdriver_test. Previously the test invoked gopackagesdriver with 'bazel run' in a temporary workspace, which forced Bazel to build it, slowing down the test significantly, especially on Windows. Now, the test embeds the gopackagesdriver library and invokes it directly. The driver still runs in a temporary workspace and still needs to invoke bazel, so the test is still slow, but it's faster than before. Also, a number of other improvements: - gopackagesdriver now sets --consistent_labels if the bazel version is 6.4.0 or later instead of guessing based on the link-stamped rules_go repo name. - bazel_testing.BazelCmd now writes --output_user_root to a .bazelrc file instead of passing it to commands. gopackagesdriver invokes bazel directly, so this lets it use the same output root.
1 parent 31549c1 commit 2abfe08

File tree

10 files changed

+164
-109
lines changed

10 files changed

+164
-109
lines changed

go/tools/bazel_testing/bazel_testing.go

Lines changed: 17 additions & 15 deletions
Original file line numberDiff line numberDiff line change
@@ -129,7 +129,7 @@ func TestMain(m *testing.M, args Args) {
129129
workspaceDir, cleanup, err := setupWorkspace(args, files)
130130
defer func() {
131131
if err := cleanup(); err != nil {
132-
fmt.Fprintf(os.Stderr, "cleanup error: %v\n", err)
132+
fmt.Fprintf(os.Stderr, "cleanup warning: %v\n", err)
133133
// Don't fail the test on a cleanup error.
134134
// Some operating systems (windows, maybe also darwin) can't reliably
135135
// delete executable files after they're run.
@@ -169,13 +169,7 @@ func TestMain(m *testing.M, args Args) {
169169
// hide that this code is executing inside a bazel test.
170170
func BazelCmd(args ...string) *exec.Cmd {
171171
cmd := exec.Command("bazel")
172-
if outputUserRoot != "" {
173-
cmd.Args = append(cmd.Args,
174-
"--output_user_root="+outputUserRoot,
175-
"--nosystem_rc",
176-
"--nohome_rc",
177-
)
178-
}
172+
cmd.Args = append(cmd.Args, "--nosystem_rc", "--nohome_rc")
179173
cmd.Args = append(cmd.Args, args...)
180174
for _, e := range os.Environ() {
181175
// Filter environment variables set by the bazel test wrapper script.
@@ -285,7 +279,11 @@ func setupWorkspace(args Args, files []string) (dir string, cleanup func() error
285279
tmpDir = filepath.Clean(tmpDir)
286280
if i := strings.Index(tmpDir, string(os.PathSeparator)+"execroot"+string(os.PathSeparator)); i >= 0 {
287281
outBaseDir = tmpDir[:i]
288-
outputUserRoot = filepath.Dir(outBaseDir)
282+
if dir, err := filepath.Abs(outBaseDir); err == nil {
283+
// Use forward slashes, even on Windows. Bazel's rc file parser
284+
// reports an error if there are backslashes.
285+
outputUserRoot = strings.ReplaceAll(dir, `\`, `/`)
286+
}
289287
cacheDir = filepath.Join(outBaseDir, "bazel_testing")
290288
} else {
291289
cacheDir = filepath.Join(tmpDir, "bazel_testing")
@@ -312,14 +310,18 @@ func setupWorkspace(args Args, files []string) (dir string, cleanup func() error
312310
return "", cleanup, err
313311
}
314312

315-
// Create a .bazelrc file if GO_BAZEL_TEST_BAZELFLAGS is set.
313+
// Create a .bazelrc file with the contents of GO_BAZEL_TEST_BAZELFLAGS is set.
316314
// The test can override this with its own .bazelrc or with flags in commands.
315+
bazelrcPath := filepath.Join(mainDir, ".bazelrc")
316+
bazelrcBuf := &bytes.Buffer{}
317+
if outputUserRoot != "" {
318+
fmt.Fprintf(bazelrcBuf, "startup --output_user_root=%s\n", outputUserRoot)
319+
}
317320
if flags := os.Getenv("GO_BAZEL_TEST_BAZELFLAGS"); flags != "" {
318-
bazelrcPath := filepath.Join(mainDir, ".bazelrc")
319-
content := "build " + flags
320-
if err := ioutil.WriteFile(bazelrcPath, []byte(content), 0666); err != nil {
321-
return "", cleanup, err
322-
}
321+
fmt.Fprintf(bazelrcBuf, "build %s\n", flags)
322+
}
323+
if err := os.WriteFile(bazelrcPath, bazelrcBuf.Bytes(), 0666); err != nil {
324+
return "", cleanup, err
323325
}
324326

325327
// Extract test files for the main workspace.

go/tools/gopackagesdriver/BUILD.bazel

Lines changed: 14 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,6 @@
11
load("//go:def.bzl", "go_binary", "go_library")
22
load("//go/private:common.bzl", "RULES_GO_REPO_NAME")
3+
load("//go/tools/bazel_testing:def.bzl", "go_bazel_test")
34

45
go_library(
56
name = "gopackagesdriver_lib",
@@ -31,6 +32,19 @@ go_binary(
3132
},
3233
)
3334

35+
RULES_GO_REPO_NAME_FOR_TEST = RULES_GO_REPO_NAME if RULES_GO_REPO_NAME != "@" else "@io_bazel_rules_go"
36+
37+
go_bazel_test(
38+
name = "gopackagesdriver_test",
39+
size = "enormous",
40+
srcs = ["gopackagesdriver_test.go"],
41+
embed = [":gopackagesdriver_lib"],
42+
rule_files = ["//:all_files"],
43+
x_defs = {
44+
"rulesGoRepositoryName": RULES_GO_REPO_NAME_FOR_TEST,
45+
},
46+
)
47+
3448
filegroup(
3549
name = "all_files",
3650
testonly = True,

go/tools/gopackagesdriver/bazel.go

Lines changed: 33 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -26,6 +26,7 @@ import (
2626
"os"
2727
"os/exec"
2828
"path/filepath"
29+
"strconv"
2930
"strings"
3031
)
3132

@@ -38,7 +39,7 @@ type Bazel struct {
3839
workspaceRoot string
3940
bazelStartupFlags []string
4041
info map[string]string
41-
version string
42+
version bazelVersion
4243
}
4344

4445
// Minimal BEP structs to access the build outputs
@@ -56,7 +57,7 @@ func NewBazel(ctx context.Context, bazelBin, workspaceRoot string, bazelStartupF
5657
bazelBin: bazelBin,
5758
workspaceRoot: workspaceRoot,
5859
bazelStartupFlags: bazelStartupFlags,
59-
version: "6",
60+
version: bazelVersion{6, 0, 0}, // assumed until 'bazel info' output parsed
6061
}
6162
if err := b.fillInfo(ctx); err != nil {
6263
return nil, fmt.Errorf("unable to query bazel info: %w", err)
@@ -77,7 +78,9 @@ func (b *Bazel) fillInfo(ctx context.Context) error {
7778
}
7879
release := strings.Split(b.info["release"], " ")
7980
if len(release) == 2 {
80-
b.version = release[1]
81+
if version, ok := parseBazelVersion(release[1]); ok {
82+
b.version = version
83+
}
8184
}
8285
return nil
8386
}
@@ -168,3 +171,30 @@ func (b *Bazel) ExecutionRoot() string {
168171
func (b *Bazel) OutputBase() string {
169172
return b.info["output_base"]
170173
}
174+
175+
type bazelVersion [3]int
176+
177+
func parseBazelVersion(raw string) (bazelVersion, bool) {
178+
parts := strings.Split(raw, ".")
179+
if len(parts) != 3 {
180+
return [3]int{}, false
181+
}
182+
var version [3]int
183+
for i, part := range parts {
184+
v, err := strconv.Atoi(part)
185+
if err != nil {
186+
return [3]int{}, false
187+
}
188+
version[i] = v
189+
}
190+
return version, true
191+
}
192+
193+
func (a bazelVersion) compare(b bazelVersion) int {
194+
for i := 0; i < len(a); i++ {
195+
if c := a[i] - b[i]; c != 0 {
196+
return c
197+
}
198+
}
199+
return 0
200+
}

go/tools/gopackagesdriver/bazel_json_builder.go

Lines changed: 1 addition & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -161,8 +161,7 @@ func (b *BazelJSONBuilder) outputGroupsForMode(mode LoadMode) string {
161161

162162
func (b *BazelJSONBuilder) query(ctx context.Context, query string) ([]string, error) {
163163
var bzlmodQueryFlags []string
164-
if strings.HasPrefix(rulesGoRepositoryName, "@@") {
165-
// Requires Bazel 6.4.0.
164+
if b.bazel.version.compare(bazelVersion{6, 4, 0}) >= 0 {
166165
bzlmodQueryFlags = []string{"--consistent_labels"}
167166
}
168167
queryArgs := concatStringsArrays(bazelQueryFlags, bzlmodQueryFlags, []string{

tests/integration/gopackagesdriver/gopackagesdriver_test.go renamed to go/tools/gopackagesdriver/gopackagesdriver_test.go

Lines changed: 73 additions & 28 deletions
Original file line numberDiff line numberDiff line change
@@ -1,18 +1,20 @@
1-
package gopackagesdriver_test
1+
package main
22

33
import (
4+
"bytes"
5+
"context"
46
"encoding/json"
7+
"os"
58
"path"
69
"strings"
710
"testing"
811

912
"github.com/bazelbuild/rules_go/go/tools/bazel_testing"
10-
gpd "github.com/bazelbuild/rules_go/go/tools/gopackagesdriver"
1113
)
1214

1315
type response struct {
1416
Roots []string `json:",omitempty"`
15-
Packages []*gpd.FlatPackage
17+
Packages []*FlatPackage
1618
}
1719

1820
func TestMain(m *testing.M) {
@@ -69,18 +71,7 @@ const (
6971
)
7072

7173
func TestBaseFileLookup(t *testing.T) {
72-
reader := strings.NewReader("{}")
73-
out, _, err := bazel_testing.BazelOutputWithInput(reader, "run", "@io_bazel_rules_go//go/tools/gopackagesdriver", "--", "file=hello.go")
74-
if err != nil {
75-
t.Errorf("Unexpected error: %w", err.Error())
76-
return
77-
}
78-
var resp response
79-
err = json.Unmarshal(out, &resp)
80-
if err != nil {
81-
t.Errorf("Failed to unmarshal packages driver response: %w\n%w", err.Error(), out)
82-
return
83-
}
74+
resp := runForTest(t, "file=hello.go")
8475

8576
t.Run("roots", func(t *testing.T) {
8677
if len(resp.Roots) != 1 {
@@ -95,7 +86,7 @@ func TestBaseFileLookup(t *testing.T) {
9586
})
9687

9788
t.Run("package", func(t *testing.T) {
98-
var pkg *gpd.FlatPackage
89+
var pkg *FlatPackage
9990
for _, p := range resp.Packages {
10091
if p.ID == resp.Roots[0] {
10192
pkg = p
@@ -130,7 +121,7 @@ func TestBaseFileLookup(t *testing.T) {
130121
})
131122

132123
t.Run("dependency", func(t *testing.T) {
133-
var osPkg *gpd.FlatPackage
124+
var osPkg *FlatPackage
134125
for _, p := range resp.Packages {
135126
if p.ID == osPkgID || p.ID == bzlmodOsPkgID {
136127
osPkg = p
@@ -150,17 +141,7 @@ func TestBaseFileLookup(t *testing.T) {
150141
}
151142

152143
func TestExternalTests(t *testing.T) {
153-
reader := strings.NewReader("{}")
154-
out, stderr, err := bazel_testing.BazelOutputWithInput(reader, "run", "@io_bazel_rules_go//go/tools/gopackagesdriver", "--", "file=hello_external_test.go")
155-
if err != nil {
156-
t.Errorf("Unexpected error: %w\n=====\n%s\n=====", err.Error(), stderr)
157-
}
158-
var resp response
159-
err = json.Unmarshal(out, &resp)
160-
if err != nil {
161-
t.Errorf("Failed to unmarshal packages driver response: %w\n%w", err.Error(), out)
162-
}
163-
144+
resp := runForTest(t, "file=hello_external_test.go")
164145
if len(resp.Roots) != 2 {
165146
t.Errorf("Expected exactly two roots for package: %+v", resp.Roots)
166147
}
@@ -186,7 +167,71 @@ func TestExternalTests(t *testing.T) {
186167
}
187168
}
188169

170+
func runForTest(t *testing.T, args ...string) driverResponse {
171+
t.Helper()
172+
173+
// Remove most environment variables, other than those on an allowlist.
174+
//
175+
// Bazel sets TEST_* and RUNFILES_* and a bunch of other variables.
176+
// If Bazel is invoked when these variables, it assumes (correctly)
177+
// that it's being invoked by a test, and it does different things that
178+
// we don't want. For example, it randomizes the output directory, which
179+
// is extremely expensive here. Out test framework creates an output
180+
// directory shared among go_bazel_tests and points to it using .bazelrc.
181+
//
182+
// This only works if TEST_TMPDIR is not set when invoking bazel.
183+
// bazel_testing.BazelCmd normally unsets that, but since gopackagesdriver
184+
// invokes bazel directly, we need to unset it here.
185+
allowEnv := map[string]bool{
186+
"HOME": true,
187+
"PATH": true,
188+
"PWD": true,
189+
"SYSTEMDRIVE": true,
190+
"SYSTEMROOT": true,
191+
"TEMP": true,
192+
"TMP": true,
193+
"TZ": true,
194+
"USER": true,
195+
}
196+
var oldEnv []string
197+
for _, env := range os.Environ() {
198+
key, value, ok := strings.Cut(env, "=")
199+
if ok && !allowEnv[key] {
200+
os.Unsetenv(key)
201+
oldEnv = append(oldEnv, key, value)
202+
}
203+
}
204+
defer func() {
205+
for i := 0; i < len(oldEnv); i += 2 {
206+
os.Setenv(oldEnv[i], oldEnv[i+1])
207+
}
208+
}()
209+
210+
// Set workspaceRoot global variable.
211+
// It's initialized to the BUILD_WORKSPACE_DIRECTORY environment variable
212+
// before this point.
213+
wd, err := os.Getwd()
214+
if err != nil {
215+
t.Fatal(err)
216+
}
217+
oldWorkspaceRoot := workspaceRoot
218+
workspaceRoot = wd
219+
defer func() { workspaceRoot = oldWorkspaceRoot }()
220+
221+
in := strings.NewReader("{}")
222+
out := &bytes.Buffer{}
223+
if err := run(context.Background(), in, out, args); err != nil {
224+
t.Fatalf("running gopackagesdriver: %v", err)
225+
}
226+
var resp driverResponse
227+
if err := json.Unmarshal(out.Bytes(), &resp); err != nil {
228+
t.Fatalf("unmarshaling response: %v", err)
229+
}
230+
return resp
231+
}
232+
189233
func assertSuffixesInList(t *testing.T, list []string, expectedSuffixes ...string) {
234+
t.Helper()
190235
for _, suffix := range expectedSuffixes {
191236
itemFound := false
192237
for _, listItem := range list {

go/tools/gopackagesdriver/json_packages_driver.go

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -23,7 +23,7 @@ type JSONPackagesDriver struct {
2323
registry *PackageRegistry
2424
}
2525

26-
func NewJSONPackagesDriver(jsonFiles []string, prf PathResolverFunc, bazelVersion string) (*JSONPackagesDriver, error) {
26+
func NewJSONPackagesDriver(jsonFiles []string, prf PathResolverFunc, bazelVersion bazelVersion) (*JSONPackagesDriver, error) {
2727
jpd := &JSONPackagesDriver{
2828
registry: NewPackageRegistry(bazelVersion),
2929
}

0 commit comments

Comments
 (0)