Skip to content

Commit 8f019b3

Browse files
committed
feat(simulate): expose run ID for CI baseline handoff
1 parent 760d580 commit 8f019b3

4 files changed

Lines changed: 58 additions & 0 deletions

File tree

README.md

Lines changed: 14 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -455,6 +455,20 @@ The above simulates 5 concurrent rooms, where each room has:
455455
456456
Once the specified duration is over (or if the load test is manually stopped), the load test statistics will be displayed in the form of a table.
457457
458+
## Agent simulations in CI
459+
460+
Use a finished simulation run as a baseline so known failures are reported without failing CI, while regressions still return a nonzero exit code:
461+
462+
```shell
463+
lk agent simulate \
464+
--scenarios scenarios.yaml \
465+
--baseline "$SIMULATION_BASELINE_RUN_ID" \
466+
--run-id-file "$RUNNER_TEMP/simulation-run-id"
467+
```
468+
469+
The CLI writes the new run ID to `--run-id-file` as soon as the run is created, even if the simulation later fails. Store the ID in your CI provider's variable or artifact store. Only a successful run on the main branch should replace the stored baseline; pull requests and failed main runs should leave it unchanged.
470+
471+
For the first run, omit `--baseline`, inspect and accept its results, then store the ID written to the file. A missing, unfinished, or inaccessible baseline fails CI rather than silently using strict comparison.
458472

459473
## Browsing documentation
460474

cmd/lk/simulate.go

Lines changed: 9 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -105,6 +105,10 @@ var simulateCommand = &cli.Command{
105105
Name: "baseline",
106106
Usage: "Compare failures against the finished run with run `ID`: only scenarios that pass there and fail here fail the exit code. Non-interactive (CI) runs only",
107107
},
108+
&cli.StringFlag{
109+
Name: "run-id-file",
110+
Usage: "Write the simulation run ID to `FILE` as soon as it is available. Non-interactive (CI) runs only",
111+
},
108112
&cli.StringFlag{
109113
Name: "agent-name",
110114
Usage: "Run against an already-running agent instead of spawning one locally. Pass the registered `NAME`, or \"\" to target the project's default agent (the one that auto-joins every room). Requires --scenarios.",
@@ -219,6 +223,7 @@ type simulateConfig struct {
219223
scenariosPath string // path to the --scenarios file (empty when generating from source)
220224
viewModeRunID string // non-empty when --view opens a pre-existing run
221225
baselineRunID string // --baseline: failures this run also has don't fail CI
226+
runIDFile string // --run-id-file: machine-readable handoff to CI
222227
liveAgent bool // --agent-name: run against an already-running agent, don't spawn one
223228
warnings []string // config-level warnings surfaced at setup (e.g. ignored flags)
224229

@@ -422,6 +427,7 @@ func runSimulate(ctx context.Context, cmd *cli.Command, simulationMode livekit.S
422427
scenariosPath: scenariosPath,
423428
viewModeRunID: runID,
424429
baselineRunID: cmd.String("baseline"),
430+
runIDFile: cmd.String("run-id-file"),
425431
liveAgent: liveAgent,
426432
warnings: simulateConfigWarnings(mode, numSimulations),
427433
}
@@ -438,6 +444,9 @@ func runSimulate(ctx context.Context, cmd *cli.Command, simulationMode livekit.S
438444
if simCfg.baselineRunID != "" {
439445
return fmt.Errorf("--baseline only applies to non-interactive (CI) runs; the TUI already shows every failure")
440446
}
447+
if simCfg.runIDFile != "" {
448+
return fmt.Errorf("--run-id-file only applies to non-interactive (CI) runs; the TUI already shows the run ID")
449+
}
441450
return runSimulateTUI(simCfg)
442451
}
443452

cmd/lk/simulate_ci.go

Lines changed: 18 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -120,6 +120,11 @@ func runSimulateCI(ctx context.Context, config *simulateConfig) error {
120120
report.EndSetup()
121121
return err
122122
}
123+
if err := writeSimulationRunID(config.runIDFile, runID); err != nil {
124+
report.SetupFailed(err)
125+
report.EndSetup()
126+
return err
127+
}
123128
report.SimulationCreated(time.Since(start))
124129

125130
if config.mode == modeGenerateFromSource {
@@ -221,6 +226,16 @@ func runSimulateCI(ctx context.Context, config *simulateConfig) error {
221226
return baselineFailureError(ctx, config, run)
222227
}
223228

229+
func writeSimulationRunID(path, runID string) error {
230+
if path == "" {
231+
return nil
232+
}
233+
if err := os.WriteFile(path, []byte(runID+"\n"), 0o644); err != nil {
234+
return fmt.Errorf("write simulation run ID to %s: %w", path, err)
235+
}
236+
return nil
237+
}
238+
224239
// baselineFailureError fetches the --baseline run when one was given and
225240
// reports which failures it already had before deciding the exit error. A
226241
// baseline that can't be fetched fails CI loudly rather than silently
@@ -332,6 +347,9 @@ func runSimulateCIView(ctx context.Context, config *simulateConfig) error {
332347

333348
report := newSimLog(out.ResultWriter(), out.StatusWriter())
334349
runID := config.viewModeRunID
350+
if err := writeSimulationRunID(config.runIDFile, runID); err != nil {
351+
return err
352+
}
335353

336354
ticker := time.NewTicker(simulationPollInterval)
337355
defer ticker.Stop()

cmd/lk/simulate_ci_test.go

Lines changed: 17 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -15,12 +15,29 @@
1515
package main
1616

1717
import (
18+
"os"
19+
"path/filepath"
1820
"strings"
1921
"testing"
2022

2123
"github.com/livekit/protocol/livekit"
2224
)
2325

26+
func TestWriteSimulationRunID(t *testing.T) {
27+
path := filepath.Join(t.TempDir(), "run-id")
28+
29+
if err := writeSimulationRunID(path, "SR_test123"); err != nil {
30+
t.Fatalf("writeSimulationRunID: %v", err)
31+
}
32+
got, err := os.ReadFile(path)
33+
if err != nil {
34+
t.Fatalf("read run ID file: %v", err)
35+
}
36+
if string(got) != "SR_test123\n" {
37+
t.Errorf("run ID file = %q, want %q", got, "SR_test123\\n")
38+
}
39+
}
40+
2441
func simJob(label string, failed bool) *livekit.SimulationRun_Job {
2542
status := livekit.SimulationRun_Job_STATUS_COMPLETED
2643
if failed {

0 commit comments

Comments
 (0)