mirror of
https://github.com/ddworken/hishtory.git
synced 2024-12-29 02:08:53 +01:00
163 lines
4.6 KiB
Go
163 lines
4.6 KiB
Go
// Exports test metrics to DD so we can monitor for flaky tests over time
|
|
package main
|
|
|
|
import (
|
|
"bufio"
|
|
"fmt"
|
|
"log"
|
|
"os"
|
|
"path"
|
|
"path/filepath"
|
|
"runtime"
|
|
"slices"
|
|
"strings"
|
|
|
|
"github.com/DataDog/datadog-go/statsd"
|
|
"gotest.tools/gotestsum/testjson"
|
|
)
|
|
|
|
var GLOBAL_STATSD *statsd.Client = nil
|
|
|
|
var NUM_TEST_RETRIES map[string]int
|
|
|
|
var UNUSED_GOLDENS []string = []string{
|
|
"testCustomColumns-query-isAction=false", "testCustomColumns-tquery-bash",
|
|
"testCustomColumns-tquery-zsh",
|
|
}
|
|
|
|
func main() {
|
|
if os.Args[1] == "export" {
|
|
exportMetrics()
|
|
}
|
|
if os.Args[1] == "check-goldens" {
|
|
checkGoldensUsed()
|
|
}
|
|
}
|
|
|
|
func checkGoldensUsed() {
|
|
if os.Getenv("HISHTORY_FILTERED_TEST") != "" {
|
|
return
|
|
}
|
|
// Read the goldens that were used
|
|
usedGoldens := make([]string, 0)
|
|
filenames, err := filepath.Glob("*/goldens-used.txt")
|
|
if err != nil {
|
|
log.Fatalf("failed to list golden files: %v", err)
|
|
}
|
|
fmt.Printf("Found used goldens in %#v\n", filenames)
|
|
for _, filename := range filenames {
|
|
usedGoldensFile, err := os.Open(filename)
|
|
if err != nil {
|
|
log.Fatalf("failed to open %s: %v", filename, err)
|
|
}
|
|
defer usedGoldensFile.Close()
|
|
scanner := bufio.NewScanner(usedGoldensFile)
|
|
for scanner.Scan() {
|
|
usedGoldens = append(usedGoldens, strings.TrimSpace(scanner.Text()))
|
|
}
|
|
if err := scanner.Err(); err != nil {
|
|
log.Fatalf("failed to read lines from /tmp/goldens-used.txt: %v", err)
|
|
}
|
|
}
|
|
|
|
// List all the goldens that exist
|
|
goldensDir := "client/testdata/"
|
|
files, err := os.ReadDir(goldensDir)
|
|
if err != nil {
|
|
panic(fmt.Errorf("failed to list files in %s: %w", goldensDir, err))
|
|
}
|
|
|
|
// And check for mismatches
|
|
var unusedGoldenErr error = nil
|
|
for _, f := range files {
|
|
goldenName := path.Base(f.Name())
|
|
if !slices.Contains(usedGoldens, goldenName) {
|
|
if slices.Contains(UNUSED_GOLDENS, goldenName) {
|
|
// It is allowlisted to not be used
|
|
continue
|
|
}
|
|
unusedGoldenErr = fmt.Errorf("golden file %v was never used", goldenName)
|
|
fmt.Println(unusedGoldenErr)
|
|
}
|
|
}
|
|
if unusedGoldenErr != nil {
|
|
log.Fatalf("%v", unusedGoldenErr)
|
|
}
|
|
|
|
// And print out anything that is in UNUSED_GOLDENS that was actually used, so we
|
|
// can manually trim UNUSED_GOLDENS
|
|
for _, g := range UNUSED_GOLDENS {
|
|
if slices.Contains(usedGoldens, g) {
|
|
fmt.Printf("Golden %s is in UNUSED_GOLDENS, but was actually used\n", g)
|
|
}
|
|
}
|
|
fmt.Println("Validated that all goldens in testdata/ were referenced!")
|
|
}
|
|
|
|
func exportMetrics() {
|
|
// Configure Datadog
|
|
if _, has_dd_api_key := os.LookupEnv("DD_API_KEY"); has_dd_api_key {
|
|
ddStats, err := statsd.New("localhost:8125")
|
|
if err != nil {
|
|
err := fmt.Errorf("failed to start DataDog statsd: %w", err)
|
|
if runtime.GOOS == "darwin" {
|
|
fmt.Printf("failed to init datadog: %v", err)
|
|
os.Exit(0)
|
|
} else {
|
|
log.Fatalf("failed to init datadog: %v", err)
|
|
}
|
|
}
|
|
defer ddStats.Close()
|
|
GLOBAL_STATSD = ddStats
|
|
} else {
|
|
fmt.Printf("Skipping exporting test stats to datadog\n")
|
|
}
|
|
|
|
// Parse the test output
|
|
NUM_TEST_RETRIES = make(map[string]int)
|
|
inputFile, err := os.Open("/tmp/testrun.json")
|
|
if err != nil {
|
|
log.Fatalf("failed to open test input file: %v", err)
|
|
}
|
|
_, err = testjson.ScanTestOutput(testjson.ScanConfig{
|
|
Stdout: inputFile,
|
|
Handler: eventHandler{},
|
|
})
|
|
if err != nil {
|
|
log.Fatalf("failed to scan testjson: %v", err)
|
|
}
|
|
for testId, count := range NUM_TEST_RETRIES {
|
|
GLOBAL_STATSD.Distribution("test_retry_count", float64(count), []string{"test:" + testId, "os:" + runtime.GOOS}, 1.0)
|
|
}
|
|
if GLOBAL_STATSD == nil {
|
|
fmt.Printf("Skipped uploading data about %d tests to datadog because GLOBAL_STATSD==nil\n", len(NUM_TEST_RETRIES))
|
|
} else {
|
|
err := GLOBAL_STATSD.Flush()
|
|
if err != nil {
|
|
log.Fatalf("failed to flush metrics: %v", err)
|
|
}
|
|
fmt.Printf("Uploaded data about %d tests to datadog\n", len(NUM_TEST_RETRIES))
|
|
}
|
|
}
|
|
|
|
type eventHandler struct{}
|
|
|
|
func (eventHandler) Event(event testjson.TestEvent, execution *testjson.Execution) error {
|
|
testIdentifier := event.Test
|
|
if event.Action == testjson.ActionFail {
|
|
fmt.Println("Recorded failure for " + testIdentifier)
|
|
GLOBAL_STATSD.Incr("test_status", []string{"result:failed", "test:" + testIdentifier, "os:" + runtime.GOOS}, 1.0)
|
|
NUM_TEST_RETRIES[testIdentifier] += 1
|
|
}
|
|
if event.Action == testjson.ActionPass {
|
|
GLOBAL_STATSD.Distribution("test_runtime", event.Elapsed, []string{"test:" + testIdentifier, "os:" + runtime.GOOS}, 1.0)
|
|
GLOBAL_STATSD.Incr("test_status", []string{"result:passed", "test:" + testIdentifier, "os:" + runtime.GOOS}, 1.0)
|
|
NUM_TEST_RETRIES[testIdentifier] += 1
|
|
}
|
|
return nil
|
|
}
|
|
|
|
func (eventHandler) Err(text string) error {
|
|
return fmt.Errorf("unexpected error when parsing test output: %v", text)
|
|
}
|