github.com/replicatedhq/ship@v0.55.0/pkg/lifecycle/render/config/resolve/dep_graph_test.go (about)

     1  package resolve
     2  
     3  import (
     4  	"fmt"
     5  	"testing"
     6  
     7  	"github.com/replicatedhq/libyaml"
     8  	"github.com/replicatedhq/ship/pkg/templates"
     9  	"github.com/replicatedhq/ship/pkg/testing/logger"
    10  	"github.com/spf13/viper"
    11  	"github.com/stretchr/testify/require"
    12  )
    13  
    14  type depGraphTestCase struct {
    15  	dependencies   map[string][]string
    16  	resolveOrder   []string
    17  	expectError    bool   //expect an error fetching head nodes
    18  	expectNotFound string //expect this dependency not to be part of the head nodes
    19  
    20  	name string
    21  }
    22  
    23  func TestDepGraph(t *testing.T) {
    24  	tests := []depGraphTestCase{
    25  		{
    26  			dependencies: map[string][]string{
    27  				"alpha":   {},
    28  				"bravo":   {"alpha"},
    29  				"charlie": {"bravo"},
    30  				"delta":   {"alpha", "charlie"},
    31  				"echo":    {},
    32  			},
    33  			resolveOrder: []string{"alpha", "bravo", "charlie", "delta", "echo"},
    34  			name:         "basic_dependency_chain",
    35  		},
    36  		{
    37  			dependencies: map[string][]string{
    38  				"alpha": {"bravo"},
    39  				"bravo": {"alpha"},
    40  			},
    41  			resolveOrder: []string{"alpha", "bravo"},
    42  			expectError:  true,
    43  			name:         "basic_circle",
    44  		},
    45  		{
    46  			dependencies: map[string][]string{
    47  				"alpha":   {},
    48  				"bravo":   {"alpha"},
    49  				"charlie": {"alpha"},
    50  				"delta":   {"bravo", "charlie"},
    51  				"echo":    {"delta"},
    52  			},
    53  			resolveOrder: []string{"alpha", "bravo", "charlie", "delta", "echo"},
    54  			name:         "basic_forked_chain",
    55  		},
    56  		{
    57  			dependencies: map[string][]string{
    58  				"alpha":   {},
    59  				"bravo":   {"alpha"},
    60  				"charlie": {"alpha"},
    61  				"delta":   {"bravo", "charlie", "foxtrot"},
    62  				"echo":    {"delta"},
    63  				"foxtrot": {},
    64  			},
    65  			resolveOrder:   []string{"alpha", "bravo", "charlie", "delta", "echo", "foxtrot"},
    66  			expectNotFound: "delta",
    67  			name:           "unresolved_dependency",
    68  		},
    69  		{
    70  			dependencies: map[string][]string{
    71  				"alpha":   {},
    72  				"bravo":   {},
    73  				"charlie": {"alpha"},
    74  				"delta":   {"bravo"},
    75  				"echo":    {"delta"},
    76  			},
    77  			resolveOrder: []string{"alpha", "bravo", "charlie", "delta", "echo"},
    78  			name:         "two_chains",
    79  		},
    80  		{
    81  			dependencies: map[string][]string{
    82  				"alpha":   {},
    83  				"bravo":   {"alpha"},
    84  				"charlie": {"alpha", "bravo"},
    85  				"delta":   {"alpha", "bravo", "charlie"},
    86  				"echo":    {"alpha", "bravo", "charlie", "delta"},
    87  				"foxtrot": {"alpha", "bravo", "charlie", "delta", "echo"},
    88  				"golf":    {"alpha", "bravo", "charlie", "delta", "echo", "foxtrot"},
    89  				"hotel":   {"alpha", "bravo", "charlie", "delta", "echo", "foxtrot", "golf"},
    90  				"india":   {"alpha", "bravo", "charlie", "delta", "echo", "foxtrot", "golf", "hotel"},
    91  				"juliet":  {"alpha", "bravo", "charlie", "delta", "echo", "foxtrot", "golf", "hotel", "india"},
    92  				"kilo":    {"alpha", "bravo", "charlie", "delta", "echo", "foxtrot", "golf", "hotel", "india", "juliet"},
    93  				"lima":    {"alpha", "bravo", "charlie", "delta", "echo", "foxtrot", "golf", "hotel", "india", "juliet", "kilo"},
    94  				"mike":    {"alpha", "bravo", "charlie", "delta", "echo", "foxtrot", "golf", "hotel", "india", "juliet", "kilo", "lima"},
    95  			},
    96  			resolveOrder: []string{"alpha", "bravo", "charlie", "delta", "echo", "foxtrot", "golf", "hotel", "india", "juliet", "kilo", "lima", "mike"},
    97  			name:         "pyramid",
    98  		},
    99  	}
   100  	for _, test := range tests {
   101  		t.Run(test.name, func(t *testing.T) {
   102  
   103  			builderBuilder := &templates.BuilderBuilder{
   104  				Logger: &logger.TestLogger{T: t},
   105  				Viper:  viper.New(),
   106  			}
   107  			graph := depGraph{
   108  				BuilderBuilder: builderBuilder,
   109  			}
   110  			for source, deps := range test.dependencies {
   111  				graph.AddNode(source)
   112  				for _, dep := range deps {
   113  					graph.AddDep(source, dep)
   114  				}
   115  			}
   116  			runGraphTests(t, test, graph)
   117  		})
   118  
   119  		t.Run(test.name+"+parse", func(t *testing.T) {
   120  			builderBuilder := &templates.BuilderBuilder{
   121  				Logger: &logger.TestLogger{T: t},
   122  				Viper:  viper.New(),
   123  			}
   124  
   125  			graph := depGraph{
   126  				BuilderBuilder: builderBuilder,
   127  			}
   128  
   129  			groups := buildTestConfigGroups(test.dependencies, "templateStringStart", "templateStringEnd", true)
   130  
   131  			err := graph.ParseConfigGroup(groups)
   132  			require.NoError(t, err)
   133  
   134  			runGraphTests(t, test, graph)
   135  		})
   136  	}
   137  }
   138  
   139  func buildTestConfigGroups(dependencies map[string][]string, prefix string, suffix string, rotate bool) []libyaml.ConfigGroup {
   140  	group := libyaml.ConfigGroup{}
   141  	group.Items = make([]*libyaml.ConfigItem, 0)
   142  	counter := 0
   143  
   144  	templateFuncs := []string{
   145  		"{{repl ConfigOption \"%s\" }}",
   146  		"{{repl ConfigOptionIndex \"%s\" }}",
   147  		"{{repl ConfigOptionData \"%s\" }}",
   148  		"{{repl ConfigOptionEquals \"%s\" \"abc\" }}",
   149  		"{{repl ConfigOptionNotEquals \"%s\" \"xyz\" }}{{repl DoesNotExistFunc }}",
   150  	}
   151  
   152  	if !rotate {
   153  		//use only ConfigOption, not all 5
   154  		templateFuncs = []string{
   155  			"{{repl ConfigOption \"%s\" }}",
   156  		}
   157  	}
   158  
   159  	for source, deps := range dependencies {
   160  		newItem := libyaml.ConfigItem{Type: "text", Name: source}
   161  		depString := prefix
   162  		for i, dep := range deps {
   163  			depString += fmt.Sprintf(templateFuncs[i%len(templateFuncs)], dep)
   164  		}
   165  		depString += suffix
   166  
   167  		if counter%2 == 0 {
   168  			newItem.Value = depString
   169  		} else {
   170  			newItem.Default = depString
   171  		}
   172  		counter++
   173  
   174  		group.Items = append(group.Items, &newItem)
   175  	}
   176  
   177  	return []libyaml.ConfigGroup{group}
   178  }
   179  
   180  func runGraphTests(t *testing.T, test depGraphTestCase, graph depGraph) {
   181  	depLen := len(graph.Dependencies)
   182  	graphCopy, err := graph.Copy()
   183  	require.NoError(t, err)
   184  
   185  	for _, toResolve := range test.resolveOrder {
   186  		available, err := graph.GetHeadNodes()
   187  		if err != nil && test.expectError {
   188  			return
   189  		}
   190  
   191  		require.NoError(t, err, "toResolve: %s", toResolve)
   192  
   193  		if test.expectNotFound != "" && toResolve == test.expectNotFound {
   194  			require.NotContains(t, available, toResolve)
   195  			return
   196  		}
   197  
   198  		require.Contains(t, available, toResolve)
   199  
   200  		graph.ResolveDep(toResolve)
   201  	}
   202  
   203  	available, err := graph.GetHeadNodes()
   204  	require.NoError(t, err)
   205  	require.Empty(t, available)
   206  
   207  	require.False(t, test.expectError, "Did not find expected error")
   208  
   209  	require.Equal(t, depLen, len(graphCopy.Dependencies))
   210  }