Ivan Valdes f6fa1daab7
tools/testgrid-analysis: fix var-naming warnings
Signed-off-by: Ivan Valdes <ivan@vald.es>
2024-05-03 10:41:17 -07:00

156 lines
5.2 KiB
Go

// Copyright 2024 The etcd Authors
//
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at
//
// http://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and
// limitations under the License.
package cmd
import (
"fmt"
"io"
"net/http"
"os"
"strings"
"time"
apipb "github.com/GoogleCloudPlatform/testgrid/pb/api/v1"
statuspb "github.com/GoogleCloudPlatform/testgrid/pb/test_status"
"google.golang.org/protobuf/encoding/protojson"
)
var (
validTestStatuses = []statuspb.TestStatus{statuspb.TestStatus_PASS, statuspb.TestStatus_FAIL, statuspb.TestStatus_FLAKY}
failureTestStatuses = []statuspb.TestStatus{statuspb.TestStatus_FAIL, statuspb.TestStatus_FLAKY}
validTestStatusesInt = intStatusSet(validTestStatuses)
failureTestStatusesInt = intStatusSet(failureTestStatuses)
skippedTestStatuses = make(map[int32]struct{})
)
type TestResultSummary struct {
Name string
FullName string
TotalRuns, FailedRuns int
FailureRate float32
FailureLogs []string
IssueBody string
}
func fetchTestResultSummaries(dashboard, tab string) []*TestResultSummary {
// Fetch test data
rowsURL := fmt.Sprintf("http://testgrid-data.k8s.io/api/v1/dashboards/%s/tabs/%s/rows", dashboard, tab)
headersURL := fmt.Sprintf("http://testgrid-data.k8s.io/api/v1/dashboards/%s/tabs/%s/headers", dashboard, tab)
var testData apipb.ListRowsResponse
var headerData apipb.ListHeadersResponse
protojson.Unmarshal(fetchJSON(rowsURL), &testData)
protojson.Unmarshal(fetchJSON(headersURL), &headerData)
var allTests []string
for _, row := range testData.Rows {
allTests = append(allTests, row.Name)
}
summaries := []*TestResultSummary{}
// Process rows
for _, row := range testData.Rows {
t := processRow(dashboard, tab, row, allTests, headerData.Headers)
summaries = append(summaries, t)
}
return summaries
}
func processRow(dashboard, tab string, row *apipb.ListRowsResponse_Row, allTests []string, headers []*apipb.ListHeadersResponse_Header) *TestResultSummary {
t := TestResultSummary{Name: shortenTestName(row.Name), FullName: row.Name}
// we do not want to create issues for a parent test.
if isParentTest(row.Name, allTests) {
return &t
}
if !strings.HasPrefix(row.Name, "go.etcd.io") {
return &t
}
earliestTimeToConsider := time.Now().AddDate(0, 0, -1*maxDays)
total := 0
failed := 0
logs := []string{}
for i, cell := range row.Cells {
// ignore tests with status not in the validTestStatuses
// cell result codes are listed in https://github.com/GoogleCloudPlatform/testgrid/blob/main/pb/test_status/test_status.proto
if _, ok := validTestStatusesInt[cell.Result]; !ok {
if cell.Result != 0 {
skippedTestStatuses[cell.Result] = struct{}{}
}
continue
}
header := headers[i]
if maxDays > 0 && header.Started.AsTime().Before(earliestTimeToConsider) {
continue
}
total++
if _, ok := failureTestStatusesInt[cell.Result]; ok {
failed++
// markdown table format of | commit | log |
logs = append(logs, fmt.Sprintf("| %s | %s | https://prow.k8s.io/view/gs/kubernetes-jenkins/logs/%s/%s |", strings.Join(header.Extra, ","), header.Started.AsTime().String(), tab, header.Build))
}
if maxRuns > 0 && total >= maxRuns {
break
}
}
t.FailedRuns = failed
t.TotalRuns = total
t.FailureLogs = logs
t.FailureRate = float32(failed) / float32(total)
if t.FailedRuns > 0 {
dashboardURL := fmt.Sprintf("[%s](https://testgrid.k8s.io/%s#%s)", tab, dashboard, tab)
t.IssueBody = fmt.Sprintf("## %s Test: %s \nTest failed %.1f%% (%d/%d) of the time\n\nfailure logs are:\n| commit | started | log |\n| --- | --- | --- |\n%s\n",
dashboardURL, t.FullName, t.FailureRate*100, t.FailedRuns, t.TotalRuns, strings.Join(t.FailureLogs, "\n"))
t.IssueBody += "\nPlease follow the [instructions in the contributing guide](https://github.com/etcd-io/etcd/blob/main/CONTRIBUTING.md#check-for-flaky-tests) to reproduce the issue.\n"
fmt.Printf("%s failed %.1f%% (%d/%d) of the time\n", t.FullName, t.FailureRate*100, t.FailedRuns, t.TotalRuns)
}
return &t
}
// isParentTest checks if a test is a rollup of some child tests.
func isParentTest(test string, allTests []string) bool {
for _, t := range allTests {
if t != test && strings.HasPrefix(t, test+"/") {
return true
}
}
return false
}
func fetchJSON(url string) []byte {
resp, err := http.Get(url)
if err != nil {
fmt.Println("Error fetching test data:", err)
os.Exit(1)
}
defer resp.Body.Close()
testBody, _ := io.ReadAll(resp.Body)
return testBody
}
// intStatusSet converts a list of statuspb.TestStatus into a set of int.
func intStatusSet(statuses []statuspb.TestStatus) map[int32]struct{} {
s := make(map[int32]struct{})
for _, status := range statuses {
s[int32(status)] = struct{}{}
}
return s
}
func shortenTestName(fullname string) string {
parts := strings.Split(fullname, ".")
return parts[len(parts)-1]
}