Repository: dragonflyoss/Dragonfly2
Pull request #1468 (2022-07-18 11:33:17)

feat: add directed acyclic graph package
Signed-off-by: Gaius <gaius.qi@gmail.com>

<!--- Provide a general summary of your changes in the Title above -->

## Description
- Add directed acyclic graph package.
<!--- Describe your changes in detail -->

## Related Issue

<!--- This project only accepts pull requests related to open issues -->
<!--- If suggesting a new feature or change, please discuss it in an issue first -->
<!--- If fixing a bug, there should be an issue describing it with steps to reproduce -->
<!--- Please link to the issue here: -->

## Motivation and Context

<!--- Why is this change required? What problem does it solve? -->

## Screenshots (if appropriate)

## Types of changes

<!--- What types of changes does your code introduce? Put an `x` in all the boxes that apply: -->

- [ ] Bug fix (non-breaking change which fixes an issue)
- [x] New feature (non-breaking change which adds functionality)
- [ ] Breaking change (fix or feature that would cause existing functionality to change)
- [ ] Documentation Update (if none of the other choices apply)

## Checklist

<!--- Go over all the following points, and put an `x` in all the boxes that apply. -->
<!--- If you're unsure about any of these, don't hesitate to ask. We're here to help! -->

- [ ] My change requires a change to the documentation.
- [ ] I have updated the documentation accordingly.
- [x] I have read the **CONTRIBUTING** document.
- [ ] I have added tests to cover my changes.



Patch:
diff --git a/pkg/dag/dag.go b/pkg/dag/dag.go
new file mode 100644
index 00000000..81bedd52
--- /dev/null
+++ b/pkg/dag/dag.go
@@ -0,0 +1,222 @@
+/*
+ *     Copyright 2022 The Dragonfly Authors
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package dag
+
+import (
+	"errors"
+	"sync"
+)
+
+var (
+	// ErrVertexNotFound represents vertex not found.
+	ErrVertexNotFound = errors.New("vertex not found")
+
+	// ErrVertexAlreadyExists represents vertex already exists.
+	ErrVertexAlreadyExists = errors.New("vertex already exists")
+
+	// ErrParnetAlreadyExists represents parent of vertex already exists.
+	ErrParnetAlreadyExists = errors.New("parent of vertex already exists")
+
+	// ErrChildAlreadyExists represents child of vertex already exists.
+	ErrChildAlreadyExists = errors.New("child of vertex already exists")
+
+	// ErrCycleBetweenVertices represents cycle between vertices.
+	ErrCycleBetweenVertices = errors.New("cycle between vertices")
+)
+
+// DAG is the interface used for directed acyclic graph.
+type DAG interface {
+	// AddVertex adds vertex to graph.
+	AddVertex(id string, value any) error
+
+	// DeleteVertex deletes vertex graph.
+	DeleteVertex(id string)
+
+	// GetVertex gets vertex from graph.
+	GetVertex(id string) (*Vertex, error)
+
+	// AddEdge adds edge between two vertices.
+	AddEdge(fromVertexID, toVertexID string) error
+
+	// DeleteEdge deletes edge between two vertices.
+	DeleteEdge(fromVertexID, toVertexID string) error
+}
+
+// dag provides directed acyclic graph function.
+type dag struct {
+	mu       sync.RWMutex
+	vertices map[string]*Vertex
+}
+
+// New returns a new DAG interface.
+func NewDAG() DAG {
+	return &dag{
+		vertices: make(map[string]*Vertex),
+	}
+}
+
+// AddVertex adds vertex to graph.
+func (d *dag) AddVertex(id string, value any) error {
+	d.mu.Lock()
+	defer d.mu.Unlock()
+
+	if _, ok := d.vertices[id]; ok {
+		return ErrVertexAlreadyExists
+	}
+
+	d.vertices[id] = NewVertex(id, value)
+	return nil
+}
+
+// DeleteVertex deletes vertex graph.
+func (d *dag) DeleteVertex(id string) {
+	d.mu.Lock()
+	defer d.mu.Unlock()
+
+	vertex, ok := d.vertices[id]
+	if !ok {
+		return
+	}
+
+	vertex.Parents.Range(func(item any) bool {
+		parent, ok := item.(*Vertex)
+		if !ok {
+			return true
+		}
+
+		parent.Children.Delete(vertex)
+		return true
+	})
+
+	vertex.Children.Range(func(item any) bool {
+		child, ok := item.(*Vertex)
+		if !ok {
+			return true
+		}
+
+		child.Parents.Delete(vertex)
+		return true
+	})
+
+	delete(d.vertices, id)
+}
+
+// GetVertex gets vertex from graph.
+func (d *dag) GetVertex(id string) (*Vertex, error) {
+	d.mu.RLock()
+	defer d.mu.RUnlock()
+
+	vertex, ok := d.vertices[id]
+	if !ok {
+		return nil, ErrVertexNotFound
+	}
+
+	return vertex, nil
+}
+
+// AddEdge adds edge between two vertices.
+func (d *dag) AddEdge(fromVertexID, toVertexID string) error {
+	d.mu.Lock()
+	defer d.mu.Unlock()
+
+	if fromVertexID == toVertexID {
+		return ErrCycleBetweenVertices
+	}
+
+	fromVertex, ok := d.vertices[fromVertexID]
+	if !ok {
+		return ErrVertexNotFound
+	}
+
+	toVertex, ok := d.vertices[toVertexID]
+	if !ok {
+		return ErrVertexNotFound
+	}
+
+	for _, child := range fromVertex.Children.Values() {
+		vertex, ok := child.(*Vertex)
+		if !ok {
+			continue
+		}
+
+		if vertex.ID == toVertexID {
+			return ErrCycleBetweenVertices
+		}
+	}
+
+	if d.depthFirstSearch(toVertexID, fromVertexID) {
+		return ErrCycleBetweenVertices
+	}
+
+	if ok := fromVertex.Children.Add(toVertex); !ok {
+		return ErrChildAlreadyExists
+	}
+
+	if ok := toVertex.Parents.Add(fromVertex); !ok {
+		return ErrParnetAlreadyExists
+	}
+
+	return nil
+}
+
+// DeleteEdge deletes edge between two vertices.
+func (d *dag) DeleteEdge(fromVertexID, toVertexID string) error {
+	d.mu.Lock()
+	defer d.mu.Unlock()
+
+	fromVertex, ok := d.vertices[fromVertexID]
+	if !ok {
+		return ErrVertexNotFound
+	}
+
+	toVertex, ok := d.vertices[toVertexID]
+	if !ok {
+		return ErrVertexNotFound
+	}
+
+	fromVertex.Children.Delete(toVertex)
+	toVertex.Parents.Delete(fromVertex)
+	return nil
+}
+
+// depthFirstSearch is a depth-first search of the directed acyclic graph.
+func (d *dag) depthFirstSearch(fromVertexID, toVertexID string) bool {
+	successors := make(map[string]struct{})
+	d.search(fromVertexID, successors)
+	_, ok := successors[toVertexID]
+	return ok
+}
+
+// depthFirstSearch finds successors of vertex.
+func (d *dag) search(vertexID string, successors map[string]struct{}) {
+	vertex, ok := d.vertices[vertexID]
+	if !ok {
+		return
+	}
+
+	for _, child := range vertex.Children.Values() {
+		vertex, ok := child.(*Vertex)
+		if !ok {
+			continue
+		}
+
+		if _, ok := successors[vertex.ID]; !ok {
+			successors[vertex.ID] = struct{}{}
+			d.search(vertex.ID, successors)
+		}
+	}
+}
diff --git a/pkg/dag/vertex.go b/pkg/dag/vertex.go
new file mode 100644
index 00000000..99ff1442
--- /dev/null
+++ b/pkg/dag/vertex.go
@@ -0,0 +1,52 @@
+/*
+ *     Copyright 2022 The Dragonfly Authors
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package dag
+
+import "d7y.io/dragonfly/v2/pkg/container/set"
+
+// Vertex is a vertex of the directed acyclic graph.
+type Vertex struct {
+	ID       string
+	Value    any
+	Parents  set.SafeSet
+	Children set.SafeSet
+}
+
+// New returns a new Vertex instance.
+func NewVertex(id string, value any) *Vertex {
+	return &Vertex{
+		ID:       id,
+		Value:    value,
+		Parents:  set.NewSafeSet(),
+		Children: set.NewSafeSet(),
+	}
+}
+
+// Degree returns the degree of vertex.
+func (v *Vertex) Degree() int {
+	return int(v.Parents.Len() + v.Children.Len())
+}
+
+// InDegree returns the indegree of vertex.
+func (v *Vertex) InDegree() int {
+	return int(v.Parents.Len())
+}
+
+// OutDegree returns the outdegree of vertex.
+func (v *Vertex) OutDegree() int {
+	return int(v.Children.Len())
+}


Test patch:
diff --git a/pkg/dag/dag_test.go b/pkg/dag/dag_test.go
new file mode 100644
index 00000000..de0bdf37
--- /dev/null
+++ b/pkg/dag/dag_test.go
@@ -0,0 +1,560 @@
+/*
+ *     Copyright 2022 The Dragonfly Authors
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package dag
+
+import (
+	"fmt"
+	"reflect"
+	"testing"
+
+	"github.com/stretchr/testify/assert"
+)
+
+func TestNewDAG(t *testing.T) {
+	d := NewDAG()
+	assert := assert.New(t)
+	assert.Equal(reflect.TypeOf(d).Elem().Name(), "dag")
+}
+
+func TestDAGAddVertex(t *testing.T) {
+	tests := []struct {
+		name   string
+		id     string
+		value  any
+		expect func(t *testing.T, d DAG, err error)
+	}{
+		{
+			name:  "add vertex",
+			id:    mockVertexID,
+			value: mockVertexValue,
+			expect: func(t *testing.T, d DAG, err error) {
+				assert := assert.New(t)
+				assert.NoError(err)
+			},
+		},
+		{
+			name:  "vertex already exists",
+			id:    mockVertexID,
+			value: mockVertexValue,
+			expect: func(t *testing.T, d DAG, err error) {
+				assert := assert.New(t)
+				assert.NoError(err)
+
+				assert.EqualError(d.AddVertex(mockVertexID, mockVertexValue), ErrVertexAlreadyExists.Error())
+			},
+		},
+	}
+
+	for _, tc := range tests {
+		t.Run(tc.name, func(t *testing.T) {
+			d := NewDAG()
+			tc.expect(t, d, d.AddVertex(tc.id, tc.name))
+		})
+	}
+}
+
+func TestDAGDeleteVertex(t *testing.T) {
+	tests := []struct {
+		name   string
+		expect func(t *testing.T, d DAG)
+	}{
+		{
+			name: "delete vertex",
+			expect: func(t *testing.T, d DAG) {
+				assert := assert.New(t)
+				if err := d.AddVertex(mockVertexID, mockVertexValue); err != nil {
+					assert.NoError(err)
+				}
+				d.DeleteVertex(mockVertexID)
+
+				_, err := d.GetVertex(mockVertexID)
+				assert.EqualError(err, ErrVertexNotFound.Error())
+			},
+		},
+		{
+			name: "delete vertex with edges",
+			expect: func(t *testing.T, d DAG) {
+				assert := assert.New(t)
+
+				var (
+					mockToVertexID = "baz"
+				)
+				if err := d.AddVertex(mockToVertexID, mockVertexValue); err != nil {
+					assert.NoError(err)
+				}
+
+				if err := d.AddVertex(mockVertexID, mockVertexValue); err != nil {
+					assert.NoError(err)
+				}
+
+				if err := d.AddEdge(mockVertexID, mockToVertexID); err != nil {
+					assert.NoError(err)
+				}
+
+				d.DeleteVertex(mockVertexID)
+
+				_, err := d.GetVertex(mockVertexID)
+				assert.EqualError(err, ErrVertexNotFound.Error())
+
+				vertex, err := d.GetVertex(mockToVertexID)
+				assert.NoError(err)
+				assert.Equal(vertex.Parents.Len(), uint(0))
+			},
+		},
+	}
+
+	for _, tc := range tests {
+		t.Run(tc.name, func(t *testing.T) {
+			d := NewDAG()
+			tc.expect(t, d)
+		})
+	}
+}
+
+func TestDAGGetVertex(t *testing.T) {
+	tests := []struct {
+		name   string
+		expect func(t *testing.T, d DAG)
+	}{
+		{
+			name: "get vertex",
+			expect: func(t *testing.T, d DAG) {
+				assert := assert.New(t)
+				if err := d.AddVertex(mockVertexID, mockVertexValue); err != nil {
+					assert.NoError(err)
+				}
+
+				vertex, err := d.GetVertex(mockVertexID)
+				assert.NoError(err)
+				assert.Equal(vertex.ID, mockVertexID)
+				assert.Equal(vertex.Value, mockVertexValue)
+				assert.Equal(vertex.Parents.Len(), uint(0))
+				assert.Equal(vertex.Children.Len(), uint(0))
+			},
+		},
+		{
+			name: "vertex not found",
+			expect: func(t *testing.T, d DAG) {
+				assert := assert.New(t)
+				_, err := d.GetVertex(mockVertexID)
+				assert.EqualError(err, ErrVertexNotFound.Error())
+			},
+		},
+	}
+
+	for _, tc := range tests {
+		t.Run(tc.name, func(t *testing.T) {
+			d := NewDAG()
+			tc.expect(t, d)
+		})
+	}
+}
+
+func TestDAGAddEdge(t *testing.T) {
+	tests := []struct {
+		name   string
+		expect func(t *testing.T, d DAG)
+	}{
+		{
+			name: "add edge",
+			expect: func(t *testing.T, d DAG) {
+				assert := assert.New(t)
+				var (
+					mockVertexEID = "bae"
+					mockVertexFID = "baf"
+					mockVertexGID = "bag"
+					mockVertexHID = "bah"
+					mockVertexIID = "bai"
+				)
+
+				if err := d.AddVertex(mockVertexEID, mockVertexValue); err != nil {
+					assert.NoError(err)
+				}
+
+				if err := d.AddVertex(mockVertexFID, mockVertexValue); err != nil {
+					assert.NoError(err)
+				}
+
+				if err := d.AddVertex(mockVertexGID, mockVertexValue); err != nil {
+					assert.NoError(err)
+				}
+
+				if err := d.AddVertex(mockVertexHID, mockVertexValue); err != nil {
+					assert.NoError(err)
+				}
+
+				if err := d.AddVertex(mockVertexIID, mockVertexValue); err != nil {
+					assert.NoError(err)
+				}
+
+				if err := d.AddEdge(mockVertexEID, mockVertexFID); err != nil {
+					assert.NoError(err)
+				}
+
+				if err := d.AddEdge(mockVertexFID, mockVertexGID); err != nil {
+					assert.NoError(err)
+				}
+
+				if err := d.AddEdge(mockVertexFID, mockVertexHID); err != nil {
+					assert.NoError(err)
+				}
+
+				if err := d.AddEdge(mockVertexGID, mockVertexIID); err != nil {
+					assert.NoError(err)
+				}
+
+				if err := d.AddEdge(mockVertexIID, mockVertexHID); err != nil {
+					assert.NoError(err)
+				}
+			},
+		},
+		{
+			name: "cycle between vertices",
+			expect: func(t *testing.T, d DAG) {
+				assert := assert.New(t)
+				var (
+					mockVertexEID = "bae"
+					mockVertexFID = "baf"
+					mockVertexGID = "bag"
+					mockVertexHID = "bah"
+					mockVertexIID = "bai"
+				)
+
+				if err := d.AddVertex(mockVertexEID, mockVertexValue); err != nil {
+					assert.NoError(err)
+				}
+
+				if err := d.AddVertex(mockVertexFID, mockVertexValue); err != nil {
+					assert.NoError(err)
+				}
+
+				if err := d.AddVertex(mockVertexGID, mockVertexValue); err != nil {
+					assert.NoError(err)
+				}
+
+				if err := d.AddVertex(mockVertexHID, mockVertexValue); err != nil {
+					assert.NoError(err)
+				}
+
+				if err := d.AddVertex(mockVertexIID, mockVertexValue); err != nil {
+					assert.NoError(err)
+				}
+
+				if err := d.AddEdge(mockVertexEID, mockVertexEID); err != nil {
+					assert.EqualError(err, ErrCycleBetweenVertices.Error())
+				}
+
+				if err := d.AddEdge(mockVertexEID, mockVertexFID); err != nil {
+					assert.NoError(err)
+				}
+
+				if err := d.AddEdge(mockVertexFID, mockVertexEID); err != nil {
+					assert.EqualError(err, ErrCycleBetweenVertices.Error())
+				}
+
+				if err := d.AddEdge(mockVertexFID, mockVertexGID); err != nil {
+					assert.NoError(err)
+				}
+
+				if err := d.AddEdge(mockVertexGID, mockVertexEID); err != nil {
+					assert.EqualError(err, ErrCycleBetweenVertices.Error())
+				}
+
+				if err := d.AddEdge(mockVertexGID, mockVertexHID); err != nil {
+					assert.NoError(err)
+				}
+
+				if err := d.AddEdge(mockVertexHID, mockVertexIID); err != nil {
+					assert.NoError(err)
+				}
+
+				if err := d.AddEdge(mockVertexIID, mockVertexEID); err != nil {
+					assert.EqualError(err, ErrCycleBetweenVertices.Error())
+				}
+			},
+		},
+		{
+			name: "vertex not found",
+			expect: func(t *testing.T, d DAG) {
+				assert := assert.New(t)
+				var (
+					mockVertexEID = "bae"
+					mockVertexFID = "baf"
+				)
+
+				if err := d.AddVertex(mockVertexEID, mockVertexValue); err != nil {
+					assert.NoError(err)
+				}
+
+				if err := d.AddEdge(mockVertexEID, mockVertexFID); err != nil {
+					assert.EqualError(err, ErrVertexNotFound.Error())
+				}
+
+				if err := d.AddEdge(mockVertexFID, mockVertexEID); err != nil {
+					assert.EqualError(err, ErrVertexNotFound.Error())
+				}
+			},
+		},
+	}
+
+	for _, tc := range tests {
+		t.Run(tc.name, func(t *testing.T) {
+			d := NewDAG()
+			tc.expect(t, d)
+		})
+	}
+}
+
+func TestDAGDeleteEdge(t *testing.T) {
+	tests := []struct {
+		name   string
+		expect func(t *testing.T, d DAG)
+	}{
+		{
+			name: "delete edge",
+			expect: func(t *testing.T, d DAG) {
+				assert := assert.New(t)
+				var (
+					mockVertexEID = "bae"
+					mockVertexFID = "baf"
+				)
+
+				if err := d.AddVertex(mockVertexEID, mockVertexValue); err != nil {
+					assert.NoError(err)
+				}
+
+				if err := d.AddVertex(mockVertexFID, mockVertexValue); err != nil {
+					assert.NoError(err)
+				}
+
+				if err := d.AddEdge(mockVertexEID, mockVertexFID); err != nil {
+					assert.NoError(err)
+				}
+
+				if err := d.DeleteEdge(mockVertexFID, mockVertexEID); err != nil {
+					assert.NoError(err)
+				}
+
+				vf, err := d.GetVertex(mockVertexFID)
+				assert.NoError(err)
+				assert.Equal(vf.Parents.Len(), uint(1))
+				ve, err := d.GetVertex(mockVertexEID)
+				assert.NoError(err)
+				assert.Equal(ve.Children.Len(), uint(1))
+
+				if err := d.DeleteEdge(mockVertexEID, mockVertexFID); err != nil {
+					assert.NoError(err)
+				}
+
+				vf, err = d.GetVertex(mockVertexFID)
+				assert.NoError(err)
+				assert.Equal(vf.Parents.Len(), uint(0))
+				ve, err = d.GetVertex(mockVertexEID)
+				assert.NoError(err)
+				assert.Equal(ve.Children.Len(), uint(0))
+			},
+		},
+		{
+			name: "vertex not found",
+			expect: func(t *testing.T, d DAG) {
+				assert := assert.New(t)
+				var (
+					mockVertexEID = "bae"
+					mockVertexFID = "baf"
+				)
+
+				if err := d.AddVertex(mockVertexEID, mockVertexValue); err != nil {
+					assert.NoError(err)
+				}
+
+				if err := d.AddEdge(mockVertexEID, mockVertexFID); err != nil {
+					assert.EqualError(err, ErrVertexNotFound.Error())
+				}
+
+				if err := d.AddEdge(mockVertexFID, mockVertexEID); err != nil {
+					assert.EqualError(err, ErrVertexNotFound.Error())
+				}
+			},
+		},
+	}
+
+	for _, tc := range tests {
+		t.Run(tc.name, func(t *testing.T) {
+			d := NewDAG()
+			tc.expect(t, d)
+		})
+	}
+}
+
+func BenchmarkDAGAddVertex(b *testing.B) {
+	var ids []string
+	d := NewDAG()
+	for n := 0; n < b.N; n++ {
+		ids = append(ids, fmt.Sprint(n))
+	}
+
+	b.ResetTimer()
+	for _, id := range ids {
+		if err := d.AddVertex(id, nil); err != nil {
+			b.Fatal(err)
+		}
+	}
+}
+
+func BenchmarkDAGDeleteVertex(b *testing.B) {
+	var ids []string
+	d := NewDAG()
+	for n := 0; n < b.N; n++ {
+		id := fmt.Sprint(n)
+		if err := d.AddVertex(id, nil); err != nil {
+			b.Fatal(err)
+		}
+
+		ids = append(ids, id)
+	}
+
+	b.ResetTimer()
+	for _, id := range ids {
+		d.DeleteVertex(id)
+	}
+}
+
+func BenchmarkDAGDeleteVertexWithMultiEdges(b *testing.B) {
+	var ids []string
+	d := NewDAG()
+	for n := 0; n < b.N; n++ {
+		id := fmt.Sprint(n)
+		if err := d.AddVertex(id, nil); err != nil {
+			b.Fatal(err)
+		}
+
+		ids = append(ids, id)
+	}
+
+	edgeCount := 5
+	for index, id := range ids {
+		if index+edgeCount > len(ids)-1 {
+			break
+		}
+
+		for n := 1; n < edgeCount; n++ {
+			if err := d.AddEdge(id, ids[index+n]); err != nil {
+				b.Fatal(err)
+			}
+		}
+	}
+
+	b.ResetTimer()
+	for _, id := range ids {
+		d.DeleteVertex(id)
+	}
+}
+
+func BenchmarkDAGAddEdge(b *testing.B) {
+	var ids []string
+	d := NewDAG()
+	for n := 0; n < b.N; n++ {
+		id := fmt.Sprint(n)
+		if err := d.AddVertex(id, nil); err != nil {
+			b.Fatal(err)
+		}
+
+		ids = append(ids, id)
+	}
+
+	b.ResetTimer()
+	for index, id := range ids {
+		if index < 1 {
+			continue
+		}
+
+		if err := d.AddEdge(id, ids[index-1]); err != nil {
+			b.Fatal(err)
+		}
+	}
+}
+
+func BenchmarkDAGAddEdgeWithMultiEdges(b *testing.B) {
+	var ids []string
+	d := NewDAG()
+	for n := 0; n < b.N; n++ {
+		id := fmt.Sprint(n)
+		if err := d.AddVertex(id, nil); err != nil {
+			b.Fatal(err)
+		}
+
+		ids = append(ids, id)
+	}
+
+	edgeCount := 5
+	for index, id := range ids {
+		if index+edgeCount > len(ids)-1 {
+			break
+		}
+
+		for n := 1; n < edgeCount; n++ {
+			if err := d.AddEdge(id, ids[index+n]); err != nil {
+				b.Fatal(err)
+			}
+		}
+	}
+
+	b.ResetTimer()
+	for index, id := range ids {
+		if index+edgeCount+1 > len(ids)-1 {
+			break
+		}
+
+		if err := d.AddEdge(id, ids[index+edgeCount+1]); err != nil {
+			b.Fatal(err)
+		}
+	}
+}
+
+func BenchmarkDAGDeleteEdge(b *testing.B) {
+	var ids []string
+	d := NewDAG()
+	for n := 0; n < b.N; n++ {
+		id := fmt.Sprint(n)
+		if err := d.AddVertex(id, nil); err != nil {
+			b.Fatal(err)
+		}
+
+		ids = append(ids, id)
+	}
+
+	for index, id := range ids {
+		if index < 1 {
+			continue
+		}
+
+		if err := d.AddEdge(id, ids[index-1]); err != nil {
+			b.Fatal(err)
+		}
+	}
+
+	b.ResetTimer()
+	for index, id := range ids {
+		if index < 1 {
+			continue
+		}
+
+		if err := d.DeleteEdge(id, ids[index-1]); err != nil {
+			b.Fatal(err)
+		}
+	}
+}
diff --git a/pkg/dag/vertex_test.go b/pkg/dag/vertex_test.go
new file mode 100644
index 00000000..21b1a10c
--- /dev/null
+++ b/pkg/dag/vertex_test.go
@@ -0,0 +1,97 @@
+/*
+ *     Copyright 2022 The Dragonfly Authors
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package dag
+
+import (
+	"testing"
+
+	"github.com/stretchr/testify/assert"
+)
+
+const (
+	mockVertexID    = "foo"
+	mockVertexValue = "var"
+)
+
+func TestNewVertex(t *testing.T) {
+	v := NewVertex(mockVertexID, mockVertexValue)
+	assert := assert.New(t)
+	assert.Equal(v.ID, mockVertexID)
+	assert.Equal(v.Value, mockVertexValue)
+	assert.Equal(v.Parents.Len(), uint(0))
+	assert.Equal(v.Children.Len(), uint(0))
+}
+
+func TestVertexDegree(t *testing.T) {
+	v := NewVertex(mockVertexID, mockVertexValue)
+	assert := assert.New(t)
+	assert.Equal(v.ID, mockVertexID)
+	assert.Equal(v.Value, mockVertexValue)
+	assert.Equal(v.Degree(), 0)
+
+	v.Parents.Add(mockVertexID)
+	assert.Equal(v.Degree(), 1)
+
+	v.Children.Add(mockVertexID)
+	assert.Equal(v.Degree(), 2)
+
+	v.Parents.Delete(mockVertexID)
+	assert.Equal(v.Degree(), 1)
+
+	v.Children.Delete(mockVertexID)
+	assert.Equal(v.Degree(), 0)
+}
+
+func TestVertexInDegree(t *testing.T) {
+	v := NewVertex(mockVertexID, mockVertexValue)
+	assert := assert.New(t)
+	assert.Equal(v.ID, mockVertexID)
+	assert.Equal(v.Value, mockVertexValue)
+	assert.Equal(v.InDegree(), 0)
+
+	v.Parents.Add(mockVertexID)
+	assert.Equal(v.InDegree(), 1)
+
+	v.Children.Add(mockVertexID)
+	assert.Equal(v.InDegree(), 1)
+
+	v.Parents.Delete(mockVertexID)
+	assert.Equal(v.InDegree(), 0)
+
+	v.Children.Delete(mockVertexID)
+	assert.Equal(v.InDegree(), 0)
+}
+
+func TestVertexOutDegree(t *testing.T) {
+	v := NewVertex(mockVertexID, mockVertexValue)
+	assert := assert.New(t)
+	assert.Equal(v.ID, mockVertexID)
+	assert.Equal(v.Value, mockVertexValue)
+	assert.Equal(v.OutDegree(), 0)
+
+	v.Parents.Add(mockVertexID)
+	assert.Equal(v.OutDegree(), 0)
+
+	v.Children.Add(mockVertexID)
+	assert.Equal(v.OutDegree(), 1)
+
+	v.Parents.Delete(mockVertexID)
+	assert.Equal(v.OutDegree(), 1)
+
+	v.Children.Delete(mockVertexID)
+	assert.Equal(v.OutDegree(), 0)
+}