mirror of
https://github.com/golang/go.git
synced 2025-12-08 06:10:04 +00:00
crypto/tls: add flag to render HTML BoGo report
Updates the BoGo test runner to add a `-bogo-html-report` flag. When provided, an HTML report is written to the flag argument path. The report shows the fail/pass/skip status of run tests and allows sorting/searching the output. Change-Id: I8c704a51fbb03500f4134ebfaba06248baa3ca2f Reviewed-on: https://go-review.googlesource.com/c/go/+/684955 Auto-Submit: Daniel McCarney <daniel@binaryparadox.net> Reviewed-by: Roland Shoemaker <roland@golang.org> Reviewed-by: Carlos Amedee <carlos@golang.org> TryBot-Bypass: Daniel McCarney <daniel@binaryparadox.net> Commit-Queue: Carlos Amedee <carlos@golang.org>
This commit is contained in:
parent
adce7f196e
commit
630799c6c9
2 changed files with 152 additions and 1 deletions
|
|
@ -13,6 +13,7 @@ import (
|
||||||
"encoding/pem"
|
"encoding/pem"
|
||||||
"flag"
|
"flag"
|
||||||
"fmt"
|
"fmt"
|
||||||
|
"html/template"
|
||||||
"internal/byteorder"
|
"internal/byteorder"
|
||||||
"internal/testenv"
|
"internal/testenv"
|
||||||
"io"
|
"io"
|
||||||
|
|
@ -25,10 +26,13 @@ import (
|
||||||
"strconv"
|
"strconv"
|
||||||
"strings"
|
"strings"
|
||||||
"testing"
|
"testing"
|
||||||
|
"time"
|
||||||
|
|
||||||
"golang.org/x/crypto/cryptobyte"
|
"golang.org/x/crypto/cryptobyte"
|
||||||
)
|
)
|
||||||
|
|
||||||
|
const boringsslModVer = "v0.0.0-20250620172916-f51d8b099832"
|
||||||
|
|
||||||
var (
|
var (
|
||||||
port = flag.String("port", "", "")
|
port = flag.String("port", "", "")
|
||||||
server = flag.Bool("server", false, "")
|
server = flag.Bool("server", false, "")
|
||||||
|
|
@ -557,7 +561,6 @@ func TestBogoSuite(t *testing.T) {
|
||||||
if *bogoLocalDir != "" {
|
if *bogoLocalDir != "" {
|
||||||
bogoDir = *bogoLocalDir
|
bogoDir = *bogoLocalDir
|
||||||
} else {
|
} else {
|
||||||
const boringsslModVer = "v0.0.0-20250620172916-f51d8b099832"
|
|
||||||
bogoDir = cryptotest.FetchModule(t, "boringssl.googlesource.com/boringssl.git", boringsslModVer)
|
bogoDir = cryptotest.FetchModule(t, "boringssl.googlesource.com/boringssl.git", boringsslModVer)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
@ -606,6 +609,12 @@ func TestBogoSuite(t *testing.T) {
|
||||||
t.Fatalf("failed to parse results JSON: %s", err)
|
t.Fatalf("failed to parse results JSON: %s", err)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
if *bogoReport != "" {
|
||||||
|
if err := generateReport(results, *bogoReport); err != nil {
|
||||||
|
t.Fatalf("failed to generate report: %v", err)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
// assertResults contains test results we want to make sure
|
// assertResults contains test results we want to make sure
|
||||||
// are present in the output. They are only checked if -bogo-filter
|
// are present in the output. They are only checked if -bogo-filter
|
||||||
// was not passed.
|
// was not passed.
|
||||||
|
|
@ -655,6 +664,23 @@ func TestBogoSuite(t *testing.T) {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func generateReport(results bogoResults, outPath string) error {
|
||||||
|
data := reportData{
|
||||||
|
Results: results,
|
||||||
|
Timestamp: time.Unix(int64(results.SecondsSinceEpoch), 0).Format("2006-01-02 15:04:05"),
|
||||||
|
Revision: boringsslModVer,
|
||||||
|
}
|
||||||
|
|
||||||
|
tmpl := template.Must(template.New("report").Parse(reportTemplate))
|
||||||
|
file, err := os.Create(outPath)
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
defer file.Close()
|
||||||
|
|
||||||
|
return tmpl.Execute(file, data)
|
||||||
|
}
|
||||||
|
|
||||||
// bogoResults is a copy of boringssl.googlesource.com/boringssl/testresults.Results
|
// bogoResults is a copy of boringssl.googlesource.com/boringssl/testresults.Results
|
||||||
type bogoResults struct {
|
type bogoResults struct {
|
||||||
Version int `json:"version"`
|
Version int `json:"version"`
|
||||||
|
|
@ -669,3 +695,127 @@ type bogoResults struct {
|
||||||
Error string `json:"error,omitempty"`
|
Error string `json:"error,omitempty"`
|
||||||
} `json:"tests"`
|
} `json:"tests"`
|
||||||
}
|
}
|
||||||
|
|
||||||
|
type reportData struct {
|
||||||
|
Results bogoResults
|
||||||
|
SkipReasons map[string]string
|
||||||
|
Timestamp string
|
||||||
|
Revision string
|
||||||
|
}
|
||||||
|
|
||||||
|
const reportTemplate = `
|
||||||
|
<!DOCTYPE html>
|
||||||
|
<html>
|
||||||
|
<head>
|
||||||
|
<title>BoGo Results Report</title>
|
||||||
|
<style>
|
||||||
|
body { font-family: monospace; margin: 20px; }
|
||||||
|
.summary { background: #f5f5f5; padding: 10px; margin-bottom: 20px; }
|
||||||
|
.controls { margin-bottom: 10px; }
|
||||||
|
.controls input, select { margin-right: 10px; }
|
||||||
|
table { width: 100%; border-collapse: collapse; table-layout: fixed; }
|
||||||
|
th, td { border: 1px solid #ddd; padding: 8px; text-align: left; vertical-align: top; }
|
||||||
|
th { background-color: #f2f2f2; cursor: pointer; }
|
||||||
|
.name-col { width: 30%; }
|
||||||
|
.status-col { width: 8%; }
|
||||||
|
.actual-col { width: 8%; }
|
||||||
|
.expected-col { width: 8%; }
|
||||||
|
.error-col { width: 26%; }
|
||||||
|
.PASS { background-color: #d4edda; }
|
||||||
|
.FAIL { background-color: #f8d7da; }
|
||||||
|
.SKIP { background-color: #fff3cd; }
|
||||||
|
.error {
|
||||||
|
font-family: monospace;
|
||||||
|
font-size: 0.9em;
|
||||||
|
color: #721c24;
|
||||||
|
white-space: pre-wrap;
|
||||||
|
word-break: break-word;
|
||||||
|
}
|
||||||
|
</style>
|
||||||
|
</head>
|
||||||
|
<body>
|
||||||
|
<h1>BoGo Results Report</h1>
|
||||||
|
|
||||||
|
<div class="summary">
|
||||||
|
<strong>Generated:</strong> {{.Timestamp}} | <strong>BoGo Revision:</strong> {{.Revision}}<br>
|
||||||
|
{{range $status, $count := .Results.NumFailuresByType}}
|
||||||
|
<strong>{{$status}}:</strong> {{$count}} |
|
||||||
|
{{end}}
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div class="controls">
|
||||||
|
<input type="text" id="search" placeholder="Search tests..." onkeyup="filterTests()">
|
||||||
|
<select id="statusFilter" onchange="filterTests()">
|
||||||
|
<option value="">All</option>
|
||||||
|
<option value="FAIL">Failed</option>
|
||||||
|
<option value="PASS">Passed</option>
|
||||||
|
<option value="SKIP">Skipped</option>
|
||||||
|
</select>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<table id="resultsTable">
|
||||||
|
<thead>
|
||||||
|
<tr>
|
||||||
|
<th class="name-col" onclick="sortBy('name')">Test Name</th>
|
||||||
|
<th class="status-col" onclick="sortBy('status')">Status</th>
|
||||||
|
<th class="actual-col" onclick="sortBy('actual')">Actual</th>
|
||||||
|
<th class="expected-col" onclick="sortBy('expected')">Expected</th>
|
||||||
|
<th class="error-col">Error</th>
|
||||||
|
</tr>
|
||||||
|
</thead>
|
||||||
|
<tbody>
|
||||||
|
{{range $name, $test := .Results.Tests}}
|
||||||
|
<tr class="{{$test.Actual}}" data-name="{{$name}}" data-status="{{$test.Actual}}">
|
||||||
|
<td>{{$name}}</td>
|
||||||
|
<td>{{$test.Actual}}</td>
|
||||||
|
<td>{{$test.Actual}}</td>
|
||||||
|
<td>{{$test.Expected}}</td>
|
||||||
|
<td class="error">{{$test.Error}}</td>
|
||||||
|
</tr>
|
||||||
|
{{end}}
|
||||||
|
</tbody>
|
||||||
|
</table>
|
||||||
|
|
||||||
|
<script>
|
||||||
|
function filterTests() {
|
||||||
|
const search = document.getElementById('search').value.toLowerCase();
|
||||||
|
const status = document.getElementById('statusFilter').value;
|
||||||
|
const rows = document.querySelectorAll('#resultsTable tbody tr');
|
||||||
|
|
||||||
|
rows.forEach(row => {
|
||||||
|
const name = row.dataset.name.toLowerCase();
|
||||||
|
const rowStatus = row.dataset.status;
|
||||||
|
const matchesSearch = name.includes(search);
|
||||||
|
const matchesStatus = !status || rowStatus === status;
|
||||||
|
|
||||||
|
row.style.display = matchesSearch && matchesStatus ? '' : 'none';
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
function sortBy(column) {
|
||||||
|
const tbody = document.querySelector('#resultsTable tbody');
|
||||||
|
const rows = Array.from(tbody.querySelectorAll('tr'));
|
||||||
|
|
||||||
|
rows.sort((a, b) => {
|
||||||
|
if (column === 'status') {
|
||||||
|
const statusOrder = {'FAIL': 0, 'PASS': 1, 'SKIP': 2};
|
||||||
|
const aStatus = a.dataset.status;
|
||||||
|
const bStatus = b.dataset.status;
|
||||||
|
if (aStatus !== bStatus) {
|
||||||
|
return statusOrder[aStatus] - statusOrder[bStatus];
|
||||||
|
}
|
||||||
|
return a.dataset.name.localeCompare(b.dataset.name);
|
||||||
|
} else {
|
||||||
|
return a.dataset.name.localeCompare(b.dataset.name);
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
rows.forEach(row => tbody.appendChild(row));
|
||||||
|
filterTests();
|
||||||
|
}
|
||||||
|
|
||||||
|
sortBy("status");
|
||||||
|
</script>
|
||||||
|
</body>
|
||||||
|
</html>
|
||||||
|
`
|
||||||
|
|
|
||||||
|
|
@ -47,6 +47,7 @@ var (
|
||||||
bogoMode = flag.Bool("bogo-mode", false, "Enabled bogo shim mode, ignore everything else")
|
bogoMode = flag.Bool("bogo-mode", false, "Enabled bogo shim mode, ignore everything else")
|
||||||
bogoFilter = flag.String("bogo-filter", "", "BoGo test filter")
|
bogoFilter = flag.String("bogo-filter", "", "BoGo test filter")
|
||||||
bogoLocalDir = flag.String("bogo-local-dir", "", "Local BoGo to use, instead of fetching from source")
|
bogoLocalDir = flag.String("bogo-local-dir", "", "Local BoGo to use, instead of fetching from source")
|
||||||
|
bogoReport = flag.String("bogo-html-report", "", "File path to render an HTML report with BoGo results")
|
||||||
)
|
)
|
||||||
|
|
||||||
func runTestAndUpdateIfNeeded(t *testing.T, name string, run func(t *testing.T, update bool), wait bool) {
|
func runTestAndUpdateIfNeeded(t *testing.T, name string, run func(t *testing.T, update bool), wait bool) {
|
||||||
|
|
|
||||||
Loading…
Add table
Add a link
Reference in a new issue