github.com/jgarto/itcv@v0.0.0-20180826224514-4eea09c1aa0d/_vendor/src/golang.org/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", line, 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 // Merge samples from the same location. 95 j := 1 96 for i := 1; i < len(p.Blocks); i++ { 97 b := p.Blocks[i] 98 last := p.Blocks[j-1] 99 if b.StartLine == last.StartLine && 100 b.StartCol == last.StartCol && 101 b.EndLine == last.EndLine && 102 b.EndCol == last.EndCol { 103 if b.NumStmt != last.NumStmt { 104 return nil, fmt.Errorf("inconsistent NumStmt: changed from %d to %d", last.NumStmt, b.NumStmt) 105 } 106 if mode == "set" { 107 p.Blocks[j-1].Count |= b.Count 108 } else { 109 p.Blocks[j-1].Count += b.Count 110 } 111 continue 112 } 113 p.Blocks[j] = b 114 j++ 115 } 116 p.Blocks = p.Blocks[:j] 117 } 118 // Generate a sorted slice. 119 profiles := make([]*Profile, 0, len(files)) 120 for _, profile := range files { 121 profiles = append(profiles, profile) 122 } 123 sort.Sort(byFileName(profiles)) 124 return profiles, nil 125 } 126 127 type blocksByStart []ProfileBlock 128 129 func (b blocksByStart) Len() int { return len(b) } 130 func (b blocksByStart) Swap(i, j int) { b[i], b[j] = b[j], b[i] } 131 func (b blocksByStart) Less(i, j int) bool { 132 bi, bj := b[i], b[j] 133 return bi.StartLine < bj.StartLine || bi.StartLine == bj.StartLine && bi.StartCol < bj.StartCol 134 } 135 136 var lineRe = regexp.MustCompile(`^(.+):([0-9]+).([0-9]+),([0-9]+).([0-9]+) ([0-9]+) ([0-9]+)$`) 137 138 func toInt(s string) int { 139 i, err := strconv.Atoi(s) 140 if err != nil { 141 panic(err) 142 } 143 return i 144 } 145 146 // Boundary represents the position in a source file of the beginning or end of a 147 // block as reported by the coverage profile. In HTML mode, it will correspond to 148 // the opening or closing of a <span> tag and will be used to colorize the source 149 type Boundary struct { 150 Offset int // Location as a byte offset in the source file. 151 Start bool // Is this the start of a block? 152 Count int // Event count from the cover profile. 153 Norm float64 // Count normalized to [0..1]. 154 } 155 156 // Boundaries returns a Profile as a set of Boundary objects within the provided src. 157 func (p *Profile) Boundaries(src []byte) (boundaries []Boundary) { 158 // Find maximum count. 159 max := 0 160 for _, b := range p.Blocks { 161 if b.Count > max { 162 max = b.Count 163 } 164 } 165 // Divisor for normalization. 166 divisor := math.Log(float64(max)) 167 168 // boundary returns a Boundary, populating the Norm field with a normalized Count. 169 boundary := func(offset int, start bool, count int) Boundary { 170 b := Boundary{Offset: offset, Start: start, Count: count} 171 if !start || count == 0 { 172 return b 173 } 174 if max <= 1 { 175 b.Norm = 0.8 // Profile is in"set" mode; we want a heat map. Use cov8 in the CSS. 176 } else if count > 0 { 177 b.Norm = math.Log(float64(count)) / divisor 178 } 179 return b 180 } 181 182 line, col := 1, 2 // TODO: Why is this 2? 183 for si, bi := 0, 0; si < len(src) && bi < len(p.Blocks); { 184 b := p.Blocks[bi] 185 if b.StartLine == line && b.StartCol == col { 186 boundaries = append(boundaries, boundary(si, true, b.Count)) 187 } 188 if b.EndLine == line && b.EndCol == col || line > b.EndLine { 189 boundaries = append(boundaries, boundary(si, false, 0)) 190 bi++ 191 continue // Don't advance through src; maybe the next block starts here. 192 } 193 if src[si] == '\n' { 194 line++ 195 col = 0 196 } 197 col++ 198 si++ 199 } 200 sort.Sort(boundariesByPos(boundaries)) 201 return 202 } 203 204 type boundariesByPos []Boundary 205 206 func (b boundariesByPos) Len() int { return len(b) } 207 func (b boundariesByPos) Swap(i, j int) { b[i], b[j] = b[j], b[i] } 208 func (b boundariesByPos) Less(i, j int) bool { 209 if b[i].Offset == b[j].Offset { 210 return !b[i].Start && b[j].Start 211 } 212 return b[i].Offset < b[j].Offset 213 }