GoPLS Viewer

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