Files
nezha_domains/integration/agentcompat/internal/scenario/reconnect_fault_real_test.go
T
2026-07-20 04:51:20 +00:00

107 lines
4.4 KiB
Go

//go:build linux && agentcompat
package scenario
import (
"context"
"encoding/json"
"net"
"os"
"path/filepath"
"strconv"
"testing"
"time"
"github.com/nezhahq/nezha/integration/agentcompat/internal/contract"
processharness "github.com/nezhahq/nezha/integration/agentcompat/internal/process"
"github.com/stretchr/testify/require"
)
func TestReconnectScenario_DashboardExitFaultCleansRealProcesses(t *testing.T) {
// Given
nezhaSource := os.Getenv("AGENTCOMPAT_NEZHA_SOURCE")
agentSource := os.Getenv("AGENTCOMPAT_AGENT_SOURCE")
if nezhaSource == "" || agentSource == "" {
t.Skip("set AGENTCOMPAT_NEZHA_SOURCE and AGENTCOMPAT_AGENT_SOURCE")
}
evidenceDirectory := os.Getenv("AGENTCOMPAT_RECONNECT_FAULT_EVIDENCE_DIR")
if evidenceDirectory == "" {
evidenceDirectory = t.TempDir()
}
require.NoError(t, os.MkdirAll(evidenceDirectory, 0o700))
paths, err := contract.NewPaths(nezhaSource, agentSource, evidenceDirectory)
require.NoError(t, err)
testContext, cancel := context.WithTimeout(t.Context(), 10*time.Minute)
defer cancel()
// When
result, reconnectEvidence, runErr := (Reconnect{}).RunWithEvidence(testContext, ReconnectInput{Paths: paths, DashboardFault: "dashboard-exit"})
// Then
require.ErrorIs(t, runErr, ErrReconnectDashboardExitFault)
require.Equal(t, reconnectScenarioName, result.Name)
require.False(t, result.Passed)
require.True(t, result.CleanupOK)
require.Contains(t, result.Error, ErrReconnectDashboardExitFault.Error())
require.Len(t, result.Assertions, 3)
require.True(t, result.Assertions[0].Passed)
require.Equal(t, "Dashboard disconnect barrier stopped generation one", result.Assertions[0].Name)
require.True(t, result.Assertions[1].Passed)
require.Equal(t, "outside-root sentinel remains unchanged", result.Assertions[1].Name)
require.True(t, result.Assertions[2].Passed)
require.Equal(t, "multi-generation process listener and workspace cleanup completed", result.Assertions[2].Name)
require.True(t, reconnectEvidence.Lifecycle.OutsideRootSentinelUnchanged)
require.True(t, reconnectEvidence.AgentCleanup.Passed)
require.False(t, reconnectEvidence.AgentCleanup.Forced)
require.Len(t, reconnectEvidence.AgentCleanup.Processes, 1)
require.True(t, reconnectEvidence.DashboardCleanup.Passed)
require.False(t, reconnectEvidence.DashboardCleanup.Forced)
require.Len(t, reconnectEvidence.DashboardCleanup.Processes, 1)
require.Zero(t, reconnectEvidence.Runtime.DashboardAfter)
require.Zero(t, reconnectEvidence.Runtime.AgentAfter)
require.NoDirExists(t, reconnectEvidence.Fixture.AgentRoot)
require.NoDirExists(t, reconnectEvidence.Fixture.Dashboard.WorkspaceRoot)
for _, cleanupReceipt := range []struct {
name string
processes []processharness.CleanupRecord
}{
{name: "Agent", processes: reconnectEvidence.AgentCleanup.Processes},
{name: "Dashboard", processes: reconnectEvidence.DashboardCleanup.Processes},
} {
for _, process := range cleanupReceipt.processes {
require.NoDirExists(t, filepath.Join("/proc", strconv.Itoa(process.PID)), "%s process %d survived cleanup", cleanupReceipt.name, process.PID)
}
}
for _, listenerIdentity := range []struct {
name string
address string
}{
{name: "Dashboard HTTP", address: reconnectEvidence.Fixture.Dashboard.HTTP.Address},
{name: "Dashboard receipt", address: reconnectEvidence.Fixture.Dashboard.Receipt.Address},
} {
listener, listenErr := net.Listen("tcp", listenerIdentity.address)
require.NoError(t, listenErr, "%s listener was not released", listenerIdentity.name)
require.NoError(t, listener.Close())
}
artifactPath := filepath.Join(evidenceDirectory, "reconnect-dashboard-exit-real-process.json")
type faultArtifact struct {
Result Result `json:"result"`
Evidence ReconnectEvidence `json:"evidence"`
Error string `json:"error"`
}
recordedArtifact := faultArtifact{Result: result, Evidence: reconnectEvidence, Error: runErr.Error()}
artifact, err := json.MarshalIndent(recordedArtifact, "", " ")
require.NoError(t, err)
require.NoError(t, os.WriteFile(artifactPath, append(artifact, '\n'), 0o600))
artifactInfo, err := os.Stat(artifactPath)
require.NoError(t, err)
require.Equal(t, os.FileMode(0o600), artifactInfo.Mode().Perm())
readArtifact, err := os.ReadFile(artifactPath)
require.NoError(t, err)
var decodedArtifact faultArtifact
require.NoError(t, json.Unmarshal(readArtifact, &decodedArtifact))
require.Equal(t, recordedArtifact, decodedArtifact)
t.Logf("reconnect Dashboard-exit fault artifact: %s", artifactPath)
}