github.com/aclements/go-misc@v0.0.0-20240129233631-2f6ede80790c/findflakes/html.go (about)

     1  // Copyright 2015 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  	"html/template"
     9  	"io"
    10  	"log"
    11  	"reflect"
    12  )
    13  
    14  // TODO: OS/Arch counts
    15  
    16  const htmlReport = `
    17  <html>
    18    <head>
    19      <meta charset="utf-8" />
    20      <title>Top test failures</title>
    21      <style>
    22  body {
    23    font-family: sans-serif;
    24    color: #222;
    25  }
    26  a {
    27    text-decoration: none;
    28  }
    29  table {
    30    border-spacing: 0;
    31    border-collapse: collapse;
    32  }
    33  table#failures {
    34    width: 100%;
    35    max-width: 100%;
    36  }
    37  table>caption {
    38    padding-top: 8px;
    39    padding-bottom: 8px;
    40    color: #777;
    41    text-align: left;
    42  }
    43  table>tbody>tr>td, table>tbody>tr>th, table>thead>tr>th {
    44    padding: 8px;
    45    vertical-align: top;
    46    line-height: 1.4;
    47  }
    48  table.lined>tbody>tr:not(.expand)>td, table.lined>tbody>tr:not(.expand)>th {
    49    border-top: 1px solid #ddd;
    50  }
    51  table.lined>thead>tr>th {
    52    vertical-align: bottom;
    53    border-bottom: 2px solid #ddd;
    54    border-top: 0px;
    55  }
    56  th {
    57    text-align: left;
    58  }
    59  table#failures>tbody>tr:not(.expand) {
    60    cursor: pointer;
    61  }
    62  table#failures>tbody>tr:not(.expand):hover {
    63    color: #337ab7;
    64  }
    65  td.pct, th.pct {
    66    text-align: right;
    67  }
    68  td.plus {
    69    color: #337ab7;
    70  }
    71  tr.expand {
    72    display: none;
    73  }
    74  table>tbody>tr.expand>td {
    75    padding-top: 0px;
    76  }
    77  a.hash {
    78    font-family: monospace;
    79    font-size: 120%;
    80  }
    81  .toggleRow {
    82    display: none;
    83  }
    84      </style>
    85      <script src="https://ajax.googleapis.com/ajax/libs/jquery/1.8.2/jquery.min.js"></script>
    86    </head>
    87    <body>
    88      <table id="failures" class="lined">
    89        <caption>Test failures as of {{(lastRev .).Date.Format "02 Jan 15:04 2006"}}, sorted by chance the failure is still happening. Click row for details and culprits.</caption>
    90        <thead>
    91          <tr><th></th><th class="pct">P(current)</th><th class="pct">P(failure)</th><th style="width:100%">Failure</th></tr>
    92        </thead>
    93        {{range $i, $class := .}}
    94        {{$failuresByT := groupByT .Failures}}
    95        <tr><td class="plus">+</td><td class="pct">{{pct .Current}}</td><td class="pct">{{pct .Latest.FailureProbability}}</td><td>{{.Class.String}}</td></tr>
    96        <tr class="expand"><td></td><td colspan="3">
    97          <table>
    98            <tr><th>Chance failure is still happening</th><td>{{pct .Current}}</td></tr>
    99            {{with .Latest}}
   100            <tr><th>Failure probability</th><td>{{pct .FailureProbability}} ({{.Failures}} of {{numCommits .}} commits)</td></tr>
   101            {{if eq (numCommits .) 1}}
   102            <tr><th>Observed</th><td>{{template "observation" (index $failuresByT .First)}}</td></tr>
   103            {{else}}
   104            <tr><th>First observed</th><td>{{template "observation" (index $failuresByT .First)}}</td></tr>
   105            {{if ge (numCommits .) 2}}
   106            <tr><th></th><td><a href="#" class="toggleRows">show other observations</a></td></tr>
   107            {{range $_, $t := (slice .Times 1 -1)}}
   108            <tr class="toggleRow"><th></th><td>{{template "observation" (index $failuresByT $t)}}</td></tr>
   109            {{end}}
   110            {{end}}
   111            <tr><th>Last observed</th><td>{{template "observation" (index $failuresByT .Last)}}</td></tr>
   112            <tr><th>Likely culprits</th>
   113  	    <td style="padding:0px">
   114  	      <table>
   115  		{{range (.Culprits 0.9 10)}}
   116  		<tr><td class="pct">{{pct .P}}</td><td>{{template "revSubject" (index $class.Revs .T)}}</td></tr>
   117  		{{end}}
   118  	      </table>
   119  	    </td>
   120            </tr>
   121            {{end}}{{/* numCommits == 1*/}}
   122            {{end}}{{/* with .Latest */}}
   123            {{with (slice .Test.All 1 (len .Test.All))}}
   124              <tr><th>{{len .}} past failure(s)</th><td><a href="#" class="toggleRows">show</a></td></tr>
   125              {{range .}}
   126                <tr class="toggleRow"><th></th><td>{{template "observation" (index $failuresByT .First)}} to {{template "observation" (index $failuresByT .Last)}}; {{pct .FailureProbability}} failure probability</td></tr>
   127              {{end}}
   128            {{else}}
   129              <tr><th>No known past failures</th></tr>
   130            {{end}}
   131          </table>
   132        </td></tr>
   133        {{end}}
   134      </table>
   135      <script>
   136  $("#failures").click(function(ev) {
   137      var target = $(ev.target);
   138      if (target.closest("table").filter("#failures").length === 0)
   139        return;
   140  
   141      ev.stopPropagation();
   142      var tr = target.closest("tr");
   143  
   144      if (!tr.hasClass("expand")) {
   145          tr.next().toggle();
   146      }
   147  });
   148  $("a.toggleRows").click(function(ev) {
   149      ev.stopPropagation();
   150      $(ev.target).closest("tr").nextUntil(":not(.toggleRow)").toggle();
   151      var text = $(ev.target).text();
   152      text = text.replace(/show|hide/, function(x) { return x === "show" ? "hide" : "show"; });
   153      $(ev.target).text(text);
   154      return false;
   155  });
   156      </script>
   157    </body>
   158  </html>
   159  
   160  {{/* observation expands a []*failure in to an observation line. */}}
   161  {{define "observation"}}
   162  {{$first := (index . 0)}}
   163  {{template "revDate" $first.Rev}} ({{$first.CommitsAgo}} commits ago) on{{range .}} <a href="{{.Build.LogURL}}">{{.Build.Builder}}</a>{{end}}
   164  {{end}}
   165  {{/* revLink expands a *Revision to a link to that commit. */}}
   166  {{define "revLink"}}
   167  <a href="https://github.com/golang/go/commit/{{.Revision}}" class="hash">{{printf "%.7s" .Revision}}</a>
   168  {{end}}
   169  {{/* revDate expands a *Revision to the commit's hash and date. */}}
   170  {{define "revDate"}}
   171  {{template "revLink" .}} {{.Date.Format "02 Jan 15:04 2006"}}
   172  {{end}}
   173  {{/* revSubject expands a *Revision to the commit's hash and subject. */}}
   174  {{define "revSubject"}}
   175  {{template "revLink" .}} {{.Subject}}
   176  {{end}}
   177  `
   178  
   179  var htmlFuncs = template.FuncMap(map[string]interface{}{
   180  	"pct": pct,
   181  	"lastRev": func(classes []*failureClass) *Revision {
   182  		// TODO: Ugh. It's lame that the same Revs is in every
   183  		// failureClass.
   184  		revs := classes[0].Revs
   185  		return revs[len(revs)-1]
   186  	},
   187  	"numCommits": func(r FlakeRegion) int {
   188  		return r.Last - r.First + 1
   189  	},
   190  	"groupByT": func(failures []*failure) map[int][]*failure {
   191  		out := make(map[int][]*failure)
   192  		if len(failures) == 0 {
   193  			return out
   194  		}
   195  		lastI, lastT := 0, failures[0].T
   196  		for i := 1; i < len(failures); i++ {
   197  			if failures[i].T != lastT {
   198  				out[lastT] = failures[lastI:i]
   199  				lastI, lastT = i, failures[i].T
   200  			}
   201  		}
   202  		out[lastT] = failures[lastI:]
   203  		return out
   204  	},
   205  	"slice": func(v interface{}, start, end int) interface{} {
   206  		val := reflect.ValueOf(v)
   207  		if start < 0 {
   208  			start = val.Len() + start
   209  		}
   210  		if end < 0 {
   211  			end = val.Len() + end
   212  		}
   213  		return val.Slice(start, end).Interface()
   214  	},
   215  })
   216  
   217  var htmlTemplate = template.Must(template.New("report").Funcs(htmlFuncs).Parse(htmlReport))
   218  
   219  func printHTMLReport(w io.Writer, classes []*failureClass) {
   220  	err := htmlTemplate.Execute(w, classes)
   221  	if err != nil {
   222  		log.Fatal(err)
   223  	}
   224  }