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 }