Files
yay/pkg/dep/dep_graph_rpc_test.go
T
2026-06-18 19:58:23 +02:00

841 lines
31 KiB
Go

//go:build !integration
// Package dep provides tests for tree resolution and parsing using RPC/.SRCINFO metadata
// instead of PKGBUILD parsing. These tests validate:
// - Reliable parser: Ability to handle complex packages using provided metadata
// - Reliable solver: Ability to correctly solve and build complex dependency chains
// - Split packages: Ability to correctly build and install split packages
package dep
import (
"context"
"fmt"
"io"
"os"
"testing"
aurc "github.com/Jguer/aur"
"github.com/stretchr/testify/require"
"github.com/Jguer/yay/v13/pkg/db/mock"
mockaur "github.com/Jguer/yay/v13/pkg/dep/mock"
aur "github.com/Jguer/yay/v13/pkg/query"
"github.com/Jguer/yay/v13/pkg/text"
)
// TestGrapher_ReliableParser_AWSCliGit tests the reliable parsing capability
// for complex packages like aws-cli-git that have many dependencies.
// This validates that the RPC metadata is correctly parsed and dependencies
// are properly resolved without needing PKGBUILD parsing.
func TestGrapher_ReliableParser_AWSCliGit(t *testing.T) {
t.Parallel()
mockDB := &mock.DBExecutor{
SyncPackageFn: func(string) mock.IPackage { return nil },
PackagesFromGroupFn: func(string) []mock.IPackage { return []mock.IPackage{} },
SyncSatisfierFn: func(s string) mock.IPackage {
switch s {
case "aws-cli-git":
return nil
case "python":
return &mock.Package{PName: "python", PVersion: "3.11.0-1", PDB: mock.NewDB("core")}
case "python-botocore>=1.19.35", "python-botocore":
return &mock.Package{PName: "python-botocore", PVersion: "1.29.0-1", PDB: mock.NewDB("extra")}
case "python-docutils>=0.10", "python-docutils":
return &mock.Package{PName: "python-docutils", PVersion: "0.19-1", PDB: mock.NewDB("extra")}
case "python-rsa>=3.1.2", "python-rsa":
return &mock.Package{PName: "python-rsa", PVersion: "4.9-1", PDB: mock.NewDB("extra")}
case "python-s3transfer>=0.3.0", "python-s3transfer":
return &mock.Package{PName: "python-s3transfer", PVersion: "0.6.0-1", PDB: mock.NewDB("extra")}
case "python-yaml>=3.10", "python-yaml":
return &mock.Package{PName: "python-yaml", PVersion: "6.0-1", PDB: mock.NewDB("extra")}
case "python-colorama>=0.2.5", "python-colorama":
return &mock.Package{PName: "python-colorama", PVersion: "0.4.6-1", PDB: mock.NewDB("extra")}
case "python-tox>=2.3.1", "python-tox":
return &mock.Package{PName: "python-tox", PVersion: "4.0.0-1", PDB: mock.NewDB("extra")}
case "python-nose>=1.3.7", "python-nose":
return &mock.Package{PName: "python-nose", PVersion: "1.3.7-1", PDB: mock.NewDB("extra")}
case "python-mock>=1.3.0", "python-mock":
return &mock.Package{PName: "python-mock", PVersion: "5.0.0-1", PDB: mock.NewDB("extra")}
case "python-wheel>=0.24.0", "python-wheel":
return &mock.Package{PName: "python-wheel", PVersion: "0.38.0-1", PDB: mock.NewDB("extra")}
case "python-dateutil>=2.1", "python-dateutil":
return &mock.Package{PName: "python-dateutil", PVersion: "2.8.2-1", PDB: mock.NewDB("extra")}
case "python-sphinx>=1.1.3", "python-sphinx":
return &mock.Package{PName: "python-sphinx", PVersion: "6.0.0-1", PDB: mock.NewDB("extra")}
case "python-distribute":
return &mock.Package{PName: "python-distribute", PVersion: "0.7.3-1", PDB: mock.NewDB("extra")}
case "git":
return &mock.Package{PName: "git", PVersion: "2.39.0-1", PDB: mock.NewDB("extra")}
}
panic("implement me " + s)
},
LocalSatisfierExistsFn: func(s string) bool {
switch s {
case "aws-cli-git":
return false
case "python", "python-botocore>=1.19.35", "python-docutils>=0.10",
"python-rsa>=3.1.2", "python-s3transfer>=0.3.0", "python-yaml>=3.10",
"python-colorama>=0.2.5", "python-tox>=2.3.1", "python-nose>=1.3.7",
"python-mock>=1.3.0", "python-wheel>=0.24.0", "python-dateutil>=2.1",
"python-sphinx>=1.1.3", "python-distribute", "git":
return true
}
panic("implement me " + s)
},
LocalPackageFn: func(string) mock.IPackage { return nil },
}
mockAUR := &mockaur.MockAUR{GetFn: func(ctx context.Context, query *aurc.Query) ([]aur.Pkg, error) {
if len(query.Needles) > 0 && query.Needles[0] == "aws-cli-git" {
awsFn := getFromFile(t, "testdata/aws-cli-git.json")
return awsFn(ctx, query)
}
return []aur.Pkg{}, nil
}}
t.Run("parses aws-cli-git with all its dependencies", func(td *testing.T) {
td.Parallel()
g := NewGrapher(mockDB, mockAUR, false, true, false, false, false,
text.NewLogger(io.Discard, io.Discard, &os.File{}, true, "test"))
got, err := g.GraphFromTargets(td.Context(), nil, []string{"aws-cli-git"})
require.NoError(td, err)
layers := got.TopoSortedLayers(nil)
require.NotEmpty(td, layers)
require.Contains(td, layers[0], "aws-cli-git")
require.Equal(td, "1.27.145.r11217.g5885ee4dc-1", layers[0]["aws-cli-git"].Version)
require.Equal(td, "aws-cli-git", layers[0]["aws-cli-git"].AURBase)
require.Equal(td, Explicit, layers[0]["aws-cli-git"].Reason)
require.Equal(td, AUR, layers[0]["aws-cli-git"].Source)
})
t.Run("validates provides field for aws-cli", func(td *testing.T) {
td.Parallel()
g := NewGrapher(mockDB, mockAUR, false, true, false, false, false,
text.NewLogger(io.Discard, io.Discard, &os.File{}, true, "test"))
got, err := g.GraphFromTargets(td.Context(), nil, []string{"aws-cli-git"})
require.NoError(td, err)
layers := got.TopoSortedLayers(nil)
require.NotEmpty(td, layers)
require.Contains(td, layers[0], "aws-cli-git")
})
}
// TestGrapher_ReliableSolver_LiriDesktopGit tests the dependency solver
// with complex dependency chains like liri-desktop-git metapackage.
func TestGrapher_ReliableSolver_LiriDesktopGit(t *testing.T) {
t.Parallel()
mockDB := &mock.DBExecutor{
SyncPackageFn: func(string) mock.IPackage { return nil },
PackagesFromGroupFn: func(string) []mock.IPackage { return []mock.IPackage{} },
SyncSatisfierFn: func(s string) mock.IPackage {
switch s {
case "liri-desktop-git", "liri-shell-git", "liri-settings-git",
"libliri-git", "fluid-git", "liri-cmake-shared-git":
return nil
case "qt5-declarative":
return &mock.Package{PName: "qt5-declarative", PVersion: "5.15.10-1", PDB: mock.NewDB("extra")}
case "qt5-quickcontrols2":
return &mock.Package{PName: "qt5-quickcontrols2", PVersion: "5.15.10-1", PDB: mock.NewDB("extra")}
case "qt5-svg":
return &mock.Package{PName: "qt5-svg", PVersion: "5.15.10-1", PDB: mock.NewDB("extra")}
case "qt5-graphicaleffects":
return &mock.Package{PName: "qt5-graphicaleffects", PVersion: "5.15.10-1", PDB: mock.NewDB("extra")}
case "qt5-wayland":
return &mock.Package{PName: "qt5-wayland", PVersion: "5.15.10-1", PDB: mock.NewDB("extra")}
case "wayland":
return &mock.Package{PName: "wayland", PVersion: "1.22.0-1", PDB: mock.NewDB("extra")}
case "cmake":
return &mock.Package{PName: "cmake", PVersion: "3.28.0-1", PDB: mock.NewDB("extra")}
case "qt5-tools":
return &mock.Package{PName: "qt5-tools", PVersion: "5.15.10-1", PDB: mock.NewDB("extra")}
case "git":
return &mock.Package{PName: "git", PVersion: "2.43.0-1", PDB: mock.NewDB("extra")}
}
panic("implement me " + s)
},
LocalSatisfierExistsFn: func(s string) bool {
switch s {
case "liri-desktop-git", "liri-shell-git", "liri-settings-git",
"libliri-git", "fluid-git":
return false
case "liri-cmake-shared-git": // makedepend from AUR
return false
case "qt5-declarative", "qt5-quickcontrols2", "qt5-svg",
"qt5-graphicaleffects", "qt5-wayland", "wayland",
"cmake", "qt5-tools", "git":
return true
}
panic("implement me " + s)
},
LocalPackageFn: func(string) mock.IPackage { return nil },
}
mockAUR := &mockaur.MockAUR{GetFn: func(ctx context.Context, query *aurc.Query) ([]aur.Pkg, error) {
for _, needle := range query.Needles {
switch needle {
case "liri-desktop-git", "liri-shell-git", "liri-settings-git",
"libliri-git", "fluid-git", "liri-cmake-shared-git":
liriFn := getFromFile(t, "testdata/liri-desktop-git.json")
return liriFn(ctx, query)
}
}
return []aur.Pkg{}, nil
}}
t.Run("liri-desktop-git pulls all dependencies", func(td *testing.T) {
td.Parallel()
g := NewGrapher(mockDB, mockAUR, false, true, false, false, false,
text.NewLogger(io.Discard, io.Discard, &os.File{}, true, "test"))
got, err := g.GraphFromTargets(td.Context(), nil, []string{"liri-desktop-git"})
require.NoError(td, err)
layers := got.TopoSortedLayers(nil)
totalPkgs := 0
for _, layer := range layers {
totalPkgs += len(layer)
}
// 6 packages: liri-desktop-git + 4 deps + liri-cmake-shared-git (makedep)
require.Equal(td, 6, totalPkgs)
require.Contains(td, layers[0], "liri-desktop-git")
require.Equal(td, Explicit, layers[0]["liri-desktop-git"].Reason)
})
t.Run("complex dependency chain resolves in correct order", func(td *testing.T) {
td.Parallel()
g := NewGrapher(mockDB, mockAUR, false, true, false, false, false,
text.NewLogger(io.Discard, io.Discard, &os.File{}, true, "test"))
got, err := g.GraphFromTargets(td.Context(), nil, []string{"liri-desktop-git"})
require.NoError(td, err)
layers := got.TopoSortedLayers(nil)
require.Contains(td, layers[0], "liri-desktop-git")
allPkgs := make(map[string]bool)
for _, layer := range layers {
for pkg := range layer {
allPkgs[pkg] = true
}
}
require.True(td, allPkgs["liri-desktop-git"])
require.True(td, allPkgs["liri-shell-git"])
require.True(td, allPkgs["liri-settings-git"])
require.True(td, allPkgs["fluid-git"])
require.True(td, allPkgs["libliri-git"])
require.True(td, allPkgs["liri-cmake-shared-git"]) // makedepend
})
}
// TestGrapher_SplitPackages_Clion tests split packages where multiple packages
// come from the same package base, ensuring no rebuilding or reinstalling multiple times.
func TestGrapher_SplitPackages_Clion(t *testing.T) {
t.Parallel()
mockDB := &mock.DBExecutor{
SyncPackageFn: func(string) mock.IPackage { return nil },
PackagesFromGroupFn: func(string) []mock.IPackage { return []mock.IPackage{} },
SyncSatisfierFn: func(s string) mock.IPackage {
switch s {
case "clion", "clion-jre", "clion-cmake", "clion-gdb", "clion-lldb":
return nil
case "libdbusmenu-glib":
return &mock.Package{PName: "libdbusmenu-glib", PVersion: "16.04.0-5", PDB: mock.NewDB("extra")}
case "rsync":
return &mock.Package{PName: "rsync", PVersion: "3.2.7-1", PDB: mock.NewDB("extra")}
case "glibc":
return &mock.Package{PName: "glibc", PVersion: "2.38-1", PDB: mock.NewDB("core")}
case "gcc-libs":
return &mock.Package{PName: "gcc-libs", PVersion: "13.2.1-1", PDB: mock.NewDB("core")}
case "python":
return &mock.Package{PName: "python", PVersion: "3.11.0-1", PDB: mock.NewDB("core")}
}
panic("implement me " + s)
},
LocalSatisfierExistsFn: func(s string) bool {
switch s {
case "clion", "clion-jre", "clion-cmake", "clion-gdb", "clion-lldb":
return false
case "libdbusmenu-glib", "rsync", "glibc", "gcc-libs", "python":
return true
}
panic("implement me " + s)
},
LocalPackageFn: func(string) mock.IPackage { return nil },
}
mockAUR := &mockaur.MockAUR{GetFn: func(ctx context.Context, query *aurc.Query) ([]aur.Pkg, error) {
for _, needle := range query.Needles {
switch needle {
case "clion", "clion-jre", "clion-cmake", "clion-gdb", "clion-lldb":
clionFn := getFromFile(t, "testdata/clion.json")
return clionFn(ctx, query)
}
}
return []aur.Pkg{}, nil
}}
installInfos := map[string]*InstallInfo{
"clion exp": {Source: AUR, Reason: Explicit, Version: "2025.3.1.1-1", AURBase: "clion", LastModified: 1768325120, Maintainer: "Zrax"},
"clion-jre exp": {Source: AUR, Reason: Explicit, Version: "2025.3.1.1-1", AURBase: "clion", LastModified: 1768325120, Maintainer: "Zrax"},
"clion-cmake exp": {Source: AUR, Reason: Explicit, Version: "2025.3.1.1-1", AURBase: "clion", LastModified: 1768325120, Maintainer: "Zrax"},
"clion-gdb exp": {Source: AUR, Reason: Explicit, Version: "2025.3.1.1-1", AURBase: "clion", LastModified: 1768325120, Maintainer: "Zrax"},
"clion-lldb exp": {Source: AUR, Reason: Explicit, Version: "2025.3.1.1-1", AURBase: "clion", LastModified: 1768325120, Maintainer: "Zrax"},
}
tests := []struct {
name string
targets []string
wantLayers []map[string]*InstallInfo
}{
{
name: "single clion package",
targets: []string{"clion"},
wantLayers: []map[string]*InstallInfo{
{"clion": installInfos["clion exp"]},
},
},
{
name: "clion with clion-jre",
targets: []string{"clion", "clion-jre"},
wantLayers: []map[string]*InstallInfo{
{"clion": installInfos["clion exp"], "clion-jre": installInfos["clion-jre exp"]},
},
},
{
name: "all clion packages from same base",
targets: []string{"clion", "clion-jre", "clion-cmake", "clion-gdb", "clion-lldb"},
wantLayers: []map[string]*InstallInfo{
{
"clion": installInfos["clion exp"],
"clion-jre": installInfos["clion-jre exp"],
"clion-cmake": installInfos["clion-cmake exp"],
"clion-gdb": installInfos["clion-gdb exp"],
"clion-lldb": installInfos["clion-lldb exp"],
},
},
},
}
for _, tt := range tests {
t.Run(tt.name, func(t *testing.T) {
t.Parallel()
g := NewGrapher(mockDB, mockAUR, false, true, false, false, false,
text.NewLogger(io.Discard, io.Discard, &os.File{}, true, "test"))
got, err := g.GraphFromTargets(t.Context(), nil, tt.targets)
require.NoError(t, err)
layers := got.TopoSortedLayers(nil)
require.EqualValues(t, tt.wantLayers, layers, layers)
})
}
t.Run("packages from same base share AURBase", func(t *testing.T) {
g := NewGrapher(mockDB, mockAUR, false, true, false, false, false,
text.NewLogger(io.Discard, io.Discard, &os.File{}, true, "test"))
got, err := g.GraphFromTargets(t.Context(), nil,
[]string{"clion", "clion-jre", "clion-cmake"})
require.NoError(t, err)
layers := got.TopoSortedLayers(nil)
require.Len(t, layers, 1)
for _, info := range layers[0] {
require.NotEmpty(t, info.AURBase)
require.Equal(t, "clion", info.AURBase)
}
})
}
// TestGrapher_SplitPackages_SamsungUnifiedDriver tests split packages where
// packages depend on another package from the same package base.
func TestGrapher_SplitPackages_SamsungUnifiedDriver(t *testing.T) {
t.Parallel()
mockDB := &mock.DBExecutor{
SyncPackageFn: func(string) mock.IPackage { return nil },
PackagesFromGroupFn: func(string) []mock.IPackage { return []mock.IPackage{} },
SyncSatisfierFn: func(s string) mock.IPackage {
switch s {
case "samsung-unified-driver", "samsung-unified-driver-common",
"samsung-unified-driver-printer", "samsung-unified-driver-scanner":
return nil
case "cups":
return &mock.Package{PName: "cups", PVersion: "2.4.2-1", PDB: mock.NewDB("extra")}
case "ghostscript":
return &mock.Package{PName: "ghostscript", PVersion: "10.02.0-1", PDB: mock.NewDB("extra")}
case "libxml2-legacy":
return &mock.Package{PName: "libxml2-legacy", PVersion: "2.10.3-1", PDB: mock.NewDB("extra")}
case "libusb-compat":
return &mock.Package{PName: "libusb-compat", PVersion: "0.1.8-1", PDB: mock.NewDB("extra")}
case "sane":
return &mock.Package{PName: "sane", PVersion: "1.2.1-1", PDB: mock.NewDB("extra")}
}
panic("implement me " + s)
},
LocalSatisfierExistsFn: func(s string) bool {
switch s {
case "samsung-unified-driver", "samsung-unified-driver-common",
"samsung-unified-driver-printer", "samsung-unified-driver-scanner":
return false
case "cups", "ghostscript", "libxml2-legacy", "libusb-compat", "sane":
return true
}
panic("implement me " + s)
},
LocalPackageFn: func(string) mock.IPackage { return nil },
}
mockAUR := &mockaur.MockAUR{GetFn: func(ctx context.Context, query *aurc.Query) ([]aur.Pkg, error) {
for _, needle := range query.Needles {
switch needle {
case "samsung-unified-driver", "samsung-unified-driver-common",
"samsung-unified-driver-printer", "samsung-unified-driver-scanner":
samsungFn := getFromFile(t, "testdata/samsung-unified-driver.json")
return samsungFn(ctx, query)
}
}
return []aur.Pkg{}, nil
}}
tests := []struct {
name string
targets []string
wantPkgCount int
wantContains []string
wantNotContain []string
}{
{
name: "metapackage samsung-unified-driver pulls printer and scanner",
targets: []string{"samsung-unified-driver"},
wantPkgCount: 4,
wantContains: []string{
"samsung-unified-driver",
"samsung-unified-driver-printer",
"samsung-unified-driver-scanner",
"samsung-unified-driver-common",
},
},
{
name: "printer alone pulls common",
targets: []string{"samsung-unified-driver-printer"},
wantPkgCount: 2,
wantContains: []string{"samsung-unified-driver-printer", "samsung-unified-driver-common"},
wantNotContain: []string{"samsung-unified-driver-scanner"},
},
{
name: "scanner alone pulls common",
targets: []string{"samsung-unified-driver-scanner"},
wantPkgCount: 2,
wantContains: []string{"samsung-unified-driver-scanner", "samsung-unified-driver-common"},
wantNotContain: []string{"samsung-unified-driver-printer"},
},
{
name: "common alone",
targets: []string{"samsung-unified-driver-common"},
wantPkgCount: 1,
wantContains: []string{"samsung-unified-driver-common"},
},
}
for _, tt := range tests {
t.Run(tt.name, func(t *testing.T) {
t.Parallel()
g := NewGrapher(mockDB, mockAUR, false, true, false, false, false,
text.NewLogger(io.Discard, io.Discard, &os.File{}, true, "test"))
got, err := g.GraphFromTargets(t.Context(), nil, tt.targets)
require.NoError(t, err)
layers := got.TopoSortedLayers(nil)
allPkgs := make(map[string]bool)
for _, layer := range layers {
for pkg := range layer {
allPkgs[pkg] = true
}
}
require.Equal(t, tt.wantPkgCount, len(allPkgs))
for _, pkg := range tt.wantContains {
require.True(t, allPkgs[pkg], "expected package %s not found", pkg)
}
for _, pkg := range tt.wantNotContain {
require.False(t, allPkgs[pkg], "unexpected package %s found", pkg)
}
})
}
t.Run("split package internal deps resolved correctly", func(t *testing.T) {
g := NewGrapher(mockDB, mockAUR, false, true, false, false, false,
text.NewLogger(io.Discard, io.Discard, &os.File{}, true, "test"))
got, err := g.GraphFromTargets(t.Context(), nil, []string{"samsung-unified-driver"})
require.NoError(t, err)
layers := got.TopoSortedLayers(nil)
lastLayer := layers[len(layers)-1]
require.Contains(t, lastLayer, "samsung-unified-driver-common")
for _, layer := range layers {
for _, info := range layer {
require.NotEmpty(t, info.AURBase)
require.Equal(t, "samsung-unified-driver", info.AURBase)
}
}
})
}
// TestGrapher_SplitPackages_NX tests independent split packages like nxproxy and nxagent.
func TestGrapher_SplitPackages_NX(t *testing.T) {
t.Parallel()
mockDB := &mock.DBExecutor{
SyncPackageFn: func(string) mock.IPackage { return nil },
PackagesFromGroupFn: func(string) []mock.IPackage { return []mock.IPackage{} },
SyncSatisfierFn: func(s string) mock.IPackage {
switch s {
case "nxproxy", "nxagent", "nx-x11", "libxcomp":
return nil
case "libjpeg-turbo":
return &mock.Package{PName: "libjpeg-turbo", PVersion: "2.1.5-1", PDB: mock.NewDB("extra")}
case "libpng":
return &mock.Package{PName: "libpng", PVersion: "1.6.39-1", PDB: mock.NewDB("extra")}
case "gcc-libs":
return &mock.Package{PName: "gcc-libs", PVersion: "13.2.1-1", PDB: mock.NewDB("core")}
case "libxml2":
return &mock.Package{PName: "libxml2", PVersion: "2.11.0-1", PDB: mock.NewDB("extra")}
case "xkeyboard-config":
return &mock.Package{PName: "xkeyboard-config", PVersion: "2.38-1", PDB: mock.NewDB("extra")}
case "xorg-xkbcomp":
return &mock.Package{PName: "xorg-xkbcomp", PVersion: "1.4.6-1", PDB: mock.NewDB("extra")}
case "libxfont2":
return &mock.Package{PName: "libxfont2", PVersion: "2.0.6-1", PDB: mock.NewDB("extra")}
case "libxinerama":
return &mock.Package{PName: "libxinerama", PVersion: "1.1.5-1", PDB: mock.NewDB("extra")}
case "xorg-font-util":
return &mock.Package{PName: "xorg-font-util", PVersion: "1.4.0-1", PDB: mock.NewDB("extra")}
case "pixman":
return &mock.Package{PName: "pixman", PVersion: "0.42.2-1", PDB: mock.NewDB("extra")}
case "libxrandr":
return &mock.Package{PName: "libxrandr", PVersion: "1.5.3-1", PDB: mock.NewDB("extra")}
case "libxtst":
return &mock.Package{PName: "libxtst", PVersion: "1.2.4-1", PDB: mock.NewDB("extra")}
case "libxcomposite":
return &mock.Package{PName: "libxcomposite", PVersion: "0.4.6-1", PDB: mock.NewDB("extra")}
case "libxpm":
return &mock.Package{PName: "libxpm", PVersion: "3.5.16-1", PDB: mock.NewDB("extra")}
case "libxdamage":
return &mock.Package{PName: "libxdamage", PVersion: "1.1.6-1", PDB: mock.NewDB("extra")}
case "libtirpc":
return &mock.Package{PName: "libtirpc", PVersion: "1.3.3-1", PDB: mock.NewDB("extra")}
case "xorgproto":
return &mock.Package{PName: "xorgproto", PVersion: "2023.2-1", PDB: mock.NewDB("extra")}
case "imake":
return &mock.Package{PName: "imake", PVersion: "1.0.9-1", PDB: mock.NewDB("extra")}
}
panic("implement me " + s)
},
LocalSatisfierExistsFn: func(s string) bool {
switch s {
case "nxproxy", "nxagent", "nx-x11", "libxcomp":
return false
case "libjpeg-turbo", "libpng", "gcc-libs", "libxml2",
"xkeyboard-config", "xorg-xkbcomp", "libxfont2", "libxinerama",
"xorg-font-util", "pixman", "libxrandr", "libxtst",
"libxcomposite", "libxpm", "libxdamage", "libtirpc",
"xorgproto", "imake":
return true
}
panic("implement me " + s)
},
LocalPackageFn: func(string) mock.IPackage { return nil },
}
mockAUR := &mockaur.MockAUR{GetFn: func(ctx context.Context, query *aurc.Query) ([]aur.Pkg, error) {
for _, needle := range query.Needles {
switch needle {
case "nxproxy", "nxagent", "nx-x11", "libxcomp":
nxFn := getFromFile(t, "testdata/nx.json")
return nxFn(ctx, query)
}
}
return []aur.Pkg{}, nil
}}
installInfos := map[string]*InstallInfo{
"nxproxy exp": {Source: AUR, Reason: Explicit, Version: "3.5.99.27-3", AURBase: "nx", LastModified: 1693834449, Maintainer: "harrietobrien"},
"nxagent exp": {Source: AUR, Reason: Explicit, Version: "3.5.99.27-3", AURBase: "nx", LastModified: 1693834449, Maintainer: "harrietobrien"},
"nx-x11 dep": {Source: AUR, Reason: Dep, Version: "3.5.99.27-3", AURBase: "nx", LastModified: 1693834449, Maintainer: "harrietobrien"},
"libxcomp dep": {Source: AUR, Reason: Dep, Version: "3.5.99.27-3", AURBase: "nx", LastModified: 1693834449, Maintainer: "harrietobrien"},
"libxcomp exp": {Source: AUR, Reason: Explicit, Version: "3.5.99.27-3", AURBase: "nx", LastModified: 1693834449, Maintainer: "harrietobrien"},
}
tests := []struct {
name string
targets []string
wantLayers []map[string]*InstallInfo
}{
{
name: "nxproxy independently",
targets: []string{"nxproxy"},
wantLayers: []map[string]*InstallInfo{
{"nxproxy": installInfos["nxproxy exp"]},
{"libxcomp": installInfos["libxcomp dep"]},
},
},
{
name: "nxagent independently - has more deps",
targets: []string{"nxagent"},
wantLayers: []map[string]*InstallInfo{
{"nxagent": installInfos["nxagent exp"]},
{"nx-x11": installInfos["nx-x11 dep"]},
{"libxcomp": installInfos["libxcomp dep"]},
},
},
{
name: "both nxproxy and nxagent",
targets: []string{"nxproxy", "nxagent"},
wantLayers: []map[string]*InstallInfo{
{"nxproxy": installInfos["nxproxy exp"], "nxagent": installInfos["nxagent exp"]},
{"nx-x11": installInfos["nx-x11 dep"]},
{"libxcomp": installInfos["libxcomp dep"]},
},
},
{
name: "libxcomp independently",
targets: []string{"libxcomp"},
wantLayers: []map[string]*InstallInfo{
{"libxcomp": installInfos["libxcomp exp"]},
},
},
}
for _, tt := range tests {
t.Run(tt.name, func(t *testing.T) {
t.Parallel()
g := NewGrapher(mockDB, mockAUR, false, true, false, false, false,
text.NewLogger(io.Discard, io.Discard, &os.File{}, true, "test"))
got, err := g.GraphFromTargets(t.Context(), nil, tt.targets)
require.NoError(t, err)
layers := got.TopoSortedLayers(nil)
require.EqualValues(t, tt.wantLayers, layers, layers)
})
}
t.Run("split packages share AURBase but can be installed independently", func(t *testing.T) {
g := NewGrapher(mockDB, mockAUR, false, true, false, false, false,
text.NewLogger(io.Discard, io.Discard, &os.File{}, true, "test"))
got1, err := g.GraphFromTargets(t.Context(), nil, []string{"nxproxy"})
require.NoError(t, err)
layers1 := got1.TopoSortedLayers(nil)
got2, err := g.GraphFromTargets(t.Context(), nil, []string{"nxagent"})
require.NoError(t, err)
layers2 := got2.TopoSortedLayers(nil)
require.Equal(t, "nx", layers1[0]["nxproxy"].AURBase)
require.Equal(t, "nx", layers2[0]["nxagent"].AURBase)
totalPkgs1 := 0
for _, layer := range layers1 {
totalPkgs1 += len(layer)
}
totalPkgs2 := 0
for _, layer := range layers2 {
totalPkgs2 += len(layer)
}
require.Equal(t, 2, totalPkgs1)
require.Equal(t, 3, totalPkgs2)
})
}
// TestGrapher_SplitPackages_ReversedOrder tests that split packages resolve
// correctly regardless of the order they are specified in.
func TestGrapher_SplitPackages_ReversedOrder(t *testing.T) {
t.Parallel()
mockDB := &mock.DBExecutor{
SyncPackageFn: func(string) mock.IPackage { return nil },
PackagesFromGroupFn: func(string) []mock.IPackage { return []mock.IPackage{} },
SyncSatisfierFn: func(s string) mock.IPackage {
switch s {
case "nxproxy", "nxagent", "nx-x11", "libxcomp":
return nil
default:
return &mock.Package{PName: s, PVersion: "1.0.0-1", PDB: mock.NewDB("extra")}
}
},
LocalSatisfierExistsFn: func(s string) bool {
switch s {
case "nxproxy", "nxagent", "nx-x11", "libxcomp":
return false
default:
return true
}
},
LocalPackageFn: func(string) mock.IPackage { return nil },
}
mockAUR := &mockaur.MockAUR{GetFn: func(ctx context.Context, query *aurc.Query) ([]aur.Pkg, error) {
for _, needle := range query.Needles {
switch needle {
case "nxproxy", "nxagent", "nx-x11", "libxcomp":
nxFn := getFromFile(t, "testdata/nx.json")
return nxFn(ctx, query)
}
}
return []aur.Pkg{}, nil
}}
t.Run("nxproxy nxagent order", func(t *testing.T) {
g := NewGrapher(mockDB, mockAUR, false, true, false, false, false,
text.NewLogger(io.Discard, io.Discard, &os.File{}, true, "test"))
got, err := g.GraphFromTargets(t.Context(), nil, []string{"nxproxy", "nxagent"})
require.NoError(t, err)
layers1 := got.TopoSortedLayers(nil)
require.Contains(t, layers1[0], "nxproxy")
require.Contains(t, layers1[0], "nxagent")
})
t.Run("nxagent nxproxy reversed order", func(t *testing.T) {
g := NewGrapher(mockDB, mockAUR, false, true, false, false, false,
text.NewLogger(io.Discard, io.Discard, &os.File{}, true, "test"))
got, err := g.GraphFromTargets(t.Context(), nil, []string{"nxagent", "nxproxy"})
require.NoError(t, err)
layers2 := got.TopoSortedLayers(nil)
require.Contains(t, layers2[0], "nxproxy")
require.Contains(t, layers2[0], "nxagent")
})
}
// TestGrapher_MultipleInstallInfo ensures that when the same package appears as
// both explicit target and dependency, the explicit reason takes precedence.
func TestGrapher_MultipleInstallInfo(t *testing.T) {
t.Parallel()
mockDB := &mock.DBExecutor{
SyncPackageFn: func(string) mock.IPackage { return nil },
PackagesFromGroupFn: func(string) []mock.IPackage { return []mock.IPackage{} },
SyncSatisfierFn: func(s string) mock.IPackage {
switch s {
case "samsung-unified-driver", "samsung-unified-driver-common",
"samsung-unified-driver-printer", "samsung-unified-driver-scanner":
return nil
default:
return &mock.Package{PName: s, PVersion: "1.0.0-1", PDB: mock.NewDB("extra")}
}
},
LocalSatisfierExistsFn: func(s string) bool {
switch s {
case "samsung-unified-driver", "samsung-unified-driver-common",
"samsung-unified-driver-printer", "samsung-unified-driver-scanner":
return false
default:
return true
}
},
LocalPackageFn: func(string) mock.IPackage { return nil },
}
mockAUR := &mockaur.MockAUR{GetFn: func(ctx context.Context, query *aurc.Query) ([]aur.Pkg, error) {
for _, needle := range query.Needles {
switch needle {
case "samsung-unified-driver", "samsung-unified-driver-common",
"samsung-unified-driver-printer", "samsung-unified-driver-scanner":
samsungFn := getFromFile(t, "testdata/samsung-unified-driver.json")
return samsungFn(ctx, query)
}
}
return []aur.Pkg{}, nil
}}
t.Run("explicit target takes precedence over dependency", func(t *testing.T) {
g := NewGrapher(mockDB, mockAUR, false, true, false, false, false,
text.NewLogger(io.Discard, io.Discard, &os.File{}, true, "test"))
got, err := g.GraphFromTargets(t.Context(), nil,
[]string{"samsung-unified-driver", "samsung-unified-driver-common"})
require.NoError(t, err)
layers := got.TopoSortedLayers(nil)
found := false
for _, layer := range layers {
if info, ok := layer["samsung-unified-driver-common"]; ok {
found = true
require.Equal(t, Explicit, info.Reason,
"explicit target should have Explicit reason, not Dep")
}
}
require.True(t, found, "samsung-unified-driver-common should be in layers")
})
}
// TestGrapher_VersionedDependencies tests proper handling of versioned dependencies.
func TestGrapher_VersionedDependencies(t *testing.T) {
t.Parallel()
mockDB := &mock.DBExecutor{
SyncPackageFn: func(string) mock.IPackage { return nil },
PackagesFromGroupFn: func(string) []mock.IPackage { return []mock.IPackage{} },
SyncSatisfierFn: func(s string) mock.IPackage {
switch s {
case "versioned-pkg", "dep-pkg", "dep-pkg>=2.0.0":
return nil
}
panic(fmt.Sprintf("implement me: %s", s))
},
LocalSatisfierExistsFn: func(s string) bool {
switch s {
case "versioned-pkg", "dep-pkg", "dep-pkg>=2.0.0":
return false
}
return true
},
LocalPackageFn: func(string) mock.IPackage { return nil },
}
mockAUR := &mockaur.MockAUR{GetFn: func(ctx context.Context, query *aurc.Query) ([]aur.Pkg, error) {
mockPkgs := map[string]aur.Pkg{
"versioned-pkg": {
Name: "versioned-pkg",
PackageBase: "versioned-pkg",
Version: "1.0.0-1",
Depends: []string{"dep-pkg>=2.0.0"},
},
"dep-pkg": {
Name: "dep-pkg",
PackageBase: "dep-pkg",
Version: "2.5.0-1",
},
}
pkgs := []aur.Pkg{}
for _, needle := range query.Needles {
if pkg, ok := mockPkgs[needle]; ok {
pkgs = append(pkgs, pkg)
}
}
return pkgs, nil
}}
t.Run("versioned dependency satisfied by higher version", func(t *testing.T) {
g := NewGrapher(mockDB, mockAUR, false, true, false, false, false,
text.NewLogger(io.Discard, io.Discard, &os.File{}, true, "test"))
got, err := g.GraphFromTargets(t.Context(), nil, []string{"versioned-pkg"})
require.NoError(t, err)
layers := got.TopoSortedLayers(nil)
require.Len(t, layers, 2)
require.Contains(t, layers[0], "versioned-pkg")
require.Contains(t, layers[1], "dep-pkg")
require.Equal(t, "2.5.0-1", layers[1]["dep-pkg"].Version)
})
}