github.com/operator-framework/operator-lifecycle-manager@v0.30.0/pkg/controller/registry/resolver/solver/solve_test.go (about)

     1  package solver
     2  
     3  import (
     4  	"bytes"
     5  	"context"
     6  	"fmt"
     7  	"reflect"
     8  	"sort"
     9  	"testing"
    10  
    11  	"github.com/stretchr/testify/assert"
    12  )
    13  
    14  type TestVariable struct {
    15  	identifier  Identifier
    16  	constraints []Constraint
    17  }
    18  
    19  func (i TestVariable) Identifier() Identifier {
    20  	return i.identifier
    21  }
    22  
    23  func (i TestVariable) Constraints() []Constraint {
    24  	return i.constraints
    25  }
    26  
    27  func (i TestVariable) GoString() string {
    28  	return fmt.Sprintf("%q", i.Identifier())
    29  }
    30  
    31  func variable(id Identifier, constraints ...Constraint) Variable {
    32  	return TestVariable{
    33  		identifier:  id,
    34  		constraints: constraints,
    35  	}
    36  }
    37  
    38  func TestNotSatisfiableError(t *testing.T) {
    39  	type tc struct {
    40  		Name   string
    41  		Error  NotSatisfiable
    42  		String string
    43  	}
    44  
    45  	for _, tt := range []tc{
    46  		{
    47  			Name:   "nil",
    48  			String: "constraints not satisfiable",
    49  		},
    50  		{
    51  			Name:   "empty",
    52  			String: "constraints not satisfiable",
    53  			Error:  NotSatisfiable{},
    54  		},
    55  		{
    56  			Name: "single failure",
    57  			Error: NotSatisfiable{
    58  				AppliedConstraint{
    59  					Variable:   variable("a", Mandatory()),
    60  					Constraint: Mandatory(),
    61  				},
    62  			},
    63  			String: fmt.Sprintf("constraints not satisfiable: %s",
    64  				Mandatory().String("a")),
    65  		},
    66  		{
    67  			Name: "multiple failures",
    68  			Error: NotSatisfiable{
    69  				AppliedConstraint{
    70  					Variable:   variable("a", Mandatory()),
    71  					Constraint: Mandatory(),
    72  				},
    73  				AppliedConstraint{
    74  					Variable:   variable("b", Prohibited()),
    75  					Constraint: Prohibited(),
    76  				},
    77  			},
    78  			String: fmt.Sprintf("constraints not satisfiable: %s, %s",
    79  				Mandatory().String("a"), Prohibited().String("b")),
    80  		},
    81  	} {
    82  		t.Run(tt.Name, func(t *testing.T) {
    83  			assert.Equal(t, tt.String, tt.Error.Error())
    84  		})
    85  	}
    86  }
    87  
    88  func TestSolve(t *testing.T) {
    89  	type tc struct {
    90  		Name      string
    91  		Variables []Variable
    92  		Installed []Identifier
    93  		Error     error
    94  	}
    95  
    96  	for _, tt := range []tc{
    97  		{
    98  			Name: "no variables",
    99  		},
   100  		{
   101  			Name:      "unnecessary variable is not installed",
   102  			Variables: []Variable{variable("a")},
   103  		},
   104  		{
   105  			Name:      "single mandatory variable is installed",
   106  			Variables: []Variable{variable("a", Mandatory())},
   107  			Installed: []Identifier{"a"},
   108  		},
   109  		{
   110  			Name:      "both mandatory and prohibited produce error",
   111  			Variables: []Variable{variable("a", Mandatory(), Prohibited())},
   112  			Error: NotSatisfiable{
   113  				{
   114  					Variable:   variable("a", Mandatory(), Prohibited()),
   115  					Constraint: Mandatory(),
   116  				},
   117  				{
   118  					Variable:   variable("a", Mandatory(), Prohibited()),
   119  					Constraint: Prohibited(),
   120  				},
   121  			},
   122  		},
   123  		{
   124  			Name: "dependency is installed",
   125  			Variables: []Variable{
   126  				variable("a"),
   127  				variable("b", Mandatory(), Dependency("a")),
   128  			},
   129  			Installed: []Identifier{"a", "b"},
   130  		},
   131  		{
   132  			Name: "transitive dependency is installed",
   133  			Variables: []Variable{
   134  				variable("a"),
   135  				variable("b", Dependency("a")),
   136  				variable("c", Mandatory(), Dependency("b")),
   137  			},
   138  			Installed: []Identifier{"a", "b", "c"},
   139  		},
   140  		{
   141  			Name: "both dependencies are installed",
   142  			Variables: []Variable{
   143  				variable("a"),
   144  				variable("b"),
   145  				variable("c", Mandatory(), Dependency("a"), Dependency("b")),
   146  			},
   147  			Installed: []Identifier{"a", "b", "c"},
   148  		},
   149  		{
   150  			Name: "solution with first dependency is selected",
   151  			Variables: []Variable{
   152  				variable("a"),
   153  				variable("b", Conflict("a")),
   154  				variable("c", Mandatory(), Dependency("a", "b")),
   155  			},
   156  			Installed: []Identifier{"a", "c"},
   157  		},
   158  		{
   159  			Name: "solution with only first dependency is selected",
   160  			Variables: []Variable{
   161  				variable("a"),
   162  				variable("b"),
   163  				variable("c", Mandatory(), Dependency("a", "b")),
   164  			},
   165  			Installed: []Identifier{"a", "c"},
   166  		},
   167  		{
   168  			Name: "solution with first dependency is selected (reverse)",
   169  			Variables: []Variable{
   170  				variable("a"),
   171  				variable("b", Conflict("a")),
   172  				variable("c", Mandatory(), Dependency("b", "a")),
   173  			},
   174  			Installed: []Identifier{"b", "c"},
   175  		},
   176  		{
   177  			Name: "two mandatory but conflicting packages",
   178  			Variables: []Variable{
   179  				variable("a", Mandatory()),
   180  				variable("b", Mandatory(), Conflict("a")),
   181  			},
   182  			Error: NotSatisfiable{
   183  				{
   184  					Variable:   variable("a", Mandatory()),
   185  					Constraint: Mandatory(),
   186  				},
   187  				{
   188  					Variable:   variable("b", Mandatory(), Conflict("a")),
   189  					Constraint: Mandatory(),
   190  				},
   191  				{
   192  					Variable:   variable("b", Mandatory(), Conflict("a")),
   193  					Constraint: Conflict("a"),
   194  				},
   195  			},
   196  		},
   197  		{
   198  			Name: "irrelevant dependencies don't influence search order",
   199  			Variables: []Variable{
   200  				variable("a", Dependency("x", "y")),
   201  				variable("b", Mandatory(), Dependency("y", "x")),
   202  				variable("x"),
   203  				variable("y"),
   204  			},
   205  			Installed: []Identifier{"b", "y"},
   206  		},
   207  		{
   208  			Name: "cardinality constraint prevents resolution",
   209  			Variables: []Variable{
   210  				variable("a", Mandatory(), Dependency("x", "y"), AtMost(1, "x", "y")),
   211  				variable("x", Mandatory()),
   212  				variable("y", Mandatory()),
   213  			},
   214  			Error: NotSatisfiable{
   215  				{
   216  					Variable:   variable("a", Mandatory(), Dependency("x", "y"), AtMost(1, "x", "y")),
   217  					Constraint: AtMost(1, "x", "y"),
   218  				},
   219  				{
   220  					Variable:   variable("x", Mandatory()),
   221  					Constraint: Mandatory(),
   222  				},
   223  				{
   224  					Variable:   variable("y", Mandatory()),
   225  					Constraint: Mandatory(),
   226  				},
   227  			},
   228  		},
   229  		{
   230  			Name: "cardinality constraint forces alternative",
   231  			Variables: []Variable{
   232  				variable("a", Mandatory(), Dependency("x", "y"), AtMost(1, "x", "y")),
   233  				variable("b", Mandatory(), Dependency("y")),
   234  				variable("x"),
   235  				variable("y"),
   236  			},
   237  			Installed: []Identifier{"a", "b", "y"},
   238  		},
   239  		{
   240  			Name: "two dependencies satisfied by one variable",
   241  			Variables: []Variable{
   242  				variable("a", Mandatory(), Dependency("y")),
   243  				variable("b", Mandatory(), Dependency("x", "y")),
   244  				variable("x"),
   245  				variable("y"),
   246  			},
   247  			Installed: []Identifier{"a", "b", "y"},
   248  		},
   249  		{
   250  			Name: "foo two dependencies satisfied by one variable",
   251  			Variables: []Variable{
   252  				variable("a", Mandatory(), Dependency("y", "z", "m")),
   253  				variable("b", Mandatory(), Dependency("x", "y")),
   254  				variable("x"),
   255  				variable("y"),
   256  				variable("z"),
   257  				variable("m"),
   258  			},
   259  			Installed: []Identifier{"a", "b", "y"},
   260  		},
   261  		{
   262  			Name: "result size larger than minimum due to preference",
   263  			Variables: []Variable{
   264  				variable("a", Mandatory(), Dependency("x", "y")),
   265  				variable("b", Mandatory(), Dependency("y")),
   266  				variable("x"),
   267  				variable("y"),
   268  			},
   269  			Installed: []Identifier{"a", "b", "x", "y"},
   270  		},
   271  		{
   272  			Name: "only the least preferable choice is acceptable",
   273  			Variables: []Variable{
   274  				variable("a", Mandatory(), Dependency("a1", "a2")),
   275  				variable("a1", Conflict("c1"), Conflict("c2")),
   276  				variable("a2", Conflict("c1")),
   277  				variable("b", Mandatory(), Dependency("b1", "b2")),
   278  				variable("b1", Conflict("c1"), Conflict("c2")),
   279  				variable("b2", Conflict("c1")),
   280  				variable("c", Mandatory(), Dependency("c1", "c2")),
   281  				variable("c1"),
   282  				variable("c2"),
   283  			},
   284  			Installed: []Identifier{"a", "a2", "b", "b2", "c", "c2"},
   285  		},
   286  		{
   287  			Name: "preferences respected with multiple dependencies per variable",
   288  			Variables: []Variable{
   289  				variable("a", Mandatory(), Dependency("x1", "x2"), Dependency("y1", "y2")),
   290  				variable("x1"),
   291  				variable("x2"),
   292  				variable("y1"),
   293  				variable("y2"),
   294  			},
   295  			Installed: []Identifier{"a", "x1", "y1"},
   296  		},
   297  	} {
   298  		t.Run(tt.Name, func(t *testing.T) {
   299  			assert := assert.New(t)
   300  
   301  			var traces bytes.Buffer
   302  			s, err := New(WithInput(tt.Variables), WithTracer(LoggingTracer{Writer: &traces}))
   303  			if err != nil {
   304  				t.Fatalf("failed to initialize solver: %s", err)
   305  			}
   306  
   307  			installed, err := s.Solve(context.TODO())
   308  
   309  			if installed != nil {
   310  				sort.SliceStable(installed, func(i, j int) bool {
   311  					return installed[i].Identifier() < installed[j].Identifier()
   312  				})
   313  			}
   314  
   315  			// Failed constraints are sorted in lexically
   316  			// increasing order of the identifier of the
   317  			// constraint's variable, with ties broken
   318  			// in favor of the constraint that appears
   319  			// earliest in the variable's list of
   320  			// constraints.
   321  			if ns, ok := err.(NotSatisfiable); ok {
   322  				sort.SliceStable(ns, func(i, j int) bool {
   323  					if ns[i].Variable.Identifier() != ns[j].Variable.Identifier() {
   324  						return ns[i].Variable.Identifier() < ns[j].Variable.Identifier()
   325  					}
   326  					var x, y int
   327  					for ii, c := range ns[i].Variable.Constraints() {
   328  						if reflect.DeepEqual(c, ns[i].Constraint) {
   329  							x = ii
   330  							break
   331  						}
   332  					}
   333  					for ij, c := range ns[j].Variable.Constraints() {
   334  						if reflect.DeepEqual(c, ns[j].Constraint) {
   335  							y = ij
   336  							break
   337  						}
   338  					}
   339  					return x < y
   340  				})
   341  			}
   342  
   343  			var ids []Identifier
   344  			for _, variable := range installed {
   345  				ids = append(ids, variable.Identifier())
   346  			}
   347  			assert.Equal(tt.Installed, ids)
   348  			assert.Equal(tt.Error, err)
   349  
   350  			if t.Failed() {
   351  				t.Logf("\n%s", traces.String())
   352  			}
   353  		})
   354  	}
   355  }
   356  
   357  func TestDuplicateIdentifier(t *testing.T) {
   358  	_, err := New(WithInput([]Variable{
   359  		variable("a"),
   360  		variable("a"),
   361  	}))
   362  	assert.Equal(t, DuplicateIdentifier("a"), err)
   363  }