From 97663ebaab562d56c086d644054587a9df6c4c65 Mon Sep 17 00:00:00 2001 From: Niels Thykier Date: Sun, 17 Jan 2016 14:33:43 +0000 Subject: [PATCH] inst-tester: split _pick_choice into two Signed-off-by: Niels Thykier --- installability/tester.py | 158 +++++++++++++++++++++------------------ 1 file changed, 85 insertions(+), 73 deletions(-) diff --git a/installability/tester.py b/installability/tester.py index 986dde4..b87ae6e 100644 --- a/installability/tester.py +++ b/installability/tester.py @@ -328,11 +328,11 @@ class InstallabilityTester(object): # # * A package is installable if never and musts are disjointed # and both check and choices are empty. - # - exception: _pick_choice may determine the installability + # - exception: resolve_choice may determine the installability # of t via recursion (calls _check_inst). In this case # check and choices are not (always) empty. - def _pick_choice(rebuild, set=set, len=len): + def _prune_choices(rebuild, set=set, len=len): """Picks a choice from choices and updates rebuild. Prunes the choices and updates "rebuild" to reflect the @@ -382,75 +382,14 @@ class InstallabilityTester(object): # all alternatives would violate the conflicts or are uninstallable # => package is not installable stats.choice_presolved += 1 - return None + return False # The choice is still deferred rebuild.add(frozenset(remain)) - if check or not rebuild: - return False - - choice = iter(rebuild.pop()) - last = next(choice) # pick one to go last - for p in choice: - musts_copy = musts.copy() - never_tmp = set() - choices_tmp = set() - check_tmp = set([p]) - if not self._check_loop(universe, testing, eqv_table, - stats, musts_copy, never_tmp, - cbroken, choices_tmp, - check_tmp): - # p cannot be chosen/is broken (unlikely, but ...) - continue - - # Test if we can pick p without any consequences. - # - when we can, we avoid a backtrack point. - if never_tmp <= never and choices_tmp <= rebuild: - # we can pick p without picking up new conflicts - # or unresolved choices. Therefore we commit to - # using p. - # - # NB: Optimally, we would go to the start of this - # routine, but to conserve stack-space, we return - # and expect to be called again later. - musts.update(musts_copy) - stats.choice_resolved_without_restore_point += 1 - return False - - if not musts.isdisjoint(never_tmp): - # If we pick p, we will definitely end up making - # t uninstallable, so p is a no-go. - continue + return True - stats.backtrace_restore_point_created += 1 - # We are not sure that p is safe, setup a backtrack - # point and recurse. - never_tmp |= never - choices_tmp |= rebuild - if self._check_inst(p, musts_copy, never_tmp, - choices_tmp): - # Success, p was a valid choice and made it all - # installable - return True - - # If we get here, we failed to find something that - # would satisfy choice (without breaking the - # installability of t). This means p cannot be used - # to satisfy the dependencies, so pretend to conflict - # with it - hopefully it will reduce future choices. - never.add(p) - stats.backtrace_restore_point_used += 1 - - # Optimization for the last case; avoid the recursive call - # and just assume the last will lead to a solution. If it - # doesn't there is no solution and if it does, we don't - # have to back-track anyway. - check.add(last) - musts.add(last) - stats.backtrace_last_option += 1 - return False - # END _pick_choice + # END _prune_choices while check: if not check_loop(choices, check): @@ -459,15 +398,20 @@ class InstallabilityTester(object): if choices: rebuild = set() - # We have to "guess" now, which is always fun, but not cheap - r = _pick_choice(rebuild) - if r is None: + + if not _prune_choices(rebuild): verdict = False break - if r: - # The recursive call have already updated the - # cache so there is not point in doing it again. - return True + + while not check and rebuild: + # We have to "guess" now, which is always fun, but not cheap. We + # stop guessing: + # - once we run out of choices to make (obviously), OR + # - if one of the choices exhaust all but one option + if self.resolve_choice(check, musts, never, rebuild): + # The recursive call have already updated the + # cache so there is not point in doing it again. + return True choices = rebuild if verdict: @@ -479,6 +423,74 @@ class InstallabilityTester(object): return verdict + def resolve_choice(self, check, musts, never, choices): + universe = self._universe + testing = self._testing + eqv_table = self._eqv_table + stats = self._stats + cbroken = self._cache_broken + + choice = iter(choices.pop()) + last = next(choice) # pick one to go last + for p in choice: + musts_copy = musts.copy() + never_tmp = set() + choices_tmp = set() + check_tmp = set([p]) + if not self._check_loop(universe, testing, eqv_table, + stats, musts_copy, never_tmp, + cbroken, choices_tmp, + check_tmp): + # p cannot be chosen/is broken (unlikely, but ...) + continue + + # Test if we can pick p without any consequences. + # - when we can, we avoid a backtrack point. + if never_tmp <= never and choices_tmp <= choices: + # we can pick p without picking up new conflicts + # or unresolved choices. Therefore we commit to + # using p. + # + # NB: Optimally, we would go to the start of this + # routine, but to conserve stack-space, we return + # and expect to be called again later. + musts.update(musts_copy) + stats.choice_resolved_without_restore_point += 1 + return False + + if not musts.isdisjoint(never_tmp): + # If we pick p, we will definitely end up making + # t uninstallable, so p is a no-go. + continue + + stats.backtrace_restore_point_created += 1 + # We are not sure that p is safe, setup a backtrack + # point and recurse. + never_tmp |= never + choices_tmp |= choices + if self._check_inst(p, musts_copy, never_tmp, + choices_tmp): + # Success, p was a valid choice and made it all + # installable + return True + + # If we get here, we failed to find something that + # would satisfy choice (without breaking the + # installability of t). This means p cannot be used + # to satisfy the dependencies, so pretend to conflict + # with it - hopefully it will reduce future choices. + never.add(p) + stats.backtrace_restore_point_used += 1 + + # Optimization for the last case; avoid the recursive call + # and just assume the last will lead to a solution. If it + # doesn't there is no solution and if it does, we don't + # have to back-track anyway. + check.add(last) + musts.add(last) + stats.backtrace_last_option += 1 + return False + def _check_loop(self, universe, testing, eqv_table, stats, musts, never, cbroken, choices, check, len=len, frozenset=frozenset):