Skip to content

Commit

Permalink
add xgo tool coverage
Browse files Browse the repository at this point in the history
  • Loading branch information
xhd2015 committed May 9, 2024
1 parent 68ff63b commit dcc9e2a
Show file tree
Hide file tree
Showing 14 changed files with 321 additions and 111 deletions.
2 changes: 1 addition & 1 deletion .github/workflows/go.yml
Original file line number Diff line number Diff line change
Expand Up @@ -43,7 +43,7 @@ jobs:
run: go run ./script/run-test --reset-instrument --debug -v -cover -coverpkg github.com/xhd2015/xgo/runtime/... -coverprofile cover.out

- name: Merge Coverages
run: go run ./script/cover merge ./cover-runtime.out ./cover-runtime-sub.out -o cover-runtime-merged.out
run: go run ./cmd/coverage merge ./cover-runtime.out ./cover-runtime-sub.out -o cover-runtime-merged.out --exclude-prefix github.com/xhd2015/xgo/runtime/test

- name: Print coverage
run: cd runtime && go tool cover --func ../cover-runtime-merged.out
Expand Down
14 changes: 14 additions & 0 deletions cmd/coverage/main.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,14 @@
package main

import (
"os"

"github.com/xhd2015/xgo/cmd/xgo/coverage"
)

func main() {
// os.Arg[0] = coverage
// os.Arg[1] = args
args := os.Args[1:]
coverage.Main(args)
}
22 changes: 22 additions & 0 deletions cmd/xgo/coverage/help.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,22 @@
package coverage

const help = `
Xgo tool coverage is a tool to view and merge go coverage profiles, just like an extension to go tool cover.
Usage:
xgo tool coverage <cmd> [arguments]
The commands are:
merge merge coverage profiles
help show help message
Options for merge:
-o <file> output to file instead of stdout
--exclude-prefix <pkg> exclude coverage of a specific package and sub packages
Examples:
xgo tool coverage merge -o cover.a cover-a.out cover-b.out merge multiple files into one
See https://github.com/xhd2015/xgo for documentation.
`
135 changes: 135 additions & 0 deletions cmd/xgo/coverage/main.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,135 @@
package coverage

import (
"fmt"
"io"
"os"
"strings"

"github.com/xhd2015/xgo/support/coverage"
)

func Main(args []string) {
if len(args) == 0 {
fmt.Fprintf(os.Stderr, "requires cmd\n")
os.Exit(1)
}
cmd := args[0]
if cmd == "help" {
fmt.Print(strings.TrimPrefix(help, "\n"))
return
}
args = args[1:]

var remainArgs []string
var outFile string
n := len(args)

var flagHelp bool
var excludePrefix []string
for i := 0; i < n; i++ {
arg := args[i]
if arg == "--" {
remainArgs = append(remainArgs, args[i+1:]...)
break
}
if arg == "-o" {
if i+1 >= n {
fmt.Fprintf(os.Stderr, "%s requires file\n", arg)
os.Exit(1)
}
outFile = args[i+1]
i++
continue
}
if arg == "--exclude-prefix" {
if i+1 >= n {
fmt.Fprintf(os.Stderr, "%s requires argument\n", arg)
os.Exit(1)
}
if args[i+1] == "" {
fmt.Fprintf(os.Stderr, "%s requires non empty argument\n", arg)
os.Exit(1)
}
excludePrefix = append(excludePrefix, args[i+1])
i++
continue
}
if arg == "--help" || arg == "-h" {
flagHelp = true
continue
}
if !strings.HasPrefix(arg, "-") {
remainArgs = append(remainArgs, arg)
continue
}
fmt.Fprintf(os.Stderr, "unrecognized flag: %s\n", arg)
os.Exit(1)
}
if flagHelp {
fmt.Print(strings.TrimPrefix(help, "\n"))
return
}

switch cmd {
case "merge":
if len(remainArgs) == 0 {
fmt.Fprintf(os.Stderr, "requires files\n")
os.Exit(1)
}
err := mergeCover(remainArgs, outFile, excludePrefix)
if err != nil {
fmt.Fprintf(os.Stderr, "%v\n", err)
os.Exit(1)
}
default:
fmt.Fprintf(os.Stderr, "unrecognized cmd: %s\n", cmd)
os.Exit(1)
}
}

func mergeCover(files []string, outFile string, excludePrefix []string) error {
var mode string
covs := make([][]*coverage.CovLine, 0, len(files))
for _, file := range files {
content, err := os.ReadFile(file)
if err != nil {
return err
}
covMode, cov := coverage.Parse(string(content))
covs = append(covs, cov)
if mode == "" {
mode = covMode
}
}
res := coverage.Merge(covs...)
res = coverage.Filter(res, func(line *coverage.CovLine) bool {
return !hasAnyPrefix(line.Prefix, excludePrefix)
})

if mode == "" {
mode = "set"
}
mergedCov := coverage.Format(mode, res)

var out io.Writer = os.Stdout
if outFile != "" {
file, err := os.Create(outFile)
if err != nil {
return err
}
defer file.Close()
out = file
}
_, err := io.WriteString(out, mergedCov)
return err
}

func hasAnyPrefix(s string, prefixes []string) bool {
for _, prefix := range prefixes {
if strings.HasPrefix(s, prefix) {
return true
}
}
return false
}
6 changes: 3 additions & 3 deletions cmd/xgo/runtime_gen/core/version.go
Original file line number Diff line number Diff line change
Expand Up @@ -6,9 +6,9 @@ import (
"os"
)

const VERSION = "1.0.30"
const REVISION = "83fdf348a92806bbdc0b6746fc7d55ae1671dfab+1"
const NUMBER = 209
const VERSION = "1.0.31"
const REVISION = "12ae038b4e052f42cd97af0721488c1b5f77eb6e+1"
const NUMBER = 210

// these fields will be filled by compiler
const XGO_VERSION = ""
Expand Down
8 changes: 7 additions & 1 deletion cmd/xgo/tool.go
Original file line number Diff line number Diff line change
Expand Up @@ -8,13 +8,19 @@ import (
"runtime"
"strings"

"github.com/xhd2015/xgo/cmd/xgo/coverage"
"github.com/xhd2015/xgo/cmd/xgo/trace"
"github.com/xhd2015/xgo/support/cmd"
)

func handleTool(tool string, args []string) error {
if tool == "trace" {
return trace.Main(args)
trace.Main(args)
return nil
}
if tool == "coverage" {
coverage.Main(args)
return nil
}
tools := []string{
tool,
Expand Down
6 changes: 3 additions & 3 deletions cmd/xgo/trace/main.go
Original file line number Diff line number Diff line change
Expand Up @@ -20,13 +20,13 @@ import (
"github.com/xhd2015/xgo/support/cmd"
)

func Main(args []string) error {
func Main(args []string) {
if len(args) == 0 {
return errors.New("requires file")
fmt.Fprintf(os.Stderr, "requires file\n")
os.Exit(1)
}
file := args[0]
serveFile(file)
return nil
}

func serveFile(file string) {
Expand Down
6 changes: 3 additions & 3 deletions cmd/xgo/version.go
Original file line number Diff line number Diff line change
Expand Up @@ -2,9 +2,9 @@ package main

import "fmt"

const VERSION = "1.0.30"
const REVISION = "83fdf348a92806bbdc0b6746fc7d55ae1671dfab+1"
const NUMBER = 209
const VERSION = "1.0.31"
const REVISION = "12ae038b4e052f42cd97af0721488c1b5f77eb6e+1"
const NUMBER = 210

func getRevision() string {
revSuffix := ""
Expand Down
6 changes: 3 additions & 3 deletions runtime/core/version.go
Original file line number Diff line number Diff line change
Expand Up @@ -6,9 +6,9 @@ import (
"os"
)

const VERSION = "1.0.30"
const REVISION = "83fdf348a92806bbdc0b6746fc7d55ae1671dfab+1"
const NUMBER = 209
const VERSION = "1.0.31"
const REVISION = "12ae038b4e052f42cd97af0721488c1b5f77eb6e+1"
const NUMBER = 210

// these fields will be filled by compiler
const XGO_VERSION = ""
Expand Down
105 changes: 8 additions & 97 deletions script/cover/cover.go
Original file line number Diff line number Diff line change
Expand Up @@ -4,8 +4,9 @@ import (
"fmt"
"io"
"os"
"strconv"
"strings"

"github.com/xhd2015/xgo/support/coverage"
)

func main() {
Expand Down Expand Up @@ -54,23 +55,23 @@ func main() {
}

func mergeCover(files []string, outFile string) error {
covs := make([][]*covLine, 0, len(files))
covs := make([][]*coverage.CovLine, 0, len(files))
for _, file := range files {
content, err := os.ReadFile(file)
if err != nil {
return err
}
covs = append(covs, parseCover(string(content)))
covs = append(covs, coverage.Parse(string(content)))
}
res := merge(covs)
res = filter(res, func(line *covLine) bool {
if strings.HasPrefix(line.prefix, "github.com/xhd2015/xgo/runtime/test") {
res := coverage.Merge(covs...)
res = coverage.Filter(res, func(line *coverage.CovLine) bool {
if strings.HasPrefix(line.Prefix, "github.com/xhd2015/xgo/runtime/test") {
return false
}
return true
})

mergedCov := formatCoverage("set", res)
mergedCov := coverage.Format("set", res)

var out io.Writer = os.Stdout
if outFile != "" {
Expand All @@ -84,93 +85,3 @@ func mergeCover(files []string, outFile string) error {
_, err := io.WriteString(out, mergedCov)
return err
}

func filter(covs []*covLine, check func(line *covLine) bool) []*covLine {
n := len(covs)
j := 0
for i := 0; i < n; i++ {
if check(covs[i]) {
covs[j] = covs[i]
j++
}
}
return covs[:j]
}

func formatCoverage(mode string, lines []*covLine) string {
strs := make([]string, 0, len(lines)+1)
strs = append(strs, "mode: "+mode)
for _, line := range lines {
strs = append(strs, line.prefix+" "+strconv.FormatInt(line.count, 10))
}
return strings.Join(strs, "\n")
}

func merge(covs [][]*covLine) []*covLine {
if len(covs) == 0 {
return nil
}
if len(covs) == 1 {
return covs[0]
}
result := covs[0]
for i := 1; i < len(covs); i++ {
result = mergeCov(result, covs[i])
}
return result
}

func mergeCov(a []*covLine, b []*covLine) []*covLine {
for _, line := range b {
idx := -1
for i := 0; i < len(a); i++ {
if a[i].prefix == line.prefix {
idx = i
break
}
}
if idx < 0 {
a = append(a, line)
} else {
// fmt.Printf("add %s %d %d\n", a[idx].prefix, a[idx].count, line.count)
a[idx].count += line.count
}
}
return a
}

type covLine struct {
prefix string
count int64
}

func parseCover(content string) []*covLine {
lines := strings.Split(content, "\n")
if len(lines) > 0 && strings.HasPrefix(lines[0], "mode:") {
lines = lines[1:]
}
covLines := make([]*covLine, 0, len(lines))
for _, line := range lines {
covLine := parseCovLine(line)
if covLine == nil {
continue
}
covLines = append(covLines, covLine)
}
return covLines
}

func parseCovLine(line string) *covLine {
idx := strings.LastIndex(line, " ")
if idx < 0 {
return nil
}
cnt, err := strconv.ParseInt(line[idx+1:], 10, 64)
if err != nil {
cnt = 0
}
return &covLine{
prefix: line[:idx],
count: cnt,
}
}
Loading

0 comments on commit dcc9e2a

Please sign in to comment.