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

@@ -21,110 +21,384 @@ func TestIntegration_DNF_Detection(t *testing.T) {
}
func TestIntegration_DNF(t *testing.T) {
var mgr snack.Manager = dnf.New()
mgr := dnf.New()
if !mgr.Available() {
t.Skip("dnf not available")
}
ctx := context.Background()
assert.Equal(t, "dnf", mgr.Name())
caps := snack.GetCapabilities(mgr)
assert.True(t, caps.VersionQuery, "dnf should support VersionQuery")
assert.True(t, caps.Hold, "dnf should support Hold")
assert.True(t, caps.Clean, "dnf should support Clean")
assert.True(t, caps.FileOwnership, "dnf should support FileOwnership")
assert.True(t, caps.RepoManagement, "dnf should support RepoManagement")
assert.True(t, caps.KeyManagement, "dnf should support KeyManagement")
assert.True(t, caps.Groups, "dnf should support Groups")
assert.True(t, caps.NameNormalize, "dnf should 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)
found := false
for _, p := range pkgs {
if p.Name == "curl" {
found = true
assert.NotEmpty(t, p.Description)
break
}
}
assert.True(t, found, "curl should appear in search results")
})
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", func(t *testing.T) {
pkg, err := mgr.Info(ctx, "curl")
pkg, err := mgr.Info(ctx, "bash")
require.NoError(t, err)
require.NotNil(t, pkg)
assert.Equal(t, "curl", pkg.Name)
assert.Equal(t, "bash", 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)
assert.ErrorIs(t, err, snack.ErrNotFound)
})
t.Run("Install_Single", func(t *testing.T) {
_ = mgr.Remove(ctx, snack.Targets("tree"), snack.WithAssumeYes())
err := mgr.Install(ctx, snack.Targets("tree"), snack.WithAssumeYes())
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)
assert.ErrorIs(t, err, snack.ErrNotInstalled)
})
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("Install_Multiple", func(t *testing.T) {
err := mgr.Install(ctx, snack.Targets("tree", "less"), snack.WithAssumeYes())
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("Install_WithRefresh", func(t *testing.T) {
err := mgr.Install(ctx, snack.Targets("tree"), snack.WithAssumeYes(), snack.WithRefresh())
require.NoError(t, err)
})
t.Run("IsInstalled_After_Remove", func(t *testing.T) {
t.Run("Purge", func(t *testing.T) {
err := mgr.Purge(ctx, snack.Targets("less"), snack.WithAssumeYes())
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"), snack.WithAssumeYes())
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 {
// --- 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))
for _, p := range pkgs {
assert.NotEmpty(t, p.Name)
assert.NotEmpty(t, p.Version)
}
})
if cl, ok := mgr.(snack.Cleaner); ok {
t.Run("UpgradeAvailable", func(t *testing.T) {
avail, err := vq.UpgradeAvailable(ctx, "bash")
require.NoError(t, err)
_ = avail
})
t.Run("VersionCmp", func(t *testing.T) {
// rpmdev-vercmp may not be installed; skip if not available
cmp, err := vq.VersionCmp(ctx, "1.0-1", "1.0-2")
if err != nil {
t.Skip("rpmdev-vercmp not available")
}
assert.Equal(t, -1, cmp)
cmp, err = vq.VersionCmp(ctx, "2.0-1", "1.0-1")
require.NoError(t, err)
assert.Equal(t, 1, cmp)
cmp, err = vq.VersionCmp(ctx, "1.0-1", "1.0-1")
require.NoError(t, err)
assert.Equal(t, 0, cmp)
})
})
// --- Holder ---
t.Run("Holder", func(t *testing.T) {
h, ok := mgr.(snack.Holder)
require.True(t, ok)
// Install tree for hold tests; also ensure versionlock plugin
_ = mgr.Install(ctx, snack.Targets("tree"), snack.WithAssumeYes())
t.Run("Hold", func(t *testing.T) {
err := h.Hold(ctx, []string{"tree"})
if err != nil {
t.Skip("versionlock plugin not available:", err)
}
})
t.Run("ListHeld", func(t *testing.T) {
held, err := h.ListHeld(ctx)
if err != nil {
t.Skip("versionlock plugin not available:", err)
}
found := false
for _, p := range held {
if p.Name == "tree" {
found = true
break
}
}
assert.True(t, found, "tree should be in held list")
})
t.Run("Unhold", func(t *testing.T) {
err := h.Unhold(ctx, []string{"tree"})
if err != nil {
t.Skip("versionlock plugin not available:", err)
}
})
_ = mgr.Remove(ctx, snack.Targets("tree"), snack.WithAssumeYes())
})
// --- Cleaner ---
t.Run("Cleaner", func(t *testing.T) {
cl, ok := mgr.(snack.Cleaner)
require.True(t, ok)
t.Run("Autoremove", func(t *testing.T) {
err := cl.Autoremove(ctx)
require.NoError(t, 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/dnf")
if err == nil {
assert.NotEmpty(t, owner)
}
}
// --- FileOwner ---
t.Run("FileOwner", func(t *testing.T) {
fo, ok := mgr.(snack.FileOwner)
require.True(t, ok)
if g, ok := mgr.(snack.Grouper); ok {
groups, err := g.GroupList(ctx)
_ = mgr.Install(ctx, snack.Targets("tree"), snack.WithAssumeYes())
t.Run("FileList", func(t *testing.T) {
files, err := fo.FileList(ctx, "tree")
require.NoError(t, err)
_ = groups
}
require.NotEmpty(t, files)
if rm, ok := mgr.(snack.RepoManager); ok {
found := false
for _, f := range files {
if f == "/usr/bin/tree" {
found = true
break
}
}
assert.True(t, found, "/usr/bin/tree should be in file list")
})
t.Run("FileList_NotInstalled", func(t *testing.T) {
_, err := fo.FileList(ctx, "xyznonexistentpackage999")
assert.Error(t, err)
assert.ErrorIs(t, err, snack.ErrNotInstalled)
})
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"), snack.WithAssumeYes())
})
// --- RepoManager ---
t.Run("RepoManager", func(t *testing.T) {
rm, ok := mgr.(snack.RepoManager)
require.True(t, ok)
t.Run("ListRepos", func(t *testing.T) {
repos, err := rm.ListRepos(ctx)
require.NoError(t, err)
require.NotEmpty(t, repos, "should have at least one repo")
require.NotEmpty(t, repos)
for _, r := range repos {
assert.NotEmpty(t, r.ID)
}
t.Logf("repos: %d", len(repos))
}
})
})
if nn, ok := mgr.(snack.NameNormalizer); ok {
got := nn.NormalizeName("curl.x86_64")
assert.Equal(t, "curl", got)
// --- KeyManager ---
t.Run("KeyManager", func(t *testing.T) {
km, ok := mgr.(snack.KeyManager)
require.True(t, ok)
t.Run("ListKeys", func(t *testing.T) {
keys, err := km.ListKeys(ctx)
require.NoError(t, err)
require.NotEmpty(t, keys, "Fedora should have GPG keys")
t.Logf("keys: %d", len(keys))
})
})
// --- Grouper ---
t.Run("Grouper", func(t *testing.T) {
g, ok := mgr.(snack.Grouper)
require.True(t, ok)
t.Run("GroupList", func(t *testing.T) {
groups, err := g.GroupList(ctx)
require.NoError(t, err)
require.NotEmpty(t, groups)
t.Logf("groups: %d", len(groups))
})
t.Run("GroupInfo", func(t *testing.T) {
// Use a group that should exist
groups, err := g.GroupList(ctx)
require.NoError(t, err)
require.NotEmpty(t, groups)
pkgs, err := g.GroupInfo(ctx, groups[0])
// Some groups may not have packages queryable by name
if err == nil {
t.Logf("group %q packages: %d", groups[0], len(pkgs))
}
})
t.Run("GroupInfo_NotFound", func(t *testing.T) {
_, err := g.GroupInfo(ctx, "xyznonexistentgroup999")
assert.Error(t, err)
})
})
// --- NameNormalizer ---
t.Run("NameNormalizer", func(t *testing.T) {
nn, ok := mgr.(snack.NameNormalizer)
require.True(t, ok)
tests := []struct {
input, wantName, wantArch string
}{
{"curl.x86_64", "curl", "x86_64"},
{"bash.aarch64", "bash", "aarch64"},
{"python3.noarch", "python3", "noarch"},
{"python3.11.x86_64", "python3.11", "x86_64"},
{"glibc", "glibc", ""},
}
for _, tt := range tests {
t.Run("NormalizeName_"+tt.input, func(t *testing.T) {
got := nn.NormalizeName(tt.input)
assert.Equal(t, tt.wantName, got)
})
t.Run("ParseArch_"+tt.input, func(t *testing.T) {
name, arch := nn.ParseArch(tt.input)
assert.Equal(t, tt.wantName, name)
assert.Equal(t, tt.wantArch, arch)
})
}
})
// --- Upgrade ---
t.Run("Upgrade", func(t *testing.T) {
err := mgr.Upgrade(ctx, snack.WithAssumeYes())
require.NoError(t, err)
})
}