github.com/wolfi-dev/wolfictl@v0.16.11/pkg/dag/graph_test.go (about) 1 package dag 2 3 import ( 4 "context" 5 "os" 6 "path/filepath" 7 "testing" 8 9 "github.com/stretchr/testify/assert" 10 "github.com/stretchr/testify/require" 11 "golang.org/x/exp/maps" 12 ) 13 14 const ( 15 packageRepo = "testdata/packages" 16 key = "testdata/packages/key.rsa.pub" 17 ) 18 19 func TestNewGraph(t *testing.T) { 20 t.Run("basic", func(t *testing.T) { 21 ctx := context.Background() 22 var ( 23 testDir = "testdata/basic" 24 ) 25 t.Run("allowed dangling", func(t *testing.T) { 26 pkgs, err := NewPackages(ctx, os.DirFS(testDir), testDir, "") 27 require.NoError(t, err) 28 graph, err := NewGraph(ctx, pkgs, WithAllowUnresolved()) 29 require.NoError(t, err) 30 amap, err := graph.Graph.AdjacencyMap() 31 require.NoError(t, err) 32 allBusybox := pkgs.Config("busybox", false) 33 require.Len(t, allBusybox, 1) 34 busybox := allBusybox[0] 35 require.Contains(t, amap, PackageHash(busybox)) 36 busyboxDeps := amap[PackageHash(busybox)] 37 expectedDeps := []string{ 38 "ca-certificates-bundle:@unknown", 39 "build-base:@unknown", 40 "binutils:@unknown", 41 "wget:@unknown", 42 "scanelf:@unknown", 43 "patch:@unknown", 44 } 45 assert.Len(t, busyboxDeps, len(expectedDeps)) // 3 direct and 4 from the pipeline 46 // the direct dependencies from environment.contents.packages should be dangling, i.e. unresolved 47 for _, dep := range expectedDeps { 48 assert.Contains(t, busyboxDeps, dep) 49 assert.Equal(t, busyboxDeps[dep].Source, PackageHash(busybox)) 50 assert.Equal(t, busyboxDeps[dep].Target, dep) 51 vertex, err := graph.Graph.Vertex(dep) 52 require.NoError(t, err) 53 assert.False(t, vertex.Resolved()) 54 } 55 }) 56 t.Run("has expected tree", func(t *testing.T) { 57 pkgs, err := NewPackages(ctx, os.DirFS(testDir), testDir, "") 58 require.NoError(t, err) 59 graph, err := NewGraph(ctx, pkgs, WithRepos(packageRepo), WithKeys(key)) 60 require.NoError(t, err) 61 amap, err := graph.Graph.AdjacencyMap() 62 require.NoError(t, err) 63 allBusybox := pkgs.Config("busybox", false) 64 require.Len(t, allBusybox, 1) 65 busybox := allBusybox[0] 66 require.Contains(t, amap, PackageHash(busybox)) 67 busyboxDeps := amap[PackageHash(busybox)] 68 // these deps are taken from the environment.contents.packages of testdata/busybox.yaml 69 // these do not include versions or sources, which are calculated by the graph. 70 // so we need to get the correct deps. 71 // We reference the APKINDEX.tar.gz in testdata/packages, which contains the following packages: 72 expectedDeps := []string{ 73 "ca-certificates-bundle:20220614-r1@testdata/packages/x86_64", 74 "build-base:1-r2@testdata/packages/x86_64", 75 "binutils:2.39-r1@testdata/packages/x86_64", 76 "wget:1.21.3-r1@testdata/packages/x86_64", 77 "scanelf:1.3.4-r1@testdata/packages/x86_64", 78 "patch:2.7.6-r1@testdata/packages/x86_64", 79 } 80 assert.Len(t, busyboxDeps, len(expectedDeps)) // 3 direct and 4 from the pipeline 81 // the direct dependencies from environment.contents.packages should be dangling, i.e. unresolved 82 for _, dep := range expectedDeps { 83 assert.Contains(t, busyboxDeps, dep) 84 assert.Equal(t, busyboxDeps[dep].Source, PackageHash(busybox)) 85 assert.Equal(t, busyboxDeps[dep].Target, dep) 86 vertex, err := graph.Graph.Vertex(dep) 87 require.NoError(t, err) 88 assert.True(t, vertex.Resolved()) 89 } 90 }) 91 }) 92 t.Run("multiple", func(t *testing.T) { 93 ctx := context.Background() 94 var testDir = "testdata/multiple" 95 t.Run("allowed dangling", func(t *testing.T) { 96 pkgs, err := NewPackages(ctx, os.DirFS(testDir), testDir, "") 97 require.NoError(t, err) 98 _, err = NewGraph(ctx, pkgs, WithAllowUnresolved()) 99 require.NoError(t, err) 100 }) 101 t.Run("external dependencies only", func(t *testing.T) { 102 pkgs, err := NewPackages(ctx, os.DirFS(testDir), testDir, "") 103 require.NoError(t, err) 104 graph, err := NewGraph(ctx, pkgs, WithRepos(packageRepo), WithKeys(key)) 105 require.NoError(t, err) 106 amap, err := graph.Graph.AdjacencyMap() 107 require.NoError(t, err) 108 configs := pkgs.Config("one", true) 109 require.Len(t, configs, 1) 110 111 for _, conf := range configs { 112 require.Contains(t, amap, PackageHash(conf)) 113 deps := amap[PackageHash(conf)] 114 expectedDeps := []string{ 115 "wolfi-baselayout:1-r2@testdata/packages/x86_64", 116 "ca-certificates-bundle:20220614-r1@testdata/packages/x86_64", 117 "build-base:1-r2@testdata/packages/x86_64", 118 "busybox:1.35.0-r2@testdata/packages/x86_64", 119 "binutils:2.39-r1@testdata/packages/x86_64", 120 "wget:1.21.3-r1@testdata/packages/x86_64", 121 "scanelf:1.3.4-r1@testdata/packages/x86_64", 122 "make:4.3-r1@testdata/packages/x86_64", 123 } 124 assert.Len(t, deps, len(expectedDeps)) 125 // the direct dependencies from environment.contents.packages should be dangling, i.e. unresolved 126 for _, dep := range expectedDeps { 127 assert.Contains(t, deps, dep) 128 assert.Equal(t, deps[dep].Source, PackageHash(conf)) 129 assert.Equal(t, deps[dep].Target, dep) 130 vertex, err := graph.Graph.Vertex(dep) 131 require.NoError(t, err) 132 assert.True(t, vertex.Resolved()) 133 } 134 } 135 }) 136 t.Run("internal and external dependencies", func(t *testing.T) { 137 pkgs, err := NewPackages(ctx, os.DirFS(testDir), testDir, "") 138 require.NoError(t, err) 139 graph, err := NewGraph(ctx, pkgs, WithRepos(packageRepo), WithKeys(key)) 140 require.NoError(t, err) 141 amap, err := graph.Graph.AdjacencyMap() 142 require.NoError(t, err) 143 allConfigs := pkgs.Config("three-other", true) 144 require.Len(t, allConfigs, 1) 145 conf := allConfigs[0] 146 require.Contains(t, amap, PackageHash(conf)) 147 deps := amap[PackageHash(conf)] 148 // external dependencies, therefore dangling 149 externalDeps := []string{ 150 "wolfi-baselayout:1-r2@testdata/packages/x86_64", 151 "ca-certificates-bundle:20220614-r1@testdata/packages/x86_64", 152 "busybox:1.35.0-r2@testdata/packages/x86_64", 153 } 154 // internal dependencies, therefore resolved 155 // we have two "one", so it takes the highest value 156 internalDeps := []string{ 157 "one:1.2.3-r1@local", 158 "two:4.5.6-r1@local", 159 } 160 assert.Len(t, deps, len(externalDeps)+len(internalDeps)) 161 // the external dependencies should be dangling, i.e. unresolved 162 for _, dep := range externalDeps { 163 assert.Contains(t, deps, dep) 164 assert.Equal(t, deps[dep].Source, PackageHash(conf)) 165 assert.Equal(t, deps[dep].Target, dep) 166 vertex, err := graph.Graph.Vertex(dep) 167 require.NoError(t, err) 168 assert.True(t, vertex.Resolved()) 169 } 170 // the internal dependencies should be resolved 171 for _, dep := range internalDeps { 172 assert.Contains(t, deps, dep) 173 assert.Equal(t, deps[dep].Source, PackageHash(conf)) 174 assert.Equal(t, deps[dep].Target, dep) 175 vertex, err := graph.Graph.Vertex(dep) 176 require.NoError(t, err) 177 assert.Equal(t, PackageHash(vertex), dep) 178 assert.True(t, vertex.Resolved()) 179 } 180 }) 181 182 t.Run("internal dependencies numbered", func(t *testing.T) { 183 pkgs, err := NewPackages(ctx, os.DirFS(testDir), testDir, "") 184 require.NoError(t, err) 185 graph, err := NewGraph(ctx, pkgs, WithRepos(packageRepo), WithKeys(key)) 186 require.NoError(t, err) 187 amap, err := graph.Graph.AdjacencyMap() 188 require.NoError(t, err) 189 allConfigs := pkgs.Config("two", true) 190 require.Len(t, allConfigs, 1) 191 conf := allConfigs[0] 192 require.Contains(t, amap, PackageHash(conf)) 193 deps := amap[PackageHash(conf)] 194 // external dependencies, therefore dangling 195 externalDeps := []string{ 196 "wolfi-baselayout:1-r2@testdata/packages/x86_64", 197 "ca-certificates-bundle:20220614-r1@testdata/packages/x86_64", 198 "build-base:1-r2@testdata/packages/x86_64", 199 "busybox:1.35.0-r2@testdata/packages/x86_64", 200 "binutils:2.39-r1@testdata/packages/x86_64", 201 "wget:1.21.3-r1@testdata/packages/x86_64", 202 "scanelf:1.3.4-r1@testdata/packages/x86_64", 203 "make:4.3-r1@testdata/packages/x86_64", 204 } 205 // internal dependencies, therefore resolved 206 // we have two "one", so we explicitly pick the lower one 207 internalDeps := []string{ 208 "one:1.2.3-r1@local", 209 } 210 assert.Len(t, deps, len(externalDeps)+len(internalDeps)) 211 // the external dependencies should be dangling, i.e. unresolved 212 for _, dep := range externalDeps { 213 assert.Contains(t, deps, dep) 214 assert.Equal(t, deps[dep].Source, PackageHash(conf)) 215 assert.Equal(t, deps[dep].Target, dep) 216 vertex, err := graph.Graph.Vertex(dep) 217 require.NoError(t, err) 218 assert.True(t, vertex.Resolved()) 219 } 220 // the internal dependencies should be resolved 221 for _, dep := range internalDeps { 222 assert.Contains(t, deps, dep) 223 assert.Equal(t, deps[dep].Source, PackageHash(conf)) 224 assert.Equal(t, deps[dep].Target, dep) 225 vertex, err := graph.Graph.Vertex(dep) 226 require.NoError(t, err) 227 assert.Equal(t, PackageHash(vertex), dep) 228 assert.True(t, vertex.Resolved()) 229 } 230 }) 231 }) 232 t.Run("resolve cycle", func(t *testing.T) { 233 ctx := context.Background() 234 var ( 235 testDir = "testdata/cycle" 236 cyclePackageRepo = filepath.Join(testDir, "packages") 237 cycleKey = filepath.Join(cyclePackageRepo, "key.rsa.pub") 238 expectedDeps = map[string][]string{ 239 "a": {"b:1.2.3-r1@local", "c:1.5.5-r1@local", "d:1.0.0-r0@testdata/cycle/packages/x86_64"}, 240 "b": {"c:1.5.5-r1@local", "d:1.0.0-r0@testdata/cycle/packages/x86_64"}, 241 "c": {"d:1.0.0-r0@testdata/cycle/packages/x86_64"}, 242 "d": {"a:1.3.5-r1@local"}, 243 } 244 ) 245 pkgs, err := NewPackages(ctx, os.DirFS(testDir), testDir, "") 246 require.NoError(t, err) 247 graph, err := NewGraph(ctx, pkgs, WithRepos(cyclePackageRepo), WithKeys(cycleKey)) 248 require.NoError(t, err) 249 amap, err := graph.Graph.AdjacencyMap() 250 require.NoError(t, err) 251 for k, v := range expectedDeps { 252 allConfigs := pkgs.Config(k, true) 253 require.Len(t, allConfigs, 1, "no configs for %s", k) 254 confKey := PackageHash(allConfigs[0]) 255 require.Contains(t, amap, confKey, "missing key %s", confKey) 256 var deps []string 257 for d := range amap[confKey] { 258 deps = append(deps, d) 259 } 260 assert.ElementsMatch(t, v, deps, "unexpected dependencies for %s", k) 261 } 262 }) 263 } 264 265 func TestTargets(t *testing.T) { 266 ctx := context.Background() 267 testDir := "testdata/subpackages" 268 269 pkgs, err := NewPackages(ctx, os.DirFS(testDir), testDir, "") 270 require.NoError(t, err) 271 graph, err := NewGraph(ctx, pkgs, WithAllowUnresolved()) 272 require.NoError(t, err) 273 graph, err = graph.Filter(FilterLocal()) 274 require.NoError(t, err) 275 graph, err = graph.Targets() 276 require.NoError(t, err) 277 amap, err := graph.Graph.AdjacencyMap() 278 require.NoError(t, err) 279 expectedDeps := map[string][]string{ 280 "one:1.2.3-r1@local": {}, 281 "two:4.5.6-r1@local": {"one:1.2.3-r1@local"}, 282 "three:4.5.6-r1@local": {"two:4.5.6-r1@local"}, 283 } 284 // the direct dependencies from environment.contents.packages should be dangling, i.e. unresolved 285 for k, want := range expectedDeps { 286 got, ok := amap[k] 287 if !ok { 288 for k := range amap { 289 t.Errorf("found %q", k) 290 } 291 t.Fatalf("did not find %q", k) 292 } 293 294 keys := maps.Keys(got) 295 assert.ElementsMatch(t, want, keys) 296 } 297 }