From b611b3a8cc8c4cab3853853a135d5c29e807f513 Mon Sep 17 00:00:00 2001 From: Maksadbek Akhmedov Date: Wed, 8 Feb 2023 14:58:04 +0100 Subject: [PATCH] cmd/go: make go test build multiple executables If -c is set while testing multiple packages, then allow to build testing binary executables to the current directory or to the directory that -o refers to. $ go test -c -o /tmp ./pkg1 ./pkg2 ./pkg2 $ ls /tmp pkg1.test pkg2.test pkg3.test Fixes #15513. Change-Id: I3aba01bebfa90e61e59276f2832d99c0d323b82e Reviewed-on: https://go-review.googlesource.com/c/go/+/466397 Run-TryBot: Bryan Mills Auto-Submit: Bryan Mills TryBot-Result: Gopher Robot Reviewed-by: Bryan Mills Reviewed-by: Dmitri Shuralyov --- src/cmd/go/internal/test/test.go | 91 ++++++++++++++----- .../script/test_compile_multi_pkg.txt | 46 ++++++++++ 2 files changed, 116 insertions(+), 21 deletions(-) create mode 100644 src/cmd/go/testdata/script/test_compile_multi_pkg.txt diff --git a/src/cmd/go/internal/test/test.go b/src/cmd/go/internal/test/test.go index 904144f279..a986718abf 100644 --- a/src/cmd/go/internal/test/test.go +++ b/src/cmd/go/internal/test/test.go @@ -587,6 +587,8 @@ var ( testCacheExpire time.Time // ignore cached test results before this time testBlockProfile, testCPUProfile, testMemProfile, testMutexProfile, testTrace string // profiling flag that limits test to one package + + testODir = false ) // testProfile returns the name of an arbitrary single-package profiling flag @@ -694,12 +696,6 @@ func runTest(ctx context.Context, cmd *base.Command, args []string) { base.Fatalf("no packages to test") } - if testC && len(pkgs) != 1 { - base.Fatalf("cannot use -c flag with multiple packages") - } - if testO != "" && len(pkgs) != 1 { - base.Fatalf("cannot use -o flag with multiple packages") - } if testFuzz != "" { if !platform.FuzzSupported(cfg.Goos, cfg.Goarch) { base.Fatalf("-fuzz flag is not supported on %s/%s", cfg.Goos, cfg.Goarch) @@ -749,6 +745,42 @@ func runTest(ctx context.Context, cmd *base.Command, args []string) { if testProfile() != "" && len(pkgs) != 1 { base.Fatalf("cannot use %s flag with multiple packages", testProfile()) } + + if testO != "" { + if strings.HasSuffix(testO, "/") || strings.HasSuffix(testO, string(os.PathSeparator)) { + testODir = true + } else if fi, err := os.Stat(testO); err == nil && fi.IsDir() { + testODir = true + } + } + + if len(pkgs) > 1 && (testC || testO != "") && !base.IsNull(testO) { + if testO != "" && !testODir { + base.Fatalf("with multiple packages, -o must refer to a directory or %s", os.DevNull) + } + + pkgsForBinary := map[string][]*load.Package{} + + for _, p := range pkgs { + testBinary := testBinaryName(p) + pkgsForBinary[testBinary] = append(pkgsForBinary[testBinary], p) + } + + for testBinary, pkgs := range pkgsForBinary { + if len(pkgs) > 1 { + var buf strings.Builder + for _, pkg := range pkgs { + buf.WriteString(pkg.ImportPath) + buf.WriteString("\n") + } + + base.Errorf("cannot write test binary %s for multiple packages:\n%s", testBinary, buf.String()) + } + } + + base.ExitIfErrors() + } + initCoverProfile() defer closeCoverProfile() @@ -978,17 +1010,7 @@ func builderTest(b *work.Builder, ctx context.Context, pkgOpts load.PackageOpts, buildTest.Deps = append(buildTest.Deps, buildP) } - // Use last element of import path, not package name. - // They differ when package name is "main". - // But if the import path is "command-line-arguments", - // like it is during 'go run', use the package name. - var elem string - if p.ImportPath == "command-line-arguments" { - elem = p.Name - } else { - elem = p.DefaultExecName() - } - testBinary := elem + ".test" + testBinary := testBinaryName(p) testDir := b.NewObjdir() if err := b.Mkdir(testDir); err != nil { @@ -1048,14 +1070,25 @@ func builderTest(b *work.Builder, ctx context.Context, pkgOpts load.PackageOpts, // -c or profiling flag: create action to copy binary to ./test.out. target := filepath.Join(base.Cwd(), testBinary+cfg.ExeSuffix) isNull := false + if testO != "" { target = testO - if base.IsNull(target) { - isNull = true - } else if !filepath.IsAbs(target) { - target = filepath.Join(base.Cwd(), target) + + if testODir { + if filepath.IsAbs(target) { + target = filepath.Join(target, testBinary+cfg.ExeSuffix) + } else { + target = filepath.Join(base.Cwd(), target, testBinary+cfg.ExeSuffix) + } + } else { + if base.IsNull(target) { + isNull = true + } else if !filepath.IsAbs(target) { + target = filepath.Join(base.Cwd(), target) + } } } + if isNull { runAction = buildAction } else { @@ -1862,3 +1895,19 @@ func printExitStatus(b *work.Builder, ctx context.Context, a *work.Action) error } return nil } + +// testBinaryName can be used to create name for test binary executable. +// Use last element of import path, not package name. +// They differ when package name is "main". +// But if the import path is "command-line-arguments", +// like it is during 'go run', use the package name. +func testBinaryName(p *load.Package) string { + var elem string + if p.ImportPath == "command-line-arguments" { + elem = p.Name + } else { + elem = p.DefaultExecName() + } + + return elem + ".test" +} diff --git a/src/cmd/go/testdata/script/test_compile_multi_pkg.txt b/src/cmd/go/testdata/script/test_compile_multi_pkg.txt new file mode 100644 index 0000000000..1f298b6fd5 --- /dev/null +++ b/src/cmd/go/testdata/script/test_compile_multi_pkg.txt @@ -0,0 +1,46 @@ +[short] skip 'links test binaries' + +# Verify test -c can output multiple executables to a directory. + +go test -c -o $WORK/some/nonexisting/directory/ ./pkg/... +exists -exec $WORK/some/nonexisting/directory/pkg1.test$GOEXE +exists -exec $WORK/some/nonexisting/directory/pkg2.test$GOEXE + +go test -c ./pkg/... +exists -exec pkg1.test$GOEXE +exists -exec pkg2.test$GOEXE + +! go test -c -o $WORK/bin/test/bin.test.exe ./pkg/... +stderr '^with multiple packages, -o must refer to a directory or '$devnull + +! go test -c ./... +stderr '^cannot write test binary pkg1.test for multiple packages:\nexample/anotherpkg/pkg1\nexample/pkg/pkg1' + +! go test -c -o $WORK/bin/test/ ./... +stderr '^cannot write test binary pkg1.test for multiple packages:\nexample/anotherpkg/pkg1\nexample/pkg/pkg1' + +! go test -o $WORK/bin/filename.exe ./pkg/... +stderr '^with multiple packages, -o must refer to a directory or '$devnull + +! go test -o $WORK/bin/ ./... +stderr '^cannot write test binary pkg1.test for multiple packages:\nexample/anotherpkg/pkg1\nexample/pkg/pkg1' + +go test -c -o $devnull ./... + +rm pkg1.test$GOEXE +rm pkg2.test$GOEXE +go test -o . ./pkg/... +exists -exec pkg1.test$GOEXE +exists -exec pkg2.test$GOEXE + +-- go.mod -- +module example + +-- pkg/pkg1/pkg1_test.go -- +package pkg1 + +-- pkg/pkg2/pkg2_test.go -- +package pkg2 + +-- anotherpkg/pkg1/pkg1_test.go -- +package pkg1 \ No newline at end of file -- 2.50.0