Skip to content

Commit

Permalink
fix: merge coverage reports from each test rerun (#5360)
Browse files Browse the repository at this point in the history
* fix_: fix test coverage when rerunning tests

* fix_: make lint-fix

* chore_: change test coverage report artifact name

* chore_: added codeclimate.json to artifacts

* chore_: bring in gocovmerge util

---------

Co-authored-by: Siddarth Kumar <[email protected]>
  • Loading branch information
igor-sirotin and siddarthkay authored Jun 19, 2024
1 parent a300e12 commit d2f4cae
Show file tree
Hide file tree
Showing 11 changed files with 414 additions and 15 deletions.
3 changes: 2 additions & 1 deletion _assets/ci/Jenkinsfile.tests
Original file line number Diff line number Diff line change
Expand Up @@ -132,7 +132,8 @@ pipeline {
]) {
nix.shell('make test-unit V=1', pure: false)
}
archiveArtifacts('c.out')
sh "mv c.out test-coverage.out"
archiveArtifacts('test-coverage.out, coverage/codeclimate.json')
}
} }
post { cleanup { /* Leftover DB containers. */
Expand Down
17 changes: 13 additions & 4 deletions _assets/scripts/run_unit_tests.sh
Original file line number Diff line number Diff line change
Expand Up @@ -79,15 +79,24 @@ run_test_for_package() {
gotestsum_flags="${gotestsum_flags} --junitfile=${report_file} --rerun-fails-report=${rerun_report_file}"
fi

gotestsum --packages="${package}" ${gotestsum_flags} -- \
# Cleanup previous coverage reports
rm -f ${package_dir}/coverage.out.rerun.*

PACKAGE_DIR=${package_dir} gotestsum --packages="${package}" ${gotestsum_flags} --raw-command -- \
./_assets/scripts/test-with-coverage.sh \
${package} \
-v ${GOTEST_EXTRAFLAGS} \
-timeout "${package_timeout}" \
-count 1 \
-tags "${BUILD_TAGS}" \
-covermode=atomic \
-coverprofile="${coverage_file}" | \
-tags "${BUILD_TAGS}" |
redirect_stdout "${output_file}"

# Merge package coverage results
go run ./cmd/test-coverage-utils/gocovmerge.go ${package_dir}/coverage.out.rerun.* > ${coverage_file}

# Cleanup coverage reports
rm -f ${package_dir}/coverage.out.rerun.*

local go_test_exit=$?
echo "${go_test_exit}" > "${exit_code_file}"
if [[ "${go_test_exit}" -ne 0 ]]; then
Expand Down
7 changes: 7 additions & 0 deletions _assets/scripts/test-with-coverage.sh
Original file line number Diff line number Diff line change
@@ -0,0 +1,7 @@
#!/usr/bin/env bash
set -eu
coverage_file_path="${PACKAGE_DIR}/$(mktemp coverage.out.rerun.XXXXXXXXXX)"
go test -json \
-covermode=atomic \
-coverprofile="${coverage_file_path}" \
"$@"
111 changes: 111 additions & 0 deletions cmd/test-coverage-utils/gocovmerge.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,111 @@
// gocovmerge takes the results from multiple `go test -coverprofile` runs and
// merges them into one profile
package main

import (
"flag"
"fmt"
"io"
"log"
"os"
"sort"

"golang.org/x/tools/cover"
)

func mergeProfiles(p *cover.Profile, merge *cover.Profile) {
if p.Mode != merge.Mode {
log.Fatalf("cannot merge profiles with different modes")
}
// Since the blocks are sorted, we can keep track of where the last block
// was inserted and only look at the blocks after that as targets for merge
startIndex := 0
for _, b := range merge.Blocks {
startIndex = mergeProfileBlock(p, b, startIndex)
}
}

func mergeProfileBlock(p *cover.Profile, pb cover.ProfileBlock, startIndex int) int {
sortFunc := func(i int) bool {
pi := p.Blocks[i+startIndex]
return pi.StartLine >= pb.StartLine && (pi.StartLine != pb.StartLine || pi.StartCol >= pb.StartCol)
}

i := 0
if !sortFunc(i) {
i = sort.Search(len(p.Blocks)-startIndex, sortFunc)
}
i += startIndex
if i < len(p.Blocks) && p.Blocks[i].StartLine == pb.StartLine && p.Blocks[i].StartCol == pb.StartCol {
if p.Blocks[i].EndLine != pb.EndLine || p.Blocks[i].EndCol != pb.EndCol {
log.Fatalf("OVERLAP MERGE: %v %v %v", p.FileName, p.Blocks[i], pb)
}
switch p.Mode {
case "set":
p.Blocks[i].Count |= pb.Count
case "count", "atomic":
p.Blocks[i].Count += pb.Count
default:
log.Fatalf("unsupported covermode: '%s'", p.Mode)
}
} else {
if i > 0 {
pa := p.Blocks[i-1]
if pa.EndLine >= pb.EndLine && (pa.EndLine != pb.EndLine || pa.EndCol > pb.EndCol) {
log.Fatalf("OVERLAP BEFORE: %v %v %v", p.FileName, pa, pb)
}
}
if i < len(p.Blocks)-1 {
pa := p.Blocks[i+1]
if pa.StartLine <= pb.StartLine && (pa.StartLine != pb.StartLine || pa.StartCol < pb.StartCol) {
log.Fatalf("OVERLAP AFTER: %v %v %v", p.FileName, pa, pb)
}
}
p.Blocks = append(p.Blocks, cover.ProfileBlock{})
copy(p.Blocks[i+1:], p.Blocks[i:])
p.Blocks[i] = pb
}
return i + 1
}

func addProfile(profiles []*cover.Profile, p *cover.Profile) []*cover.Profile {
i := sort.Search(len(profiles), func(i int) bool { return profiles[i].FileName >= p.FileName })
if i < len(profiles) && profiles[i].FileName == p.FileName {
mergeProfiles(profiles[i], p)
} else {
profiles = append(profiles, nil)
copy(profiles[i+1:], profiles[i:])
profiles[i] = p
}
return profiles
}

func dumpProfiles(profiles []*cover.Profile, out io.Writer) {
if len(profiles) == 0 {
return
}
fmt.Fprintf(out, "mode: %s\n", profiles[0].Mode)
for _, p := range profiles {
for _, b := range p.Blocks {
fmt.Fprintf(out, "%s:%d.%d,%d.%d %d %d\n", p.FileName, b.StartLine, b.StartCol, b.EndLine, b.EndCol, b.NumStmt, b.Count)
}
}
}

func main() {
flag.Parse()

var merged []*cover.Profile

for _, file := range flag.Args() {
profiles, err := cover.ParseProfiles(file)
if err != nil {
log.Fatalf("failed to parse profiles: %v", err)
}
for _, p := range profiles {
merged = addProfile(merged, p)
}
}

dumpProfiles(merged, os.Stdout)
}
2 changes: 1 addition & 1 deletion go.mod
Original file line number Diff line number Diff line change
Expand Up @@ -102,6 +102,7 @@ require (
golang.org/x/net v0.25.0
golang.org/x/text v0.15.0
golang.org/x/time v0.5.0
golang.org/x/tools v0.21.0
)

require (
Expand Down Expand Up @@ -279,7 +280,6 @@ require (
golang.org/x/sync v0.7.0 // indirect
golang.org/x/sys v0.20.0 // indirect
golang.org/x/term v0.20.0 // indirect
golang.org/x/tools v0.21.0 // indirect
gopkg.in/natefinch/npipe.v2 v2.0.0-20160621034901-c1b8fa8bdcce // indirect
gopkg.in/yaml.v3 v3.0.1 // indirect
lukechampine.com/blake3 v1.2.1 // indirect
Expand Down

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

3 changes: 2 additions & 1 deletion telemetry/client.go
Original file line number Diff line number Diff line change
Expand Up @@ -14,8 +14,9 @@ import (
"github.com/status-im/status-go/protocol/transport"
"github.com/status-im/status-go/wakuv2"

v1protocol "github.com/status-im/status-go/protocol/v1"
v2protocol "github.com/waku-org/go-waku/waku/v2/protocol"

v1protocol "github.com/status-im/status-go/protocol/v1"
)

type TelemetryType string
Expand Down
5 changes: 3 additions & 2 deletions telemetry/client_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -10,12 +10,13 @@ import (
"go.uber.org/zap"
"google.golang.org/protobuf/proto"

v2protocol "github.com/waku-org/go-waku/waku/v2/protocol"
"github.com/waku-org/go-waku/waku/v2/protocol/pb"

"github.com/status-im/status-go/eth-node/types"
"github.com/status-im/status-go/protocol/transport"
v1protocol "github.com/status-im/status-go/protocol/v1"
"github.com/status-im/status-go/wakuv2"
v2protocol "github.com/waku-org/go-waku/waku/v2/protocol"
"github.com/waku-org/go-waku/waku/v2/protocol/pb"
)

func createMockServer(t *testing.T) *httptest.Server {
Expand Down
Loading

0 comments on commit d2f4cae

Please sign in to comment.