Initial commit: Go 1.23 release state

This commit is contained in:
Vorapol Rinsatitnon
2024-09-21 23:49:08 +10:00
commit 17cd57a668
13231 changed files with 3114330 additions and 0 deletions

271
src/cmd/cover/cfg_test.go Normal file
View File

@@ -0,0 +1,271 @@
// 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 main_test
import (
"cmd/internal/cov/covcmd"
"encoding/json"
"fmt"
"internal/testenv"
"os"
"path/filepath"
"strings"
"testing"
)
func writeFile(t *testing.T, path string, contents []byte) {
if err := os.WriteFile(path, contents, 0666); err != nil {
t.Fatalf("os.WriteFile(%s) failed: %v", path, err)
}
}
func writePkgConfig(t *testing.T, outdir, tag, ppath, pname string, gran string, mpath string) string {
incfg := filepath.Join(outdir, tag+"incfg.txt")
outcfg := filepath.Join(outdir, "outcfg.txt")
p := covcmd.CoverPkgConfig{
PkgPath: ppath,
PkgName: pname,
Granularity: gran,
OutConfig: outcfg,
EmitMetaFile: mpath,
}
data, err := json.Marshal(p)
if err != nil {
t.Fatalf("json.Marshal failed: %v", err)
}
writeFile(t, incfg, data)
return incfg
}
func writeOutFileList(t *testing.T, infiles []string, outdir, tag string) ([]string, string) {
outfilelist := filepath.Join(outdir, tag+"outfilelist.txt")
var sb strings.Builder
cv := filepath.Join(outdir, "covervars.go")
outfs := []string{cv}
fmt.Fprintf(&sb, "%s\n", cv)
for _, inf := range infiles {
base := filepath.Base(inf)
of := filepath.Join(outdir, tag+".cov."+base)
outfs = append(outfs, of)
fmt.Fprintf(&sb, "%s\n", of)
}
if err := os.WriteFile(outfilelist, []byte(sb.String()), 0666); err != nil {
t.Fatalf("writing %s: %v", outfilelist, err)
}
return outfs, outfilelist
}
func runPkgCover(t *testing.T, outdir string, tag string, incfg string, mode string, infiles []string, errExpected bool) ([]string, string, string) {
// Write the pkgcfg file.
outcfg := filepath.Join(outdir, "outcfg.txt")
// Form up the arguments and run the tool.
outfiles, outfilelist := writeOutFileList(t, infiles, outdir, tag)
args := []string{"-pkgcfg", incfg, "-mode=" + mode, "-var=var" + tag, "-outfilelist", outfilelist}
args = append(args, infiles...)
cmd := testenv.Command(t, testcover(t), args...)
if errExpected {
errmsg := runExpectingError(cmd, t)
return nil, "", errmsg
} else {
run(cmd, t)
return outfiles, outcfg, ""
}
}
func TestCoverWithCfg(t *testing.T) {
testenv.MustHaveGoRun(t)
t.Parallel()
// Subdir in testdata that has our input files of interest.
tpath := filepath.Join("testdata", "pkgcfg")
dir := tempDir(t)
instdira := filepath.Join(dir, "insta")
if err := os.Mkdir(instdira, 0777); err != nil {
t.Fatal(err)
}
scenarios := []struct {
mode, gran string
}{
{
mode: "count",
gran: "perblock",
},
{
mode: "set",
gran: "perfunc",
},
{
mode: "regonly",
gran: "perblock",
},
}
var incfg string
apkgfiles := []string{filepath.Join(tpath, "a", "a.go")}
for _, scenario := range scenarios {
// Instrument package "a", producing a set of instrumented output
// files and an 'output config' file to pass on to the compiler.
ppath := "cfg/a"
pname := "a"
mode := scenario.mode
gran := scenario.gran
tag := mode + "_" + gran
incfg = writePkgConfig(t, instdira, tag, ppath, pname, gran, "")
ofs, outcfg, _ := runPkgCover(t, instdira, tag, incfg, mode,
apkgfiles, false)
t.Logf("outfiles: %+v\n", ofs)
// Run the compiler on the files to make sure the result is
// buildable.
bargs := []string{"tool", "compile", "-p", "a", "-coveragecfg", outcfg}
bargs = append(bargs, ofs...)
cmd := testenv.Command(t, testenv.GoToolPath(t), bargs...)
cmd.Dir = instdira
run(cmd, t)
}
// Do some error testing to ensure that various bad options and
// combinations are properly rejected.
// Expect error if config file inaccessible/unreadable.
mode := "atomic"
errExpected := true
tag := "errors"
_, _, errmsg := runPkgCover(t, instdira, tag, "/not/a/file", mode,
apkgfiles, errExpected)
want := "error reading pkgconfig file"
if !strings.Contains(errmsg, want) {
t.Errorf("'bad config file' test: wanted %s got %s", want, errmsg)
}
// Expect err if config file contains unknown stuff.
t.Logf("mangling in config")
writeFile(t, incfg, []byte("blah=foo\n"))
_, _, errmsg = runPkgCover(t, instdira, tag, incfg, mode,
apkgfiles, errExpected)
want = "error reading pkgconfig file"
if !strings.Contains(errmsg, want) {
t.Errorf("'bad config file' test: wanted %s got %s", want, errmsg)
}
// Expect error on empty config file.
t.Logf("writing empty config")
writeFile(t, incfg, []byte("\n"))
_, _, errmsg = runPkgCover(t, instdira, tag, incfg, mode,
apkgfiles, errExpected)
if !strings.Contains(errmsg, want) {
t.Errorf("'bad config file' test: wanted %s got %s", want, errmsg)
}
}
func TestCoverOnPackageWithNoTestFiles(t *testing.T) {
testenv.MustHaveGoRun(t)
// For packages with no test files, the new "go test -cover"
// strategy is to run cmd/cover on the package in a special
// "EmitMetaFile" mode. When running in this mode, cmd/cover walks
// the package doing instrumentation, but when finished, instead of
// writing out instrumented source files, it directly emits a
// meta-data file for the package in question, essentially
// simulating the effect that you would get if you added a dummy
// "no-op" x_test.go file and then did a build and run of the test.
t.Run("YesFuncsNoTests", func(t *testing.T) {
testCoverNoTestsYesFuncs(t)
})
t.Run("NoFuncsNoTests", func(t *testing.T) {
testCoverNoTestsNoFuncs(t)
})
}
func testCoverNoTestsYesFuncs(t *testing.T) {
t.Parallel()
dir := tempDir(t)
// Run the cover command with "emit meta" enabled on a package
// with functions but no test files.
tpath := filepath.Join("testdata", "pkgcfg")
pkg1files := []string{filepath.Join(tpath, "yesFuncsNoTests", "yfnt.go")}
ppath := "cfg/yesFuncsNoTests"
pname := "yesFuncsNoTests"
mode := "count"
gran := "perblock"
tag := mode + "_" + gran
instdir := filepath.Join(dir, "inst")
if err := os.Mkdir(instdir, 0777); err != nil {
t.Fatal(err)
}
mdir := filepath.Join(dir, "meta")
if err := os.Mkdir(mdir, 0777); err != nil {
t.Fatal(err)
}
mpath := filepath.Join(mdir, "covmeta.xxx")
incfg := writePkgConfig(t, instdir, tag, ppath, pname, gran, mpath)
_, _, errmsg := runPkgCover(t, instdir, tag, incfg, mode,
pkg1files, false)
if errmsg != "" {
t.Fatalf("runPkgCover err: %q", errmsg)
}
// Check for existence of meta-data file.
if inf, err := os.Open(mpath); err != nil {
t.Fatalf("meta-data file not created: %v", err)
} else {
inf.Close()
}
// Make sure it is digestible.
cdargs := []string{"tool", "covdata", "percent", "-i", mdir}
cmd := testenv.Command(t, testenv.GoToolPath(t), cdargs...)
run(cmd, t)
}
func testCoverNoTestsNoFuncs(t *testing.T) {
t.Parallel()
dir := tempDir(t)
// Run the cover command with "emit meta" enabled on a package
// with no functions and no test files.
tpath := filepath.Join("testdata", "pkgcfg")
pkgfiles := []string{filepath.Join(tpath, "noFuncsNoTests", "nfnt.go")}
pname := "noFuncsNoTests"
mode := "count"
gran := "perblock"
ppath := "cfg/" + pname
tag := mode + "_" + gran
instdir := filepath.Join(dir, "inst2")
if err := os.Mkdir(instdir, 0777); err != nil {
t.Fatal(err)
}
mdir := filepath.Join(dir, "meta2")
if err := os.Mkdir(mdir, 0777); err != nil {
t.Fatal(err)
}
mpath := filepath.Join(mdir, "covmeta.yyy")
incfg := writePkgConfig(t, instdir, tag, ppath, pname, gran, mpath)
_, _, errmsg := runPkgCover(t, instdir, tag, incfg, mode,
pkgfiles, false)
if errmsg != "" {
t.Fatalf("runPkgCover err: %q", errmsg)
}
// We expect to see an empty meta-data file in this case.
if inf, err := os.Open(mpath); err != nil {
t.Fatalf("opening meta-data file: error %v", err)
} else {
defer inf.Close()
fi, err := inf.Stat()
if err != nil {
t.Fatalf("stat meta-data file: %v", err)
}
if fi.Size() != 0 {
t.Fatalf("want zero-sized meta-data file got size %d",
fi.Size())
}
}
}

1214
src/cmd/cover/cover.go Normal file

File diff suppressed because it is too large Load Diff

649
src/cmd/cover/cover_test.go Normal file
View File

@@ -0,0 +1,649 @@
// Copyright 2013 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_test
import (
"bufio"
"bytes"
cmdcover "cmd/cover"
"flag"
"fmt"
"go/ast"
"go/parser"
"go/token"
"internal/testenv"
"log"
"os"
"os/exec"
"path/filepath"
"regexp"
"strings"
"sync"
"testing"
)
const (
// Data directory, also the package directory for the test.
testdata = "testdata"
)
// testcover returns the path to the cmd/cover binary that we are going to
// test. At one point this was created via "go build"; we now reuse the unit
// test executable itself.
func testcover(t testing.TB) string {
exe, err := os.Executable()
if err != nil {
t.Helper()
t.Fatal(err)
}
return exe
}
// testTempDir is a temporary directory created in TestMain.
var testTempDir string
// If set, this will preserve all the tmpdir files from the test run.
var debug = flag.Bool("debug", false, "keep tmpdir files for debugging")
// TestMain used here so that we can leverage the test executable
// itself as a cmd/cover executable; compare to similar usage in
// the cmd/go tests.
func TestMain(m *testing.M) {
if os.Getenv("CMDCOVER_TOOLEXEC") != "" {
// When CMDCOVER_TOOLEXEC is set, the test binary is also
// running as a -toolexec wrapper.
tool := strings.TrimSuffix(filepath.Base(os.Args[1]), ".exe")
if tool == "cover" {
// Inject this test binary as cmd/cover in place of the
// installed tool, so that the go command's invocations of
// cover produce coverage for the configuration in which
// the test was built.
os.Args = os.Args[1:]
cmdcover.Main()
} else {
cmd := exec.Command(os.Args[1], os.Args[2:]...)
cmd.Stdout = os.Stdout
cmd.Stderr = os.Stderr
if err := cmd.Run(); err != nil {
os.Exit(1)
}
}
os.Exit(0)
}
if os.Getenv("CMDCOVER_TEST_RUN_MAIN") != "" {
// When CMDCOVER_TEST_RUN_MAIN is set, we're reusing the test
// binary as cmd/cover. In this case we run the main func exported
// via export_test.go, and exit; CMDCOVER_TEST_RUN_MAIN is set below
// for actual test invocations.
cmdcover.Main()
os.Exit(0)
}
flag.Parse()
topTmpdir, err := os.MkdirTemp("", "cmd-cover-test-")
if err != nil {
log.Fatal(err)
}
testTempDir = topTmpdir
if !*debug {
defer os.RemoveAll(topTmpdir)
} else {
fmt.Fprintf(os.Stderr, "debug: preserving tmpdir %s\n", topTmpdir)
}
os.Setenv("CMDCOVER_TEST_RUN_MAIN", "normal")
os.Exit(m.Run())
}
var tdmu sync.Mutex
var tdcount int
func tempDir(t *testing.T) string {
tdmu.Lock()
dir := filepath.Join(testTempDir, fmt.Sprintf("%03d", tdcount))
tdcount++
if err := os.Mkdir(dir, 0777); err != nil {
t.Fatal(err)
}
defer tdmu.Unlock()
return dir
}
// TestCoverWithToolExec runs a set of subtests that all make use of a
// "-toolexec" wrapper program to invoke the cover test executable
// itself via "go test -cover".
func TestCoverWithToolExec(t *testing.T) {
testenv.MustHaveExec(t)
toolexecArg := "-toolexec=" + testcover(t)
t.Run("CoverHTML", func(t *testing.T) {
testCoverHTML(t, toolexecArg)
})
t.Run("HtmlUnformatted", func(t *testing.T) {
testHtmlUnformatted(t, toolexecArg)
})
t.Run("FuncWithDuplicateLines", func(t *testing.T) {
testFuncWithDuplicateLines(t, toolexecArg)
})
t.Run("MissingTrailingNewlineIssue58370", func(t *testing.T) {
testMissingTrailingNewlineIssue58370(t, toolexecArg)
})
}
// Execute this command sequence:
//
// replace the word LINE with the line number < testdata/test.go > testdata/test_line.go
// testcover -mode=count -var=CoverTest -o ./testdata/test_cover.go testdata/test_line.go
// go run ./testdata/main.go ./testdata/test.go
func TestCover(t *testing.T) {
testenv.MustHaveGoRun(t)
t.Parallel()
dir := tempDir(t)
// Read in the test file (testTest) and write it, with LINEs specified, to coverInput.
testTest := filepath.Join(testdata, "test.go")
file, err := os.ReadFile(testTest)
if err != nil {
t.Fatal(err)
}
lines := bytes.Split(file, []byte("\n"))
for i, line := range lines {
lines[i] = bytes.ReplaceAll(line, []byte("LINE"), []byte(fmt.Sprint(i+1)))
}
// Add a function that is not gofmt'ed. This used to cause a crash.
// We don't put it in test.go because then we would have to gofmt it.
// Issue 23927.
lines = append(lines, []byte("func unFormatted() {"),
[]byte("\tif true {"),
[]byte("\t}else{"),
[]byte("\t}"),
[]byte("}"))
lines = append(lines, []byte("func unFormatted2(b bool) {if b{}else{}}"))
coverInput := filepath.Join(dir, "test_line.go")
if err := os.WriteFile(coverInput, bytes.Join(lines, []byte("\n")), 0666); err != nil {
t.Fatal(err)
}
// testcover -mode=count -var=thisNameMustBeVeryLongToCauseOverflowOfCounterIncrementStatementOntoNextLineForTest -o ./testdata/test_cover.go testdata/test_line.go
coverOutput := filepath.Join(dir, "test_cover.go")
cmd := testenv.Command(t, testcover(t), "-mode=count", "-var=thisNameMustBeVeryLongToCauseOverflowOfCounterIncrementStatementOntoNextLineForTest", "-o", coverOutput, coverInput)
run(cmd, t)
cmd = testenv.Command(t, testcover(t), "-mode=set", "-var=Not_an-identifier", "-o", coverOutput, coverInput)
err = cmd.Run()
if err == nil {
t.Error("Expected cover to fail with an error")
}
// Copy testmain to tmpdir, so that it is in the same directory
// as coverOutput.
testMain := filepath.Join(testdata, "main.go")
b, err := os.ReadFile(testMain)
if err != nil {
t.Fatal(err)
}
tmpTestMain := filepath.Join(dir, "main.go")
if err := os.WriteFile(tmpTestMain, b, 0444); err != nil {
t.Fatal(err)
}
// go run ./testdata/main.go ./testdata/test.go
cmd = testenv.Command(t, testenv.GoToolPath(t), "run", tmpTestMain, coverOutput)
run(cmd, t)
file, err = os.ReadFile(coverOutput)
if err != nil {
t.Fatal(err)
}
// compiler directive must appear right next to function declaration.
if got, err := regexp.MatchString(".*\n//go:nosplit\nfunc someFunction().*", string(file)); err != nil || !got {
t.Error("misplaced compiler directive")
}
// "go:linkname" compiler directive should be present.
if got, err := regexp.MatchString(`.*go\:linkname some\_name some\_name.*`, string(file)); err != nil || !got {
t.Error("'go:linkname' compiler directive not found")
}
// Other comments should be preserved too.
c := ".*// This comment didn't appear in generated go code.*"
if got, err := regexp.MatchString(c, string(file)); err != nil || !got {
t.Errorf("non compiler directive comment %q not found", c)
}
}
// TestDirectives checks that compiler directives are preserved and positioned
// correctly. Directives that occur before top-level declarations should remain
// above those declarations, even if they are not part of the block of
// documentation comments.
func TestDirectives(t *testing.T) {
testenv.MustHaveExec(t)
t.Parallel()
// Read the source file and find all the directives. We'll keep
// track of whether each one has been seen in the output.
testDirectives := filepath.Join(testdata, "directives.go")
source, err := os.ReadFile(testDirectives)
if err != nil {
t.Fatal(err)
}
sourceDirectives := findDirectives(source)
// testcover -mode=atomic ./testdata/directives.go
cmd := testenv.Command(t, testcover(t), "-mode=atomic", testDirectives)
cmd.Stderr = os.Stderr
output, err := cmd.Output()
if err != nil {
t.Fatal(err)
}
// Check that all directives are present in the output.
outputDirectives := findDirectives(output)
foundDirective := make(map[string]bool)
for _, p := range sourceDirectives {
foundDirective[p.name] = false
}
for _, p := range outputDirectives {
if found, ok := foundDirective[p.name]; !ok {
t.Errorf("unexpected directive in output: %s", p.text)
} else if found {
t.Errorf("directive found multiple times in output: %s", p.text)
}
foundDirective[p.name] = true
}
for name, found := range foundDirective {
if !found {
t.Errorf("missing directive: %s", name)
}
}
// Check that directives that start with the name of top-level declarations
// come before the beginning of the named declaration and after the end
// of the previous declaration.
fset := token.NewFileSet()
astFile, err := parser.ParseFile(fset, testDirectives, output, 0)
if err != nil {
t.Fatal(err)
}
prevEnd := 0
for _, decl := range astFile.Decls {
var name string
switch d := decl.(type) {
case *ast.FuncDecl:
name = d.Name.Name
case *ast.GenDecl:
if len(d.Specs) == 0 {
// An empty group declaration. We still want to check that
// directives can be associated with it, so we make up a name
// to match directives in the test data.
name = "_empty"
} else if spec, ok := d.Specs[0].(*ast.TypeSpec); ok {
name = spec.Name.Name
}
}
pos := fset.Position(decl.Pos()).Offset
end := fset.Position(decl.End()).Offset
if name == "" {
prevEnd = end
continue
}
for _, p := range outputDirectives {
if !strings.HasPrefix(p.name, name) {
continue
}
if p.offset < prevEnd || pos < p.offset {
t.Errorf("directive %s does not appear before definition %s", p.text, name)
}
}
prevEnd = end
}
}
type directiveInfo struct {
text string // full text of the comment, not including newline
name string // text after //go:
offset int // byte offset of first slash in comment
}
func findDirectives(source []byte) []directiveInfo {
var directives []directiveInfo
directivePrefix := []byte("\n//go:")
offset := 0
for {
i := bytes.Index(source[offset:], directivePrefix)
if i < 0 {
break
}
i++ // skip newline
p := source[offset+i:]
j := bytes.IndexByte(p, '\n')
if j < 0 {
// reached EOF
j = len(p)
}
directive := directiveInfo{
text: string(p[:j]),
name: string(p[len(directivePrefix)-1 : j]),
offset: offset + i,
}
directives = append(directives, directive)
offset += i + j
}
return directives
}
// Makes sure that `cover -func=profile.cov` reports accurate coverage.
// Issue #20515.
func TestCoverFunc(t *testing.T) {
testenv.MustHaveExec(t)
// testcover -func ./testdata/profile.cov
coverProfile := filepath.Join(testdata, "profile.cov")
cmd := testenv.Command(t, testcover(t), "-func", coverProfile)
out, err := cmd.Output()
if err != nil {
if ee, ok := err.(*exec.ExitError); ok {
t.Logf("%s", ee.Stderr)
}
t.Fatal(err)
}
if got, err := regexp.Match(".*total:.*100.0.*", out); err != nil || !got {
t.Logf("%s", out)
t.Errorf("invalid coverage counts. got=(%v, %v); want=(true; nil)", got, err)
}
}
// Check that cover produces correct HTML.
// Issue #25767.
func testCoverHTML(t *testing.T, toolexecArg string) {
testenv.MustHaveGoRun(t)
dir := tempDir(t)
t.Parallel()
// go test -coverprofile testdata/html/html.cov cmd/cover/testdata/html
htmlProfile := filepath.Join(dir, "html.cov")
cmd := testenv.Command(t, testenv.GoToolPath(t), "test", toolexecArg, "-coverprofile", htmlProfile, "cmd/cover/testdata/html")
cmd.Env = append(cmd.Environ(), "CMDCOVER_TOOLEXEC=true")
run(cmd, t)
// testcover -html testdata/html/html.cov -o testdata/html/html.html
htmlHTML := filepath.Join(dir, "html.html")
cmd = testenv.Command(t, testcover(t), "-html", htmlProfile, "-o", htmlHTML)
run(cmd, t)
// Extract the parts of the HTML with comment markers,
// and compare against a golden file.
entireHTML, err := os.ReadFile(htmlHTML)
if err != nil {
t.Fatal(err)
}
var out strings.Builder
scan := bufio.NewScanner(bytes.NewReader(entireHTML))
in := false
for scan.Scan() {
line := scan.Text()
if strings.Contains(line, "// START") {
in = true
}
if in {
fmt.Fprintln(&out, line)
}
if strings.Contains(line, "// END") {
in = false
}
}
if scan.Err() != nil {
t.Error(scan.Err())
}
htmlGolden := filepath.Join(testdata, "html", "html.golden")
golden, err := os.ReadFile(htmlGolden)
if err != nil {
t.Fatalf("reading golden file: %v", err)
}
// Ignore white space differences.
// Break into lines, then compare by breaking into words.
goldenLines := strings.Split(string(golden), "\n")
outLines := strings.Split(out.String(), "\n")
// Compare at the line level, stopping at first different line so
// we don't generate tons of output if there's an inserted or deleted line.
for i, goldenLine := range goldenLines {
if i >= len(outLines) {
t.Fatalf("output shorter than golden; stops before line %d: %s\n", i+1, goldenLine)
}
// Convert all white space to simple spaces, for easy comparison.
goldenLine = strings.Join(strings.Fields(goldenLine), " ")
outLine := strings.Join(strings.Fields(outLines[i]), " ")
if outLine != goldenLine {
t.Fatalf("line %d differs: got:\n\t%s\nwant:\n\t%s", i+1, outLine, goldenLine)
}
}
if len(goldenLines) != len(outLines) {
t.Fatalf("output longer than golden; first extra output line %d: %q\n", len(goldenLines)+1, outLines[len(goldenLines)])
}
}
// Test HTML processing with a source file not run through gofmt.
// Issue #27350.
func testHtmlUnformatted(t *testing.T, toolexecArg string) {
testenv.MustHaveGoRun(t)
dir := tempDir(t)
t.Parallel()
htmlUDir := filepath.Join(dir, "htmlunformatted")
htmlU := filepath.Join(htmlUDir, "htmlunformatted.go")
htmlUTest := filepath.Join(htmlUDir, "htmlunformatted_test.go")
htmlUProfile := filepath.Join(htmlUDir, "htmlunformatted.cov")
htmlUHTML := filepath.Join(htmlUDir, "htmlunformatted.html")
if err := os.Mkdir(htmlUDir, 0777); err != nil {
t.Fatal(err)
}
if err := os.WriteFile(filepath.Join(htmlUDir, "go.mod"), []byte("module htmlunformatted\n"), 0666); err != nil {
t.Fatal(err)
}
const htmlUContents = `
package htmlunformatted
var g int
func F() {
//line x.go:1
{ { F(); goto lab } }
lab:
}`
const htmlUTestContents = `package htmlunformatted`
if err := os.WriteFile(htmlU, []byte(htmlUContents), 0444); err != nil {
t.Fatal(err)
}
if err := os.WriteFile(htmlUTest, []byte(htmlUTestContents), 0444); err != nil {
t.Fatal(err)
}
// go test -covermode=count -coverprofile TMPDIR/htmlunformatted.cov
cmd := testenv.Command(t, testenv.GoToolPath(t), "test", "-test.v", toolexecArg, "-covermode=count", "-coverprofile", htmlUProfile)
cmd.Env = append(cmd.Environ(), "CMDCOVER_TOOLEXEC=true")
cmd.Dir = htmlUDir
run(cmd, t)
// testcover -html TMPDIR/htmlunformatted.cov -o unformatted.html
cmd = testenv.Command(t, testcover(t), "-html", htmlUProfile, "-o", htmlUHTML)
cmd.Dir = htmlUDir
run(cmd, t)
}
// lineDupContents becomes linedup.go in testFuncWithDuplicateLines.
const lineDupContents = `
package linedup
var G int
func LineDup(c int) {
for i := 0; i < c; i++ {
//line ld.go:100
if i % 2 == 0 {
G++
}
if i % 3 == 0 {
G++; G++
}
//line ld.go:100
if i % 4 == 0 {
G++; G++; G++
}
if i % 5 == 0 {
G++; G++; G++; G++
}
}
}
`
// lineDupTestContents becomes linedup_test.go in testFuncWithDuplicateLines.
const lineDupTestContents = `
package linedup
import "testing"
func TestLineDup(t *testing.T) {
LineDup(100)
}
`
// Test -func with duplicate //line directives with different numbers
// of statements.
func testFuncWithDuplicateLines(t *testing.T, toolexecArg string) {
testenv.MustHaveGoRun(t)
dir := tempDir(t)
t.Parallel()
lineDupDir := filepath.Join(dir, "linedup")
lineDupGo := filepath.Join(lineDupDir, "linedup.go")
lineDupTestGo := filepath.Join(lineDupDir, "linedup_test.go")
lineDupProfile := filepath.Join(lineDupDir, "linedup.out")
if err := os.Mkdir(lineDupDir, 0777); err != nil {
t.Fatal(err)
}
if err := os.WriteFile(filepath.Join(lineDupDir, "go.mod"), []byte("module linedup\n"), 0666); err != nil {
t.Fatal(err)
}
if err := os.WriteFile(lineDupGo, []byte(lineDupContents), 0444); err != nil {
t.Fatal(err)
}
if err := os.WriteFile(lineDupTestGo, []byte(lineDupTestContents), 0444); err != nil {
t.Fatal(err)
}
// go test -cover -covermode count -coverprofile TMPDIR/linedup.out
cmd := testenv.Command(t, testenv.GoToolPath(t), "test", toolexecArg, "-cover", "-covermode", "count", "-coverprofile", lineDupProfile)
cmd.Env = append(cmd.Environ(), "CMDCOVER_TOOLEXEC=true")
cmd.Dir = lineDupDir
run(cmd, t)
// testcover -func=TMPDIR/linedup.out
cmd = testenv.Command(t, testcover(t), "-func", lineDupProfile)
cmd.Dir = lineDupDir
run(cmd, t)
}
func run(c *exec.Cmd, t *testing.T) {
t.Helper()
t.Log("running", c.Args)
out, err := c.CombinedOutput()
if len(out) > 0 {
t.Logf("%s", out)
}
if err != nil {
t.Fatal(err)
}
}
func runExpectingError(c *exec.Cmd, t *testing.T) string {
t.Helper()
t.Log("running", c.Args)
out, err := c.CombinedOutput()
if err == nil {
return fmt.Sprintf("unexpected pass for %+v", c.Args)
}
return string(out)
}
// Test instrumentation of package that ends before an expected
// trailing newline following package clause. Issue #58370.
func testMissingTrailingNewlineIssue58370(t *testing.T, toolexecArg string) {
testenv.MustHaveGoBuild(t)
dir := tempDir(t)
t.Parallel()
noeolDir := filepath.Join(dir, "issue58370")
noeolGo := filepath.Join(noeolDir, "noeol.go")
noeolTestGo := filepath.Join(noeolDir, "noeol_test.go")
if err := os.Mkdir(noeolDir, 0777); err != nil {
t.Fatal(err)
}
if err := os.WriteFile(filepath.Join(noeolDir, "go.mod"), []byte("module noeol\n"), 0666); err != nil {
t.Fatal(err)
}
const noeolContents = `package noeol`
if err := os.WriteFile(noeolGo, []byte(noeolContents), 0444); err != nil {
t.Fatal(err)
}
const noeolTestContents = `
package noeol
import "testing"
func TestCoverage(t *testing.T) { }
`
if err := os.WriteFile(noeolTestGo, []byte(noeolTestContents), 0444); err != nil {
t.Fatal(err)
}
// go test -covermode atomic
cmd := testenv.Command(t, testenv.GoToolPath(t), "test", toolexecArg, "-covermode", "atomic")
cmd.Env = append(cmd.Environ(), "CMDCOVER_TOOLEXEC=true")
cmd.Dir = noeolDir
run(cmd, t)
}
func TestSrcPathWithNewline(t *testing.T) {
testenv.MustHaveExec(t)
t.Parallel()
// srcPath is intentionally not clean so that the path passed to testcover
// will not normalize the trailing / to a \ on Windows.
srcPath := t.TempDir() + string(filepath.Separator) + "\npackage main\nfunc main() { panic(string([]rune{'u', 'h', '-', 'o', 'h'}))\n/*/main.go"
mainSrc := ` package main
func main() {
/* nothing here */
println("ok")
}
`
if err := os.MkdirAll(filepath.Dir(srcPath), 0777); err != nil {
t.Skipf("creating directory with bogus path: %v", err)
}
if err := os.WriteFile(srcPath, []byte(mainSrc), 0666); err != nil {
t.Skipf("writing file with bogus directory: %v", err)
}
cmd := testenv.Command(t, testcover(t), "-mode=atomic", srcPath)
cmd.Stderr = new(bytes.Buffer)
out, err := cmd.Output()
t.Logf("%v:\n%s", cmd, out)
t.Logf("stderr:\n%s", cmd.Stderr)
if err == nil {
t.Errorf("unexpected success; want failure due to newline in file path")
}
}

32
src/cmd/cover/doc.go Normal file
View File

@@ -0,0 +1,32 @@
// Copyright 2013 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.
/*
Cover is a program for analyzing the coverage profiles generated by
'go test -coverprofile=cover.out'.
Cover is also used by 'go test -cover' to rewrite the source code with
annotations to track which parts of each function are executed (this
is referred to "instrumentation"). Cover can operate in "legacy mode"
on a single Go source file at a time, or when invoked by the Go tool
it will process all the source files in a single package at a time
(package-scope instrumentation is enabled via "-pkgcfg" option).
When generated instrumented code, the cover tool computes approximate
basic block information by studying the source. It is thus more
portable than binary-rewriting coverage tools, but also a little less
capable. For instance, it does not probe inside && and || expressions,
and can be mildly confused by single statements with multiple function
literals.
When computing coverage of a package that uses cgo, the cover tool
must be applied to the output of cgo preprocessing, not the input,
because cover deletes comments that are significant to cgo.
For usage information, please see:
go help testflag
go tool cover -help
*/
package main

View File

@@ -0,0 +1,7 @@
// 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 main
func Main() { main() }

248
src/cmd/cover/func.go Normal file
View File

@@ -0,0 +1,248 @@
// Copyright 2013 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.
// This file implements the visitor that computes the (line, column)-(line-column) range for each function.
package main
import (
"bufio"
"bytes"
"encoding/json"
"errors"
"fmt"
"go/ast"
"go/parser"
"go/token"
"io"
"os"
"os/exec"
"path"
"path/filepath"
"runtime"
"strings"
"text/tabwriter"
"golang.org/x/tools/cover"
)
// funcOutput takes two file names as arguments, a coverage profile to read as input and an output
// file to write ("" means to write to standard output). The function reads the profile and produces
// as output the coverage data broken down by function, like this:
//
// fmt/format.go:30: init 100.0%
// fmt/format.go:57: clearflags 100.0%
// ...
// fmt/scan.go:1046: doScan 100.0%
// fmt/scan.go:1075: advance 96.2%
// fmt/scan.go:1119: doScanf 96.8%
// total: (statements) 91.9%
func funcOutput(profile, outputFile string) error {
profiles, err := cover.ParseProfiles(profile)
if err != nil {
return err
}
dirs, err := findPkgs(profiles)
if err != nil {
return err
}
var out *bufio.Writer
if outputFile == "" {
out = bufio.NewWriter(os.Stdout)
} else {
fd, err := os.Create(outputFile)
if err != nil {
return err
}
defer fd.Close()
out = bufio.NewWriter(fd)
}
defer out.Flush()
tabber := tabwriter.NewWriter(out, 1, 8, 1, '\t', 0)
defer tabber.Flush()
var total, covered int64
for _, profile := range profiles {
fn := profile.FileName
file, err := findFile(dirs, fn)
if err != nil {
return err
}
funcs, err := findFuncs(file)
if err != nil {
return err
}
// Now match up functions and profile blocks.
for _, f := range funcs {
c, t := f.coverage(profile)
fmt.Fprintf(tabber, "%s:%d:\t%s\t%.1f%%\n", fn, f.startLine, f.name, percent(c, t))
total += t
covered += c
}
}
fmt.Fprintf(tabber, "total:\t(statements)\t%.1f%%\n", percent(covered, total))
return nil
}
// findFuncs parses the file and returns a slice of FuncExtent descriptors.
func findFuncs(name string) ([]*FuncExtent, error) {
fset := token.NewFileSet()
parsedFile, err := parser.ParseFile(fset, name, nil, 0)
if err != nil {
return nil, err
}
visitor := &FuncVisitor{
fset: fset,
name: name,
astFile: parsedFile,
}
ast.Walk(visitor, visitor.astFile)
return visitor.funcs, nil
}
// FuncExtent describes a function's extent in the source by file and position.
type FuncExtent struct {
name string
startLine int
startCol int
endLine int
endCol int
}
// FuncVisitor implements the visitor that builds the function position list for a file.
type FuncVisitor struct {
fset *token.FileSet
name string // Name of file.
astFile *ast.File
funcs []*FuncExtent
}
// Visit implements the ast.Visitor interface.
func (v *FuncVisitor) Visit(node ast.Node) ast.Visitor {
switch n := node.(type) {
case *ast.FuncDecl:
if n.Body == nil {
// Do not count declarations of assembly functions.
break
}
start := v.fset.Position(n.Pos())
end := v.fset.Position(n.End())
fe := &FuncExtent{
name: n.Name.Name,
startLine: start.Line,
startCol: start.Column,
endLine: end.Line,
endCol: end.Column,
}
v.funcs = append(v.funcs, fe)
}
return v
}
// coverage returns the fraction of the statements in the function that were covered, as a numerator and denominator.
func (f *FuncExtent) coverage(profile *cover.Profile) (num, den int64) {
// We could avoid making this n^2 overall by doing a single scan and annotating the functions,
// but the sizes of the data structures is never very large and the scan is almost instantaneous.
var covered, total int64
// The blocks are sorted, so we can stop counting as soon as we reach the end of the relevant block.
for _, b := range profile.Blocks {
if b.StartLine > f.endLine || (b.StartLine == f.endLine && b.StartCol >= f.endCol) {
// Past the end of the function.
break
}
if b.EndLine < f.startLine || (b.EndLine == f.startLine && b.EndCol <= f.startCol) {
// Before the beginning of the function
continue
}
total += int64(b.NumStmt)
if b.Count > 0 {
covered += int64(b.NumStmt)
}
}
return covered, total
}
// Pkg describes a single package, compatible with the JSON output from 'go list'; see 'go help list'.
type Pkg struct {
ImportPath string
Dir string
Error *struct {
Err string
}
}
func findPkgs(profiles []*cover.Profile) (map[string]*Pkg, error) {
// Run go list to find the location of every package we care about.
pkgs := make(map[string]*Pkg)
var list []string
for _, profile := range profiles {
if strings.HasPrefix(profile.FileName, ".") || filepath.IsAbs(profile.FileName) {
// Relative or absolute path.
continue
}
pkg := path.Dir(profile.FileName)
if _, ok := pkgs[pkg]; !ok {
pkgs[pkg] = nil
list = append(list, pkg)
}
}
if len(list) == 0 {
return pkgs, nil
}
// Note: usually run as "go tool cover" in which case $GOROOT is set,
// in which case runtime.GOROOT() does exactly what we want.
goTool := filepath.Join(runtime.GOROOT(), "bin/go")
cmd := exec.Command(goTool, append([]string{"list", "-e", "-json"}, list...)...)
var stderr bytes.Buffer
cmd.Stderr = &stderr
stdout, err := cmd.Output()
if err != nil {
return nil, fmt.Errorf("cannot run go list: %v\n%s", err, stderr.Bytes())
}
dec := json.NewDecoder(bytes.NewReader(stdout))
for {
var pkg Pkg
err := dec.Decode(&pkg)
if err == io.EOF {
break
}
if err != nil {
return nil, fmt.Errorf("decoding go list json: %v", err)
}
pkgs[pkg.ImportPath] = &pkg
}
return pkgs, nil
}
// findFile finds the location of the named file in GOROOT, GOPATH etc.
func findFile(pkgs map[string]*Pkg, file string) (string, error) {
if strings.HasPrefix(file, ".") || filepath.IsAbs(file) {
// Relative or absolute path.
return file, nil
}
pkg := pkgs[path.Dir(file)]
if pkg != nil {
if pkg.Dir != "" {
return filepath.Join(pkg.Dir, path.Base(file)), nil
}
if pkg.Error != nil {
return "", errors.New(pkg.Error.Err)
}
}
return "", fmt.Errorf("did not find package for %s in go list output", file)
}
func percent(covered, total int64) float64 {
if total == 0 {
total = 1 // Avoid zero denominator.
}
return 100.0 * float64(covered) / float64(total)
}

306
src/cmd/cover/html.go Normal file
View File

@@ -0,0 +1,306 @@
// Copyright 2013 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 (
"bufio"
"cmd/internal/browser"
"fmt"
"html/template"
"io"
"math"
"os"
"path/filepath"
"strings"
"golang.org/x/tools/cover"
)
// htmlOutput reads the profile data from profile and generates an HTML
// coverage report, writing it to outfile. If outfile is empty,
// it writes the report to a temporary file and opens it in a web browser.
func htmlOutput(profile, outfile string) error {
profiles, err := cover.ParseProfiles(profile)
if err != nil {
return err
}
var d templateData
dirs, err := findPkgs(profiles)
if err != nil {
return err
}
for _, profile := range profiles {
fn := profile.FileName
if profile.Mode == "set" {
d.Set = true
}
file, err := findFile(dirs, fn)
if err != nil {
return err
}
src, err := os.ReadFile(file)
if err != nil {
return fmt.Errorf("can't read %q: %v", fn, err)
}
var buf strings.Builder
err = htmlGen(&buf, src, profile.Boundaries(src))
if err != nil {
return err
}
d.Files = append(d.Files, &templateFile{
Name: fn,
Body: template.HTML(buf.String()),
Coverage: percentCovered(profile),
})
}
var out *os.File
if outfile == "" {
var dir string
dir, err = os.MkdirTemp("", "cover")
if err != nil {
return err
}
out, err = os.Create(filepath.Join(dir, "coverage.html"))
} else {
out, err = os.Create(outfile)
}
if err != nil {
return err
}
err = htmlTemplate.Execute(out, d)
if err2 := out.Close(); err == nil {
err = err2
}
if err != nil {
return err
}
if outfile == "" {
if !browser.Open("file://" + out.Name()) {
fmt.Fprintf(os.Stderr, "HTML output written to %s\n", out.Name())
}
}
return nil
}
// percentCovered returns, as a percentage, the fraction of the statements in
// the profile covered by the test run.
// In effect, it reports the coverage of a given source file.
func percentCovered(p *cover.Profile) float64 {
var total, covered int64
for _, b := range p.Blocks {
total += int64(b.NumStmt)
if b.Count > 0 {
covered += int64(b.NumStmt)
}
}
if total == 0 {
return 0
}
return float64(covered) / float64(total) * 100
}
// htmlGen generates an HTML coverage report with the provided filename,
// source code, and tokens, and writes it to the given Writer.
func htmlGen(w io.Writer, src []byte, boundaries []cover.Boundary) error {
dst := bufio.NewWriter(w)
for i := range src {
for len(boundaries) > 0 && boundaries[0].Offset == i {
b := boundaries[0]
if b.Start {
n := 0
if b.Count > 0 {
n = int(math.Floor(b.Norm*9)) + 1
}
fmt.Fprintf(dst, `<span class="cov%v" title="%v">`, n, b.Count)
} else {
dst.WriteString("</span>")
}
boundaries = boundaries[1:]
}
switch b := src[i]; b {
case '>':
dst.WriteString("&gt;")
case '<':
dst.WriteString("&lt;")
case '&':
dst.WriteString("&amp;")
case '\t':
dst.WriteString(" ")
default:
dst.WriteByte(b)
}
}
return dst.Flush()
}
// rgb returns an rgb value for the specified coverage value
// between 0 (no coverage) and 10 (max coverage).
func rgb(n int) string {
if n == 0 {
return "rgb(192, 0, 0)" // Red
}
// Gradient from gray to green.
r := 128 - 12*(n-1)
g := 128 + 12*(n-1)
b := 128 + 3*(n-1)
return fmt.Sprintf("rgb(%v, %v, %v)", r, g, b)
}
// colors generates the CSS rules for coverage colors.
func colors() template.CSS {
var buf strings.Builder
for i := 0; i < 11; i++ {
fmt.Fprintf(&buf, ".cov%v { color: %v }\n", i, rgb(i))
}
return template.CSS(buf.String())
}
var htmlTemplate = template.Must(template.New("html").Funcs(template.FuncMap{
"colors": colors,
}).Parse(tmplHTML))
type templateData struct {
Files []*templateFile
Set bool
}
// PackageName returns a name for the package being shown.
// It does this by choosing the penultimate element of the path
// name, so foo.bar/baz/foo.go chooses 'baz'. This is cheap
// and easy, avoids parsing the Go file, and gets a better answer
// for package main. It returns the empty string if there is
// a problem.
func (td templateData) PackageName() string {
if len(td.Files) == 0 {
return ""
}
fileName := td.Files[0].Name
elems := strings.Split(fileName, "/") // Package path is always slash-separated.
// Return the penultimate non-empty element.
for i := len(elems) - 2; i >= 0; i-- {
if elems[i] != "" {
return elems[i]
}
}
return ""
}
type templateFile struct {
Name string
Body template.HTML
Coverage float64
}
const tmplHTML = `
<!DOCTYPE html>
<html>
<head>
<meta http-equiv="Content-Type" content="text/html; charset=utf-8">
<title>{{$pkg := .PackageName}}{{if $pkg}}{{$pkg}}: {{end}}Go Coverage Report</title>
<style>
body {
background: black;
color: rgb(80, 80, 80);
}
body, pre, #legend span {
font-family: Menlo, monospace;
font-weight: bold;
}
#topbar {
background: black;
position: fixed;
top: 0; left: 0; right: 0;
height: 42px;
border-bottom: 1px solid rgb(80, 80, 80);
}
#content {
margin-top: 50px;
}
#nav, #legend {
float: left;
margin-left: 10px;
}
#legend {
margin-top: 12px;
}
#nav {
margin-top: 10px;
}
#legend span {
margin: 0 5px;
}
{{colors}}
</style>
</head>
<body>
<div id="topbar">
<div id="nav">
<select id="files">
{{range $i, $f := .Files}}
<option value="file{{$i}}">{{$f.Name}} ({{printf "%.1f" $f.Coverage}}%)</option>
{{end}}
</select>
</div>
<div id="legend">
<span>not tracked</span>
{{if .Set}}
<span class="cov0">not covered</span>
<span class="cov8">covered</span>
{{else}}
<span class="cov0">no coverage</span>
<span class="cov1">low coverage</span>
<span class="cov2">*</span>
<span class="cov3">*</span>
<span class="cov4">*</span>
<span class="cov5">*</span>
<span class="cov6">*</span>
<span class="cov7">*</span>
<span class="cov8">*</span>
<span class="cov9">*</span>
<span class="cov10">high coverage</span>
{{end}}
</div>
</div>
<div id="content">
{{range $i, $f := .Files}}
<pre class="file" id="file{{$i}}" style="display: none">{{$f.Body}}</pre>
{{end}}
</div>
</body>
<script>
(function() {
var files = document.getElementById('files');
var visible;
files.addEventListener('change', onChange, false);
function select(part) {
if (visible)
visible.style.display = 'none';
visible = document.getElementById(part);
if (!visible)
return;
files.value = part;
visible.style.display = 'block';
location.hash = part;
}
function onChange() {
select(files.value);
window.scrollTo(0, 0);
}
if (location.hash != "") {
select(location.hash.substr(1));
}
if (!visible) {
select("file0");
}
})();
</script>
</html>
`

View File

@@ -0,0 +1,31 @@
// Copyright 2020 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 "testing"
func TestPackageName(t *testing.T) {
var tests = []struct {
fileName, pkgName string
}{
{"", ""},
{"///", ""},
{"fmt", ""}, // No Go file, improper form.
{"fmt/foo.go", "fmt"},
{"encoding/binary/foo.go", "binary"},
{"encoding/binary/////foo.go", "binary"},
}
var tf templateFile
for _, test := range tests {
tf.Name = test.fileName
td := templateData{
Files: []*templateFile{&tf},
}
got := td.PackageName()
if got != test.pkgName {
t.Errorf("%s: got %s want %s", test.fileName, got, test.pkgName)
}
}
}

40
src/cmd/cover/testdata/directives.go vendored Normal file
View File

@@ -0,0 +1,40 @@
// Copyright 2017 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.
// This file is processed by the cover command, then a test verifies that
// all compiler directives are preserved and positioned appropriately.
//go:a
//go:b
package main
//go:c1
//go:c2
//doc
func c() {
}
//go:d1
//doc
//go:d2
type d int
//go:e1
//doc
//go:e2
type (
e int
f int
)
//go:_empty1
//doc
//go:_empty2
type ()
//go:f

30
src/cmd/cover/testdata/html/html.go vendored Normal file
View File

@@ -0,0 +1,30 @@
package html
import "fmt"
// This file is tested by html_test.go.
// The comments below are markers for extracting the annotated source
// from the HTML output.
// This is a regression test for incorrect sorting of boundaries
// that coincide, specifically for empty select clauses.
// START f
func f() {
ch := make(chan int)
select {
case <-ch:
default:
}
}
// END f
// https://golang.org/issue/25767
// START g
func g() {
if false {
fmt.Printf("Hello")
}
}
// END g

18
src/cmd/cover/testdata/html/html.golden vendored Normal file
View File

@@ -0,0 +1,18 @@
// START f
func f() <span class="cov8" title="1">{
ch := make(chan int)
select </span>{
case &lt;-ch:<span class="cov0" title="0"></span>
default:<span class="cov8" title="1"></span>
}
}
// END f
// START g
func g() <span class="cov8" title="1">{
if false </span><span class="cov0" title="0">{
fmt.Printf("Hello")
}</span>
}
// END g

View File

@@ -0,0 +1,8 @@
package html
import "testing"
func TestAll(t *testing.T) {
f()
g()
}

116
src/cmd/cover/testdata/main.go vendored Normal file
View File

@@ -0,0 +1,116 @@
// Copyright 2013 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.
// Test runner for coverage test. This file is not coverage-annotated; test.go is.
// It knows the coverage counter is called
// "thisNameMustBeVeryLongToCauseOverflowOfCounterIncrementStatementOntoNextLineForTest".
package main
import (
"fmt"
"os"
)
func main() {
testAll()
verify()
}
type block struct {
count uint32
line uint32
}
var counters = make(map[block]bool)
// shorthand for the long counter variable.
var coverTest = &thisNameMustBeVeryLongToCauseOverflowOfCounterIncrementStatementOntoNextLineForTest
// check records the location and expected value for a counter.
func check(line, count uint32) {
b := block{
count,
line,
}
counters[b] = true
}
// checkVal is a version of check that returns its extra argument,
// so it can be used in conditionals.
func checkVal(line, count uint32, val int) int {
b := block{
count,
line,
}
counters[b] = true
return val
}
var PASS = true
// verify checks the expected counts against the actual. It runs after the test has completed.
func verify() {
for b := range counters {
got, index := count(b.line)
if b.count == anything && got != 0 {
got = anything
}
if got != b.count {
fmt.Fprintf(os.Stderr, "test_go:%d expected count %d got %d [counter %d]\n", b.line, b.count, got, index)
PASS = false
}
}
verifyPanic()
if !PASS {
fmt.Fprintf(os.Stderr, "FAIL\n")
os.Exit(2)
}
}
// verifyPanic is a special check for the known counter that should be
// after the panic call in testPanic.
func verifyPanic() {
if coverTest.Count[panicIndex-1] != 1 {
// Sanity check for test before panic.
fmt.Fprintf(os.Stderr, "bad before panic")
PASS = false
}
if coverTest.Count[panicIndex] != 0 {
fmt.Fprintf(os.Stderr, "bad at panic: %d should be 0\n", coverTest.Count[panicIndex])
PASS = false
}
if coverTest.Count[panicIndex+1] != 1 {
fmt.Fprintf(os.Stderr, "bad after panic")
PASS = false
}
}
// count returns the count and index for the counter at the specified line.
func count(line uint32) (uint32, int) {
// Linear search is fine. Choose perfect fit over approximate.
// We can have a closing brace for a range on the same line as a condition for an "else if"
// and we don't want that brace to steal the count for the condition on the "if".
// Therefore we test for a perfect (lo==line && hi==line) match, but if we can't
// find that we take the first imperfect match.
index := -1
indexLo := uint32(1e9)
for i := range coverTest.Count {
lo, hi := coverTest.Pos[3*i], coverTest.Pos[3*i+1]
if lo == line && line == hi {
return coverTest.Count[i], i
}
// Choose the earliest match (the counters are in unpredictable order).
if lo <= line && line <= hi && indexLo > lo {
index = i
indexLo = lo
}
}
if index == -1 {
fmt.Fprintln(os.Stderr, "cover_test: no counter for line", line)
PASS = false
return 0, 0
}
return coverTest.Count[index], index
}

27
src/cmd/cover/testdata/p.go vendored Normal file
View File

@@ -0,0 +1,27 @@
// Copyright 2017 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.
// A package such that there are 3 functions with zero total and covered lines.
// And one with 1 total and covered lines. Reproduces issue #20515.
package p
//go:noinline
func A() {
}
//go:noinline
func B() {
}
//go:noinline
func C() {
}
//go:noinline
func D() int64 {
return 42
}

28
src/cmd/cover/testdata/pkgcfg/a/a.go vendored Normal file
View File

@@ -0,0 +1,28 @@
package a
type Atyp int
func (ap *Atyp) Set(q int) {
*ap = Atyp(q)
}
func (ap Atyp) Get() int {
inter := func(q Atyp) int {
return int(q)
}
return inter(ap)
}
var afunc = func(x int) int {
return x + 1
}
var Avar = afunc(42)
func A(x int) int {
if x == 0 {
return 22
} else if x == 1 {
return 33
}
return 44
}

8
src/cmd/cover/testdata/pkgcfg/a/a2.go vendored Normal file
View File

@@ -0,0 +1,8 @@
package a
func A2() {
{
}
{
}
}

View File

@@ -0,0 +1,14 @@
package a_test
import (
"cfg/a"
"testing"
)
func TestA(t *testing.T) {
a.A(0)
var aat a.Atyp
at := &aat
at.Set(42)
println(at.Get())
}

View File

@@ -0,0 +1,8 @@
package noFuncsNoTests
const foo = 1
var G struct {
x int
y bool
}

View File

@@ -0,0 +1,13 @@
package yesFuncsNoTests
func F1() {
println("hi")
}
func F2(x int) int {
if x < 0 {
return 9
} else {
return 10
}
}

5
src/cmd/cover/testdata/profile.cov vendored Normal file
View File

@@ -0,0 +1,5 @@
mode: set
./testdata/p.go:10.10,12.2 0 0
./testdata/p.go:15.10,17.2 0 0
./testdata/p.go:20.10,22.2 0 0
./testdata/p.go:25.16,27.2 1 1

300
src/cmd/cover/testdata/test.go vendored Normal file
View File

@@ -0,0 +1,300 @@
// Copyright 2013 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.
// This program is processed by the cover command, and then testAll is called.
// The test driver in main.go can then compare the coverage statistics with expectation.
// The word LINE is replaced by the line number in this file. When the file is executed,
// the coverage processing has changed the line numbers, so we can't use runtime.Caller.
package main
import _ "unsafe" // for go:linkname
//go:linkname some_name some_name
var some_name int
const anything = 1e9 // Just some unlikely value that means "we got here, don't care how often"
func testAll() {
testSimple()
testBlockRun()
testIf()
testFor()
testRange()
testSwitch()
testTypeSwitch()
testSelect1()
testSelect2()
testPanic()
testEmptySwitches()
testFunctionLiteral()
testGoto()
}
// The indexes of the counters in testPanic are known to main.go
const panicIndex = 3
// This test appears first because the index of its counters is known to main.go
func testPanic() {
defer func() {
recover()
}()
check(LINE, 1)
panic("should not get next line")
check(LINE, 0) // this is GoCover.Count[panicIndex]
// The next counter is in testSimple and it will be non-zero.
// If the panic above does not trigger a counter, the test will fail
// because GoCover.Count[panicIndex] will be the one in testSimple.
}
func testSimple() {
check(LINE, 1)
}
func testIf() {
if true {
check(LINE, 1)
} else {
check(LINE, 0)
}
if false {
check(LINE, 0)
} else {
check(LINE, 1)
}
for i := 0; i < 3; i++ {
if checkVal(LINE, 3, i) <= 2 {
check(LINE, 3)
}
if checkVal(LINE, 3, i) <= 1 {
check(LINE, 2)
}
if checkVal(LINE, 3, i) <= 0 {
check(LINE, 1)
}
}
for i := 0; i < 3; i++ {
if checkVal(LINE, 3, i) <= 1 {
check(LINE, 2)
} else {
check(LINE, 1)
}
}
for i := 0; i < 3; i++ {
if checkVal(LINE, 3, i) <= 0 {
check(LINE, 1)
} else if checkVal(LINE, 2, i) <= 1 {
check(LINE, 1)
} else if checkVal(LINE, 1, i) <= 2 {
check(LINE, 1)
} else if checkVal(LINE, 0, i) <= 3 {
check(LINE, 0)
}
}
if func(a, b int) bool { return a < b }(3, 4) {
check(LINE, 1)
}
}
func testFor() {
for i := 0; i < 10; func() { i++; check(LINE, 10) }() {
check(LINE, 10)
}
}
func testRange() {
for _, f := range []func(){
func() { check(LINE, 1) },
} {
f()
check(LINE, 1)
}
}
func testBlockRun() {
check(LINE, 1)
{
check(LINE, 1)
}
{
check(LINE, 1)
}
check(LINE, 1)
{
check(LINE, 1)
}
{
check(LINE, 1)
}
check(LINE, 1)
}
func testSwitch() {
for i := 0; i < 5; func() { i++; check(LINE, 5) }() {
goto label2
label1:
goto label1
label2:
switch i {
case 0:
check(LINE, 1)
case 1:
check(LINE, 1)
case 2:
check(LINE, 1)
default:
check(LINE, 2)
}
}
}
func testTypeSwitch() {
var x = []any{1, 2.0, "hi"}
for _, v := range x {
switch func() { check(LINE, 3) }(); v.(type) {
case int:
check(LINE, 1)
case float64:
check(LINE, 1)
case string:
check(LINE, 1)
case complex128:
check(LINE, 0)
default:
check(LINE, 0)
}
}
}
func testSelect1() {
c := make(chan int)
go func() {
for i := 0; i < 1000; i++ {
c <- i
}
}()
for {
select {
case <-c:
check(LINE, anything)
case <-c:
check(LINE, anything)
default:
check(LINE, 1)
return
}
}
}
func testSelect2() {
c1 := make(chan int, 1000)
c2 := make(chan int, 1000)
for i := 0; i < 1000; i++ {
c1 <- i
c2 <- i
}
for {
select {
case <-c1:
check(LINE, 1000)
case <-c2:
check(LINE, 1000)
default:
check(LINE, 1)
return
}
}
}
// Empty control statements created syntax errors. This function
// is here just to be sure that those are handled correctly now.
func testEmptySwitches() {
check(LINE, 1)
switch 3 {
}
check(LINE, 1)
switch i := (any)(3).(int); i {
}
check(LINE, 1)
c := make(chan int)
go func() {
check(LINE, 1)
c <- 1
select {}
}()
<-c
check(LINE, 1)
}
func testFunctionLiteral() {
a := func(f func()) error {
f()
f()
return nil
}
b := func(f func()) bool {
f()
f()
return true
}
check(LINE, 1)
a(func() {
check(LINE, 2)
})
if err := a(func() {
check(LINE, 2)
}); err != nil {
}
switch b(func() {
check(LINE, 2)
}) {
}
x := 2
switch x {
case func() int { check(LINE, 1); return 1 }():
check(LINE, 0)
panic("2=1")
case func() int { check(LINE, 1); return 2 }():
check(LINE, 1)
case func() int { check(LINE, 0); return 3 }():
check(LINE, 0)
panic("2=3")
}
}
func testGoto() {
for i := 0; i < 2; i++ {
if i == 0 {
goto Label
}
check(LINE, 1)
Label:
check(LINE, 2)
}
// Now test that we don't inject empty statements
// between a label and a loop.
loop:
for {
check(LINE, 1)
break loop
}
}
// This comment didn't appear in generated go code.
func haha() {
// Needed for cover to add counter increment here.
_ = 42
}
// Some someFunction.
//
//go:nosplit
func someFunction() {
}