Skip to content

Commit 8eb39ae

Browse files
committed
UT Coverage
1 parent 58ec813 commit 8eb39ae

3 files changed

Lines changed: 101 additions & 45 deletions

File tree

src/core/src/package_managers/Dnf5PackageManager.py

Lines changed: 11 additions & 25 deletions
Original file line numberDiff line numberDiff line change
@@ -45,7 +45,7 @@ def __init__(self, env_layer, execution_config, composite_logger, telemetry_writ
4545

4646
# Support to check if reboot is required
4747
# dnf-utils not required (needs-restarting is built into dnf5)
48-
self.needs_restarting_with_flag = 'sudo LANG=en_US.UTF8 dnf5 needs-restarting'
48+
self.needs_restarting_with_flag = 'sudo LANG=en_US.UTF8 dnf5 needs-restarting -r'
4949

5050
# DNF5 exit codes
5151
self.dnf_exitcode_ok = 0
@@ -552,9 +552,8 @@ def backup_image_default_patch_configuration_if_not_exists(self):
552552
self.env_layer.file_system.write_with_retry(self.image_default_patch_configuration_backup_path,'{0}'.format(json.dumps(image_default_patch_configuration_backup)),mode='w+')
553553

554554
except Exception as error:
555-
error_message = "[DNF5] Exception during fetching and logging default auto update settings on the machine. [Exception={0}]".format(repr(error))
556-
self.composite_logger.log_error(error_message)
557-
self.status_handler.add_error_to_status(error_message, Constants.PatchOperationErrorCodes.DEFAULT_ERROR)
555+
self.composite_logger.log_error( "[DNF5] Exception during fetching and logging default auto update settings on the machine. [Exception={0}]".format(repr(error)))
556+
self.status_handler.add_error_to_status( "[DNF5] Exception during fetching and logging default auto update settings on the machine. [Exception={0}]".format(repr(error)), Constants.PatchOperationErrorCodes.DEFAULT_ERROR)
558557
raise
559558

560559
def is_image_default_patch_configuration_backup_valid(self, image_default_patch_configuration_backup):
@@ -845,25 +844,15 @@ def is_service_set_to_enable_on_reboot(self, command):
845844

846845
def __set_dnf5_automatic_execstart_flags(self, download_updates=None, apply_updates=None):
847846
try:
848-
if download_updates is None and apply_updates is None:
849-
self.__remove_dnf5_automatic_execstart_override()
850-
return
851-
852847
flags = ["/usr/bin/dnf5", "automatic", "--timer"]
853848

854849
if apply_updates is True:
855850
flags.append("--installupdates")
856-
857-
elif apply_updates is False:
858-
if download_updates is True:
859-
flags.append("--downloadupdates")
851+
flags.append("--downloadupdates")
852+
else: # apply_updates is False OR None
853+
if apply_updates is False:
860854
flags.append("--no-installupdates")
861-
elif download_updates is False:
862-
flags.append("--no-downloadupdates")
863-
flags.append("--no-installupdates")
864-
else:
865-
flags.append("--no-installupdates")
866-
else:
855+
867856
if download_updates is True:
868857
flags.append("--downloadupdates")
869858
elif download_updates is False:
@@ -877,16 +866,14 @@ def __set_dnf5_automatic_execstart_flags(self, download_updates=None, apply_upda
877866
self.composite_logger.log_debug("[DNF5] Wrote override. [ExecStart={0}]".format(" ".join(flags)))
878867

879868
except Exception as error:
880-
error_msg = "[DNF5] Error writing override. [Exception={0}]".format(repr(error))
881-
self.composite_logger.log_error(error_msg)
882-
self.status_handler.add_error_to_status(error_msg, Constants.PatchOperationErrorCodes.DEFAULT_ERROR)
869+
self.composite_logger.log_error("[DNF5] Error writing override. [Exception={0}]".format(repr(error)))
870+
self.status_handler.add_error_to_status("[DNF5] Error writing override. [Exception={0}]".format(repr(error)), Constants.PatchOperationErrorCodes.DEFAULT_ERROR)
883871
raise
884872

885873
def __remove_dnf5_automatic_execstart_override(self):
886874
"""Removes systemd override file for dnf5-automatic if it exists."""
887875
try:
888876
try:
889-
# self.env_layer.file_system.write_with_retry(self.dnf5_automatic_override_file,"",mode='w+')
890877
if os.path.exists(self.dnf5_automatic_override_file):
891878
os.remove(self.dnf5_automatic_override_file)
892879
self.composite_logger.log_debug("[DNF5] Removed override file. [File={0}]".format(self.dnf5_automatic_override_file))
@@ -900,9 +887,8 @@ def __remove_dnf5_automatic_execstart_override(self):
900887
self.composite_logger.log_debug("[DNF5] Cleared dnf5 automatic override file. [File={0}]".format(self.dnf5_automatic_override_file))
901888

902889
except Exception as error:
903-
error_msg = "[DNF5] Error removing dnf5 automatic override. [Exception={0}]".format(repr(error))
904-
self.composite_logger.log_error(error_msg)
905-
self.status_handler.add_error_to_status(error_msg, Constants.PatchOperationErrorCodes.DEFAULT_ERROR)
890+
self.composite_logger.log_error("[DNF5] Error removing dnf5 automatic override. [Exception={0}]".format(repr(error)))
891+
self.status_handler.add_error_to_status("[DNF5] Error removing dnf5 automatic override. [Exception={0}]".format(repr(error)), Constants.PatchOperationErrorCodes.DEFAULT_ERROR)
906892
raise
907893

908894
# endregion

src/core/tests/Test_Dnf5PackageManager.py

Lines changed: 79 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -17,6 +17,8 @@
1717
import os
1818
import sys
1919
import unittest
20+
from unittest.mock import patch
21+
2022
# Conditional import for StringIO
2123
try:
2224
from StringIO import StringIO # Python 2
@@ -643,6 +645,12 @@ def test_package_manager_dnf5(self):
643645
size = package_manager.get_package_size(out)
644646
self.assertEqual(size, "135.09k")
645647

648+
# test for get_package_size when size is not available
649+
cmd = package_manager.single_package_upgrade_cmd + "systemd"
650+
code, out = self.runtime.env_layer.run_command_output(cmd, False, False)
651+
size = package_manager.get_package_size(out)
652+
self.assertEqual(size, Constants.UNKNOWN_PACKAGE_SIZE)
653+
646654
# Test: get_all_available_versions_of_package (ONLY python3 since mock exists)
647655
versions = package_manager.get_all_available_versions_of_package("python3")
648656
self.assertTrue(versions is not None)
@@ -801,9 +809,7 @@ def test_update_image_default_patch_mode_dnf5(self):
801809

802810
override_read = self.runtime.env_layer.file_system.read_with_retry(override_file)
803811
self.assertTrue(override_read is not None)
804-
self.assertTrue("--no-installupdates" in override_read)
805812
self.assertTrue("--downloadupdates" in override_read)
806-
self.assertTrue("--no-downloadupdates" not in override_read)
807813
self.assertTrue("--installupdates" not in override_read)
808814

809815
# -----------------------------
@@ -837,9 +843,7 @@ def test_update_image_default_patch_mode_dnf5(self):
837843

838844
override_read = self.runtime.env_layer.file_system.read_with_retry(override_file)
839845
self.assertTrue(override_read is not None)
840-
self.assertTrue("--no-installupdates" in override_read)
841846
self.assertTrue("--downloadupdates" in override_read)
842-
self.assertTrue("--no-downloadupdates" not in override_read)
843847
self.assertTrue("--installupdates" not in override_read)
844848

845849

@@ -883,6 +887,77 @@ def test_install_security_updates_azgps_coordinated(self):
883887
self.assertTrue(code == 0)
884888
self.assertTrue("Complete!", out)
885889

890+
def test_backup_image_default_patch_configuration_with_downloadupdates_only_execstart(self):
891+
"""
892+
DNF5:
893+
Validates backup creation when current ExecStart contains only --downloadupdates
894+
and no explicit apply flag.
895+
896+
Expected backup:
897+
- installation_state = True
898+
- enable_on_reboot = True
899+
- download_updates = "yes"
900+
- apply_updates = ""
901+
"""
902+
self.runtime.set_legacy_test_type('AnotherHappyPath')
903+
package_manager = self.container.get('package_manager')
904+
self.assertIsNotNone(package_manager)
905+
906+
# Arrange
907+
package_manager._Dnf5PackageManager__init_auto_update_for_dnf5_automatic()
908+
909+
# Make sure no stale backup exists from previous runs
910+
if os.path.exists(package_manager.image_default_patch_configuration_backup_path):
911+
os.remove(package_manager.image_default_patch_configuration_backup_path)
912+
913+
# Make sure service override dir exists
914+
override_dir = os.path.dirname(package_manager.dnf5_automatic_override_file)
915+
os.makedirs(override_dir, exist_ok=True)
916+
917+
# Write override with ONLY --downloadupdates
918+
override_text = "[Service]\nExecStart=\nExecStart=/usr/bin/dnf5 automatic --timer --downloadupdates\n"
919+
self.runtime.env_layer.file_system.write_with_retry(
920+
package_manager.dnf5_automatic_override_file,
921+
override_text,
922+
mode='w+'
923+
)
924+
925+
# Act
926+
package_manager.backup_image_default_patch_configuration_if_not_exists()
927+
928+
# Assert
929+
self.assertTrue(os.path.exists(package_manager.image_default_patch_configuration_backup_path))
930+
931+
backup_text = self.runtime.env_layer.file_system.read_with_retry(package_manager.image_default_patch_configuration_backup_path)
932+
backup_json = json.loads(backup_text)
933+
934+
self.assertIn(package_manager.current_auto_os_update_service, backup_json)
935+
936+
service_backup = backup_json[package_manager.current_auto_os_update_service]
937+
938+
self.assertEqual("yes", service_backup[package_manager.download_updates_identifier_text])
939+
self.assertEqual("", service_backup[package_manager.apply_updates_identifier_text])
940+
self.assertFalse(service_backup[package_manager.enable_on_reboot_identifier_text])
941+
self.assertTrue(service_backup[package_manager.installation_state_identifier_text])
942+
943+
944+
def test_set_dnf5_automatic_execstart_flags_exception_handling(self):
945+
"""Test exception handling when override file write fails"""
946+
self.runtime.set_legacy_test_type('HappyPath')
947+
package_manager = self.container.get('package_manager')
948+
949+
# Mock file_system.write_with_retry to raise exception
950+
with patch.object(package_manager.env_layer.file_system, 'write_with_retry') as mock_write:
951+
mock_write.side_effect = IOError("Permission denied")
952+
953+
# Should raise exception caught by outer try-except
954+
self.assertRaises(Exception, package_manager._Dnf5PackageManager__set_dnf5_automatic_execstart_flags,
955+
download_updates=False, apply_updates=False)
956+
957+
def test_get_package_install_expected_avg_time_in_seconds(self):
958+
self.runtime.set_legacy_test_type('HappyPath')
959+
package_manager = self.container.get('package_manager')
960+
self.assertTrue(package_manager.get_package_install_expected_avg_time_in_seconds(), 90)
886961

887962
if __name__ == '__main__':
888963
unittest.main()

src/core/tests/library/LegacyEnvLayerExtensions.py

Lines changed: 11 additions & 16 deletions
Original file line numberDiff line numberDiff line change
@@ -678,14 +678,6 @@ def run_command_output(self, cmd, no_output=False, chk_err=True):
678678
"python3.x86_64 3.12.3-4.azl4~20260501 azurelinux-base\n" + \
679679
"python3.x86_64 3.12.3-5.azl4~20260501 azurelinux-base\n" + \
680680
"python3.x86_64 3.12.3-6.azl4~20260501 azurelinux-base\n"
681-
682-
elif ("list available" in cmd) and ("hyperv-daemons.x86_64" in cmd):
683-
code = 0
684-
output = "Updating and loading repositories:\n" + \
685-
"Repositories loaded.\n" + \
686-
"Available packages\n" + \
687-
"hyperv-daemons.x86_64 6.10-3.azl4~20260501 azurelinux-base\n"
688-
689681
elif cmd.find("dnf5 install --assumeno --skip-broken") > -1 and "hyperv-daemons" in cmd:
690682
code = 1
691683
output = "Updating and loading repositories:\n" + \
@@ -761,10 +753,7 @@ def run_command_output(self, cmd, no_output=False, chk_err=True):
761753
code = 0
762754
output = ''
763755
elif self.legacy_package_manager_name is Constants.DNF:
764-
if cmd.find("systemctl cat dnf5-automatic.service") > -1:
765-
code = 0
766-
output = "ExecStart=/usr/bin/dnf5 automatic --timer --downloadupdates --no-installupdates"
767-
elif cmd.find("systemctl enable --now dnf5-automatic.timer") > -1:
756+
if cmd.find("systemctl enable --now dnf5-automatic.timer") > -1:
768757
code = 1
769758
output = ''
770759
else:
@@ -854,6 +843,16 @@ def run_command_output(self, cmd, no_output=False, chk_err=True):
854843
elif self.legacy_test_type == 'ExceptionPath':
855844
code = -1
856845
output = ''
846+
elif self.legacy_test_type == 'AnotherHappyPath':
847+
if cmd.find("systemctl cat dnf5-automatic.service") > -1:
848+
code = 0
849+
output = "ExecStart=/usr/bin/dnf5 automatic --timer --downloadupdates"
850+
elif cmd.find("rpm -qa") > -1:
851+
code = 0
852+
output = 'dnf5-plugin-automatic'
853+
elif "systemctl is-enabled" in cmd:
854+
code = 0
855+
output = "disabled"
857856
elif self.legacy_test_type == 'SuccessInstallPath':
858857
if cmd.find("cat /proc/cpuinfo | grep name") > -1:
859858
code = 0
@@ -1618,10 +1617,6 @@ def run_command_output(self, cmd, no_output=False, chk_err=True):
16181617
if cmd.find("systemctl list-unit-files --type=service | grep dnf-automatic.service") > -1:
16191618
code = 0
16201619
output = 'Auto update service installed'
1621-
elif self.legacy_package_manager_name is Constants.DNF:
1622-
if cmd.find("systemctl list-unit-files --type=service | grep dnf5-automatic.service") > -1:
1623-
code = 0
1624-
output = 'Auto update service installed'
16251620
major_version = self.get_python_major_version()
16261621
if major_version == 2:
16271622
return code, output.decode('utf8', 'ignore').encode('ascii', 'ignore')

0 commit comments

Comments
 (0)