2020-04-04 11:45:26 +03:00
// Copyright 2020 The Gitea Authors. All rights reserved.
// Copyright (c) 2015, Wade Simmons
2022-11-27 21:20:29 +03:00
// SPDX-License-Identifier: MIT
2020-04-04 11:45:26 +03:00
// gocovmerge takes the results from multiple `go test -coverprofile` runs and
// merges them into one profile
2021-08-24 19:47:09 +03:00
//go:build ignore
2020-04-04 11:45:26 +03:00
package main
import (
"flag"
"fmt"
"io"
"log"
"os"
"sort"
"golang.org/x/tools/cover"
)
2022-01-20 20:46:10 +03:00
func mergeProfiles ( p , merge * cover . Profile ) {
2020-04-04 11:45:26 +03:00
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 ) != true {
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 {
2021-08-26 11:50:04 +03:00
log . Fatalf ( "failed to parse profile '%s': %v" , file , err )
2020-04-04 11:45:26 +03:00
}
for _ , p := range profiles {
merged = addProfile ( merged , p )
}
}
dumpProfiles ( merged , os . Stdout )
}