//go:build linux package microvm import ( "strings" "testing" "tangled.org/core/spindle/models" "tangled.org/core/spindle/secrets" ) func TestDebugSSHCommandUsesJumpHost(t *testing.T) { got := debugSSHCommand("0.0.0.0:2224", "executor-a.internal", "executor-a", "spindle.example", "job-1") want := "ssh -tt -J spindle.example -p 2224 job-1@executor-a" if got != want { t.Fatalf("debug ssh command = %q, want %q", got, want) } } func TestDebugSSHCommandUsesExecutorHostWithoutJump(t *testing.T) { got := debugSSHCommand("0.0.0.0:22", "executor-a.example", "", "", "job-1") want := "ssh -tt job-1@executor-a.example" if got != want { t.Fatalf("debug ssh command = %q, want %q", got, want) } } func TestStepEnvironmentPreservesFailedStepContext(t *testing.T) { workflow := &models.Workflow{ Environment: map[string]string{ "CI": "true", "OVERRIDE": "workflow", }, } step := Step{ environment: map[string]string{ "OVERRIDE": "step", "STEP_ONLY": "yes", }, } unlocked := []secrets.UnlockedSecret{{ Key: "SECRET", Value: "value", }} got := make(map[string]string) for _, value := range stepEnvironment(workflow, step, unlocked) { key, value, ok := strings.Cut(value, "=") if ok { got[key] = value } } want := map[string]string{ "HOME": "/workspace", "LOGNAME": guestWorkflowUser, "PATH": guestBasePATH, "USER": guestWorkflowUser, "CI": "true", "OVERRIDE": "step", "STEP_ONLY": "yes", "SECRET": "value", } for key, value := range want { if got[key] != value { t.Errorf("%s = %q, want %q", key, got[key], value) } } } func heldTarget(repo, actor string) debugTarget { return debugTarget{ holdRepo: repo, actor: actor, connected: make(chan struct{}), released: make(chan struct{}), evicted: make(chan struct{}), } } func holdWid(name string) models.WorkflowId { return models.WorkflowId{PipelineId: models.PipelineId("p"), Name: name} } func TestNewerFailureReplacesAnUnconnectedHold(t *testing.T) { e := &Engine{debug: make(map[string]debugTarget)} first := heldTarget("did:plc:repo", "did:plc:alice") if !e.claimDebugHold(holdWid("first"), first) { t.Fatal("first failure was not held") } // another repo, but the same pusher if !e.claimDebugHold(holdWid("second"), heldTarget("did:plc:other", "did:plc:alice")) { t.Fatal("newer failure was not held") } if !isClosed(first.evicted) { t.Fatal("the older hold was not told to tear down") } if _, ok := e.lookupDebugTarget(newDebugHandle(holdWid("first"))); ok { t.Fatal("the older hold still accepts debug shells") } } func TestConnectedHoldIsNeverReplaced(t *testing.T) { e := &Engine{debug: make(map[string]debugTarget)} first := heldTarget("did:plc:repo", "did:plc:alice") e.claimDebugHold(holdWid("first"), first) if !e.markDebugConnected(newDebugHandle(holdWid("first"))) { t.Fatal("connecting to the hold failed") } if e.claimDebugHold(holdWid("second"), heldTarget("did:plc:repo", "did:plc:bob")) { t.Fatal("a failure in the same repo was held while someone is debugging") } if isClosed(first.evicted) { t.Fatal("the connected hold was torn down") } if !e.claimDebugHold(holdWid("third"), heldTarget("did:plc:other", "did:plc:bob")) { t.Fatal("an unrelated repo and pusher should still get a hold") } } func TestHoldsWithoutAnActorOnlyShareByRepo(t *testing.T) { e := &Engine{debug: make(map[string]debugTarget)} first := heldTarget("did:plc:repo-a", "") e.claimDebugHold(holdWid("first"), first) e.claimDebugHold(holdWid("second"), heldTarget("did:plc:repo-b", "")) if isClosed(first.evicted) { t.Fatal("two repos with no recorded pusher replaced each other") } } func TestReplacedHoldRefusesAShellThatWasStillOpening(t *testing.T) { e := &Engine{debug: make(map[string]debugTarget)} e.claimDebugHold(holdWid("first"), heldTarget("did:plc:repo", "did:plc:alice")) e.claimDebugHold(holdWid("second"), heldTarget("did:plc:repo", "did:plc:alice")) if e.markDebugConnected(newDebugHandle(holdWid("first"))) { t.Fatal("a shell connected to a replaced hold") } }