github.com/1aal/kubeblocks@v0.0.0-20231107070852-e1c03e598921/pkg/controller/graph/dag_test.go (about)

     1  /*
     2  Copyright (C) 2022-2023 ApeCloud Co., Ltd
     3  
     4  This file is part of KubeBlocks project
     5  
     6  This program is free software: you can redistribute it and/or modify
     7  it under the terms of the GNU Affero General Public License as published by
     8  the Free Software Foundation, either version 3 of the License, or
     9  (at your option) any later version.
    10  
    11  This program is distributed in the hope that it will be useful
    12  but WITHOUT ANY WARRANTY; without even the implied warranty of
    13  MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
    14  GNU Affero General Public License for more details.
    15  
    16  You should have received a copy of the GNU Affero General Public License
    17  along with this program.  If not, see <http://www.gnu.org/licenses/>.
    18  */
    19  
    20  package graph
    21  
    22  import (
    23  	"strings"
    24  	"testing"
    25  )
    26  
    27  func TestAddVertex(t *testing.T) {
    28  	dag := NewDAG()
    29  	added := dag.AddVertex(nil)
    30  	if added {
    31  		t.Error("should return false if add nil vertex")
    32  	}
    33  	v := 6
    34  	added = dag.AddVertex(v)
    35  	if !added {
    36  		t.Error("should return true if add none nil vertex")
    37  	}
    38  	if len(dag.Vertices()) != 1 {
    39  		t.Error("vertex not added")
    40  	}
    41  	if dag.Vertices()[0] != v {
    42  		t.Error("vertex not added")
    43  	}
    44  }
    45  
    46  func TestRemoveVertex(t *testing.T) {
    47  	dag := NewDAG()
    48  	removed := dag.RemoveVertex(nil)
    49  	if !removed {
    50  		t.Error("should return true if removing nil vertex")
    51  	}
    52  	for i := 0; i < 4; i++ {
    53  		dag.AddVertex(i)
    54  	}
    55  	dag.Connect(0, 1)
    56  	dag.Connect(1, 2)
    57  	dag.Connect(1, 3)
    58  	if len(dag.vertices) != 4 {
    59  		t.Error("unexpected vertices", len(dag.vertices))
    60  	}
    61  	if len(dag.edges) != 3 {
    62  		t.Error("unexpected edges", len(dag.edges))
    63  	}
    64  	for i := 3; i >= 0; i-- {
    65  		dag.RemoveVertex(i)
    66  	}
    67  	if len(dag.vertices) != 0 {
    68  		t.Error("unexpected vertices", len(dag.vertices))
    69  	}
    70  	if len(dag.edges) != 0 {
    71  		t.Error("unexpected edges", len(dag.edges))
    72  	}
    73  }
    74  
    75  func TestAddNRemoveEdge(t *testing.T) {
    76  	dag := NewDAG()
    77  	added := dag.AddEdge(RealEdge(nil, nil))
    78  	if added {
    79  		t.Error("should return false if nil edge added")
    80  	}
    81  	v1, v2 := 3, 5
    82  	e1 := RealEdge(v1, v2)
    83  	e2 := RealEdge(v1, v2)
    84  	added = dag.AddEdge(e1)
    85  	if !added {
    86  		t.Errorf("edge %v should be added", e1)
    87  	}
    88  	added = dag.AddEdge(e2)
    89  	if !added {
    90  		t.Errorf("edge %v should be added", e2)
    91  	}
    92  	if len(dag.edges) != 1 {
    93  		t.Error("edge add failed")
    94  	}
    95  	if dag.edges[e1] != e1 {
    96  		t.Error("edge add failed")
    97  	}
    98  
    99  	removed := dag.RemoveEdge(e2)
   100  	if !removed {
   101  		t.Errorf("remove edge %v failed", e2)
   102  	}
   103  	if len(dag.edges) != 0 {
   104  		t.Errorf("remove edge %v failed", e2)
   105  	}
   106  }
   107  
   108  func TestXConnect(t *testing.T) {
   109  	dag := NewDAG()
   110  	v1, v2 := 3, 5
   111  	connected := dag.Connect(nil, v2)
   112  	if connected {
   113  		t.Error("connect nil vertex should return false")
   114  	}
   115  	connected = dag.Connect(v1, v2)
   116  	if !connected {
   117  		t.Errorf("connect %v to %v failed", v1, v2)
   118  	}
   119  	connected = dag.Connect(v1, v2)
   120  	if !connected {
   121  		t.Errorf("connect %v to %v failed", v1, v2)
   122  	}
   123  	if len(dag.edges) != 1 {
   124  		t.Error("connect failed")
   125  	}
   126  	for edge := range dag.edges {
   127  		if edge.From() != v1 || edge.To() != v2 {
   128  			t.Errorf("edge in dag: %v, edge need: %v", edge, RealEdge(v1, v2))
   129  		}
   130  	}
   131  
   132  	v3 := 7
   133  	connected = dag.AddConnect(v1, nil)
   134  	if connected {
   135  		t.Error("AddConnect nil vertex should return false")
   136  	}
   137  	connected = dag.AddConnect(v1, v3)
   138  	if !connected {
   139  		t.Errorf("AddConnect %v to %v should succeed", v1, v3)
   140  	}
   141  	v4 := 9
   142  	connected = dag.AddConnectRoot(v4)
   143  	if connected {
   144  		t.Errorf("AddConnectRoot to %v with nil root should failed", v4)
   145  	}
   146  	dag.AddVertex(v1)
   147  	connected = dag.AddConnectRoot(v4)
   148  	if !connected {
   149  		t.Errorf("AddConnectRoot to %v should succeed", v4)
   150  	}
   151  }
   152  
   153  func TestWalkTopoOrder(t *testing.T) {
   154  	dag := newTestDAG()
   155  
   156  	expected := []int{4, 5, 1, 10, 12, 11, 9, 6, 0, 3, 2, 7, 8}
   157  	walkOrder := make([]int, 0, len(expected))
   158  
   159  	walkFunc := func(v Vertex) error {
   160  		walkOrder = append(walkOrder, v.(int))
   161  		return nil
   162  	}
   163  	if err := dag.WalkReverseTopoOrder(walkFunc, nil); err != nil {
   164  		t.Error(err)
   165  	}
   166  	for i := range expected {
   167  		if walkOrder[i] != expected[i] {
   168  			t.Errorf("unexpected order, index %d\n expected: %v\nactual: %v\n", i, expected, walkOrder)
   169  		}
   170  	}
   171  
   172  	expected = []int{8, 7, 2, 3, 0, 6, 9, 11, 12, 10, 1, 5, 4}
   173  	walkOrder = make([]int, 0, len(expected))
   174  	if err := dag.WalkTopoOrder(walkFunc, nil); err != nil {
   175  		t.Error(err)
   176  	}
   177  	for i := range expected {
   178  		if walkOrder[i] != expected[i] {
   179  			t.Errorf("unexpected order, index %d\n expected: %v\nactual: %v\n", i, expected, walkOrder)
   180  		}
   181  	}
   182  }
   183  
   184  func TestWalkBFS(t *testing.T) {
   185  	dag := newTestDAG()
   186  
   187  	expected := []int{8, 7, 2, 6, 0, 3, 4, 9, 1, 5, 10, 11, 12}
   188  	walkOrder := make([]int, 0, len(expected))
   189  
   190  	walkFunc := func(v Vertex) error {
   191  		walkOrder = append(walkOrder, v.(int))
   192  		return nil
   193  	}
   194  	if err := dag.bfs(walkFunc, less); err != nil {
   195  		t.Error(err)
   196  	}
   197  	for i := range expected {
   198  		if walkOrder[i] != expected[i] {
   199  			t.Errorf("unexpected order, index %d\n expected: %v\nactual: %v\n", i, expected, walkOrder)
   200  		}
   201  	}
   202  }
   203  
   204  func TestValidate(t *testing.T) {
   205  	dag := NewDAG()
   206  	err := dag.validate()
   207  	if err == nil {
   208  		t.Error("nil root not found")
   209  	}
   210  	if !strings.Contains(err.Error(), "no single Root found") {
   211  		t.Error("nil root not found")
   212  	}
   213  	for i := 0; i < 4; i++ {
   214  		dag.AddVertex(i)
   215  	}
   216  	dag.Connect(0, 1)
   217  	dag.Connect(1, 2)
   218  	dag.Connect(2, 3)
   219  	dag.Connect(3, 1)
   220  	err = dag.validate()
   221  	if err == nil {
   222  		t.Error("cycle not found")
   223  	}
   224  	if err.Error() != "cycle found" {
   225  		t.Error("error not as expected")
   226  	}
   227  	dag.Connect(1, 1)
   228  	err = dag.validate()
   229  	if err == nil {
   230  		t.Error("self-cycle not found")
   231  	}
   232  	if err.Error() != "self-cycle found: 1" {
   233  		t.Error("error not as expected")
   234  	}
   235  }
   236  
   237  func TestEquals(t *testing.T) {
   238  	d1 := newTestDAG()
   239  	equal := d1.Equals(nil, less)
   240  	if equal {
   241  		t.Error("should return false if nil other")
   242  	}
   243  	d2 := NewDAG()
   244  	equal = d1.Equals(d2, nil)
   245  	if equal {
   246  		t.Error("should return false if nil less func")
   247  	}
   248  	for i := 0; i < 13; i++ {
   249  		d2.AddVertex(12 - i)
   250  	}
   251  
   252  	// add edges in reverse order
   253  	d2.Connect(1, 5)
   254  	d2.Connect(10, 1)
   255  	d2.Connect(12, 10)
   256  	d2.Connect(3, 0)
   257  	d2.Connect(7, 2)
   258  	d2.Connect(7, 6)
   259  	d2.Connect(6, 9)
   260  	d2.Connect(6, 4)
   261  	d2.Connect(0, 5)
   262  	d2.Connect(5, 4)
   263  	d2.Connect(8, 7)
   264  	d2.Connect(3, 5)
   265  	d2.Connect(9, 11)
   266  	d2.Connect(9, 10)
   267  	d2.Connect(9, 12)
   268  	d2.Connect(11, 12)
   269  	d2.Connect(2, 0)
   270  	d2.Connect(0, 1)
   271  	d2.Connect(0, 6)
   272  	d2.Connect(2, 3)
   273  
   274  	if !d1.Equals(d2, less) {
   275  		t.Error("equals test failed")
   276  	}
   277  
   278  	d1 = NewDAG()
   279  	d2 = NewDAG()
   280  
   281  	d1.AddVertex(0)
   282  	d1.AddVertex(1)
   283  	d1.AddVertex(2)
   284  	d1.AddVertex(3)
   285  	d1.AddVertex(4)
   286  	d2.AddVertex(0)
   287  	d2.AddVertex(2)
   288  	d2.AddVertex(3)
   289  	d2.AddVertex(1)
   290  	d2.AddVertex(4)
   291  
   292  	d1.Connect(0, 1)
   293  	d1.Connect(0, 2)
   294  	d1.Connect(0, 3)
   295  	d1.Connect(0, 4)
   296  	d2.Connect(0, 2)
   297  	d2.Connect(0, 3)
   298  	d2.Connect(0, 4)
   299  	d2.Connect(0, 1)
   300  
   301  	if !d1.Equals(d2, less) {
   302  		t.Error("equals test failed")
   303  	}
   304  }
   305  
   306  func TestMerge(t *testing.T) {
   307  	dag1 := NewDAG()
   308  	dag2 := NewDAG()
   309  	v1, v2, v3, v4, v5, v6 := 1, 2, 3, 4, 5, 6
   310  	dag1.AddVertex(v1)
   311  	dag1.AddVertex(v2)
   312  	dag1.Connect(v1, v2)
   313  	dag2.AddVertex(v3)
   314  	dag2.AddVertex(v4)
   315  	dag2.AddVertex(v5)
   316  	dag2.AddVertex(v6)
   317  	dag2.Connect(v2, v3)
   318  	dag2.Connect(v2, v4)
   319  	dag2.Connect(v3, v5)
   320  	dag2.Connect(v4, v5)
   321  	dag2.Connect(v6, v4)
   322  	dag2.Connect(v6, v5)
   323  
   324  	dagExpected := NewDAG()
   325  	dagExpected.AddVertex(v1)
   326  	dagExpected.AddVertex(v2)
   327  	dagExpected.AddVertex(v3)
   328  	dagExpected.AddVertex(v4)
   329  	dagExpected.AddVertex(v5)
   330  	dagExpected.AddVertex(v6)
   331  	dagExpected.Connect(v1, v2)
   332  	dagExpected.Connect(v1, v6)
   333  	dagExpected.Connect(v2, v3)
   334  	dagExpected.Connect(v2, v4)
   335  	dagExpected.Connect(v3, v5)
   336  	dagExpected.Connect(v4, v5)
   337  	dagExpected.Connect(v6, v4)
   338  	dagExpected.Connect(v6, v5)
   339  
   340  	dag1.Merge(dag2)
   341  	if !dag1.Equals(dagExpected, less) {
   342  		t.Errorf("dag merge error, expected: %v, actual: %v", dagExpected, dag1)
   343  	}
   344  }
   345  
   346  func TestString(t *testing.T) {
   347  	dag := newTestDAG()
   348  	str := dag.String()
   349  	expectedOrder := []string{"|", "4", "5", "1", "10", "12", "11", "9", "6", "0", "3", "2", "7", "8"}
   350  	expectedStr := strings.Join(expectedOrder, "->")
   351  	if str != expectedStr {
   352  		t.Errorf("dag string error, expected: %s, actual: %s", expectedStr, str)
   353  	}
   354  }
   355  
   356  func less(v1, v2 Vertex) bool {
   357  	val1, _ := v1.(int)
   358  	val2, _ := v2.(int)
   359  	return val1 < val2
   360  }
   361  
   362  func newTestDAG() *DAG {
   363  	dag := NewDAG()
   364  	for i := 0; i < 13; i++ {
   365  		dag.AddVertex(i)
   366  	}
   367  	dag.Connect(2, 3)
   368  	dag.Connect(0, 6)
   369  	dag.Connect(0, 1)
   370  	dag.Connect(2, 0)
   371  	dag.Connect(11, 12)
   372  	dag.Connect(9, 12)
   373  	dag.Connect(9, 10)
   374  	dag.Connect(9, 11)
   375  	dag.Connect(3, 5)
   376  	dag.Connect(8, 7)
   377  	dag.Connect(5, 4)
   378  	dag.Connect(0, 5)
   379  	dag.Connect(6, 4)
   380  	dag.Connect(6, 9)
   381  	dag.Connect(7, 6)
   382  	dag.Connect(7, 2)
   383  	dag.Connect(3, 0)
   384  	dag.Connect(12, 10)
   385  	dag.Connect(10, 1)
   386  	dag.Connect(1, 5)
   387  	return dag
   388  }