Skip to content

Commit 1c2c4ba

Browse files
authored
Tighten agent session e2e shutdown assertions (#883)
* Tighten agent session e2e shutdown assertions * test(session-e2e): give start a 15s timeout The detached daemon boot + agent venv + LLM connect takes longer than the shared 5s sessionE2ETimeout, which killed start on the ubuntu and windows CI runners (signal: killed / exit status 1). Bump start to 15s.
1 parent 264857e commit 1c2c4ba

1 file changed

Lines changed: 62 additions & 18 deletions

File tree

cmd/lk/session_e2e_test.go

Lines changed: 62 additions & 18 deletions
Original file line numberDiff line numberDiff line change
@@ -15,6 +15,7 @@
1515
package main
1616

1717
import (
18+
"bytes"
1819
"context"
1920
"net"
2021
"os"
@@ -28,10 +29,13 @@ import (
2829
"github.com/stretchr/testify/require"
2930
)
3031

32+
const sessionE2ETimeout = 5 * time.Second
33+
3134
// TestSessionE2E drives the real `lk agent session` lifecycle end to end:
3235
// build the binary, `start` the detached daemon, `say` to make the model echo
33-
// a token (asserting the CLI→daemon→agent→LLM round-trip), `end`, then confirm
34-
// the daemon exited (nothing answers on the port).
36+
// a token (asserting the CLI→daemon→agent→LLM round-trip), `end`, confirm a
37+
// second `say` cannot still reach the agent, then confirm the daemon exited
38+
// (nothing answers on the port).
3539
//
3640
// Opt-in: needs a prepared agent venv + live creds, so it skips unless
3741
// LIVEKIT_API_KEY is set. Defaults to testdata/echo-agent; override with LK_SESSION_E2E_AGENT.
@@ -55,47 +59,87 @@ func TestSessionE2E(t *testing.T) {
5559

5660
bin := buildLK(t)
5761

58-
run := func(timeout time.Duration, args ...string) (string, error) {
62+
type runResult struct {
63+
stdout string
64+
stderr string
65+
exitCode int
66+
}
67+
68+
runCapture := func(timeout time.Duration, args ...string) (runResult, error) {
5969
ctx, cancel := context.WithTimeout(context.Background(), timeout)
6070
defer cancel()
6171
cmd := exec.CommandContext(ctx, bin, args...)
6272
cmd.Env = os.Environ()
63-
out, err := cmd.CombinedOutput()
64-
return string(out), err
73+
var stdout, stderr bytes.Buffer
74+
cmd.Stdout = &stdout
75+
cmd.Stderr = &stderr
76+
err := cmd.Run()
77+
require.NotNil(t, cmd.ProcessState, "command did not start: %v", err)
78+
79+
return runResult{
80+
stdout: stdout.String(),
81+
stderr: stderr.String(),
82+
exitCode: cmd.ProcessState.ExitCode(),
83+
}, err
84+
}
85+
86+
run := func(timeout time.Duration, args ...string) (string, error) {
87+
res, err := runCapture(timeout, args...)
88+
return res.stdout + res.stderr, err
89+
}
90+
91+
portIsFree := func() bool {
92+
conn, derr := net.DialTimeout("tcp", "127.0.0.1:"+port, 200*time.Millisecond)
93+
if derr != nil {
94+
return true // refused -> daemon exited
95+
}
96+
conn.Close()
97+
return false
6598
}
6699

67100
// Best-effort teardown so a mid-run failure doesn't leave the daemon alive.
68101
t.Cleanup(func() {
69-
_, _ = run(15*time.Second, "agent", "session", "end", "--port", port)
102+
_, _ = run(sessionE2ETimeout, "agent", "session", "end", "--port", port)
70103
})
71104

72105
// start: launches the detached daemon and returns once the agent is ready.
73-
startOut, err := run(90*time.Second, "agent", "session", "start", "--port", port, entrypoint)
106+
startOut, err := run(15*time.Second, "agent", "session", "start", "--port", port, entrypoint)
74107
require.NoError(t, err, "session start failed:\n%s", startOut)
75108
require.Contains(t, startOut, "Session started.", "start did not report readiness:\n%s", startOut)
76109

77110
// say: the token appears once in the echoed prompt and again in the reply, so
78111
// >=2 occurrences proves the agent answered, not just the local echo.
79112
token := "PINEAPPLE7351"
80-
sayOut, err := run(90*time.Second, "agent", "session", "say", "--port", port,
113+
sayOut, err := run(sessionE2ETimeout, "agent", "session", "say", "--port", port,
81114
"Repeat this token back to me exactly and nothing else: "+token)
82115
require.NoError(t, err, "session say failed:\n%s", sayOut)
83116
require.GreaterOrEqualf(t, strings.Count(sayOut, token), 2,
84117
"agent did not echo the token back; say output:\n%s", sayOut)
85118

86-
endOut, err := run(30*time.Second, "agent", "session", "end", "--port", port)
119+
endOut, err := run(sessionE2ETimeout, "agent", "session", "end", "--port", port)
87120
require.NoError(t, err, "session end failed:\n%s", endOut)
88121
require.Contains(t, endOut, "Session ended.", "end did not confirm shutdown:\n%s", endOut)
89122

90-
// The detached daemon should now be gone: nothing should answer on the port.
91-
require.Eventually(t, func() bool {
92-
conn, derr := net.DialTimeout("tcp", "127.0.0.1:"+port, 200*time.Millisecond)
93-
if derr != nil {
94-
return true // refused → daemon exited
95-
}
96-
conn.Close()
97-
return false
98-
}, 10*time.Second, 200*time.Millisecond, "session daemon still listening on port %s after end", port)
123+
require.Eventually(t, portIsFree, sessionE2ETimeout, 200*time.Millisecond,
124+
"session daemon still listening on port %s after end", port)
125+
126+
// After a successful match and shutdown, another say must not reach a live
127+
// agent or reproduce the token.
128+
afterEndSay, err := runCapture(sessionE2ETimeout, "agent", "session", "say", "--port", port,
129+
"Repeat this token back to me exactly and nothing else: "+token)
130+
afterEndSayOut := afterEndSay.stdout + afterEndSay.stderr
131+
require.Error(t, err, "session say unexpectedly succeeded after end:\n%s", afterEndSayOut)
132+
require.Equal(t, 1, afterEndSay.exitCode,
133+
"session say after end exited with wrong code; stdout:\n%s\nstderr:\n%s",
134+
afterEndSay.stdout, afterEndSay.stderr)
135+
require.Truef(t, strings.HasPrefix(afterEndSayOut, "no session running"),
136+
"session say after end output did not start with no session running; stdout:\n%s\nstderr:\n%s",
137+
afterEndSay.stdout, afterEndSay.stderr)
138+
require.NotContains(t, afterEndSayOut, token,
139+
"session say after end unexpectedly contained the matched token; stdout:\n%s\nstderr:\n%s",
140+
afterEndSay.stdout, afterEndSay.stderr)
141+
142+
require.True(t, portIsFree(), "session daemon started listening again on port %s after failed say", port)
99143
}
100144

101145
// buildLK returns the path to the lk binary under test. If LK_SESSION_E2E_BIN

0 commit comments

Comments
 (0)