Phase 5-7: README, Makefile, unit tests

- README.md: full usage docs
- Makefile: build/install/uninstall/test/lint/clean targets
- config/config_test.go: 7 tests (load, save, find, add, roundtrip, invalid yaml)
- runtime/runtime_test.go: 6 tests (get, available, ListRunning graceful degradation)
- systemd/generator_test.go: 7 tests (ServiceName, Generate system/user/custom/docker/unknown, buildExecCommands)
- fix: docker/podman ListRunning returns nil (not error) when daemon unavailable
- fix: invalid YAML test uses tab-indent which is genuinely unparseable
This commit is contained in:
2026-04-03 14:50:36 +02:00
parent 203e8e3f04
commit 5078558d01
7 changed files with 576 additions and 2 deletions

View File

@@ -26,7 +26,8 @@ func (d *Docker) ListRunning() ([]Container, error) {
out, err := exec.Command(bin, "ps", "--format", "json").Output()
if err != nil {
return nil, fmt.Errorf("docker ps: %w", err)
// Docker installed but daemon not running — treat as no containers
return nil, nil
}
// Docker outputs one JSON object per line (not a JSON array)

View File

@@ -26,7 +26,8 @@ func (p *Podman) ListRunning() ([]Container, error) {
out, err := exec.Command(bin, "ps", "--format", "json").Output()
if err != nil {
return nil, fmt.Errorf("podman ps: %w", err)
// Podman installed but not usable — treat as no containers
return nil, nil
}
var raw []podmanContainer

71
runtime/runtime_test.go Normal file
View File

@@ -0,0 +1,71 @@
package runtime
import (
"testing"
)
func TestGet_Podman(t *testing.T) {
rt := Get("podman")
if rt == nil {
t.Fatal("Get(podman) returned nil")
}
if rt.Name() != "podman" {
t.Errorf("Name() = %q, want %q", rt.Name(), "podman")
}
}
func TestGet_Docker(t *testing.T) {
rt := Get("docker")
if rt == nil {
t.Fatal("Get(docker) returned nil")
}
if rt.Name() != "docker" {
t.Errorf("Name() = %q, want %q", rt.Name(), "docker")
}
}
func TestGet_Unknown(t *testing.T) {
rt := Get("containerd")
if rt != nil {
t.Errorf("Get(containerd) should return nil for unknown runtime, got %+v", rt)
}
}
func TestAvailable_ReturnsRuntimes(t *testing.T) {
runtimes := Available()
if len(runtimes) == 0 {
t.Error("Available() returned empty list")
}
names := map[string]bool{}
for _, rt := range runtimes {
names[rt.Name()] = true
}
if !names["podman"] {
t.Error("Available() should include podman")
}
if !names["docker"] {
t.Error("Available() should include docker")
}
}
// ListRunning and Exists are integration-level — they call external binaries.
// We test that they don't panic when the binary is absent (returns nil/false, no error).
func TestPodman_ListRunning_NoBinary(t *testing.T) {
// This test is meaningful if podman isn't installed; if it is, we just check no error occurs
p := &Podman{}
containers, err := p.ListRunning()
if err != nil {
t.Errorf("ListRunning() should not error when podman is absent: %v", err)
}
// containers may be nil or populated — both are fine
_ = containers
}
func TestDocker_ListRunning_NoBinary(t *testing.T) {
d := &Docker{}
containers, err := d.ListRunning()
if err != nil {
t.Errorf("ListRunning() should not error when docker is absent: %v", err)
}
_ = containers
}