Skip to content

Commit

Permalink
Add unit test and address review comments
Browse files Browse the repository at this point in the history
Signed-off-by: Akash Kumar <meakash7902@gmail.com>
  • Loading branch information
AkashKumar7902 committed Feb 18, 2024
1 parent 5804939 commit 6fcfb5c
Show file tree
Hide file tree
Showing 5 changed files with 178 additions and 63 deletions.
85 changes: 23 additions & 62 deletions pkg/client/client.go
Original file line number Diff line number Diff line change
Expand Up @@ -17,6 +17,7 @@ import (
"kcl-lang.io/kpm/pkg/env"
"kcl-lang.io/kpm/pkg/errors"
"kcl-lang.io/kpm/pkg/git"
pkgGraph "kcl-lang.io/kpm/pkg/graph"
"kcl-lang.io/kpm/pkg/oci"
"kcl-lang.io/kpm/pkg/opt"
pkg "kcl-lang.io/kpm/pkg/package"
Expand Down Expand Up @@ -1069,53 +1070,34 @@ func (c *KpmClient) ParseOciOptionFromString(oci string, tag string) (*opt.OciOp
return ociOpt, nil
}

// PrintDependencyGraph will print the dependency graph of kcl package dependencies
func (c *KpmClient) PrintDependencyGraph(kclPkg *pkg.KclPkg) error {
_, depGraph, err := c.downloadDeps(kclPkg.Dependencies, kclPkg.ModFile.Dependencies)
// GetDependencyGraph will get the dependency graph of kcl package dependencies
func (c *KpmClient) GetDependencyGraph(kclPkg *pkg.KclPkg) (graph.Graph[string, string], error) {
_, depGraph, err := c.downloadDeps(kclPkg.ModFile.Dependencies, kclPkg.Dependencies)
if err != nil {
return err
return nil, err
}

// add the root vertex(package name) to the dependency graph.
root := fmt.Sprint(kclPkg.GetPkgName())
err = depGraph.AddVertex(root)
sources, err := pkgGraph.FindSources(depGraph)
if err != nil {
return err
return nil, err
}

sources, err := FindSource(depGraph)
// add the root vertex(package name) to the dependency graph.
root := fmt.Sprintf("%s@%s", kclPkg.GetPkgName(), kclPkg.GetPkgVersion())
err = depGraph.AddVertex(root)
if err != nil {
return err
return nil, err
}

// make an edge between the root vertex and all the sources of the dependency graph.
for _, source := range sources {
err = depGraph.AddEdge(source, root)
err = depGraph.AddEdge(root, source)
if err != nil {
return err
}
}

adjMap, err := depGraph.AdjacencyMap()
if err != nil {
return err
}

// print the dependency graph to stdout.
err = graph.BFS(depGraph, root, func(source string) bool {
for target := range adjMap[source] {
reporter.ReportMsgTo(
fmt.Sprint(source, target),
c.logWriter,
)
return nil, err
}
return false
})
if err != nil {
return err
}

return nil
return depGraph, nil
}

// dependencyExists will check whether the dependency exists in the local filesystem.
Expand Down Expand Up @@ -1194,13 +1176,6 @@ func (c *KpmClient) downloadDeps(deps pkg.Dependencies, lockDeps pkg.Dependencie

depGraph := graph.New(graph.StringHash, graph.Directed())

for _, d := range newDeps.Deps {
err := depGraph.AddVertex(fmt.Sprintf("%s@%s", d.Name, d.Version))
if err != nil {
return nil, nil, err
}
}

// Recursively download the dependencies of the new dependencies.
for _, d := range newDeps.Deps {
// Load kcl.mod file of the new downloaded dependencies.
Expand All @@ -1223,20 +1198,25 @@ func (c *KpmClient) downloadDeps(deps pkg.Dependencies, lockDeps pkg.Dependencie
}

source := fmt.Sprintf("%s@%s", d.Name, d.Version)
sourcesOfNestedDepGraph, err := FindSource(nestedDepGraph)
err = depGraph.AddVertex(source)
if err != nil && err != graph.ErrVertexAlreadyExists {
return nil, nil, err
}

sourcesOfNestedDepGraph, err := pkgGraph.FindSources(nestedDepGraph)
if err != nil {
return nil, nil, err
}

depGraph, err = graph.Union(depGraph, nestedDepGraph)
depGraph, err = pkgGraph.Union(depGraph, nestedDepGraph)
if err != nil {
return nil, nil, err
}

// make an edge between the source of all nested dep graph and main dep graph
// make an edge between the source of all nested dep graph and main dep graph
for _, sourceOfNestedDepGraph := range sourcesOfNestedDepGraph {
err = depGraph.AddEdge(source, sourceOfNestedDepGraph)
if err != nil {
if err != nil && err != graph.ErrEdgeAlreadyExists {
return nil, nil, err
}
}
Expand Down Expand Up @@ -1322,22 +1302,3 @@ func check(dep pkg.Dependency, newDepPath string) bool {

return dep.Sum == sum
}

func FindSource[K comparable, T any](g graph.Graph[K, T]) ([]K, error) {
if !g.Traits().IsDirected {
return nil, fmt.Errorf("cannot find source of a non-DAG graph ")
}

predecessorMap, err := g.PredecessorMap()
if err != nil {
return nil, fmt.Errorf("failed to get predecessor map: %w", err)
}

var sources []K
for vertex, predecessors := range predecessorMap {
if len(predecessors) == 0 {
sources = append(sources, vertex)
}
}
return sources, nil
}
40 changes: 40 additions & 0 deletions pkg/client/client_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -12,6 +12,7 @@ import (
"strings"
"testing"

"github.com/dominikbraun/graph"
"github.com/otiai10/copy"
"github.com/stretchr/testify/assert"
"kcl-lang.io/kpm/pkg/env"
Expand Down Expand Up @@ -116,6 +117,45 @@ func TestDownloadLatestOci(t *testing.T) {
assert.Equal(t, err, nil)
}

func TestDependencyGraph(t *testing.T) {
testDir := getTestDir("test_dependency_graph")
assert.Equal(t, utils.DirExists(filepath.Join(testDir, "kcl.mod.lock")), false)
kpmcli, err := NewKpmClient()
assert.Equal(t, err, nil)
kclPkg, err := kpmcli.LoadPkgFromPath(testDir)
assert.Equal(t, err, nil)

depGraph, err := kpmcli.GetDependencyGraph(kclPkg)
assert.Equal(t, err, nil)
adjMap, err := depGraph.AdjacencyMap()
assert.Equal(t, err, nil)

edgeProp := graph.EdgeProperties{
Attributes: map[string]string{},
Weight: 0,
Data: nil,
}
assert.Equal(t, adjMap,
map[string]map[string]graph.Edge[string]{
"dependency_graph@0.0.1": {
"teleport@0.1.0": {Source: "dependency_graph@0.0.1", Target: "teleport@0.1.0", Properties: edgeProp},
"rabbitmq@0.0.1": {Source: "dependency_graph@0.0.1", Target: "rabbitmq@0.0.1", Properties: edgeProp},
"agent@0.1.0": {Source: "dependency_graph@0.0.1", Target: "agent@0.1.0", Properties: edgeProp},
},
"teleport@0.1.0": {
"k8s@1.28": {Source: "teleport@0.1.0", Target: "k8s@1.28", Properties: edgeProp},
},
"rabbitmq@0.0.1": {
"k8s@1.28": {Source: "rabbitmq@0.0.1", Target: "k8s@1.28", Properties: edgeProp},
},
"agent@0.1.0": {
"k8s@1.28": {Source: "agent@0.1.0", Target: "k8s@1.28", Properties: edgeProp},
},
"k8s@1.28": {},
},
)
}

func TestInitEmptyPkg(t *testing.T) {
testDir := initTestDir("test_init_empty_mod")
kclPkg := pkg.NewKclPkg(&opt.InitOptions{Name: "test_name", InitPath: testDir})
Expand Down
9 changes: 9 additions & 0 deletions pkg/client/test_data/test_dependency_graph/kcl.mod
Original file line number Diff line number Diff line change
@@ -0,0 +1,9 @@
[package]
name = "dependency_graph"
edition = "0.0.1"
version = "0.0.1"

[dependencies]
rabbitmq = "0.0.1"
teleport = "0.1.0"
agent = "0.1.0"
24 changes: 23 additions & 1 deletion pkg/cmd/cmd_graph.go
Original file line number Diff line number Diff line change
Expand Up @@ -3,8 +3,10 @@
package cmd

import (
"fmt"
"os"

"github.com/dominikbraun/graph"
"github.com/urfave/cli/v2"
"kcl-lang.io/kpm/pkg/client"
"kcl-lang.io/kpm/pkg/env"
Expand Down Expand Up @@ -60,7 +62,27 @@ func KpmGraph(c *cli.Context, kpmcli *client.KpmClient) error {
return err
}

err = kpmcli.PrintDependencyGraph(kclPkg)
depGraph, err := kpmcli.GetDependencyGraph(kclPkg)
if err != nil {
return err
}

adjMap, err := depGraph.AdjacencyMap()
if err != nil {
return err
}

// print the dependency graph to stdout.
root := fmt.Sprintf("%s@%s", kclPkg.GetPkgName(), kclPkg.GetPkgVersion())
err = graph.BFS(depGraph, root, func(source string) bool {
for target := range adjMap[source] {
reporter.ReportMsgTo(
fmt.Sprint(source, " ", target),
kpmcli.GetLogWriter(),
)
}
return false
})
if err != nil {
return err
}
Expand Down
83 changes: 83 additions & 0 deletions pkg/graph/graph.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,83 @@
package graph

import (
"fmt"
"github.com/dominikbraun/graph"
)

// Union combines two given graphs into a new graph. The vertex hashes in both
// graphs are expected to be unique. The two input graphs will remain unchanged.
//
// Both graphs should be either directed or undirected. All traits for the new
// graph will be derived from g.
//
// If the same vertex/edge happens to be in both g and h, then an error will not be
// thrown as happens in original Union function and successful operation takes place.
func Union[K comparable, T any](g, h graph.Graph[K, T]) (graph.Graph[K, T], error) {
union, err := g.Clone()
if err != nil {
return union, fmt.Errorf("failed to clone g: %w", err)
}

adjacencyMap, err := h.AdjacencyMap()
if err != nil {
return union, fmt.Errorf("failed to get adjacency map: %w", err)
}

addedEdges := make(map[K]map[K]struct{})

for currentHash := range adjacencyMap {
vertex, err := h.Vertex(currentHash)
if err != nil {
return union, fmt.Errorf("failed to get vertex %v: %w", currentHash, err)
}

err = union.AddVertex(vertex)
if err != nil && err != graph.ErrVertexAlreadyExists {
return union, fmt.Errorf("failed to add vertex %v: %w", currentHash, err)
}
}

for _, adjacencies := range adjacencyMap {
for _, edge := range adjacencies {
if _, sourceOK := addedEdges[edge.Source]; sourceOK {
if _, targetOK := addedEdges[edge.Source][edge.Target]; targetOK {
// If the edge addedEdges[source][target] exists, the edge
// has already been created and thus can be skipped here.
continue
}
}

err = union.AddEdge(edge.Source, edge.Target)
if err != nil && err != graph.ErrEdgeAlreadyExists {
return union, fmt.Errorf("failed to add edge (%v, %v): %w", edge.Source, edge.Target, err)
}

if _, ok := addedEdges[edge.Source]; !ok {
addedEdges[edge.Source] = make(map[K]struct{})
}
addedEdges[edge.Source][edge.Target] = struct{}{}
}
}

return union, nil
}

func FindSources[K comparable, T any](g graph.Graph[K, T]) ([]K, error) {
if !g.Traits().IsDirected {
return nil, fmt.Errorf("cannot find source of a non-DAG graph ")
}

predecessorMap, err := g.PredecessorMap()
if err != nil {
return nil, fmt.Errorf("failed to get predecessor map: %w", err)
}

var sources []K
for vertex, predecessors := range predecessorMap {
if len(predecessors) == 0 {
sources = append(sources, vertex)
}
}
return sources, nil
}

0 comments on commit 6fcfb5c

Please sign in to comment.