diff options
| author | Russ Egan <russell.egan@thalesgroup.com> | 2026-03-05 00:46:20 +0000 |
|---|---|---|
| committer | Gopher Robot <gobot@golang.org> | 2026-03-06 10:42:12 -0800 |
| commit | 6c083034f82ddb2a91d3fbe0f96e39f1ecd194d8 (patch) | |
| tree | 5adefb16c5e80b9bf0a94261f5dfcb713bd16253 /src/testing | |
| parent | 73db2f85aab25c06f47c832364600d2c5e243ffa (diff) | |
| download | go-6c083034f82ddb2a91d3fbe0f96e39f1ecd194d8.tar.xz | |
testing: fix construction of the testing artifacts path
The existing implementation had a few problems:
- It constructs a path which starts with a forward slash, which is
then immediately rejected by filepath.Localize() as invalid.
- It did not correctly remove the module path from the import path if
the test was in the root package of the module: it would do the
equivalent of strings.TrimPrefix("example.com/jdoe/loader",
"example.com/jdoe/loader/"), which trims nothing, and leaves the full
module name in the base.
- Tests didn't check for any behaviors related to which artifact path
was selected.
Removed leading slash, handle tests in the root package by omitting the
<pkg> component from the artifact dir path, and add tests.
Fixes #77763
Change-Id: I00fc7381b939e4d8a8a9811e2a95fc2d8c5a6e84
GitHub-Last-Rev: 9c39392a691a81cef48adde88ab45dc40ed81105
GitHub-Pull-Request: golang/go#77778
Reviewed-on: https://go-review.googlesource.com/c/go/+/748581
Auto-Submit: Damien Neil <dneil@google.com>
Reviewed-by: Damien Neil <dneil@google.com>
LUCI-TryBot-Result: Go LUCI <golang-scoped@luci-project-accounts.iam.gserviceaccount.com>
Reviewed-by: Cherry Mui <cherryyz@google.com>
Diffstat (limited to 'src/testing')
| -rw-r--r-- | src/testing/testing.go | 42 | ||||
| -rw-r--r-- | src/testing/testing_test.go | 95 |
2 files changed, 121 insertions, 16 deletions
diff --git a/src/testing/testing.go b/src/testing/testing.go index 34b45b41b9..bf95f1cfbb 100644 --- a/src/testing/testing.go +++ b/src/testing/testing.go @@ -1363,6 +1363,21 @@ func (c *common) makeArtifactDir() (string, error) { return c.makeTempDir() } + artifactBase := filepath.Join(artifactDir, c.relativeArtifactBase()) + if err := os.MkdirAll(artifactBase, 0o777); err != nil { + return "", err + } + dir, err := os.MkdirTemp(artifactBase, "") + if err != nil { + return "", err + } + if c.chatty != nil { + c.chatty.Updatef(c.name, "=== ARTIFACTS %s %v\n", c.name, dir) + } + return dir, nil +} + +func (c *common) relativeArtifactBase() string { // If the test name is longer than maxNameSize, truncate it and replace the last // hashSize bytes with a hash of the full name. const maxNameSize = 64 @@ -1373,11 +1388,17 @@ func (c *common) makeArtifactDir() (string, error) { } // Remove the module path prefix from the import path. - pkg := strings.TrimPrefix(c.importPath, c.modulePath+"/") + // If this is the root package, pkg will be empty. + pkg := strings.TrimPrefix(c.importPath, c.modulePath) + // Remove the leading slash. + pkg = strings.TrimPrefix(pkg, "/") - // Join with /, not filepath.Join: the import path is /-separated, - // and we don't want removeSymbolsExcept to strip \ separators on Windows. - base := "/" + pkg + "/" + name + base := name + if pkg != "" { + // Join with /, not filepath.Join: the import path is /-separated, + // and we don't want removeSymbolsExcept to strip \ separators on Windows. + base = pkg + "/" + name + } base = removeSymbolsExcept(base, "!#$%&()+,-.=@^_{}~ /") base, err := filepath.Localize(base) if err != nil { @@ -1386,18 +1407,7 @@ func (c *common) makeArtifactDir() (string, error) { base = "" } - artifactBase := filepath.Join(artifactDir, base) - if err := os.MkdirAll(artifactBase, 0o777); err != nil { - return "", err - } - dir, err := os.MkdirTemp(artifactBase, "") - if err != nil { - return "", err - } - if c.chatty != nil { - c.chatty.Updatef(c.name, "=== ARTIFACTS %s %v\n", c.name, dir) - } - return dir, nil + return base } func removeSymbolsExcept(s, allowed string) string { diff --git a/src/testing/testing_test.go b/src/testing/testing_test.go index 167f4a0b45..afa3c267df 100644 --- a/src/testing/testing_test.go +++ b/src/testing/testing_test.go @@ -1123,6 +1123,101 @@ func TestArtifactDirConsistent(t *testing.T) { } } +func TestArtifactDirectoryPaths(t *testing.T) { + testenv.MustHaveExec(t) + t.Parallel() + + tempDir := t.TempDir() + + // 1. Setup the temporary module. + if err := os.WriteFile(filepath.Join(tempDir, "go.mod"), []byte("module example.com/testmod\n"), 0666); err != nil { + t.Fatal(err) + } + + writeTmpl := func(pkgDir string, content string) { + fullDir := filepath.Join(tempDir, pkgDir) + if err := os.MkdirAll(fullDir, 0777); err != nil { + t.Fatal(err) + } + if err := os.WriteFile(filepath.Join(fullDir, "a_test.go"), []byte(content), 0666); err != nil { + t.Fatal(err) + } + } + + // Root package test + writeTmpl(".", `package root_test +import "testing" +func TestRootNiceName(t *testing.T) { t.ArtifactDir() } +`) + + // Subpackage test + writeTmpl("subpkg", `package subpkg_test +import "testing" +func TestSubNiceName(t *testing.T) { t.ArtifactDir() } +`) + + // Deep subpackage test with various scenarios + writeTmpl("deep/nested/pkg", `package pkg_test +import "testing" +func TestNiceName(t *testing.T) { t.ArtifactDir() } +func TestParent(t *testing.T) { + t.Run("SubTest", func(t *testing.T) { t.ArtifactDir() }) +} +func TestVeryLongNameThatExceedsSixtyFourCharactersAndThereforeMustBeTruncatedAndHashed(t *testing.T) { t.ArtifactDir() } +func TestInvalid_Chars_In_Name(t *testing.T) { t.ArtifactDir() } +`) + // We use "TestInvalid_Chars_In_Name" because go test framework parses functions by ^Test. + // But let's actually make it have invalid path chars: + writeTmpl("deep/nested/pkg2", `package pkg2_test +import "testing" +func TestInvalid_Chars_In_Name(t *testing.T) { + t.Run("SubTest:with*stars", func(t *testing.T) { t.ArtifactDir() }) +} +`) + + // 2. Run the tests. + cmd := testenv.Command(t, testenv.GoToolPath(t), "test", "-v", "-artifacts", "./...") + cmd.Dir = tempDir + cmd = testenv.CleanCmdEnv(cmd) + out, err := cmd.CombinedOutput() + if err != nil { + t.Fatalf("%v failed: %v\n%s", cmd, err, out) + } + + // 3. Parse and verify output. + tests := []struct { + name string + wantPrefix string + }{ + // Root package tests don't have a package prefix because modulePath == importPath + {"TestRootNiceName", "TestRootNiceName"}, + {"TestSubNiceName", "subpkg/TestSubNiceName"}, + {"TestNiceName", "deep/nested/pkg/TestNiceName"}, + {"TestParent/SubTest", "deep/nested/pkg/TestParent__SubTest"}, + {"TestVeryLongNameThatExceedsSixtyFourCharactersAndThereforeMustBeTruncatedAndHashed", `deep/nested/pkg/TestVeryLongNameThatExceedsSixtyFourCharactersAn[0-9a-f]+`}, + {`TestInvalid_Chars_In_Name/SubTest:with\*stars`, `deep/nested/pkg2/TestInvalid_Chars_In_Name__SubTestwithstars`}, + } + + for _, tt := range tests { + re := regexp.MustCompile(`=== ARTIFACTS ` + tt.name + ` ([^\n]+)`) + match := re.FindSubmatch(out) + if match == nil { + t.Errorf("expected output matching %q, got\n%q", re, out) + continue + } + artifactDir := string(match[1]) + + slashDir := filepath.ToSlash(artifactDir) + + wantSuffixPattern := `_artifacts/` + tt.wantPrefix + `/[^/]+$` + wantRegex := regexp.MustCompile(wantSuffixPattern) + + if !wantRegex.MatchString(slashDir) { + t.Errorf("artifact directory for %s: got %s, want suffix matching %s", tt.name, slashDir, wantSuffixPattern) + } + } +} + func checkArtifactDir(t *testing.T, out []byte, testName, outputDir string) { t.Helper() |
