|
| 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) != true { |
| 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 | +} |
0 commit comments