github.com/graybobo/golang.org-package-offline-cache@v0.0.0-20200626051047-6608995c132f/x/tools/cover/profile.go (about) 1 // Copyright 2013 The Go Authors. All rights reserved. 2 // Use of this source code is governed by a BSD-style 3 // license that can be found in the LICENSE file. 4 5 // Package cover provides support for parsing coverage profiles 6 // generated by "go test -coverprofile=cover.out". 7 package cover // import "golang.org/x/tools/cover" 8 9 import ( 10 "bufio" 11 "fmt" 12 "math" 13 "os" 14 "regexp" 15 "sort" 16 "strconv" 17 "strings" 18 ) 19 20 // Profile represents the profiling data for a specific file. 21 type Profile struct { 22 FileName string 23 Mode string 24 Blocks []ProfileBlock 25 } 26 27 // ProfileBlock represents a single block of profiling data. 28 type ProfileBlock struct { 29 StartLine, StartCol int 30 EndLine, EndCol int 31 NumStmt, Count int 32 } 33 34 type byFileName []*Profile 35 36 func (p byFileName) Len() int { return len(p) } 37 func (p byFileName) Less(i, j int) bool { return p[i].FileName < p[j].FileName } 38 func (p byFileName) Swap(i, j int) { p[i], p[j] = p[j], p[i] } 39 40 // ParseProfiles parses profile data in the specified file and returns a 41 // Profile for each source file described therein. 42 func ParseProfiles(fileName string) ([]*Profile, error) { 43 pf, err := os.Open(fileName) 44 if err != nil { 45 return nil, err 46 } 47 defer pf.Close() 48 49 files := make(map[string]*Profile) 50 buf := bufio.NewReader(pf) 51 // First line is "mode: foo", where foo is "set", "count", or "atomic". 52 // Rest of file is in the format 53 // encoding/base64/base64.go:34.44,37.40 3 1 54 // where the fields are: name.go:line.column,line.column numberOfStatements count 55 s := bufio.NewScanner(buf) 56 mode := "" 57 for s.Scan() { 58 line := s.Text() 59 if mode == "" { 60 const p = "mode: " 61 if !strings.HasPrefix(line, p) || line == p { 62 return nil, fmt.Errorf("bad mode line: %v", line) 63 } 64 mode = line[len(p):] 65 continue 66 } 67 m := lineRe.FindStringSubmatch(line) 68 if m == nil { 69 return nil, fmt.Errorf("line %q doesn't match expected format: %v", m, lineRe) 70 } 71 fn := m[1] 72 p := files[fn] 73 if p == nil { 74 p = &Profile{ 75 FileName: fn, 76 Mode: mode, 77 } 78 files[fn] = p 79 } 80 p.Blocks = append(p.Blocks, ProfileBlock{ 81 StartLine: toInt(m[2]), 82 StartCol: toInt(m[3]), 83 EndLine: toInt(m[4]), 84 EndCol: toInt(m[5]), 85 NumStmt: toInt(m[6]), 86 Count: toInt(m[7]), 87 }) 88 } 89 if err := s.Err(); err != nil { 90 return nil, err 91 } 92 for _, p := range files { 93 sort.Sort(blocksByStart(p.Blocks)) 94 } 95 // Generate a sorted slice. 96 profiles := make([]*Profile, 0, len(files)) 97 for _, profile := range files { 98 profiles = append(profiles, profile) 99 } 100 sort.Sort(byFileName(profiles)) 101 return profiles, nil 102 } 103 104 type blocksByStart []ProfileBlock 105 106 func (b blocksByStart) Len() int { return len(b) } 107 func (b blocksByStart) Swap(i, j int) { b[i], b[j] = b[j], b[i] } 108 func (b blocksByStart) Less(i, j int) bool { 109 bi, bj := b[i], b[j] 110 return bi.StartLine < bj.StartLine || bi.StartLine == bj.StartLine && bi.StartCol < bj.StartCol 111 } 112 113 var lineRe = regexp.MustCompile(`^(.+):([0-9]+).([0-9]+),([0-9]+).([0-9]+) ([0-9]+) ([0-9]+)$`) 114 115 func toInt(s string) int { 116 i, err := strconv.Atoi(s) 117 if err != nil { 118 panic(err) 119 } 120 return i 121 } 122 123 // Boundary represents the position in a source file of the beginning or end of a 124 // block as reported by the coverage profile. In HTML mode, it will correspond to 125 // the opening or closing of a <span> tag and will be used to colorize the source 126 type Boundary struct { 127 Offset int // Location as a byte offset in the source file. 128 Start bool // Is this the start of a block? 129 Count int // Event count from the cover profile. 130 Norm float64 // Count normalized to [0..1]. 131 } 132 133 // Boundaries returns a Profile as a set of Boundary objects within the provided src. 134 func (p *Profile) Boundaries(src []byte) (boundaries []Boundary) { 135 // Find maximum count. 136 max := 0 137 for _, b := range p.Blocks { 138 if b.Count > max { 139 max = b.Count 140 } 141 } 142 // Divisor for normalization. 143 divisor := math.Log(float64(max)) 144 145 // boundary returns a Boundary, populating the Norm field with a normalized Count. 146 boundary := func(offset int, start bool, count int) Boundary { 147 b := Boundary{Offset: offset, Start: start, Count: count} 148 if !start || count == 0 { 149 return b 150 } 151 if max <= 1 { 152 b.Norm = 0.8 // Profile is in"set" mode; we want a heat map. Use cov8 in the CSS. 153 } else if count > 0 { 154 b.Norm = math.Log(float64(count)) / divisor 155 } 156 return b 157 } 158 159 line, col := 1, 2 // TODO: Why is this 2? 160 for si, bi := 0, 0; si < len(src) && bi < len(p.Blocks); { 161 b := p.Blocks[bi] 162 if b.StartLine == line && b.StartCol == col { 163 boundaries = append(boundaries, boundary(si, true, b.Count)) 164 } 165 if b.EndLine == line && b.EndCol == col || line > b.EndLine { 166 boundaries = append(boundaries, boundary(si, false, 0)) 167 bi++ 168 continue // Don't advance through src; maybe the next block starts here. 169 } 170 if src[si] == '\n' { 171 line++ 172 col = 0 173 } 174 col++ 175 si++ 176 } 177 sort.Sort(boundariesByPos(boundaries)) 178 return 179 } 180 181 type boundariesByPos []Boundary 182 183 func (b boundariesByPos) Len() int { return len(b) } 184 func (b boundariesByPos) Swap(i, j int) { b[i], b[j] = b[j], b[i] } 185 func (b boundariesByPos) Less(i, j int) bool { 186 if b[i].Offset == b[j].Offset { 187 return !b[i].Start && b[j].Start 188 } 189 return b[i].Offset < b[j].Offset 190 }