aboutsummaryrefslogtreecommitdiff
path: root/src/cmd
diff options
context:
space:
mode:
Diffstat (limited to 'src/cmd')
-rw-r--r--src/cmd/go/internal/doc/dirs.go59
-rw-r--r--src/cmd/go/internal/doc/doc.go116
-rw-r--r--src/cmd/go/internal/doc/doc_test.go16
-rw-r--r--src/cmd/go/internal/doc/mod.go12
-rw-r--r--src/cmd/go/internal/doc/pkg.go12
-rw-r--r--src/cmd/go/internal/doc/pkgsite.go8
-rw-r--r--src/cmd/go/internal/modload/init.go5
-rw-r--r--src/cmd/go/testdata/script/doc_http_url.txt31
-rw-r--r--src/cmd/go/testdata/script/mod_doc.txt3
-rw-r--r--src/cmd/go/testdata/script/mod_outside.txt10
-rw-r--r--src/cmd/internal/script/scripttest/readme.go1
11 files changed, 171 insertions, 102 deletions
diff --git a/src/cmd/go/internal/doc/dirs.go b/src/cmd/go/internal/doc/dirs.go
index 5efd40b1d5..86b4b526a3 100644
--- a/src/cmd/go/internal/doc/dirs.go
+++ b/src/cmd/go/internal/doc/dirs.go
@@ -15,6 +15,9 @@ import (
"strings"
"sync"
+ "cmd/go/internal/cfg"
+ "cmd/go/internal/modload"
+
"golang.org/x/mod/semver"
)
@@ -41,29 +44,18 @@ var dirs Dirs
// dirsInit starts the scanning of package directories in GOROOT and GOPATH. Any
// extra paths passed to it are included in the channel.
func dirsInit(extra ...Dir) {
- if buildCtx.GOROOT == "" {
- stdout, err := exec.Command("go", "env", "GOROOT").Output()
- if err != nil {
- if ee, ok := err.(*exec.ExitError); ok && len(ee.Stderr) > 0 {
- log.Fatalf("failed to determine GOROOT: $GOROOT is not set and 'go env GOROOT' failed:\n%s", ee.Stderr)
- }
- log.Fatalf("failed to determine GOROOT: $GOROOT is not set and could not run 'go env GOROOT':\n\t%s", err)
- }
- buildCtx.GOROOT = string(bytes.TrimSpace(stdout))
- }
-
dirs.hist = make([]Dir, 0, 1000)
dirs.hist = append(dirs.hist, extra...)
dirs.scan = make(chan Dir)
go dirs.walk(codeRoots())
}
-// goCmd returns the "go" command path corresponding to buildCtx.GOROOT.
+// goCmd returns the "go" command path corresponding to cfg.GOROOT.
func goCmd() string {
- if buildCtx.GOROOT == "" {
+ if cfg.GOROOT == "" {
return "go"
}
- return filepath.Join(buildCtx.GOROOT, "bin", "go")
+ return filepath.Join(cfg.GOROOT, "bin", "go")
}
// Reset puts the scan back at the beginning.
@@ -187,30 +179,31 @@ var usingModules bool
func findCodeRoots() []Dir {
var list []Dir
if !testGOPATH {
- // Check for use of modules by 'go env GOMOD',
- // which reports a go.mod file path if modules are enabled.
- stdout, _ := exec.Command(goCmd(), "env", "GOMOD").Output()
- gomod := string(bytes.TrimSpace(stdout))
-
- usingModules = len(gomod) > 0
- if usingModules && buildCtx.GOROOT != "" {
- list = append(list,
- Dir{dir: filepath.Join(buildCtx.GOROOT, "src"), inModule: true},
- Dir{importPath: "cmd", dir: filepath.Join(buildCtx.GOROOT, "src", "cmd"), inModule: true})
- }
+ // TODO: use the same state used to load the package.
+ // For now it's okay to use a new state because we're just
+ // using it to determine whether we're in module mode. But
+ // it would be good to avoid an extra run of modload.Init.
+ if state := modload.NewState(); state.WillBeEnabled() {
+ usingModules = state.HasModRoot()
+ if usingModules && cfg.GOROOT != "" {
+ list = append(list,
+ Dir{dir: filepath.Join(cfg.GOROOT, "src"), inModule: true},
+ Dir{importPath: "cmd", dir: filepath.Join(cfg.GOROOT, "src", "cmd"), inModule: true})
+ }
- if gomod == os.DevNull {
- // Modules are enabled, but the working directory is outside any module.
- // We can still access std, cmd, and packages specified as source files
- // on the command line, but there are no module roots.
- // Avoid 'go list -m all' below, since it will not work.
- return list
+ if !usingModules {
+ // Modules are enabled, but the working directory is outside any module.
+ // We can still access std, cmd, and packages specified as source files
+ // on the command line, but there are no module roots.
+ // Avoid 'go list -m all' below, since it will not work.
+ return list
+ }
}
}
if !usingModules {
- if buildCtx.GOROOT != "" {
- list = append(list, Dir{dir: filepath.Join(buildCtx.GOROOT, "src")})
+ if cfg.GOROOT != "" {
+ list = append(list, Dir{dir: filepath.Join(cfg.GOROOT, "src")})
}
for _, root := range splitGopath() {
list = append(list, Dir{dir: filepath.Join(root, "src")})
diff --git a/src/cmd/go/internal/doc/doc.go b/src/cmd/go/internal/doc/doc.go
index 4acee8ed42..3ebd0f5dab 100644
--- a/src/cmd/go/internal/doc/doc.go
+++ b/src/cmd/go/internal/doc/doc.go
@@ -21,6 +21,10 @@ import (
"strings"
"cmd/go/internal/base"
+ "cmd/go/internal/cfg"
+ "cmd/go/internal/load"
+ "cmd/go/internal/modload"
+ "cmd/go/internal/search"
"cmd/internal/telemetry/counter"
)
@@ -362,14 +366,29 @@ func failMessage(paths []string, symbol, method string) error {
// and there may be more matches. For example, if the argument
// is rand.Float64, we must scan both crypto/rand and math/rand
// to find the symbol, and the first call will return crypto/rand, true.
-func parseArgs(ctx context.Context, flagSet *flag.FlagSet, args []string) (pkg *build.Package, path, symbol string, more bool) {
+func parseArgs(ctx context.Context, flagSet *flag.FlagSet, args []string) (pkg *load.Package, path, symbol string, more bool) {
wd, err := os.Getwd()
if err != nil {
log.Fatal(err)
}
+ loader := modload.NewState()
+ if testGOPATH {
+ loader = modload.DisabledState()
+ }
+ if len(args) > 0 && strings.Index(args[0], "@") >= 0 {
+ // Version query: force no root
+ loader.ForceUseModules = true
+ loader.RootMode = modload.NoRoot
+ modload.Init(loader)
+ } else if loader.WillBeEnabled() {
+ loader.InitWorkfile()
+ modload.Init(loader)
+ modload.LoadModFile(loader, context.TODO())
+ }
+
if len(args) == 0 {
// Easy: current directory.
- return importDir(wd), "", "", false
+ return mustLoadPackage(ctx, loader, wd), "", "", false
}
arg := args[0]
@@ -388,11 +407,11 @@ func parseArgs(ctx context.Context, flagSet *flag.FlagSet, args []string) (pkg *
log.Fatal("cannot use @version with local or absolute paths")
}
- importPkg := func(p string) (*build.Package, error) {
+ importPkg := func(p string) (*load.Package, error) {
if version != "" {
- return loadVersioned(ctx, p, version)
+ return loadVersioned(ctx, loader, p, version)
}
- return build.Import(p, wd, build.ImportComment)
+ return loadPackage(ctx, loader, p)
}
switch len(args) {
@@ -407,16 +426,16 @@ func parseArgs(ctx context.Context, flagSet *flag.FlagSet, args []string) (pkg *
return pkg, arg, args[1], false
}
for {
- dir, importPath, ok := findNextPackage(arg)
+ importPath, ok := findNextPackage(arg)
if !ok {
break
}
if version != "" {
- if pkg, err = loadVersioned(ctx, importPath, version); err == nil {
+ if pkg, err = loadVersioned(ctx, loader, importPath, version); err == nil {
return pkg, arg, args[1], true
}
} else {
- if pkg, err = build.ImportDir(dir, build.ImportComment); err == nil {
+ if pkg, err = loadPackage(ctx, loader, importPath); err == nil {
return pkg, arg, args[1], true
}
}
@@ -434,7 +453,7 @@ func parseArgs(ctx context.Context, flagSet *flag.FlagSet, args []string) (pkg *
// package paths as their prefix.
var importErr error
if filepath.IsAbs(arg) {
- pkg, importErr = build.ImportDir(arg, build.ImportComment)
+ pkg, importErr = loadPackage(ctx, loader, arg)
if importErr == nil {
return pkg, arg, "", false
}
@@ -449,7 +468,7 @@ func parseArgs(ctx context.Context, flagSet *flag.FlagSet, args []string) (pkg *
// Kills the problem caused by case-insensitive file systems
// matching an upper case name as a package name.
if !strings.ContainsAny(arg, `/\`) && token.IsExported(arg) {
- pkg, err := build.ImportDir(".", build.ImportComment)
+ pkg, err := loadPackage(ctx, loader, ".")
if err == nil {
return pkg, "", arg, false
}
@@ -475,7 +494,7 @@ func parseArgs(ctx context.Context, flagSet *flag.FlagSet, args []string) (pkg *
symbol = arg[period+1:]
}
// Have we identified a package already?
- pkg, err := importPkg(arg[0:period])
+ pkg, err := loadPackage(ctx, loader, arg[0:period])
if err == nil {
return pkg, arg[0:period], symbol, false
}
@@ -483,18 +502,16 @@ func parseArgs(ctx context.Context, flagSet *flag.FlagSet, args []string) (pkg *
// or ivy/value for robpike.io/ivy/value.
pkgName := arg[:period]
for {
- dir, importPath, ok := findNextPackage(pkgName)
+ importPath, ok := findNextPackage(pkgName)
if !ok {
break
}
if version != "" {
- if pkg, err = loadVersioned(ctx, importPath, version); err == nil {
- return pkg, arg[0:period], symbol, true
- }
- } else {
- if pkg, err = build.ImportDir(dir, build.ImportComment); err == nil {
+ if pkg, err = loadVersioned(ctx, loader, importPath, version); err == nil {
return pkg, arg[0:period], symbol, true
}
+ } else if pkg, err = loadPackage(ctx, loader, importPath); err == nil {
+ return pkg, arg[0:period], symbol, true
}
}
dirs.Reset() // Next iteration of for loop must scan all the directories again.
@@ -506,7 +523,7 @@ func parseArgs(ctx context.Context, flagSet *flag.FlagSet, args []string) (pkg *
if version == "" {
version = v
}
- pkg, err := loadVersioned(ctx, pkgPath, version)
+ pkg, err := loadVersioned(ctx, loader, pkgPath, version)
if err == nil {
return pkg, pkgPath, "", false
}
@@ -536,7 +553,38 @@ func parseArgs(ctx context.Context, flagSet *flag.FlagSet, args []string) (pkg *
}
}
// Guess it's a symbol in the current directory.
- return importDir(wd), "", arg, false
+ return mustLoadPackage(ctx, loader, wd), "", arg, false
+}
+
+func loadPackage(ctx context.Context, loader *modload.State, pattern string) (*load.Package, error) {
+ if !search.NewMatch(pattern).IsLiteral() {
+ return nil, fmt.Errorf("pattern %q does not specify a single package", pattern)
+ }
+
+ pkgOpts := load.PackageOpts{
+ IgnoreImports: true,
+ SuppressBuildInfo: true,
+ SuppressEmbedFiles: true,
+ }
+ pkgs := load.PackagesAndErrors(loader, ctx, pkgOpts, []string{pattern})
+
+ if len(pkgs) != 1 {
+ return nil, fmt.Errorf("path %q matched multiple packages", pattern)
+ }
+
+ p := pkgs[0]
+ if p.Error != nil {
+ return nil, p.Error
+ }
+ return p, nil
+}
+
+func mustLoadPackage(ctx context.Context, loader *modload.State, dir string) *load.Package {
+ pkg, err := loadPackage(ctx, loader, dir)
+ if err != nil {
+ log.Fatal(err)
+ }
+ return pkg
}
// dotPaths lists all the dotted paths legal on Unix-like and
@@ -564,15 +612,6 @@ func isDotSlash(arg string) bool {
return false
}
-// importDir is just an error-catching wrapper for build.ImportDir.
-func importDir(dir string) *build.Package {
- pkg, err := build.ImportDir(dir, build.ImportComment)
- if err != nil {
- log.Fatal(err)
- }
- return pkg
-}
-
// parseSymbol breaks str apart into a symbol and method.
// Both may be missing or the method may be missing.
// If present, each must be a valid Go identifier.
@@ -600,36 +639,33 @@ func isExported(name string) bool {
return unexported || token.IsExported(name)
}
-// findNextPackage returns the next full file name path and import path that
-// matches the (perhaps partial) package path pkg. The boolean reports if
-// any match was found.
-func findNextPackage(pkg string) (string, string, bool) {
+// findNextPackage returns the next import path that matches the
+// (perhaps partial) package path pkg. The boolean reports if any match was found.
+func findNextPackage(pkg string) (string, bool) {
if filepath.IsAbs(pkg) {
if dirs.offset == 0 {
dirs.offset = -1
- return pkg, "", true
+ return pkg, true
}
- return "", "", false
+ return "", false
}
if pkg == "" || token.IsExported(pkg) { // Upper case symbol cannot be a package name.
- return "", "", false
+ return "", false
}
pkg = path.Clean(pkg)
pkgSuffix := "/" + pkg
for {
d, ok := dirs.Next()
if !ok {
- return "", "", false
+ return "", false
}
if d.importPath == pkg || strings.HasSuffix(d.importPath, pkgSuffix) {
- return d.dir, d.importPath, true
+ return d.importPath, true
}
}
}
-var buildCtx = build.Default
-
// splitGopath splits $GOPATH into a list of roots.
func splitGopath() []string {
- return filepath.SplitList(buildCtx.GOPATH)
+ return filepath.SplitList(cfg.BuildContext.GOPATH)
}
diff --git a/src/cmd/go/internal/doc/doc_test.go b/src/cmd/go/internal/doc/doc_test.go
index 3eeaffc25b..cd931e33c0 100644
--- a/src/cmd/go/internal/doc/doc_test.go
+++ b/src/cmd/go/internal/doc/doc_test.go
@@ -7,7 +7,6 @@ package doc
import (
"bytes"
"flag"
- "go/build"
"internal/testenv"
"log"
"os"
@@ -16,18 +15,19 @@ import (
"runtime"
"strings"
"testing"
+
+ "cmd/go/internal/cfg"
)
func TestMain(m *testing.M) {
// Clear GOPATH so we don't access the user's own packages in the test.
- buildCtx.GOPATH = ""
+ cfg.BuildContext.GOPATH = ""
testGOPATH = true // force GOPATH mode; module test is in cmd/go/testdata/script/mod_doc.txt
// Set GOROOT in case runtime.GOROOT is wrong (for example, if the test was
// built with -trimpath). dirsInit would identify it using 'go env GOROOT',
// but we can't be sure that the 'go' in $PATH is the right one either.
- buildCtx.GOROOT = testenv.GOROOT(nil)
- build.Default.GOROOT = testenv.GOROOT(nil)
+ cfg.GOROOT = testenv.GOROOT(nil)
// Add $GOROOT/src/cmd/go/internal/doc/testdata explicitly so we can access its contents in the test.
// Normally testdata directories are ignored, but sending it to dirs.scan directly is
@@ -37,9 +37,9 @@ func TestMain(m *testing.M) {
panic(err)
}
dirsInit(
- Dir{importPath: "testdata", dir: testdataDir},
- Dir{importPath: "testdata/nested", dir: filepath.Join(testdataDir, "nested")},
- Dir{importPath: "testdata/nested/nested", dir: filepath.Join(testdataDir, "nested", "nested")})
+ Dir{importPath: "cmd/go/internal/doc/testdata", dir: testdataDir},
+ Dir{importPath: "cmd/go/internal/doc/testdata/nested", dir: filepath.Join(testdataDir, "nested")},
+ Dir{importPath: "cmd/go/internal/doc/testdata/nested/nested", dir: filepath.Join(testdataDir, "nested", "nested")})
os.Exit(m.Run())
}
@@ -1204,7 +1204,7 @@ func TestDotSlashLookup(t *testing.T) {
t.Skip("scanning file system takes too long")
}
maybeSkip(t)
- t.Chdir(filepath.Join(buildCtx.GOROOT, "src", "text"))
+ t.Chdir(filepath.Join(cfg.GOROOT, "src", "text"))
var b strings.Builder
var flagSet flag.FlagSet
diff --git a/src/cmd/go/internal/doc/mod.go b/src/cmd/go/internal/doc/mod.go
index a15ff29526..e139cc1208 100644
--- a/src/cmd/go/internal/doc/mod.go
+++ b/src/cmd/go/internal/doc/mod.go
@@ -8,7 +8,6 @@ import (
"context"
"debug/buildinfo"
"fmt"
- "go/build"
"os/exec"
"cmd/go/internal/load"
@@ -16,24 +15,19 @@ import (
)
// loadVersioned loads a package at a specific version.
-func loadVersioned(ctx context.Context, pkgPath, version string) (*build.Package, error) {
- loaderState := modload.NewState()
- loaderState.ForceUseModules = true
- loaderState.RootMode = modload.NoRoot
- modload.Init(loaderState)
-
+func loadVersioned(ctx context.Context, loader *modload.State, pkgPath, version string) (*load.Package, error) {
var opts load.PackageOpts
args := []string{
fmt.Sprintf("%s@%s", pkgPath, version),
}
- pkgs, err := load.PackagesAndErrorsOutsideModule(loaderState, ctx, opts, args)
+ pkgs, err := load.PackagesAndErrorsOutsideModule(loader, ctx, opts, args)
if err != nil {
return nil, err
}
if len(pkgs) != 1 {
return nil, fmt.Errorf("incorrect number of packages: want 1, got %d", len(pkgs))
}
- return pkgs[0].Internal.Build, nil
+ return pkgs[0], nil
}
// inferVersion checks if the argument matches a command on $PATH and returns its module path and version.
diff --git a/src/cmd/go/internal/doc/pkg.go b/src/cmd/go/internal/doc/pkg.go
index c531595f86..bd4a2f6e4f 100644
--- a/src/cmd/go/internal/doc/pkg.go
+++ b/src/cmd/go/internal/doc/pkg.go
@@ -9,7 +9,6 @@ import (
"bytes"
"fmt"
"go/ast"
- "go/build"
"go/doc"
"go/format"
"go/parser"
@@ -22,6 +21,9 @@ import (
"strings"
"unicode"
"unicode/utf8"
+
+ "cmd/go/internal/cfg"
+ "cmd/go/internal/load"
)
const (
@@ -36,7 +38,7 @@ type Package struct {
pkg *ast.Package // Parsed package.
file *ast.File // Merged from all files in the package
doc *doc.Package
- build *build.Package
+ build *load.Package
typedValue map[*doc.Value]bool // Consts and vars related to types.
constructor map[*doc.Func]bool // Constructors.
fs *token.FileSet // Needed for printing.
@@ -96,8 +98,8 @@ func (pkg *Package) prettyPath() string {
// Also convert everything to slash-separated paths for uniform handling.
path = filepath.Clean(filepath.ToSlash(pkg.build.Dir))
// Can we find a decent prefix?
- if buildCtx.GOROOT != "" {
- goroot := filepath.Join(buildCtx.GOROOT, "src")
+ if cfg.GOROOT != "" {
+ goroot := filepath.Join(cfg.GOROOT, "src")
if p, ok := trim(path, filepath.ToSlash(goroot)); ok {
return p
}
@@ -137,7 +139,7 @@ func (pkg *Package) Fatalf(format string, args ...any) {
// parsePackage turns the build package we found into a parsed package
// we can then use to generate documentation.
-func parsePackage(writer io.Writer, pkg *build.Package, userPath string) *Package {
+func parsePackage(writer io.Writer, pkg *load.Package, userPath string) *Package {
// include tells parser.ParseDir which files to include.
// That means the file must be in the build package's GoFiles, CgoFiles,
// TestGoFiles or XTestGoFiles list only (no tag-ignored files, swig or
diff --git a/src/cmd/go/internal/doc/pkgsite.go b/src/cmd/go/internal/doc/pkgsite.go
index dc344cbbca..2c135cdc34 100644
--- a/src/cmd/go/internal/doc/pkgsite.go
+++ b/src/cmd/go/internal/doc/pkgsite.go
@@ -16,6 +16,8 @@ import (
"os/signal"
"path/filepath"
"strings"
+
+ "cmd/go/internal/cfg"
)
// pickUnusedPort finds an unused port by trying to listen on port 0
@@ -48,6 +50,10 @@ func doPkgsite(urlPath, fragment string) error {
path += "#" + fragment
}
+ if file := os.Getenv("TEST_GODOC_URL_FILE"); file != "" {
+ return os.WriteFile(file, []byte(path+"\n"), 0666)
+ }
+
// Turn off the default signal handler for SIGINT (and SIGQUIT on Unix)
// and instead wait for the child process to handle the signal and
// exit before exiting ourselves.
@@ -73,7 +79,7 @@ func doPkgsite(urlPath, fragment string) error {
const version = "v0.0.0-20251223195805-1a3bd3c788fe"
cmd := exec.Command(goCmd(), "run", "golang.org/x/pkgsite/cmd/internal/doc@"+version,
- "-gorepo", buildCtx.GOROOT,
+ "-gorepo", cfg.GOROOT,
"-http", addr,
"-open", path)
cmd.Env = env
diff --git a/src/cmd/go/internal/modload/init.go b/src/cmd/go/internal/modload/init.go
index 8501d3f5ee..e71e467d9f 100644
--- a/src/cmd/go/internal/modload/init.go
+++ b/src/cmd/go/internal/modload/init.go
@@ -467,6 +467,11 @@ func NewState() *State {
return s
}
+func DisabledState() *State {
+ fips140.Init()
+ return &State{initialized: true, modulesEnabled: false}
+}
+
func (s *State) Fetcher() *modfetch.Fetcher {
return s.fetcher
}
diff --git a/src/cmd/go/testdata/script/doc_http_url.txt b/src/cmd/go/testdata/script/doc_http_url.txt
new file mode 100644
index 0000000000..19947dd770
--- /dev/null
+++ b/src/cmd/go/testdata/script/doc_http_url.txt
@@ -0,0 +1,31 @@
+env TEST_GODOC_URL_FILE=$WORK/url.txt
+
+# Outside of a module.
+go doc -http
+grep '/std' $TEST_GODOC_URL_FILE
+
+# Inside a module with a major version suffix.
+# We should use the major version suffix rather than
+# the location in the gopath (#75976).
+cd example.com/m
+go doc -http
+grep '/example.com/m/v5' $TEST_GODOC_URL_FILE
+go doc -http .
+grep '/example.com/m/v5' $TEST_GODOC_URL_FILE
+
+# In GOPATH mode, we should use the location in the GOPATH.
+env GO111MODULE=off
+go doc -http
+grep '/std' $TEST_GODOC_URL_FILE
+# TODO(matloob): This should probably this be the same as 'go doc -http .'
+! grep '/example.com/m' $TEST_GODOC_URL_FILE
+go doc -http .
+grep '/example.com/m' $TEST_GODOC_URL_FILE
+! grep '/example.com/m/v5' $TEST_GODOC_URL_FILE
+
+-- example.com/m/go.mod --
+module example.com/m/v5
+
+go 1.27
+-- example.com/m/m.go --
+package m \ No newline at end of file
diff --git a/src/cmd/go/testdata/script/mod_doc.txt b/src/cmd/go/testdata/script/mod_doc.txt
index bf0a19d770..cbfd9336dd 100644
--- a/src/cmd/go/testdata/script/mod_doc.txt
+++ b/src/cmd/go/testdata/script/mod_doc.txt
@@ -38,9 +38,8 @@ go doc rsc.io/quote
stdout 'Package quote collects pithy sayings.'
# Check that a sensible error message is printed when a package is not found.
-env GOPROXY=off
! go doc example.com/hello
-stderr '^doc: cannot find module providing package example.com/hello: module lookup disabled by GOPROXY=off$'
+stderr 'doc: no required module provides package example.com/hello; to add it:\n\s+go get example.com/hello'
# When in a module with a vendor directory, doc should use the vendored copies
# of the packages. 'std' and 'cmd' are convenient examples of such modules.
diff --git a/src/cmd/go/testdata/script/mod_outside.txt b/src/cmd/go/testdata/script/mod_outside.txt
index 7a0dc9f22f..18cc3d5a27 100644
--- a/src/cmd/go/testdata/script/mod_outside.txt
+++ b/src/cmd/go/testdata/script/mod_outside.txt
@@ -173,13 +173,15 @@ go build -n fmt
go build ./newgo/newgo.go
# 'go doc' without arguments implicitly operates on the current directory, and should fail.
-# TODO(golang.org/issue/32027): currently, it succeeds.
cd needmod
-go doc
+! go doc
+stderr '^doc: go: go.mod file not found in current directory or any parent directory; see ''go help modules''$'
cd ..
-# 'go doc' of a non-module directory should also succeed.
-go doc ./needmod
+# 'go doc' of a non-module directory should also fail.
+! go doc ./needmod
+stderr '^doc: go: go.mod file not found in current directory or any parent directory; see ''go help modules''$'
+
# 'go doc' should succeed for standard-library packages.
go doc fmt
diff --git a/src/cmd/internal/script/scripttest/readme.go b/src/cmd/internal/script/scripttest/readme.go
index af7397223f..8f333b06c2 100644
--- a/src/cmd/internal/script/scripttest/readme.go
+++ b/src/cmd/internal/script/scripttest/readme.go
@@ -37,6 +37,7 @@ func checkScriptReadme(t *testing.T, engine *script.Engine, env []string, script
doc := new(strings.Builder)
cmd := testenv.Command(t, gotool, "doc", "cmd/internal/script")
+ cmd.Dir = t.TempDir() // make sure the test is not running inside the std or cmd module of another GOROOT
cmd.Env = env
cmd.Stdout = doc
if err := cmd.Run(); err != nil {