Fix backup equal and add hash to objects with eq (#6059)

* Fix backup equal and add hash to objects with eq

* Add test for failed consolidate
This commit is contained in:
Mike Degatano 2025-08-04 08:19:33 -04:00 committed by GitHub
parent 478e00c0fe
commit 033896480d
No known key found for this signature in database
GPG Key ID: B5690EEEBB952194
3 changed files with 52 additions and 27 deletions

View File

@ -262,41 +262,35 @@ class Backup(JobGroup):
def __eq__(self, other: Any) -> bool:
"""Return true if backups have same metadata."""
if not isinstance(other, Backup):
return False
return isinstance(other, Backup) and self.slug == other.slug
# Compare all fields except ones about protection. Current encryption status does not affect equality
keys = self._data.keys() | other._data.keys()
for k in keys - IGNORED_COMPARISON_FIELDS:
if (
k not in self._data
or k not in other._data
or self._data[k] != other._data[k]
):
_LOGGER.info(
"Backup %s and %s not equal because %s field has different value: %s and %s",
self.slug,
other.slug,
k,
self._data.get(k),
other._data.get(k),
)
return False
return True
def __hash__(self) -> int:
"""Return hash of backup."""
return hash(self.slug)
def consolidate(self, backup: Self) -> None:
"""Consolidate two backups with same slug in different locations."""
if self.slug != backup.slug:
if self != backup:
raise ValueError(
f"Backup {self.slug} and {backup.slug} are not the same backup"
)
if self != backup:
raise BackupInvalidError(
f"Backup in {backup.location} and {self.location} both have slug {self.slug} but are not the same!"
)
# Compare all fields except ones about protection. Current encryption status does not affect equality
other_data = backup._data # pylint: disable=protected-access
keys = self._data.keys() | other_data.keys()
for k in keys - IGNORED_COMPARISON_FIELDS:
if (
k not in self._data
or k not in other_data
or self._data[k] != other_data[k]
):
raise BackupInvalidError(
f"Cannot consolidate backups in {backup.location} and {self.location} with slug {self.slug} "
f"because field {k} has different values: {self._data.get(k)} and {other_data.get(k)}!",
_LOGGER.error,
)
# In case of conflict we always ignore the ones from the first one. But log them to let the user know
if conflict := {
loc: val.path
for loc, val in self.all_locations.items()

View File

@ -164,10 +164,14 @@ class Mount(CoreSysAttributes, ABC):
"""Return true if successfully mounted and available."""
return self.state == UnitActiveState.ACTIVE
def __eq__(self, other):
def __eq__(self, other: object) -> bool:
"""Return true if mounts are the same."""
return isinstance(other, Mount) and self.name == other.name
def __hash__(self) -> int:
"""Return hash of mount."""
return hash(self.name)
async def load(self) -> None:
"""Initialize object."""
# If there's no mount unit, mount it to make one

View File

@ -185,6 +185,33 @@ async def test_consolidate(
}
@pytest.mark.usefixtures("tmp_supervisor_data")
async def test_consolidate_failure(coresys: CoreSys, tmp_path: Path):
"""Test consolidate with two backups that are not the same."""
(mount_dir := coresys.config.path_mounts / "backup_test").mkdir()
tar1 = Path(copy(get_fixture_path("test_consolidate_unc.tar"), tmp_path))
backup1 = Backup(coresys, tar1, "test", None)
await backup1.load()
tar2 = Path(copy(get_fixture_path("backup_example.tar"), mount_dir))
backup2 = Backup(coresys, tar2, "test", "backup_test")
await backup2.load()
with pytest.raises(
ValueError,
match=f"Backup {backup1.slug} and {backup2.slug} are not the same backup",
):
backup1.consolidate(backup2)
# Force slugs to be the same to run the fields check
backup1._data["slug"] = backup2.slug # pylint: disable=protected-access
with pytest.raises(
BackupInvalidError,
match=f"Cannot consolidate backups in {backup2.location} and {backup1.location} with slug {backup1.slug}",
):
backup1.consolidate(backup2)
@pytest.mark.parametrize(
(
"tarfile_side_effect",