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 }