github.com/status-im/status-go@v1.1.0/cmd/test-coverage-utils/gocovmerge.go (about) 1 // gocovmerge takes the results from multiple `go test -coverprofile` runs and 2 // merges them into one profile 3 package main 4 5 import ( 6 "flag" 7 "fmt" 8 "io" 9 "log" 10 "os" 11 "sort" 12 13 "golang.org/x/tools/cover" 14 ) 15 16 func mergeProfiles(p *cover.Profile, merge *cover.Profile) { 17 if p.Mode != merge.Mode { 18 log.Fatalf("cannot merge profiles with different modes") 19 } 20 // Since the blocks are sorted, we can keep track of where the last block 21 // was inserted and only look at the blocks after that as targets for merge 22 startIndex := 0 23 for _, b := range merge.Blocks { 24 startIndex = mergeProfileBlock(p, b, startIndex) 25 } 26 } 27 28 func mergeProfileBlock(p *cover.Profile, pb cover.ProfileBlock, startIndex int) int { 29 sortFunc := func(i int) bool { 30 pi := p.Blocks[i+startIndex] 31 return pi.StartLine >= pb.StartLine && (pi.StartLine != pb.StartLine || pi.StartCol >= pb.StartCol) 32 } 33 34 i := 0 35 if !sortFunc(i) { 36 i = sort.Search(len(p.Blocks)-startIndex, sortFunc) 37 } 38 i += startIndex 39 if i < len(p.Blocks) && p.Blocks[i].StartLine == pb.StartLine && p.Blocks[i].StartCol == pb.StartCol { 40 if p.Blocks[i].EndLine != pb.EndLine || p.Blocks[i].EndCol != pb.EndCol { 41 log.Fatalf("OVERLAP MERGE: %v %v %v", p.FileName, p.Blocks[i], pb) 42 } 43 switch p.Mode { 44 case "set": 45 p.Blocks[i].Count |= pb.Count 46 case "count", "atomic": 47 p.Blocks[i].Count += pb.Count 48 default: 49 log.Fatalf("unsupported covermode: '%s'", p.Mode) 50 } 51 } else { 52 if i > 0 { 53 pa := p.Blocks[i-1] 54 if pa.EndLine >= pb.EndLine && (pa.EndLine != pb.EndLine || pa.EndCol > pb.EndCol) { 55 log.Fatalf("OVERLAP BEFORE: %v %v %v", p.FileName, pa, pb) 56 } 57 } 58 if i < len(p.Blocks)-1 { 59 pa := p.Blocks[i+1] 60 if pa.StartLine <= pb.StartLine && (pa.StartLine != pb.StartLine || pa.StartCol < pb.StartCol) { 61 log.Fatalf("OVERLAP AFTER: %v %v %v", p.FileName, pa, pb) 62 } 63 } 64 p.Blocks = append(p.Blocks, cover.ProfileBlock{}) 65 copy(p.Blocks[i+1:], p.Blocks[i:]) 66 p.Blocks[i] = pb 67 } 68 return i + 1 69 } 70 71 func addProfile(profiles []*cover.Profile, p *cover.Profile) []*cover.Profile { 72 i := sort.Search(len(profiles), func(i int) bool { return profiles[i].FileName >= p.FileName }) 73 if i < len(profiles) && profiles[i].FileName == p.FileName { 74 mergeProfiles(profiles[i], p) 75 } else { 76 profiles = append(profiles, nil) 77 copy(profiles[i+1:], profiles[i:]) 78 profiles[i] = p 79 } 80 return profiles 81 } 82 83 func dumpProfiles(profiles []*cover.Profile, out io.Writer) { 84 if len(profiles) == 0 { 85 return 86 } 87 fmt.Fprintf(out, "mode: %s\n", profiles[0].Mode) 88 for _, p := range profiles { 89 for _, b := range p.Blocks { 90 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) 91 } 92 } 93 } 94 95 func main() { 96 flag.Parse() 97 98 var merged []*cover.Profile 99 100 for _, file := range flag.Args() { 101 profiles, err := cover.ParseProfiles(file) 102 if err != nil { 103 log.Fatalf("failed to parse profiles: %v", err) 104 } 105 for _, p := range profiles { 106 merged = addProfile(merged, p) 107 } 108 } 109 110 dumpProfiles(merged, os.Stdout) 111 }