Skip to content

Commit d503b88

Browse files
committed
add unit test for fstab gen
1 parent 8c05d2a commit d503b88

1 file changed

Lines changed: 252 additions & 0 deletions

File tree

test/unit/test_otaclient/test_boot_control/test_grub_new.py

Lines changed: 252 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -35,6 +35,7 @@
3535
MENUENTRY_TITLE_PA,
3636
ABPartitionDetector,
3737
_BootMenuEntry,
38+
_GrubBootControl,
3839
_GrubBootHelperFuncs,
3940
iter_menuentries,
4041
)
@@ -721,3 +722,254 @@ def test_invalid_json_raises(self, mocker):
721722

722723
with pytest.raises(GrubBootControllerError, match="failed to detect"):
723724
ABPartitionDetector._list_partitions("/dev/sda1")
725+
726+
727+
#
728+
# ------ _GrubBootControl._generate_fstab ------
729+
#
730+
731+
732+
def _fstab_entry(uuid: str, mp: str, fstype: str, opts: str, dump: str, pass_: str) -> str:
733+
return f"UUID={uuid}\t{mp}\t{fstype}\t{opts}\t{dump}\t{pass_}"
734+
735+
736+
def _build_fstab(*entries: str, comments: tuple = ()) -> str:
737+
lines = list(comments) + [e + "\n" for e in entries]
738+
return "".join(lines)
739+
740+
741+
# Reusable fstab entries — synthetic.
742+
_ROOT_SYNTH = _fstab_entry("aaaa-1111", "/", "ext4", "errors=remount-ro", "0", "1")
743+
_BOOT_SYNTH = _fstab_entry("bbbb-2222", "/boot", "ext4", "defaults", "0", "1")
744+
_EFI_SYNTH = _fstab_entry("cccc-3333", "/boot/efi", "vfat", "umask=0077", "0", "1")
745+
_ROOT_SYNTH_STANDBY = _fstab_entry("old-root", "/", "ext4", "errors=remount-ro", "0", "1")
746+
_DATA_SYNTH = _fstab_entry("dddd-4444", "/data", "ext4", "defaults", "0", "2")
747+
_HOME_SYNTH = _fstab_entry("eeee-5555", "/home", "ext4", "defaults", "0", "2")
748+
_VARLOG_SYNTH = _fstab_entry("ffff-6666", "/var/log", "ext4", "defaults", "0", "2")
749+
750+
# Reusable fstab entries — real-world OTA setup ECU.
751+
_ROOT_REAL = _fstab_entry(
752+
"cb7519f4-924b-4f2c-ac30-9318e31cf64e", "/", "ext4", "errors=remount-ro", "0", "1"
753+
)
754+
_BOOT_REAL = _fstab_entry(
755+
"d9a87440-5dcf-4fa1-994a-e84f8a9ae9df", "/boot", "ext4", "defaults", "0", "1"
756+
)
757+
_EFI_REAL = _fstab_entry("CB4C-72D3", "/boot/efi", "vfat", "defaults", "0", "1")
758+
_DATA_REAL = _fstab_entry(
759+
"ba7ed9ca-0188-4f66-bb01-b1ac990f2a31", "/data", "ext4", "defaults", "0", "2"
760+
)
761+
762+
_SYNTH_STANDBY_FSUUID = "new-standby-uuid"
763+
_REAL_STANDBY_FSUUID = "5f563e84-6422-4844-aa82-f912cf8561b8"
764+
765+
766+
def _make_grub_ctrl(mocker, *, boot_uuid, efi_uuid):
767+
"""Create a _GrubBootControl instance bypassing __init__."""
768+
ctrl = object.__new__(_GrubBootControl)
769+
boot_slots = mocker.MagicMock()
770+
boot_slots.boot_partition.uuid = boot_uuid
771+
if efi_uuid is not None:
772+
boot_slots.efi_partition.uuid = efi_uuid
773+
else:
774+
boot_slots.efi_partition = None
775+
ctrl.boot_slots = boot_slots
776+
return ctrl
777+
778+
779+
@pytest.mark.parametrize(
780+
"boot_uuid, efi_uuid, slot_fsuuid, "
781+
"reference_fstab, base_fstab, expected_lines",
782+
[
783+
pytest.param(
784+
"bbbb-2222",
785+
"cccc-3333",
786+
_SYNTH_STANDBY_FSUUID,
787+
_build_fstab(
788+
_ROOT_SYNTH, _BOOT_SYNTH, _EFI_SYNTH,
789+
comments=("# /etc/fstab: static file system information.\n",),
790+
),
791+
_build_fstab(_ROOT_SYNTH_STANDBY, _BOOT_SYNTH, _EFI_SYNTH, _DATA_SYNTH),
792+
[
793+
_fstab_entry(_SYNTH_STANDBY_FSUUID, "/", "ext4", "errors=remount-ro", "0", "1"),
794+
_BOOT_SYNTH,
795+
_EFI_SYNTH,
796+
_DATA_SYNTH,
797+
],
798+
id="synth_with_extra_mount",
799+
),
800+
pytest.param(
801+
"bbbb-2222",
802+
"cccc-3333",
803+
_SYNTH_STANDBY_FSUUID,
804+
_build_fstab(_ROOT_SYNTH, _BOOT_SYNTH, _EFI_SYNTH),
805+
_build_fstab(
806+
_ROOT_SYNTH_STANDBY, _BOOT_SYNTH, _EFI_SYNTH,
807+
_DATA_SYNTH, _HOME_SYNTH, _VARLOG_SYNTH,
808+
),
809+
[
810+
_fstab_entry(_SYNTH_STANDBY_FSUUID, "/", "ext4", "errors=remount-ro", "0", "1"),
811+
_BOOT_SYNTH,
812+
_EFI_SYNTH,
813+
_DATA_SYNTH,
814+
_HOME_SYNTH,
815+
_VARLOG_SYNTH,
816+
],
817+
id="synth_multiple_extra_mounts",
818+
),
819+
pytest.param(
820+
"d9a87440-5dcf-4fa1-994a-e84f8a9ae9df",
821+
"CB4C-72D3",
822+
_REAL_STANDBY_FSUUID,
823+
_build_fstab(_ROOT_REAL, _BOOT_REAL, _EFI_REAL),
824+
_build_fstab(_ROOT_REAL, _BOOT_REAL, _EFI_REAL),
825+
[
826+
_fstab_entry(_REAL_STANDBY_FSUUID, "/", "ext4", "errors=remount-ro", "0", "1"),
827+
_BOOT_REAL,
828+
_EFI_REAL,
829+
],
830+
id="real_ecu",
831+
),
832+
pytest.param(
833+
"d9a87440-5dcf-4fa1-994a-e84f8a9ae9df",
834+
"CB4C-72D3",
835+
_REAL_STANDBY_FSUUID,
836+
_build_fstab(_ROOT_REAL, _BOOT_REAL, _EFI_REAL),
837+
_build_fstab(_ROOT_REAL, _BOOT_REAL, _EFI_REAL, _DATA_REAL),
838+
[
839+
_fstab_entry(_REAL_STANDBY_FSUUID, "/", "ext4", "errors=remount-ro", "0", "1"),
840+
_BOOT_REAL,
841+
_EFI_REAL,
842+
_DATA_REAL,
843+
],
844+
id="real_ecu_with_extra_mount",
845+
),
846+
],
847+
)
848+
class TestGenerateFstab:
849+
"""Tests for _GrubBootControl._generate_fstab."""
850+
851+
def test_expected_output(
852+
self, mocker, boot_uuid, efi_uuid, slot_fsuuid,
853+
reference_fstab, base_fstab, expected_lines,
854+
):
855+
ctrl = _make_grub_ctrl(mocker, boot_uuid=boot_uuid, efi_uuid=efi_uuid)
856+
result = ctrl._generate_fstab(
857+
base_fstab=base_fstab,
858+
reference_fstab=reference_fstab,
859+
slot_fsuuid=slot_fsuuid,
860+
)
861+
assert result.strip().splitlines() == expected_lines
862+
863+
def test_trailing_newline(
864+
self, mocker, boot_uuid, efi_uuid, slot_fsuuid,
865+
reference_fstab, base_fstab, expected_lines,
866+
):
867+
ctrl = _make_grub_ctrl(mocker, boot_uuid=boot_uuid, efi_uuid=efi_uuid)
868+
result = ctrl._generate_fstab(
869+
base_fstab=base_fstab,
870+
reference_fstab=reference_fstab,
871+
slot_fsuuid=slot_fsuuid,
872+
)
873+
assert result.endswith("\n")
874+
875+
def test_no_comments_in_output(
876+
self, mocker, boot_uuid, efi_uuid, slot_fsuuid,
877+
reference_fstab, base_fstab, expected_lines,
878+
):
879+
ctrl = _make_grub_ctrl(mocker, boot_uuid=boot_uuid, efi_uuid=efi_uuid)
880+
result = ctrl._generate_fstab(
881+
base_fstab=base_fstab,
882+
reference_fstab=reference_fstab,
883+
slot_fsuuid=slot_fsuuid,
884+
)
885+
for line in result.strip().splitlines():
886+
assert not line.startswith("#")
887+
888+
def test_special_entries_not_duplicated(
889+
self, mocker, boot_uuid, efi_uuid, slot_fsuuid,
890+
reference_fstab, base_fstab, expected_lines,
891+
):
892+
ctrl = _make_grub_ctrl(mocker, boot_uuid=boot_uuid, efi_uuid=efi_uuid)
893+
result = ctrl._generate_fstab(
894+
base_fstab=base_fstab,
895+
reference_fstab=reference_fstab,
896+
slot_fsuuid=slot_fsuuid,
897+
)
898+
mount_points = [line.split("\t")[1] for line in result.strip().splitlines()]
899+
assert mount_points.count("/") == 1
900+
assert mount_points.count("/boot") == 1
901+
assert mount_points.count("/boot/efi") == 1
902+
903+
def test_boot_appears_before_efi(
904+
self, mocker, boot_uuid, efi_uuid, slot_fsuuid,
905+
reference_fstab, base_fstab, expected_lines,
906+
):
907+
ctrl = _make_grub_ctrl(mocker, boot_uuid=boot_uuid, efi_uuid=efi_uuid)
908+
result = ctrl._generate_fstab(
909+
base_fstab=base_fstab,
910+
reference_fstab=reference_fstab,
911+
slot_fsuuid=slot_fsuuid,
912+
)
913+
mount_points = [line.split("\t")[1] for line in result.strip().splitlines()]
914+
assert mount_points.index("/boot") < mount_points.index("/boot/efi")
915+
916+
917+
class TestGenerateFstabFallback:
918+
"""Tests for _generate_fstab fallback paths (no reference or missing entries)."""
919+
920+
@pytest.mark.parametrize(
921+
"boot_uuid, efi_uuid, reference_fstab, slot_fsuuid, "
922+
"expected_root, expected_boot, expected_efi",
923+
[
924+
pytest.param(
925+
"bbbb-2222",
926+
"cccc-3333",
927+
None,
928+
_SYNTH_STANDBY_FSUUID,
929+
_fstab_entry(_SYNTH_STANDBY_FSUUID, "/", "ext4", "errors=remount-ro", "0", "1"),
930+
_fstab_entry("bbbb-2222", "/boot", "ext4", "defaults", "0", "1"),
931+
_fstab_entry("cccc-3333", "/boot/efi", "vfat", "defaults", "0", "1"),
932+
id="no_reference",
933+
),
934+
pytest.param(
935+
"boot-uuid-fb",
936+
"efi-uuid-fb",
937+
_build_fstab(_ROOT_SYNTH),
938+
_SYNTH_STANDBY_FSUUID,
939+
_fstab_entry(_SYNTH_STANDBY_FSUUID, "/", "ext4", "errors=remount-ro", "0", "1"),
940+
_fstab_entry("boot-uuid-fb", "/boot", "ext4", "defaults", "0", "1"),
941+
_fstab_entry("efi-uuid-fb", "/boot/efi", "vfat", "defaults", "0", "1"),
942+
id="reference_missing_boot_and_efi",
943+
),
944+
],
945+
)
946+
def test_fallback_entries(
947+
self, mocker, boot_uuid, efi_uuid, reference_fstab, slot_fsuuid,
948+
expected_root, expected_boot, expected_efi,
949+
):
950+
ctrl = _make_grub_ctrl(mocker, boot_uuid=boot_uuid, efi_uuid=efi_uuid)
951+
result = ctrl._generate_fstab(
952+
base_fstab=_build_fstab(_ROOT_SYNTH_STANDBY),
953+
reference_fstab=reference_fstab,
954+
slot_fsuuid=slot_fsuuid,
955+
)
956+
lines = result.strip().splitlines()
957+
assert lines[0] == expected_root
958+
assert lines[1] == expected_boot
959+
assert lines[2] == expected_efi
960+
961+
def test_no_efi_partition_omits_efi_entry(self, mocker):
962+
"""When efi_partition is None, /boot/efi entry is not included."""
963+
ctrl = _make_grub_ctrl(mocker, boot_uuid="bbbb-2222", efi_uuid=None)
964+
result = ctrl._generate_fstab(
965+
base_fstab=_build_fstab(
966+
_ROOT_SYNTH_STANDBY, _BOOT_SYNTH, _EFI_SYNTH, _DATA_SYNTH,
967+
),
968+
reference_fstab=_build_fstab(_ROOT_SYNTH, _BOOT_SYNTH, _EFI_SYNTH),
969+
slot_fsuuid=_SYNTH_STANDBY_FSUUID,
970+
)
971+
mount_points = [line.split("\t")[1] for line in result.strip().splitlines()]
972+
assert "/" in mount_points
973+
assert "/boot" in mount_points
974+
assert "/boot/efi" not in mount_points
975+
assert "/data" in mount_points

0 commit comments

Comments
 (0)