157 lines
3.8 KiB
Go
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())
|
|
}
|
|
}
|