github.com/jsoriano/terraform@v0.6.7-0.20151026070445-8b70867fdd95/depgraph/graph_test.go (about)

     1  package depgraph
     2  
     3  import (
     4  	"fmt"
     5  	"reflect"
     6  	"sort"
     7  	"strings"
     8  	"sync"
     9  	"testing"
    10  )
    11  
    12  // ParseNouns is used to parse a string in the format of:
    13  // a -> b ; edge name
    14  // b -> c
    15  // Into a series of nouns and dependencies
    16  func ParseNouns(s string) map[string]*Noun {
    17  	lines := strings.Split(s, "\n")
    18  	nodes := make(map[string]*Noun)
    19  	for _, line := range lines {
    20  		var edgeName string
    21  		if idx := strings.Index(line, ";"); idx >= 0 {
    22  			edgeName = strings.Trim(line[idx+1:], " \t\r\n")
    23  			line = line[:idx]
    24  		}
    25  		parts := strings.SplitN(line, "->", 2)
    26  		if len(parts) != 2 {
    27  			continue
    28  		}
    29  		head_name := strings.Trim(parts[0], " \t\r\n")
    30  		tail_name := strings.Trim(parts[1], " \t\r\n")
    31  		head := nodes[head_name]
    32  		if head == nil {
    33  			head = &Noun{Name: head_name}
    34  			nodes[head_name] = head
    35  		}
    36  		tail := nodes[tail_name]
    37  		if tail == nil {
    38  			tail = &Noun{Name: tail_name}
    39  			nodes[tail_name] = tail
    40  		}
    41  		edge := &Dependency{
    42  			Name:   edgeName,
    43  			Source: head,
    44  			Target: tail,
    45  		}
    46  		head.Deps = append(head.Deps, edge)
    47  	}
    48  	return nodes
    49  }
    50  
    51  func NounMapToList(m map[string]*Noun) []*Noun {
    52  	list := make([]*Noun, 0, len(m))
    53  	for _, n := range m {
    54  		list = append(list, n)
    55  	}
    56  	return list
    57  }
    58  
    59  func TestGraph_Noun(t *testing.T) {
    60  	nodes := ParseNouns(`a -> b
    61  a -> c
    62  b -> d
    63  b -> e
    64  c -> d
    65  c -> e`)
    66  
    67  	g := &Graph{
    68  		Name:  "Test",
    69  		Nouns: NounMapToList(nodes),
    70  	}
    71  
    72  	n := g.Noun("a")
    73  	if n == nil {
    74  		t.Fatal("should not be nil")
    75  	}
    76  	if n.Name != "a" {
    77  		t.Fatalf("bad: %#v", n)
    78  	}
    79  }
    80  
    81  func TestGraph_String(t *testing.T) {
    82  	nodes := ParseNouns(`a -> b
    83  a -> c
    84  b -> d
    85  b -> e
    86  c -> d
    87  c -> e`)
    88  
    89  	g := &Graph{
    90  		Name:  "Test",
    91  		Nouns: NounMapToList(nodes),
    92  		Root:  nodes["a"],
    93  	}
    94  	actual := g.String()
    95  
    96  	expected := `
    97  root: a
    98  a
    99    a -> b
   100    a -> c
   101  b
   102    b -> d
   103    b -> e
   104  c
   105    c -> d
   106    c -> e
   107  d
   108  e
   109  `
   110  
   111  	actual = strings.TrimSpace(actual)
   112  	expected = strings.TrimSpace(expected)
   113  	if actual != expected {
   114  		t.Fatalf("bad:\n%s\n!=\n%s", actual, expected)
   115  	}
   116  }
   117  
   118  func TestGraph_Validate(t *testing.T) {
   119  	nodes := ParseNouns(`a -> b
   120  a -> c
   121  b -> d
   122  b -> e
   123  c -> d
   124  c -> e`)
   125  	list := NounMapToList(nodes)
   126  
   127  	g := &Graph{Name: "Test", Nouns: list}
   128  	if err := g.Validate(); err != nil {
   129  		t.Fatalf("err: %v", err)
   130  	}
   131  }
   132  
   133  func TestGraph_Validate_Cycle(t *testing.T) {
   134  	nodes := ParseNouns(`a -> b
   135  a -> c
   136  b -> d
   137  d -> b`)
   138  	list := NounMapToList(nodes)
   139  
   140  	g := &Graph{Name: "Test", Nouns: list}
   141  	err := g.Validate()
   142  	if err == nil {
   143  		t.Fatalf("expected err")
   144  	}
   145  
   146  	vErr, ok := err.(*ValidateError)
   147  	if !ok {
   148  		t.Fatalf("expected validate error")
   149  	}
   150  
   151  	if len(vErr.Cycles) != 1 {
   152  		t.Fatalf("expected cycles")
   153  	}
   154  
   155  	cycle := vErr.Cycles[0]
   156  	cycleNodes := make([]string, len(cycle))
   157  	for i, c := range cycle {
   158  		cycleNodes[i] = c.Name
   159  	}
   160  	sort.Strings(cycleNodes)
   161  
   162  	if cycleNodes[0] != "b" {
   163  		t.Fatalf("bad: %v", cycle)
   164  	}
   165  	if cycleNodes[1] != "d" {
   166  		t.Fatalf("bad: %v", cycle)
   167  	}
   168  }
   169  
   170  func TestGraph_Validate_MultiRoot(t *testing.T) {
   171  	nodes := ParseNouns(`a -> b
   172  c -> d`)
   173  	list := NounMapToList(nodes)
   174  
   175  	g := &Graph{Name: "Test", Nouns: list}
   176  	err := g.Validate()
   177  	if err == nil {
   178  		t.Fatalf("expected err")
   179  	}
   180  
   181  	vErr, ok := err.(*ValidateError)
   182  	if !ok {
   183  		t.Fatalf("expected validate error")
   184  	}
   185  
   186  	if !vErr.MissingRoot {
   187  		t.Fatalf("expected missing root")
   188  	}
   189  }
   190  
   191  func TestGraph_Validate_NoRoot(t *testing.T) {
   192  	nodes := ParseNouns(`a -> b
   193  b -> a`)
   194  	list := NounMapToList(nodes)
   195  
   196  	g := &Graph{Name: "Test", Nouns: list}
   197  	err := g.Validate()
   198  	if err == nil {
   199  		t.Fatalf("expected err")
   200  	}
   201  
   202  	vErr, ok := err.(*ValidateError)
   203  	if !ok {
   204  		t.Fatalf("expected validate error")
   205  	}
   206  
   207  	if !vErr.MissingRoot {
   208  		t.Fatalf("expected missing root")
   209  	}
   210  }
   211  
   212  func TestGraph_Validate_Unreachable(t *testing.T) {
   213  	nodes := ParseNouns(`a -> b
   214  a -> c
   215  b -> d
   216  x -> x`)
   217  	list := NounMapToList(nodes)
   218  
   219  	g := &Graph{Name: "Test", Nouns: list}
   220  	err := g.Validate()
   221  	if err == nil {
   222  		t.Fatalf("expected err")
   223  	}
   224  
   225  	vErr, ok := err.(*ValidateError)
   226  	if !ok {
   227  		t.Fatalf("expected validate error")
   228  	}
   229  
   230  	if len(vErr.Unreachable) != 1 {
   231  		t.Fatalf("expected unreachable")
   232  	}
   233  
   234  	if vErr.Unreachable[0].Name != "x" {
   235  		t.Fatalf("bad: %v", vErr.Unreachable[0])
   236  	}
   237  }
   238  
   239  type VersionMeta int
   240  type VersionConstraint struct {
   241  	Min int
   242  	Max int
   243  }
   244  
   245  func (v *VersionConstraint) Satisfied(head, tail *Noun) (bool, error) {
   246  	vers := int(tail.Meta.(VersionMeta))
   247  	if vers < v.Min {
   248  		return false, fmt.Errorf("version %d below minimum %d",
   249  			vers, v.Min)
   250  	} else if vers > v.Max {
   251  		return false, fmt.Errorf("version %d above maximum %d",
   252  			vers, v.Max)
   253  	}
   254  	return true, nil
   255  }
   256  
   257  func (v *VersionConstraint) String() string {
   258  	return "version"
   259  }
   260  
   261  func TestGraph_ConstraintViolation(t *testing.T) {
   262  	nodes := ParseNouns(`a -> b
   263  a -> c
   264  b -> d
   265  b -> e
   266  c -> d
   267  c -> e`)
   268  	list := NounMapToList(nodes)
   269  
   270  	// Add a version constraint
   271  	vers := &VersionConstraint{1, 3}
   272  
   273  	// Introduce some constraints
   274  	depB := nodes["a"].Deps[0]
   275  	depB.Constraints = []Constraint{vers}
   276  	depC := nodes["a"].Deps[1]
   277  	depC.Constraints = []Constraint{vers}
   278  
   279  	// Add some versions
   280  	nodes["b"].Meta = VersionMeta(0)
   281  	nodes["c"].Meta = VersionMeta(4)
   282  
   283  	g := &Graph{Name: "Test", Nouns: list}
   284  	err := g.Validate()
   285  	if err != nil {
   286  		t.Fatalf("err: %v", err)
   287  	}
   288  
   289  	err = g.CheckConstraints()
   290  	if err == nil {
   291  		t.Fatalf("Expected err")
   292  	}
   293  
   294  	cErr, ok := err.(*ConstraintError)
   295  	if !ok {
   296  		t.Fatalf("expected constraint error")
   297  	}
   298  
   299  	if len(cErr.Violations) != 2 {
   300  		t.Fatalf("expected 2 violations: %v", cErr)
   301  	}
   302  
   303  	if cErr.Violations[0].Error() != "Constraint version between a and b violated: version 0 below minimum 1" {
   304  		t.Fatalf("err: %v", cErr.Violations[0])
   305  	}
   306  
   307  	if cErr.Violations[1].Error() != "Constraint version between a and c violated: version 4 above maximum 3" {
   308  		t.Fatalf("err: %v", cErr.Violations[1])
   309  	}
   310  }
   311  
   312  func TestGraph_Constraint_NoViolation(t *testing.T) {
   313  	nodes := ParseNouns(`a -> b
   314  a -> c
   315  b -> d
   316  b -> e
   317  c -> d
   318  c -> e`)
   319  	list := NounMapToList(nodes)
   320  
   321  	// Add a version constraint
   322  	vers := &VersionConstraint{1, 3}
   323  
   324  	// Introduce some constraints
   325  	depB := nodes["a"].Deps[0]
   326  	depB.Constraints = []Constraint{vers}
   327  	depC := nodes["a"].Deps[1]
   328  	depC.Constraints = []Constraint{vers}
   329  
   330  	// Add some versions
   331  	nodes["b"].Meta = VersionMeta(2)
   332  	nodes["c"].Meta = VersionMeta(3)
   333  
   334  	g := &Graph{Name: "Test", Nouns: list}
   335  	err := g.Validate()
   336  	if err != nil {
   337  		t.Fatalf("err: %v", err)
   338  	}
   339  
   340  	err = g.CheckConstraints()
   341  	if err != nil {
   342  		t.Fatalf("err: %v", err)
   343  	}
   344  }
   345  
   346  func TestGraphWalk(t *testing.T) {
   347  	nodes := ParseNouns(`a -> b
   348  a -> c
   349  b -> d
   350  b -> e
   351  c -> d
   352  c -> e`)
   353  	list := NounMapToList(nodes)
   354  	g := &Graph{Name: "Test", Nouns: list}
   355  	if err := g.Validate(); err != nil {
   356  		t.Fatalf("err: %s", err)
   357  	}
   358  
   359  	var namesLock sync.Mutex
   360  	names := make([]string, 0, 0)
   361  	err := g.Walk(func(n *Noun) error {
   362  		namesLock.Lock()
   363  		defer namesLock.Unlock()
   364  		names = append(names, n.Name)
   365  		return nil
   366  	})
   367  	if err != nil {
   368  		t.Fatalf("err: %s", err)
   369  	}
   370  
   371  	expected := [][]string{
   372  		{"e", "d", "c", "b", "a"},
   373  		{"e", "d", "b", "c", "a"},
   374  		{"d", "e", "c", "b", "a"},
   375  		{"d", "e", "b", "c", "a"},
   376  	}
   377  	found := false
   378  	for _, expect := range expected {
   379  		if reflect.DeepEqual(expect, names) {
   380  			found = true
   381  			break
   382  		}
   383  	}
   384  	if !found {
   385  		t.Fatalf("bad: %#v", names)
   386  	}
   387  }
   388  
   389  func TestGraphWalk_error(t *testing.T) {
   390  	nodes := ParseNouns(`a -> b
   391  b -> c
   392  a -> d
   393  a -> e
   394  e -> f
   395  f -> g
   396  g -> h`)
   397  	list := NounMapToList(nodes)
   398  	g := &Graph{Name: "Test", Nouns: list}
   399  	if err := g.Validate(); err != nil {
   400  		t.Fatalf("err: %s", err)
   401  	}
   402  
   403  	// We repeat this a lot because sometimes timing causes
   404  	// a false positive.
   405  	for i := 0; i < 100; i++ {
   406  		var lock sync.Mutex
   407  		var walked []string
   408  		err := g.Walk(func(n *Noun) error {
   409  			lock.Lock()
   410  			defer lock.Unlock()
   411  
   412  			walked = append(walked, n.Name)
   413  
   414  			if n.Name == "b" {
   415  				return fmt.Errorf("foo")
   416  			}
   417  
   418  			return nil
   419  		})
   420  		if err == nil {
   421  			t.Fatal("should error")
   422  		}
   423  
   424  		sort.Strings(walked)
   425  
   426  		expected := []string{"b", "c", "d", "e", "f", "g", "h"}
   427  		if !reflect.DeepEqual(walked, expected) {
   428  			t.Fatalf("bad: %#v", walked)
   429  		}
   430  	}
   431  }
   432  
   433  func TestGraph_DependsOn(t *testing.T) {
   434  	nodes := ParseNouns(`a -> b
   435  a -> c
   436  b -> d
   437  b -> e
   438  c -> d
   439  c -> e`)
   440  
   441  	g := &Graph{
   442  		Name:  "Test",
   443  		Nouns: NounMapToList(nodes),
   444  	}
   445  
   446  	dNoun := g.Noun("d")
   447  	incoming := g.DependsOn(dNoun)
   448  
   449  	if len(incoming) != 2 {
   450  		t.Fatalf("bad: %#v", incoming)
   451  	}
   452  
   453  	var hasB, hasC bool
   454  	for _, in := range incoming {
   455  		switch in.Name {
   456  		case "b":
   457  			hasB = true
   458  		case "c":
   459  			hasC = true
   460  		default:
   461  			t.Fatalf("Bad: %#v", in)
   462  		}
   463  	}
   464  	if !hasB || !hasC {
   465  		t.Fatalf("missing incoming edge")
   466  	}
   467  }