package loadgen import ( "bufio" "context" "errors" "fmt" "io" "net" "net/http" "net/http/httptest" "sync/atomic" "testing" "time" ) func TestRunExecutesFixedBoundedHTTPWorkload(t *testing.T) { var requests atomic.Int64 server := httptest.NewServer(http.HandlerFunc(func(response http.ResponseWriter, request *http.Request) { if request.Method != http.MethodPost || request.Header.Get("X-Scenario") != "fixed" { t.Errorf("request = %s %q", request.Method, request.Header.Get("X-Scenario")) } body, err := io.ReadAll(request.Body) if err != nil || string(body) != `{"count":1}` { t.Errorf("request body = %q, error = %v", body, err) } requests.Add(1) response.WriteHeader(http.StatusNoContent) })) defer server.Close() report, err := Run(context.Background(), Options{ TargetURL: server.URL, Method: http.MethodPost, Headers: http.Header{"X-Scenario": {"fixed"}}, RequestBody: []byte(`{"count":1}`), Requests: 12, Concurrency: 3, RequestTimeout: time.Second, }) if err != nil || report.Requests != 12 || report.Completed != 12 || report.Succeeded != 12 || report.Failed != 0 || requests.Load() != 12 || report.Latency.P50UpperBound <= 0 || report.Runtime.NumCPU <= 0 { t.Fatalf("Run() = (%+v, %v); handler requests=%d", report, err, requests.Load()) } } func TestRunClassifiesHTTPFailure(t *testing.T) { server := httptest.NewServer(http.HandlerFunc(func(response http.ResponseWriter, _ *http.Request) { response.WriteHeader(http.StatusServiceUnavailable) })) defer server.Close() report, err := Run(context.Background(), Options{ TargetURL: server.URL, Requests: 1, Concurrency: 1, RequestTimeout: time.Second, }) if err != nil || report.Succeeded != 0 || report.Failed != 1 || report.Status5xx != 1 || report.Completed != 1 { t.Fatalf("Run() = (%+v, %v)", report, err) } } func TestRunTimeBoxedRateIsBounded(t *testing.T) { var requests atomic.Int64 server := httptest.NewServer(http.HandlerFunc(func(response http.ResponseWriter, _ *http.Request) { requests.Add(1) response.WriteHeader(http.StatusNoContent) })) defer server.Close() report, err := Run(context.Background(), Options{ TargetURL: server.URL, Duration: 100 * time.Millisecond, Rate: 100, Concurrency: 2, RequestTimeout: time.Second, }) if err != nil || report.Requests == 0 || report.Requests > 20 || report.Requests != report.Completed || report.Succeeded != report.Completed || requests.Load() != int64(report.Requests) { t.Fatalf("Run() = (%+v, %v); handler requests=%d", report, err, requests.Load()) } } func TestRunExecutesBoundedCONNECTTunnelWorkload(t *testing.T) { listener, err := net.Listen("tcp", "127.0.0.1:0") if err != nil { t.Fatalf("Listen(): %v", err) } defer listener.Close() var tunnels atomic.Int64 serveDone := make(chan struct{}) go func() { defer close(serveDone) for range 3 { connection, acceptErr := listener.Accept() if acceptErr != nil { return } go func(connection net.Conn) { defer connection.Close() request, readErr := http.ReadRequest(bufio.NewReader(connection)) if readErr != nil || request.Method != http.MethodConnect || request.Host != "target.example:443" { t.Errorf("CONNECT request = %+v, error = %v", request, readErr) return } tunnels.Add(1) _, _ = fmt.Fprint(connection, "HTTP/1.1 200 Connection Established\r\n\r\n") _, _ = io.Copy(io.Discard, connection) }(connection) } }() report, err := Run(context.Background(), Options{ Scenario: ScenarioConnect, TargetURL: "https://target.example/health", ProxyURL: "http://" + listener.Addr().String(), Requests: 3, Concurrency: 2, RequestTimeout: time.Second, TunnelHold: 10 * time.Millisecond, }) if err != nil || report.Requests != 3 || report.Completed != 3 || report.Succeeded != 3 || report.Failed != 0 || report.TunnelsEstablished != 3 || tunnels.Load() != 3 || report.Latency.P50UpperBound <= 0 { t.Fatalf("Run() = (%+v, %v); CONNECT requests=%d", report, err, tunnels.Load()) } <-serveDone } func TestRunClassifiesCONNECTProxyRejection(t *testing.T) { listener, err := net.Listen("tcp", "127.0.0.1:0") if err != nil { t.Fatalf("Listen(): %v", err) } defer listener.Close() go func() { connection, acceptErr := listener.Accept() if acceptErr != nil { return } defer connection.Close() _, _ = http.ReadRequest(bufio.NewReader(connection)) _, _ = fmt.Fprint(connection, "HTTP/1.1 407 Proxy Authentication Required\r\nContent-Length: 0\r\n\r\n") }() report, err := Run(context.Background(), Options{ Scenario: ScenarioConnect, TargetURL: "https://target.example/health", ProxyURL: "http://" + listener.Addr().String(), Requests: 1, Concurrency: 1, RequestTimeout: time.Second, TunnelHold: time.Millisecond, }) if err != nil || report.Succeeded != 0 || report.Failed != 1 || report.Status4xx != 1 || report.TunnelsEstablished != 0 { t.Fatalf("Run() = (%+v, %v)", report, err) } } func TestRunRejectsCONNECTWithoutProxyOrHold(t *testing.T) { _, err := Run(context.Background(), Options{ Scenario: ScenarioConnect, TargetURL: "https://target.example/health", Requests: 1, Concurrency: 1, RequestTimeout: time.Second, }) if !errors.Is(err, ErrInvalidOptions) { t.Fatalf("Run(connect without proxy or hold) error = %v, want ErrInvalidOptions", err) } } func TestRunRejectsUnboundedWorkload(t *testing.T) { _, err := Run(context.Background(), Options{TargetURL: "http://127.0.0.1:8080", Concurrency: 1}) if !errors.Is(err, ErrInvalidOptions) { t.Fatalf("Run(unbounded) error = %v, want ErrInvalidOptions", err) } }