mirror of https://github.com/golang/go.git
262 lines
6.2 KiB
Go
262 lines
6.2 KiB
Go
// Copyright 2021 The Go Authors. All rights reserved.
|
|
// Use of this source code is governed by a BSD-style
|
|
// license that can be found in the LICENSE file.
|
|
|
|
//go:build go.1.16
|
|
// +build go.1.16
|
|
|
|
// Running this program in the tools directory will produce a coverage file /tmp/cover.out
|
|
// and a coverage report for all the packages under internal/lsp, accumulated by all the tests
|
|
// under gopls.
|
|
//
|
|
// -o controls where the coverage file is written, defaulting to /tmp/cover.out
|
|
// -i coverage-file will generate the report from an existing coverage file
|
|
// -v controls verbosity (0: only report coverage, 1: report as each directory is finished,
|
|
// 2: report on each test, 3: more details, 4: too much)
|
|
// -t tests only tests packages in the given comma-separated list of directories in gopls.
|
|
// The names should start with ., as in ./internal/regtest/bench
|
|
// -run tests. If set, -run tests is passed on to the go test command.
|
|
//
|
|
// Despite gopls' use of goroutines, the counts are almost deterministic.
|
|
package main
|
|
|
|
import (
|
|
"bytes"
|
|
"encoding/json"
|
|
"flag"
|
|
"fmt"
|
|
"log"
|
|
"os"
|
|
"os/exec"
|
|
"path/filepath"
|
|
"sort"
|
|
"strings"
|
|
"time"
|
|
|
|
"golang.org/x/tools/cover"
|
|
)
|
|
|
|
var (
|
|
proFile = flag.String("i", "", "existing profile file")
|
|
outFile = flag.String("o", "/tmp/cover.out", "where to write the coverage file")
|
|
verbose = flag.Int("v", 0, "how much detail to print as tests are running")
|
|
tests = flag.String("t", "", "list of tests to run")
|
|
run = flag.String("run", "", "value of -run to pass to go test")
|
|
)
|
|
|
|
func main() {
|
|
log.SetFlags(log.Lshortfile)
|
|
flag.Parse()
|
|
|
|
if *proFile != "" {
|
|
report(*proFile)
|
|
return
|
|
}
|
|
|
|
checkCwd()
|
|
// find the packages under gopls containing tests
|
|
tests := listDirs("gopls")
|
|
tests = onlyTests(tests)
|
|
tests = realTestName(tests)
|
|
|
|
// report coverage for packages under internal/lsp
|
|
parg := "golang.org/x/tools/internal/lsp/..."
|
|
|
|
accum := []string{}
|
|
seen := make(map[string]bool)
|
|
now := time.Now()
|
|
for _, toRun := range tests {
|
|
if excluded(toRun) {
|
|
continue
|
|
}
|
|
x := runTest(toRun, parg)
|
|
if *verbose > 0 {
|
|
fmt.Printf("finished %s %.1fs\n", toRun, time.Since(now).Seconds())
|
|
}
|
|
lines := bytes.Split(x, []byte{'\n'})
|
|
for _, l := range lines {
|
|
if len(l) == 0 {
|
|
continue
|
|
}
|
|
if !seen[string(l)] {
|
|
// not accumulating counts, so only works for mode:set
|
|
seen[string(l)] = true
|
|
accum = append(accum, string(l))
|
|
}
|
|
}
|
|
}
|
|
sort.Strings(accum[1:])
|
|
if err := os.WriteFile(*outFile, []byte(strings.Join(accum, "\n")), 0644); err != nil {
|
|
log.Print(err)
|
|
}
|
|
report(*outFile)
|
|
}
|
|
|
|
type result struct {
|
|
Time time.Time
|
|
Test string
|
|
Action string
|
|
Package string
|
|
Output string
|
|
Elapsed float64
|
|
}
|
|
|
|
func runTest(tName, parg string) []byte {
|
|
args := []string{"test", "-short", "-coverpkg", parg, "-coverprofile", *outFile,
|
|
"-json"}
|
|
if *run != "" {
|
|
args = append(args, fmt.Sprintf("-run=%s", *run))
|
|
}
|
|
args = append(args, tName)
|
|
cmd := exec.Command("go", args...)
|
|
cmd.Dir = "./gopls"
|
|
ans, err := cmd.Output()
|
|
if *verbose > 1 {
|
|
got := strings.Split(string(ans), "\n")
|
|
for _, g := range got {
|
|
if g == "" {
|
|
continue
|
|
}
|
|
var m result
|
|
if err := json.Unmarshal([]byte(g), &m); err != nil {
|
|
log.Printf("%T/%v", err, err) // shouldn't happen
|
|
continue
|
|
}
|
|
maybePrint(m)
|
|
}
|
|
}
|
|
if err != nil {
|
|
log.Printf("%s: %q, cmd=%s", tName, ans, cmd.String())
|
|
}
|
|
buf, err := os.ReadFile(*outFile)
|
|
if err != nil {
|
|
log.Fatal(err)
|
|
}
|
|
return buf
|
|
}
|
|
|
|
func report(fn string) {
|
|
profs, err := cover.ParseProfiles(fn)
|
|
if err != nil {
|
|
log.Fatal(err)
|
|
}
|
|
for _, p := range profs {
|
|
statements, counts := 0, 0
|
|
for _, x := range p.Blocks {
|
|
statements += x.NumStmt
|
|
if x.Count != 0 {
|
|
counts += x.NumStmt // sic: if any were executed, all were
|
|
}
|
|
}
|
|
pc := 100 * float64(counts) / float64(statements)
|
|
fmt.Printf("%3.0f%% %3d/%3d %s\n", pc, counts, statements, p.FileName)
|
|
}
|
|
}
|
|
|
|
var todo []string // tests to run
|
|
|
|
func excluded(tname string) bool {
|
|
if *tests == "" { // run all tests
|
|
return false
|
|
}
|
|
if todo == nil {
|
|
todo = strings.Split(*tests, ",")
|
|
}
|
|
for _, nm := range todo {
|
|
if tname == nm { // run this test
|
|
return false
|
|
}
|
|
}
|
|
// not in list, skip it
|
|
return true
|
|
}
|
|
|
|
// should m.Package be printed sometime?
|
|
func maybePrint(m result) {
|
|
switch m.Action {
|
|
case "pass", "fail", "skip":
|
|
fmt.Printf("%s %s %.3f\n", m.Action, m.Test, m.Elapsed)
|
|
case "run":
|
|
if *verbose > 2 {
|
|
fmt.Printf("%s %s %.3f\n", m.Action, m.Test, m.Elapsed)
|
|
}
|
|
case "output":
|
|
if *verbose > 3 {
|
|
fmt.Printf("%s %s %q %.3f\n", m.Action, m.Test, m.Output, m.Elapsed)
|
|
}
|
|
default:
|
|
log.Fatalf("unknown action %s\n", m.Action)
|
|
}
|
|
}
|
|
|
|
// return only the directories that contain tests
|
|
func onlyTests(s []string) []string {
|
|
ans := []string{}
|
|
outer:
|
|
for _, d := range s {
|
|
files, err := os.ReadDir(d)
|
|
if err != nil {
|
|
log.Fatalf("%s: %v", d, err)
|
|
}
|
|
for _, de := range files {
|
|
if strings.Contains(de.Name(), "_test.go") {
|
|
ans = append(ans, d)
|
|
continue outer
|
|
}
|
|
}
|
|
}
|
|
return ans
|
|
}
|
|
|
|
// replace the prefix gopls/ with ./ as the tests are run in the gopls directory
|
|
func realTestName(p []string) []string {
|
|
ans := []string{}
|
|
for _, x := range p {
|
|
x = x[len("gopls/"):]
|
|
ans = append(ans, "./"+x)
|
|
}
|
|
return ans
|
|
}
|
|
|
|
// make sure we start in a tools directory
|
|
func checkCwd() {
|
|
dir, err := os.Getwd()
|
|
if err != nil {
|
|
log.Fatal(err)
|
|
}
|
|
// we expect to be a the root of golang.org/x/tools
|
|
cmd := exec.Command("go", "list", "-m", "-f", "{{.Dir}}", "golang.org/x/tools")
|
|
buf, err := cmd.Output()
|
|
buf = bytes.Trim(buf, "\n \t") // remove \n at end
|
|
if err != nil {
|
|
log.Fatal(err)
|
|
}
|
|
if string(buf) != dir {
|
|
log.Fatalf("wrong directory: in %q, should be in %q", dir, string(buf))
|
|
}
|
|
// and we expect gopls and internal/lsp as subdirectories
|
|
_, err = os.Stat("gopls")
|
|
if err != nil {
|
|
log.Fatalf("expected a gopls directory, %v", err)
|
|
}
|
|
_, err = os.Stat("internal/lsp")
|
|
if err != nil {
|
|
log.Fatalf("expected to see internal/lsp, %v", err)
|
|
}
|
|
}
|
|
|
|
func listDirs(dir string) []string {
|
|
ans := []string{}
|
|
f := func(path string, dirEntry os.DirEntry, err error) error {
|
|
if strings.HasSuffix(path, "/testdata") || strings.HasSuffix(path, "/typescript") {
|
|
return filepath.SkipDir
|
|
}
|
|
if dirEntry.IsDir() {
|
|
ans = append(ans, path)
|
|
}
|
|
return nil
|
|
}
|
|
filepath.WalkDir(dir, f)
|
|
return ans
|
|
}
|