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