mirror of
https://github.com/lukaszraczylo/kportal.git
synced 2026-06-05 23:03:40 +00:00
3f5c1d3a5f
* Add benchmark and httplog modules, update UI for modals artefacts
283 lines
7.1 KiB
Go
283 lines
7.1 KiB
Go
package benchmark
|
|
|
|
import (
|
|
"context"
|
|
"net/http"
|
|
"net/http/httptest"
|
|
"sync"
|
|
"testing"
|
|
"time"
|
|
|
|
"github.com/stretchr/testify/assert"
|
|
"github.com/stretchr/testify/require"
|
|
)
|
|
|
|
func TestResults(t *testing.T) {
|
|
r := NewResults("test-forward", "http://localhost/test", "GET")
|
|
|
|
// Record some 2xx successes
|
|
r.RecordSuccess(200, 10*time.Millisecond, 100, 0)
|
|
r.RecordSuccess(200, 20*time.Millisecond, 150, 0)
|
|
r.RecordSuccess(201, 15*time.Millisecond, 120, 0)
|
|
|
|
// Record a transport failure
|
|
r.RecordFailure(assert.AnError, 5*time.Millisecond)
|
|
|
|
r.Finalize()
|
|
|
|
assert.Equal(t, 4, r.TotalRequests)
|
|
assert.Equal(t, 3, r.Successful)
|
|
assert.Equal(t, 1, r.Failed)
|
|
assert.Equal(t, int64(370), r.BytesRead)
|
|
assert.Equal(t, 2, r.StatusCodes[200])
|
|
assert.Equal(t, 1, r.StatusCodes[201])
|
|
}
|
|
|
|
func TestResultsNon2xxCountsAsFailure(t *testing.T) {
|
|
r := NewResults("test-forward", "http://localhost/test", "GET")
|
|
|
|
// Record a 200 success
|
|
r.RecordSuccess(200, 10*time.Millisecond, 100, 0)
|
|
|
|
// Record 4xx and 5xx - these should count as failures
|
|
r.RecordSuccess(404, 10*time.Millisecond, 50, 0)
|
|
r.RecordSuccess(500, 10*time.Millisecond, 30, 0)
|
|
|
|
r.Finalize()
|
|
|
|
assert.Equal(t, 3, r.TotalRequests)
|
|
assert.Equal(t, 1, r.Successful, "Only 2xx should count as successful")
|
|
assert.Equal(t, 2, r.Failed, "4xx and 5xx should count as failed")
|
|
assert.Equal(t, 1, r.StatusCodes[200])
|
|
assert.Equal(t, 1, r.StatusCodes[404])
|
|
assert.Equal(t, 1, r.StatusCodes[500])
|
|
}
|
|
|
|
func TestResultsStats(t *testing.T) {
|
|
r := NewResults("test", "http://localhost", "GET")
|
|
|
|
// Add latencies
|
|
latencies := []time.Duration{
|
|
10 * time.Millisecond,
|
|
20 * time.Millisecond,
|
|
30 * time.Millisecond,
|
|
40 * time.Millisecond,
|
|
50 * time.Millisecond,
|
|
}
|
|
|
|
for _, lat := range latencies {
|
|
r.RecordSuccess(200, lat, 0, 0)
|
|
}
|
|
|
|
r.EndTime = r.StartTime.Add(1 * time.Second)
|
|
|
|
stats := r.CalculateStats()
|
|
|
|
assert.Equal(t, 10*time.Millisecond, stats.MinLatency)
|
|
assert.Equal(t, 50*time.Millisecond, stats.MaxLatency)
|
|
assert.Equal(t, 30*time.Millisecond, stats.AvgLatency)
|
|
assert.Equal(t, float64(5), stats.Throughput)
|
|
}
|
|
|
|
func TestPercentile(t *testing.T) {
|
|
sorted := []time.Duration{
|
|
1 * time.Millisecond,
|
|
2 * time.Millisecond,
|
|
3 * time.Millisecond,
|
|
4 * time.Millisecond,
|
|
5 * time.Millisecond,
|
|
6 * time.Millisecond,
|
|
7 * time.Millisecond,
|
|
8 * time.Millisecond,
|
|
9 * time.Millisecond,
|
|
10 * time.Millisecond,
|
|
}
|
|
|
|
// P50 = index 5 (50*10/100 = 5) = 6ms
|
|
assert.Equal(t, 6*time.Millisecond, percentile(sorted, 50))
|
|
// P95 = index 9 (95*10/100 = 9) = 10ms
|
|
assert.Equal(t, 10*time.Millisecond, percentile(sorted, 95))
|
|
// P99 = index 9 (99*10/100 = 9) = 10ms
|
|
assert.Equal(t, 10*time.Millisecond, percentile(sorted, 99))
|
|
}
|
|
|
|
func TestRunner(t *testing.T) {
|
|
// Create a test server
|
|
server := httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
|
|
time.Sleep(5 * time.Millisecond) // Simulate some latency
|
|
w.WriteHeader(http.StatusOK)
|
|
w.Write([]byte(`{"status":"ok"}`))
|
|
}))
|
|
defer server.Close()
|
|
|
|
runner := NewRunner()
|
|
|
|
cfg := Config{
|
|
URL: server.URL,
|
|
Method: "GET",
|
|
Concurrency: 2,
|
|
Requests: 10,
|
|
Timeout: 5 * time.Second,
|
|
}
|
|
|
|
results, err := runner.Run(context.Background(), "test-forward", cfg)
|
|
require.NoError(t, err)
|
|
|
|
assert.Equal(t, 10, results.TotalRequests)
|
|
assert.Equal(t, 10, results.Successful)
|
|
assert.Equal(t, 0, results.Failed)
|
|
assert.Equal(t, 10, results.StatusCodes[200])
|
|
}
|
|
|
|
func TestRunnerWithDuration(t *testing.T) {
|
|
server := httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
|
|
w.WriteHeader(http.StatusOK)
|
|
w.Write([]byte(`ok`))
|
|
}))
|
|
defer server.Close()
|
|
|
|
runner := NewRunner()
|
|
|
|
cfg := Config{
|
|
URL: server.URL,
|
|
Method: "GET",
|
|
Concurrency: 2,
|
|
Duration: 100 * time.Millisecond,
|
|
Timeout: 1 * time.Second,
|
|
}
|
|
|
|
results, err := runner.Run(context.Background(), "test-forward", cfg)
|
|
require.NoError(t, err)
|
|
|
|
// Should have made some requests in 100ms
|
|
assert.Greater(t, results.TotalRequests, 0)
|
|
assert.Equal(t, results.Successful, results.StatusCodes[200])
|
|
}
|
|
|
|
func TestRunnerWithHeaders(t *testing.T) {
|
|
var receivedHeader string
|
|
server := httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
|
|
receivedHeader = r.Header.Get("X-Custom-Header")
|
|
w.WriteHeader(http.StatusOK)
|
|
}))
|
|
defer server.Close()
|
|
|
|
runner := NewRunner()
|
|
|
|
cfg := Config{
|
|
URL: server.URL,
|
|
Method: "GET",
|
|
Headers: map[string]string{
|
|
"X-Custom-Header": "test-value",
|
|
},
|
|
Concurrency: 1,
|
|
Requests: 1,
|
|
}
|
|
|
|
_, err := runner.Run(context.Background(), "test", cfg)
|
|
require.NoError(t, err)
|
|
|
|
assert.Equal(t, "test-value", receivedHeader)
|
|
}
|
|
|
|
func TestRunnerWithBody(t *testing.T) {
|
|
var receivedBody string
|
|
server := httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
|
|
body, _ := http.MaxBytesReader(w, r.Body, 1024).Read(make([]byte, 1024))
|
|
_ = body
|
|
w.WriteHeader(http.StatusOK)
|
|
}))
|
|
defer server.Close()
|
|
|
|
runner := NewRunner()
|
|
|
|
cfg := Config{
|
|
URL: server.URL,
|
|
Method: "POST",
|
|
Body: []byte(`{"test":"data"}`),
|
|
Concurrency: 1,
|
|
Requests: 1,
|
|
}
|
|
|
|
results, err := runner.Run(context.Background(), "test", cfg)
|
|
require.NoError(t, err)
|
|
|
|
_ = receivedBody // Used for debugging
|
|
assert.Equal(t, int64(15), results.BytesWritten)
|
|
}
|
|
|
|
func TestDefaultConfig(t *testing.T) {
|
|
cfg := DefaultConfig()
|
|
|
|
assert.Equal(t, "GET", cfg.Method)
|
|
assert.Equal(t, 10, cfg.Concurrency)
|
|
assert.Equal(t, 100, cfg.Requests)
|
|
assert.Equal(t, 30*time.Second, cfg.Timeout)
|
|
}
|
|
|
|
func TestRunnerWithProgressCallback(t *testing.T) {
|
|
server := httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
|
|
time.Sleep(10 * time.Millisecond) // Add small delay so progress ticker can fire
|
|
w.WriteHeader(http.StatusOK)
|
|
w.Write([]byte(`ok`))
|
|
}))
|
|
defer server.Close()
|
|
|
|
runner := NewRunner()
|
|
|
|
var progressUpdates []int
|
|
var mu sync.Mutex
|
|
|
|
cfg := Config{
|
|
URL: server.URL,
|
|
Method: "GET",
|
|
Concurrency: 5,
|
|
Requests: 50, // More requests to ensure progress callbacks fire
|
|
Timeout: 5 * time.Second,
|
|
ProgressCallback: func(completed, total int) {
|
|
mu.Lock()
|
|
progressUpdates = append(progressUpdates, completed)
|
|
mu.Unlock()
|
|
},
|
|
}
|
|
|
|
results, err := runner.Run(context.Background(), "test-forward", cfg)
|
|
require.NoError(t, err)
|
|
|
|
assert.Equal(t, 50, results.TotalRequests)
|
|
|
|
// Should have received some progress updates (ticker fires every 100ms)
|
|
mu.Lock()
|
|
updates := len(progressUpdates)
|
|
mu.Unlock()
|
|
assert.Greater(t, updates, 0, "Should have received progress updates")
|
|
}
|
|
|
|
func TestRunnerConcurrencyCappedAtRequests(t *testing.T) {
|
|
requestCount := 0
|
|
var mu sync.Mutex
|
|
|
|
server := httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
|
|
mu.Lock()
|
|
requestCount++
|
|
mu.Unlock()
|
|
w.WriteHeader(http.StatusOK)
|
|
}))
|
|
defer server.Close()
|
|
|
|
runner := NewRunner()
|
|
|
|
cfg := Config{
|
|
URL: server.URL,
|
|
Method: "GET",
|
|
Concurrency: 100, // Higher than requests
|
|
Requests: 5,
|
|
Timeout: 5 * time.Second,
|
|
}
|
|
|
|
results, err := runner.Run(context.Background(), "test", cfg)
|
|
require.NoError(t, err)
|
|
|
|
assert.Equal(t, 5, results.TotalRequests)
|
|
}
|