coverage: easy-wins batch across cli, system, paths, vmdns, toolingplan

Pure-Go tests for formatters, layout resolution, and validators — no
fixtures, no external processes. Targets previously-zero functions the
triage scan flagged as low-hanging fruit.

  cli          55% -> 65%
  paths        64% -> 91%
  system       65% -> 75%
  vmdns        72% -> 86%
  toolingplan  73% -> 78%

Total 52.6% -> 54.0%.
This commit is contained in:
Thales Maciel 2026-04-18 17:57:05 -03:00
parent a3cc296523
commit f8979de58a
No known key found for this signature in database
GPG key ID: 33112E6833C34679
5 changed files with 740 additions and 0 deletions

View file

@ -0,0 +1,355 @@
package cli
import (
"bytes"
"errors"
"fmt"
"reflect"
"strings"
"testing"
"banger/internal/api"
"banger/internal/model"
"github.com/spf13/cobra"
)
func TestHumanSize(t *testing.T) {
cases := []struct {
bytes int64
want string
}{
{-1, "-"},
{0, "-"},
{1, "1B"},
{1023, "1023B"},
{1024, "1.0KiB"},
{2048, "2.0KiB"},
{1024 * 1024, "1.0MiB"},
{5 * 1024 * 1024, "5.0MiB"},
{1024 * 1024 * 1024, "1.0GiB"},
{3 * 1024 * 1024 * 1024, "3.0GiB"},
}
for _, tc := range cases {
if got := humanSize(tc.bytes); got != tc.want {
t.Errorf("humanSize(%d) = %q, want %q", tc.bytes, got, tc.want)
}
}
}
func TestDashIfEmpty(t *testing.T) {
cases := map[string]string{
"": "-",
" ": "-",
"\t\n": "-",
"value": "value",
" hello ": " hello ",
}
for in, want := range cases {
if got := dashIfEmpty(in); got != want {
t.Errorf("dashIfEmpty(%q) = %q, want %q", in, got, want)
}
}
}
func TestParseKeyValuePairs(t *testing.T) {
t.Run("nil when empty", func(t *testing.T) {
got, err := parseKeyValuePairs(nil)
if err != nil {
t.Fatalf("unexpected error: %v", err)
}
if got != nil {
t.Fatalf("got %v, want nil", got)
}
})
t.Run("parses entries", func(t *testing.T) {
got, err := parseKeyValuePairs([]string{"a=1", " b = two", "c=x=y"})
if err != nil {
t.Fatalf("unexpected error: %v", err)
}
want := map[string]string{"a": "1", "b": " two", "c": "x=y"}
if !reflect.DeepEqual(got, want) {
t.Fatalf("got %v, want %v", got, want)
}
})
t.Run("rejects malformed entries", func(t *testing.T) {
for _, bad := range []string{"noequals", "=noKey", " =v"} {
if _, err := parseKeyValuePairs([]string{bad}); err == nil {
t.Errorf("expected error for %q", bad)
}
}
})
}
func TestExitCodeErrorError(t *testing.T) {
e := ExitCodeError{Code: 42}
got := e.Error()
if !strings.Contains(got, "42") {
t.Fatalf("error %q missing code", got)
}
var target ExitCodeError
if !errors.As(error(e), &target) {
t.Fatal("errors.As failed to match ExitCodeError")
}
if target.Code != 42 {
t.Fatalf("target.Code = %d, want 42", target.Code)
}
}
func TestShortID(t *testing.T) {
cases := map[string]string{
"": "",
"abc": "abc",
"0123456789ab": "0123456789ab",
"0123456789abcd": "0123456789ab",
"0123456789abcdefghij": "0123456789ab",
}
for in, want := range cases {
if got := shortID(in); got != want {
t.Errorf("shortID(%q) = %q, want %q", in, got, want)
}
}
}
func TestImageNameIndex(t *testing.T) {
images := []model.Image{
{ID: "id-a", Name: "alpha"},
{ID: "id-b", Name: "beta"},
}
idx := imageNameIndex(images)
if len(idx) != 2 {
t.Fatalf("len = %d, want 2", len(idx))
}
if idx["id-a"] != "alpha" || idx["id-b"] != "beta" {
t.Fatalf("unexpected index %v", idx)
}
empty := imageNameIndex(nil)
if empty == nil || len(empty) != 0 {
t.Fatalf("expected empty non-nil map, got %v", empty)
}
}
func TestHelpNoArgs(t *testing.T) {
called := false
cmd := &cobra.Command{
Use: "x",
RunE: func(cmd *cobra.Command, args []string) error {
called = true
return nil
},
}
cmd.SetOut(&bytes.Buffer{})
cmd.SetErr(&bytes.Buffer{})
if err := helpNoArgs(cmd, nil); err != nil {
t.Fatalf("helpNoArgs(nil): %v", err)
}
if called {
t.Fatal("helpNoArgs should not invoke Run")
}
if err := helpNoArgs(cmd, []string{"bogus"}); err == nil {
t.Fatal("expected error for unexpected args")
}
}
func TestArgsValidators(t *testing.T) {
cmd := &cobra.Command{Use: "x"}
exact := exactArgsUsage(2, "need exactly two")
if err := exact(cmd, []string{"a", "b"}); err != nil {
t.Fatalf("exact(2 args): %v", err)
}
if err := exact(cmd, []string{"a"}); err == nil {
t.Fatal("expected error for 1 arg with exactArgsUsage(2)")
}
minArgs := minArgsUsage(1, "need at least one")
if err := minArgs(cmd, []string{"a"}); err != nil {
t.Fatalf("min(1 arg): %v", err)
}
if err := minArgs(cmd, nil); err == nil {
t.Fatal("expected error for 0 args with minArgsUsage(1)")
}
maxArgs := maxArgsUsage(1, "at most one")
if err := maxArgs(cmd, []string{"a"}); err != nil {
t.Fatalf("max(1 arg): %v", err)
}
if err := maxArgs(cmd, []string{"a", "b"}); err == nil {
t.Fatal("expected error for 2 args with maxArgsUsage(1)")
}
noArgs := noArgsUsage("none allowed")
if err := noArgs(cmd, nil); err != nil {
t.Fatalf("no args: %v", err)
}
if err := noArgs(cmd, []string{"a"}); err == nil {
t.Fatal("expected error for args with noArgsUsage")
}
}
func TestPrintKernelListTable(t *testing.T) {
var buf bytes.Buffer
entries := []api.KernelEntry{
{Name: "generic-6.12", Distro: "debian", Arch: "x86_64", KernelVersion: "6.12", ImportedAt: "2026-01-01"},
{Name: "bare"},
}
if err := printKernelListTable(&buf, entries); err != nil {
t.Fatalf("printKernelListTable: %v", err)
}
got := buf.String()
for _, want := range []string{"NAME", "DISTRO", "generic-6.12", "bare"} {
if !strings.Contains(got, want) {
t.Errorf("output missing %q:\n%s", want, got)
}
}
// Empty fields render as "-".
if !strings.Contains(got, "-") {
t.Errorf("expected dash for empty fields, got:\n%s", got)
}
}
func TestPrintKernelCatalogTable(t *testing.T) {
var buf bytes.Buffer
entries := []api.KernelCatalogEntry{
{Name: "generic-6.12", Arch: "x86_64", KernelVersion: "6.12", SizeBytes: 2 * 1024 * 1024, Pulled: true},
{Name: "new-kernel", SizeBytes: 0, Pulled: false},
}
if err := printKernelCatalogTable(&buf, entries); err != nil {
t.Fatalf("printKernelCatalogTable: %v", err)
}
got := buf.String()
for _, want := range []string{"generic-6.12", "pulled", "available", "new-kernel"} {
if !strings.Contains(got, want) {
t.Errorf("output missing %q:\n%s", want, got)
}
}
if !strings.Contains(got, "2.0MiB") {
t.Errorf("expected humanSize(2 MiB), got:\n%s", got)
}
}
func TestPrintGuestSessionTable(t *testing.T) {
var buf bytes.Buffer
sessions := []model.GuestSession{
{ID: "abcdef0123456789", Name: "planner", Status: "running", Command: "pi", CWD: "/root/repo", Attachable: true},
{ID: "short", Name: "once", Status: "exited", Command: "true", CWD: "/tmp", Attachable: false},
}
if err := printGuestSessionTable(&buf, sessions); err != nil {
t.Fatalf("printGuestSessionTable: %v", err)
}
got := buf.String()
for _, want := range []string{"ID", "NAME", "planner", "once", "yes", "no", "pi"} {
if !strings.Contains(got, want) {
t.Errorf("output missing %q:\n%s", want, got)
}
}
}
func TestPrintGuestSessionSummary(t *testing.T) {
var buf bytes.Buffer
session := model.GuestSession{
ID: "id1", Name: "s", Status: "exited", Command: "true", CWD: "/root",
}
if err := printGuestSessionSummary(&buf, session); err != nil {
t.Fatalf("printGuestSessionSummary: %v", err)
}
got := buf.String()
fields := strings.Split(strings.TrimRight(got, "\n"), "\t")
if len(fields) != 5 {
t.Fatalf("expected 5 tab-separated fields, got %d: %q", len(fields), got)
}
}
func TestPrintJSON(t *testing.T) {
var buf bytes.Buffer
if err := printJSON(&buf, map[string]int{"a": 1, "b": 2}); err != nil {
t.Fatalf("printJSON: %v", err)
}
got := buf.String()
if !strings.Contains(got, `"a": 1`) || !strings.Contains(got, `"b": 2`) {
t.Errorf("unexpected JSON output:\n%s", got)
}
if !strings.HasSuffix(got, "\n") {
t.Error("printJSON should terminate with newline")
}
}
func TestPrintJSONUnmarshalableValue(t *testing.T) {
var buf bytes.Buffer
// Channels are not JSON-marshalable.
err := printJSON(&buf, make(chan int))
if err == nil {
t.Fatal("expected error for unmarshalable value")
}
}
func TestPrintVMSummary(t *testing.T) {
var buf bytes.Buffer
vm := model.VMRecord{
ID: "0123456789abcdef",
Name: "demo",
State: model.VMStateRunning,
}
vm.Runtime.GuestIP = "172.16.0.5"
vm.Runtime.DNSName = "demo.vm"
vm.Spec.WorkDiskSizeBytes = 0
if err := printVMSummary(&buf, vm); err != nil {
t.Fatalf("printVMSummary: %v", err)
}
got := buf.String()
for _, want := range []string{"0123456789ab", "demo", "172.16.0.5", "demo.vm"} {
if !strings.Contains(got, want) {
t.Errorf("summary missing %q:\n%s", want, got)
}
}
}
func TestPrintImageSummary(t *testing.T) {
var buf bytes.Buffer
img := model.Image{ID: "img-id", Name: "debian-bookworm", Managed: true, RootfsPath: "/var/rootfs.ext4"}
if err := printImageSummary(&buf, img); err != nil {
t.Fatalf("printImageSummary: %v", err)
}
got := buf.String()
for _, want := range []string{"debian-bookworm", "true", "/var/rootfs.ext4"} {
if !strings.Contains(got, want) {
t.Errorf("summary missing %q:\n%s", want, got)
}
}
}
func TestVMImageLabel(t *testing.T) {
names := map[string]string{"img-1": "debian"}
if got := vmImageLabel("img-1", names); got != "debian" {
t.Errorf("got %q, want debian", got)
}
if got := vmImageLabel("img-2", names); got != "img-2" {
t.Errorf("fallback: got %q, want img-2", got)
}
}
// failWriter lets us exercise io-error branches of the printers.
type failWriter struct{}
func (failWriter) Write([]byte) (int, error) { return 0, fmt.Errorf("boom") }
func TestPrintersPropagateWriteErrors(t *testing.T) {
sessions := []model.GuestSession{{ID: "id", Name: "n"}}
if err := printGuestSessionTable(failWriter{}, sessions); err == nil {
t.Error("expected write error from printGuestSessionTable")
}
kernels := []api.KernelEntry{{Name: "k"}}
if err := printKernelListTable(failWriter{}, kernels); err == nil {
t.Error("expected write error from printKernelListTable")
}
catalog := []api.KernelCatalogEntry{{Name: "k"}}
if err := printKernelCatalogTable(failWriter{}, catalog); err == nil {
t.Error("expected write error from printKernelCatalogTable")
}
}

View file

@ -0,0 +1,136 @@
package paths
import (
"os"
"path/filepath"
"strings"
"testing"
)
func TestResolveUsesXDGOverrides(t *testing.T) {
dir := t.TempDir()
t.Setenv("XDG_CONFIG_HOME", filepath.Join(dir, "config"))
t.Setenv("XDG_STATE_HOME", filepath.Join(dir, "state"))
t.Setenv("XDG_CACHE_HOME", filepath.Join(dir, "cache"))
t.Setenv("XDG_RUNTIME_DIR", filepath.Join(dir, "run"))
layout, err := Resolve()
if err != nil {
t.Fatalf("Resolve: %v", err)
}
if layout.ConfigDir != filepath.Join(dir, "config", "banger") {
t.Errorf("ConfigDir = %q", layout.ConfigDir)
}
if layout.StateDir != filepath.Join(dir, "state", "banger") {
t.Errorf("StateDir = %q", layout.StateDir)
}
if layout.CacheDir != filepath.Join(dir, "cache", "banger") {
t.Errorf("CacheDir = %q", layout.CacheDir)
}
if layout.RuntimeDir != filepath.Join(dir, "run", "banger") {
t.Errorf("RuntimeDir = %q", layout.RuntimeDir)
}
if !strings.HasSuffix(layout.SocketPath, "bangerd.sock") {
t.Errorf("SocketPath = %q", layout.SocketPath)
}
if !strings.HasSuffix(layout.DBPath, "state.db") {
t.Errorf("DBPath = %q", layout.DBPath)
}
}
func TestResolveFallsBackWhenRuntimeUnset(t *testing.T) {
t.Setenv("XDG_RUNTIME_DIR", "")
layout, err := Resolve()
if err != nil {
t.Fatalf("Resolve: %v", err)
}
if !strings.Contains(layout.RuntimeDir, "banger-runtime-") {
t.Errorf("expected fallback runtime dir, got %q", layout.RuntimeDir)
}
}
func TestEnsureCreatesAllDirs(t *testing.T) {
base := t.TempDir()
layout := Layout{
ConfigDir: filepath.Join(base, "config"),
StateDir: filepath.Join(base, "state"),
CacheDir: filepath.Join(base, "cache"),
RuntimeDir: filepath.Join(base, "runtime"),
VMsDir: filepath.Join(base, "state/vms"),
ImagesDir: filepath.Join(base, "state/images"),
KernelsDir: filepath.Join(base, "state/kernels"),
OCICacheDir: filepath.Join(base, "cache/oci"),
}
if err := Ensure(layout); err != nil {
t.Fatalf("Ensure: %v", err)
}
for _, dir := range []string{
layout.ConfigDir,
layout.StateDir,
layout.CacheDir,
layout.RuntimeDir,
layout.VMsDir,
layout.ImagesDir,
layout.KernelsDir,
layout.OCICacheDir,
} {
info, err := os.Stat(dir)
if err != nil {
t.Errorf("stat %q: %v", dir, err)
continue
}
if !info.IsDir() {
t.Errorf("%q is not a directory", dir)
}
}
// Idempotent.
if err := Ensure(layout); err != nil {
t.Fatalf("Ensure (second run): %v", err)
}
}
func TestBangerdPathEnvOverride(t *testing.T) {
t.Setenv("BANGER_DAEMON_BIN", "/tmp/custom-bangerd")
got, err := BangerdPath()
if err != nil {
t.Fatalf("BangerdPath: %v", err)
}
if got != "/tmp/custom-bangerd" {
t.Errorf("got %q, want /tmp/custom-bangerd", got)
}
}
func TestBangerdPathFindsSiblingBinary(t *testing.T) {
t.Setenv("BANGER_DAEMON_BIN", "")
root := t.TempDir()
sibling := filepath.Join(root, "bangerd")
if err := os.WriteFile(sibling, []byte("#!/bin/sh\n"), 0o755); err != nil {
t.Fatalf("WriteFile: %v", err)
}
original := executablePath
executablePath = func() (string, error) { return filepath.Join(root, "banger"), nil }
t.Cleanup(func() { executablePath = original })
got, err := BangerdPath()
if err != nil {
t.Fatalf("BangerdPath: %v", err)
}
if got != sibling {
t.Errorf("got %q, want %q", got, sibling)
}
}
func TestBangerdPathNotFound(t *testing.T) {
t.Setenv("BANGER_DAEMON_BIN", "")
root := t.TempDir()
original := executablePath
executablePath = func() (string, error) { return filepath.Join(root, "banger"), nil }
t.Cleanup(func() { executablePath = original })
if _, err := BangerdPath(); err == nil {
t.Fatal("expected error when no sibling bangerd exists")
}
}

View file

@ -0,0 +1,133 @@
package system
import (
"context"
"encoding/json"
"os"
"path/filepath"
"runtime"
"testing"
)
func TestWriteJSONRoundtrip(t *testing.T) {
path := filepath.Join(t.TempDir(), "out.json")
value := map[string]any{"name": "banger", "n": 42.0}
if err := WriteJSON(path, value); err != nil {
t.Fatalf("WriteJSON: %v", err)
}
data, err := os.ReadFile(path)
if err != nil {
t.Fatalf("ReadFile: %v", err)
}
var got map[string]any
if err := json.Unmarshal(data, &got); err != nil {
t.Fatalf("Unmarshal: %v", err)
}
if got["name"] != "banger" || got["n"].(float64) != 42.0 {
t.Fatalf("decoded = %v", got)
}
}
func TestWriteJSONErrorsForUnmarshalable(t *testing.T) {
path := filepath.Join(t.TempDir(), "out.json")
if err := WriteJSON(path, make(chan int)); err == nil {
t.Fatal("expected marshal error for channel value")
}
if _, err := os.Stat(path); !os.IsNotExist(err) {
t.Fatalf("expected no file when marshal fails, got %v", err)
}
}
func TestTailCommand(t *testing.T) {
cmd := TailCommand("/tmp/log.txt", false)
if cmd == nil || cmd.Path == "" {
t.Fatal("TailCommand(false) returned nil/empty")
}
// follow=false → cat, follow=true → tail -f.
if !hasArg(cmd.Args, "/tmp/log.txt") {
t.Fatalf("cat args missing path: %v", cmd.Args)
}
followCmd := TailCommand("/tmp/log.txt", true)
if !hasArg(followCmd.Args, "-f") {
t.Fatalf("follow cmd missing -f: %v", followCmd.Args)
}
if !hasArg(followCmd.Args, "/tmp/log.txt") {
t.Fatalf("follow cmd missing path: %v", followCmd.Args)
}
}
func hasArg(args []string, want string) bool {
for _, a := range args {
if a == want {
return true
}
}
return false
}
func TestReportAddWarnAndHasFailures(t *testing.T) {
var r Report
r.AddPass("a")
r.AddWarn("b", "detail-1", "detail-2")
if r.HasFailures() {
t.Fatal("HasFailures should be false with only pass+warn")
}
if len(r.Checks) != 2 {
t.Fatalf("len(Checks) = %d, want 2", len(r.Checks))
}
if r.Checks[1].Status != CheckStatusWarn {
t.Fatalf("check[1].Status = %v, want warn", r.Checks[1].Status)
}
if len(r.Checks[1].Details) != 2 {
t.Fatalf("warn details lost: %v", r.Checks[1].Details)
}
r.AddFail("c")
if !r.HasFailures() {
t.Fatal("HasFailures should be true after AddFail")
}
}
func TestRequireCommandsMissing(t *testing.T) {
err := RequireCommands(context.Background(), "this-command-cannot-possibly-exist-xyz-123")
if err == nil {
t.Fatal("expected error for missing command")
}
}
func TestRequireCommandsPresent(t *testing.T) {
// `go` is guaranteed on PATH during test runs.
if err := RequireCommands(context.Background(), "go"); err != nil {
t.Fatalf("RequireCommands(go): %v", err)
}
}
func TestReadHostResources(t *testing.T) {
if runtime.GOOS != "linux" {
t.Skip("ReadHostResources reads /proc/meminfo; Linux-only")
}
res, err := ReadHostResources()
if err != nil {
t.Fatalf("ReadHostResources: %v", err)
}
if res.CPUCount <= 0 {
t.Errorf("CPUCount = %d, want > 0", res.CPUCount)
}
if res.TotalMemoryBytes <= 0 {
t.Errorf("TotalMemoryBytes = %d, want > 0", res.TotalMemoryBytes)
}
}
func TestShortIDEdgeCases(t *testing.T) {
if got := ShortID(""); got != "" {
t.Errorf("ShortID('') = %q, want ''", got)
}
if got := ShortID("short"); got != "short" {
t.Errorf("ShortID('short') = %q, want 'short'", got)
}
long := "0123456789abcdef"
if got := ShortID(long); got != "01234567" {
t.Errorf("ShortID(long) = %q, want 01234567", got)
}
}

View file

@ -0,0 +1,23 @@
package toolingplan
import "testing"
func TestFirstMeaningfulLine(t *testing.T) {
cases := []struct {
in, want string
}{
{"", ""},
{"\n\n\n", ""},
{" \n \n", ""},
{"# just a comment\n# another\n", ""},
{"1.75.0\n", "1.75.0"},
{" 1.75.0 ", "1.75.0"},
{"# pinned toolchain\n1.75.0\nmore junk\n", "1.75.0"},
{"\n\n stable-x86_64-unknown-linux-gnu \n", "stable-x86_64-unknown-linux-gnu"},
}
for _, tc := range cases {
if got := firstMeaningfulLine(tc.in); got != tc.want {
t.Errorf("firstMeaningfulLine(%q) = %q, want %q", tc.in, got, tc.want)
}
}
}

View file

@ -0,0 +1,93 @@
package vmdns
import (
"testing"
)
func TestServerRemoveDropsRecord(t *testing.T) {
server := startTestServer(t)
if err := server.Set("devbox.vm", "172.16.0.8"); err != nil {
t.Fatalf("Set: %v", err)
}
if _, ok := server.Lookup("devbox.vm"); !ok {
t.Fatal("record missing before remove")
}
if err := server.Remove("devbox.vm"); err != nil {
t.Fatalf("Remove: %v", err)
}
if _, ok := server.Lookup("devbox.vm"); ok {
t.Fatal("record still present after Remove")
}
}
func TestServerRemoveInvalidNameIsNoop(t *testing.T) {
server := startTestServer(t)
// Non-.vm names silently normalize-fail, returning nil.
if err := server.Remove("example.com"); err != nil {
t.Fatalf("Remove: %v", err)
}
}
func TestServerRemoveNilReceiver(t *testing.T) {
var s *Server
if err := s.Remove("anything.vm"); err != nil {
t.Fatalf("nil Remove: %v", err)
}
}
func TestServerSetRejectsIPv6(t *testing.T) {
server := startTestServer(t)
if err := server.Set("six.vm", "::1"); err == nil {
t.Fatal("expected error for IPv6 address")
}
}
func TestServerSetRejectsBadIP(t *testing.T) {
server := startTestServer(t)
if err := server.Set("bad.vm", "not-an-ip"); err == nil {
t.Fatal("expected parse error for bogus IP")
}
}
func TestServerSetRejectsNonVMName(t *testing.T) {
server := startTestServer(t)
if err := server.Set("example.com", "172.16.0.1"); err == nil {
t.Fatal("expected error for non-.vm name")
}
}
func TestServerReplaceRejectsBadIP(t *testing.T) {
server := startTestServer(t)
err := server.Replace(map[string]string{"bad.vm": "nope"})
if err == nil {
t.Fatal("expected parse error")
}
}
func TestServerReplaceRejectsIPv6(t *testing.T) {
server := startTestServer(t)
err := server.Replace(map[string]string{"six.vm": "::1"})
if err == nil {
t.Fatal("expected IPv6 rejection")
}
}
func TestServerNilLookupAndAddr(t *testing.T) {
var s *Server
if _, ok := s.Lookup("x.vm"); ok {
t.Fatal("nil Lookup should return false")
}
if got := s.Addr(); got != "" {
t.Fatalf("nil Addr = %q, want empty", got)
}
if err := s.Close(); err != nil {
t.Fatalf("nil Close: %v", err)
}
if err := s.Set("x.vm", "172.16.0.1"); err != nil {
t.Fatalf("nil Set: %v", err)
}
if err := s.Replace(map[string]string{"x.vm": "172.16.0.1"}); err != nil {
t.Fatalf("nil Replace: %v", err)
}
}