mirror of
https://github.com/spalencsar/bearhub.git
synced 2026-07-06 22:54:16 +02:00
[arch] fix: not properly checking conflicts with provided virtual packages with specific versions
This commit is contained in:
@@ -8,7 +8,7 @@ The format is based on [Keep a Changelog](https://keepachangelog.com/en/1.0.0/).
|
|||||||
|
|
||||||
### Fixes
|
### Fixes
|
||||||
- Arch
|
- Arch
|
||||||
- not properly checking all provided packages by the system when checking for conflicts (upgrade requirements)
|
- not properly checking all provided packages by the system when checking for conflicts (upgrade requirements). it could lead to an unbootable system or package losses.
|
||||||
- UI
|
- UI
|
||||||
- some windows not properly being centralized (regression introduced in **0.10.4**)
|
- some windows not properly being centralized (regression introduced in **0.10.4**)
|
||||||
- some windows being displayed in the wrong place (regression introduced in **0.10.4**)
|
- some windows being displayed in the wrong place (regression introduced in **0.10.4**)
|
||||||
|
|||||||
@@ -22,7 +22,7 @@ class UpdateRequirementsContext:
|
|||||||
aur_to_update: Dict[str, ArchPackage], repo_to_install: Dict[str, ArchPackage],
|
aur_to_update: Dict[str, ArchPackage], repo_to_install: Dict[str, ArchPackage],
|
||||||
aur_to_install: Dict[str, ArchPackage], to_install: Dict[str, ArchPackage],
|
aur_to_install: Dict[str, ArchPackage], to_install: Dict[str, ArchPackage],
|
||||||
pkgs_data: Dict[str, dict], cannot_upgrade: Dict[str, UpgradeRequirement],
|
pkgs_data: Dict[str, dict], cannot_upgrade: Dict[str, UpgradeRequirement],
|
||||||
to_remove: Dict[str, UpgradeRequirement], installed_names: Dict[str, str],
|
to_remove: Dict[str, UpgradeRequirement], installed: Dict[str, str],
|
||||||
provided_map: Dict[str, Set[str]], aur_index: Set[str], arch_config: dict,
|
provided_map: Dict[str, Set[str]], aur_index: Set[str], arch_config: dict,
|
||||||
remote_provided_map: Dict[str, Set[str]], remote_repo_map: Dict[str, str],
|
remote_provided_map: Dict[str, Set[str]], remote_repo_map: Dict[str, str],
|
||||||
root_password: Optional[str], aur_supported: bool):
|
root_password: Optional[str], aur_supported: bool):
|
||||||
@@ -34,7 +34,7 @@ class UpdateRequirementsContext:
|
|||||||
self.pkgs_data = pkgs_data
|
self.pkgs_data = pkgs_data
|
||||||
self.cannot_upgrade = cannot_upgrade
|
self.cannot_upgrade = cannot_upgrade
|
||||||
self.root_password = root_password
|
self.root_password = root_password
|
||||||
self.installed = installed_names
|
self.installed = installed
|
||||||
self.provided_map = provided_map
|
self.provided_map = provided_map
|
||||||
self.to_remove = to_remove
|
self.to_remove = to_remove
|
||||||
self.to_install = to_install
|
self.to_install = to_install
|
||||||
@@ -167,6 +167,23 @@ class UpdatesSummarizer:
|
|||||||
else:
|
else:
|
||||||
del context.aur_to_update[p]
|
del context.aur_to_update[p]
|
||||||
|
|
||||||
|
def _map_virtual_providers(self, providers: Dict[str, Set[str]], installed: Dict[str, str]) -> Dict[str, Set[str]]:
|
||||||
|
ti = time.time()
|
||||||
|
virtual_version = dict()
|
||||||
|
for provider in providers:
|
||||||
|
name_version = provider.split("=")
|
||||||
|
if len(name_version) == 2 and name_version[0] not in installed:
|
||||||
|
versions = virtual_version.get(name_version[0])
|
||||||
|
|
||||||
|
if not versions:
|
||||||
|
versions = set()
|
||||||
|
virtual_version[name_version[0]] = versions
|
||||||
|
|
||||||
|
versions.add(name_version[1])
|
||||||
|
tf = time.time()
|
||||||
|
self.logger.info(f"Took {tf - ti:.6f} seconds to map virtual providers of {len(providers)} packages")
|
||||||
|
return virtual_version
|
||||||
|
|
||||||
def _map_conflicts(self, data: Dict[str, Dict[str, Any]], providers: Dict[str, Set[str]],
|
def _map_conflicts(self, data: Dict[str, Dict[str, Any]], providers: Dict[str, Set[str]],
|
||||||
versions: Dict[str, str]) -> Tuple[Dict[str, str], Dict[str, str]]:
|
versions: Dict[str, str]) -> Tuple[Dict[str, str], Dict[str, str]]:
|
||||||
"""
|
"""
|
||||||
@@ -180,6 +197,8 @@ class UpdatesSummarizer:
|
|||||||
- first: containing all conflicts
|
- first: containing all conflicts
|
||||||
- second: containing mutual conflicts
|
- second: containing mutual conflicts
|
||||||
"""
|
"""
|
||||||
|
virtual_providers = self._map_virtual_providers(providers, versions)
|
||||||
|
|
||||||
root_conflict = {}
|
root_conflict = {}
|
||||||
mutual_conflicts = {}
|
mutual_conflicts = {}
|
||||||
|
|
||||||
@@ -192,20 +211,33 @@ class UpdatesSummarizer:
|
|||||||
|
|
||||||
if conflict_name != pkg_name:
|
if conflict_name != pkg_name:
|
||||||
conflict_providers = providers.get(conflict_name)
|
conflict_providers = providers.get(conflict_name)
|
||||||
|
if conflict_providers: # it means the conflict name matches a provided package
|
||||||
|
checked_conflicts = set()
|
||||||
|
|
||||||
if conflict_providers:
|
if len(name_op_exp) == 1: # if no expression is provided, add all providers
|
||||||
checked_conflicts = []
|
checked_conflicts.update((p for p in conflict_providers if p != pkg_name))
|
||||||
for provider in conflict_providers:
|
else:
|
||||||
if provider != pkg_name:
|
virtual_versions = virtual_providers.get(conflict_name)
|
||||||
if len(name_op_exp) == 1:
|
|
||||||
checked_conflicts.append(provider)
|
|
||||||
else:
|
|
||||||
provider_version = versions.get(provider)
|
|
||||||
|
|
||||||
if match_required_version(provider_version,
|
if virtual_versions:
|
||||||
name_op_exp[1],
|
# it means it's a virtual package
|
||||||
name_op_exp[2]):
|
# (e.g: 'xorg-server' provides a virtual package called 'x-server')
|
||||||
checked_conflicts.append(provider)
|
for pversion in virtual_versions:
|
||||||
|
if match_required_version(pversion, name_op_exp[1], name_op_exp[2]):
|
||||||
|
# read the packages providing this specific virtual package version
|
||||||
|
real_providers = providers.get(f"{conflict_name}={pversion}")
|
||||||
|
|
||||||
|
if real_providers:
|
||||||
|
checked_conflicts.update(p for p in real_providers if p != pkg_name)
|
||||||
|
|
||||||
|
else:
|
||||||
|
for provider in conflict_providers:
|
||||||
|
if provider != pkg_name:
|
||||||
|
provider_version = versions.get(provider)
|
||||||
|
if provider_version and match_required_version(provider_version,
|
||||||
|
name_op_exp[1],
|
||||||
|
name_op_exp[2]):
|
||||||
|
checked_conflicts.add(provider)
|
||||||
|
|
||||||
for provider in checked_conflicts:
|
for provider in checked_conflicts:
|
||||||
root_conflict[provider] = pkg_name
|
root_conflict[provider] = pkg_name
|
||||||
@@ -476,7 +508,7 @@ class UpdatesSummarizer:
|
|||||||
remote_repo_map = pacman.map_repositories()
|
remote_repo_map = pacman.map_repositories()
|
||||||
context = UpdateRequirementsContext(to_update={}, repo_to_update={}, aur_to_update={}, repo_to_install={},
|
context = UpdateRequirementsContext(to_update={}, repo_to_update={}, aur_to_update={}, repo_to_install={},
|
||||||
aur_to_install={}, to_install={}, pkgs_data={}, cannot_upgrade={},
|
aur_to_install={}, to_install={}, pkgs_data={}, cannot_upgrade={},
|
||||||
to_remove={}, installed_names=dict(), provided_map={}, aur_index=set(),
|
to_remove={}, installed=dict(), provided_map={}, aur_index=set(),
|
||||||
arch_config=arch_config, root_password=root_password,
|
arch_config=arch_config, root_password=root_password,
|
||||||
remote_provided_map=remote_provided_map, remote_repo_map=remote_repo_map,
|
remote_provided_map=remote_provided_map, remote_repo_map=remote_repo_map,
|
||||||
aur_supported=self.aur_supported)
|
aur_supported=self.aur_supported)
|
||||||
|
|||||||
@@ -273,6 +273,199 @@ class UpdatesSummarizerGetUpgradeRequirementsTest(TestCase):
|
|||||||
self.assertFalse(res.to_install)
|
self.assertFalse(res.to_install)
|
||||||
self.assertEqual([UpgradeRequirement(pkg=pkg_a, required_size=1, extra_size=0)], res.to_upgrade)
|
self.assertEqual([UpgradeRequirement(pkg=pkg_a, required_size=1, extra_size=0)], res.to_upgrade)
|
||||||
|
|
||||||
|
@patch(f"{__app_name__}.gems.arch.updates.pacman")
|
||||||
|
def test__not_return_to_remove_when_conflict_with_provided_virtual_package_with_version_fails(self, pacman: Mock):
|
||||||
|
"""
|
||||||
|
Scenario:
|
||||||
|
- Package V (2.5.0)[update] -> conflicts: X<21.1.1, X-ABI-VIDEODRV_VERSION<25, X-ABI-VIDEODRV_VERSION>=26
|
||||||
|
- Package X (21.1.5)[installed] -> provides: X-ABI-VIDEODRV_VERSION=25.2, X
|
||||||
|
|
||||||
|
* X provides X-ABI-VIDEODRV_VERSION with a specific version (25.2)
|
||||||
|
"""
|
||||||
|
pkg_a = ArchPackage(name="V", version="2.5.0-1", latest_version="2.6.0-1", repository="community")
|
||||||
|
pacman.map_provided.side_effect = [{"V": {"V"}, # remote provided
|
||||||
|
"V=2.5.0": {"V"},
|
||||||
|
"X": {"X"},
|
||||||
|
"X=21.1.5": {"X"},
|
||||||
|
"X-ABI-VIDEODRV_VERSION=25.2": {"X"},
|
||||||
|
"X-ABI-VIDEODRV_VERSION": {"X"}
|
||||||
|
},
|
||||||
|
{"V": {"V"}, # provided
|
||||||
|
"V=2.5.0": {"V"},
|
||||||
|
"X": {"X"},
|
||||||
|
"X=21.1.5": {"X"},
|
||||||
|
"X-ABI-VIDEODRV_VERSION=25.2": {"X"},
|
||||||
|
"X-ABI-VIDEODRV_VERSION": {"X"}
|
||||||
|
},
|
||||||
|
]
|
||||||
|
pacman.map_repositories.return_value = {"V": pkg_a.repository, "X": "community"}
|
||||||
|
pacman.map_updates_data.return_value = {"V": {'ds': 1,
|
||||||
|
's': 1,
|
||||||
|
'v': pkg_a.latest_version,
|
||||||
|
'c': {"X<21.1.1",
|
||||||
|
"X-ABI-VIDEODRV_VERSION<25",
|
||||||
|
"X-ABI-VIDEODRV_VERSION>=26"},
|
||||||
|
'p': {"V": {"V"},
|
||||||
|
"V=2.5.0": {"A"}},
|
||||||
|
'd': set(),
|
||||||
|
'r': "community",
|
||||||
|
'des': pkg_a.name}}
|
||||||
|
pacman.map_installed.return_value = {"V": pkg_a.version, "X": "21.1.5-1"}
|
||||||
|
pacman.get_installed_size.return_value = {"V": 1, "X": 1}
|
||||||
|
pacman.map_required_by.return_value = {"V": set()}
|
||||||
|
|
||||||
|
self.deps_analyser.map_missing_deps.return_value = list()
|
||||||
|
|
||||||
|
res = self.summarizer.summarize(pkgs=[pkg_a], root_password=None, arch_config=self.config_)
|
||||||
|
for method in ('map_provided', 'map_repositories', 'map_updates_data', 'map_installed',
|
||||||
|
'get_installed_size', 'map_required_by'):
|
||||||
|
getattr(pacman, method).assert_called()
|
||||||
|
|
||||||
|
self.assertFalse(res.to_remove)
|
||||||
|
self.assertFalse(res.to_install)
|
||||||
|
self.assertEqual([UpgradeRequirement(pkg=pkg_a, required_size=1, extra_size=0)], res.to_upgrade)
|
||||||
|
|
||||||
|
@patch(f"{__app_name__}.gems.arch.updates.pacman")
|
||||||
|
def test__return_installed_virtual_with_defined_version_to_remove_when_conflict_version_matches(self, pacman: Mock):
|
||||||
|
"""
|
||||||
|
Scenario:
|
||||||
|
- Package V (2.5.0)[update] -> conflicts: X<21.1.1, X-ABI-VIDEODRV_VERSION<25, X-ABI-VIDEODRV_VERSION>=26
|
||||||
|
- Package X (21.1.5)[installed] -> provides: X-ABI-VIDEODRV_VERSION=26, X
|
||||||
|
|
||||||
|
* X provides X-ABI-VIDEODRV_VERSION with a specific version (26)
|
||||||
|
"""
|
||||||
|
pkg_a = ArchPackage(name="V", version="2.5.0-1", latest_version="2.6.0-1", repository="community")
|
||||||
|
|
||||||
|
pacman.map_provided.side_effect = [{"V": {"V"}, # remote provided
|
||||||
|
"V=2.5.0": {"V"},
|
||||||
|
"X": {"X"},
|
||||||
|
"X=21.1.5": {"X"},
|
||||||
|
"X-ABI-VIDEODRV_VERSION=26": {"X"},
|
||||||
|
"X-ABI-VIDEODRV_VERSION": {"X"}
|
||||||
|
},
|
||||||
|
{"V": {"V"}, # provided
|
||||||
|
"V=2.5.0": {"V"},
|
||||||
|
"X": {"X"},
|
||||||
|
"X=21.1.5": {"X"},
|
||||||
|
"X-ABI-VIDEODRV_VERSION=26": {"X"},
|
||||||
|
"X-ABI-VIDEODRV_VERSION": {"X"}
|
||||||
|
},
|
||||||
|
{"X": {"X"}, # provided to remove
|
||||||
|
"X=21.1.5": {"X"},
|
||||||
|
"X-ABI-VIDEODRV_VERSION=26": {"X"},
|
||||||
|
"X-ABI-VIDEODRV_VERSION": {"X"}
|
||||||
|
}
|
||||||
|
]
|
||||||
|
pacman.map_repositories.return_value = {"V": pkg_a.repository, "X": "community"}
|
||||||
|
pacman.map_updates_data.return_value = {"V": {'ds': 1,
|
||||||
|
's': 1,
|
||||||
|
'v': pkg_a.latest_version,
|
||||||
|
'c': {"X<21.1.1",
|
||||||
|
"X-ABI-VIDEODRV_VERSION<25",
|
||||||
|
"X-ABI-VIDEODRV_VERSION>=26"},
|
||||||
|
'p': {"V": {"V"},
|
||||||
|
"V=2.5.0": {"A"}},
|
||||||
|
'd': set(),
|
||||||
|
'r': "community",
|
||||||
|
'des': pkg_a.name}}
|
||||||
|
pacman.map_installed.return_value = {"V": pkg_a.version, "X": "21.1.5-1"}
|
||||||
|
pacman.get_installed_size.return_value = {"V": 1, "X": 1}
|
||||||
|
pacman.map_required_by.return_value = {"V": set()}
|
||||||
|
|
||||||
|
self.deps_analyser.map_missing_deps.return_value = list()
|
||||||
|
self.deps_analyser.map_all_required_by.return_value = set()
|
||||||
|
|
||||||
|
res = self.summarizer.summarize(pkgs=[pkg_a], root_password=None, arch_config=self.config_)
|
||||||
|
|
||||||
|
for method in ('map_provided', 'map_repositories', 'map_updates_data', 'map_installed',
|
||||||
|
'get_installed_size', 'map_required_by'):
|
||||||
|
getattr(pacman, method).assert_called()
|
||||||
|
|
||||||
|
for method in ('map_missing_deps', 'map_all_required_by'):
|
||||||
|
getattr(self.deps_analyser, method).assert_called()
|
||||||
|
|
||||||
|
self.assertFalse(res.to_install)
|
||||||
|
self.assertEqual([UpgradeRequirement(pkg=pkg_a, required_size=1, extra_size=0)], res.to_upgrade)
|
||||||
|
|
||||||
|
pkg_b = ArchPackage(name="X", installed=True, i18n=self.i18n)
|
||||||
|
self.assertEqual([UpgradeRequirement(pkg=pkg_b, reason=" 'V'", extra_size=1)], res.to_remove)
|
||||||
|
|
||||||
|
@patch(f"{__app_name__}.gems.arch.updates.pacman")
|
||||||
|
def test__return_installed_virtual_with_defined_version_to_remove_when_conflict_matches__case_2(self, pacman: Mock):
|
||||||
|
"""
|
||||||
|
This test case covers the same scenario as the above, but adds an additional provider for the same
|
||||||
|
virtual package with a non-conflicting version
|
||||||
|
|
||||||
|
Scenario:
|
||||||
|
- Package V (2.5.0)[update] -> conflicts: X<21.1.1, X-ABI-VIDEODRV_VERSION<25, X-ABI-VIDEODRV_VERSION>=26
|
||||||
|
- Package X (21.1.5)[installed] -> provides: X-ABI-VIDEODRV_VERSION=26, X
|
||||||
|
- Package Z (2.0.0)[installed] -> provides: X-ABI-VIDEODRV_VERSION=25.2, Z
|
||||||
|
|
||||||
|
* X provides X-ABI-VIDEODRV_VERSION with a specific version (26) [conflict]
|
||||||
|
* Z provides X-ABI-VIDEODRV_VERSION with a specific version (25.2) [no conflict]
|
||||||
|
"""
|
||||||
|
pkg_a = ArchPackage(name="V", version="2.5.0-1", latest_version="2.6.0-1", repository="community")
|
||||||
|
|
||||||
|
pacman.map_provided.side_effect = [{"V": {"V"}, # remote provided
|
||||||
|
"V=2.5.0": {"V"},
|
||||||
|
"X": {"X"},
|
||||||
|
"X=21.1.5": {"X"},
|
||||||
|
"X-ABI-VIDEODRV_VERSION=26": {"X"},
|
||||||
|
"Z": {"Z"},
|
||||||
|
"Z=2.0.0": {"X"},
|
||||||
|
"X-ABI-VIDEODRV_VERSION=25.2": {"Z"},
|
||||||
|
"X-ABI-VIDEODRV_VERSION": {"X", "Z"},
|
||||||
|
},
|
||||||
|
{"V": {"V"}, # provided
|
||||||
|
"V=2.5.0": {"V"},
|
||||||
|
"X": {"X"},
|
||||||
|
"X=21.1.5": {"X"},
|
||||||
|
"X-ABI-VIDEODRV_VERSION=26": {"X"},
|
||||||
|
"Z": {"Z"},
|
||||||
|
"Z=2.0.0": {"X"},
|
||||||
|
"X-ABI-VIDEODRV_VERSION=25.2": {"Z"},
|
||||||
|
"X-ABI-VIDEODRV_VERSION": {"X", "Z"},
|
||||||
|
},
|
||||||
|
{"X": {"X"}, # provided to remove
|
||||||
|
"X=21.1.5": {"X"},
|
||||||
|
"X-ABI-VIDEODRV_VERSION=26": {"X"},
|
||||||
|
"X-ABI-VIDEODRV_VERSION": {"X", "Z"}
|
||||||
|
}
|
||||||
|
]
|
||||||
|
pacman.map_repositories.return_value = {"V": pkg_a.repository, "X": "community"}
|
||||||
|
pacman.map_updates_data.return_value = {"V": {'ds': 1,
|
||||||
|
's': 1,
|
||||||
|
'v': pkg_a.latest_version,
|
||||||
|
'c': {"X<21.1.1",
|
||||||
|
"X-ABI-VIDEODRV_VERSION<25",
|
||||||
|
"X-ABI-VIDEODRV_VERSION>=26"},
|
||||||
|
'p': {"V": {"V"},
|
||||||
|
"V=2.5.0": {"A"}},
|
||||||
|
'd': set(),
|
||||||
|
'r': "community",
|
||||||
|
'des': pkg_a.name}}
|
||||||
|
pacman.map_installed.return_value = {"V": pkg_a.version, "X": "21.1.5-1", "Z": "2.0.0-1"}
|
||||||
|
pacman.get_installed_size.return_value = {"V": 1, "X": 1, "Z": 1}
|
||||||
|
pacman.map_required_by.return_value = {"V": set()}
|
||||||
|
|
||||||
|
self.deps_analyser.map_missing_deps.return_value = list()
|
||||||
|
self.deps_analyser.map_all_required_by.return_value = set()
|
||||||
|
|
||||||
|
res = self.summarizer.summarize(pkgs=[pkg_a], root_password=None, arch_config=self.config_)
|
||||||
|
|
||||||
|
for method in ('map_provided', 'map_repositories', 'map_updates_data', 'map_installed',
|
||||||
|
'get_installed_size', 'map_required_by'):
|
||||||
|
getattr(pacman, method).assert_called()
|
||||||
|
|
||||||
|
for method in ('map_missing_deps', 'map_all_required_by'):
|
||||||
|
getattr(self.deps_analyser, method).assert_called()
|
||||||
|
|
||||||
|
self.assertFalse(res.to_install)
|
||||||
|
self.assertEqual([UpgradeRequirement(pkg=pkg_a, required_size=1, extra_size=0)], res.to_upgrade)
|
||||||
|
|
||||||
|
pkg_b = ArchPackage(name="X", installed=True, i18n=self.i18n)
|
||||||
|
self.assertEqual([UpgradeRequirement(pkg=pkg_b, reason=" 'V'", extra_size=1)], res.to_remove)
|
||||||
|
|
||||||
@patch(f"{__app_name__}.gems.arch.updates.pacman")
|
@patch(f"{__app_name__}.gems.arch.updates.pacman")
|
||||||
def test__should_return_installed_package_to_remove_when_conflict_with_provided_matches_version(self, pacman: Mock):
|
def test__should_return_installed_package_to_remove_when_conflict_with_provided_matches_version(self, pacman: Mock):
|
||||||
"""
|
"""
|
||||||
|
|||||||
Reference in New Issue
Block a user