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  }