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