Files
2026-07-20 17:02:37 +00:00

200 lines
6.8 KiB
Go

//go:build linux && agentcompat
package agent
import (
"context"
"errors"
"fmt"
"os"
"path/filepath"
"strings"
"syscall"
"testing"
"time"
"github.com/stretchr/testify/require"
)
func TestPreparedBinary_EightIndependentAgentsShareBinaryAndCleanUp(t *testing.T) {
prepared, err := PrepareBinary(t.Context(), testAgentSourceDir(t))
require.NoError(t, err)
preparedRoot := prepared.WorkspaceRoot()
binaryPath := prepared.BinaryPath()
require.FileExists(t, binaryPath)
requireDirectoryMode(t, preparedRoot, 0o711)
requireDirectoryMode(t, filepath.Dir(binaryPath), 0o711)
initialBinaryInfo, err := os.Stat(binaryPath)
require.NoError(t, err)
initialBinaryStat, ok := initialBinaryInfo.Sys().(*syscall.Stat_t)
require.True(t, ok)
instances := make([]*Agent, 0, 8)
t.Cleanup(func() {
for _, instance := range instances {
cleanupContext, cancel := context.WithTimeout(context.Background(), 15*time.Second)
_ = instance.Stop(cleanupContext)
cancel()
}
_ = prepared.Close()
})
dashboardInstance := startTestDashboard(t, true)
configPaths := make(map[string]struct{}, 8)
logPaths := make(map[string]struct{}, 8)
for index := range 8 {
config := AgentStartConfig{
PreparedBinary: prepared,
Endpoint: dashboardInstance.TLSEndpoint(),
Secret: dashboardInstance.AgentSecret(),
UUID: preparedBinaryUUID(index),
TLS: true,
CAFilePath: dashboardInstance.TLSCACertificatePath(),
Debug: index%2 == 0,
}
if index == 6 {
config.FMObserverRunID = "prepared-binary-observer"
}
if index == 7 {
config.Credential = &syscall.Credential{Uid: 65534, Gid: 65534}
}
instance, startErr := Start(t.Context(), config)
require.NoError(t, startErr)
require.Equal(t, binaryPath, instance.BinaryPath())
processBinaryInfo, statErr := os.Stat(fmt.Sprintf("/proc/%d/exe", instance.PID()))
if errors.Is(statErr, syscall.EACCES) || errors.Is(statErr, syscall.EPERM) {
t.Logf("kernel denied /proc/%d/exe metadata for agent %d", instance.PID(), index)
} else {
require.NoError(t, statErr)
processBinaryStat, statOK := processBinaryInfo.Sys().(*syscall.Stat_t)
require.True(t, statOK)
require.Equal(t, initialBinaryStat.Dev, processBinaryStat.Dev)
require.Equal(t, initialBinaryStat.Ino, processBinaryStat.Ino)
}
require.NotEqual(t, preparedRoot, instance.WorkspaceRoot())
require.NoFileExists(t, filepath.Join(instance.WorkspaceRoot(), "bin", "agent"))
configPaths[instance.ConfigPath()] = struct{}{}
logPaths[instance.LogPath()] = struct{}{}
require.NoError(t, waitForAgentReady(t, instance, dashboardInstance))
if index == 6 {
require.NotNil(t, instance.FMProducerObserver())
require.FileExists(t, instance.fmObserverPath)
}
if index == 7 {
status, statusErr := os.ReadFile(fmt.Sprintf("/proc/%d/status", instance.PID()))
require.NoError(t, statusErr)
require.Contains(t, string(status), "Uid:\t65534\t65534\t65534\t65534")
}
instances = append(instances, instance)
}
finalBinaryInfo, err := os.Stat(binaryPath)
require.NoError(t, err)
require.True(t, os.SameFile(initialBinaryInfo, finalBinaryInfo))
require.Len(t, configPaths, 8)
require.Len(t, logPaths, 8)
closeErr := prepared.Close()
var usageErr *PreparedBinaryUsageError
require.ErrorAs(t, closeErr, &usageErr)
require.Equal(t, "close", usageErr.Operation)
require.Equal(t, "has active consumers", usageErr.Reason)
for index, instance := range instances {
stopContext, cancel := context.WithTimeout(context.Background(), 15*time.Second)
require.NoError(t, instance.Stop(stopContext), "agent %d", index)
cancel()
require.True(t, instance.CleanupReceipt().Passed)
require.False(t, instance.CleanupReceipt().Forced)
if index < len(instances)-1 {
require.DirExists(t, preparedRoot)
require.FileExists(t, binaryPath)
}
}
require.NoError(t, prepared.Close())
require.NoError(t, prepared.Close())
_, statErr := os.Stat(preparedRoot)
require.ErrorIs(t, statErr, os.ErrNotExist)
}
func requireDirectoryMode(t *testing.T, path string, want os.FileMode) {
t.Helper()
info, err := os.Stat(path)
require.NoError(t, err)
require.Equal(t, want, info.Mode().Perm())
}
func TestAgent_StartBuildsAndOwnsItsBinary(t *testing.T) {
instance, err := Start(t.Context(), AgentStartConfig{
SourceDir: testAgentSourceDir(t),
Endpoint: "127.0.0.1:1",
UUID: "00000000-0000-0000-0000-000000000197",
})
require.NoError(t, err)
workspaceRoot := instance.WorkspaceRoot()
require.Equal(t, filepath.Join(workspaceRoot, "bin", "agent"), instance.BinaryPath())
require.FileExists(t, instance.BinaryPath())
stopContext, cancel := context.WithTimeout(context.Background(), 15*time.Second)
defer cancel()
require.NoError(t, instance.Stop(stopContext))
_, statErr := os.Stat(workspaceRoot)
require.ErrorIs(t, statErr, os.ErrNotExist)
}
func TestPreparedBinary_RejectsConsumerAfterClose(t *testing.T) {
prepared, err := PrepareBinary(t.Context(), testAgentSourceDir(t))
require.NoError(t, err)
require.NoError(t, prepared.Close())
_, err = Start(t.Context(), AgentStartConfig{
PreparedBinary: prepared,
Endpoint: "127.0.0.1:1",
UUID: "00000000-0000-0000-0000-000000000198",
})
var usageErr *PreparedBinaryUsageError
require.ErrorAs(t, err, &usageErr)
require.Equal(t, "start", usageErr.Operation)
require.Equal(t, "is closed", usageErr.Reason)
}
func TestPreparedBinary_RejectsInvalidSourceDirectory(t *testing.T) {
_, err := PrepareBinary(t.Context(), "relative")
var usageErr *PreparedBinaryUsageError
require.ErrorAs(t, err, &usageErr)
require.Equal(t, "prepare", usageErr.Operation)
require.True(t, strings.Contains(usageErr.Reason, "must be absolute"))
}
func TestPreparedBinary_CancelledBuildRemovesWorkspace(t *testing.T) {
temporaryRoot := t.TempDir()
t.Setenv("TMPDIR", temporaryRoot)
ctx, cancel := context.WithCancel(t.Context())
cancel()
_, err := PrepareBinary(ctx, testAgentSourceDir(t))
require.Error(t, err)
entries, readErr := os.ReadDir(temporaryRoot)
require.NoError(t, readErr)
require.Empty(t, entries)
}
func TestPreparedBinary_RejectsUninitializedValue(t *testing.T) {
_, err := Start(t.Context(), AgentStartConfig{
PreparedBinary: &PreparedBinary{},
Endpoint: "127.0.0.1:1",
UUID: "00000000-0000-0000-0000-000000000199",
})
var usageErr *PreparedBinaryUsageError
require.ErrorAs(t, err, &usageErr)
require.Equal(t, "start", usageErr.Operation)
require.Equal(t, "is uninitialized", usageErr.Reason)
}
func TestPreparedBinary_CloseRejectsNilAndUninitializedValues(t *testing.T) {
var nilPrepared *PreparedBinary
for _, prepared := range []*PreparedBinary{nilPrepared, &PreparedBinary{}} {
err := prepared.Close()
var usageErr *PreparedBinaryUsageError
require.ErrorAs(t, err, &usageErr)
require.Equal(t, "close", usageErr.Operation)
}
}
func preparedBinaryUUID(index int) string {
return fmt.Sprintf("00000000-0000-0000-0000-%012d", 190+index)
}