llm-api-benchmark-tool/pkg/report/generator_test.go

176 lines
6.3 KiB
Go

package report
import (
"testing"
"time"
"llm-api-benchmark-tool/pkg/stats"
"github.com/go-echarts/go-echarts/v2/opts"
"github.com/stretchr/testify/assert"
"github.com/stretchr/testify/require"
)
func TestCalculateHistogram(t *testing.T) {
// Helper function to create durations from milliseconds
dms := func(ms int) time.Duration {
return time.Duration(ms) * time.Millisecond
}
tests := []struct {
name string
data []time.Duration
numBins int
wantCats []string
wantDataVals []int // Just check the values
isErrorCase bool // Indicates if we expect the "No Data" case
}{
{
name: "Empty data",
data: []time.Duration{},
numBins: 5,
wantCats: []string{"No Data"},
wantDataVals: []int{0},
isErrorCase: true,
},
{
name: "Zero bins",
data: []time.Duration{dms(100), dms(200)},
numBins: 0,
wantCats: []string{"No Data"},
wantDataVals: []int{0},
isErrorCase: true,
},
{
name: "Single data point",
data: []time.Duration{dms(150)},
numBins: 5,
wantCats: []string{"150.00 ms"}, // Special case format
wantDataVals: []int{1},
},
{
name: "All data points same",
data: []time.Duration{dms(100), dms(100), dms(100)},
numBins: 3,
wantCats: []string{"100.00 ms"},
wantDataVals: []int{3},
},
{
name: "Simple case, 2 bins",
data: []time.Duration{dms(50), dms(60), dms(110), dms(120), dms(130)},
// Range: 50 to 130 (80ms)
// Bin width: 80 / 2 = 40
// Bins: [50, 90), [90, 130]
numBins: 2,
wantCats: []string{"[50.00, 90.00) ms", "[90.00, 130.00] ms"},
wantDataVals: []int{2, 3},
},
{
name: "Even distribution, 5 bins",
data: []time.Duration{dms(10), dms(25), dms(35), dms(45), dms(55), dms(65), dms(75), dms(85), dms(95), dms(100)},
// Range: 10 to 100 (90ms) -> max should be 100? Let's make max 110 for simpler bins
// Let's adjust data slightly for easier math: min 10, max 110
// data: {10, 25, 35, 45, 55, 65, 75, 85, 95, 110}
// Range: 10 to 110 (100ms)
// Bin width: 100 / 5 = 20
// Bins: [10,30), [30,50), [50,70), [70,90), [90,110]
numBins: 5,
wantCats: []string{"[10.00, 28.00) ms", "[28.00, 46.00) ms", "[46.00, 64.00) ms", "[64.00, 82.00) ms", "[82.00, 100.00] ms"},
wantDataVals: []int{2, 2, 1, 2, 3},
},
{
name: "Data clustered at ends",
data: []time.Duration{dms(10), dms(15), dms(20), dms(90), dms(95), dms(100)},
// Range: 10 to 100 (90). Width (3 bins): 30.
// Bins: [10, 40), [40, 70), [70, 100]
numBins: 3,
wantCats: []string{"[10.00, 40.00) ms", "[40.00, 70.00) ms", "[70.00, 100.00] ms"},
wantDataVals: []int{3, 0, 3},
},
}
for _, tt := range tests {
t.Run(tt.name, func(t *testing.T) {
gcats, gdata := calculateHistogram(tt.data, tt.numBins)
require.Equal(t, len(tt.wantCats), len(gcats), "Number of categories mismatch")
assert.Equal(t, tt.wantCats, gcats, "Category labels mismatch")
require.Equal(t, len(tt.wantDataVals), len(gdata), "Number of data points mismatch")
// Extract values from opts.BarData
gotDataVals := make([]int, len(gdata))
for i, item := range gdata {
// Check if item.Value is an int or can be converted
if val, ok := item.Value.(int); ok {
gotDataVals[i] = val
} else if valF, ok := item.Value.(float64); ok {
gotDataVals[i] = int(valF) // Or handle float differently if needed
} else {
t.Fatalf("Unexpected data type in BarData: %T", item.Value)
}
}
assert.Equal(t, tt.wantDataVals, gotDataVals, "Data values mismatch")
})
}
}
// Helper to create durations
func dms(ms int) time.Duration {
return time.Duration(ms) * time.Millisecond
}
// TestCreateLatencyHistogram checks if the latency histogram chart is created correctly.
func TestCreateLatencyHistogram(t *testing.T) {
mockStats := stats.FinalStats{
LatencyData: []time.Duration{dms(50), dms(100), dms(110), dms(150), dms(200), dms(210), dms(220)},
}
chart := createLatencyHistogram(mockStats)
require.NotNil(t, chart, "Chart should not be nil")
require.NotNil(t, chart.BaseConfiguration.Title, "Chart title configuration should not be nil")
// Check title
assert.Equal(t, "Latency Distribution", chart.BaseConfiguration.Title.Title, "Chart title mismatch")
// Check series data (basic check - assuming one series with correct name)
require.Len(t, chart.MultiSeries, 1, "Expected one series")
assert.Equal(t, "Latency", chart.MultiSeries[0].Name, "Series name mismatch")
// Optionally, check the number of data points generated by calculateHistogram via the chart
// This depends on the numBins used internally by createLatencyHistogram (currently 10)
// For the mock data {50, 100, 110, 150, 200, 210, 220}, range=170, 10 bins -> width=17
// Bins: [50, 67), [67, 84), [84, 101), [101, 118), [118, 135), [135, 152), [152, 169), [169, 186), [186, 203), [203, 220]
// Counts: 1, 0, 1, 1, 0, 1, 0, 0, 1, 2 -> Total 7 points, 10 bars
seriesData, ok := chart.MultiSeries[0].Data.([]opts.BarData)
require.True(t, ok, "Series data should be of type []opts.BarData")
assert.Len(t, seriesData, 10, "Expected 10 data points (bins) in the series")
}
// TestCreateTTFTHistogram checks if the TTFT histogram chart is created correctly.
func TestCreateTTFTHistogram(t *testing.T) {
mockStats := stats.FinalStats{
TTFTData: []time.Duration{dms(20), dms(30), dms(35), dms(40), dms(60)},
}
chart := createTTFTHistogram(mockStats)
require.NotNil(t, chart, "Chart should not be nil")
require.NotNil(t, chart.BaseConfiguration.Title, "Chart title configuration should not be nil")
// Check title
assert.Equal(t, "TTFT Distribution", chart.BaseConfiguration.Title.Title, "Chart title mismatch")
// Check series data
require.Len(t, chart.MultiSeries, 1, "Expected one series")
assert.Equal(t, "TTFT", chart.MultiSeries[0].Name, "Series name mismatch")
// Optionally, check the number of data points
// Data: {20, 30, 35, 40, 60}, Range=40, 10 bins -> width=4
// Bins: [20,24), [24,28), [28,32), [32,36), [36,40), [40,44), [44,48), [48,52), [52,56), [56,60]
// Counts: 1, 0, 1, 1, 0, 1, 0, 0, 0, 1 -> Total 5 points, 10 bars
seriesData, ok := chart.MultiSeries[0].Data.([]opts.BarData)
require.True(t, ok, "Series data should be of type []opts.BarData")
assert.Len(t, seriesData, 10, "Expected 10 data points (bins) in the series")
}