Add unit tests for dockeragent runtime detection functions
Test coverage for: - detectRuntime: 11 test cases covering podman/docker detection via endpoint path, InitBinary, ServerVersion, DriverStatus, SecurityOptions - buildRuntimeCandidates: 6 test cases verifying candidate ordering, deduplication, and preference-based filtering - randomDuration: 5 test cases for boundary conditions and randomness - determineSelfUpdateArch: validates architecture detection output Coverage increased from 17.5% to 21.4%.
This commit is contained in:
parent
59067411a9
commit
73e5ff0914
1 changed files with 280 additions and 0 deletions
|
|
@ -7,6 +7,7 @@ import (
|
|||
"time"
|
||||
|
||||
containertypes "github.com/docker/docker/api/types/container"
|
||||
systemtypes "github.com/docker/docker/api/types/system"
|
||||
agentsdocker "github.com/rcourtman/pulse-go-rewrite/pkg/agents/docker"
|
||||
)
|
||||
|
||||
|
|
@ -752,6 +753,285 @@ func TestExtractPodmanMetadata(t *testing.T) {
|
|||
}
|
||||
}
|
||||
|
||||
func TestDetectRuntime(t *testing.T) {
|
||||
tests := []struct {
|
||||
name string
|
||||
info systemtypes.Info
|
||||
endpoint string
|
||||
preference RuntimeKind
|
||||
want RuntimeKind
|
||||
}{
|
||||
{
|
||||
name: "preference podman returns podman",
|
||||
info: systemtypes.Info{},
|
||||
endpoint: "unix:///var/run/docker.sock",
|
||||
preference: RuntimePodman,
|
||||
want: RuntimePodman,
|
||||
},
|
||||
{
|
||||
name: "podman in endpoint lowercase",
|
||||
info: systemtypes.Info{},
|
||||
endpoint: "unix:///run/podman/podman.sock",
|
||||
preference: RuntimeAuto,
|
||||
want: RuntimePodman,
|
||||
},
|
||||
{
|
||||
name: "libpod in endpoint",
|
||||
info: systemtypes.Info{},
|
||||
endpoint: "unix:///run/user/1000/libpod/libpod.sock",
|
||||
preference: RuntimeAuto,
|
||||
want: RuntimePodman,
|
||||
},
|
||||
{
|
||||
name: "podman in endpoint uppercase",
|
||||
info: systemtypes.Info{},
|
||||
endpoint: "unix:///run/PODMAN/podman.sock",
|
||||
preference: RuntimeAuto,
|
||||
want: RuntimePodman,
|
||||
},
|
||||
{
|
||||
name: "podman in InitBinary",
|
||||
info: systemtypes.Info{
|
||||
InitBinary: "/usr/bin/podman",
|
||||
},
|
||||
endpoint: "unix:///var/run/docker.sock",
|
||||
preference: RuntimeAuto,
|
||||
want: RuntimePodman,
|
||||
},
|
||||
{
|
||||
name: "podman in ServerVersion",
|
||||
info: systemtypes.Info{
|
||||
ServerVersion: "4.5.0-podman",
|
||||
},
|
||||
endpoint: "unix:///var/run/docker.sock",
|
||||
preference: RuntimeAuto,
|
||||
want: RuntimePodman,
|
||||
},
|
||||
{
|
||||
name: "podman in DriverStatus",
|
||||
info: systemtypes.Info{
|
||||
DriverStatus: [][2]string{
|
||||
{"Driver", "overlay"},
|
||||
{"Backing Filesystem", "extfs"},
|
||||
{"Supports d_type", "true"},
|
||||
{"Using metacopy", "podman-default"},
|
||||
},
|
||||
},
|
||||
endpoint: "unix:///var/run/docker.sock",
|
||||
preference: RuntimeAuto,
|
||||
want: RuntimePodman,
|
||||
},
|
||||
{
|
||||
name: "podman in SecurityOptions",
|
||||
info: systemtypes.Info{
|
||||
SecurityOptions: []string{
|
||||
"name=seccomp,profile=default",
|
||||
"name=rootless,podman",
|
||||
},
|
||||
},
|
||||
endpoint: "unix:///var/run/docker.sock",
|
||||
preference: RuntimeAuto,
|
||||
want: RuntimePodman,
|
||||
},
|
||||
{
|
||||
name: "preference docker returns docker",
|
||||
info: systemtypes.Info{},
|
||||
endpoint: "unix:///var/run/docker.sock",
|
||||
preference: RuntimeDocker,
|
||||
want: RuntimeDocker,
|
||||
},
|
||||
{
|
||||
name: "auto with docker socket returns docker",
|
||||
info: systemtypes.Info{},
|
||||
endpoint: "unix:///var/run/docker.sock",
|
||||
preference: RuntimeAuto,
|
||||
want: RuntimeDocker,
|
||||
},
|
||||
{
|
||||
name: "docker info with no podman indicators returns docker",
|
||||
info: systemtypes.Info{
|
||||
InitBinary: "docker-init",
|
||||
ServerVersion: "24.0.5",
|
||||
DriverStatus: [][2]string{{"Driver", "overlay2"}},
|
||||
SecurityOptions: []string{"name=seccomp,profile=default"},
|
||||
},
|
||||
endpoint: "unix:///var/run/docker.sock",
|
||||
preference: RuntimeAuto,
|
||||
want: RuntimeDocker,
|
||||
},
|
||||
}
|
||||
|
||||
for _, tt := range tests {
|
||||
t.Run(tt.name, func(t *testing.T) {
|
||||
got := detectRuntime(tt.info, tt.endpoint, tt.preference)
|
||||
if got != tt.want {
|
||||
t.Errorf("detectRuntime() = %v, want %v", got, tt.want)
|
||||
}
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
func TestBuildRuntimeCandidates(t *testing.T) {
|
||||
tests := []struct {
|
||||
name string
|
||||
preference RuntimeKind
|
||||
wantMin int // minimum expected candidates
|
||||
}{
|
||||
{
|
||||
name: "auto includes both podman and docker sockets",
|
||||
preference: RuntimeAuto,
|
||||
wantMin: 3, // at least env defaults + podman rootless + docker default
|
||||
},
|
||||
{
|
||||
name: "docker preference includes docker socket",
|
||||
preference: RuntimeDocker,
|
||||
wantMin: 2, // at least env defaults + docker default
|
||||
},
|
||||
{
|
||||
name: "podman preference includes podman sockets",
|
||||
preference: RuntimePodman,
|
||||
wantMin: 3, // at least env defaults + podman rootless + podman system
|
||||
},
|
||||
}
|
||||
|
||||
for _, tt := range tests {
|
||||
t.Run(tt.name, func(t *testing.T) {
|
||||
candidates := buildRuntimeCandidates(tt.preference)
|
||||
if len(candidates) < tt.wantMin {
|
||||
t.Errorf("buildRuntimeCandidates(%v) returned %d candidates, want at least %d", tt.preference, len(candidates), tt.wantMin)
|
||||
}
|
||||
|
||||
// Verify first candidate is always "environment defaults"
|
||||
if len(candidates) > 0 && candidates[0].label != "environment defaults" {
|
||||
t.Errorf("first candidate should be 'environment defaults', got %q", candidates[0].label)
|
||||
}
|
||||
|
||||
// Verify no duplicates
|
||||
seen := make(map[string]bool)
|
||||
for _, c := range candidates {
|
||||
key := c.host
|
||||
if key == "" {
|
||||
key = "__default__"
|
||||
}
|
||||
if seen[key] {
|
||||
t.Errorf("duplicate candidate with host %q", c.host)
|
||||
}
|
||||
seen[key] = true
|
||||
}
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
func TestBuildRuntimeCandidatesContent(t *testing.T) {
|
||||
// Test that docker preference doesn't include podman sockets
|
||||
t.Run("docker excludes podman sockets", func(t *testing.T) {
|
||||
candidates := buildRuntimeCandidates(RuntimeDocker)
|
||||
for _, c := range candidates {
|
||||
if c.label == "podman rootless socket" || c.label == "podman system socket" {
|
||||
t.Errorf("docker preference should not include %q", c.label)
|
||||
}
|
||||
}
|
||||
})
|
||||
|
||||
// Test that podman preference doesn't include default docker socket
|
||||
t.Run("podman excludes default docker socket", func(t *testing.T) {
|
||||
candidates := buildRuntimeCandidates(RuntimePodman)
|
||||
for _, c := range candidates {
|
||||
if c.label == "default docker socket" {
|
||||
t.Errorf("podman preference should not include %q", c.label)
|
||||
}
|
||||
}
|
||||
})
|
||||
|
||||
// Test that auto includes both
|
||||
t.Run("auto includes both docker and podman", func(t *testing.T) {
|
||||
candidates := buildRuntimeCandidates(RuntimeAuto)
|
||||
hasDocker := false
|
||||
hasPodman := false
|
||||
for _, c := range candidates {
|
||||
if c.label == "default docker socket" {
|
||||
hasDocker = true
|
||||
}
|
||||
if c.label == "podman rootless socket" || c.label == "podman system socket" {
|
||||
hasPodman = true
|
||||
}
|
||||
}
|
||||
if !hasDocker {
|
||||
t.Error("auto preference should include docker socket")
|
||||
}
|
||||
if !hasPodman {
|
||||
t.Error("auto preference should include podman sockets")
|
||||
}
|
||||
})
|
||||
}
|
||||
|
||||
func TestRandomDuration(t *testing.T) {
|
||||
tests := []struct {
|
||||
name string
|
||||
max time.Duration
|
||||
}{
|
||||
{"zero max returns zero", 0},
|
||||
{"negative max returns zero", -time.Second},
|
||||
{"positive max returns value in range", 5 * time.Second},
|
||||
{"large max works", time.Hour},
|
||||
}
|
||||
|
||||
for _, tt := range tests {
|
||||
t.Run(tt.name, func(t *testing.T) {
|
||||
if tt.max <= 0 {
|
||||
got := randomDuration(tt.max)
|
||||
if got != 0 {
|
||||
t.Errorf("randomDuration(%v) = %v, want 0", tt.max, got)
|
||||
}
|
||||
return
|
||||
}
|
||||
|
||||
// For positive max, verify the result is in range
|
||||
// Run multiple times to check randomness
|
||||
for i := 0; i < 10; i++ {
|
||||
got := randomDuration(tt.max)
|
||||
if got < 0 || got >= tt.max {
|
||||
t.Errorf("randomDuration(%v) = %v, want [0, %v)", tt.max, got, tt.max)
|
||||
}
|
||||
}
|
||||
})
|
||||
}
|
||||
|
||||
// Test that results are actually random (not all the same)
|
||||
t.Run("results vary", func(t *testing.T) {
|
||||
max := 10 * time.Second
|
||||
results := make(map[time.Duration]bool)
|
||||
for i := 0; i < 100; i++ {
|
||||
results[randomDuration(max)] = true
|
||||
}
|
||||
// With 100 iterations, we should get more than 1 unique result
|
||||
if len(results) < 2 {
|
||||
t.Error("randomDuration appears to not be random")
|
||||
}
|
||||
})
|
||||
}
|
||||
|
||||
func TestDetermineSelfUpdateArch(t *testing.T) {
|
||||
// This test validates the function returns a valid result
|
||||
// The actual result depends on the runtime environment
|
||||
got := determineSelfUpdateArch()
|
||||
|
||||
// Should be one of the known values or empty (if architecture unknown)
|
||||
validArches := map[string]bool{
|
||||
"": true,
|
||||
"linux-amd64": true,
|
||||
"linux-arm64": true,
|
||||
"linux-armv7": true,
|
||||
}
|
||||
|
||||
if !validArches[got] {
|
||||
t.Errorf("determineSelfUpdateArch() = %q, want one of %v", got, validArches)
|
||||
}
|
||||
|
||||
// On most test environments, we should get a non-empty result
|
||||
// But we can't assert this as it depends on the machine
|
||||
}
|
||||
|
||||
func TestDetectHostRemovedError(t *testing.T) {
|
||||
tests := []struct {
|
||||
name string
|
||||
|
|
|
|||
Loading…
Reference in a new issue