src/cmd/cgo/internal/testshared/shared_test.go | 6 +++++- src/cmd/compile/internal/syntax/parser_test.go | 12 ++++++++++++ src/cmd/compile/internal/typecheck/expr.go | 7 ------- src/cmd/compile/internal/types2/stdlib_test.go | 8 ++++++++ src/cmd/distpack/archive.go | 12 +++++++++++- src/cmd/distpack/pack.go | 15 +++++++++++++++ src/cmd/distpack/test.go | 4 ++++ src/cmd/go/go_unix_test.go | 86 ++++++++++++++++++++++++++++++++++++++++++++++++++++- src/cmd/go/internal/list/list.go | 3 +++ src/cmd/go/internal/modfetch/coderepo.go | 2 +- src/cmd/go/internal/modfetch/sumdb.go | 24 +++++++++++++++++++++++- src/cmd/go/internal/modindex/read.go | 2 +- src/cmd/go/internal/test/test.go | 10 +++++++++- src/cmd/go/internal/toolchain/select.go | 93 +++++++++++++++++++++++++++++++++++++++++++++++++++++ src/cmd/go/internal/toolchain/umask_none.go | 13 +++++++++++++ src/cmd/go/internal/toolchain/umask_unix.go | 28 ++++++++++++++++++++++++++++ src/cmd/go/testdata/script/cover_list.txt | 39 ++++++++++++++++++++++++++++++++++++++- src/cmd/internal/moddeps/moddeps_test.go | 2 +- src/cmd/internal/testdir/testdir_test.go | 11 ++++++++++- src/compress/bzip2/bit_reader.go | 2 +- src/go/internal/gcimporter/gcimporter_test.go | 12 +++++++++++- src/go/types/stdlib_test.go | 8 ++++++++ src/internal/bisect/bisect.go | 30 ++++++++++++++++++++---------- src/math/dim.go | 6 ++++++ src/net/http/roundtrip_js.go | 33 ++++++++++++++++----------------- src/net/http/server.go | 4 +++- src/os/exec.go | 4 +++- src/os/exec_unix_test.go | 18 ++++++++++++++++++ src/path/filepath/path_test.go | 31 ++++++++++++++----------------- src/reflect/arena.go | 2 +- src/runtime/race/race_linux_test.go | 28 ++++++++++++++++++++++++++++ src/runtime/race_amd64.s | 2 +- src/runtime/race_arm64.s | 2 +- src/runtime/race_ppc64le.s | 2 +- src/runtime/trace/annotation.go | 8 ++++---- src/runtime/trace/trace.go | 14 +++++++------- src/slices/sort_benchmark_test.go | 28 ++++++++++++++++++++++++++++ src/testing/fstest/testfs.go | 2 +- src/testing/fstest/testfs_test.go | 38 ++++++++++++++++++++++++++++++++++++++ test/arenas/smoke.go | 65 +++++++++++++++++++++++++++++++++++++++++++++++++++++ test/fixedbugs/issue60601.go | 50 ++++++++++++++++++++++++++++++++++++++++++++++++++ diff --git a/src/cmd/cgo/internal/testshared/shared_test.go b/src/cmd/cgo/internal/testshared/shared_test.go index dc880dd2c7d33dded744fd24892a38612ce32bf6..796c46b9bf742caf8e6c39375a158fdcd6aca83a 100644 --- a/src/cmd/cgo/internal/testshared/shared_test.go +++ b/src/cmd/cgo/internal/testshared/shared_test.go @@ -731,6 +731,10 @@ // If gccgo is not available or not new enough, call t.Skip. func requireGccgo(t *testing.T) { t.Helper() + if runtime.GOARCH == "ppc64" || runtime.GOARCH == "ppc64le" { + t.Skip("gccgo test skipped on PPC64 until issue #60798 is resolved") + } + gccgoName := os.Getenv("GCCGO") if gccgoName == "" { gccgoName = "gccgo" @@ -748,7 +752,7 @@ dot := bytes.Index(output, []byte{'.'}) if dot > 0 { output = output[:dot] } - major, err := strconv.Atoi(string(output)) + major, err := strconv.Atoi(strings.TrimSpace(string(output))) if err != nil { t.Skipf("can't parse gccgo version number %s", output) } diff --git a/src/cmd/compile/internal/syntax/parser_test.go b/src/cmd/compile/internal/syntax/parser_test.go index 74583ca9036b75fb493550c61c5c6a2b759bc3a8..d5d4290f59bc309a5a8dd68e8bbdc557a3189a55 100644 --- a/src/cmd/compile/internal/syntax/parser_test.go +++ b/src/cmd/compile/internal/syntax/parser_test.go @@ -70,6 +70,18 @@ for _, dir := range []string{ filepath.Join(goroot, "src"), filepath.Join(goroot, "misc"), } { + if filepath.Base(dir) == "misc" { + // cmd/distpack deletes GOROOT/misc, so skip that directory if it isn't present. + // cmd/distpack also requires GOROOT/VERSION to exist, so use that to + // suppress false-positive skips. + if _, err := os.Stat(dir); os.IsNotExist(err) { + if _, err := os.Stat(filepath.Join(testenv.GOROOT(t), "VERSION")); err == nil { + fmt.Printf("%s not present; skipping\n", dir) + continue + } + } + } + walkDirs(t, dir, func(filename string) { if skipRx != nil && skipRx.MatchString(filename) { // Always report skipped files since regexp diff --git a/src/cmd/compile/internal/typecheck/expr.go b/src/cmd/compile/internal/typecheck/expr.go index 425724426abb073b39299e04dfdd6203eb01f730..2d25f80473e32f0657b16b1c6860cbed739b81b3 100644 --- a/src/cmd/compile/internal/typecheck/expr.go +++ b/src/cmd/compile/internal/typecheck/expr.go @@ -184,13 +184,6 @@ return l, r, nil } } - if (op == ir.ODIV || op == ir.OMOD) && ir.IsConst(r, constant.Int) { - if constant.Sign(r.Val()) == 0 { - base.Errorf("division by zero") - return l, r, nil - } - } - return l, r, t } diff --git a/src/cmd/compile/internal/types2/stdlib_test.go b/src/cmd/compile/internal/types2/stdlib_test.go index 9a03526b68ba4d17b1fbb5249ad0efe062eafcbc..ee852f5c4c839ae2048599505b184f4f6635d8c9 100644 --- a/src/cmd/compile/internal/types2/stdlib_test.go +++ b/src/cmd/compile/internal/types2/stdlib_test.go @@ -206,6 +206,14 @@ func testTestDir(t *testing.T, path string, ignore ...string) { files, err := os.ReadDir(path) if err != nil { + // cmd/distpack deletes GOROOT/test, so skip the test if it isn't present. + // cmd/distpack also requires GOROOT/VERSION to exist, so use that to + // suppress false-positive skips. + if _, err := os.Stat(filepath.Join(testenv.GOROOT(t), "test")); os.IsNotExist(err) { + if _, err := os.Stat(filepath.Join(testenv.GOROOT(t), "VERSION")); err == nil { + t.Skipf("skipping: GOROOT/test not present") + } + } t.Fatal(err) } diff --git a/src/cmd/distpack/archive.go b/src/cmd/distpack/archive.go index 730233765c22d13986140c3f9af1e439940bf0c0..f731b3792f8c9fc2c6ee1c9ac73b1986f69f7484 100644 --- a/src/cmd/distpack/archive.go +++ b/src/cmd/distpack/archive.go @@ -92,7 +92,7 @@ }) } // Sort sorts the files in the archive. -// It is only necessary to call Sort after calling Add. +// It is only necessary to call Sort after calling Add or RenameGoMod. // ArchiveDir returns a sorted archive, and the other methods // preserve the sorting of the archive. func (a *Archive) Sort() { @@ -161,6 +161,16 @@ // SetTime sets the modification time of all files in the archive to t. func (a *Archive) SetTime(t time.Time) { for i := range a.Files { a.Files[i].Time = t + } +} + +// RenameGoMod renames the go.mod files in the archive to _go.mod, +// for use with the module form, which cannot contain other go.mod files. +func (a *Archive) RenameGoMod() { + for i, f := range a.Files { + if strings.HasSuffix(f.Name, "/go.mod") { + a.Files[i].Name = strings.TrimSuffix(f.Name, "go.mod") + "_go.mod" + } } } diff --git a/src/cmd/distpack/pack.go b/src/cmd/distpack/pack.go index cddbd0747dd0256b24a5fd7fb13c6c33adf9ba2d..6867ac17c2abe998391fcb391ca6fdb47ca6475c 100644 --- a/src/cmd/distpack/pack.go +++ b/src/cmd/distpack/pack.go @@ -14,6 +14,17 @@ // Distpack is typically invoked by the -distpack flag to make.bash. // A cross-compiled distribution for goos/goarch can be built using: // // GOOS=goos GOARCH=goarch ./make.bash -distpack +// +// To test that the module downloads are usable with the go command: +// +// ./make.bash -distpack +// mkdir -p /tmp/goproxy/golang.org/toolchain/ +// ln -sf $(pwd)/../pkg/distpack /tmp/goproxy/golang.org/toolchain/@v +// GOPROXY=file:///tmp/goproxy GOTOOLCHAIN=$(sed 1q ../VERSION) gotip version +// +// gotip can be replaced with an older released Go version once there is one. +// It just can't be the one make.bash built, because it knows it is already that +// version and will skip the download. package main import ( @@ -199,6 +210,8 @@ "test/**", ) modVers := modVersionPrefix + "-" + version + "." + goosDashGoarch modArch.AddPrefix(modPath + "@" + modVers) + modArch.RenameGoMod() + modArch.Sort() testMod(modArch) // distpack returns the full path to name in the distpack directory. @@ -234,6 +247,8 @@ strings.HasSuffix(name, ".bash") || strings.HasSuffix(name, ".sh") || strings.HasSuffix(name, ".pl") || strings.HasSuffix(name, ".rc") { + return 0o755 + } else if ok, _ := amatch("**/go_?*_?*_exec", name); ok { return 0o755 } return 0o644 diff --git a/src/cmd/distpack/test.go b/src/cmd/distpack/test.go index 93c65645949b9b80e48f645d2e5f127ce748573c..4544d72d1f386f3a2d43d90a3695015c6fff303b 100644 --- a/src/cmd/distpack/test.go +++ b/src/cmd/distpack/test.go @@ -95,6 +95,10 @@ {name: "golang.org/toolchain@*/pkg/tool/*/compile", goos: "linux"}, {name: "golang.org/toolchain@*/pkg/tool/*/compile", goos: "darwin"}, {name: "golang.org/toolchain@*/pkg/tool/*/compile", goos: "windows", exclude: true}, {name: "golang.org/toolchain@*/pkg/tool/*/compile.exe", goos: "windows"}, + + // go.mod are renamed to _go.mod. + {name: "**/go.mod", exclude: true}, + {name: "**/_go.mod"}, } func testSrc(a *Archive) { diff --git a/src/cmd/go/go_unix_test.go b/src/cmd/go/go_unix_test.go index bab94944014afa330431c328ca22edce9d80ba31..d04e496778989b9fa757ddbdcb34e3a4008b95f6 100644 --- a/src/cmd/go/go_unix_test.go +++ b/src/cmd/go/go_unix_test.go @@ -2,12 +2,19 @@ // Copyright 2015 The Go Authors. All rights reserved. // Use of this source code is governed by a BSD-style // license that can be found in the LICENSE file. -//go:build darwin || dragonfly || freebsd || linux || netbsd || openbsd || solaris +//go:build unix package main_test import ( + "bufio" + "context" + "internal/testenv" + "io" "os" + "os/exec" + "slices" + "strings" "syscall" "testing" ) @@ -33,3 +40,80 @@ if mode := fi.Mode(); mode&0077 != 0 { t.Fatalf("wrote x with mode=%v, wanted no 0077 bits", mode) } } + +// TestTestInterrupt verifies the fix for issue #60203. +// +// If the whole process group for a 'go test' invocation receives +// SIGINT (as would be sent by pressing ^C on a console), +// it should return quickly, not deadlock. +func TestTestInterrupt(t *testing.T) { + if testing.Short() { + t.Skipf("skipping in short mode: test executes many subprocesses") + } + // Don't run this test in parallel, for the same reason. + + tg := testgo(t) + defer tg.cleanup() + tg.setenv("GOROOT", testGOROOT) + + ctx, cancel := context.WithCancel(context.Background()) + cmd := testenv.CommandContext(t, ctx, tg.goTool(), "test", "std", "-short", "-count=1") + cmd.Dir = tg.execDir + + // Override $TMPDIR when running the tests: since we're terminating the tests + // with a signal they might fail to clean up some temp files, and we don't + // want that to cause an "unexpected files" failure at the end of the run. + cmd.Env = append(slices.Clip(tg.env), tempEnvName()+"="+t.TempDir()) + + cmd.SysProcAttr = &syscall.SysProcAttr{ + Setpgid: true, + } + cmd.Cancel = func() error { + pgid := cmd.Process.Pid + return syscall.Kill(-pgid, syscall.SIGINT) + } + + pipe, err := cmd.StdoutPipe() + if err != nil { + t.Fatal(err) + } + + t.Logf("running %v", cmd) + if err := cmd.Start(); err != nil { + t.Fatal(err) + } + + stdout := new(strings.Builder) + r := bufio.NewReader(pipe) + line, err := r.ReadString('\n') + if err != nil { + t.Fatal(err) + } + stdout.WriteString(line) + + // The output line for some test was written, so we know things are in progress. + // + // Cancel the rest of the run by sending SIGINT to the process group: + // it should finish up and exit with a nonzero status, + // not have to be killed with SIGKILL. + cancel() + + io.Copy(stdout, r) + if stdout.Len() > 0 { + t.Logf("stdout:\n%s", stdout) + } + err = cmd.Wait() + + ee, _ := err.(*exec.ExitError) + if ee == nil { + t.Fatalf("unexpectedly finished with nonzero status") + } + if len(ee.Stderr) > 0 { + t.Logf("stderr:\n%s", ee.Stderr) + } + if !ee.Exited() { + t.Fatalf("'go test' did not exit after interrupt: %v", err) + } + + t.Logf("interrupted tests without deadlocking") +} diff --git a/src/cmd/go/internal/list/list.go b/src/cmd/go/internal/list/list.go index 1addadfea0103814236141af79d77b6fff8f395e..79120e6a99678576b7d9a4751cc3bb81d48b09c6 100644 --- a/src/cmd/go/internal/list/list.go +++ b/src/cmd/go/internal/list/list.go @@ -730,6 +730,9 @@ if len(p.GoFiles)+len(p.CgoFiles) > 0 { a.Deps = append(a.Deps, b.AutoAction(work.ModeInstall, work.ModeInstall, p)) } } + if cfg.Experiment.CoverageRedesign && cfg.BuildCover { + load.PrepareForCoverageBuild(pkgs) + } b.Do(ctx, a) } diff --git a/src/cmd/go/internal/modfetch/coderepo.go b/src/cmd/go/internal/modfetch/coderepo.go index 50f4bb2b37a27075215d446d9ce335ac6ecdd146..8fe432a9f50197d3638ffd1c0658d876fa0c04b8 100644 --- a/src/cmd/go/internal/modfetch/coderepo.go +++ b/src/cmd/go/internal/modfetch/coderepo.go @@ -1013,7 +1013,7 @@ f, err := modfile.ParseLax("go.mod", data, nil) if err != nil { return nil, err } - retractions := make([]modfile.VersionInterval, len(f.Retract)) + retractions := make([]modfile.VersionInterval, 0, len(f.Retract)) for _, r := range f.Retract { retractions = append(retractions, r.VersionInterval) } diff --git a/src/cmd/go/internal/modfetch/sumdb.go b/src/cmd/go/internal/modfetch/sumdb.go index 6e60e7d97642d5d861a2775da6cf15b351e21daf..ea7d561d7b9e0bd76a9808405bacbc2525fa3061 100644 --- a/src/cmd/go/internal/modfetch/sumdb.go +++ b/src/cmd/go/internal/modfetch/sumdb.go @@ -34,12 +34,34 @@ // useSumDB reports whether to use the Go checksum database for the given module. func useSumDB(mod module.Version) bool { if mod.Path == "golang.org/toolchain" { + must := true // Downloaded toolchains cannot be listed in go.sum, // so we require checksum database lookups even if // GOSUMDB=off or GONOSUMDB matches the pattern. // If GOSUMDB=off, then the eventual lookup will fail // with a good error message. - return true + + // Exception #1: using GOPROXY=file:// to test a distpack. + if strings.HasPrefix(cfg.GOPROXY, "file://") && !strings.ContainsAny(cfg.GOPROXY, ",|") { + must = false + } + // Exception #2: the Go proxy+checksum database cannot check itself + // while doing the initial download. + if strings.Contains(os.Getenv("GIT_HTTP_USER_AGENT"), "proxy.golang.org") { + must = false + } + + // Another potential exception would be GOPROXY=direct, + // but that would make toolchain downloads only as secure + // as HTTPS, and in particular they'd be susceptible to MITM + // attacks on systems with less-than-trustworthy root certificates. + // The checksum database provides a stronger guarantee, + // so we don't make that exception. + + // Otherwise, require the checksum database. + if must { + return true + } } return cfg.GOSUMDB != "off" && !module.MatchPrefixPatterns(cfg.GONOSUMDB, mod.Path) } diff --git a/src/cmd/go/internal/modindex/read.go b/src/cmd/go/internal/modindex/read.go index 238355732c7ba1b17e5756ce3166404d044c9e80..83d5faf28fbefafe201dc869e5be72a30a9c74f0 100644 --- a/src/cmd/go/internal/modindex/read.go +++ b/src/cmd/go/internal/modindex/read.go @@ -160,7 +160,7 @@ // usual cached locations. return nil, errNotFromModuleCache } modroot = filepath.Clean(modroot) - if !str.HasFilePathPrefix(modroot, cfg.GOMODCACHE) { + if str.HasFilePathPrefix(modroot, cfg.GOROOTsrc) || !str.HasFilePathPrefix(modroot, cfg.GOMODCACHE) { return nil, errNotFromModuleCache } return openIndexModule(modroot, true) diff --git a/src/cmd/go/internal/test/test.go b/src/cmd/go/internal/test/test.go index 2ce4c1a28e051fb72f77d79020a8917fe2204cd5..995da15c90e59b3b98dcb9edc4ff809036d50f22 100644 --- a/src/cmd/go/internal/test/test.go +++ b/src/cmd/go/internal/test/test.go @@ -1217,7 +1217,15 @@ } func (r *runTestActor) Act(b *work.Builder, ctx context.Context, a *work.Action) error { // Wait for previous test to get started and print its first json line. - <-r.prev + select { + case <-r.prev: + case <-base.Interrupted: + // We can't wait for the previous test action to complete: we don't start + // new actions after an interrupt, so if that action wasn't already running + // it might never happen. Instead, just don't log anything for this action. + base.SetExitStatus(1) + return nil + } if a.Failed { // We were unable to build the binary. diff --git a/src/cmd/go/internal/toolchain/select.go b/src/cmd/go/internal/toolchain/select.go index 8eac03b339835fbd783d364f2c4e1975fbe056c1..8b1a0b94be4d83e4d2a3cb103ab02f566afd2746 100644 --- a/src/cmd/go/internal/toolchain/select.go +++ b/src/cmd/go/internal/toolchain/select.go @@ -245,6 +245,8 @@ // a toolchain if necessary. func Exec(gotoolchain string) { log.SetPrefix("go: ") + writeBits = sysWriteBits() + count, _ := strconv.Atoi(os.Getenv(countEnv)) if count >= maxSwitch-10 { fmt.Fprintf(os.Stderr, "go: switching from go%v to %v [depth %d]\n", gover.Local(), gotoolchain, count) @@ -357,8 +359,99 @@ allowExec(filepath.Join(dir, "bin")) } } + srcUGoMod := filepath.Join(dir, "src/_go.mod") + srcGoMod := filepath.Join(dir, "src/go.mod") + if size(srcGoMod) != size(srcUGoMod) { + err := filepath.WalkDir(dir, func(path string, d fs.DirEntry, err error) error { + if err != nil { + return err + } + if path == srcUGoMod { + // Leave for last, in case we are racing with another go command. + return nil + } + if pdir, name := filepath.Split(path); name == "_go.mod" { + if err := raceSafeCopy(path, pdir+"go.mod"); err != nil { + return err + } + } + return nil + }) + // Handle src/go.mod; this is the signal to other racing go commands + // that everything is okay and they can skip this step. + if err == nil { + err = raceSafeCopy(srcUGoMod, srcGoMod) + } + if err != nil { + base.Fatalf("download %s: %v", gotoolchain, err) + } + } + // Reinvoke the go command. execGoToolchain(gotoolchain, dir, filepath.Join(dir, "bin/go")) +} + +func size(path string) int64 { + info, err := os.Stat(path) + if err != nil { + return -1 + } + return info.Size() +} + +var writeBits fs.FileMode + +// raceSafeCopy copies the file old to the file new, being careful to ensure +// that if multiple go commands call raceSafeCopy(old, new) at the same time, +// they don't interfere with each other: both will succeed and return and +// later observe the correct content in new. Like in the build cache, we arrange +// this by opening new without truncation and then writing the content. +// Both go commands can do this simultaneously and will write the same thing +// (old never changes content). +func raceSafeCopy(old, new string) error { + oldInfo, err := os.Stat(old) + if err != nil { + return err + } + newInfo, err := os.Stat(new) + if err == nil && newInfo.Size() == oldInfo.Size() { + return nil + } + data, err := os.ReadFile(old) + if err != nil { + return err + } + // The module cache has unwritable directories by default. + // Restore the user write bit in the directory so we can create + // the new go.mod file. We clear it again at the end on a + // best-effort basis (ignoring failures). + dir := filepath.Dir(old) + info, err := os.Stat(dir) + if err != nil { + return err + } + if err := os.Chmod(dir, info.Mode()|writeBits); err != nil { + return err + } + defer os.Chmod(dir, info.Mode()) + // Note: create the file writable, so that a racing go command + // doesn't get an error before we store the actual data. + f, err := os.OpenFile(new, os.O_CREATE|os.O_WRONLY, writeBits&^0o111) + if err != nil { + // If OpenFile failed because a racing go command completed our work + // (and then OpenFile failed because the directory or file is now read-only), + // count that as a success. + if size(old) == size(new) { + return nil + } + return err + } + defer os.Chmod(new, oldInfo.Mode()) + if _, err := f.Write(data); err != nil { + f.Close() + return err + } + return f.Close() } // modGoToolchain finds the enclosing go.work or go.mod file diff --git a/src/cmd/go/internal/toolchain/umask_none.go b/src/cmd/go/internal/toolchain/umask_none.go new file mode 100644 index 0000000000000000000000000000000000000000..b092fe8b7dd51bfd1f5d231f7bef90d94002ae13 --- /dev/null +++ b/src/cmd/go/internal/toolchain/umask_none.go @@ -0,0 +1,13 @@ +// Copyright 2023 The Go Authors. All rights reserved. +// Use of this source code is governed by a BSD-style +// license that can be found in the LICENSE file. + +//go:build !(darwin || freebsd || linux || netbsd || openbsd) + +package toolchain + +import "io/fs" + +func sysWriteBits() fs.FileMode { + return 0700 +} diff --git a/src/cmd/go/internal/toolchain/umask_unix.go b/src/cmd/go/internal/toolchain/umask_unix.go new file mode 100644 index 0000000000000000000000000000000000000000..cbe4307311f806a64f5c02d8cd7c5ce5c69b986f --- /dev/null +++ b/src/cmd/go/internal/toolchain/umask_unix.go @@ -0,0 +1,28 @@ +// Copyright 2023 The Go Authors. All rights reserved. +// Use of this source code is governed by a BSD-style +// license that can be found in the LICENSE file. + +//go:build darwin || freebsd || linux || netbsd || openbsd + +package toolchain + +import ( + "io/fs" + "syscall" +) + +// sysWriteBits determines which bits to OR into the mode to make a directory writable. +// It must be called when there are no other file system operations happening. +func sysWriteBits() fs.FileMode { + // Read current umask. There's no way to read it without also setting it, + // so set it conservatively and then restore the original one. + m := syscall.Umask(0o777) + syscall.Umask(m) // restore bits + if m&0o22 == 0o22 { // group and world are unwritable by default + return 0o700 + } + if m&0o2 == 0o2 { // group is writable by default, but not world + return 0o770 + } + return 0o777 // everything is writable by default +} diff --git a/src/cmd/go/testdata/script/cover_list.txt b/src/cmd/go/testdata/script/cover_list.txt index c66c08779301c67dde0c906f16179c2e9de038a9..6b8aaf45d1e123e7b7b3d7036d7bb4f6ccc79a87 100644 --- a/src/cmd/go/testdata/script/cover_list.txt +++ b/src/cmd/go/testdata/script/cover_list.txt @@ -16,6 +16,28 @@ # installed target should indeed be stale, since we didn't build it # with -cover. stale -cover m/example +# Collect build ID from for m/example built with -cover. +go list -cover -export -f '{{.BuildID}}' m/example +cp stdout $WORK/listbuildid.txt + +# Now build the m/example binary with coverage. +go build -cover -o $WORK/m.exe m/example + +# Ask for the binary build ID by running "go tool buildid". +go tool buildid $WORK/m.exe +cp stdout $WORK/rawtoolbuildid.txt + +# Make sure that the two build IDs agree with respect to the +# m/example package. Build IDs from binaries are of the form X/Y/Z/W +# where Y/Z is the package build ID; running the program below will +# pick out the parts of the ID that we want. +env GOCOVERDIR=$WORK +exec $WORK/m.exe $WORK/rawtoolbuildid.txt +cp stdout $WORK/toolbuildid.txt + +# Build IDs should match here. +cmp $WORK/toolbuildid.txt $WORK/listbuildid.txt + -- go.mod -- module m @@ -23,6 +45,21 @@ go 1.20 -- example/main.go -- package main +import ( + "fmt" + "os" + "strings" +) + func main() { - println("hi mom") + println(os.Args[1]) + content, err := os.ReadFile(os.Args[1]) + if err != nil { + os.Exit(1) + } + fields := strings.Split(strings.TrimSpace(string(content)), "/") + if len(fields) != 4 { + os.Exit(2) + } + fmt.Println(fields[1] + "/" + fields[2]) } diff --git a/src/cmd/internal/moddeps/moddeps_test.go b/src/cmd/internal/moddeps/moddeps_test.go index af3f99b801c8d2cb7b205c6ea592b9363e8fbbb0..ae890b66cb479cb848f0a12149df8c954f3caca5 100644 --- a/src/cmd/internal/moddeps/moddeps_test.go +++ b/src/cmd/internal/moddeps/moddeps_test.go @@ -504,7 +504,7 @@ // becomes a nuisance to update, can be replaced with len(goroot.modules) check. knownGOROOTModules := [...]string{ "std", "cmd", - "misc", + // The "misc" module sometimes exists, but cmd/distpack intentionally removes it. } var seen = make(map[string]bool) // Key is module path. for _, m := range goroot.modules { diff --git a/src/cmd/internal/testdir/testdir_test.go b/src/cmd/internal/testdir/testdir_test.go index f5bd44eef2d8b92cec2afd75650d41936e984a20..bd7785900c637a5a3926697b67c361fc6b14f035 100644 --- a/src/cmd/internal/testdir/testdir_test.go +++ b/src/cmd/internal/testdir/testdir_test.go @@ -64,7 +64,7 @@ goExperiment string // dirs are the directories to look for *.go files in. // TODO(bradfitz): just use all directories? - dirs = []string{".", "ken", "chan", "interface", "syntax", "dwarf", "fixedbugs", "codegen", "runtime", "abi", "typeparam", "typeparam/mdempsky"} + dirs = []string{".", "ken", "chan", "interface", "syntax", "dwarf", "fixedbugs", "codegen", "runtime", "abi", "typeparam", "typeparam/mdempsky", "arenas"} ) // Test is the main entrypoint that runs tests in the GOROOT/test directory. @@ -115,6 +115,15 @@ common := testCommon{ gorootTestDir: filepath.Join(testenv.GOROOT(t), "test"), runoutputGate: make(chan bool, *runoutputLimit), + } + + // cmd/distpack deletes GOROOT/test, so skip the test if it isn't present. + // cmd/distpack also requires GOROOT/VERSION to exist, so use that to + // suppress false-positive skips. + if _, err := os.Stat(common.gorootTestDir); os.IsNotExist(err) { + if _, err := os.Stat(filepath.Join(testenv.GOROOT(t), "VERSION")); err == nil { + t.Skipf("skipping: GOROOT/test not present") + } } for _, dir := range dirs { diff --git a/src/compress/bzip2/bit_reader.go b/src/compress/bzip2/bit_reader.go index ab1d60651436bd5a0b8118b46d5bb7f58c24869a..b4512654755bb231c187a9df2c83b3113e704ad4 100644 --- a/src/compress/bzip2/bit_reader.go +++ b/src/compress/bzip2/bit_reader.go @@ -60,7 +60,7 @@ // ^ ^ // |------------| // br.bits (num valid bits) // - // This the next line right shifts the desired bits into the + // The next line right shifts the desired bits into the // least-significant places and masks off anything above. n = (br.n >> (br.bits - bits)) & ((1 << bits) - 1) br.bits -= bits diff --git a/src/go/internal/gcimporter/gcimporter_test.go b/src/go/internal/gcimporter/gcimporter_test.go index 9ab29f3b1cb6d4ad24c78a0f506d4be2d3d8c769..25ff402277643417dbbe52b157129f440d09e8cf 100644 --- a/src/go/internal/gcimporter/gcimporter_test.go +++ b/src/go/internal/gcimporter/gcimporter_test.go @@ -137,6 +137,16 @@ if runtime.Compiler != "gc" { t.Skipf("gc-built packages not available (compiler = %s)", runtime.Compiler) } + // cmd/distpack removes the GOROOT/test directory, so skip if it isn't there. + // cmd/distpack also requires the presence of GOROOT/VERSION, so use that to + // avoid false-positive skips. + gorootTest := filepath.Join(testenv.GOROOT(t), "test") + if _, err := os.Stat(gorootTest); os.IsNotExist(err) { + if _, err := os.Stat(filepath.Join(testenv.GOROOT(t), "VERSION")); err == nil { + t.Skipf("skipping: GOROOT/test not present") + } + } + testenv.MustHaveGoBuild(t) tmpdir := mktmpdir(t) @@ -144,7 +154,7 @@ defer os.RemoveAll(tmpdir) // Check go files in test/typeparam, except those that fail for a known // reason. - rootDir := filepath.Join(testenv.GOROOT(t), "test", "typeparam") + rootDir := filepath.Join(gorootTest, "typeparam") list, err := os.ReadDir(rootDir) if err != nil { t.Fatal(err) diff --git a/src/go/types/stdlib_test.go b/src/go/types/stdlib_test.go index 770d3bf52abeb5f6093aea7fdd7eff2a09923d80..07c92225373ce63e37e3137262f7f9dd6b576085 100644 --- a/src/go/types/stdlib_test.go +++ b/src/go/types/stdlib_test.go @@ -209,6 +209,14 @@ func testTestDir(t *testing.T, path string, ignore ...string) { files, err := os.ReadDir(path) if err != nil { + // cmd/distpack deletes GOROOT/test, so skip the test if it isn't present. + // cmd/distpack also requires GOROOT/VERSION to exist, so use that to + // suppress false-positive skips. + if _, err := os.Stat(filepath.Join(testenv.GOROOT(t), "test")); os.IsNotExist(err) { + if _, err := os.Stat(filepath.Join(testenv.GOROOT(t), "VERSION")); err == nil { + t.Skipf("skipping: GOROOT/test not present") + } + } t.Fatal(err) } diff --git a/src/internal/bisect/bisect.go b/src/internal/bisect/bisect.go index 37f76a42718496a7d66f7bfbc18824229a06cbdc..48c796e54ac7340163a57e427f19ff8bf9398a05 100644 --- a/src/internal/bisect/bisect.go +++ b/src/internal/bisect/bisect.go @@ -198,10 +198,20 @@ } m := new(Matcher) - // Allow multiple v, so that “bisect cmd vPATTERN” can force verbose all the time. p := pattern + // Special case for leading 'q' so that 'qn' quietly disables, e.g. fmahash=qn to disable fma + // Any instance of 'v' disables 'q'. + if len(p) > 0 && p[0] == 'q' { + m.quiet = true + p = p[1:] + if p == "" { + return nil, &parseError{"invalid pattern syntax: " + pattern} + } + } + // Allow multiple v, so that “bisect cmd vPATTERN” can force verbose all the time. for len(p) > 0 && p[0] == 'v' { m.verbose = true + m.quiet = false p = p[1:] if p == "" { return nil, &parseError{"invalid pattern syntax: " + pattern} @@ -297,7 +307,8 @@ // A Matcher is the parsed, compiled form of a PATTERN string. // The nil *Matcher is valid: it has all changes enabled but none reported. type Matcher struct { - verbose bool + verbose bool // annotate reporting with human-helpful information + quiet bool // disables all reporting. reset if verbose is true. use case is -d=fmahash=qn enable bool // when true, list is for “enable and report” (when false, “disable and report”) list []cond // conditions; later ones win over earlier ones dedup atomicPointerDedup @@ -339,20 +350,19 @@ func (m *Matcher) ShouldEnable(id uint64) bool { if m == nil { return true } - for i := len(m.list) - 1; i >= 0; i-- { - c := &m.list[i] - if id&c.mask == c.bits { - return c.result == m.enable - } - } - return false == m.enable + return m.matchResult(id) == m.enable } // ShouldPrint reports whether to print identifying information about the change with the given id. func (m *Matcher) ShouldPrint(id uint64) bool { - if m == nil { + if m == nil || m.quiet { return false } + return m.matchResult(id) +} + +// matchResult returns the result from the first condition that matches id. +func (m *Matcher) matchResult(id uint64) bool { for i := len(m.list) - 1; i >= 0; i-- { c := &m.list[i] if id&c.mask == c.bits { diff --git a/src/math/dim.go b/src/math/dim.go index 6a286cdc75a46fe34206e347f984bc0ac2a17f05..f369f70f000fbb69823dfe987af6b6ac0191c756 100644 --- a/src/math/dim.go +++ b/src/math/dim.go @@ -34,6 +34,9 @@ // Max(x, +Inf) = Max(+Inf, x) = +Inf // Max(x, NaN) = Max(NaN, x) = NaN // Max(+0, ±0) = Max(±0, +0) = +0 // Max(-0, -0) = -0 +// +// Note that this differs from the built-in function max when called +// with NaN and +Inf. func Max(x, y float64) float64 { if haveArchMax { return archMax(x, y) @@ -67,6 +70,9 @@ // // Min(x, -Inf) = Min(-Inf, x) = -Inf // Min(x, NaN) = Min(NaN, x) = NaN // Min(-0, ±0) = Min(±0, -0) = -0 +// +// Note that this differs from the built-in function min when called +// with NaN and -Inf. func Min(x, y float64) float64 { if haveArchMin { return archMin(x, y) diff --git a/src/net/http/roundtrip_js.go b/src/net/http/roundtrip_js.go index f4d0b9d44cb3e3e9b6f8adaacd94c56af8bba94e..2826383ce1cd082ab86e144eac43b1c88ce87f0d 100644 --- a/src/net/http/roundtrip_js.go +++ b/src/net/http/roundtrip_js.go @@ -11,6 +11,7 @@ "errors" "fmt" "io" "strconv" + "strings" "syscall/js" ) @@ -44,11 +45,15 @@ // jsFetchMissing will be true if the Fetch API is not present in // the browser globals. var jsFetchMissing = js.Global().Get("fetch").IsUndefined() -// jsFetchDisabled will be true if the "process" global is present. -// We use this as an indicator that we're running in Node.js. We -// want to disable the Fetch API in Node.js because it breaks -// our wasm tests. See https://go.dev/issue/57613 for more information. -var jsFetchDisabled = !js.Global().Get("process").IsUndefined() +// jsFetchDisabled controls whether the use of Fetch API is disabled. +// It's set to true when we detect we're running in Node.js, so that +// RoundTrip ends up talking over the same fake network the HTTP servers +// currently use in various tests and examples. See go.dev/issue/57613. +// +// TODO(go.dev/issue/60810): See if it's viable to test the Fetch API +// code path. +var jsFetchDisabled = js.Global().Get("process").Type() == js.TypeObject && + strings.HasPrefix(js.Global().Get("process").Get("argv0").String(), "node") // Determine whether the JS runtime supports streaming request bodies. // Courtesy: https://developer.chrome.com/articles/fetch-streaming-requests/#feature-detection @@ -130,7 +135,6 @@ } } opt.Set("headers", headers) - var readableStreamStart, readableStreamPull, readableStreamCancel js.Func if req.Body != nil { if !supportsPostRequestStreams() { body, err := io.ReadAll(req.Body) @@ -138,6 +142,7 @@ if err != nil { req.Body.Close() // RoundTrip must always close the body, including on errors. return nil, err } + req.Body.Close() if len(body) != 0 { buf := uint8Array.New(len(body)) js.CopyBytesToJS(buf, body) @@ -148,7 +153,7 @@ readableStreamCtorArg := js.Global().Get("Object").New() readableStreamCtorArg.Set("type", "bytes") readableStreamCtorArg.Set("autoAllocateChunkSize", t.writeBufferSize()) - readableStreamPull = js.FuncOf(func(this js.Value, args []js.Value) any { + readableStreamPull := js.FuncOf(func(this js.Value, args []js.Value) any { controller := args[0] byobRequest := controller.Get("byobRequest") if byobRequest.IsNull() { @@ -176,6 +181,10 @@ } // Note: This a return from the pull callback of the controller and *not* RoundTrip(). return nil }) + defer func() { + readableStreamPull.Release() + req.Body.Close() + }() readableStreamCtorArg.Set("pull", readableStreamPull) opt.Set("body", js.Global().Get("ReadableStream").New(readableStreamCtorArg)) @@ -196,11 +205,6 @@ ) success = js.FuncOf(func(this js.Value, args []js.Value) any { success.Release() failure.Release() - readableStreamCancel.Release() - readableStreamPull.Release() - readableStreamStart.Release() - - req.Body.Close() result := args[0] header := Header{} @@ -265,11 +269,6 @@ }) failure = js.FuncOf(func(this js.Value, args []js.Value) any { success.Release() failure.Release() - readableStreamCancel.Release() - readableStreamPull.Release() - readableStreamStart.Release() - - req.Body.Close() err := args[0] // The error is a JS Error type diff --git a/src/net/http/server.go b/src/net/http/server.go index 680c5f68f425fa2a4e01af43d5180e3e3aa55389..8f63a9029931b1299d6380f0aa59309fd2c00677 100644 --- a/src/net/http/server.go +++ b/src/net/http/server.go @@ -1856,7 +1856,9 @@ } // Serve a new connection. func (c *conn) serve(ctx context.Context) { - c.remoteAddr = c.rwc.RemoteAddr().String() + if ra := c.rwc.RemoteAddr(); ra != nil { + c.remoteAddr = ra.String() + } ctx = context.WithValue(ctx, LocalAddrContextKey, c.rwc.LocalAddr()) var inFlightResponse *response defer func() { diff --git a/src/os/exec.go b/src/os/exec.go index d01ca592baefedbacb9bf8940272e3e57c29925a..ed5a75c4d13f87405dde7c123896d5a4bf03784a 100644 --- a/src/os/exec.go +++ b/src/os/exec.go @@ -86,7 +86,9 @@ // The Process it returns can be used to obtain information // about the underlying operating system process. // // On Unix systems, FindProcess always succeeds and returns a Process -// for the given pid, regardless of whether the process exists. +// for the given pid, regardless of whether the process exists. To test whether +// the process actually exists, see whether p.Signal(syscall.Signal(0)) reports +// an error. func FindProcess(pid int) (*Process, error) { return findProcess(pid) } diff --git a/src/os/exec_unix_test.go b/src/os/exec_unix_test.go index 82c072a746dc3c11dc3b6e6d331cf78bce06508e..26045192ff65531db928ade41fbbce7a0b11efb8 100644 --- a/src/os/exec_unix_test.go +++ b/src/os/exec_unix_test.go @@ -9,6 +9,7 @@ import ( "internal/testenv" . "os" + "syscall" "testing" ) @@ -25,3 +26,20 @@ if got := p.Signal(Kill); got != ErrProcessDone { t.Errorf("got %v want %v", got, ErrProcessDone) } } + +func TestUNIXProcessAlive(t *testing.T) { + testenv.MustHaveGoBuild(t) + t.Parallel() + + p, err := StartProcess(testenv.GoToolPath(t), []string{"sleep", "1"}, &ProcAttr{}) + if err != nil { + t.Skipf("starting test process: %v", err) + } + defer p.Kill() + + proc, _ := FindProcess(p.Pid) + err = proc.Signal(syscall.Signal(0)) + if err != nil { + t.Errorf("OS reported error for running process: %v", err) + } +} diff --git a/src/path/filepath/path_test.go b/src/path/filepath/path_test.go index 469a107d147acb8904cc18e52ba60664e9d23ee5..3c78e415d22766f47d21d0653ec308c83f9f820d 100644 --- a/src/path/filepath/path_test.go +++ b/src/path/filepath/path_test.go @@ -1622,36 +1622,33 @@ func TestBug3486(t *testing.T) { // https://golang.org/issue/3486 if runtime.GOOS == "ios" { t.Skipf("skipping on %s/%s", runtime.GOOS, runtime.GOARCH) } - root, err := filepath.EvalSymlinks(testenv.GOROOT(t) + "/test") - if err != nil { - t.Fatal(err) - } - bugs := filepath.Join(root, "fixedbugs") - ken := filepath.Join(root, "ken") - seenBugs := false - seenKen := false - err = filepath.Walk(root, func(pth string, info fs.FileInfo, err error) error { + root := filepath.Join(testenv.GOROOT(t), "src", "unicode") + utf16 := filepath.Join(root, "utf16") + utf8 := filepath.Join(root, "utf8") + seenUTF16 := false + seenUTF8 := false + err := filepath.Walk(root, func(pth string, info fs.FileInfo, err error) error { if err != nil { t.Fatal(err) } switch pth { - case bugs: - seenBugs = true + case utf16: + seenUTF16 = true return filepath.SkipDir - case ken: - if !seenBugs { - t.Fatal("filepath.Walk out of order - ken before fixedbugs") + case utf8: + if !seenUTF16 { + t.Fatal("filepath.Walk out of order - utf8 before utf16") } - seenKen = true + seenUTF8 = true } return nil }) if err != nil { t.Fatal(err) } - if !seenKen { - t.Fatalf("%q not seen", ken) + if !seenUTF8 { + t.Fatalf("%q not seen", utf8) } } diff --git a/src/reflect/arena.go b/src/reflect/arena.go index 694a3a136c46814e0f8c17032b67f9c39e38f7d7..cac1a1da5eaaeff243eea1d2f2894740dbc48410 100644 --- a/src/reflect/arena.go +++ b/src/reflect/arena.go @@ -12,7 +12,7 @@ // ArenaNew returns a Value representing a pointer to a new zero value for the // specified type, allocating storage for it in the provided arena. That is, // the returned Value's Type is PointerTo(typ). func ArenaNew(a *arena.Arena, typ Type) Value { - return ValueOf(arena_New(a, typ)) + return ValueOf(arena_New(a, PointerTo(typ))) } func arena_New(a *arena.Arena, typ any) any diff --git a/src/runtime/race/race_linux_test.go b/src/runtime/race/race_linux_test.go index e8a2d0fd8c91c44e0c9b3aa100b97280e21a02e8..947ed7ca39457612559466d7ac800ea90c8c3f7b 100644 --- a/src/runtime/race/race_linux_test.go +++ b/src/runtime/race/race_linux_test.go @@ -35,3 +35,31 @@ if *a != 2 { t.Fatalf("bad atomic value: %v, want 2", *a) } } + +func TestAtomicPageBoundary(t *testing.T) { + // Test that atomic access near (but not cross) a page boundary + // doesn't fault. See issue 60825. + + // Mmap two pages of memory, and make the second page inaccessible, + // so we have an address at the end of a page. + pagesize := syscall.Getpagesize() + b, err := syscall.Mmap(0, 0, 2*pagesize, syscall.PROT_READ|syscall.PROT_WRITE, syscall.MAP_ANON|syscall.MAP_PRIVATE) + if err != nil { + t.Fatalf("mmap failed %s", err) + } + defer syscall.Munmap(b) + err = syscall.Mprotect(b[pagesize:], syscall.PROT_NONE) + if err != nil { + t.Fatalf("mprotect high failed %s\n", err) + } + + // This should not fault. + a := (*uint32)(unsafe.Pointer(&b[pagesize-4])) + atomic.StoreUint32(a, 1) + if x := atomic.LoadUint32(a); x != 1 { + t.Fatalf("bad atomic value: %v, want 1", x) + } + if x := atomic.AddUint32(a, 1); x != 2 { + t.Fatalf("bad atomic value: %v, want 2", x) + } +} diff --git a/src/runtime/race_amd64.s b/src/runtime/race_amd64.s index 0697be7180bb8339916ca1b4abf2d9eb5729689c..4fa130e8613703275e2959937f5983c524a9904a 100644 --- a/src/runtime/race_amd64.s +++ b/src/runtime/race_amd64.s @@ -333,7 +333,7 @@ // AX already contains target function. TEXT racecallatomic<>(SB), NOSPLIT|NOFRAME, $0-0 // Trigger SIGSEGV early. MOVQ 16(SP), R12 - MOVL (R12), R13 + MOVBLZX (R12), R13 // Check that addr is within [arenastart, arenaend) or within [racedatastart, racedataend). CMPQ R12, runtime·racearenastart(SB) JB racecallatomic_data diff --git a/src/runtime/race_arm64.s b/src/runtime/race_arm64.s index edbb3b12c73423d4de21761783cba11e2c9e3aa2..c818345852f6b07bd5d9f4d3465ca30297495d1d 100644 --- a/src/runtime/race_arm64.s +++ b/src/runtime/race_arm64.s @@ -348,7 +348,7 @@ // R3 = addr of incoming arg list // Trigger SIGSEGV early. MOVD 40(RSP), R3 // 1st arg is addr. after two times BL, get it at 40(RSP) - MOVD (R3), R13 // segv here if addr is bad + MOVB (R3), R13 // segv here if addr is bad // Check that addr is within [arenastart, arenaend) or within [racedatastart, racedataend). MOVD runtime·racearenastart(SB), R10 CMP R10, R3 diff --git a/src/runtime/race_ppc64le.s b/src/runtime/race_ppc64le.s index 5fd4f785c8c8ce9f9d63250260cb1a985e52d390..39cfffc39bbeb7fd89321a60d5f73ec5245f6d41 100644 --- a/src/runtime/race_ppc64le.s +++ b/src/runtime/race_ppc64le.s @@ -363,7 +363,7 @@ // R8 contains addr of target function. TEXT racecallatomic<>(SB), NOSPLIT, $0-0 // Trigger SIGSEGV early if address passed to atomic function is bad. MOVD (R6), R7 // 1st arg is addr - MOVD (R7), R9 // segv here if addr is bad + MOVB (R7), R9 // segv here if addr is bad // Check that addr is within [arenastart, arenaend) or within [racedatastart, racedataend). MOVD runtime·racearenastart(SB), R9 CMP R7, R9 diff --git a/src/runtime/trace/annotation.go b/src/runtime/trace/annotation.go index f2ef0dd31df181cd72bb97dacca1db5b5deaa1a2..2666d1420171d772482309993ce96cc684aaac4a 100644 --- a/src/runtime/trace/annotation.go +++ b/src/runtime/trace/annotation.go @@ -21,7 +21,7 @@ // The taskType is used to classify task instances. Analysis tools // like the Go execution tracer may assume there are only a bounded // number of unique task types in the system. // -// The returned Task's End method is used to mark the task's end. +// The returned Task's [Task.End] method is used to mark the task's end. // The trace tool measures task latency as the time between task creation // and when the End method is called, and provides the latency // distribution per task type. @@ -75,7 +75,7 @@ id uint64 // TODO(hyangah): record parent id? } -// End marks the end of the operation represented by the Task. +// End marks the end of the operation represented by the [Task]. func (t *Task) End() { userTaskEnd(t.id) } @@ -97,7 +97,7 @@ id := fromContext(ctx).id userLog(id, category, message) } -// Logf is like Log, but the value is formatted using the specified format spec. +// Logf is like [Log], but the value is formatted using the specified format spec. func Logf(ctx context.Context, category, format string, args ...any) { if IsEnabled() { // Ideally this should be just Log, but that will @@ -142,7 +142,7 @@ fn() } // StartRegion starts a region and returns it. -// The returned Region's End method must be called +// The returned Region's [Region.End] method must be called // from the same goroutine where the region was started. // Within each goroutine, regions must nest. That is, regions started // after this region must be ended before this region can be ended. diff --git a/src/runtime/trace/trace.go b/src/runtime/trace/trace.go index 86c97e2a11d8e9e8e435f2ea7f277fcd31abef38..935d222f02ba010bd2d44d7059c0b485a2367aca 100644 --- a/src/runtime/trace/trace.go +++ b/src/runtime/trace/trace.go @@ -33,7 +33,7 @@ // to download a live trace: // // import _ "net/http/pprof" // -// See the net/http/pprof package for more details about all of the +// See the [net/http/pprof] package for more details about all of the // debug endpoints installed by this import. // // # User annotation @@ -44,11 +44,11 @@ // // There are three types of user annotations: log messages, regions, // and tasks. // -// Log emits a timestamped message to the execution trace along with +// [Log] emits a timestamped message to the execution trace along with // additional information such as the category of the message and -// which goroutine called Log. The execution tracer provides UIs to filter +// which goroutine called [Log]. The execution tracer provides UIs to filter // and group goroutines using the log category and the message supplied -// in Log. +// in [Log]. // // A region is for logging a time interval during a goroutine's execution. // By definition, a region starts and ends in the same goroutine. @@ -72,10 +72,10 @@ // A task is a higher-level component that aids tracing of logical // operations such as an RPC request, an HTTP request, or an // interesting local operation which may require multiple goroutines // working together. Since tasks can involve multiple goroutines, -// they are tracked via a context.Context object. NewTask creates -// a new task and embeds it in the returned context.Context object. +// they are tracked via a [context.Context] object. [NewTask] creates +// a new task and embeds it in the returned [context.Context] object. // Log messages and regions are attached to the task, if any, in the -// Context passed to Log and WithRegion. +// Context passed to [Log] and [WithRegion]. // // For example, assume that we decided to froth milk, extract coffee, // and mix milk and coffee in separate goroutines. With a task, diff --git a/src/slices/sort_benchmark_test.go b/src/slices/sort_benchmark_test.go index edf29994cf3443b3772179bf11795a57f5828256..0f088425949df31a4b70c3992f29469c546f4086 100644 --- a/src/slices/sort_benchmark_test.go +++ b/src/slices/sort_benchmark_test.go @@ -8,6 +8,7 @@ import ( "fmt" "math/rand" "sort" + "strconv" "strings" "testing" ) @@ -48,6 +49,15 @@ ints := makeRandomInts(N) b.StartTimer() sort.Ints(ints) } +} + +func makeSortedStrings(n int) []string { + x := make([]string, n) + for i := 0; i < n; i++ { + x[i] = strconv.Itoa(i) + } + Sort(x) + return x } func BenchmarkSlicesSortInts(b *testing.B) { @@ -153,11 +163,29 @@ sort.Strings(ss) } } +func BenchmarkSortStrings_Sorted(b *testing.B) { + ss := makeSortedStrings(N) + b.ResetTimer() + + for i := 0; i < b.N; i++ { + sort.Strings(ss) + } +} + func BenchmarkSlicesSortStrings(b *testing.B) { for i := 0; i < b.N; i++ { b.StopTimer() ss := makeRandomStrings(N) b.StartTimer() + Sort(ss) + } +} + +func BenchmarkSlicesSortStrings_Sorted(b *testing.B) { + ss := makeSortedStrings(N) + b.ResetTimer() + + for i := 0; i < b.N; i++ { Sort(ss) } } diff --git a/src/testing/fstest/testfs.go b/src/testing/fstest/testfs.go index ddb608088233ec7b8efd4e359388358f90afbfc2..78b0b82640d0bcb909d3e1c8eff72d67d9fed309 100644 --- a/src/testing/fstest/testfs.go +++ b/src/testing/fstest/testfs.go @@ -270,7 +270,7 @@ t.errorf("%s: fs.ReadDir: list not sorted: %s before %s", dir, list2[i].Name(), list2[i+1].Name()) } } - t.checkGlob(dir, list) + t.checkGlob(dir, list2) } // formatEntry formats an fs.DirEntry into a string for error messages and comparison. diff --git a/src/testing/fstest/testfs_test.go b/src/testing/fstest/testfs_test.go index aefb4b33617ab119e30d259517d37694defbe560..a48c597ff43e320f3cca7d83aec1c310a408f37e 100644 --- a/src/testing/fstest/testfs_test.go +++ b/src/testing/fstest/testfs_test.go @@ -6,8 +6,10 @@ package fstest import ( "internal/testenv" + "io/fs" "os" "path/filepath" + "sort" "testing" ) @@ -38,3 +40,39 @@ if err := TestFS(m, "a-b/a"); err != nil { t.Error(err) } } + +type shuffledFS MapFS + +func (fsys shuffledFS) Open(name string) (fs.File, error) { + f, err := MapFS(fsys).Open(name) + if err != nil { + return nil, err + } + return &shuffledFile{File: f}, nil +} + +type shuffledFile struct{ fs.File } + +func (f *shuffledFile) ReadDir(n int) ([]fs.DirEntry, error) { + dirents, err := f.File.(fs.ReadDirFile).ReadDir(n) + // Shuffle in a deterministic way, all we care about is making sure that the + // list of directory entries is not is the lexicographic order. + // + // We do this to make sure that the TestFS test suite is not affected by the + // order of directory entries. + sort.Slice(dirents, func(i, j int) bool { + return dirents[i].Name() > dirents[j].Name() + }) + return dirents, err +} + +func TestShuffledFS(t *testing.T) { + fsys := shuffledFS{ + "tmp/one": {Data: []byte("1")}, + "tmp/two": {Data: []byte("2")}, + "tmp/three": {Data: []byte("3")}, + } + if err := TestFS(fsys, "tmp/one", "tmp/two", "tmp/three"); err != nil { + t.Error(err) + } +} diff --git a/test/arenas/smoke.go b/test/arenas/smoke.go new file mode 100644 index 0000000000000000000000000000000000000000..56dad53fd088e89d404543bab6860e050be77298 --- /dev/null +++ b/test/arenas/smoke.go @@ -0,0 +1,65 @@ +// build -goexperiment arenas + +// Copyright 2023 The Go Authors. All rights reserved. +// Use of this source code is governed by a BSD-style +// license that can be found in the LICENSE file. + +package main + +import ( + "arena" + "log" + "reflect" +) + +func main() { + a := arena.NewArena() + defer a.Free() + + const iValue = 10 + + i := arena.New[int](a) + *i = iValue + + if *i != iValue { + // This test doesn't reasonably expect this to fail. It's more likely + // that *i crashes for some reason. Still, why not check it. + log.Fatalf("bad i value: got %d, want %d", *i, iValue) + } + + const wantLen = 125 + const wantCap = 1912 + + sl := arena.MakeSlice[*int](a, wantLen, wantCap) + if len(sl) != wantLen { + log.Fatalf("bad arena slice length: got %d, want %d", len(sl), wantLen) + } + if cap(sl) != wantCap { + log.Fatalf("bad arena slice capacity: got %d, want %d", cap(sl), wantCap) + } + sl = sl[:cap(sl)] + for j := range sl { + sl[j] = i + } + for j := range sl { + if *sl[j] != iValue { + // This test doesn't reasonably expect this to fail. It's more likely + // that sl[j] crashes for some reason. Still, why not check it. + log.Fatalf("bad sl[j] value: got %d, want %d", *sl[j], iValue) + } + } + + t := reflect.TypeOf(int(0)) + v := reflect.ArenaNew(a, t) + if want := reflect.PointerTo(t); v.Type() != want { + log.Fatalf("unexpected type for arena-allocated value: got %s, want %s", v.Type(), want) + } + i2 := v.Interface().(*int) + *i2 = iValue + + if *i2 != iValue { + // This test doesn't reasonably expect this to fail. It's more likely + // that *i crashes for some reason. Still, why not check it. + log.Fatalf("bad i2 value: got %d, want %d", *i2, iValue) + } +} diff --git a/test/fixedbugs/issue60601.go b/test/fixedbugs/issue60601.go new file mode 100644 index 0000000000000000000000000000000000000000..530898985721ebe0fc22191ff3ce1034678779cd --- /dev/null +++ b/test/fixedbugs/issue60601.go @@ -0,0 +1,50 @@ +// run + +// Copyright 2023 The Go Authors. All rights reserved. +// Use of this source code is governed by a BSD-style +// license that can be found in the LICENSE file. + +package main + +import ( + "strings" + "unsafe" +) + +func shift[T any]() int64 { + return 1 << unsafe.Sizeof(*new(T)) +} + +func div[T any]() uintptr { + return 1 / unsafe.Sizeof(*new(T)) +} + +func add[T any]() int64 { + return 1<<63 - 1 + int64(unsafe.Sizeof(*new(T))) +} + +func main() { + shift[[62]byte]() + shift[[63]byte]() + shift[[64]byte]() + shift[[100]byte]() + shift[[1e6]byte]() + + add[[1]byte]() + shouldPanic("divide by zero", func() { div[[0]byte]() }) +} + +func shouldPanic(str string, f func()) { + defer func() { + err := recover() + if err == nil { + panic("did not panic") + } + s := err.(error).Error() + if !strings.Contains(s, str) { + panic("got panic " + s + ", want " + str) + } + }() + + f() +}