Files

437 lines
9.3 KiB
Go
Raw Permalink Normal View History

2026-08-23 17:23:35 -07:00
package batching
import (
"testing"
"time"
"github.com/stretchr/testify/assert"
)
func TestLLMNewBatcher(t *testing.T) {
batcher := NewLLMBatcher(10, 5*time.Second)
assert.NotNil(t, batcher)
assert.Equal(t, 0, batcher.QueueSize())
}
func TestLLMEnqueueRequest(t *testing.T) {
batcher := NewLLMBatcher(10, 5*time.Second)
req := &LLMRequest{
ID: "req-1",
Type: "implementer",
Model: "claude-opus",
Prompt: "Generate code",
}
batcher.Enqueue(req)
assert.Equal(t, 1, batcher.QueueSize())
}
func TestLLMEnqueueMultipleRequests(t *testing.T) {
batcher := NewLLMBatcher(10, 5*time.Second)
for i := 0; i < 5; i++ {
req := &LLMRequest{
Type: "implementer",
Model: "claude-opus",
Prompt: "Prompt",
}
batcher.Enqueue(req)
}
assert.Equal(t, 5, batcher.QueueSize())
}
func TestLLMAutoFlushOnMaxBatchSize(t *testing.T) {
batcher := NewLLMBatcher(5, 10*time.Second)
for i := 0; i < 5; i++ {
req := &LLMRequest{
Type: "implementer",
Model: "claude-opus",
Prompt: "Prompt",
}
batcher.Enqueue(req)
}
assert.Equal(t, 0, batcher.QueueSize())
assert.Equal(t, 1, batcher.PendingBatchCount())
}
func TestLLMManualFlush(t *testing.T) {
batcher := NewLLMBatcher(10, 5*time.Second)
req := &LLMRequest{
Type: "implementer",
Model: "claude-opus",
Prompt: "Prompt",
}
batcher.Enqueue(req)
batcher.Flush()
assert.Equal(t, 0, batcher.QueueSize())
assert.Equal(t, 1, batcher.PendingBatchCount())
}
func TestLLMGetPendingBatch(t *testing.T) {
batcher := NewLLMBatcher(10, 5*time.Second)
req := &LLMRequest{
Type: "implementer",
Model: "claude-opus",
Prompt: "Prompt",
}
batcher.Enqueue(req)
batcher.Flush()
batch := batcher.GetPendingBatch()
assert.NotNil(t, batch)
assert.Equal(t, 1, len(batch.Requests))
assert.Equal(t, "implementer", batch.Type)
}
func TestLLMMarkBatchExecuting(t *testing.T) {
batcher := NewLLMBatcher(10, 5*time.Second)
req := &LLMRequest{Type: "implementer", Model: "claude-opus", Prompt: "test"}
batcher.Enqueue(req)
batcher.Flush()
batch := batcher.GetPendingBatch()
batcher.MarkBatchExecuting(batch.ID)
updated := batcher.GetBatchByID(batch.ID)
assert.Equal(t, "executing", updated.Status)
}
func TestLLMMarkBatchCompleted(t *testing.T) {
batcher := NewLLMBatcher(10, 5*time.Second)
req := &LLMRequest{
ID: "req-1",
Type: "implementer",
Model: "claude-opus",
Prompt: "test",
}
batcher.Enqueue(req)
batcher.Flush()
batch := batcher.GetPendingBatch()
results := map[string]*LLMResult{
"req-1": {
RequestID: "req-1",
Response: "Generated code",
TokenCount: 100,
},
}
batcher.MarkBatchCompleted(batch.ID, results)
executed := batcher.GetExecutedBatches()
assert.Equal(t, 1, len(executed))
assert.Equal(t, "completed", executed[0].Status)
}
func TestLLMMarkBatchFailed(t *testing.T) {
batcher := NewLLMBatcher(10, 5*time.Second)
req := &LLMRequest{
ID: "req-1",
Type: "implementer",
Model: "claude-opus",
}
batcher.Enqueue(req)
batcher.Flush()
batch := batcher.GetPendingBatch()
testErr := assert.AnError
batcher.MarkBatchFailed(batch.ID, testErr)
failed := batcher.GetBatchByID(batch.ID)
assert.Equal(t, "failed", failed.Status)
assert.Error(t, failed.Error)
}
func TestLLMGroupByTypeAndModel(t *testing.T) {
batcher := NewLLMBatcher(100, 5*time.Second)
// Add requests of different types
for i := 0; i < 3; i++ {
req := &LLMRequest{
Type: "implementer",
Model: "claude-opus",
Prompt: "test",
}
batcher.Enqueue(req)
}
for i := 0; i < 2; i++ {
req := &LLMRequest{
Type: "judge",
Model: "claude-opus",
Prompt: "test",
}
batcher.Enqueue(req)
}
batcher.Flush()
// Should create 2 batches (one for implementer, one for judge)
assert.Equal(t, 2, batcher.PendingBatchCount())
}
func TestLLMGetStats(t *testing.T) {
batcher := NewLLMBatcher(5, 5*time.Second)
// Add 10 requests (will create 2 batches)
for i := 0; i < 10; i++ {
req := &LLMRequest{
Type: "implementer",
Model: "claude-opus",
Prompt: "test",
}
batcher.Enqueue(req)
}
stats := batcher.GetStats()
assert.Equal(t, 10, stats.TotalRequests)
assert.Equal(t, 2, stats.TotalBatches)
assert.Equal(t, 5.0, stats.AvgRequestsPerBatch)
// 10 requests in 2 batches saves 8 API calls
assert.Equal(t, 8, stats.APICallsSaved)
}
func TestLLMResultDelivery(t *testing.T) {
batcher := NewLLMBatcher(10, 5*time.Second)
req := &LLMRequest{
ID: "req-1",
Type: "implementer",
Model: "claude-opus",
Prompt: "test",
ResultCh: make(chan *LLMResult, 1),
}
batcher.Enqueue(req)
batcher.Flush()
batch := batcher.GetPendingBatch()
results := map[string]*LLMResult{
"req-1": {
RequestID: "req-1",
Response: "Response",
TokenCount: 50,
},
}
batcher.MarkBatchCompleted(batch.ID, results)
// Check if result was delivered to channel
select {
case result := <-req.ResultCh:
assert.NotNil(t, result)
assert.Equal(t, "Response", result.Response)
case <-time.After(1 * time.Second):
t.Fatal("Result not delivered")
}
}
func TestLLMMultipleBatches(t *testing.T) {
batcher := NewLLMBatcher(3, 5*time.Second)
// Create 3 batches (3 requests each)
for batch := 0; batch < 3; batch++ {
for i := 0; i < 3; i++ {
req := &LLMRequest{
Type: "implementer",
Model: "claude-opus",
Prompt: "test",
}
batcher.Enqueue(req)
}
}
assert.Equal(t, 3, batcher.PendingBatchCount())
}
func TestLLMQueueSize(t *testing.T) {
batcher := NewLLMBatcher(10, 5*time.Second)
req := &LLMRequest{Type: "implementer", Model: "claude-opus"}
batcher.Enqueue(req)
assert.Equal(t, 1, batcher.QueueSize())
}
func TestLLMPendingBatchCount(t *testing.T) {
batcher := NewLLMBatcher(10, 5*time.Second)
req := &LLMRequest{Type: "implementer", Model: "claude-opus"}
batcher.Enqueue(req)
batcher.Flush()
assert.Equal(t, 1, batcher.PendingBatchCount())
}
func TestLLMGetExecutedBatches(t *testing.T) {
batcher := NewLLMBatcher(10, 5*time.Second)
for i := 0; i < 2; i++ {
req := &LLMRequest{Type: "implementer", Model: "claude-opus"}
batcher.Enqueue(req)
batcher.Flush()
batch := batcher.GetPendingBatch()
batcher.MarkBatchCompleted(batch.ID, make(map[string]*LLMResult))
}
executed := batcher.GetExecutedBatches()
assert.Equal(t, 2, len(executed))
}
func TestLLMTimeSinceLastFlush(t *testing.T) {
batcher := NewLLMBatcher(10, 5*time.Second)
req := &LLMRequest{Type: "implementer", Model: "claude-opus"}
batcher.Enqueue(req)
batcher.Flush()
time.Sleep(100 * time.Millisecond)
elapsed := batcher.TimeSinceLastFlush()
assert.Greater(t, elapsed, 50*time.Millisecond)
assert.Less(t, elapsed, 200*time.Millisecond)
}
func TestLLMShouldFlush(t *testing.T) {
batcher := NewLLMBatcher(100, 100*time.Millisecond)
req := &LLMRequest{Type: "implementer", Model: "claude-opus"}
batcher.Enqueue(req)
// Should not flush yet
assert.False(t, batcher.ShouldFlush())
// Wait for age to exceed
time.Sleep(150 * time.Millisecond)
assert.True(t, batcher.ShouldFlush())
}
func TestLLMClear(t *testing.T) {
batcher := NewLLMBatcher(10, 5*time.Second)
req := &LLMRequest{Type: "implementer", Model: "claude-opus"}
batcher.Enqueue(req)
batcher.Flush()
batcher.Clear()
assert.Equal(t, 0, batcher.QueueSize())
assert.Equal(t, 0, batcher.PendingBatchCount())
}
func TestLLMGetBatchByID(t *testing.T) {
batcher := NewLLMBatcher(10, 5*time.Second)
req := &LLMRequest{Type: "implementer", Model: "claude-opus"}
batcher.Enqueue(req)
batcher.Flush()
batch := batcher.GetPendingBatch()
retrieved := batcher.GetBatchByID(batch.ID)
assert.NotNil(t, retrieved)
assert.Equal(t, batch.ID, retrieved.ID)
}
func TestLLMGetBatchInfo(t *testing.T) {
batch := &LLMBatch{
ID: "batch-1",
Type: "implementer",
Model: "claude-opus",
Status: "completed",
CreatedAt: time.Now(),
}
info := batch.GetInfo()
assert.Equal(t, "batch-1", info["id"])
assert.Equal(t, "implementer", info["type"])
assert.Equal(t, "claude-opus", info["model"])
assert.Equal(t, "completed", info["status"])
}
func TestLLMEnqueueNil(t *testing.T) {
batcher := NewLLMBatcher(10, 5*time.Second)
batcher.Enqueue(nil)
assert.Equal(t, 0, batcher.QueueSize())
}
func TestLLMAutoIDGeneration(t *testing.T) {
req := &LLMRequest{
Type: "implementer",
Model: "claude-opus",
Prompt: "test",
}
batcher := NewLLMBatcher(10, 5*time.Second)
batcher.Enqueue(req)
assert.NotEmpty(t, req.ID)
}
func TestLLMTokenCounting(t *testing.T) {
batcher := NewLLMBatcher(10, 5*time.Second)
req := &LLMRequest{
ID: "req-1",
Type: "implementer",
Model: "claude-opus",
Prompt: "test",
}
batcher.Enqueue(req)
batcher.Flush()
batch := batcher.GetPendingBatch()
results := map[string]*LLMResult{
"req-1": {
RequestID: "req-1",
Response: "Response",
TokenCount: 500,
},
}
batcher.MarkBatchCompleted(batch.ID, results)
stats := batcher.GetStats()
assert.Equal(t, 500, stats.TotalTokens)
}
func BenchmarkLLMEnqueue(b *testing.B) {
batcher := NewLLMBatcher(1000, 10*time.Second)
for i := 0; i < b.N; i++ {
req := &LLMRequest{
Type: "implementer",
Model: "claude-opus",
Prompt: "test",
}
batcher.Enqueue(req)
}
}
func BenchmarkLLMFlush(b *testing.B) {
batcher := NewLLMBatcher(1000, 10*time.Second)
for i := 0; i < b.N; i++ {
req := &LLMRequest{Type: "implementer", Model: "claude-opus"}
batcher.Enqueue(req)
if (i + 1) % 100 == 0 {
batcher.Flush()
}
}
}