runtime/coverage: remove uses of //go:linkname

Move code to internal/coverage/cfile, making it possible to
access directly from testing/internal/testdeps, so that we can
avoid needing //go:linkname hacks.

For #67401.

Change-Id: I10b23a9970164afd2165e718ef3b2d9e86783883
Reviewed-on: https://go-review.googlesource.com/c/go/+/585820
Auto-Submit: Russ Cox <rsc@golang.org>
LUCI-TryBot-Result: Go LUCI <golang-scoped@luci-project-accounts.iam.gserviceaccount.com>
Reviewed-by: Than McIntosh <thanm@google.com>
Reviewed-by: Cherry Mui <cherryyz@google.com>
This commit is contained in:
Russ Cox 2024-05-15 16:06:23 -04:00 committed by Gopher Robot
parent 647870becc
commit 180ea45566
20 changed files with 160 additions and 132 deletions

View file

@ -902,9 +902,6 @@ package main
import ( import (
"os" "os"
{{if .Cover}}
_ "unsafe"
{{end}}
{{if .TestMain}} {{if .TestMain}}
"reflect" "reflect"
{{end}} {{end}}
@ -944,45 +941,14 @@ var examples = []testing.InternalExample{
} }
func init() { func init() {
{{if .Cover}}
testdeps.CoverMode = {{printf "%q" .Cover.Mode}}
testdeps.Covered = {{printf "%q" .Covered}}
{{end}}
testdeps.ImportPath = {{.ImportPath | printf "%q"}} testdeps.ImportPath = {{.ImportPath | printf "%q"}}
} }
{{if .Cover}}
//go:linkname runtime_coverage_processCoverTestDir runtime/coverage.processCoverTestDir
func runtime_coverage_processCoverTestDir(dir string, cfile string, cmode string, cpkgs string) error
//go:linkname testing_registerCover2 testing.registerCover2
func testing_registerCover2(mode string, tearDown func(coverprofile string, gocoverdir string) (string, error), snapcov func() float64)
//go:linkname runtime_coverage_markProfileEmitted runtime/coverage.markProfileEmitted
func runtime_coverage_markProfileEmitted(val bool)
//go:linkname runtime_coverage_snapshot runtime/coverage.snapshot
func runtime_coverage_snapshot() float64
func coverTearDown(coverprofile string, gocoverdir string) (string, error) {
var err error
if gocoverdir == "" {
gocoverdir, err = os.MkdirTemp("", "gocoverdir")
if err != nil {
return "error setting GOCOVERDIR: bad os.MkdirTemp return", err
}
defer os.RemoveAll(gocoverdir)
}
runtime_coverage_markProfileEmitted(true)
cmode := {{printf "%q" .Cover.Mode}}
if err := runtime_coverage_processCoverTestDir(gocoverdir, coverprofile, cmode, {{printf "%q" .Covered}}); err != nil {
return "error generating coverage report", err
}
return "", nil
}
{{end}}
func main() { func main() {
{{if .Cover}}
testing_registerCover2({{printf "%q" .Cover.Mode}}, coverTearDown, runtime_coverage_snapshot)
{{end}}
m := testing.MainStart(testdeps.TestDeps{}, tests, benchmarks, fuzzTargets, examples) m := testing.MainStart(testdeps.TestDeps{}, tests, benchmarks, fuzzTargets, examples)
{{with .TestMain}} {{with .TestMain}}
{{.Package}}.{{.Name}}(m) {{.Package}}.{{.Name}}(m)

View file

@ -608,9 +608,6 @@ var depsRules = `
internal/godebug, math/rand, encoding/hex, crypto/sha256 internal/godebug, math/rand, encoding/hex, crypto/sha256
< internal/fuzz; < internal/fuzz;
internal/fuzz, internal/testlog, runtime/pprof, regexp
< testing/internal/testdeps;
OS, flag, testing, internal/cfg, internal/platform, internal/goroot OS, flag, testing, internal/cfg, internal/platform, internal/goroot
< internal/testenv; < internal/testenv;
@ -691,8 +688,12 @@ var depsRules = `
internal/coverage/decodecounter, internal/coverage/decodemeta, internal/coverage/decodecounter, internal/coverage/decodemeta,
internal/coverage/encodecounter, internal/coverage/encodemeta, internal/coverage/encodecounter, internal/coverage/encodemeta,
internal/coverage/pods internal/coverage/pods
< internal/coverage/cfile
< runtime/coverage; < runtime/coverage;
internal/coverage/cfile, internal/fuzz, internal/testlog, runtime/pprof, regexp
< testing/internal/testdeps;
# Test-only packages can have anything they want # Test-only packages can have anything they want
CGO, internal/syscall/unix < net/internal/cgotest; CGO, internal/syscall/unix < net/internal/cgotest;

View file

@ -2,7 +2,7 @@
// Use of this source code is governed by a BSD-style // Use of this source code is governed by a BSD-style
// license that can be found in the LICENSE file. // license that can be found in the LICENSE file.
package coverage package cfile
import ( import (
"fmt" "fmt"
@ -12,11 +12,7 @@ import (
"unsafe" "unsafe"
) )
// WriteMetaDir writes a coverage meta-data file for the currently // WriteMetaDir implements [runtime/coverage.WriteMetaDir].
// running program to the directory specified in 'dir'. An error will
// be returned if the operation can't be completed successfully (for
// example, if the currently running program was not built with
// "-cover", or if the directory does not exist).
func WriteMetaDir(dir string) error { func WriteMetaDir(dir string) error {
if !finalHashComputed { if !finalHashComputed {
return fmt.Errorf("error: no meta-data available (binary not built with -cover?)") return fmt.Errorf("error: no meta-data available (binary not built with -cover?)")
@ -24,12 +20,7 @@ func WriteMetaDir(dir string) error {
return emitMetaDataToDirectory(dir, getCovMetaList()) return emitMetaDataToDirectory(dir, getCovMetaList())
} }
// WriteMeta writes the meta-data content (the payload that would // WriteMeta implements [runtime/coverage.WriteMeta].
// normally be emitted to a meta-data file) for the currently running
// program to the writer 'w'. An error will be returned if the
// operation can't be completed successfully (for example, if the
// currently running program was not built with "-cover", or if a
// write fails).
func WriteMeta(w io.Writer) error { func WriteMeta(w io.Writer) error {
if w == nil { if w == nil {
return fmt.Errorf("error: nil writer in WriteMeta") return fmt.Errorf("error: nil writer in WriteMeta")
@ -41,13 +32,7 @@ func WriteMeta(w io.Writer) error {
return writeMetaData(w, ml, cmode, cgran, finalHash) return writeMetaData(w, ml, cmode, cgran, finalHash)
} }
// WriteCountersDir writes a coverage counter-data file for the // WriteCountersDir implements [runtime/coverage.WriteCountersDir].
// currently running program to the directory specified in 'dir'. An
// error will be returned if the operation can't be completed
// successfully (for example, if the currently running program was not
// built with "-cover", or if the directory does not exist). The
// counter data written will be a snapshot taken at the point of the
// call.
func WriteCountersDir(dir string) error { func WriteCountersDir(dir string) error {
if cmode != coverage.CtrModeAtomic { if cmode != coverage.CtrModeAtomic {
return fmt.Errorf("WriteCountersDir invoked for program built with -covermode=%s (please use -covermode=atomic)", cmode.String()) return fmt.Errorf("WriteCountersDir invoked for program built with -covermode=%s (please use -covermode=atomic)", cmode.String())
@ -55,12 +40,7 @@ func WriteCountersDir(dir string) error {
return emitCounterDataToDirectory(dir) return emitCounterDataToDirectory(dir)
} }
// WriteCounters writes coverage counter-data content for the // WriteCounters implements [runtime/coverage.WriteCounters].
// currently running program to the writer 'w'. An error will be
// returned if the operation can't be completed successfully (for
// example, if the currently running program was not built with
// "-cover", or if a write fails). The counter data written will be a
// snapshot taken at the point of the invocation.
func WriteCounters(w io.Writer) error { func WriteCounters(w io.Writer) error {
if w == nil { if w == nil {
return fmt.Errorf("error: nil writer in WriteCounters") return fmt.Errorf("error: nil writer in WriteCounters")
@ -85,12 +65,7 @@ func WriteCounters(w io.Writer) error {
return s.emitCounterDataToWriter(w) return s.emitCounterDataToWriter(w)
} }
// ClearCounters clears/resets all coverage counter variables in the // ClearCounters implements [runtime/coverage.ClearCounters].
// currently running program. It returns an error if the program in
// question was not built with the "-cover" flag. Clearing of coverage
// counters is also not supported for programs not using atomic
// counter mode (see more detailed comments below for the rationale
// here).
func ClearCounters() error { func ClearCounters() error {
cl := getCovCounterList() cl := getCovCounterList()
if len(cl) == 0 { if len(cl) == 0 {

View file

@ -2,7 +2,11 @@
// Use of this source code is governed by a BSD-style // Use of this source code is governed by a BSD-style
// license that can be found in the LICENSE file. // license that can be found in the LICENSE file.
package coverage // Package cfile implements management of coverage files.
// It provides functionality exported in runtime/coverage as well as
// additional functionality used directly by package testing
// through testing/internal/testdeps.
package cfile
import ( import (
"crypto/md5" "crypto/md5"
@ -28,17 +32,20 @@ import (
// getCovMetaList returns a list of meta-data blobs registered // getCovMetaList returns a list of meta-data blobs registered
// for the currently executing instrumented program. It is defined in the // for the currently executing instrumented program. It is defined in the
// runtime. // runtime.
//go:linkname getCovMetaList
func getCovMetaList() []rtcov.CovMetaBlob func getCovMetaList() []rtcov.CovMetaBlob
// getCovCounterList returns a list of counter-data blobs registered // getCovCounterList returns a list of counter-data blobs registered
// for the currently executing instrumented program. It is defined in the // for the currently executing instrumented program. It is defined in the
// runtime. // runtime.
//go:linkname getCovCounterList
func getCovCounterList() []rtcov.CovCounterBlob func getCovCounterList() []rtcov.CovCounterBlob
// getCovPkgMap returns a map storing the remapped package IDs for // getCovPkgMap returns a map storing the remapped package IDs for
// hard-coded runtime packages (see internal/coverage/pkgid.go for // hard-coded runtime packages (see internal/coverage/pkgid.go for
// more on why hard-coded package IDs are needed). This function // more on why hard-coded package IDs are needed). This function
// is defined in the runtime. // is defined in the runtime.
//go:linkname getCovPkgMap
func getCovPkgMap() map[int]int func getCovPkgMap() map[int]int
// emitState holds useful state information during the emit process. // emitState holds useful state information during the emit process.
@ -574,16 +581,12 @@ func (s *emitState) emitCounterDataFile(finalHash [16]byte, w io.Writer) error {
return nil return nil
} }
// markProfileEmitted is injected to testmain via linkname. // MarkProfileEmitted signals the coverage machinery that
//go:linkname markProfileEmitted
// markProfileEmitted signals the runtime/coverage machinery that
// coverage data output files have already been written out, and there // coverage data output files have already been written out, and there
// is no need to take any additional action at exit time. This // is no need to take any additional action at exit time. This
// function is called (via linknamed reference) from the // function is called from the coverage-related boilerplate code in _testmain.go
// coverage-related boilerplate code in _testmain.go emitted for go // emitted for go unit tests.
// unit tests. func MarkProfileEmitted(val bool) {
func markProfileEmitted(val bool) {
covProfileAlreadyEmitted = val covProfileAlreadyEmitted = val
} }

View file

@ -2,7 +2,7 @@
// Use of this source code is governed by a BSD-style // Use of this source code is governed by a BSD-style
// license that can be found in the LICENSE file. // license that can be found in the LICENSE file.
package coverage package cfile
import ( import (
"fmt" "fmt"
@ -484,7 +484,7 @@ func TestIssue56006EmitDataRaceCoverRunningGoroutine(t *testing.T) {
cmd.Dir = filepath.Join("testdata", "issue56006") cmd.Dir = filepath.Join("testdata", "issue56006")
b, err := cmd.CombinedOutput() b, err := cmd.CombinedOutput()
if err != nil { if err != nil {
t.Fatalf("go test -cover -race failed: %v", err) t.Fatalf("go test -cover -race failed: %v\n%s", err, b)
} }
// Don't want to see any data races in output. // Don't want to see any data races in output.
@ -510,7 +510,7 @@ func TestIssue59563TruncatedCoverPkgAll(t *testing.T) {
cmd.Dir = filepath.Join("testdata", "issue59563") cmd.Dir = filepath.Join("testdata", "issue59563")
b, err := cmd.CombinedOutput() b, err := cmd.CombinedOutput()
if err != nil { if err != nil {
t.Fatalf("go test -cover failed: %v", err) t.Fatalf("go test -cover failed: %v\n%s", err, b)
} }
cmd = exec.Command(testenv.GoToolPath(t), "tool", "cover", "-func="+ppath) cmd = exec.Command(testenv.GoToolPath(t), "tool", "cover", "-func="+ppath)
@ -530,7 +530,7 @@ func TestIssue59563TruncatedCoverPkgAll(t *testing.T) {
// We're only interested in the specific function "large" for // We're only interested in the specific function "large" for
// the testcase being built. See the #59563 for details on why // the testcase being built. See the #59563 for details on why
// size matters. // size matters.
if !(strings.HasPrefix(f[0], "runtime/coverage/testdata/issue59563/repro.go") && strings.Contains(line, "large")) { if !(strings.HasPrefix(f[0], "internal/coverage/cfile/testdata/issue59563/repro.go") && strings.Contains(line, "large")) {
continue continue
} }
nfound++ nfound++

View file

@ -2,13 +2,13 @@
// Use of this source code is governed by a BSD-style // Use of this source code is governed by a BSD-style
// license that can be found in the LICENSE file. // license that can be found in the LICENSE file.
package coverage package cfile
import _ "unsafe" import _ "unsafe"
// initHook is invoked from the main package "init" routine in // InitHook is invoked from the main package "init" routine in
// programs built with "-cover". This function is intended to be // programs built with "-cover". This function is intended to be
// called only by the compiler. // called only by the compiler (via runtime/coverage.initHook).
// //
// If 'istest' is false, it indicates we're building a regular program // If 'istest' is false, it indicates we're building a regular program
// ("go build -cover ..."), in which case we immediately try to write // ("go build -cover ..."), in which case we immediately try to write
@ -20,12 +20,12 @@ import _ "unsafe"
// emitCounterData as exit hooks. In the normal case (e.g. regular "go // emitCounterData as exit hooks. In the normal case (e.g. regular "go
// test -cover" run) the testmain.go boilerplate will run at the end // test -cover" run) the testmain.go boilerplate will run at the end
// of the test, write out the coverage percentage, and then invoke // of the test, write out the coverage percentage, and then invoke
// markProfileEmitted() to indicate that no more work needs to be // MarkProfileEmitted to indicate that no more work needs to be
// done. If however that call is never made, this is a sign that the // done. If however that call is never made, this is a sign that the
// test binary is being used as a replacement binary for the tool // test binary is being used as a replacement binary for the tool
// being tested, hence we do want to run exit hooks when the program // being tested, hence we do want to run exit hooks when the program
// terminates. // terminates.
func initHook(istest bool) { func InitHook(istest bool) {
// Note: hooks are run in reverse registration order, so // Note: hooks are run in reverse registration order, so
// register the counter data hook before the meta-data hook // register the counter data hook before the meta-data hook
// (in the case where two hooks are needed). // (in the case where two hooks are needed).

View file

@ -2,7 +2,7 @@
// Use of this source code is governed by a BSD-style // Use of this source code is governed by a BSD-style
// license that can be found in the LICENSE file. // license that can be found in the LICENSE file.
package coverage package cfile
import ( import (
"encoding/json" "encoding/json"
@ -22,20 +22,11 @@ import (
"unsafe" "unsafe"
) )
// processCoverTestDir is injected in testmain. // ProcessCoverTestDir is called from
//go:linkname processCoverTestDir
// processCoverTestDir is called (via a linknamed reference) from
// testmain code when "go test -cover" is in effect. It is not // testmain code when "go test -cover" is in effect. It is not
// intended to be used other than internally by the Go command's // intended to be used other than internally by the Go command's
// generated code. // generated code.
func processCoverTestDir(dir string, cfile string, cm string, cpkg string) error { func ProcessCoverTestDir(dir string, cfile string, cm string, cpkg string, w io.Writer) error {
return processCoverTestDirInternal(dir, cfile, cm, cpkg, os.Stdout)
}
// processCoverTestDirInternal is an io.Writer version of processCoverTestDir,
// exposed for unit testing.
func processCoverTestDirInternal(dir string, cfile string, cm string, cpkg string, w io.Writer) error {
cmode := coverage.ParseCounterMode(cm) cmode := coverage.ParseCounterMode(cm)
if cmode == coverage.CtrModeInvalid { if cmode == coverage.CtrModeInvalid {
return fmt.Errorf("invalid counter mode %q", cm) return fmt.Errorf("invalid counter mode %q", cm)
@ -280,16 +271,13 @@ func (ts *tstate) readAuxMetaFiles(metafiles string, importpaths map[string]stru
return nil return nil
} }
// snapshot is injected in testmain. // Snapshot returns a snapshot of coverage percentage at a moment of
//go:linkname snapshot
// snapshot returns a snapshot of coverage percentage at a moment of
// time within a running test, so as to support the testing.Coverage() // time within a running test, so as to support the testing.Coverage()
// function. This version doesn't examine coverage meta-data, so the // function. This version doesn't examine coverage meta-data, so the
// result it returns will be less accurate (more "slop") due to the // result it returns will be less accurate (more "slop") due to the
// fact that we don't look at the meta data to see how many statements // fact that we don't look at the meta data to see how many statements
// are associated with each counter. // are associated with each counter.
func snapshot() float64 { func Snapshot() float64 {
cl := getCovCounterList() cl := getCovCounterList()
if len(cl) == 0 { if len(cl) == 0 {
// no work to do here. // no work to do here.

View file

@ -2,7 +2,7 @@
// Use of this source code is governed by a BSD-style // Use of this source code is governed by a BSD-style
// license that can be found in the LICENSE file. // license that can be found in the LICENSE file.
package coverage package cfile
import ( import (
"encoding/json" "encoding/json"
@ -29,7 +29,7 @@ func testGoCoverDir(t *testing.T) string {
} }
// TestTestSupport does a basic verification of the functionality in // TestTestSupport does a basic verification of the functionality in
// runtime/coverage.processCoverTestDir (doing this here as opposed to // ProcessCoverTestDir (doing this here as opposed to
// relying on other test paths will provide a better signal when // relying on other test paths will provide a better signal when
// running "go test -cover" for this package). // running "go test -cover" for this package).
func TestTestSupport(t *testing.T) { func TestTestSupport(t *testing.T) {
@ -45,7 +45,7 @@ func TestTestSupport(t *testing.T) {
textfile := filepath.Join(t.TempDir(), "file.txt") textfile := filepath.Join(t.TempDir(), "file.txt")
var sb strings.Builder var sb strings.Builder
err := processCoverTestDirInternal(tgcd, textfile, err := ProcessCoverTestDir(tgcd, textfile,
testing.CoverMode(), "", &sb) testing.CoverMode(), "", &sb)
if err != nil { if err != nil {
t.Fatalf("bad: %v", err) t.Fatalf("bad: %v", err)
@ -91,9 +91,9 @@ func thisFunctionOnlyCalledFromSnapshotTest(n int) int {
// coverage is not enabled, the hook is designed to just return // coverage is not enabled, the hook is designed to just return
// zero. // zero.
func TestCoverageSnapshot(t *testing.T) { func TestCoverageSnapshot(t *testing.T) {
C1 := snapshot() C1 := Snapshot()
thisFunctionOnlyCalledFromSnapshotTest(15) thisFunctionOnlyCalledFromSnapshotTest(15)
C2 := snapshot() C2 := Snapshot()
cond := "C1 > C2" cond := "C1 > C2"
val := C1 > C2 val := C1 > C2
if testing.CoverMode() != "" { if testing.CoverMode() != "" {
@ -185,7 +185,7 @@ func TestAuxMetaDataFiles(t *testing.T) {
// Kick off guts of test. // Kick off guts of test.
var sb strings.Builder var sb strings.Builder
textfile := filepath.Join(td, "file2.txt") textfile := filepath.Join(td, "file2.txt")
err = processCoverTestDirInternal(tgcd, textfile, err = ProcessCoverTestDir(tgcd, textfile,
testing.CoverMode(), "", &sb) testing.CoverMode(), "", &sb)
if err != nil { if err != nil {
t.Fatalf("bad: %v", err) t.Fatalf("bad: %v", err)

View file

@ -0,0 +1,66 @@
// Copyright 2022 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 coverage
import (
"internal/coverage/cfile"
"io"
)
// initHook is invoked from main.init in programs built with -cover.
// The call is emitted by the compiler.
func initHook(istest bool) {
cfile.InitHook(istest)
}
// WriteMetaDir writes a coverage meta-data file for the currently
// running program to the directory specified in 'dir'. An error will
// be returned if the operation can't be completed successfully (for
// example, if the currently running program was not built with
// "-cover", or if the directory does not exist).
func WriteMetaDir(dir string) error {
return cfile.WriteMetaDir(dir)
}
// WriteMeta writes the meta-data content (the payload that would
// normally be emitted to a meta-data file) for the currently running
// program to the writer 'w'. An error will be returned if the
// operation can't be completed successfully (for example, if the
// currently running program was not built with "-cover", or if a
// write fails).
func WriteMeta(w io.Writer) error {
return cfile.WriteMeta(w)
}
// WriteCountersDir writes a coverage counter-data file for the
// currently running program to the directory specified in 'dir'. An
// error will be returned if the operation can't be completed
// successfully (for example, if the currently running program was not
// built with "-cover", or if the directory does not exist). The
// counter data written will be a snapshot taken at the point of the
// call.
func WriteCountersDir(dir string) error {
return cfile.WriteCountersDir(dir)
}
// WriteCounters writes coverage counter-data content for the
// currently running program to the writer 'w'. An error will be
// returned if the operation can't be completed successfully (for
// example, if the currently running program was not built with
// "-cover", or if a write fails). The counter data written will be a
// snapshot taken at the point of the invocation.
func WriteCounters(w io.Writer) error {
return cfile.WriteCounters(w)
}
// ClearCounters clears/resets all coverage counter variables in the
// currently running program. It returns an error if the program in
// question was not built with the "-cover" flag. Clearing of coverage
// counters is also not supported for programs not using atomic
// counter mode (see more detailed comments below for the rationale
// here).
func ClearCounters() error {
return cfile.ClearCounters()
}

View file

@ -1,8 +0,0 @@
// Copyright 2022 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.
// The runtime package uses //go:linkname to push a few functions into this
// package but we still need a .s file so the Go tool does not pass -complete
// to 'go tool compile' so the latter does not complain about Go functions
// with no bodies.

View file

@ -9,8 +9,8 @@ import (
"unsafe" "unsafe"
) )
//go:linkname runtime_coverage_getCovCounterList runtime/coverage.getCovCounterList //go:linkname coverage_getCovCounterList internal/coverage/cfile.getCovCounterList
func runtime_coverage_getCovCounterList() []rtcov.CovCounterBlob { func coverage_getCovCounterList() []rtcov.CovCounterBlob {
res := []rtcov.CovCounterBlob{} res := []rtcov.CovCounterBlob{}
u32sz := unsafe.Sizeof(uint32(0)) u32sz := unsafe.Sizeof(uint32(0))
for datap := &firstmoduledata; datap != nil; datap = datap.next { for datap := &firstmoduledata; datap != nil; datap = datap.next {

View file

@ -61,12 +61,12 @@ func addCovMeta(p unsafe.Pointer, dlen uint32, hash [16]byte, pkpath string, pki
return uint32(slot + 1) return uint32(slot + 1)
} }
//go:linkname runtime_coverage_getCovMetaList runtime/coverage.getCovMetaList //go:linkname coverage_getCovMetaList internal/coverage/cfile.getCovMetaList
func runtime_coverage_getCovMetaList() []rtcov.CovMetaBlob { func coverage_getCovMetaList() []rtcov.CovMetaBlob {
return covMeta.metaList return covMeta.metaList
} }
//go:linkname runtime_coverage_getCovPkgMap runtime/coverage.getCovPkgMap //go:linkname coverage_getCovPkgMap internal/coverage/cfile.getCovPkgMap
func runtime_coverage_getCovPkgMap() map[int]int { func coverage_getCovPkgMap() map[int]int {
return covMeta.pkgMap return covMeta.pkgMap
} }

View file

@ -13,6 +13,7 @@ package testdeps
import ( import (
"bufio" "bufio"
"context" "context"
"internal/coverage/cfile"
"internal/fuzz" "internal/fuzz"
"internal/testlog" "internal/testlog"
"io" "io"
@ -26,6 +27,9 @@ import (
"time" "time"
) )
// Cover indicates whether coverage is enabled.
var Cover bool
// TestDeps is an implementation of the testing.testDeps interface, // TestDeps is an implementation of the testing.testDeps interface,
// suitable for passing to [testing.MainStart]. // suitable for passing to [testing.MainStart].
type TestDeps struct{} type TestDeps struct{}
@ -197,3 +201,30 @@ func (TestDeps) ResetCoverage() {
func (TestDeps) SnapshotCoverage() { func (TestDeps) SnapshotCoverage() {
fuzz.SnapshotCoverage() fuzz.SnapshotCoverage()
} }
var CoverMode string
var Covered string
func (TestDeps) InitRuntimeCoverage() (mode string, tearDown func(string, string) (string, error), snapcov func() float64) {
if CoverMode == "" {
return
}
return CoverMode, coverTearDown, cfile.Snapshot
}
func coverTearDown(coverprofile string, gocoverdir string) (string, error) {
var err error
if gocoverdir == "" {
gocoverdir, err = os.MkdirTemp("", "gocoverdir")
if err != nil {
return "error setting GOCOVERDIR: bad os.MkdirTemp return", err
}
defer os.RemoveAll(gocoverdir)
}
cfile.MarkProfileEmitted(true)
cmode := CoverMode
if err := cfile.ProcessCoverTestDir(gocoverdir, coverprofile, cmode, Covered, os.Stdout); err != nil {
return "error generating coverage report", err
}
return "", nil
}

View file

@ -21,13 +21,13 @@ var cover2 struct {
snapshotcov func() float64 snapshotcov func() float64
} }
// registerCover2 is injected in testmain. // registerCover2 is invoked during "go test -cover" runs.
//go:linkname registerCover2 // It is used to record a 'tear down' function
// registerCover2 is invoked during "go test -cover" runs by the test harness
// code in _testmain.go; it is used to record a 'tear down' function
// (to be called when the test is complete) and the coverage mode. // (to be called when the test is complete) and the coverage mode.
func registerCover2(mode string, tearDown func(coverprofile string, gocoverdir string) (string, error), snapcov func() float64) { func registerCover2(mode string, tearDown func(coverprofile string, gocoverdir string) (string, error), snapcov func() float64) {
if mode == "" {
return
}
cover2.mode = mode cover2.mode = mode
cover2.tearDown = tearDown cover2.tearDown = tearDown
cover2.snapshotcov = snapcov cover2.snapshotcov = snapcov

View file

@ -1855,6 +1855,10 @@ func (f matchStringOnly) CheckCorpus([]any, []reflect.Type) error { return nil }
func (f matchStringOnly) ResetCoverage() {} func (f matchStringOnly) ResetCoverage() {}
func (f matchStringOnly) SnapshotCoverage() {} func (f matchStringOnly) SnapshotCoverage() {}
func (f matchStringOnly) InitRuntimeCoverage() (mode string, tearDown func(string, string) (string, error), snapcov func() float64) {
return
}
// Main is an internal function, part of the implementation of the "go test" command. // Main is an internal function, part of the implementation of the "go test" command.
// It was exported because it is cross-package and predates "internal" packages. // It was exported because it is cross-package and predates "internal" packages.
// It is no longer used by "go test" but preserved, as much as possible, for other // It is no longer used by "go test" but preserved, as much as possible, for other
@ -1902,12 +1906,14 @@ type testDeps interface {
CheckCorpus([]any, []reflect.Type) error CheckCorpus([]any, []reflect.Type) error
ResetCoverage() ResetCoverage()
SnapshotCoverage() SnapshotCoverage()
InitRuntimeCoverage() (mode string, tearDown func(coverprofile string, gocoverdir string) (string, error), snapcov func() float64)
} }
// MainStart is meant for use by tests generated by 'go test'. // MainStart is meant for use by tests generated by 'go test'.
// It is not meant to be called directly and is not subject to the Go 1 compatibility document. // It is not meant to be called directly and is not subject to the Go 1 compatibility document.
// It may change signature from release to release. // It may change signature from release to release.
func MainStart(deps testDeps, tests []InternalTest, benchmarks []InternalBenchmark, fuzzTargets []InternalFuzzTarget, examples []InternalExample) *M { func MainStart(deps testDeps, tests []InternalTest, benchmarks []InternalBenchmark, fuzzTargets []InternalFuzzTarget, examples []InternalExample) *M {
registerCover2(deps.InitRuntimeCoverage())
Init() Init()
return &M{ return &M{
deps: deps, deps: deps,