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  }