Skip to content

Commit

Permalink
bzltestutil: set importmap to fix run_dir (#3679)
Browse files Browse the repository at this point in the history
Go 1.21 clarified package initialization order and changed behavior:
packages with lexicographically lower paths are now initialized earlier.

We need bzltestutil to initialize before user packages so it can
change to the correct directory. To accomplish this, we set an importmap
prefix that starts with '+', the lowest allowed character.

Fixes #3675
  • Loading branch information
jayconrod authored Sep 5, 2023
1 parent 85f2440 commit a8cb4b7
Show file tree
Hide file tree
Showing 5 changed files with 47 additions and 10 deletions.
7 changes: 5 additions & 2 deletions go/private/rules/test.bzl
Original file line number Diff line number Diff line change
Expand Up @@ -128,8 +128,11 @@ def _go_test_impl(ctx):
# Disable symbol table and DWARF generation for test binaries.
test_gc_linkopts.extend(["-s", "-w"])

# Link in the run_dir global for bzltestutil
test_gc_linkopts.extend(["-X", "github.com/bazelbuild/rules_go/go/tools/bzltestutil.RunDir=" + run_dir])
# Link in the run_dir global for bzltestutil.
# We add "+initfirst/" to the package path so the package is initialized
# before user code. See comment above the init function
# in bzltestutil/init.go.
test_gc_linkopts.extend(["-X", "+initfirst/github.com/bazelbuild/rules_go/go/tools/bzltestutil.RunDir=" + run_dir])

# Now compile the test binary itself
test_library = GoLibrary(
Expand Down
9 changes: 4 additions & 5 deletions go/tools/builders/generate_test_main.go
Original file line number Diff line number Diff line change
Expand Up @@ -87,11 +87,10 @@ func (c *Cases) Version(v string) bool {
const testMainTpl = `
package main
// This package must be initialized before packages being tested.
// NOTE: this relies on the order of package initialization, which is the spec
// is somewhat unclear about-- it only clearly guarantees that imported packages
// are initialized before their importers, though in practice (and implied) it
// also respects declaration order, which we're relying on here.
// bzltestutil may change the current directory in its init function to emulate
// 'go test' behavior. It must be initialized before user packages.
// In Go 1.20 and earlier, this import declaration must appear before
// imports of user packages. See comment in bzltestutil/init.go.
import "github.com/bazelbuild/rules_go/go/tools/bzltestutil"
import (
Expand Down
3 changes: 3 additions & 0 deletions go/tools/bzltestutil/BUILD.bazel
Original file line number Diff line number Diff line change
Expand Up @@ -9,6 +9,9 @@ go_tool_library(
"wrap.go",
"xml.go",
],
# We add "+initfirst/" to the package path so this package is initialized
# before user code. See comment above the init function in init.go.
importmap = "+initfirst/github.com/bazelbuild/rules_go/go/tools/bzltestutil",
importpath = "github.com/bazelbuild/rules_go/go/tools/bzltestutil",
visibility = ["//visibility:public"],
)
Expand Down
31 changes: 28 additions & 3 deletions go/tools/bzltestutil/init.go
Original file line number Diff line number Diff line change
Expand Up @@ -30,10 +30,35 @@ var (
testExecDir string
)

// Before Go 1.21, this initializer runs before any user's package.
// This function sets the current working directory to RunDir when the test
// executable is started by Bazel (when TEST_SRCDIR and TEST_WORKSPACE are set).
//
// Since Go 1.21, the order of this initializer calls is not guarenteed to be first.
// See https://go.dev/doc/go1.21#language for more details.
// This hides a difference between Bazel and 'go test': 'go test' starts test
// executables in the package source directory, while Bazel starts test
// executables in a directory made to look like the repository root directory.
// Tests frequently refer to testdata files using paths relative to their
// package directory, so open source tests frequently break unless they're
// written with Bazel specifically in mind (using go/runfiles).
//
// For this init function to work, it must be called before init functions
// in all user packages.
//
// In Go 1.20 and earlier, the package initialization order was underspecified,
// other than a requirement that each package is initialized after all its
// transitively imported packages. We relied on the linker initializing
// packages in the order their imports appeared in source, so we imported
// bzltestutil from the generated test main before other packages.
//
// In Go 1.21, the package initialization order was clarified, and the
// linker implementation was changed. See https://go.dev/doc/go1.21#language.
// The order is now affected by import path: packages with lexicographically
// lower import paths go first.
//
// To ensure this package is initialized before user code, we add the prefix
// '+initfirst/' to this package's path with the 'importmap' directive.
// '+' is the first allowed character that sorts higher than letters.
// Because we're using 'importmap' and not 'importpath', this hack does not
// affect .go source files.
func init() {
var err error
testExecDir, err = os.Getwd()
Expand Down
7 changes: 7 additions & 0 deletions tests/legacy/test_chdir/data_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -8,6 +8,13 @@ import (

const file = "data.txt"

func init() {
_, err := os.Stat(file)
if err != nil {
log.Fatalf("in init(), could not stat %s: %v", file, err)
}
}

func TestMain(m *testing.M) {
_, err := os.Stat(file)
if err != nil {
Expand Down

0 comments on commit a8cb4b7

Please sign in to comment.