Files
dataStructures/tests/graphTests/dag_test.go
T
Acid 5bc2be9e03
Go / build (push) Successful in 31s
added Directed graphs and DAG
2026-07-24 20:35:34 -04:00

157 lines
3.8 KiB
Go

package tests
import (
"slices"
"testing"
"datastructures/graphs"
)
func TestNewDAGIsEmpty(t *testing.T) {
dag := graphs.NewDAG[int]()
if dag.Order() != 0 {
t.Errorf("new dag Order = %d, want 0", dag.Order())
}
if dag.Size() != 0 {
t.Errorf("new dag Size = %d, want 0", dag.Size())
}
}
func TestDAGAddVertexReturnsTrueOnSuccess(t *testing.T) {
dag := graphs.NewDAG[string]()
if !dag.AddVertex("a", "b") {
t.Error("AddVertex(a, b) = false, want true")
}
if dag.Order() != 2 {
t.Errorf("Order = %d, want 2", dag.Order())
}
if dag.Size() != 1 {
t.Errorf("Size = %d, want 1", dag.Size())
}
if !slices.Contains(dag.Display()["a"], "b") {
t.Errorf("expected a->b, got a: %v", dag.Display()["a"])
}
}
// The core invariant: an edge that would close a cycle must be rejected.
func TestDAGRejectsCycle(t *testing.T) {
dag := graphs.NewDAG[string]()
dag.AddVertex("a", "b")
dag.AddVertex("b", "c")
// c -> a would close a -> b -> c -> a
if dag.AddVertex("c", "a") {
t.Error("AddVertex(c, a) = true, want false (would create a cycle)")
}
if dag.Size() != 2 {
t.Errorf("Size = %d, want 2 (cycle edge must not be added)", dag.Size())
}
if slices.Contains(dag.Display()["c"], "a") {
t.Errorf("c->a should not have been added, got c: %v", dag.Display()["c"])
}
}
// A back-edge across a longer chain must also be caught.
func TestDAGRejectsLongCycle(t *testing.T) {
dag := graphs.NewDAG[int]()
dag.AddVertex(1, 2)
dag.AddVertex(2, 3)
dag.AddVertex(3, 4)
dag.AddVertex(4, 5)
// 5 -> 1 would close the whole chain into a loop
if dag.AddVertex(5, 1) {
t.Error("AddVertex(5, 1) = true, want false (long cycle)")
}
if dag.Size() != 4 {
t.Errorf("Size = %d, want 4", dag.Size())
}
}
func TestDAGSelfLoopRejected(t *testing.T) {
dag := graphs.NewDAG[int]()
if dag.AddVertex(1, 1) {
t.Error("AddVertex(1, 1) = true, want false (self loop)")
}
if dag.Size() != 0 {
t.Errorf("self-loop should add no edge, Size = %d", dag.Size())
}
}
// A diamond has multiple paths between two vertices but no cycle: all edges
// should be accepted.
func TestDAGAllowsDiamond(t *testing.T) {
dag := graphs.NewDAG[string]()
if !dag.AddVertex("a", "b") {
t.Error("a->b rejected")
}
if !dag.AddVertex("a", "c") {
t.Error("a->c rejected")
}
if !dag.AddVertex("b", "d") {
t.Error("b->d rejected")
}
if !dag.AddVertex("c", "d") {
t.Error("c->d rejected (diamond, not a cycle)")
}
if dag.Order() != 4 {
t.Errorf("Order = %d, want 4", dag.Order())
}
if dag.Size() != 4 {
t.Errorf("Size = %d, want 4", dag.Size())
}
}
func TestDAGDuplicateEdgeRejected(t *testing.T) {
dag := graphs.NewDAG[string]()
dag.AddVertex("a", "b")
if dag.AddVertex("a", "b") {
t.Error("duplicate AddVertex(a, b) = true, want false")
}
if len(dag.Display()["a"]) != 1 {
t.Errorf("expected no duplicate edge, got a: %v", dag.Display()["a"])
}
if dag.Size() != 1 {
t.Errorf("Size = %d, want 1", dag.Size())
}
}
// Rejecting a cycle edge must not corrupt the graph: valid edges added
// afterward should still work.
func TestDAGStillUsableAfterRejection(t *testing.T) {
dag := graphs.NewDAG[string]()
dag.AddVertex("a", "b")
dag.AddVertex("b", "c")
dag.AddVertex("c", "a") // rejected
if !dag.AddVertex("c", "d") {
t.Error("AddVertex(c, d) = false after a rejection, want true")
}
if dag.Size() != 3 {
t.Errorf("Size = %d, want 3", dag.Size())
}
}
func TestDAGAddIsolatedVertex(t *testing.T) {
dag := graphs.NewDAG[string]()
if !dag.AddIsolatedVertex("solo") {
t.Error("AddIsolatedVertex on new vertex = false, want true")
}
if dag.AddIsolatedVertex("solo") {
t.Error("AddIsolatedVertex on existing vertex = true, want false")
}
if dag.Order() != 1 {
t.Errorf("Order = %d, want 1", dag.Order())
}
if dag.Size() != 0 {
t.Errorf("isolated vertex should add no edges, Size = %d", dag.Size())
}
}