//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")
}
}