Skip to content

Commit

Permalink
runtime/coverage: remove uses of //go:linkname
Browse files Browse the repository at this point in the history
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 <[email protected]>
LUCI-TryBot-Result: Go LUCI <[email protected]>
Reviewed-by: Than McIntosh <[email protected]>
Reviewed-by: Cherry Mui <[email protected]>
  • Loading branch information
rsc authored and gopherbot committed May 21, 2024
1 parent 647870b commit 180ea45
Show file tree
Hide file tree
Showing 20 changed files with 160 additions and 132 deletions.
42 changes: 4 additions & 38 deletions src/cmd/go/internal/load/test.go
Original file line number Diff line number Diff line change
Expand Up @@ -902,9 +902,6 @@ package main
import (
"os"
{{if .Cover}}
_ "unsafe"
{{end}}
{{if .TestMain}}
"reflect"
{{end}}
Expand Down Expand Up @@ -944,45 +941,14 @@ var examples = []testing.InternalExample{
}
func init() {
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
}
testdeps.CoverMode = {{printf "%q" .Cover.Mode}}
testdeps.Covered = {{printf "%q" .Covered}}
{{end}}
testdeps.ImportPath = {{.ImportPath | printf "%q"}}
}
func main() {
{{if .Cover}}
testing_registerCover2({{printf "%q" .Cover.Mode}}, coverTearDown, runtime_coverage_snapshot)
{{end}}
m := testing.MainStart(testdeps.TestDeps{}, tests, benchmarks, fuzzTargets, examples)
{{with .TestMain}}
{{.Package}}.{{.Name}}(m)
Expand Down
7 changes: 4 additions & 3 deletions src/go/build/deps_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -608,9 +608,6 @@ var depsRules = `
internal/godebug, math/rand, encoding/hex, crypto/sha256
< internal/fuzz;
internal/fuzz, internal/testlog, runtime/pprof, regexp
< testing/internal/testdeps;
OS, flag, testing, internal/cfg, internal/platform, internal/goroot
< internal/testenv;
Expand Down Expand Up @@ -691,8 +688,12 @@ var depsRules = `
internal/coverage/decodecounter, internal/coverage/decodemeta,
internal/coverage/encodecounter, internal/coverage/encodemeta,
internal/coverage/pods
< internal/coverage/cfile
< runtime/coverage;
internal/coverage/cfile, internal/fuzz, internal/testlog, runtime/pprof, regexp
< testing/internal/testdeps;
# Test-only packages can have anything they want
CGO, internal/syscall/unix < net/internal/cgotest;
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -2,7 +2,7 @@
// Use of this source code is governed by a BSD-style
// license that can be found in the LICENSE file.

package coverage
package cfile

import (
"fmt"
Expand All @@ -12,24 +12,15 @@ import (
"unsafe"
)

// 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).
// WriteMetaDir implements [runtime/coverage.WriteMetaDir].
func WriteMetaDir(dir string) error {
if !finalHashComputed {
return fmt.Errorf("error: no meta-data available (binary not built with -cover?)")
}
return emitMetaDataToDirectory(dir, getCovMetaList())
}

// 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).
// WriteMeta implements [runtime/coverage.WriteMeta].
func WriteMeta(w io.Writer) error {
if w == nil {
return fmt.Errorf("error: nil writer in WriteMeta")
Expand All @@ -41,26 +32,15 @@ func WriteMeta(w io.Writer) error {
return writeMetaData(w, ml, cmode, cgran, finalHash)
}

// 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.
// WriteCountersDir implements [runtime/coverage.WriteCountersDir].
func WriteCountersDir(dir string) error {
if cmode != coverage.CtrModeAtomic {
return fmt.Errorf("WriteCountersDir invoked for program built with -covermode=%s (please use -covermode=atomic)", cmode.String())
}
return emitCounterDataToDirectory(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.
// WriteCounters implements [runtime/coverage.WriteCounters].
func WriteCounters(w io.Writer) error {
if w == nil {
return fmt.Errorf("error: nil writer in WriteCounters")
Expand All @@ -85,12 +65,7 @@ func WriteCounters(w io.Writer) error {
return s.emitCounterDataToWriter(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).
// ClearCounters implements [runtime/coverage.ClearCounters].
func ClearCounters() error {
cl := getCovCounterList()
if len(cl) == 0 {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -2,7 +2,11 @@
// Use of this source code is governed by a BSD-style
// 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 (
"crypto/md5"
Expand All @@ -28,17 +32,20 @@ import (
// getCovMetaList returns a list of meta-data blobs registered
// for the currently executing instrumented program. It is defined in the
// runtime.
//go:linkname getCovMetaList
func getCovMetaList() []rtcov.CovMetaBlob

// getCovCounterList returns a list of counter-data blobs registered
// for the currently executing instrumented program. It is defined in the
// runtime.
//go:linkname getCovCounterList
func getCovCounterList() []rtcov.CovCounterBlob

// getCovPkgMap returns a map storing the remapped package IDs for
// hard-coded runtime packages (see internal/coverage/pkgid.go for
// more on why hard-coded package IDs are needed). This function
// is defined in the runtime.
//go:linkname getCovPkgMap
func getCovPkgMap() map[int]int

// emitState holds useful state information during the emit process.
Expand Down Expand Up @@ -574,16 +581,12 @@ func (s *emitState) emitCounterDataFile(finalHash [16]byte, w io.Writer) error {
return nil
}

// markProfileEmitted is injected to testmain via linkname.
//go:linkname markProfileEmitted

// markProfileEmitted signals the runtime/coverage machinery that
// MarkProfileEmitted signals the coverage machinery that
// coverage data output files have already been written out, and there
// is no need to take any additional action at exit time. This
// function is called (via linknamed reference) from the
// coverage-related boilerplate code in _testmain.go emitted for go
// unit tests.
func markProfileEmitted(val bool) {
// function is called from the coverage-related boilerplate code in _testmain.go
// emitted for go unit tests.
func MarkProfileEmitted(val bool) {
covProfileAlreadyEmitted = val
}

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -2,7 +2,7 @@
// Use of this source code is governed by a BSD-style
// license that can be found in the LICENSE file.

package coverage
package cfile

import (
"fmt"
Expand Down Expand Up @@ -484,7 +484,7 @@ func TestIssue56006EmitDataRaceCoverRunningGoroutine(t *testing.T) {
cmd.Dir = filepath.Join("testdata", "issue56006")
b, err := cmd.CombinedOutput()
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.
Expand All @@ -510,7 +510,7 @@ func TestIssue59563TruncatedCoverPkgAll(t *testing.T) {
cmd.Dir = filepath.Join("testdata", "issue59563")
b, err := cmd.CombinedOutput()
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)
Expand All @@ -530,7 +530,7 @@ func TestIssue59563TruncatedCoverPkgAll(t *testing.T) {
// We're only interested in the specific function "large" for
// the testcase being built. See the #59563 for details on why
// 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
}
nfound++
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -2,13 +2,13 @@
// Use of this source code is governed by a BSD-style
// license that can be found in the LICENSE file.

package coverage
package cfile

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
// 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
// ("go build -cover ..."), in which case we immediately try to write
Expand All @@ -20,12 +20,12 @@ import _ "unsafe"
// emitCounterData as exit hooks. In the normal case (e.g. regular "go
// test -cover" run) the testmain.go boilerplate will run at the end
// 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
// 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
// terminates.
func initHook(istest bool) {
func InitHook(istest bool) {
// Note: hooks are run in reverse registration order, so
// register the counter data hook before the meta-data hook
// (in the case where two hooks are needed).
Expand Down
File renamed without changes.
Original file line number Diff line number Diff line change
Expand Up @@ -2,7 +2,7 @@
// Use of this source code is governed by a BSD-style
// license that can be found in the LICENSE file.

package coverage
package cfile

import (
"encoding/json"
Expand All @@ -22,20 +22,11 @@ import (
"unsafe"
)

// processCoverTestDir is injected in testmain.
//go:linkname processCoverTestDir

// processCoverTestDir is called (via a linknamed reference) from
// ProcessCoverTestDir is called from
// testmain code when "go test -cover" is in effect. It is not
// intended to be used other than internally by the Go command's
// generated code.
func processCoverTestDir(dir string, cfile string, cm string, cpkg string) 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 {
func ProcessCoverTestDir(dir string, cfile string, cm string, cpkg string, w io.Writer) error {
cmode := coverage.ParseCounterMode(cm)
if cmode == coverage.CtrModeInvalid {
return fmt.Errorf("invalid counter mode %q", cm)
Expand Down Expand Up @@ -280,16 +271,13 @@ func (ts *tstate) readAuxMetaFiles(metafiles string, importpaths map[string]stru
return nil
}

// snapshot is injected in testmain.
//go:linkname snapshot

// snapshot returns a snapshot of coverage percentage at a moment of
// Snapshot returns a snapshot of coverage percentage at a moment of
// time within a running test, so as to support the testing.Coverage()
// function. This version doesn't examine coverage meta-data, so 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
// are associated with each counter.
func snapshot() float64 {
func Snapshot() float64 {
cl := getCovCounterList()
if len(cl) == 0 {
// no work to do here.
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -2,7 +2,7 @@
// Use of this source code is governed by a BSD-style
// license that can be found in the LICENSE file.

package coverage
package cfile

import (
"encoding/json"
Expand All @@ -29,7 +29,7 @@ func testGoCoverDir(t *testing.T) string {
}

// 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
// running "go test -cover" for this package).
func TestTestSupport(t *testing.T) {
Expand All @@ -45,7 +45,7 @@ func TestTestSupport(t *testing.T) {

textfile := filepath.Join(t.TempDir(), "file.txt")
var sb strings.Builder
err := processCoverTestDirInternal(tgcd, textfile,
err := ProcessCoverTestDir(tgcd, textfile,
testing.CoverMode(), "", &sb)
if err != nil {
t.Fatalf("bad: %v", err)
Expand Down Expand Up @@ -91,9 +91,9 @@ func thisFunctionOnlyCalledFromSnapshotTest(n int) int {
// coverage is not enabled, the hook is designed to just return
// zero.
func TestCoverageSnapshot(t *testing.T) {
C1 := snapshot()
C1 := Snapshot()
thisFunctionOnlyCalledFromSnapshotTest(15)
C2 := snapshot()
C2 := Snapshot()
cond := "C1 > C2"
val := C1 > C2
if testing.CoverMode() != "" {
Expand Down Expand Up @@ -185,7 +185,7 @@ func TestAuxMetaDataFiles(t *testing.T) {
// Kick off guts of test.
var sb strings.Builder
textfile := filepath.Join(td, "file2.txt")
err = processCoverTestDirInternal(tgcd, textfile,
err = ProcessCoverTestDir(tgcd, textfile,
testing.CoverMode(), "", &sb)
if err != nil {
t.Fatalf("bad: %v", err)
Expand Down
Loading

0 comments on commit 180ea45

Please sign in to comment.