lazydocker/pkg/commands/socket_detection_test.go
DeiAsPie 81e4e44309
Enhance Podman support with improved socket detection and error handling across platforms
Signed-off-by: DeiAsPie <93835541+DeiAsPie@users.noreply.github.com>
2025-12-27 14:56:14 +05:30

437 lines
12 KiB
Go

//go:build !windows
package commands
import (
"context"
"errors"
"os"
"testing"
"github.com/docker/cli/cli/config/configfile"
"github.com/sirupsen/logrus"
"github.com/stretchr/testify/assert"
)
func TestGetSocketCandidates(t *testing.T) {
// Save and restore
oldGetuid := getuidFunc
oldGetenv := getenvFunc
oldUserHomeDir := userHomeDirFunc
defer func() {
getuidFunc = oldGetuid
getenvFunc = oldGetenv
userHomeDirFunc = oldUserHomeDir
}()
getuidFunc = func() int { return 1000 }
getenvFunc = func(key string) string {
if key == "XDG_RUNTIME_DIR" {
return "/run/user/1000"
}
return ""
}
userHomeDirFunc = func() (string, error) { return "/home/user", nil }
candidates := getSocketCandidates()
expectedPaths := []string{
"unix:///var/run/docker.sock",
"unix:///run/user/1000/docker.sock",
"unix:///home/user/.docker/run/docker.sock",
"unix:///run/user/1000/podman/podman.sock",
"unix:///home/user/.colima/default/docker.sock",
"unix:///home/user/.orbstack/run/docker.sock",
"unix:///home/user/.lima/default/sock/docker.sock",
"unix:///home/user/.rd/docker.sock",
}
var paths []string
for _, c := range candidates {
paths = append(paths, c.Path)
}
for _, expected := range expectedPaths {
assert.Contains(t, paths, expected)
}
}
func TestDetectDockerHost_DOCKER_HOST_Priority(t *testing.T) {
// Save env var
oldDockerHost := os.Getenv("DOCKER_HOST")
defer os.Setenv("DOCKER_HOST", oldDockerHost)
expectedHost := "unix:///tmp/custom.sock"
os.Setenv("DOCKER_HOST", expectedHost)
// Mock validateSocketFunc to succeed
oldValidate := validateSocketFunc
defer func() { validateSocketFunc = oldValidate }()
validateSocketFunc = func(ctx context.Context, host string, useEnv bool) error {
return nil
}
// Reset cache for test
ResetDockerHostCache()
log := logrus.NewEntry(logrus.New())
host, _, err := DetectDockerHost(log)
assert.NoError(t, err)
assert.Equal(t, expectedHost, host)
}
func TestDetectDockerHost_Caching(t *testing.T) {
// Save env var
oldDockerHost := os.Getenv("DOCKER_HOST")
defer os.Setenv("DOCKER_HOST", oldDockerHost)
os.Setenv("DOCKER_HOST", "unix:///tmp/first.sock")
// Mock validateSocketFunc to succeed
oldValidate := validateSocketFunc
defer func() { validateSocketFunc = oldValidate }()
validateSocketFunc = func(ctx context.Context, host string, useEnv bool) error {
return nil
}
// Reset cache for test
ResetDockerHostCache()
log := logrus.NewEntry(logrus.New())
host1, _, _ := DetectDockerHost(log)
// Change env var - should still return first one from cache
os.Setenv("DOCKER_HOST", "unix:///tmp/second.sock")
host2, _, _ := DetectDockerHost(log)
assert.Equal(t, host1, host2)
assert.Equal(t, "unix:///tmp/first.sock", host2)
}
func TestDetectDockerHost_DOCKER_HOST_Invalid(t *testing.T) {
oldDockerHost := os.Getenv("DOCKER_HOST")
os.Setenv("DOCKER_HOST", "unix:///tmp/invalid.sock")
defer os.Setenv("DOCKER_HOST", oldDockerHost)
ResetDockerHostCache()
oldValidate := validateSocketFunc
defer func() { validateSocketFunc = oldValidate }()
validateSocketFunc = func(ctx context.Context, host string, useEnv bool) error {
return errors.New("invalid")
}
log := logrus.NewEntry(logrus.New())
_, _, err := DetectDockerHost(log)
if assert.Error(t, err) {
assert.Contains(t, err.Error(), "DOCKER_HOST")
}
}
func TestDetectDockerHost_Context_Success(t *testing.T) {
oldDockerHost := os.Getenv("DOCKER_HOST")
os.Setenv("DOCKER_HOST", "")
defer os.Setenv("DOCKER_HOST", oldDockerHost)
oldGetHost := getHostFromContextFunc
oldValidate := validateSocketFunc
defer func() {
getHostFromContextFunc = oldGetHost
validateSocketFunc = oldValidate
}()
getHostFromContextFunc = func() (string, error) {
return "unix:///tmp/context.sock", nil
}
validateSocketFunc = func(ctx context.Context, host string, useEnv bool) error {
if host == "unix:///tmp/context.sock" {
return nil
}
return errors.New("invalid")
}
ResetDockerHostCache()
log := logrus.NewEntry(logrus.New())
host, _, err := DetectDockerHost(log)
assert.NoError(t, err)
assert.Equal(t, "unix:///tmp/context.sock", host)
}
func TestDetectDockerHost_Context_Invalid_Fallback(t *testing.T) {
oldDockerHost := os.Getenv("DOCKER_HOST")
os.Setenv("DOCKER_HOST", "")
defer os.Setenv("DOCKER_HOST", oldDockerHost)
oldGetHost := getHostFromContextFunc
oldValidate := validateSocketFunc
oldDetectPlatform := detectPlatformCandidatesFunc
defer func() {
getHostFromContextFunc = oldGetHost
validateSocketFunc = oldValidate
detectPlatformCandidatesFunc = oldDetectPlatform
}()
getHostFromContextFunc = func() (string, error) {
return "unix:///tmp/invalid-context.sock", nil
}
validateSocketFunc = func(ctx context.Context, host string, useEnv bool) error {
return errors.New("invalid")
}
detectPlatformCandidatesFunc = func(log *logrus.Entry) (string, ContainerRuntime, error) {
return "unix:///tmp/fallback.sock", RuntimeDocker, nil
}
ResetDockerHostCache()
log := logrus.NewEntry(logrus.New())
host, _, err := DetectDockerHost(log)
assert.NoError(t, err)
assert.Equal(t, "unix:///tmp/fallback.sock", host)
}
func TestDetectDockerHost_Context_Invalid(t *testing.T) {
// Save env vars
oldDockerHost := os.Getenv("DOCKER_HOST")
oldDockerContext := os.Getenv("DOCKER_CONTEXT")
defer func() {
os.Setenv("DOCKER_HOST", oldDockerHost)
os.Setenv("DOCKER_CONTEXT", oldDockerContext)
}()
os.Setenv("DOCKER_HOST", "")
os.Setenv("DOCKER_CONTEXT", "nonexistent-context-12345")
// Reset cache for test
ResetDockerHostCache()
log := logrus.NewEntry(logrus.New())
_, _, err := DetectDockerHost(log)
assert.Error(t, err)
assert.Contains(t, err.Error(), "failed to use DOCKER_CONTEXT")
}
func TestDetectDockerHost_Context_Error_NoEnv(t *testing.T) {
// Save and restore
oldGetHost := getHostFromContextFunc
oldDetectPlatform := detectPlatformCandidatesFunc
defer func() {
getHostFromContextFunc = oldGetHost
detectPlatformCandidatesFunc = oldDetectPlatform
}()
// Mock getHostFromContext to return error
getHostFromContextFunc = func() (string, error) {
return "", errors.New("context error")
}
// Mock detectPlatformCandidates to return success so we can see it falls through
detectPlatformCandidatesFunc = func(log *logrus.Entry) (string, ContainerRuntime, error) {
return "unix:///tmp/fallback.sock", RuntimeDocker, nil
}
// Clear DOCKER_HOST
oldDockerHost := os.Getenv("DOCKER_HOST")
os.Setenv("DOCKER_HOST", "")
defer os.Setenv("DOCKER_HOST", oldDockerHost)
ResetDockerHostCache()
log := logrus.NewEntry(logrus.New())
host, runtime, err := DetectDockerHost(log)
assert.NoError(t, err)
assert.Equal(t, "unix:///tmp/fallback.sock", host)
assert.Equal(t, RuntimeDocker, runtime)
}
func TestDetectDockerHost_SSH(t *testing.T) {
oldDockerHost := os.Getenv("DOCKER_HOST")
os.Setenv("DOCKER_HOST", "ssh://user@host")
defer os.Setenv("DOCKER_HOST", oldDockerHost)
ResetDockerHostCache()
log := logrus.NewEntry(logrus.New())
host, _, err := DetectDockerHost(log)
assert.NoError(t, err)
assert.Equal(t, "ssh://user@host", host)
}
func TestDetectDockerHost_PlainPath(t *testing.T) {
oldDockerHost := os.Getenv("DOCKER_HOST")
defer os.Setenv("DOCKER_HOST", oldDockerHost)
// Create a temporary file to act as a socket
tmpFile, err := os.CreateTemp("", "lazydocker-test-*.sock")
assert.NoError(t, err)
defer os.Remove(tmpFile.Name())
tmpFile.Close()
os.Setenv("DOCKER_HOST", tmpFile.Name())
ResetDockerHostCache()
// Mock validateSocketFunc to succeed
oldValidate := validateSocketFunc
defer func() { validateSocketFunc = oldValidate }()
validateSocketFunc = func(ctx context.Context, host string, useEnv bool) error {
return nil
}
log := logrus.NewEntry(logrus.New())
host, _, err := DetectDockerHost(log)
assert.NoError(t, err)
assert.Equal(t, "unix://"+tmpFile.Name(), host)
}
func TestValidateSocket_UseEnv(t *testing.T) {
ctx := context.Background()
oldDockerHost := os.Getenv("DOCKER_HOST")
os.Setenv("DOCKER_HOST", "unix:///tmp/test.sock")
defer os.Setenv("DOCKER_HOST", oldDockerHost)
// This will fail ping but cover the useEnv branch
err := validateSocket(ctx, "unix:///tmp/test.sock", true)
assert.Error(t, err)
assert.Contains(t, err.Error(), "ping failed")
}
func TestGetHostFromContext_Implementation(t *testing.T) {
// Save and restore
oldLoad := cliconfigLoadFunc
defer func() { cliconfigLoadFunc = oldLoad }()
t.Run("DOCKER_CONTEXT set", func(t *testing.T) {
oldCtx := os.Getenv("DOCKER_CONTEXT")
os.Setenv("DOCKER_CONTEXT", "default")
defer os.Setenv("DOCKER_CONTEXT", oldCtx)
host, err := getHostFromContext()
assert.NoError(t, err)
assert.Empty(t, host)
})
t.Run("Config load success but empty context", func(t *testing.T) {
cliconfigLoadFunc = func(dir string) (*configfile.ConfigFile, error) {
return &configfile.ConfigFile{CurrentContext: ""}, nil
}
host, err := getHostFromContext()
assert.NoError(t, err)
assert.Empty(t, host)
})
t.Run("Config load error", func(t *testing.T) {
cliconfigLoadFunc = func(dir string) (*configfile.ConfigFile, error) {
return nil, errors.New("load error")
}
_, err := getHostFromContext()
assert.Error(t, err)
assert.Contains(t, err.Error(), "load error")
})
}
func TestValidateSocket_Failures(t *testing.T) {
ctx := context.Background()
// Test non-existent path
err := validateSocket(ctx, "unix:///tmp/nonexistent-12345.sock", false)
assert.Error(t, err)
// Test invalid schema
err = validateSocket(ctx, "invalid:///tmp/test.sock", false)
assert.Error(t, err)
}
func TestDetectPlatformCandidates_Unix(t *testing.T) {
// Save and restore
oldStat := statFunc
oldValidate := validateSocketFunc
defer func() {
statFunc = oldStat
validateSocketFunc = oldValidate
}()
log := logrus.New().WithField("test", "test")
t.Run("No sockets exist", func(t *testing.T) {
statFunc = func(name string) (os.FileInfo, error) {
return nil, os.ErrNotExist
}
_, _, err := detectPlatformCandidates(log)
assert.Error(t, err)
assert.Contains(t, err.Error(), "ensure Docker or Podman is running")
})
t.Run("Docker socket exists and is valid", func(t *testing.T) {
statFunc = func(name string) (os.FileInfo, error) {
if name == "/var/run/docker.sock" {
return nil, nil
}
return nil, os.ErrNotExist
}
validateSocketFunc = func(ctx context.Context, host string, silent bool) error {
if host == "unix:///var/run/docker.sock" {
return nil
}
return errors.New("invalid")
}
path, runtime, err := detectPlatformCandidates(log)
assert.NoError(t, err)
assert.Equal(t, "unix:///var/run/docker.sock", path)
assert.Equal(t, RuntimeDocker, runtime)
})
t.Run("Docker socket exists but permission denied", func(t *testing.T) {
statFunc = func(name string) (os.FileInfo, error) {
if name == "/var/run/docker.sock" {
return nil, nil
}
return nil, os.ErrNotExist
}
validateSocketFunc = func(ctx context.Context, host string, silent bool) error {
return errors.New("permission denied")
}
_, _, err := detectPlatformCandidates(log)
assert.Error(t, err)
assert.Contains(t, err.Error(), "permission denied")
})
t.Run("Docker socket exists but other error", func(t *testing.T) {
statFunc = func(name string) (os.FileInfo, error) {
if name == "/var/run/docker.sock" {
return nil, nil
}
return nil, os.ErrNotExist
}
validateSocketFunc = func(ctx context.Context, host string, silent bool) error {
return errors.New("other error")
}
_, _, err := detectPlatformCandidates(log)
assert.Error(t, err)
assert.Contains(t, err.Error(), "other error")
})
t.Run("Podman socket exists and is valid", func(t *testing.T) {
// Mock getuid to return 1000
oldGetuid := getuidFunc
getuidFunc = func() int { return 1000 }
defer func() { getuidFunc = oldGetuid }()
statFunc = func(name string) (os.FileInfo, error) {
if name == "/run/user/1000/podman/podman.sock" {
return nil, nil
}
return nil, os.ErrNotExist
}
validateSocketFunc = func(ctx context.Context, host string, silent bool) error {
if host == "unix:///run/user/1000/podman/podman.sock" {
return nil
}
return errors.New("invalid")
}
path, runtime, err := detectPlatformCandidates(log)
assert.NoError(t, err)
assert.Equal(t, "unix:///run/user/1000/podman/podman.sock", path)
assert.Equal(t, RuntimePodman, runtime)
})
}