test: exhaustive integration tests with codecov

- Root package unit tests: Targets, TargetNames, ApplyOptions, error sentinels
- Every provider integration test now covers:
  - All Manager interface methods (positive + negative cases)
  - GetCapabilities verification (assert expected interfaces)
  - VersionQuerier: LatestVersion, ListUpgrades, UpgradeAvailable, VersionCmp
  - Holder: Hold, ListHeld, Unhold (apt, dnf)
  - Cleaner: Autoremove, Clean
  - FileOwner: FileList, Owner (+ not-found cases)
  - RepoManager: ListRepos (apt, dnf, flatpak)
  - KeyManager: ListKeys (apt, dnf)
  - Grouper: GroupList, GroupInfo (pacman, dnf)
  - NameNormalizer: NormalizeName, ParseArch table tests (apt, dpkg, dnf, rpm)
- Containertest matrix: 5 distros (debian, alpine, arch, fedora39, fedora-latest)
- CI: coverage profiles uploaded per-job, merged in codecov job
- Added .gitignore for coverage files
This commit is contained in:
2026-02-26 02:50:48 +00:00
parent d2efcebb9e
commit b12f956e45
13 changed files with 1739 additions and 215 deletions

View File

@@ -13,95 +13,249 @@ import (
)
func TestIntegration_Apk(t *testing.T) {
var mgr snack.Manager = apk.New()
mgr := apk.New()
if !mgr.Available() {
t.Skip("apk not available")
}
ctx := context.Background()
assert.Equal(t, "apk", mgr.Name())
caps := snack.GetCapabilities(mgr)
assert.True(t, caps.VersionQuery, "apk should support VersionQuery")
assert.True(t, caps.Clean, "apk should support Clean")
assert.True(t, caps.FileOwnership, "apk should support FileOwnership")
assert.False(t, caps.Hold, "apk should not support Hold")
assert.False(t, caps.RepoManagement, "apk should not support RepoManagement")
assert.False(t, caps.KeyManagement, "apk should not support KeyManagement")
assert.False(t, caps.Groups, "apk should not support Groups")
assert.False(t, caps.NameNormalize, "apk should not support NameNormalize")
t.Run("Update", func(t *testing.T) {
err := mgr.Update(ctx)
require.NoError(t, err)
require.NoError(t, mgr.Update(ctx))
})
t.Run("Search", func(t *testing.T) {
pkgs, err := mgr.Search(ctx, "curl")
require.NoError(t, err)
require.NotEmpty(t, pkgs)
require.NotEmpty(t, pkgs, "search for curl should return results")
found := false
for _, p := range pkgs {
if p.Name == "curl" {
found = true
break
}
}
assert.True(t, found, "curl should appear in search results")
})
t.Run("Info", func(t *testing.T) {
// apk info only works on installed packages, use "tree" after install
// or test with a pre-installed package like "busybox"
t.Run("Search_NoResults", func(t *testing.T) {
pkgs, err := mgr.Search(ctx, "xyznonexistentpackage999")
require.NoError(t, err)
assert.Empty(t, pkgs)
})
t.Run("Info_PreInstalled", func(t *testing.T) {
pkg, err := mgr.Info(ctx, "busybox")
if err != nil {
t.Skip("busybox not installed, skipping Info test")
t.Skip("busybox not installed")
}
require.NotNil(t, pkg)
assert.Equal(t, "busybox", pkg.Name)
assert.NotEmpty(t, pkg.Version)
})
t.Run("Install", func(t *testing.T) {
err := mgr.Install(ctx, snack.Targets("tree"), snack.WithSudo(), snack.WithAssumeYes())
t.Run("Info_NotFound", func(t *testing.T) {
_, err := mgr.Info(ctx, "xyznonexistentpackage999")
assert.Error(t, err)
})
t.Run("Install_Single", func(t *testing.T) {
_ = mgr.Remove(ctx, snack.Targets("tree"))
err := mgr.Install(ctx, snack.Targets("tree"))
require.NoError(t, err)
})
t.Run("IsInstalled", func(t *testing.T) {
t.Run("IsInstalled_True", func(t *testing.T) {
installed, err := mgr.IsInstalled(ctx, "tree")
require.NoError(t, err)
assert.True(t, installed)
})
t.Run("Version", func(t *testing.T) {
t.Run("IsInstalled_False", func(t *testing.T) {
installed, err := mgr.IsInstalled(ctx, "xyznonexistentpackage999")
require.NoError(t, err)
assert.False(t, installed)
})
t.Run("Version_Installed", func(t *testing.T) {
ver, err := mgr.Version(ctx, "tree")
require.NoError(t, err)
assert.NotEmpty(t, ver)
t.Logf("tree version: %s", ver)
})
t.Run("List", func(t *testing.T) {
t.Run("Version_NotInstalled", func(t *testing.T) {
_, err := mgr.Version(ctx, "xyznonexistentpackage999")
assert.Error(t, err)
})
t.Run("List_ContainsInstalled", func(t *testing.T) {
pkgs, err := mgr.List(ctx)
require.NoError(t, err)
require.NotEmpty(t, pkgs)
found := false
for _, p := range pkgs {
if p.Name == "tree" {
found = true
assert.NotEmpty(t, p.Version)
break
}
}
assert.True(t, found, "tree should be in installed list")
})
t.Run("Remove", func(t *testing.T) {
err := mgr.Remove(ctx, snack.Targets("tree"), snack.WithSudo(), snack.WithAssumeYes())
t.Run("Info_AfterInstall", func(t *testing.T) {
pkg, err := mgr.Info(ctx, "tree")
require.NoError(t, err)
require.NotNil(t, pkg)
assert.Equal(t, "tree", pkg.Name)
assert.NotEmpty(t, pkg.Version)
})
t.Run("IsInstalled_After_Remove", func(t *testing.T) {
t.Run("Install_Multiple", func(t *testing.T) {
err := mgr.Install(ctx, snack.Targets("tree", "less"))
require.NoError(t, err)
for _, pkg := range []string{"tree", "less"} {
installed, err := mgr.IsInstalled(ctx, pkg)
require.NoError(t, err)
assert.True(t, installed, "%s should be installed", pkg)
}
})
t.Run("Purge", func(t *testing.T) {
// apk purge is same as remove
err := mgr.Purge(ctx, snack.Targets("less"))
require.NoError(t, err)
installed, err := mgr.IsInstalled(ctx, "less")
require.NoError(t, err)
assert.False(t, installed)
})
t.Run("Remove", func(t *testing.T) {
err := mgr.Remove(ctx, snack.Targets("tree"))
require.NoError(t, err)
installed, err := mgr.IsInstalled(ctx, "tree")
require.NoError(t, err)
assert.False(t, installed)
})
t.Run("Capabilities", func(t *testing.T) {
if vq, ok := mgr.(snack.VersionQuerier); ok {
ver, err := vq.LatestVersion(ctx, "busybox")
// --- VersionQuerier ---
t.Run("VersionQuerier", func(t *testing.T) {
vq, ok := mgr.(snack.VersionQuerier)
require.True(t, ok)
t.Run("LatestVersion", func(t *testing.T) {
ver, err := vq.LatestVersion(ctx, "curl")
require.NoError(t, err)
assert.NotEmpty(t, ver)
t.Logf("curl latest: %s", ver)
})
upgrades, err := vq.ListUpgrades(ctx)
t.Run("LatestVersion_NotFound", func(t *testing.T) {
_, err := vq.LatestVersion(ctx, "xyznonexistentpackage999")
assert.Error(t, err)
})
t.Run("ListUpgrades", func(t *testing.T) {
pkgs, err := vq.ListUpgrades(ctx)
require.NoError(t, err)
_ = upgrades
}
t.Logf("upgradable packages: %d", len(pkgs))
})
if cl, ok := mgr.(snack.Cleaner); ok {
t.Run("UpgradeAvailable", func(t *testing.T) {
avail, err := vq.UpgradeAvailable(ctx, "busybox")
require.NoError(t, err)
_ = avail
})
t.Run("VersionCmp", func(t *testing.T) {
tests := []struct {
v1, v2 string
want int
}{
{"1.0.0-r0", "1.0.0-r1", -1},
{"2.0.0-r0", "1.0.0-r0", 1},
{"1.0.0-r0", "1.0.0-r0", 0},
}
for _, tt := range tests {
cmp, err := vq.VersionCmp(ctx, tt.v1, tt.v2)
require.NoError(t, err, "VersionCmp(%s, %s)", tt.v1, tt.v2)
assert.Equal(t, tt.want, cmp, "VersionCmp(%s, %s)", tt.v1, tt.v2)
}
})
})
// --- Cleaner ---
t.Run("Cleaner", func(t *testing.T) {
cl, ok := mgr.(snack.Cleaner)
require.True(t, ok)
t.Run("Autoremove", func(t *testing.T) {
// apk doesn't have autoremove but we exercise the path
err := cl.Autoremove(ctx)
// May or may not be supported
_ = err
})
t.Run("Clean", func(t *testing.T) {
err := cl.Clean(ctx)
require.NoError(t, err)
}
})
})
if fo, ok := mgr.(snack.FileOwner); ok {
owner, err := fo.Owner(ctx, "/usr/bin/apk")
if err == nil {
assert.NotEmpty(t, owner)
}
}
// --- FileOwner ---
t.Run("FileOwner", func(t *testing.T) {
fo, ok := mgr.(snack.FileOwner)
require.True(t, ok)
// Install tree for file tests
_ = mgr.Install(ctx, snack.Targets("tree"))
t.Run("FileList", func(t *testing.T) {
files, err := fo.FileList(ctx, "tree")
require.NoError(t, err)
require.NotEmpty(t, files)
t.Logf("tree files: %d", len(files))
})
t.Run("FileList_NotInstalled", func(t *testing.T) {
_, err := fo.FileList(ctx, "xyznonexistentpackage999")
assert.Error(t, err)
})
t.Run("Owner", func(t *testing.T) {
owner, err := fo.Owner(ctx, "/usr/bin/tree")
require.NoError(t, err)
assert.Contains(t, owner, "tree")
})
t.Run("Owner_NotFound", func(t *testing.T) {
_, err := fo.Owner(ctx, "/nonexistent/path/xyz")
assert.Error(t, err)
})
_ = mgr.Remove(ctx, snack.Targets("tree"))
})
// --- Upgrade ---
t.Run("Upgrade", func(t *testing.T) {
err := mgr.Upgrade(ctx)
require.NoError(t, err)
})
}