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:
Daniel McCarney 2025-06-30 12:45:36 -04:00 committed by Gopher Robot
parent adce7f196e
commit 630799c6c9
2 changed files with 152 additions and 1 deletions

View file

@ -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>
`

View file

@ -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) {