Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
17 changes: 8 additions & 9 deletions src/core/src/package_managers/Dnf5PackageManager.py
Original file line number Diff line number Diff line change
Expand Up @@ -36,7 +36,7 @@ def __init__(self, env_layer, execution_config, composite_logger, telemetry_writ
self.single_package_check_versions = 'sudo dnf5 list --available <PACKAGE-NAME> '
self.single_package_check_installed = 'sudo dnf5 list --installed <PACKAGE-NAME> '

self.single_package_upgrade_simulation_cmd = "sudo dnf5 install --assumeno --skip-broken "
self.single_package_upgrade_simulation_cmd = "sudo dnf5 upgrade --assumeno "

# Install update
self.single_package_upgrade_cmd = 'sudo dnf5 -y upgrade '
Expand All @@ -48,8 +48,8 @@ def __init__(self, env_layer, execution_config, composite_logger, telemetry_writ
self.dnf_exitcode_ok = [0, 100]
# DNF5 valid exit codes for simulation commands
self.dnf5_simulation_valid_exit_codes = [0, 1]
self.dnf5_dependency_failure_text = ["Skipping packages with broken dependencies", "Nothing to do."]
self.dnf5_dependency_success_text = "Installing dependencies:"
self.dnf5_dependency_failure_text = ["Skipping packages with broken dependencies", "Nothing to do.", "Failed to resolve the transaction:"]
self.dnf5_dependency_success_text = ["Installing dependencies:", "Upgrading dependencies:"]
self.dnf5_dependency_exit_text = "Transaction Summary"
self.dnf5_not_installed_exit_code = 1
self.dnf5_not_installed_text = "No matching packages to list"
Expand Down Expand Up @@ -257,9 +257,8 @@ def get_dependent_list(self, packages):
# Gets the dependent list from packages.Refer dnf5_output_expected_format.txt for examples of output formats.
package_names = " ".join(packages)
cmd = self.single_package_upgrade_simulation_cmd + package_names
# Dependency simulation using dnf5 install --assumeno --skip-broken has non-standard exit code behavior. A valid simulation run may return exit code 1 (e.g., "Operation aborted by the user"),
# while dependency resolution failures may still return exit code 0 with output indicating skipped packages (e.g., "Skipping packages with broken dependencies" and "Nothing to do.").
# calling the runcommand directly to get the output as well as code to determine failure/success cases
# DNF5 dependency simulation using `upgrade --assumeno` may return non-standard exit codes. Successful simulations and transaction
# resolution failures can both return exit code 1, therefore both command output and exit code are evaluated.
code, output = self.env_layer.run_command_output(cmd, False, False)
self.composite_logger.log_verbose("[DNF5] Dependency simulation. [Command={0}][Code={1}]".format(cmd, str(code)))
if code not in self.dnf5_simulation_valid_exit_codes:
Expand All @@ -277,8 +276,8 @@ def extract_dependencies(self, output, packages):
dependencies = []

# Handle non-blocking dependency failure / nothing-to-do cases
if all(text in output for text in self.dnf5_dependency_failure_text):
self.composite_logger.log_warning("[DNF5] Packages skipped due to broken dependencies (non-blocking)")
if any(text in output for text in self.dnf5_dependency_failure_text):
self.composite_logger.log_warning("[DNF5] Dependency simulation did not return dependency information (non-blocking)")
return dependencies

package_arch_to_look_for = ["x86_64", "noarch", "i686", "aarch64"]
Expand All @@ -290,7 +289,7 @@ def extract_dependencies(self, output, packages):
line_str = lines[line_index].strip()

# Detect start of dependency section
if line_str.startswith(self.dnf5_dependency_success_text):
if any(line_str.startswith(text) for text in self.dnf5_dependency_success_text):
in_dependency_section = True

# Detect exit of dependency section
Comment on lines 291 to 295
Expand Down
7 changes: 7 additions & 0 deletions src/core/tests/Test_Dnf5PackageManager.py
Original file line number Diff line number Diff line change
Expand Up @@ -545,6 +545,13 @@ def test_package_manager(self):
dependent_list = package_manager.get_dependent_list(["hyperv-daemons.x86_64"])
self.assertIsNotNone(dependent_list)

self.runtime.set_legacy_test_type('AnotherSadPath')
package_manager = self.container.get('package_manager')
self.assertIsNotNone(package_manager)
sad_dependent_list = package_manager.get_dependent_list(["openssl-999.999"])
self.assertIsNotNone(sad_dependent_list)
self.assertEqual(len(sad_dependent_list), 0)

def test_install_package_success(self):
"""Unit test for install package success"""
self.runtime.set_legacy_test_type('SuccessInstallPath')
Expand Down
48 changes: 37 additions & 11 deletions src/core/tests/library/LegacyEnvLayerExtensions.py
Original file line number Diff line number Diff line change
Expand Up @@ -723,17 +723,20 @@ def run_command_output(self, cmd, no_output=False, chk_err=True):
"python3.x86_64 3.12.3-4.azl4~20260501 azurelinux-base\n" + \
"python3.x86_64 3.12.3-5.azl4~20260501 azurelinux-base\n" + \
"python3.x86_64 3.12.3-6.azl4~20260501 azurelinux-base\n"
elif cmd.find("dnf5 install --assumeno --skip-broken") > -1 and "hyperv-daemons" in cmd:
elif cmd.find("dnf5 upgrade --assumeno") > -1 and "hyperv-daemons" in cmd:
code = 1
output = "Updating and loading repositories:\n" + \
"Repositories loaded.\n" + \
"Package Arch Version Repository Size\n" + \
"Installing:\n" + \
" hyperv-daemons x86_64 6.10-3.azl4~20260501 azurelinux-base 20.08k\n\n" + \
"Transaction Summary:\n" + \
" Installing: 1 package\n\n" + \
"Total download size: 135.09k\n" + \
"Operation aborted by the user.\n"
output = (
"Updating and loading repositories:\n"
"Repositories loaded.\n"
"Package Arch Version Repository Size\n"
"Installing:\n"
" hyperv-daemons x86_64 6.10-3.azl4~20260501 azurelinux-base 20.08k\n\n"
"Installing dependencies:\n"
" hyperv-daemons-license noarch 6.10-3.azl4~20260501 azurelinux-base 18.3 KiB\n\n"
"Transaction Summary:\n"
" Installing: 2 packages\n\n"
"Total download size: 135.09k\n"
"Operation aborted by the user.\n")
elif cmd.find("systemctl cat dnf5-automatic.service") > -1:
code = 0
output = "ExecStart=/usr/bin/dnf5 automatic --timer"
Expand Down Expand Up @@ -902,6 +905,15 @@ def run_command_output(self, cmd, no_output=False, chk_err=True):
elif "systemctl enable --nows dnf-automatic.timer" in cmd:
code = 1
output = 'systemctl: unrecognized option --nows'
elif cmd.find("dnf5 upgrade --assumeno") > -1 and "openssl-999.999" in cmd:
code = 1
output = (
"Updating and loading repositories:\n"
"Repositories loaded.\n"
"Failed to resolve the transaction:\n"
"No match for argument: openssl-999.999\n"
"You can try to add to command line:\n"
" --skip-unavailable to skip unavailable packages\n")
elif self.legacy_test_type == 'ExceptionPath':
code = -1
output = ''
Expand Down Expand Up @@ -1086,6 +1098,20 @@ def run_command_output(self, cmd, no_output=False, chk_err=True):
'Repositories loaded.\n'
'Package "rubygem-json-2.13.2-2.azl4~20260501.x86_64" is already installed.\n\n'
'Nothing to do.\n')
elif "hyperv-daemons" in cmd and "--assumeno" in cmd:
code = 1
output = (
"Updating and loading repositories:\n"
"Repositories loaded.\n"
"Package Arch Version Repository Size\n"
"Installing:\n"
" hyperv-daemons x86_64 6.10-3.azl4~20260501 azurelinux-base 20.08k\n\n"
"Installing dependencies:\n"
" hyperv-daemons-license noarch 6.10-3.azl4~20260501 azurelinux-base 18.3 KiB\n\n"
"Transaction Summary:\n"
" Installing: 2 packages\n\n"
"Total download size: 135.09k\n"
"Operation aborted by the user.\n")
elif "dnf5 list --installed rubygem-json" in cmd:
code = 0
output = (
Expand Down Expand Up @@ -1254,7 +1280,7 @@ def run_command_output(self, cmd, no_output=False, chk_err=True):
code = 100
output = "Failed to install package"
elif self.legacy_package_manager_name is Constants.DNF5:
if cmd.find("simulate-install") > -1 or cmd.find("sudo dnf5 install --assumeno --skip-broken hyperv-daemons-license") > -1:
if cmd.find("simulate-install") > -1 or cmd.find("sudo dnf5 upgrade --assumeno hyperv-daemons-license") > -1:
code = 1
output = "Updating and loading repositories:\n" + \
"Repositories loaded.\n" + \
Expand Down
Loading
Loading