github.com/sercand/please@v13.4.0+incompatible/src/test/istanbul_coverage.go (about)

     1  // Code for parsing Istanbul JSON coverage results.
     2  
     3  package test
     4  
     5  import (
     6  	"bytes"
     7  	"encoding/json"
     8  	"path/filepath"
     9  	"strings"
    10  
    11  	"github.com/thought-machine/please/src/core"
    12  )
    13  
    14  func looksLikeIstanbulCoverageResults(results []byte) bool {
    15  	// This works because this is the only JSON format that we accept. If we accept another
    16  	// we may need to get cleverer about it.
    17  	return bytes.HasPrefix(results, []byte("{"))
    18  }
    19  
    20  func parseIstanbulCoverageResults(target *core.BuildTarget, coverage *core.TestCoverage, data []byte) error {
    21  	files := map[string]istanbulFile{}
    22  	if err := json.Unmarshal(data, &files); err != nil {
    23  		return err
    24  	}
    25  	for filename, file := range files {
    26  		coverage.Files[sanitiseFileName(target, filename)] = file.toLineCoverage()
    27  	}
    28  	coverage.Tests[target.Label] = coverage.Files
    29  	return nil
    30  }
    31  
    32  type istanbulFile struct {
    33  	// StatementMap identifies the start and end for each statement.
    34  	StatementMap map[string]istanbulLocation `json:"statementMap"`
    35  	// Statements identifies the covered statements.
    36  	Statements map[string]int `json:"s"`
    37  }
    38  
    39  // An istanbulLocation defines a start/end location in the instrumented source code.
    40  type istanbulLocation struct {
    41  	Start istanbulLineLocation `json:"start"`
    42  	End   istanbulLineLocation `json:"end"`
    43  }
    44  
    45  // An istanbulLineLocation defines a single location in the instrumented source code.
    46  type istanbulLineLocation struct {
    47  	Column int `json:"column"`
    48  	Line   int `json:"line"`
    49  }
    50  
    51  // toLineCoverage coverts this object to our internal format.
    52  func (file *istanbulFile) toLineCoverage() []core.LineCoverage {
    53  	ret := make([]core.LineCoverage, file.maxLineNumber())
    54  	for statement, count := range file.Statements {
    55  		val := core.Uncovered
    56  		if count > 0 {
    57  			val = core.Covered
    58  		}
    59  		s := file.StatementMap[statement]
    60  		for i := s.Start.Line; i <= s.End.Line; i++ {
    61  			if val > ret[i-1] {
    62  				ret[i-1] = val // -1 because 1-indexed
    63  			}
    64  		}
    65  	}
    66  	return ret
    67  }
    68  
    69  // maxLineNumber returns the highest line number present in this file.
    70  func (file *istanbulFile) maxLineNumber() int {
    71  	max := 0
    72  	for _, s := range file.StatementMap {
    73  		if s.End.Line > max {
    74  			max = s.End.Line
    75  		}
    76  	}
    77  	return max
    78  }
    79  
    80  // sanitiseFileName strips out any build/test paths found in the given file.
    81  func sanitiseFileName(target *core.BuildTarget, filename string) string {
    82  	if s := sanitiseFileNameDir(filename, target.OutDir(), false); s != "" {
    83  		return s
    84  	} else if s := sanitiseFileNameDir(filename, target.TmpDir(), true); s != "" {
    85  		return s
    86  	} else if s := sanitiseFileNameDir(filename, target.TestDir(), true); s != "" {
    87  		return s
    88  	}
    89  	return filename
    90  }
    91  
    92  // sanitiseFileNameDir attempts to strip off a directory from the middle of a given path.
    93  // It returns a non-empty string if successful.
    94  // If matchAnyLastDir is true it will match any directory for the last component.
    95  func sanitiseFileNameDir(filename string, dir string, matchAnyLastDir bool) string {
    96  	if matchAnyLastDir {
    97  		dir = filepath.Dir(dir)
    98  	}
    99  	if index := strings.Index(filename, dir); index != -1 {
   100  		ret := filename[index+len(dir)+1:]
   101  		if matchAnyLastDir {
   102  			if index := strings.IndexRune(ret, filepath.Separator); index != -1 {
   103  				return ret[index+1:]
   104  			}
   105  		}
   106  		return ret
   107  	}
   108  	return ""
   109  }