github.com/panjjo/go@v0.0.0-20161104043856-d62b31386338/src/cmd/cover/html.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 main 6 7 import ( 8 "bufio" 9 "bytes" 10 "cmd/internal/browser" 11 "fmt" 12 "html/template" 13 "io" 14 "io/ioutil" 15 "math" 16 "os" 17 "path/filepath" 18 ) 19 20 // htmlOutput reads the profile data from profile and generates an HTML 21 // coverage report, writing it to outfile. If outfile is empty, 22 // it writes the report to a temporary file and opens it in a web browser. 23 func htmlOutput(profile, outfile string) error { 24 profiles, err := ParseProfiles(profile) 25 if err != nil { 26 return err 27 } 28 29 var d templateData 30 31 for _, profile := range profiles { 32 fn := profile.FileName 33 if profile.Mode == "set" { 34 d.Set = true 35 } 36 file, err := findFile(fn) 37 if err != nil { 38 return err 39 } 40 src, err := ioutil.ReadFile(file) 41 if err != nil { 42 return fmt.Errorf("can't read %q: %v", fn, err) 43 } 44 var buf bytes.Buffer 45 err = htmlGen(&buf, src, profile.Boundaries(src)) 46 if err != nil { 47 return err 48 } 49 d.Files = append(d.Files, &templateFile{ 50 Name: fn, 51 Body: template.HTML(buf.String()), 52 Coverage: percentCovered(profile), 53 }) 54 } 55 56 var out *os.File 57 if outfile == "" { 58 var dir string 59 dir, err = ioutil.TempDir("", "cover") 60 if err != nil { 61 return err 62 } 63 out, err = os.Create(filepath.Join(dir, "coverage.html")) 64 } else { 65 out, err = os.Create(outfile) 66 } 67 err = htmlTemplate.Execute(out, d) 68 if err == nil { 69 err = out.Close() 70 } 71 if err != nil { 72 return err 73 } 74 75 if outfile == "" { 76 if !browser.Open("file://" + out.Name()) { 77 fmt.Fprintf(os.Stderr, "HTML output written to %s\n", out.Name()) 78 } 79 } 80 81 return nil 82 } 83 84 // percentCovered returns, as a percentage, the fraction of the statements in 85 // the profile covered by the test run. 86 // In effect, it reports the coverage of a given source file. 87 func percentCovered(p *Profile) float64 { 88 var total, covered int64 89 for _, b := range p.Blocks { 90 total += int64(b.NumStmt) 91 if b.Count > 0 { 92 covered += int64(b.NumStmt) 93 } 94 } 95 if total == 0 { 96 return 0 97 } 98 return float64(covered) / float64(total) * 100 99 } 100 101 // htmlGen generates an HTML coverage report with the provided filename, 102 // source code, and tokens, and writes it to the given Writer. 103 func htmlGen(w io.Writer, src []byte, boundaries []Boundary) error { 104 dst := bufio.NewWriter(w) 105 for i := range src { 106 for len(boundaries) > 0 && boundaries[0].Offset == i { 107 b := boundaries[0] 108 if b.Start { 109 n := 0 110 if b.Count > 0 { 111 n = int(math.Floor(b.Norm*9)) + 1 112 } 113 fmt.Fprintf(dst, `<span class="cov%v" title="%v">`, n, b.Count) 114 } else { 115 dst.WriteString("</span>") 116 } 117 boundaries = boundaries[1:] 118 } 119 switch b := src[i]; b { 120 case '>': 121 dst.WriteString(">") 122 case '<': 123 dst.WriteString("<") 124 case '&': 125 dst.WriteString("&") 126 case '\t': 127 dst.WriteString(" ") 128 default: 129 dst.WriteByte(b) 130 } 131 } 132 return dst.Flush() 133 } 134 135 // rgb returns an rgb value for the specified coverage value 136 // between 0 (no coverage) and 10 (max coverage). 137 func rgb(n int) string { 138 if n == 0 { 139 return "rgb(192, 0, 0)" // Red 140 } 141 // Gradient from gray to green. 142 r := 128 - 12*(n-1) 143 g := 128 + 12*(n-1) 144 b := 128 + 3*(n-1) 145 return fmt.Sprintf("rgb(%v, %v, %v)", r, g, b) 146 } 147 148 // colors generates the CSS rules for coverage colors. 149 func colors() template.CSS { 150 var buf bytes.Buffer 151 for i := 0; i < 11; i++ { 152 fmt.Fprintf(&buf, ".cov%v { color: %v }\n", i, rgb(i)) 153 } 154 return template.CSS(buf.String()) 155 } 156 157 var htmlTemplate = template.Must(template.New("html").Funcs(template.FuncMap{ 158 "colors": colors, 159 }).Parse(tmplHTML)) 160 161 type templateData struct { 162 Files []*templateFile 163 Set bool 164 } 165 166 type templateFile struct { 167 Name string 168 Body template.HTML 169 Coverage float64 170 } 171 172 const tmplHTML = ` 173 <!DOCTYPE html> 174 <html> 175 <head> 176 <meta http-equiv="Content-Type" content="text/html; charset=utf-8"> 177 <style> 178 body { 179 background: black; 180 color: rgb(80, 80, 80); 181 } 182 body, pre, #legend span { 183 font-family: Menlo, monospace; 184 font-weight: bold; 185 } 186 #topbar { 187 background: black; 188 position: fixed; 189 top: 0; left: 0; right: 0; 190 height: 42px; 191 border-bottom: 1px solid rgb(80, 80, 80); 192 } 193 #content { 194 margin-top: 50px; 195 } 196 #nav, #legend { 197 float: left; 198 margin-left: 10px; 199 } 200 #legend { 201 margin-top: 12px; 202 } 203 #nav { 204 margin-top: 10px; 205 } 206 #legend span { 207 margin: 0 5px; 208 } 209 {{colors}} 210 </style> 211 </head> 212 <body> 213 <div id="topbar"> 214 <div id="nav"> 215 <select id="files"> 216 {{range $i, $f := .Files}} 217 <option value="file{{$i}}">{{$f.Name}} ({{printf "%.1f" $f.Coverage}}%)</option> 218 {{end}} 219 </select> 220 </div> 221 <div id="legend"> 222 <span>not tracked</span> 223 {{if .Set}} 224 <span class="cov0">not covered</span> 225 <span class="cov8">covered</span> 226 {{else}} 227 <span class="cov0">no coverage</span> 228 <span class="cov1">low coverage</span> 229 <span class="cov2">*</span> 230 <span class="cov3">*</span> 231 <span class="cov4">*</span> 232 <span class="cov5">*</span> 233 <span class="cov6">*</span> 234 <span class="cov7">*</span> 235 <span class="cov8">*</span> 236 <span class="cov9">*</span> 237 <span class="cov10">high coverage</span> 238 {{end}} 239 </div> 240 </div> 241 <div id="content"> 242 {{range $i, $f := .Files}} 243 <pre class="file" id="file{{$i}}" style="display: none">{{$f.Body}}</pre> 244 {{end}} 245 </div> 246 </body> 247 <script> 248 (function() { 249 var files = document.getElementById('files'); 250 var visible; 251 files.addEventListener('change', onChange, false); 252 function select(part) { 253 if (visible) 254 visible.style.display = 'none'; 255 visible = document.getElementById(part); 256 if (!visible) 257 return; 258 files.value = part; 259 visible.style.display = 'block'; 260 location.hash = part; 261 } 262 function onChange() { 263 select(files.value); 264 window.scrollTo(0, 0); 265 } 266 if (location.hash != "") { 267 select(location.hash.substr(1)); 268 } 269 if (!visible) { 270 select("file0"); 271 } 272 })(); 273 </script> 274 </html> 275 `