python-tuf/tests/test_metadata_eq_.py
Martin Vrachev ce11102dee Tests: restore objects to initial state after test
Inside test_metadata_eq_.py we test the __eq__ implementations of all
classes. In order to do this, we change the attribute of the object and
then compare them to the unchanged version of those objects.
Usually, we do it in the following steps:
1. create an initial version "a"
2. create a copy of "a" called "b"
3. iterate all attributes inside "b" and change them to a given value
4. check that "a" and "b" are different

We do however forget to restore the object `b` to its initial state
which means we don't check the `__eq__` correctly as we stop on the
first, the found difference which could be of an older attribute changed
in one of the past iterations.

Signed-off-by: Martin Vrachev <mvrachev@vmware.com>
2022-04-13 16:28:31 +03:00

322 lines
12 KiB
Python

#!/usr/bin/env python
# Copyright New York University and the TUF contributors
# SPDX-License-Identifier: MIT OR Apache-2.0
"""Test __eq__ implementations of classes inside tuf/api/metadata.py."""
import copy
import os
import sys
import unittest
from typing import Any, ClassVar, Dict
from securesystemslib.signer import Signature
from tests import utils
from tuf.api.metadata import (
TOP_LEVEL_ROLE_NAMES,
DelegatedRole,
Delegations,
Key,
Metadata,
MetaFile,
Role,
Root,
Snapshot,
TargetFile,
Targets,
Timestamp,
)
class TestMetadataComparisions(unittest.TestCase):
"""Test __eq__ for all classes inside tuf/api/metadata.py."""
metadata: ClassVar[Dict[str, bytes]]
@classmethod
def setUpClass(cls) -> None:
cls.repo_dir = os.path.join(
utils.TESTS_DIR, "repository_data", "repository", "metadata"
)
cls.metadata = {}
for md in TOP_LEVEL_ROLE_NAMES:
with open(os.path.join(cls.repo_dir, f"{md}.json"), "rb") as f:
cls.metadata[md] = f.read()
def copy_and_simple_assert(self, obj: Any) -> Any:
# Assert that obj is not equal to an object from another type
self.assertNotEqual(obj, "")
result_obj = copy.deepcopy(obj)
# Assert that __eq__ works for equal objects.
self.assertEqual(obj, result_obj)
return result_obj
def test_metadata_eq_(self) -> None:
md = Metadata.from_bytes(self.metadata["snapshot"])
md_2: Metadata = self.copy_and_simple_assert(md)
for attr, value in [("signed", None), ("signatures", None)]:
setattr(md_2, attr, value)
self.assertNotEqual(md, md_2, f"Failed case: {attr}")
# Restore the old value of the attribute.
setattr(md_2, attr, getattr(md, attr))
def test_md_eq_signatures_reversed_order(self) -> None:
# Test comparing objects with same signatures but different order.
# Remove all signatures and create new ones.
md = Metadata.from_bytes(self.metadata["snapshot"])
md.signatures = {"a": Signature("a", "a"), "b": Signature("b", "b")}
md_2 = copy.deepcopy(md)
# Reverse signatures order in md_2.
# In python3.7 we need to cast to a list and then reverse.
md_2.signatures = dict(reversed(list(md_2.signatures.items())))
# Assert that both objects are not the same because of signatures order.
self.assertNotEqual(md, md_2)
# but if we fix the signatures order they will be equal
md_2.signatures = {"a": Signature("a", "a"), "b": Signature("b", "b")}
self.assertEqual(md, md_2)
def test_md_eq_special_signatures_tests(self) -> None:
# Test that metadata objects with different signatures are not equal.
md = Metadata.from_bytes(self.metadata["snapshot"])
md_2 = copy.deepcopy(md)
md_2.signatures = {}
self.assertNotEqual(md, md_2)
# Test that metadata objects with empty signatures are equal
md.signatures = {}
self.assertEqual(md, md_2)
# Metadata objects with different signatures types are not equal.
md_2.signatures = "" # type: ignore
self.assertNotEqual(md, md_2)
def test_signed_eq_(self) -> None:
md = Metadata.from_bytes(self.metadata["snapshot"])
md_2: Metadata = self.copy_and_simple_assert(md)
# We don't need to make "signed" = None as that was done when testing
# metadata attribute modifications.
for attr, value in [("version", -1), ("spec_version", "0.0.0")]:
setattr(md_2.signed, attr, value)
self.assertNotEqual(md.signed, md_2.signed, f"Failed case: {attr}")
# Restore the old value of the attribute.
setattr(md_2.signed, attr, getattr(md.signed, attr))
def test_key_eq_(self) -> None:
key_dict = {
"keytype": "rsa",
"scheme": "rsassa-pss-sha256",
"keyval": {"public": "foo"},
}
key = Key.from_dict("12sa12", key_dict)
key_2: Key = self.copy_and_simple_assert(key)
for attr, value in [
("keyid", "a"),
("keytype", "foo"),
("scheme", "b"),
("keytype", "b"),
]:
setattr(key_2, attr, value)
self.assertNotEqual(key, key_2, f"Failed case: {attr}")
# Restore the old value of the attribute.
setattr(key_2, attr, getattr(key, attr))
def test_role_eq_(self) -> None:
role_dict = {
"keyids": ["keyid1", "keyid2"],
"threshold": 3,
}
role = Role.from_dict(role_dict)
role_2: Role = self.copy_and_simple_assert(role)
for attr, value in [("keyids", []), ("threshold", 10)]:
setattr(role_2, attr, value)
self.assertNotEqual(role, role_2, f"Failed case: {attr}")
# Restore the old value of the attribute.
setattr(role_2, attr, getattr(role, attr))
def test_root_eq_(self) -> None:
md = Metadata.from_bytes(self.metadata["root"])
signed_copy: Root = self.copy_and_simple_assert(md.signed)
# Common attributes between Signed and Root doesn't need testing.
# Ignore mypy request for type annotations on attr and value
for attr, value in [ # type: ignore
("consistent_snapshot", None),
("keys", {}),
("roles", {}),
]:
setattr(signed_copy, attr, value)
self.assertNotEqual(md.signed, signed_copy, f"Failed case: {attr}")
# Restore the old value of the attribute.
setattr(signed_copy, attr, getattr(md.signed, attr))
def test_metafile_eq_(self) -> None:
metafile_dict = {
"version": 1,
"length": 12,
"hashes": {"sha256": "abc"},
}
metafile = MetaFile.from_dict(metafile_dict)
metafile_2: MetaFile = self.copy_and_simple_assert(metafile)
# Ignore mypy request for type annotations on attr and value
for attr, value in [ # type: ignore
("version", None),
("length", None),
("hashes", {}),
]:
setattr(metafile_2, attr, value)
self.assertNotEqual(metafile, metafile_2, f"Failed case: {attr}")
# Restore the old value of the attribute.
setattr(metafile_2, attr, getattr(metafile, attr))
def test_timestamp_eq_(self) -> None:
md = Metadata.from_bytes(self.metadata["timestamp"])
signed_copy: Timestamp = self.copy_and_simple_assert(md.signed)
# Common attributes between Signed and Timestamp doesn't need testing.
setattr(signed_copy, "snapshot_meta", None)
self.assertNotEqual(md.signed, signed_copy)
def test_snapshot_eq_(self) -> None:
md = Metadata.from_bytes(self.metadata["snapshot"])
signed_copy: Snapshot = self.copy_and_simple_assert(md.signed)
# Common attributes between Signed and Snapshot doesn't need testing.
setattr(signed_copy, "meta", None)
self.assertNotEqual(md.signed, signed_copy)
def test_delegated_role_eq_(self) -> None:
delegated_role_dict = {
"keyids": ["keyid"],
"name": "a",
"terminating": False,
"threshold": 1,
"paths": ["fn1", "fn2"],
}
delegated_role = DelegatedRole.from_dict(delegated_role_dict)
delegated_role_2: DelegatedRole = self.copy_and_simple_assert(
delegated_role
)
# Common attributes between DelegatedRole and Role doesn't need testing.
for attr, value in [
("name", ""),
("terminating", None),
("paths", [""]),
("path_hash_prefixes", [""]),
]:
setattr(delegated_role_2, attr, value)
msg = f"Failed case: {attr}"
self.assertNotEqual(delegated_role, delegated_role_2, msg)
# Restore the old value of the attribute.
setattr(delegated_role_2, attr, getattr(delegated_role, attr))
def test_delegations_eq_(self) -> None:
delegations_dict = {
"keys": {
"keyid2": {
"keytype": "ed25519",
"scheme": "ed25519",
"keyval": {"public": "bar"},
}
},
"roles": [
{
"keyids": ["keyid2"],
"name": "b",
"terminating": True,
"paths": ["fn2"],
"threshold": 4,
}
],
}
delegations = Delegations.from_dict(delegations_dict)
delegations_2: Delegations = self.copy_and_simple_assert(delegations)
# Ignore mypy request for type annotations on attr and value
for attr, value in [("keys", {}), ("roles", {})]: # type: ignore
setattr(delegations_2, attr, value)
msg = f"Failed case: {attr}"
self.assertNotEqual(delegations, delegations_2, msg)
# Restore the old value of the attribute.
setattr(delegations_2, attr, getattr(delegations, attr))
def test_targetfile_eq_(self) -> None:
targetfile_dict = {
"length": 12,
"hashes": {"sha256": "abc"},
}
targetfile = TargetFile.from_dict(targetfile_dict, "file1.txt")
targetfile_2: TargetFile = self.copy_and_simple_assert(targetfile)
# Common attr between TargetFile and MetaFile doesn't need testing.
setattr(targetfile_2, "path", "")
self.assertNotEqual(targetfile, targetfile_2)
def test_delegations_eq_roles_reversed_order(self) -> None:
# Test comparing objects with same delegated roles but different order.
role_one_dict = {
"keyids": ["keyid1"],
"name": "a",
"terminating": False,
"paths": ["fn1"],
"threshold": 1,
}
role_two_dict = {
"keyids": ["keyid2"],
"name": "b",
"terminating": True,
"paths": ["fn2"],
"threshold": 4,
}
delegations_dict = {
"keys": {
"keyid2": {
"keytype": "ed25519",
"scheme": "ed25519",
"keyval": {"public": "bar"},
}
},
"roles": [role_one_dict, role_two_dict],
}
delegations = Delegations.from_dict(copy.deepcopy(delegations_dict))
# Create a second delegations obj with reversed roles order
delegations_2 = copy.deepcopy(delegations)
# In python3.7 we need to cast to a list and then reverse.
delegations_2.roles = dict(reversed(list(delegations.roles.items())))
# Both objects are not the equal because of delegated roles order.
self.assertNotEqual(delegations, delegations_2)
# but if we fix the delegated roles order they will be equal
delegations_2.roles = delegations.roles
self.assertEqual(delegations, delegations_2)
def test_targets_eq_(self) -> None:
md = Metadata.from_bytes(self.metadata["targets"])
signed_copy: Targets = self.copy_and_simple_assert(md.signed)
# Common attributes between Targets and Signed doesn't need testing.
# Ignore mypy request for type annotations on attr and value
for attr, value in [("targets", {}), ("delegations", [])]: # type: ignore
setattr(signed_copy, attr, value)
self.assertNotEqual(md.signed, signed_copy, f"Failed case: {attr}")
# Restore the old value of the attribute.
setattr(signed_copy, attr, getattr(md.signed, attr))
# Run unit test.
if __name__ == "__main__":
utils.configure_test_logging(sys.argv)
unittest.main()