Skip to content

Commit 5694a85

Browse files
committed
test: add compaction e2e tests for all SDK flavors
Add e2e tests for context compaction to Go, Python, and .NET SDKs: - Test that compaction triggers with low thresholds and emits events - Test that no compaction events occur when infinite sessions disabled - Verify session still works after compaction (context preserved via summary) These tests mirror the existing Node.js compaction tests.
1 parent a318f06 commit 5694a85

File tree

3 files changed

+323
-0
lines changed

3 files changed

+323
-0
lines changed

dotnet/test/CompactionTests.cs

Lines changed: 109 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,109 @@
1+
/*---------------------------------------------------------------------------------------------
2+
* Copyright (c) Microsoft Corporation. All rights reserved.
3+
*--------------------------------------------------------------------------------------------*/
4+
5+
using GitHub.Copilot.SDK.Test.Harness;
6+
using Xunit;
7+
using Xunit.Abstractions;
8+
9+
namespace GitHub.Copilot.SDK.Test;
10+
11+
public class CompactionTests(E2ETestFixture fixture, ITestOutputHelper output) : E2ETestBase(fixture, "compaction", output)
12+
{
13+
[Fact]
14+
public async Task Should_Trigger_Compaction_With_Low_Threshold_And_Emit_Events()
15+
{
16+
// Create session with very low compaction thresholds to trigger compaction quickly
17+
var session = await Client.CreateSessionAsync(new SessionConfig
18+
{
19+
InfiniteSessions = new InfiniteSessionConfig
20+
{
21+
Enabled = true,
22+
// Trigger background compaction at 0.5% context usage (~1000 tokens)
23+
BackgroundCompactionThreshold = 0.005,
24+
// Block at 1% to ensure compaction runs
25+
BufferExhaustionThreshold = 0.01
26+
}
27+
});
28+
29+
var compactionStartEvents = new List<SessionCompactionStartEvent>();
30+
var compactionCompleteEvents = new List<SessionCompactionCompleteEvent>();
31+
32+
session.On(evt =>
33+
{
34+
if (evt is SessionCompactionStartEvent startEvt)
35+
{
36+
compactionStartEvents.Add(startEvt);
37+
}
38+
if (evt is SessionCompactionCompleteEvent completeEvt)
39+
{
40+
compactionCompleteEvents.Add(completeEvt);
41+
}
42+
});
43+
44+
// Send multiple messages to fill up the context window
45+
await session.SendAndWaitAsync(new MessageOptions
46+
{
47+
Prompt = "Tell me a long story about a dragon. Be very detailed."
48+
});
49+
await session.SendAndWaitAsync(new MessageOptions
50+
{
51+
Prompt = "Continue the story with more details about the dragon's castle."
52+
});
53+
await session.SendAndWaitAsync(new MessageOptions
54+
{
55+
Prompt = "Now describe the dragon's treasure in great detail."
56+
});
57+
58+
// Should have triggered compaction at least once
59+
Assert.True(compactionStartEvents.Count >= 1, "Expected at least 1 compaction_start event");
60+
Assert.True(compactionCompleteEvents.Count >= 1, "Expected at least 1 compaction_complete event");
61+
62+
// Compaction should have succeeded
63+
var lastComplete = compactionCompleteEvents[^1];
64+
Assert.True(lastComplete.Data.Success, "Expected compaction to succeed");
65+
66+
// Should have removed some tokens
67+
if (lastComplete.Data.TokensRemoved.HasValue)
68+
{
69+
Assert.True(lastComplete.Data.TokensRemoved > 0, "Expected tokensRemoved > 0");
70+
}
71+
72+
// Verify the session still works after compaction
73+
var answer = await session.SendAndWaitAsync(new MessageOptions
74+
{
75+
Prompt = "What was the story about?"
76+
});
77+
Assert.NotNull(answer);
78+
Assert.NotNull(answer!.Data.Content);
79+
// Should remember it was about a dragon (context preserved via summary)
80+
Assert.Contains("dragon", answer.Data.Content.ToLower());
81+
}
82+
83+
[Fact]
84+
public async Task Should_Not_Emit_Compaction_Events_When_Infinite_Sessions_Disabled()
85+
{
86+
var session = await Client.CreateSessionAsync(new SessionConfig
87+
{
88+
InfiniteSessions = new InfiniteSessionConfig
89+
{
90+
Enabled = false
91+
}
92+
});
93+
94+
var compactionEvents = new List<SessionEvent>();
95+
96+
session.On(evt =>
97+
{
98+
if (evt is SessionCompactionStartEvent or SessionCompactionCompleteEvent)
99+
{
100+
compactionEvents.Add(evt);
101+
}
102+
});
103+
104+
await session.SendAndWaitAsync(new MessageOptions { Prompt = "What is 2+2?" });
105+
106+
// Should not have any compaction events when disabled
107+
Assert.Empty(compactionEvents);
108+
}
109+
}

go/e2e/compaction_test.go

Lines changed: 122 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,122 @@
1+
package e2e
2+
3+
import (
4+
"strings"
5+
"testing"
6+
"time"
7+
8+
copilot "github.com/github/copilot-sdk/go"
9+
"github.com/github/copilot-sdk/go/e2e/testharness"
10+
)
11+
12+
func TestCompaction(t *testing.T) {
13+
ctx := testharness.NewTestContext(t)
14+
client := ctx.NewClient()
15+
t.Cleanup(func() { client.ForceStop() })
16+
17+
t.Run("should trigger compaction with low threshold and emit events", func(t *testing.T) {
18+
ctx.ConfigureForTest(t)
19+
20+
enabled := true
21+
backgroundThreshold := 0.005 // 0.5%
22+
bufferThreshold := 0.01 // 1%
23+
24+
session, err := client.CreateSession(&copilot.SessionConfig{
25+
InfiniteSessions: &copilot.InfiniteSessionConfig{
26+
Enabled: &enabled,
27+
BackgroundCompactionThreshold: &backgroundThreshold,
28+
BufferExhaustionThreshold: &bufferThreshold,
29+
},
30+
})
31+
if err != nil {
32+
t.Fatalf("Failed to create session: %v", err)
33+
}
34+
35+
var compactionStartEvents []copilot.SessionEvent
36+
var compactionCompleteEvents []copilot.SessionEvent
37+
38+
session.On(func(event copilot.SessionEvent) {
39+
if event.Type == copilot.SessionCompactionStart {
40+
compactionStartEvents = append(compactionStartEvents, event)
41+
}
42+
if event.Type == copilot.SessionCompactionComplete {
43+
compactionCompleteEvents = append(compactionCompleteEvents, event)
44+
}
45+
})
46+
47+
// Send multiple messages to fill up the context window
48+
_, err = session.SendAndWait(copilot.MessageOptions{Prompt: "Tell me a long story about a dragon. Be very detailed."}, 60*time.Second)
49+
if err != nil {
50+
t.Fatalf("Failed to send first message: %v", err)
51+
}
52+
53+
_, err = session.SendAndWait(copilot.MessageOptions{Prompt: "Continue the story with more details about the dragon's castle."}, 60*time.Second)
54+
if err != nil {
55+
t.Fatalf("Failed to send second message: %v", err)
56+
}
57+
58+
_, err = session.SendAndWait(copilot.MessageOptions{Prompt: "Now describe the dragon's treasure in great detail."}, 60*time.Second)
59+
if err != nil {
60+
t.Fatalf("Failed to send third message: %v", err)
61+
}
62+
63+
// Should have triggered compaction at least once
64+
if len(compactionStartEvents) < 1 {
65+
t.Errorf("Expected at least 1 compaction_start event, got %d", len(compactionStartEvents))
66+
}
67+
if len(compactionCompleteEvents) < 1 {
68+
t.Errorf("Expected at least 1 compaction_complete event, got %d", len(compactionCompleteEvents))
69+
}
70+
71+
// Compaction should have succeeded
72+
if len(compactionCompleteEvents) > 0 {
73+
lastComplete := compactionCompleteEvents[len(compactionCompleteEvents)-1]
74+
if lastComplete.Data.Success == nil || !*lastComplete.Data.Success {
75+
t.Errorf("Expected compaction to succeed")
76+
}
77+
if lastComplete.Data.TokensRemoved != nil && *lastComplete.Data.TokensRemoved <= 0 {
78+
t.Errorf("Expected tokensRemoved > 0, got %d", *lastComplete.Data.TokensRemoved)
79+
}
80+
}
81+
82+
// Verify session still works after compaction
83+
answer, err := session.SendAndWait(copilot.MessageOptions{Prompt: "What was the story about?"}, 60*time.Second)
84+
if err != nil {
85+
t.Fatalf("Failed to send verification message: %v", err)
86+
}
87+
if answer.Data.Content == nil || !strings.Contains(strings.ToLower(*answer.Data.Content), "dragon") {
88+
t.Errorf("Expected answer to contain 'dragon', got %v", answer.Data.Content)
89+
}
90+
})
91+
92+
t.Run("should not emit compaction events when infinite sessions disabled", func(t *testing.T) {
93+
ctx.ConfigureForTest(t)
94+
95+
enabled := false
96+
session, err := client.CreateSession(&copilot.SessionConfig{
97+
InfiniteSessions: &copilot.InfiniteSessionConfig{
98+
Enabled: &enabled,
99+
},
100+
})
101+
if err != nil {
102+
t.Fatalf("Failed to create session: %v", err)
103+
}
104+
105+
var compactionEvents []copilot.SessionEvent
106+
session.On(func(event copilot.SessionEvent) {
107+
if event.Type == copilot.SessionCompactionStart || event.Type == copilot.SessionCompactionComplete {
108+
compactionEvents = append(compactionEvents, event)
109+
}
110+
})
111+
112+
_, err = session.SendAndWait(copilot.MessageOptions{Prompt: "What is 2+2?"}, 60*time.Second)
113+
if err != nil {
114+
t.Fatalf("Failed to send message: %v", err)
115+
}
116+
117+
// Should not have any compaction events when disabled
118+
if len(compactionEvents) != 0 {
119+
t.Errorf("Expected 0 compaction events when disabled, got %d", len(compactionEvents))
120+
}
121+
})
122+
}

python/e2e/test_compaction.py

Lines changed: 92 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,92 @@
1+
"""E2E Compaction Tests"""
2+
3+
import pytest
4+
5+
from copilot.types import SessionEventType
6+
7+
from .testharness import E2ETestContext
8+
9+
pytestmark = pytest.mark.asyncio(loop_scope="module")
10+
11+
12+
class TestCompaction:
13+
@pytest.mark.timeout(120)
14+
async def test_should_trigger_compaction_with_low_threshold_and_emit_events(
15+
self, ctx: E2ETestContext
16+
):
17+
# Create session with very low compaction thresholds to trigger compaction quickly
18+
session = await ctx.client.create_session(
19+
{
20+
"infinite_sessions": {
21+
"enabled": True,
22+
# Trigger background compaction at 0.5% context usage (~1000 tokens)
23+
"background_compaction_threshold": 0.005,
24+
# Block at 1% to ensure compaction runs
25+
"buffer_exhaustion_threshold": 0.01,
26+
}
27+
}
28+
)
29+
30+
compaction_start_events = []
31+
compaction_complete_events = []
32+
33+
def on_event(event):
34+
if event.type == SessionEventType.SESSION_COMPACTION_START:
35+
compaction_start_events.append(event)
36+
if event.type == SessionEventType.SESSION_COMPACTION_COMPLETE:
37+
compaction_complete_events.append(event)
38+
39+
session.on(on_event)
40+
41+
# Send multiple messages to fill up the context window
42+
await session.send_and_wait(
43+
{"prompt": "Tell me a long story about a dragon. Be very detailed."}
44+
)
45+
await session.send_and_wait(
46+
{"prompt": "Continue the story with more details about the dragon's castle."}
47+
)
48+
await session.send_and_wait(
49+
{"prompt": "Now describe the dragon's treasure in great detail."}
50+
)
51+
52+
# Should have triggered compaction at least once
53+
assert len(compaction_start_events) >= 1, "Expected at least 1 compaction_start event"
54+
assert len(compaction_complete_events) >= 1, "Expected at least 1 compaction_complete event"
55+
56+
# Compaction should have succeeded
57+
last_complete = compaction_complete_events[-1]
58+
assert last_complete.data.success is True, "Expected compaction to succeed"
59+
60+
# Should have removed some tokens
61+
if last_complete.data.tokens_removed is not None:
62+
assert last_complete.data.tokens_removed > 0, "Expected tokensRemoved > 0"
63+
64+
# Verify the session still works after compaction
65+
answer = await session.send_and_wait({"prompt": "What was the story about?"})
66+
assert answer is not None
67+
assert answer.data.content is not None
68+
# Should remember it was about a dragon (context preserved via summary)
69+
assert "dragon" in answer.data.content.lower()
70+
71+
async def test_should_not_emit_compaction_events_when_infinite_sessions_disabled(
72+
self, ctx: E2ETestContext
73+
):
74+
session = await ctx.client.create_session(
75+
{"infinite_sessions": {"enabled": False}}
76+
)
77+
78+
compaction_events = []
79+
80+
def on_event(event):
81+
if event.type in (
82+
SessionEventType.SESSION_COMPACTION_START,
83+
SessionEventType.SESSION_COMPACTION_COMPLETE,
84+
):
85+
compaction_events.append(event)
86+
87+
session.on(on_event)
88+
89+
await session.send_and_wait({"prompt": "What is 2+2?"})
90+
91+
# Should not have any compaction events when disabled
92+
assert len(compaction_events) == 0, "Expected no compaction events when disabled"

0 commit comments

Comments
 (0)