depgraph: alphabetize/style

pull/5/head
Mitchell Hashimoto 12 years ago
parent c0a7e5b98b
commit f4e9dda0ea

@ -11,7 +11,7 @@ import (
"github.com/hashicorp/terraform/digraph"
)
// Graph is used to represent the entire dependency graph
// Graph is used to represent a dependency graph.
type Graph struct {
Name string
Meta interface{}
@ -19,58 +19,8 @@ type Graph struct {
Root *Noun
}
// Validate is used to ensure that a few properties of the graph are not violated:
// 1) There must be a single "root", or source on which nothing depends.
// 2) All nouns in the graph must be reachable from the root
// 3) The graph must be cycle free, meaning there are no cicular dependencies
func (g *Graph) Validate() error {
// Convert to node list
nodes := make([]digraph.Node, len(g.Nouns))
for i, n := range g.Nouns {
nodes[i] = n
}
// Create a validate erro
vErr := &ValidateError{}
// Search for all the sources, if we have only 1, it must be the root
if sources := digraph.Sources(nodes); len(sources) != 1 {
vErr.MissingRoot = true
goto CHECK_CYCLES
} else {
g.Root = sources[0].(*Noun)
}
// Check reachability
if unreached := digraph.Unreachable(g.Root, nodes); len(unreached) > 0 {
vErr.Unreachable = make([]*Noun, len(unreached))
for i, u := range unreached {
vErr.Unreachable[i] = u.(*Noun)
}
}
CHECK_CYCLES:
// Check for cycles
if cycles := digraph.StronglyConnectedComponents(nodes, true); len(cycles) > 0 {
vErr.Cycles = make([][]*Noun, len(cycles))
for i, cycle := range cycles {
group := make([]*Noun, len(cycle))
for j, n := range cycle {
group[j] = n.(*Noun)
}
vErr.Cycles[i] = group
}
}
// Return the detailed error
if vErr.MissingRoot || vErr.Unreachable != nil || vErr.Cycles != nil {
return vErr
}
return nil
}
// ValidateError implements the Error interface but provides
// additional information on a validation error
// additional information on a validation error.
type ValidateError struct {
// If set, then the graph is missing a single root, on which
// there are no depdendencies
@ -89,6 +39,31 @@ func (v *ValidateError) Error() string {
return "The depedency graph is not valid"
}
// ConstraintError is used to return detailed violation
// information from CheckConstraints
type ConstraintError struct {
Violations []*Violation
}
func (c *ConstraintError) Error() string {
return fmt.Sprintf("%d constraint violations", len(c.Violations))
}
// Violation is used to pass along information about
// a constraint violation
type Violation struct {
Source *Noun
Target *Noun
Dependency *Dependency
Constraint Constraint
Err error
}
func (v *Violation) Error() string {
return fmt.Sprintf("Constraint %v between %v and %v violated: %v",
v.Constraint, v.Source, v.Target, v.Err)
}
// CheckConstraints walks the graph and ensures that all
// user imposed constraints are satisfied.
func (g *Graph) CheckConstraints() error {
@ -129,27 +104,52 @@ func (g *Graph) CheckConstraints() error {
return nil
}
// ConstraintError is used to return detailed violation
// information from CheckConstraints
type ConstraintError struct {
Violations []*Violation
}
// Validate is used to ensure that a few properties of the graph are not violated:
// 1) There must be a single "root", or source on which nothing depends.
// 2) All nouns in the graph must be reachable from the root
// 3) The graph must be cycle free, meaning there are no cicular dependencies
func (g *Graph) Validate() error {
// Convert to node list
nodes := make([]digraph.Node, len(g.Nouns))
for i, n := range g.Nouns {
nodes[i] = n
}
func (c *ConstraintError) Error() string {
return fmt.Sprintf("%d constraint violations", len(c.Violations))
}
// Create a validate erro
vErr := &ValidateError{}
// Violation is used to pass along information about
// a constraint violation
type Violation struct {
Source *Noun
Target *Noun
Dependency *Dependency
Constraint Constraint
Err error
}
// Search for all the sources, if we have only 1, it must be the root
if sources := digraph.Sources(nodes); len(sources) != 1 {
vErr.MissingRoot = true
goto CHECK_CYCLES
} else {
g.Root = sources[0].(*Noun)
}
func (v *Violation) Error() string {
return fmt.Sprintf("Constraint %v between %v and %v violated: %v",
v.Constraint, v.Source, v.Target, v.Err)
// Check reachability
if unreached := digraph.Unreachable(g.Root, nodes); len(unreached) > 0 {
vErr.Unreachable = make([]*Noun, len(unreached))
for i, u := range unreached {
vErr.Unreachable[i] = u.(*Noun)
}
}
CHECK_CYCLES:
// Check for cycles
if cycles := digraph.StronglyConnectedComponents(nodes, true); len(cycles) > 0 {
vErr.Cycles = make([][]*Noun, len(cycles))
for i, cycle := range cycles {
group := make([]*Noun, len(cycle))
for j, n := range cycle {
group[j] = n.(*Noun)
}
vErr.Cycles[i] = group
}
}
// Return the detailed error
if vErr.MissingRoot || vErr.Unreachable != nil || vErr.Cycles != nil {
return vErr
}
return nil
}

@ -53,9 +53,26 @@ func NounMapToList(m map[string]*Noun) []*Noun {
return list
}
func TestGraph_Validate_NoRoot(t *testing.T) {
func TestGraph_Validate(t *testing.T) {
nodes := ParseNouns(`a -> b
b -> a`)
a -> c
b -> d
b -> e
c -> d
c -> e`)
list := NounMapToList(nodes)
g := &Graph{Name: "Test", Nouns: list}
if err := g.Validate(); err != nil {
t.Fatalf("err: %v", err)
}
}
func TestGraph_Validate_Cycle(t *testing.T) {
nodes := ParseNouns(`a -> b
a -> c
b -> d
d -> b`)
list := NounMapToList(nodes)
g := &Graph{Name: "Test", Nouns: list}
@ -69,8 +86,16 @@ b -> a`)
t.Fatalf("expected validate error")
}
if !vErr.MissingRoot {
t.Fatalf("expected missing root")
if len(vErr.Cycles) != 1 {
t.Fatalf("expected cycles")
}
cycle := vErr.Cycles[0]
if cycle[0].Name != "d" {
t.Fatalf("bad: %v", cycle)
}
if cycle[1].Name != "b" {
t.Fatalf("bad: %v", cycle)
}
}
@ -95,11 +120,9 @@ c -> d`)
}
}
func TestGraph_Validate_Unreachable(t *testing.T) {
func TestGraph_Validate_NoRoot(t *testing.T) {
nodes := ParseNouns(`a -> b
a -> c
b -> d
x -> x`)
b -> a`)
list := NounMapToList(nodes)
g := &Graph{Name: "Test", Nouns: list}
@ -113,20 +136,16 @@ x -> x`)
t.Fatalf("expected validate error")
}
if len(vErr.Unreachable) != 1 {
t.Fatalf("expected unreachable")
}
if vErr.Unreachable[0].Name != "x" {
t.Fatalf("bad: %v", vErr.Unreachable[0])
if !vErr.MissingRoot {
t.Fatalf("expected missing root")
}
}
func TestGraph_Validate_Cycle(t *testing.T) {
func TestGraph_Validate_Unreachable(t *testing.T) {
nodes := ParseNouns(`a -> b
a -> c
b -> d
d -> b`)
x -> x`)
list := NounMapToList(nodes)
g := &Graph{Name: "Test", Nouns: list}
@ -140,32 +159,12 @@ d -> b`)
t.Fatalf("expected validate error")
}
if len(vErr.Cycles) != 1 {
t.Fatalf("expected cycles")
}
cycle := vErr.Cycles[0]
if cycle[0].Name != "d" {
t.Fatalf("bad: %v", cycle)
}
if cycle[1].Name != "b" {
t.Fatalf("bad: %v", cycle)
if len(vErr.Unreachable) != 1 {
t.Fatalf("expected unreachable")
}
}
func TestGraph_Validate(t *testing.T) {
nodes := ParseNouns(`a -> b
a -> c
b -> d
b -> e
c -> d
c -> e`)
list := NounMapToList(nodes)
g := &Graph{Name: "Test", Nouns: list}
err := g.Validate()
if err != nil {
t.Fatalf("err: %v", err)
if vErr.Unreachable[0].Name != "x" {
t.Fatalf("bad: %v", vErr.Unreachable[0])
}
}

Loading…
Cancel
Save