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" "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 { type Graph struct {
Name string Name string
Meta interface{} Meta interface{}
@ -19,58 +19,8 @@ type Graph struct {
Root *Noun 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 // ValidateError implements the Error interface but provides
// additional information on a validation error // additional information on a validation error.
type ValidateError struct { type ValidateError struct {
// If set, then the graph is missing a single root, on which // If set, then the graph is missing a single root, on which
// there are no depdendencies // there are no depdendencies
@ -89,6 +39,31 @@ func (v *ValidateError) Error() string {
return "The depedency graph is not valid" 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 // CheckConstraints walks the graph and ensures that all
// user imposed constraints are satisfied. // user imposed constraints are satisfied.
func (g *Graph) CheckConstraints() error { func (g *Graph) CheckConstraints() error {
@ -129,27 +104,52 @@ func (g *Graph) CheckConstraints() error {
return nil return nil
} }
// ConstraintError is used to return detailed violation // Validate is used to ensure that a few properties of the graph are not violated:
// information from CheckConstraints // 1) There must be a single "root", or source on which nothing depends.
type ConstraintError struct { // 2) All nouns in the graph must be reachable from the root
Violations []*Violation // 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 { // Create a validate erro
return fmt.Sprintf("%d constraint violations", len(c.Violations)) vErr := &ValidateError{}
}
// Violation is used to pass along information about // Search for all the sources, if we have only 1, it must be the root
// a constraint violation if sources := digraph.Sources(nodes); len(sources) != 1 {
type Violation struct { vErr.MissingRoot = true
Source *Noun goto CHECK_CYCLES
Target *Noun } else {
Dependency *Dependency g.Root = sources[0].(*Noun)
Constraint Constraint }
Err error
}
func (v *Violation) Error() string { // Check reachability
return fmt.Sprintf("Constraint %v between %v and %v violated: %v", if unreached := digraph.Unreachable(g.Root, nodes); len(unreached) > 0 {
v.Constraint, v.Source, v.Target, v.Err) 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 return list
} }
func TestGraph_Validate_NoRoot(t *testing.T) { func TestGraph_Validate(t *testing.T) {
nodes := ParseNouns(`a -> b 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) list := NounMapToList(nodes)
g := &Graph{Name: "Test", Nouns: list} g := &Graph{Name: "Test", Nouns: list}
@ -69,8 +86,16 @@ b -> a`)
t.Fatalf("expected validate error") t.Fatalf("expected validate error")
} }
if !vErr.MissingRoot { if len(vErr.Cycles) != 1 {
t.Fatalf("expected missing root") 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 nodes := ParseNouns(`a -> b
a -> c b -> a`)
b -> d
x -> x`)
list := NounMapToList(nodes) list := NounMapToList(nodes)
g := &Graph{Name: "Test", Nouns: list} g := &Graph{Name: "Test", Nouns: list}
@ -113,20 +136,16 @@ x -> x`)
t.Fatalf("expected validate error") t.Fatalf("expected validate error")
} }
if len(vErr.Unreachable) != 1 { if !vErr.MissingRoot {
t.Fatalf("expected unreachable") t.Fatalf("expected missing root")
}
if vErr.Unreachable[0].Name != "x" {
t.Fatalf("bad: %v", vErr.Unreachable[0])
} }
} }
func TestGraph_Validate_Cycle(t *testing.T) { func TestGraph_Validate_Unreachable(t *testing.T) {
nodes := ParseNouns(`a -> b nodes := ParseNouns(`a -> b
a -> c a -> c
b -> d b -> d
d -> b`) x -> x`)
list := NounMapToList(nodes) list := NounMapToList(nodes)
g := &Graph{Name: "Test", Nouns: list} g := &Graph{Name: "Test", Nouns: list}
@ -140,32 +159,12 @@ d -> b`)
t.Fatalf("expected validate error") t.Fatalf("expected validate error")
} }
if len(vErr.Cycles) != 1 { if len(vErr.Unreachable) != 1 {
t.Fatalf("expected cycles") t.Fatalf("expected unreachable")
}
cycle := vErr.Cycles[0]
if cycle[0].Name != "d" {
t.Fatalf("bad: %v", cycle)
}
if cycle[1].Name != "b" {
t.Fatalf("bad: %v", cycle)
} }
}
func TestGraph_Validate(t *testing.T) { if vErr.Unreachable[0].Name != "x" {
nodes := ParseNouns(`a -> b t.Fatalf("bad: %v", vErr.Unreachable[0])
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)
} }
} }

Loading…
Cancel
Save