cuelang.org/go@v0.13.0/internal/core/toposort/scc_test.go (about)

     1  // Copyright 2024 CUE Authors
     2  //
     3  // Licensed under the Apache License, Version 2.0 (the "License");
     4  // you may not use this file except in compliance with the License.
     5  // You may obtain a copy of the License at
     6  //
     7  //     http://www.apache.org/licenses/LICENSE-2.0
     8  //
     9  // Unless required by applicable law or agreed to in writing, software
    10  // distributed under the License is distributed on an "AS IS" BASIS,
    11  // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
    12  // See the License for the specific language governing permissions and
    13  // limitations under the License.
    14  
    15  package toposort_test
    16  
    17  import (
    18  	"slices"
    19  	"testing"
    20  
    21  	"cuelang.org/go/internal/core/adt"
    22  	"cuelang.org/go/internal/core/runtime"
    23  	"cuelang.org/go/internal/core/toposort"
    24  )
    25  
    26  func TestStronglyConnectedComponents(t *testing.T) {
    27  	type TestCase struct {
    28  		name     string
    29  		inputs   [][]string
    30  		expected [][]string
    31  	}
    32  
    33  	a, b, c, d, e, f, g := "a", "b", "c", "d", "e", "f", "g"
    34  
    35  	testCases := []TestCase{
    36  		{
    37  			name:     "one",
    38  			inputs:   [][]string{{a}},
    39  			expected: [][]string{{a}},
    40  		},
    41  		{
    42  			name:     "independent",
    43  			inputs:   [][]string{{a}, {b}, {c}},
    44  			expected: [][]string{{a}, {b}, {c}},
    45  		},
    46  		{
    47  			name:     "simple chain two",
    48  			inputs:   [][]string{{c, b}, {d, a}},
    49  			expected: [][]string{{a}, {b}, {c}, {d}},
    50  		},
    51  		{
    52  			name:     "simple chain three",
    53  			inputs:   [][]string{{c, b}, {d, a}, {f, e}},
    54  			expected: [][]string{{a}, {b}, {c}, {d}, {e}, {f}},
    55  		},
    56  		{
    57  			name:     "smallest cycle",
    58  			inputs:   [][]string{{g, f}, {f, g}},
    59  			expected: [][]string{{f, g}},
    60  		},
    61  		{
    62  			name:     "smallest cycle with prefix",
    63  			inputs:   [][]string{{a, b, g, f}, {f, g}},
    64  			expected: [][]string{{a}, {b}, {f, g}},
    65  		},
    66  		{
    67  			name:     "smallest cycle with suffix",
    68  			inputs:   [][]string{{g, f, a, b}, {f, g}},
    69  			expected: [][]string{{a}, {b}, {f, g}},
    70  		},
    71  		{
    72  			name:     "smallest cycle with prefices",
    73  			inputs:   [][]string{{a, b, g, f}, {c, d, f, g}},
    74  			expected: [][]string{{a}, {b}, {c}, {d}, {f, g}},
    75  		},
    76  		{
    77  			name:     "smallest cycle with suffices",
    78  			inputs:   [][]string{{g, f, a, b}, {f, g, c, d}},
    79  			expected: [][]string{{a}, {b}, {c}, {d}, {f, g}},
    80  		},
    81  		{
    82  			name:     "smallest cycle with prefices and sufficies",
    83  			inputs:   [][]string{{a, g, f}, {b, f}, {g, c}, {f, g, d}},
    84  			expected: [][]string{{a}, {b}, {c}, {d}, {f, g}},
    85  		},
    86  		{
    87  			name:     "nested cycles",
    88  			inputs:   [][]string{{b, c}, {e, c, b, d}, {d, f, a, e}, {a, f}},
    89  			expected: [][]string{{a, b, c, d, e, f}},
    90  		},
    91  		{
    92  			name:     "cycles through common node",
    93  			inputs:   [][]string{{a, b, c}, {c, a}, {f, b, g}, {g, f}},
    94  			expected: [][]string{{a, b, c, f, g}},
    95  		},
    96  		{
    97  			name:     "split",
    98  			inputs:   [][]string{{a, b, c}, {a, d, e}, {c, b}, {e, d}},
    99  			expected: [][]string{{a}, {b, c}, {d, e}},
   100  		},
   101  	}
   102  
   103  	index := runtime.New()
   104  
   105  	for _, tc := range testCases {
   106  		t.Run(tc.name, func(t *testing.T) {
   107  			testAllPermutations(t, index, tc.inputs,
   108  				func(t *testing.T, perm [][]adt.Feature, graph *toposort.Graph) {
   109  					components := graph.StronglyConnectedComponents()
   110  
   111  					componentsNames := make([][]string, len(components))
   112  					for i, component := range components {
   113  						fs := component.Nodes.Features()
   114  						names := make([]string, len(fs))
   115  						for j, f := range fs {
   116  							names[j] = f.StringValue(index)
   117  						}
   118  						slices.Sort(names)
   119  						componentsNames[i] = names
   120  					}
   121  					slices.SortFunc(componentsNames, slices.Compare)
   122  
   123  					if !slices.EqualFunc(componentsNames, tc.expected, slices.Equal) {
   124  						t.Fatalf(`
   125  For permutation: %v
   126         Expected: %v
   127              Got: %v`,
   128  							permutationNames(index, perm),
   129  							tc.expected, componentsNames)
   130  					}
   131  
   132  					seen := make(map[*toposort.StronglyConnectedComponent]struct{})
   133  					// by definition, the graph of components (the
   134  					// "condensation graph") must be a DAG. I.e. no cycles the
   135  					// components are already sorted in a topological ordering
   136  					for _, component := range components {
   137  						if _, found := seen[component]; found {
   138  							t.Fatalf(`
   139  For permutation: %v
   140    List of components contains the same component twice: %v`,
   141  								permutationNames(index, perm), component)
   142  						}
   143  						seen[component] = struct{}{}
   144  						for _, next := range component.Outgoing {
   145  							if _, found := seen[next]; found {
   146  								t.Fatalf(`
   147  For permutation: %v
   148    Either the list of components is not topologically sorted, or the condensation graph is not a DAG!
   149        Component: %v
   150         Outgoing: %v`,
   151  									permutationNames(index, perm),
   152  									component.Nodes, next.Nodes)
   153  							}
   154  						}
   155  					}
   156  				})
   157  		})
   158  	}
   159  }