blob: d91bcdd2a96f1a164b3a75def0123c63c326dbcf [file] [log] [blame]
package convert
import (
"encoding/json"
"math"
"os"
"path/filepath"
"testing"
"go.skia.org/infra/go/deepequal/assertdeep"
)
func createTempFile(t *testing.T, content string) string {
t.Helper()
tempDir := t.TempDir()
tempFile := filepath.Join(tempDir, "input.json")
err := os.WriteFile(tempFile, []byte(content), 0644)
if err != nil {
t.Fatalf("Failed to create temp file: %v", err)
}
return tempFile
}
func TestRun_FileErrors(t *testing.T) {
cfg := Config{
InputFile: "nonexistent.json",
OutputDir: "output_dir/",
Master: "test-master",
}
err := Run(cfg)
if err == nil {
t.Errorf("Expected an error for non-existent input file, got nil")
}
}
func TestRun_MissingMaster(t *testing.T) {
cfg := Config{
InputFile: "input.json",
OutputDir: "output_dir/",
}
err := Run(cfg)
if err == nil {
t.Errorf("Expected an error for missing master, got nil")
}
}
func TestRun_JSONUnmarshalErrors(t *testing.T) {
testCases := []struct {
name string
content string
}{
{"EmptyFile", ""},
{"InvalidJSON", "[{"},
{"NullFields", `[{"build_id": null, "builder": "test", "commit_id": "123", "perf_results": null}]`},
{"MissingFields", `[{"builder": "test"}]`},
{"TypeMismatch", `[{"build_id": 123}]`},
}
for _, tc := range testCases {
t.Run(tc.name, func(t *testing.T) {
inputFile := createTempFile(t, tc.content)
cfg := Config{
InputFile: inputFile,
OutputDir: "output_dir/",
Master: "test-master",
}
err := Run(cfg)
if err == nil {
t.Errorf("Expected an error for invalid JSON content: %s, got nil", tc.content)
}
})
}
}
func TestMapUnitAndDirection(t *testing.T) {
testCases := []struct {
input string
wantUnit string
wantDir string
}{
{"nanoseconds", "ms", "smallerIsBetter"},
{"ns", "ms", "smallerIsBetter"},
{"milliseconds", "ms", "smallerIsBetter"},
{"bytes", "sizeInBytes", "smallerIsBetter"},
{"frames/second", "Hz", "biggerIsBetter"},
{"percent", "n%", "smallerIsBetter"},
{"bytes/second", "unitless", "biggerIsBetter"},
{"bits/second", "unitless", "biggerIsBetter"},
{"ms", "ms", "smallerIsBetter"},
{"sizeInBytes", "sizeInBytes", "smallerIsBetter"},
{"count", "count", "smallerIsBetter"},
{"foobar", "unitless", "biggerIsBetter"},
{"ms_biggerIsBetter", "ms", "biggerIsBetter"},
{"bytes_smallerIsBetter", "sizeInBytes", "smallerIsBetter"},
{"percent_invalidDirection", "n%", "smallerIsBetter"},
}
for _, tc := range testCases {
t.Run(tc.input, func(t *testing.T) {
gotUnit, gotDir := MapUnitAndDirection(tc.input)
if gotUnit != tc.wantUnit || gotDir != tc.wantDir {
t.Errorf("mapUnitAndDirection(%q) = %q, %q; want %q, %q", tc.input, gotUnit, gotDir, tc.wantUnit, tc.wantDir)
}
})
}
}
func compareStatItems(t *testing.T, got, want []StatItem) {
t.Helper()
if len(got) != len(want) {
t.Fatalf("Mismatched stat item counts: got %d, want %d", len(got), len(want))
return
}
for i := range got {
if got[i].Value != want[i].Value {
t.Errorf("Mismatched Value at index %d: got %s, want %s", i, got[i].Value, want[i].Value)
}
if math.Abs(got[i].Measurement-want[i].Measurement) > 1e-9 {
t.Errorf("Mismatched Measurement at index %d: got %f, want %f", i, got[i].Measurement, want[i].Measurement)
}
}
}
func TestCalculateStats(t *testing.T) {
testCases := []struct {
name string
results []FuchsiaPerfResultItem
wantStats []StatItem
wantAvg float64
wantStd float64
}{
{
name: "Empty",
results: []FuchsiaPerfResultItem{},
wantStats: nil,
wantAvg: 0.0,
wantStd: 0.0,
},
{
name: "SingleValue",
results: []FuchsiaPerfResultItem{
{Unit: "ms", Value: 100},
},
wantStats: []StatItem{
{Value: "value", Measurement: 100},
{Value: "error", Measurement: 0},
{Value: "count", Measurement: 1},
{Value: "max", Measurement: 100},
{Value: "min", Measurement: 100},
{Value: "sum", Measurement: 100},
},
wantAvg: 100,
wantStd: 0,
},
{
name: "MultipleValues",
results: []FuchsiaPerfResultItem{
{Unit: "percent", Value: 10},
{Unit: "percent", Value: 20},
{Unit: "percent", Value: 30},
},
wantStats: []StatItem{
{Value: "value", Measurement: 10},
{Value: "error", Measurement: 10},
{Value: "count", Measurement: 3},
{Value: "max", Measurement: 30},
{Value: "min", Measurement: 10},
{Value: "sum", Measurement: 60},
},
wantAvg: 20,
wantStd: 10,
},
{
name: "NanosecondsConversion",
results: []FuchsiaPerfResultItem{
{Unit: "nanoseconds", Value: 2000000},
{Unit: "nanoseconds", Value: 4000000},
},
wantStats: []StatItem{
{Value: "value", Measurement: 2},
{Value: "error", Measurement: 1.414213562373095},
{Value: "count", Measurement: 2},
{Value: "max", Measurement: 4},
{Value: "min", Measurement: 2},
{Value: "sum", Measurement: 6},
},
wantAvg: 3,
wantStd: 1.414213562373095,
},
{
name: "BytesPerSecondConversion",
results: []FuchsiaPerfResultItem{
{Unit: "bytes/second", Value: 2097152}, // 2 MiB
},
wantStats: []StatItem{
{Value: "value", Measurement: 2},
{Value: "error", Measurement: 0},
{Value: "count", Measurement: 1},
{Value: "max", Measurement: 2},
{Value: "min", Measurement: 2},
{Value: "sum", Measurement: 2},
},
wantAvg: 2,
wantStd: 0,
},
}
for _, tc := range testCases {
t.Run(tc.name, func(t *testing.T) {
gotStats, gotAvg, gotStd := CalculateStats(tc.results)
if tc.wantStats == nil && gotStats != nil {
t.Errorf("calculateStats() stats = %v, want nil", gotStats)
} else if tc.wantStats != nil && gotStats == nil {
t.Errorf("calculateStats() stats = nil, want %v", tc.wantStats)
} else if tc.wantStats != nil && gotStats != nil {
compareStatItems(t, gotStats, tc.wantStats)
}
if math.Abs(gotAvg-tc.wantAvg) > 1e-9 {
t.Errorf("calculateStats() avg = %v, want %v", gotAvg, tc.wantAvg)
}
if math.Abs(gotStd-tc.wantStd) > 1e-9 { // Compare floats with tolerance
t.Errorf("calculateStats() std = %v, want %v", gotStd, tc.wantStd)
}
})
}
}
func TestPopulateResults(t *testing.T) {
input := []FuchsiaPerfResultItem{
{TestSuite: "S1", TestName: "T1", Value: 10, Unit: "ms"},
{TestSuite: "S1", TestName: "T1", Value: 20, Unit: "ms"},
{TestSuite: "S1", TestName: "T2", Value: 100, Unit: "bytes"},
}
expectedKeys := map[string]bool{
"T1": true,
"T1_avg": true,
"T2": true,
"T2_avg": true,
}
got := PopulateResults(input)
if len(got) != len(expectedKeys) {
t.Fatalf("populateResults() returned %d items, want %d", len(got), len(expectedKeys))
}
foundKeys := make(map[string]bool)
for _, item := range got {
foundKeys[item.Key.Test] = true
if item.Key.Unit == "" {
t.Errorf("populateResults() item %s has empty Unit", item.Key.Test)
}
if item.Key.ImprovementDirection == "" {
t.Errorf("populateResults() item %s has empty ImprovementDirection", item.Key.Test)
}
if len(item.Measurements.Stat) == 0 {
t.Errorf("populateResults() item %s has empty Measurements.Stat", item.Key.Test)
}
}
assertdeep.Equal(t, foundKeys, expectedKeys)
}
// TODO(eduardoyap): Add more comprehensive end-to-end tests for Run function
func TestRun_ValidJSON(t *testing.T) {
testCases := []struct {
name string
content string
}{
{
name: "ValidJSON",
content: `
[
{
"build_id": "8698222556732727921",
"builder": "pixel_watch.arm64-release",
"commit_id": "fce52a75c28903e6bbe1a079eec7097db8d89c3f",
"perf_results": [
{
"test_suite": "fuchsia.wearos.app_launch.complex_layout",
"test_name": "AtmDisplayTimeAverage",
"value": 2150,
"unit": "milliseconds"
}
]
}
]`,
},
}
for _, tc := range testCases {
t.Run(tc.name, func(t *testing.T) {
inputFile := createTempFile(t, tc.content)
tempDir := t.TempDir() // Use a fresh temp dir for output
outputDir := tempDir
cfg := Config{
InputFile: inputFile,
OutputDir: outputDir,
Master: "test-master",
}
err := Run(cfg)
if err != nil {
t.Errorf("Run failed for valid JSON: %v", err)
}
// Check if output file was created
expectedOutputFileName := "8698222556732727921-fuchsia.wearos.app_launch.complex_layout-pixel_watch.arm64-release-test-master.json"
expectedOutputPath := filepath.Join(outputDir, expectedOutputFileName)
if _, err := os.Stat(expectedOutputPath); os.IsNotExist(err) {
t.Errorf("Expected output file %s was not created", expectedOutputPath)
} else {
// Basic content check
data, err := os.ReadFile(expectedOutputPath)
if err != nil {
t.Fatalf("Failed to read output file: %v", err)
}
var result SkiaPerfResult
if err := json.Unmarshal(data, &result); err != nil {
t.Fatalf("Failed to unmarshal output file: %v", err)
}
if result.Key["benchmark"] != "fuchsia.wearos.app_launch.complex_layout" {
t.Errorf("Output file has incorrect benchmark key: %s", result.Key["benchmark"])
}
if len(result.Results) != 2 { // Base + Avg
t.Errorf("Output file should have 2 results, got %d", len(result.Results))
}
}
})
}
}