From 57885178bb3c0eb8deaf543270d56280d152b936 Mon Sep 17 00:00:00 2001 From: Yashna Parikh Date: Thu, 25 Jun 2026 10:22:18 -0400 Subject: [PATCH 1/4] BugFix for DNF5: Exclusion list not honored --- .../package_managers/Dnf5PackageManager.py | 15 ++- .../tests/library/LegacyEnvLayerExtensions.py | 2 +- .../dnf5_output_expected_format | 96 ++++++------------- 3 files changed, 37 insertions(+), 76 deletions(-) diff --git a/src/core/src/package_managers/Dnf5PackageManager.py b/src/core/src/package_managers/Dnf5PackageManager.py index d2942a1c..4f03318d 100644 --- a/src/core/src/package_managers/Dnf5PackageManager.py +++ b/src/core/src/package_managers/Dnf5PackageManager.py @@ -36,7 +36,7 @@ def __init__(self, env_layer, execution_config, composite_logger, telemetry_writ self.single_package_check_versions = 'sudo dnf5 list --available ' self.single_package_check_installed = 'sudo dnf5 list --installed ' - 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 ' @@ -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" @@ -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: @@ -277,7 +276,7 @@ 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): + if any(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)") return dependencies @@ -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 diff --git a/src/core/tests/library/LegacyEnvLayerExtensions.py b/src/core/tests/library/LegacyEnvLayerExtensions.py index fb0fa6ce..cfecf921 100644 --- a/src/core/tests/library/LegacyEnvLayerExtensions.py +++ b/src/core/tests/library/LegacyEnvLayerExtensions.py @@ -723,7 +723,7 @@ 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" + \ diff --git a/src/tools/references/cmd_output_references/dnf5_output_expected_format b/src/tools/references/cmd_output_references/dnf5_output_expected_format index deb7c9df..3911ea2f 100644 --- a/src/tools/references/cmd_output_references/dnf5_output_expected_format +++ b/src/tools/references/cmd_output_references/dnf5_output_expected_format @@ -1,81 +1,43 @@ Example of different output formats for DNF 5 commands: -In DNF 5: Sample output for the command `sudo dnf5 install --assumeno --skip-broken jq` to extract_dependencies Success Case is(exit code : 1): +In DNF 5: Sample output for the command 'sudo dnf5 upgrade --assumeno openssl' Success case : Exit code :1 Updating and loading repositories: Repositories loaded. - Package Arch Version Repository Size - Installing: - jq x86_64 1.8.1-3.azl4~20260501 azurelinux-base 457.7 KiB - Installing dependencies: - oniguruma x86_64 6.9.10-3.azl4~20260501 azurelinux-base 763.1 KiB - Transaction Summary: - Installing: 2 packages - Total size of inbound packages is 428 KiB. Need to download 428 KiB. - After this operation, 1 MiB extra will be used (install 1 MiB, remove 0 B). - Operation aborted by the user. -In DNF 5: Sample output for the command `sudo dnf5 install --assumeno --skip-broken git` to extract_dependencies Failure Case is(exit code : 0): - Updating and loading repositories: - Repositories loaded. - Problem: package perl-Getopt-Long-1:2.58-521.azl4~20260501.noarch from azurelinux-base requires perl(Pod::Usage) >= 1.14, but none of the providers can be installed - - package perl-Pod-Usage-4:2.05-521.azl4~20260501.noarch from azurelinux-base requires perl(Pod::Text) >= 4, but none of the providers can be installed - - package git-2.53.0-2.azl4~20260501.x86_64 from azurelinux-base requires perl(Getopt::Long), but none of the providers can be installed - - package perl-podlators-1:6.0.2-521.azl4~20260501.noarch from azurelinux-base requires perl(Pod::Simple) >= 3.26, but none of the providers can be installed - - conflicting requests - - nothing provides perl(Text::Wrap) >= 98.112902 needed by perl-Pod-Simple-1:3.47-4.azl4~20260501.noarch from azurelinux-base - Package Arch Version Repository Size - Skipping packages with broken dependencies: - git x86_64 2.53.0-2.azl4~20260501 azurelinux-base 56.4 KiB - perl-Getopt-Long noarch 1:2.58-521.azl4~20260501 azurelinux-base 144.5 KiB - perl-Pod-Simple noarch 1:3.47-4.azl4~20260501 azurelinux-base 565.3 KiB - perl-Pod-Usage noarch 4:2.05-521.azl4~20260501 azurelinux-base 86.3 KiB - perl-podlators noarch 1:6.0.2-521.azl4~20260501 azurelinux-base 317.5 KiB - Nothing to do. + Package Arch Version Repository + Upgrading: + openssl x86_64 3.x.x azurelinux-base + + Upgrading dependencies: + openssl-libs x86_64 3.x.x azurelinux-base -In DNF 5: Sample output for the command 'sudo dnf5 -y install jq' for multi-arch dependencies is(exit code : 0): - Updating and loading repositories: - Repositories loaded. - Package Arch Version Repository Size - Installing: - jq x86_64 1.8.1-3.azl4~20260501 azurelinux-base 457.7 KiB - Installing dependencies: - oniguruma x86_64 6.9.10-3.azl4~20260501 azurelinux-base 763.1 KiB Transaction Summary: - Installing: 2 packages - Total size of inbound packages is 428 KiB. Need to download 428 KiB. - After this operation, 1 MiB extra will be used (install 1 MiB, remove 0 B). + Upgrading: 2 packages + Operation aborted by the user. -In DNF 5: Sample output for the command ' sudo dnf5 install --assumeno --skip-broken git' Failure case : Dependency Fails and exit code : 0 +In DNF 5: Sample output for the command 'sudo dnf5 upgrade --assumeno p11-kit.x86_64 p11-kit.x86_64' Updating and loading repositories: Repositories loaded. - Problem: package perl-Getopt-Long-1:2.58-521.azl4~20260501.noarch from azurelinux-base requires perl(Pod::Usage) >= 1.14, but none of the providers can be installed - - package perl-Pod-Usage-4:2.05-521.azl4~20260501.noarch from azurelinux-base requires perl(Pod::Text) >= 4, but none of the providers can be installed - - package git-2.53.0-2.azl4~20260501.x86_64 from azurelinux-base requires perl(Getopt::Long), but none of the providers can be installed - - package perl-podlators-1:6.0.2-521.azl4~20260501.noarch from azurelinux-base requires perl(Pod::Simple) >= 3.26, but none of the providers can be installed - - conflicting requests - - nothing provides perl(Text::Wrap) >= 98.112902 needed by perl-Pod-Simple-1:3.47-4.azl4~20260501.noarch from azurelinux-base - Package Arch Version Repository Size - Skipping packages with broken dependencies: - git x86_64 2.53.0-2.azl4~202 azurelinux 56.4 KiB - perl-Getopt-Long noarch 1:2.58-521.azl4~2 azurelinux 144.5 KiB - perl-Pod-Simple noarch 1:3.47-4.azl4~202 azurelinux 565.3 KiB - perl-Pod-Usage noarch 4:2.05-521.azl4~2 azurelinux 86.3 KiB - perl-podlators noarch 1:6.0.2-521.azl4~ azurelinux 317.5 KiB - Nothing to do. + Package Arch Version Repository Size + Upgrading: + p11-kit x86_64 0.26.2-3.azl4 azurelinux-base 2.5 MiB + replacing p11-kit x86_64 0.26.2-2.azl4 64806a6b30824b51aafe6d2d87587286 2.5 MiB + p11-kit-trust x86_64 0.26.2-3.azl4 azurelinux-base 466.2 KiB + replacing p11-kit-trust x86_64 0.26.2-2.azl4 64806a6b30824b51aafe6d2d87587286 466.5 KiB -In DNF 5: Sample output for the command ' sudo dnf5 install --assumeno --skip-broken NetworkManager' Success case : Dependency Fails and exit code 1 - Updating and loading repositories: - Repositories loaded. - Package Arch Version Repository Size - Installing: - NetworkManager x86_64 1:1.54.3-3.azl4~20260501 azurelinux-base 5.9 MiB - Installing dependencies: - NetworkManager-libnm x86_64 1:1.54.3-3.azl4~20260501 azurelinux-base 10.0 MiB - libndp x86_64 1.9-5.azl4~20260501 azurelinux-base 86.0 KiB Transaction Summary: - Installing: 3 packages - Total size of inbound packages is 4 MiB. Need to download 4 MiB. - After this operation, 16 MiB extra will be used (install 16 MiB, remove 0 B). + Upgrading: 2 packages + Replacing: 2 packages + + Total size of inbound packages is 664 KiB. Need to download 664 KiB. + After this operation, 535 B will be freed (install 3 MiB, remove 3 MiB). Operation aborted by the user. - Exit Code : 1 + +Ind DNF 5 : Sample outut for the command 'sudo dnf5 upgrade --assumeno openssl-999.999' : Failure Case , exit code :1 + Updating and loading repositories: + Repositories loaded. + Failed to resolve the transaction: + No match for argument: openssl-999.999 + You can try to add to command line: + --skip-unavailable to skip unavailable packages From d56a67c8fbb846035f79400269be15a93b6565be Mon Sep 17 00:00:00 2001 From: Yashna Parikh Date: Thu, 25 Jun 2026 11:36:33 -0400 Subject: [PATCH 2/4] Update mocks to cover code path --- .../tests/library/LegacyEnvLayerExtensions.py | 37 ++++++++++++++----- 1 file changed, 27 insertions(+), 10 deletions(-) diff --git a/src/core/tests/library/LegacyEnvLayerExtensions.py b/src/core/tests/library/LegacyEnvLayerExtensions.py index cfecf921..b13d3320 100644 --- a/src/core/tests/library/LegacyEnvLayerExtensions.py +++ b/src/core/tests/library/LegacyEnvLayerExtensions.py @@ -725,15 +725,18 @@ def run_command_output(self, cmd, no_output=False, chk_err=True): "python3.x86_64 3.12.3-6.azl4~20260501 azurelinux-base\n" 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" @@ -1086,6 +1089,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 = ( @@ -1254,7 +1271,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" + \ From 0327c051d1bfca4aac3eadbc2af4770e46886a47 Mon Sep 17 00:00:00 2001 From: Yashna Parikh Date: Thu, 25 Jun 2026 11:59:38 -0400 Subject: [PATCH 3/4] Address some of Copilot comments --- .../package_managers/Dnf5PackageManager.py | 2 +- .../dnf5_output_expected_format | 22 ++++++++++--------- 2 files changed, 13 insertions(+), 11 deletions(-) diff --git a/src/core/src/package_managers/Dnf5PackageManager.py b/src/core/src/package_managers/Dnf5PackageManager.py index 4f03318d..fc463629 100644 --- a/src/core/src/package_managers/Dnf5PackageManager.py +++ b/src/core/src/package_managers/Dnf5PackageManager.py @@ -277,7 +277,7 @@ def extract_dependencies(self, output, packages): # Handle non-blocking dependency failure / nothing-to-do cases if any(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)") + 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"] diff --git a/src/tools/references/cmd_output_references/dnf5_output_expected_format b/src/tools/references/cmd_output_references/dnf5_output_expected_format index 3911ea2f..d97659c1 100644 --- a/src/tools/references/cmd_output_references/dnf5_output_expected_format +++ b/src/tools/references/cmd_output_references/dnf5_output_expected_format @@ -1,22 +1,24 @@ Example of different output formats for DNF 5 commands: -In DNF 5: Sample output for the command 'sudo dnf5 upgrade --assumeno openssl' Success case : Exit code :1 +In DNF 5: Sample command output for 'sudo dnf5 upgrade --assumeno openssl' Success case: Exit code: 1 Updating and loading repositories: Repositories loaded. - - Package Arch Version Repository + Package Arch Version Repository Size Upgrading: - openssl x86_64 3.x.x azurelinux-base - - Upgrading dependencies: - openssl-libs x86_64 3.x.x azurelinux-base + openssl x86_64 1:3.5.4-7.azl4 azurelinux-base 1.8 MiB + replacing openssl x86_64 1:3.5.4-4.azl4 64806a6b30824b51aafe6d2d87587286 1.8 MiB + openssl-libs x86_64 1:3.5.4-7.azl4 azurelinux-base 6.5 MiB + replacing openssl-libs x86_64 1:3.5.4-4.azl4 64806a6b30824b51aafe6d2d87587286 6.5 MiB Transaction Summary: - Upgrading: 2 packages + Upgrading: 2 packages + Replacing: 2 packages + Total size of inbound packages is 3 MiB. Need to download 3 MiB. + After this operation, 32 B extra will be used (install 8 MiB, remove 8 MiB). Operation aborted by the user. -In DNF 5: Sample output for the command 'sudo dnf5 upgrade --assumeno p11-kit.x86_64 p11-kit.x86_64' +In DNF 5: Sample output for the command 'sudo dnf5 upgrade --assumeno p11-kit.x86_64' Updating and loading repositories: Repositories loaded. Package Arch Version Repository Size @@ -34,7 +36,7 @@ In DNF 5: Sample output for the command 'sudo dnf5 upgrade --assumeno p11-kit.x8 After this operation, 535 B will be freed (install 3 MiB, remove 3 MiB). Operation aborted by the user. -Ind DNF 5 : Sample outut for the command 'sudo dnf5 upgrade --assumeno openssl-999.999' : Failure Case , exit code :1 +In DNF 5 : Sample outut for the command 'sudo dnf5 upgrade --assumeno openssl-999.999' : Failure Case , exit code :1 Updating and loading repositories: Repositories loaded. Failed to resolve the transaction: From 10fa093b639f75d785805b0f7b9d0a0c65b23e5d Mon Sep 17 00:00:00 2001 From: Yashna Parikh Date: Thu, 25 Jun 2026 12:43:09 -0400 Subject: [PATCH 4/4] Covered missing lines coverage --- src/core/tests/Test_Dnf5PackageManager.py | 7 +++++++ src/core/tests/library/LegacyEnvLayerExtensions.py | 9 +++++++++ 2 files changed, 16 insertions(+) diff --git a/src/core/tests/Test_Dnf5PackageManager.py b/src/core/tests/Test_Dnf5PackageManager.py index 450ac74f..2a66eb9c 100644 --- a/src/core/tests/Test_Dnf5PackageManager.py +++ b/src/core/tests/Test_Dnf5PackageManager.py @@ -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') diff --git a/src/core/tests/library/LegacyEnvLayerExtensions.py b/src/core/tests/library/LegacyEnvLayerExtensions.py index b13d3320..0913fb34 100644 --- a/src/core/tests/library/LegacyEnvLayerExtensions.py +++ b/src/core/tests/library/LegacyEnvLayerExtensions.py @@ -905,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 = ''