Initial commit: Go 1.23 release state
This commit is contained in:
271
src/cmd/cover/cfg_test.go
Normal file
271
src/cmd/cover/cfg_test.go
Normal 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
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
649
src/cmd/cover/cover_test.go
Normal 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
32
src/cmd/cover/doc.go
Normal 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
|
||||
7
src/cmd/cover/export_test.go
Normal file
7
src/cmd/cover/export_test.go
Normal 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
248
src/cmd/cover/func.go
Normal 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
306
src/cmd/cover/html.go
Normal 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(">")
|
||||
case '<':
|
||||
dst.WriteString("<")
|
||||
case '&':
|
||||
dst.WriteString("&")
|
||||
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>
|
||||
`
|
||||
31
src/cmd/cover/pkgname_test.go
Normal file
31
src/cmd/cover/pkgname_test.go
Normal 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
40
src/cmd/cover/testdata/directives.go
vendored
Normal 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
30
src/cmd/cover/testdata/html/html.go
vendored
Normal 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
18
src/cmd/cover/testdata/html/html.golden
vendored
Normal file
@@ -0,0 +1,18 @@
|
||||
// START f
|
||||
func f() <span class="cov8" title="1">{
|
||||
ch := make(chan int)
|
||||
select </span>{
|
||||
case <-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
|
||||
8
src/cmd/cover/testdata/html/html_test.go
vendored
Normal file
8
src/cmd/cover/testdata/html/html_test.go
vendored
Normal 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
116
src/cmd/cover/testdata/main.go
vendored
Normal 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
27
src/cmd/cover/testdata/p.go
vendored
Normal 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
28
src/cmd/cover/testdata/pkgcfg/a/a.go
vendored
Normal 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
8
src/cmd/cover/testdata/pkgcfg/a/a2.go
vendored
Normal file
@@ -0,0 +1,8 @@
|
||||
package a
|
||||
|
||||
func A2() {
|
||||
{
|
||||
}
|
||||
{
|
||||
}
|
||||
}
|
||||
14
src/cmd/cover/testdata/pkgcfg/a/a_test.go
vendored
Normal file
14
src/cmd/cover/testdata/pkgcfg/a/a_test.go
vendored
Normal 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())
|
||||
}
|
||||
8
src/cmd/cover/testdata/pkgcfg/noFuncsNoTests/nfnt.go
vendored
Normal file
8
src/cmd/cover/testdata/pkgcfg/noFuncsNoTests/nfnt.go
vendored
Normal file
@@ -0,0 +1,8 @@
|
||||
package noFuncsNoTests
|
||||
|
||||
const foo = 1
|
||||
|
||||
var G struct {
|
||||
x int
|
||||
y bool
|
||||
}
|
||||
13
src/cmd/cover/testdata/pkgcfg/yesFuncsNoTests/yfnt.go
vendored
Normal file
13
src/cmd/cover/testdata/pkgcfg/yesFuncsNoTests/yfnt.go
vendored
Normal 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
5
src/cmd/cover/testdata/profile.cov
vendored
Normal 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
300
src/cmd/cover/testdata/test.go
vendored
Normal 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() {
|
||||
}
|
||||
Reference in New Issue
Block a user