first commit
This commit is contained in:
642
core/internal/mcptests/concurrency_advanced_test.go
Normal file
642
core/internal/mcptests/concurrency_advanced_test.go
Normal file
@@ -0,0 +1,642 @@
|
||||
package mcptests
|
||||
|
||||
import (
|
||||
"context"
|
||||
"encoding/json"
|
||||
"fmt"
|
||||
"strings"
|
||||
"sync"
|
||||
"sync/atomic"
|
||||
"testing"
|
||||
"time"
|
||||
|
||||
"github.com/maximhq/bifrost/core/schemas"
|
||||
"github.com/stretchr/testify/assert"
|
||||
"github.com/stretchr/testify/require"
|
||||
)
|
||||
|
||||
// =============================================================================
|
||||
// CONCURRENT CODE MODE EXECUTION TESTS
|
||||
// =============================================================================
|
||||
|
||||
func TestConcurrent_CodeModeExecution(t *testing.T) {
|
||||
t.Parallel()
|
||||
|
||||
config := GetTestConfig(t)
|
||||
if config.HTTPServerURL == "" {
|
||||
t.Skip("MCP_HTTP_URL not set")
|
||||
}
|
||||
|
||||
codeModeClient := GetSampleCodeModeClientConfig(t, config.HTTPServerURL)
|
||||
manager := setupMCPManager(t, codeModeClient)
|
||||
bifrost := setupBifrost(t)
|
||||
bifrost.SetMCPManager(manager)
|
||||
|
||||
const numConcurrent = 50
|
||||
var wg sync.WaitGroup
|
||||
errors := make(chan error, numConcurrent)
|
||||
successCount := atomic.Int32{}
|
||||
|
||||
for i := 0; i < numConcurrent; i++ {
|
||||
wg.Add(1)
|
||||
go func(id int) {
|
||||
defer wg.Done()
|
||||
|
||||
ctx := createTestContext()
|
||||
toolCall := CreateExecuteToolCodeCall(
|
||||
fmt.Sprintf("call-%d", id),
|
||||
fmt.Sprintf("return %d * 2", id),
|
||||
)
|
||||
|
||||
result, bifrostErr := bifrost.ExecuteChatMCPTool(ctx, &toolCall)
|
||||
if bifrostErr != nil {
|
||||
errors <- fmt.Errorf("execution %d failed: %v", id, bifrostErr.Error.Message)
|
||||
return
|
||||
}
|
||||
if result == nil {
|
||||
errors <- fmt.Errorf("execution %d returned nil result", id)
|
||||
return
|
||||
}
|
||||
|
||||
successCount.Add(1)
|
||||
}(i)
|
||||
}
|
||||
|
||||
wg.Wait()
|
||||
close(errors)
|
||||
|
||||
// Collect errors
|
||||
var errorList []error
|
||||
for err := range errors {
|
||||
errorList = append(errorList, err)
|
||||
}
|
||||
|
||||
// Should have high success rate (at least 80%)
|
||||
successRate := float64(successCount.Load()) / float64(numConcurrent)
|
||||
assert.Greater(t, successRate, 0.8, "Should have at least 80%% success rate, got %.2f%%, errors: %v", successRate*100, errorList)
|
||||
}
|
||||
|
||||
func TestConcurrent_CodeModeExecutionWithToolCalls(t *testing.T) {
|
||||
t.Parallel()
|
||||
|
||||
// Use InProcess tools for reliable concurrent testing
|
||||
manager := setupMCPManager(t)
|
||||
|
||||
// Register multiple tools
|
||||
require.NoError(t, RegisterEchoTool(manager))
|
||||
require.NoError(t, RegisterCalculatorTool(manager))
|
||||
require.NoError(t, RegisterGetTimeTool(manager))
|
||||
|
||||
bifrost := setupBifrost(t)
|
||||
bifrost.SetMCPManager(manager)
|
||||
|
||||
const numConcurrent = 30
|
||||
var wg sync.WaitGroup
|
||||
errors := make(chan error, numConcurrent)
|
||||
|
||||
for i := 0; i < numConcurrent; i++ {
|
||||
wg.Add(1)
|
||||
go func(id int) {
|
||||
defer wg.Done()
|
||||
|
||||
ctx := createTestContext()
|
||||
// Different code for each goroutine
|
||||
var code string
|
||||
switch id % 3 {
|
||||
case 0:
|
||||
code = fmt.Sprintf(`await bifrostInternal.echo({message: "test-%d"})`, id)
|
||||
case 1:
|
||||
code = fmt.Sprintf(`await bifrostInternal.calculator({operation: "add", x: %d, y: 10})`, id)
|
||||
case 2:
|
||||
code = `await bifrostInternal.get_time({timezone: "UTC"})`
|
||||
}
|
||||
|
||||
toolCall := CreateExecuteToolCodeCall(fmt.Sprintf("call-%d", id), code)
|
||||
_, bifrostErr := bifrost.ExecuteChatMCPTool(ctx, &toolCall)
|
||||
|
||||
if bifrostErr != nil {
|
||||
errors <- fmt.Errorf("execution %d failed: %v", id, bifrostErr.Error.Message)
|
||||
}
|
||||
}(i)
|
||||
}
|
||||
|
||||
wg.Wait()
|
||||
close(errors)
|
||||
|
||||
// Check for errors
|
||||
for err := range errors {
|
||||
t.Errorf("Concurrent execution error: %v", err)
|
||||
}
|
||||
}
|
||||
|
||||
// =============================================================================
|
||||
// CONCURRENT CLIENT OPERATIONS TESTS
|
||||
// =============================================================================
|
||||
|
||||
func TestConcurrent_AddRemoveClients(t *testing.T) {
|
||||
t.Parallel()
|
||||
|
||||
manager := setupMCPManager(t)
|
||||
bifrost := setupBifrost(t)
|
||||
bifrost.SetMCPManager(manager)
|
||||
|
||||
const numOperations = 20
|
||||
var wg sync.WaitGroup
|
||||
errors := make(chan error, numOperations*2)
|
||||
addCount := atomic.Int32{}
|
||||
removeCount := atomic.Int32{}
|
||||
|
||||
// Concurrently add and remove clients
|
||||
for i := 0; i < numOperations; i++ {
|
||||
wg.Add(2)
|
||||
|
||||
// Add client
|
||||
go func(id int) {
|
||||
defer wg.Done()
|
||||
|
||||
clientConfig := schemas.MCPClientConfig{
|
||||
ID: fmt.Sprintf("test-client-%d", id),
|
||||
Name: fmt.Sprintf("TestClient%d", id),
|
||||
ConnectionType: schemas.MCPConnectionTypeInProcess,
|
||||
ToolsToExecute: []string{"*"},
|
||||
ToolsToAutoExecute: []string{},
|
||||
}
|
||||
|
||||
err := manager.AddClient(&clientConfig)
|
||||
if err != nil {
|
||||
// InProcess connections without a server instance will fail
|
||||
// This is expected - we're just testing that the operations are concurrent and don't deadlock
|
||||
if !strings.Contains(err.Error(), "server instance") {
|
||||
errors <- fmt.Errorf("failed to add client %d: %v", id, err)
|
||||
}
|
||||
} else {
|
||||
addCount.Add(1)
|
||||
}
|
||||
}(i)
|
||||
|
||||
// Remove client (after a short delay)
|
||||
go func(id int) {
|
||||
defer wg.Done()
|
||||
|
||||
time.Sleep(50 * time.Millisecond)
|
||||
err := manager.RemoveClient(fmt.Sprintf("test-client-%d", id))
|
||||
if err != nil {
|
||||
// It's OK if client doesn't exist (race condition)
|
||||
if err.Error() != "client not found" && !strings.Contains(err.Error(), "not found") {
|
||||
errors <- fmt.Errorf("failed to remove client %d: %v", id, err)
|
||||
}
|
||||
} else {
|
||||
removeCount.Add(1)
|
||||
}
|
||||
}(i)
|
||||
}
|
||||
|
||||
wg.Wait()
|
||||
close(errors)
|
||||
|
||||
// Collect actual errors (not expected race conditions)
|
||||
var actualErrors []error
|
||||
for err := range errors {
|
||||
actualErrors = append(actualErrors, err)
|
||||
}
|
||||
|
||||
// Should have no unexpected errors
|
||||
if len(actualErrors) > 0 {
|
||||
for _, err := range actualErrors {
|
||||
t.Errorf("Concurrent client operation error: %v", err)
|
||||
}
|
||||
t.Fail()
|
||||
}
|
||||
|
||||
// The test passes if operations complete without deadlock/panic
|
||||
// Even if add/remove operations fail due to missing server instances
|
||||
t.Logf("Successfully completed concurrent add/remove test: %d adds, %d removes", addCount.Load(), removeCount.Load())
|
||||
}
|
||||
|
||||
func TestConcurrent_EditClientDuringExecution_Advanced(t *testing.T) {
|
||||
t.Parallel()
|
||||
|
||||
manager := setupMCPManager(t)
|
||||
|
||||
// Register a tool
|
||||
require.NoError(t, RegisterEchoTool(manager))
|
||||
|
||||
bifrost := setupBifrost(t)
|
||||
bifrost.SetMCPManager(manager)
|
||||
|
||||
var wg sync.WaitGroup
|
||||
errors := make(chan error, 100)
|
||||
|
||||
// Start multiple tool executions
|
||||
for i := 0; i < 50; i++ {
|
||||
wg.Add(1)
|
||||
go func(id int) {
|
||||
defer wg.Done()
|
||||
|
||||
ctx := createTestContext()
|
||||
toolCall := GetSampleEchoToolCall(fmt.Sprintf("call-%d", id), fmt.Sprintf("message-%d", id))
|
||||
|
||||
_, bifrostErr := bifrost.ExecuteChatMCPTool(ctx, &toolCall)
|
||||
if bifrostErr != nil {
|
||||
errors <- fmt.Errorf("execution %d failed: %v", id, bifrostErr.Error.Message)
|
||||
}
|
||||
}(i)
|
||||
}
|
||||
|
||||
// Concurrently edit the client configuration
|
||||
for i := 0; i < 10; i++ {
|
||||
wg.Add(1)
|
||||
go func(id int) {
|
||||
defer wg.Done()
|
||||
|
||||
time.Sleep(time.Duration(id*10) * time.Millisecond)
|
||||
|
||||
clients := manager.GetClients()
|
||||
for _, client := range clients {
|
||||
if client.ExecutionConfig.ID == "bifrostInternal" {
|
||||
// Create a fresh config instead of modifying the returned snapshot
|
||||
// to avoid race conditions with concurrent reads
|
||||
newConfig := &schemas.MCPClientConfig{
|
||||
ID: client.ExecutionConfig.ID,
|
||||
Name: client.ExecutionConfig.Name,
|
||||
ConnectionType: client.ExecutionConfig.ConnectionType,
|
||||
ToolsToExecute: []string{"echo"},
|
||||
}
|
||||
err := manager.UpdateClient(newConfig.ID, newConfig)
|
||||
if err != nil {
|
||||
errors <- fmt.Errorf("edit %d failed: %v", id, err)
|
||||
}
|
||||
break
|
||||
}
|
||||
}
|
||||
}(i)
|
||||
}
|
||||
|
||||
wg.Wait()
|
||||
close(errors)
|
||||
|
||||
// Some operations may fail due to race conditions, but system should remain stable
|
||||
errorCount := 0
|
||||
for err := range errors {
|
||||
errorCount++
|
||||
t.Logf("Expected race condition error: %v", err)
|
||||
}
|
||||
|
||||
// Should have at least some successful operations
|
||||
assert.Less(t, errorCount, 40, "Too many errors, system may be unstable")
|
||||
}
|
||||
|
||||
// =============================================================================
|
||||
// CONCURRENT HEALTH MONITORING TESTS
|
||||
// =============================================================================
|
||||
|
||||
func TestConcurrent_HealthCheckDuringExecution_Advanced(t *testing.T) {
|
||||
t.Parallel()
|
||||
|
||||
manager := setupMCPManager(t)
|
||||
|
||||
// Register a delay tool for long-running execution
|
||||
require.NoError(t, RegisterDelayTool(manager))
|
||||
|
||||
bifrost := setupBifrost(t)
|
||||
bifrost.SetMCPManager(manager)
|
||||
|
||||
var wg sync.WaitGroup
|
||||
errors := make(chan error, 30)
|
||||
|
||||
// Start long-running tool executions
|
||||
for i := 0; i < 10; i++ {
|
||||
wg.Add(1)
|
||||
go func(id int) {
|
||||
defer wg.Done()
|
||||
|
||||
ctx := createTestContext()
|
||||
argsMap := map[string]interface{}{"seconds": 2.0}
|
||||
toolCall := schemas.ChatAssistantMessageToolCall{
|
||||
ID: schemas.Ptr(fmt.Sprintf("call-%d", id)),
|
||||
Type: schemas.Ptr("function"),
|
||||
Function: schemas.ChatAssistantMessageToolCallFunction{
|
||||
Name: schemas.Ptr("bifrostInternal-delay"),
|
||||
Arguments: toJSON(argsMap),
|
||||
},
|
||||
}
|
||||
|
||||
_, bifrostErr := bifrost.ExecuteChatMCPTool(ctx, &toolCall)
|
||||
if bifrostErr != nil {
|
||||
errors <- fmt.Errorf("execution %d failed: %v", id, bifrostErr.Error.Message)
|
||||
}
|
||||
}(i)
|
||||
}
|
||||
|
||||
// Concurrently check client health
|
||||
for i := 0; i < 20; i++ {
|
||||
wg.Add(1)
|
||||
go func(id int) {
|
||||
defer wg.Done()
|
||||
|
||||
time.Sleep(time.Duration(id*10) * time.Millisecond)
|
||||
clients := manager.GetClients()
|
||||
if len(clients) == 0 {
|
||||
errors <- fmt.Errorf("health check %d: no clients found", id)
|
||||
}
|
||||
}(i)
|
||||
}
|
||||
|
||||
wg.Wait()
|
||||
close(errors)
|
||||
|
||||
// Check for errors
|
||||
for err := range errors {
|
||||
t.Errorf("Concurrent health check error: %v", err)
|
||||
}
|
||||
}
|
||||
|
||||
// =============================================================================
|
||||
// CONCURRENT TOOL REGISTRATION TESTS
|
||||
// =============================================================================
|
||||
|
||||
func TestConcurrent_ToolRegistration(t *testing.T) {
|
||||
t.Parallel()
|
||||
|
||||
manager := setupMCPManager(t)
|
||||
bifrost := setupBifrost(t)
|
||||
bifrost.SetMCPManager(manager)
|
||||
|
||||
const numTools = 50
|
||||
var wg sync.WaitGroup
|
||||
errors := make(chan error, numTools)
|
||||
successCount := atomic.Int32{}
|
||||
|
||||
// Register tools concurrently
|
||||
for i := 0; i < numTools; i++ {
|
||||
wg.Add(1)
|
||||
go func(id int) {
|
||||
defer wg.Done()
|
||||
|
||||
toolName := fmt.Sprintf("test_tool_%d", id)
|
||||
toolSchema := schemas.ChatTool{
|
||||
Type: schemas.ChatToolTypeFunction,
|
||||
Function: &schemas.ChatToolFunction{
|
||||
Name: toolName,
|
||||
Description: schemas.Ptr(fmt.Sprintf("Test tool %d", id)),
|
||||
Parameters: &schemas.ToolFunctionParameters{
|
||||
Type: "object",
|
||||
Properties: schemas.NewOrderedMap(),
|
||||
},
|
||||
},
|
||||
}
|
||||
|
||||
err := manager.RegisterTool(
|
||||
toolName,
|
||||
fmt.Sprintf("Test tool %d", id),
|
||||
func(args any) (string, error) {
|
||||
return fmt.Sprintf("Result from tool %d", id), nil
|
||||
},
|
||||
toolSchema,
|
||||
)
|
||||
|
||||
if err != nil {
|
||||
errors <- fmt.Errorf("failed to register tool %d: %v", id, err)
|
||||
} else {
|
||||
successCount.Add(1)
|
||||
}
|
||||
}(i)
|
||||
}
|
||||
|
||||
wg.Wait()
|
||||
close(errors)
|
||||
|
||||
// Check errors
|
||||
for err := range errors {
|
||||
t.Errorf("Tool registration error: %v", err)
|
||||
}
|
||||
|
||||
// Verify tools were registered
|
||||
ctx := createTestContext()
|
||||
tools := manager.GetToolPerClient(ctx)
|
||||
totalTools := 0
|
||||
for _, clientTools := range tools {
|
||||
totalTools += len(clientTools)
|
||||
}
|
||||
|
||||
assert.Greater(t, totalTools, 40, "Should have most tools registered successfully")
|
||||
}
|
||||
|
||||
func TestConcurrent_ToolExecutionMixedClients(t *testing.T) {
|
||||
t.Parallel()
|
||||
|
||||
manager := setupMCPManager(t)
|
||||
|
||||
// Register multiple tools on internal client
|
||||
require.NoError(t, RegisterEchoTool(manager))
|
||||
require.NoError(t, RegisterCalculatorTool(manager))
|
||||
require.NoError(t, RegisterGetTimeTool(manager))
|
||||
require.NoError(t, RegisterSearchTool(manager))
|
||||
|
||||
bifrost := setupBifrost(t)
|
||||
bifrost.SetMCPManager(manager)
|
||||
|
||||
const numConcurrent = 100
|
||||
var wg sync.WaitGroup
|
||||
errors := make(chan error, numConcurrent)
|
||||
successCount := atomic.Int32{}
|
||||
|
||||
for i := 0; i < numConcurrent; i++ {
|
||||
wg.Add(1)
|
||||
go func(id int) {
|
||||
defer wg.Done()
|
||||
|
||||
ctx := createTestContext()
|
||||
|
||||
// Execute different tools
|
||||
var toolCall schemas.ChatAssistantMessageToolCall
|
||||
switch id % 4 {
|
||||
case 0:
|
||||
toolCall = GetSampleEchoToolCall(fmt.Sprintf("call-%d", id), fmt.Sprintf("msg-%d", id))
|
||||
case 1:
|
||||
toolCall = GetSampleCalculatorToolCall(fmt.Sprintf("call-%d", id), "add", float64(id), 10)
|
||||
case 2:
|
||||
argsMap := map[string]interface{}{"timezone": "UTC"}
|
||||
toolCall = schemas.ChatAssistantMessageToolCall{
|
||||
ID: schemas.Ptr(fmt.Sprintf("call-%d", id)),
|
||||
Type: schemas.Ptr("function"),
|
||||
Function: schemas.ChatAssistantMessageToolCallFunction{
|
||||
Name: schemas.Ptr("bifrostInternal-get_time"),
|
||||
Arguments: toJSON(argsMap),
|
||||
},
|
||||
}
|
||||
case 3:
|
||||
argsMap := map[string]interface{}{"query": fmt.Sprintf("search-%d", id), "max_results": 5.0}
|
||||
toolCall = schemas.ChatAssistantMessageToolCall{
|
||||
ID: schemas.Ptr(fmt.Sprintf("call-%d", id)),
|
||||
Type: schemas.Ptr("function"),
|
||||
Function: schemas.ChatAssistantMessageToolCallFunction{
|
||||
Name: schemas.Ptr("bifrostInternal-search"),
|
||||
Arguments: toJSON(argsMap),
|
||||
},
|
||||
}
|
||||
}
|
||||
|
||||
result, bifrostErr := bifrost.ExecuteChatMCPTool(ctx, &toolCall)
|
||||
if bifrostErr != nil {
|
||||
errors <- fmt.Errorf("execution %d failed: %v", id, bifrostErr.Error.Message)
|
||||
} else if result != nil {
|
||||
successCount.Add(1)
|
||||
}
|
||||
}(i)
|
||||
}
|
||||
|
||||
wg.Wait()
|
||||
close(errors)
|
||||
|
||||
// Collect errors
|
||||
var errorList []error
|
||||
for err := range errors {
|
||||
errorList = append(errorList, err)
|
||||
}
|
||||
|
||||
// Should have high success rate
|
||||
successRate := float64(successCount.Load()) / float64(numConcurrent)
|
||||
assert.Greater(t, successRate, 0.9, "Should have at least 90%% success rate, got %.2f%%, errors: %v", successRate*100, errorList)
|
||||
}
|
||||
|
||||
// =============================================================================
|
||||
// CONCURRENT FILTERING TESTS
|
||||
// =============================================================================
|
||||
|
||||
func TestConcurrent_FilteringChanges(t *testing.T) {
|
||||
t.Parallel()
|
||||
|
||||
manager := setupMCPManager(t)
|
||||
|
||||
// Register multiple tools
|
||||
require.NoError(t, RegisterEchoTool(manager))
|
||||
require.NoError(t, RegisterCalculatorTool(manager))
|
||||
require.NoError(t, RegisterGetTimeTool(manager))
|
||||
|
||||
bifrost := setupBifrost(t)
|
||||
bifrost.SetMCPManager(manager)
|
||||
|
||||
var wg sync.WaitGroup
|
||||
errors := make(chan error, 100)
|
||||
|
||||
// Execute tools while concurrently changing filters
|
||||
for i := 0; i < 50; i++ {
|
||||
wg.Add(1)
|
||||
go func(id int) {
|
||||
defer wg.Done()
|
||||
|
||||
// Create context with different filter settings
|
||||
var ctx *schemas.BifrostContext
|
||||
|
||||
if id%2 == 0 {
|
||||
// Even: allow all tools
|
||||
baseCtx := context.Background()
|
||||
baseCtx = context.WithValue(baseCtx, schemas.MCPContextKeyIncludeClients, []string{"*"})
|
||||
baseCtx = context.WithValue(baseCtx, schemas.MCPContextKeyIncludeTools, []string{"bifrostInternal-*"})
|
||||
ctx = schemas.NewBifrostContext(baseCtx, schemas.NoDeadline)
|
||||
} else {
|
||||
// Odd: allow only echo
|
||||
baseCtx := context.Background()
|
||||
baseCtx = context.WithValue(baseCtx, schemas.MCPContextKeyIncludeClients, []string{"*"})
|
||||
baseCtx = context.WithValue(baseCtx, schemas.MCPContextKeyIncludeTools, []string{"bifrostInternal-echo"})
|
||||
ctx = schemas.NewBifrostContext(baseCtx, schemas.NoDeadline)
|
||||
}
|
||||
|
||||
toolCall := GetSampleEchoToolCall(fmt.Sprintf("call-%d", id), fmt.Sprintf("msg-%d", id))
|
||||
_, bifrostErr := bifrost.ExecuteChatMCPTool(ctx, &toolCall)
|
||||
|
||||
if bifrostErr != nil {
|
||||
errors <- fmt.Errorf("execution %d failed: %v", id, bifrostErr.Error.Message)
|
||||
}
|
||||
}(i)
|
||||
}
|
||||
|
||||
wg.Wait()
|
||||
close(errors)
|
||||
|
||||
// Check for errors
|
||||
for err := range errors {
|
||||
t.Errorf("Concurrent filtering error: %v", err)
|
||||
}
|
||||
}
|
||||
|
||||
// =============================================================================
|
||||
// STRESS TESTS
|
||||
// =============================================================================
|
||||
|
||||
func TestConcurrent_HighLoad(t *testing.T) {
|
||||
if testing.Short() {
|
||||
t.Skip("Skipping stress test in short mode")
|
||||
}
|
||||
|
||||
t.Parallel()
|
||||
|
||||
manager := setupMCPManager(t)
|
||||
require.NoError(t, RegisterEchoTool(manager))
|
||||
|
||||
bifrost := setupBifrost(t)
|
||||
bifrost.SetMCPManager(manager)
|
||||
|
||||
const numConcurrent = 500
|
||||
const duration = 10 * time.Second
|
||||
|
||||
var wg sync.WaitGroup
|
||||
errors := make(chan error, numConcurrent)
|
||||
successCount := atomic.Int32{}
|
||||
stopTime := time.Now().Add(duration)
|
||||
|
||||
for i := 0; i < numConcurrent; i++ {
|
||||
wg.Add(1)
|
||||
go func(id int) {
|
||||
defer wg.Done()
|
||||
|
||||
counter := 0
|
||||
for time.Now().Before(stopTime) {
|
||||
ctx := createTestContext()
|
||||
toolCall := GetSampleEchoToolCall(
|
||||
fmt.Sprintf("call-%d-%d", id, counter),
|
||||
fmt.Sprintf("msg-%d-%d", id, counter),
|
||||
)
|
||||
|
||||
result, bifrostErr := bifrost.ExecuteChatMCPTool(ctx, &toolCall)
|
||||
if bifrostErr != nil {
|
||||
errors <- fmt.Errorf("execution %d-%d failed: %v", id, counter, bifrostErr.Error.Message)
|
||||
return
|
||||
}
|
||||
if result != nil {
|
||||
successCount.Add(1)
|
||||
}
|
||||
|
||||
counter++
|
||||
time.Sleep(50 * time.Millisecond)
|
||||
}
|
||||
}(i)
|
||||
}
|
||||
|
||||
wg.Wait()
|
||||
close(errors)
|
||||
|
||||
// Count errors
|
||||
errorCount := 0
|
||||
for range errors {
|
||||
errorCount++
|
||||
}
|
||||
|
||||
totalExecutions := int(successCount.Load()) + errorCount
|
||||
successRate := float64(successCount.Load()) / float64(totalExecutions)
|
||||
|
||||
t.Logf("Stress test completed: %d successful, %d failed, %.2f%% success rate",
|
||||
successCount.Load(), errorCount, successRate*100)
|
||||
|
||||
assert.Greater(t, successRate, 0.95, "Should maintain >95%% success rate under load")
|
||||
}
|
||||
|
||||
// =============================================================================
|
||||
// HELPER FUNCTIONS
|
||||
// =============================================================================
|
||||
|
||||
func toJSON(v interface{}) string {
|
||||
b, _ := json.Marshal(v)
|
||||
return string(b)
|
||||
}
|
||||
Reference in New Issue
Block a user