feat: empty-container spawn (scratch workspace, no repo volume); fix bridge session isolation — explicit SessionManager per spawn stops SDK resuming the repo's old session (which also overrode LVMH_MODEL)
This commit is contained in:
+52
-21
@@ -29,7 +29,7 @@ func TestSpawnerStartHappyPath(t *testing.T) {
|
||||
f := newFakeDocker()
|
||||
sp, store := newTestSpawner(t, f)
|
||||
|
||||
res, err := sp.Start(context.Background(), "group/project", "main", "")
|
||||
res, err := sp.Start(context.Background(), "group/project", "main", "", false)
|
||||
if err != nil {
|
||||
t.Fatalf("Start: %v", err)
|
||||
}
|
||||
@@ -148,7 +148,7 @@ func TestSpawnerBuildsImageWhenMissing(t *testing.T) {
|
||||
f.images = 0 // image absent → ensureImage must build
|
||||
sp, _ := newTestSpawner(t, f)
|
||||
|
||||
res, err := sp.Start(context.Background(), "group/project", "", "")
|
||||
res, err := sp.Start(context.Background(), "group/project", "", "", false)
|
||||
if err != nil {
|
||||
t.Fatalf("Start: %v", err)
|
||||
}
|
||||
@@ -172,7 +172,7 @@ func TestSpawnerStartValidatesDockerAndDockerfile(t *testing.T) {
|
||||
if err != nil {
|
||||
t.Fatalf("NewSpawner: %v", err)
|
||||
}
|
||||
_, err = sp2.Start(context.Background(), "group/project", "", "")
|
||||
_, err = sp2.Start(context.Background(), "group/project", "", "", false)
|
||||
if err == nil || !strings.Contains(err.Error(), "no worker Dockerfile") {
|
||||
t.Fatalf("Start without dockerfile err = %v", err)
|
||||
}
|
||||
@@ -247,7 +247,7 @@ func TestSpawnerRunJobErrorStates(t *testing.T) {
|
||||
tc.setup(t, f)
|
||||
}
|
||||
sp, _ := newTestSpawner(t, f)
|
||||
res, err := sp.Start(context.Background(), "group/project", "", "")
|
||||
res, err := sp.Start(context.Background(), "group/project", "", "", false)
|
||||
if err != nil {
|
||||
t.Fatalf("Start: %v", err)
|
||||
}
|
||||
@@ -384,11 +384,11 @@ func TestSpawnerSameRepoSpawnsSerialize(t *testing.T) {
|
||||
t.Fatal("slugLock must return distinct mutexes per slug")
|
||||
}
|
||||
|
||||
res1, err := sp.Start(context.Background(), "group/project", "", "")
|
||||
res1, err := sp.Start(context.Background(), "group/project", "", "", false)
|
||||
if err != nil {
|
||||
t.Fatalf("Start 1: %v", err)
|
||||
}
|
||||
res2, err := sp.Start(context.Background(), "group/project", "", "")
|
||||
res2, err := sp.Start(context.Background(), "group/project", "", "", false)
|
||||
if err != nil {
|
||||
t.Fatalf("Start 2: %v", err)
|
||||
}
|
||||
@@ -436,7 +436,7 @@ func TestSpawnerCloneUsesHeaderAuthNotURLCredentials(t *testing.T) {
|
||||
t.Fatalf("set token: %v", err)
|
||||
}
|
||||
|
||||
res, err := sp.Start(context.Background(), "group/project", "", "")
|
||||
res, err := sp.Start(context.Background(), "group/project", "", "", false)
|
||||
if err != nil {
|
||||
t.Fatalf("Start: %v", err)
|
||||
}
|
||||
@@ -491,7 +491,7 @@ func TestSpawnerFailedSeedRemovesRepoVolume(t *testing.T) {
|
||||
f.failArchive = true // CopyToContainer fails → seed fails
|
||||
sp, _ := newTestSpawner(t, f)
|
||||
|
||||
res, err := sp.Start(context.Background(), "group/project", "", "")
|
||||
res, err := sp.Start(context.Background(), "group/project", "", "", false)
|
||||
if err != nil {
|
||||
t.Fatalf("Start: %v", err)
|
||||
}
|
||||
@@ -514,7 +514,7 @@ func TestSpawnerFailedSeedSurvivesVolumeRemoveFailure(t *testing.T) {
|
||||
f.failVolumeDelete = true // cleanup itself fails; seed error still surfaces
|
||||
sp, _ := newTestSpawner(t, f)
|
||||
|
||||
res, err := sp.Start(context.Background(), "group/project", "", "")
|
||||
res, err := sp.Start(context.Background(), "group/project", "", "", false)
|
||||
if err != nil {
|
||||
t.Fatalf("Start: %v", err)
|
||||
}
|
||||
@@ -722,7 +722,7 @@ func TestSpawnerStartDockerUnavailable(t *testing.T) {
|
||||
// because the client is already built; close the fake server instead.
|
||||
sp.cli.Close()
|
||||
closeDocker(t, sp)
|
||||
if _, err := sp.Start(context.Background(), "group/project", "", ""); err == nil || !strings.Contains(err.Error(), "docker unavailable") {
|
||||
if _, err := sp.Start(context.Background(), "group/project", "", "", false); err == nil || !strings.Contains(err.Error(), "docker unavailable") {
|
||||
t.Fatalf("Start with dead docker = %v, want docker unavailable", err)
|
||||
}
|
||||
}
|
||||
@@ -799,7 +799,7 @@ func TestSpawnerSessionsVolumeCreateFails(t *testing.T) {
|
||||
f.volume[volumeRepoPrefix+repoSlug("group/project")] = true // repo volume exists → skip seed
|
||||
f.failVolumeCreate = true
|
||||
sp, _ := newTestSpawner(t, f)
|
||||
res, err := sp.Start(context.Background(), "group/project", "", "")
|
||||
res, err := sp.Start(context.Background(), "group/project", "", "", false)
|
||||
if err != nil {
|
||||
t.Fatalf("Start: %v", err)
|
||||
}
|
||||
@@ -844,7 +844,7 @@ func TestSpawnerStoreFailures(t *testing.T) {
|
||||
dead2 := openTestStore(t)
|
||||
_ = dead2.Close()
|
||||
sp2.store = dead2
|
||||
res, err := sp2.Start(context.Background(), "group/project", "", "")
|
||||
res, err := sp2.Start(context.Background(), "group/project", "", "", false)
|
||||
if err != nil {
|
||||
t.Fatalf("Start: %v", err)
|
||||
}
|
||||
@@ -877,7 +877,7 @@ func TestSpawnerWorkerCreateStartFailures(t *testing.T) {
|
||||
sp, _ := newTestSpawner(t, f)
|
||||
|
||||
f.failCreate = true
|
||||
res, err := sp.Start(context.Background(), "group/project", "", "")
|
||||
res, err := sp.Start(context.Background(), "group/project", "", "", false)
|
||||
if err != nil {
|
||||
t.Fatalf("Start: %v", err)
|
||||
}
|
||||
@@ -887,7 +887,7 @@ func TestSpawnerWorkerCreateStartFailures(t *testing.T) {
|
||||
|
||||
f.failCreate = false
|
||||
f.failStart = true
|
||||
res2, err := sp.Start(context.Background(), "group/project", "", "")
|
||||
res2, err := sp.Start(context.Background(), "group/project", "", "", false)
|
||||
if err != nil {
|
||||
t.Fatalf("Start 2: %v", err)
|
||||
}
|
||||
@@ -906,7 +906,7 @@ func TestSpawnerBuildHTTPErrors(t *testing.T) {
|
||||
f.images = 0
|
||||
f.failBuildHTTP = true // /build endpoint itself 500s
|
||||
sp, _ := newTestSpawner(t, f)
|
||||
res, err := sp.Start(context.Background(), "group/project", "", "")
|
||||
res, err := sp.Start(context.Background(), "group/project", "", "", false)
|
||||
if err != nil {
|
||||
t.Fatalf("Start: %v", err)
|
||||
}
|
||||
@@ -1186,7 +1186,7 @@ func TestSpawnerStartDedupesImageList(t *testing.T) {
|
||||
useFakeGit(t, fakeGitModeOK)
|
||||
f := newFakeDocker()
|
||||
sp, _ := newTestSpawner(t, f)
|
||||
res, err := sp.Start(context.Background(), "group/project", "", "")
|
||||
res, err := sp.Start(context.Background(), "group/project", "", "", false)
|
||||
if err != nil {
|
||||
t.Fatalf("Start: %v", err)
|
||||
}
|
||||
@@ -1209,7 +1209,7 @@ func TestSpawnerCustomImageUsed(t *testing.T) {
|
||||
t.Fatalf("set repo image: %v", err)
|
||||
}
|
||||
|
||||
res, err := sp.Start(context.Background(), "group/project", "", "")
|
||||
res, err := sp.Start(context.Background(), "group/project", "", "", false)
|
||||
if err != nil {
|
||||
t.Fatalf("Start: %v", err)
|
||||
}
|
||||
@@ -1238,7 +1238,7 @@ func TestSpawnerCustomImageMissing(t *testing.T) {
|
||||
t.Fatalf("set repo image: %v", err)
|
||||
}
|
||||
|
||||
res, err := sp.Start(context.Background(), "group/project", "", "")
|
||||
res, err := sp.Start(context.Background(), "group/project", "", "", false)
|
||||
if err != nil {
|
||||
t.Fatalf("Start: %v", err)
|
||||
}
|
||||
@@ -1266,7 +1266,7 @@ func TestSpawnerCustomImageDeleteFallsBack(t *testing.T) {
|
||||
t.Fatalf("delete repo image: %v", err)
|
||||
}
|
||||
|
||||
res, err := sp.Start(context.Background(), "group/project", "", "")
|
||||
res, err := sp.Start(context.Background(), "group/project", "", "", false)
|
||||
if err != nil {
|
||||
t.Fatalf("Start: %v", err)
|
||||
}
|
||||
@@ -1287,7 +1287,7 @@ func TestSpawnerCustomImageCheckDockerDead(t *testing.T) {
|
||||
t.Fatalf("set repo image: %v", err)
|
||||
}
|
||||
sp.images0AndDead(t)
|
||||
_, _, err := sp.createAndStart(context.Background(), "group/project", repoSlug("group/project"), "", "s1")
|
||||
_, _, err := sp.createAndStart(context.Background(), "group/project", repoSlug("group/project"), "", false, "s1")
|
||||
if err == nil || !strings.Contains(err.Error(), "docker unavailable") {
|
||||
t.Fatalf("createAndStart with dead docker = %v, want docker unavailable", err)
|
||||
}
|
||||
@@ -1298,7 +1298,7 @@ func TestSpawnerModelEnv(t *testing.T) {
|
||||
f := newFakeDocker()
|
||||
sp, _ := newTestSpawner(t, f)
|
||||
|
||||
res, err := sp.Start(context.Background(), "group/project", "", "zai-renaud/glm-5.2")
|
||||
res, err := sp.Start(context.Background(), "group/project", "", "zai-renaud/glm-5.2", false)
|
||||
if err != nil {
|
||||
t.Fatalf("Start: %v", err)
|
||||
}
|
||||
@@ -1317,3 +1317,34 @@ func TestSpawnerModelEnv(t *testing.T) {
|
||||
t.Fatalf("LVMH_MODEL missing from %v", creates[0].Env)
|
||||
}
|
||||
}
|
||||
|
||||
func TestSpawnerEmptyScratch(t *testing.T) {
|
||||
useFakeGit(t, fakeGitModeOK)
|
||||
f := newFakeDocker()
|
||||
sp, _ := newTestSpawner(t, f)
|
||||
|
||||
res, err := sp.Start(context.Background(), "group/project", "", "", true)
|
||||
if err != nil {
|
||||
t.Fatalf("Start: %v", err)
|
||||
}
|
||||
waitJobState(t, sp, res.SessionID, stateRunning)
|
||||
creates := f.createsByName("lvmh-agent-")
|
||||
if len(creates) != 1 {
|
||||
t.Fatalf("creates = %d", len(creates))
|
||||
}
|
||||
binds := creates[0].HostConfig.Binds
|
||||
for _, b := range binds {
|
||||
if b == "lvmh-repo-group--project-ab12cd:/workspace" {
|
||||
t.Fatalf("empty spawn must not mount the repo volume: %v", binds)
|
||||
}
|
||||
}
|
||||
hasSessions := false
|
||||
for _, b := range binds {
|
||||
if b == "lvmh-sessions:/pi-sessions" {
|
||||
hasSessions = true
|
||||
}
|
||||
}
|
||||
if !hasSessions {
|
||||
t.Fatalf("sessions volume missing: %v", binds)
|
||||
}
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user