Skip to content

Commit 0f1fc6e

Browse files
author
Jussi Kukkonen
authored
Merge pull request #1689 from sechkova/test_delegated_roles
Test delegation graphs
2 parents 3823fd6 + 2562aff commit 0f1fc6e

6 files changed

+414
-97
lines changed

tests/repository_simulator.py

Lines changed: 3 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -345,13 +345,7 @@ def add_target(self, role: str, data: bytes, path: str) -> None:
345345
self.target_files[path] = RepositoryTarget(data, target)
346346

347347
def add_delegation(
348-
self,
349-
delegator_name: str,
350-
name: str,
351-
targets: Targets,
352-
terminating: bool,
353-
paths: Optional[List[str]],
354-
hash_prefixes: Optional[List[str]],
348+
self, delegator_name: str, role: DelegatedRole, targets: Targets
355349
) -> None:
356350
"""Add delegated target role to the repository."""
357351
if delegator_name == Targets.type:
@@ -360,7 +354,6 @@ def add_delegation(
360354
delegator = self.md_delegates[delegator_name].signed
361355

362356
# Create delegation
363-
role = DelegatedRole(name, [], 1, terminating, paths, hash_prefixes)
364357
if delegator.delegations is None:
365358
delegator.delegations = Delegations({}, OrderedDict())
366359
# put delegation last by default
@@ -372,7 +365,8 @@ def add_delegation(
372365
self.add_signer(role.name, signer)
373366

374367
# Add metadata for the role
375-
self.md_delegates[role.name] = Metadata(targets, OrderedDict())
368+
if role.name not in self.md_delegates:
369+
self.md_delegates[role.name] = Metadata(targets, OrderedDict())
376370

377371
def write(self) -> None:
378372
"""Dump current repository metadata to self.dump_dir

tests/test_updater_consistent_snapshot.py

Lines changed: 109 additions & 81 deletions
Original file line numberDiff line numberDiff line change
@@ -16,6 +16,7 @@
1616
from tuf.api.metadata import (
1717
SPECIFICATION_VERSION,
1818
TOP_LEVEL_ROLE_NAMES,
19+
DelegatedRole,
1920
TargetFile,
2021
Targets,
2122
)
@@ -27,17 +28,40 @@ class TestConsistentSnapshot(unittest.TestCase):
2728
'prefix_targets_with_hash' and verify that the correct URLs
2829
are formed for each combination"""
2930

31+
# set dump_dir to trigger repository state dumps
32+
dump_dir: Optional[str] = None
33+
3034
def setUp(self) -> None:
3135
# pylint: disable=consider-using-with
36+
self.subtest_count = 0
3237
self.temp_dir = tempfile.TemporaryDirectory()
3338
self.metadata_dir = os.path.join(self.temp_dir.name, "metadata")
3439
self.targets_dir = os.path.join(self.temp_dir.name, "targets")
3540
os.mkdir(self.metadata_dir)
3641
os.mkdir(self.targets_dir)
42+
self.sim: RepositorySimulator
3743

3844
def tearDown(self) -> None:
3945
self.temp_dir.cleanup()
4046

47+
def setup_subtest(
48+
self, consistent_snapshot: bool, prefix_targets: bool = True
49+
) -> None:
50+
self.sim = self._init_repo(consistent_snapshot, prefix_targets)
51+
52+
self.subtest_count += 1
53+
if self.dump_dir is not None:
54+
# create subtest dumpdir
55+
name = f"{self.id().split('.')[-1]}-{self.subtest_count}"
56+
self.sim.dump_dir = os.path.join(self.dump_dir, name)
57+
os.mkdir(self.sim.dump_dir)
58+
59+
def teardown_subtest(self) -> None:
60+
if self.dump_dir is not None:
61+
self.sim.write()
62+
63+
utils.cleanup_dir(self.metadata_dir)
64+
4165
def _init_repo(
4266
self, consistent_snapshot: bool, prefix_targets: bool = True
4367
) -> RepositorySimulator:
@@ -54,24 +78,16 @@ def _init_repo(
5478

5579
return sim
5680

57-
def _init_updater(self, sim: RepositorySimulator) -> Updater:
81+
def _init_updater(self) -> Updater:
5882
"""Create a new Updater instance"""
5983
return Updater(
6084
self.metadata_dir,
6185
"https://example.com/metadata/",
6286
self.targets_dir,
6387
"https://example.com/targets/",
64-
sim,
88+
self.sim,
6589
)
6690

67-
@staticmethod
68-
def _cleanup_dir(path: str) -> None:
69-
"""Delete all files inside a directory"""
70-
for filepath in [
71-
os.path.join(path, filename) for filename in os.listdir(path)
72-
]:
73-
os.remove(filepath)
74-
7591
def _assert_metadata_files_exist(self, roles: Iterable[str]) -> None:
7692
"""Assert that local metadata files exist for 'roles'"""
7793
local_metadata_files = os.listdir(self.metadata_dir)
@@ -111,22 +127,23 @@ def test_top_level_roles_update(
111127
) -> None:
112128
# Test if the client fetches and stores metadata files with the
113129
# correct version prefix, depending on 'consistent_snapshot' config
114-
consistent_snapshot: bool = test_case_data["consistent_snapshot"]
115-
expected_calls: List[Any] = test_case_data["calls"]
116-
117-
sim = self._init_repo(consistent_snapshot)
118-
updater = self._init_updater(sim)
130+
try:
131+
consistent_snapshot: bool = test_case_data["consistent_snapshot"]
132+
exp_calls: List[Any] = test_case_data["calls"]
119133

120-
# cleanup fetch tracker metadata
121-
sim.fetch_tracker.metadata.clear()
122-
updater.refresh()
134+
self.setup_subtest(consistent_snapshot)
135+
updater = self._init_updater()
123136

124-
# metadata files are fetched with the expected version (or None)
125-
self.assertListEqual(sim.fetch_tracker.metadata, expected_calls)
126-
# metadata files are always persisted without a version prefix
127-
self._assert_metadata_files_exist(TOP_LEVEL_ROLE_NAMES)
137+
# cleanup fetch tracker metadata
138+
self.sim.fetch_tracker.metadata.clear()
139+
updater.refresh()
128140

129-
self._cleanup_dir(self.metadata_dir)
141+
# metadata files are fetched with the expected version (or None)
142+
self.assertListEqual(self.sim.fetch_tracker.metadata, exp_calls)
143+
# metadata files are always persisted without a version prefix
144+
self._assert_metadata_files_exist(TOP_LEVEL_ROLE_NAMES)
145+
finally:
146+
self.teardown_subtest()
130147

131148
delegated_roles_data: utils.DataSet = {
132149
"consistent_snaphot disabled": {
@@ -145,31 +162,35 @@ def test_delegated_roles_update(
145162
) -> None:
146163
# Test if the client fetches and stores delegated metadata files with
147164
# the correct version prefix, depending on 'consistent_snapshot' config
148-
consistent_snapshot: bool = test_case_data["consistent_snapshot"]
149-
expected_version: Optional[int] = test_case_data["expected_version"]
150-
rolenames = ["role1", "..", "."]
151-
expected_calls = [(role, expected_version) for role in rolenames]
152-
153-
sim = self._init_repo(consistent_snapshot)
154-
# Add new delegated targets
155-
spec_version = ".".join(SPECIFICATION_VERSION)
156-
targets = Targets(1, spec_version, sim.safe_expiry, {}, None)
157-
for role in rolenames:
158-
sim.add_delegation("targets", role, targets, False, ["*"], None)
159-
sim.update_snapshot()
160-
updater = self._init_updater(sim)
161-
updater.refresh()
162-
163-
# cleanup fetch tracker metadata
164-
sim.fetch_tracker.metadata.clear()
165-
# trigger updater to fetch the delegated metadata
166-
updater.get_targetinfo("anything")
167-
# metadata files are fetched with the expected version (or None)
168-
self.assertListEqual(sim.fetch_tracker.metadata, expected_calls)
169-
# metadata files are always persisted without a version prefix
170-
self._assert_metadata_files_exist(rolenames)
171-
172-
self._cleanup_dir(self.metadata_dir)
165+
try:
166+
consistent_snapshot: bool = test_case_data["consistent_snapshot"]
167+
exp_version: Optional[int] = test_case_data["expected_version"]
168+
rolenames = ["role1", "..", "."]
169+
exp_calls = [(role, exp_version) for role in rolenames]
170+
171+
self.setup_subtest(consistent_snapshot)
172+
# Add new delegated targets
173+
spec_version = ".".join(SPECIFICATION_VERSION)
174+
for role in rolenames:
175+
delegated_role = DelegatedRole(role, [], 1, False, ["*"], None)
176+
targets = Targets(
177+
1, spec_version, self.sim.safe_expiry, {}, None
178+
)
179+
self.sim.add_delegation("targets", delegated_role, targets)
180+
self.sim.update_snapshot()
181+
updater = self._init_updater()
182+
updater.refresh()
183+
184+
# cleanup fetch tracker metadata
185+
self.sim.fetch_tracker.metadata.clear()
186+
# trigger updater to fetch the delegated metadata
187+
updater.get_targetinfo("anything")
188+
# metadata files are fetched with the expected version (or None)
189+
self.assertListEqual(self.sim.fetch_tracker.metadata, exp_calls)
190+
# metadata files are always persisted without a version prefix
191+
self._assert_metadata_files_exist(rolenames)
192+
finally:
193+
self.teardown_subtest()
173194

174195
targets_download_data: utils.DataSet = {
175196
"consistent_snaphot disabled": {
@@ -197,42 +218,49 @@ def test_download_targets(self, test_case_data: Dict[str, Any]) -> None:
197218
# Test if the client fetches and stores target files with
198219
# the correct hash prefix, depending on 'consistent_snapshot'
199220
# and 'prefix_targets_with_hash' config
200-
consistent_snapshot: bool = test_case_data["consistent_snapshot"]
201-
prefix_targets_with_hash: bool = test_case_data["prefix_targets"]
202-
hash_algo: Optional[str] = test_case_data["hash_algo"]
203-
targetpaths: List[str] = test_case_data["targetpaths"]
204-
205-
sim = self._init_repo(consistent_snapshot, prefix_targets_with_hash)
206-
# Add targets to repository
207-
for targetpath in targetpaths:
208-
sim.targets.version += 1
209-
sim.add_target("targets", b"content", targetpath)
210-
sim.update_snapshot()
211-
212-
updater = self._init_updater(sim)
213-
updater.config.prefix_targets_with_hash = prefix_targets_with_hash
214-
updater.refresh()
215-
216-
for targetpath in targetpaths:
217-
info = updater.get_targetinfo(targetpath)
218-
assert isinstance(info, TargetFile)
219-
updater.download_target(info)
220-
221-
# target files are always persisted without hash prefix
222-
self._assert_targets_files_exist([info.path])
223-
224-
# files are fetched with the expected hash prefix (or None)
225-
expected_fetches = [
226-
(targetpath, None if not hash_algo else info.hashes[hash_algo])
227-
]
228-
229-
self.assertListEqual(sim.fetch_tracker.targets, expected_fetches)
230-
sim.fetch_tracker.targets.clear()
231-
232-
self._cleanup_dir(self.targets_dir)
221+
try:
222+
consistent_snapshot: bool = test_case_data["consistent_snapshot"]
223+
prefix_targets_with_hash: bool = test_case_data["prefix_targets"]
224+
hash_algo: Optional[str] = test_case_data["hash_algo"]
225+
targetpaths: List[str] = test_case_data["targetpaths"]
226+
227+
self.setup_subtest(consistent_snapshot, prefix_targets_with_hash)
228+
# Add targets to repository
229+
for targetpath in targetpaths:
230+
self.sim.targets.version += 1
231+
self.sim.add_target("targets", b"content", targetpath)
232+
self.sim.update_snapshot()
233+
234+
updater = self._init_updater()
235+
updater.config.prefix_targets_with_hash = prefix_targets_with_hash
236+
updater.refresh()
237+
238+
for path in targetpaths:
239+
info = updater.get_targetinfo(path)
240+
assert isinstance(info, TargetFile)
241+
updater.download_target(info)
242+
243+
# target files are always persisted without hash prefix
244+
self._assert_targets_files_exist([info.path])
245+
246+
# files are fetched with the expected hash prefix (or None)
247+
exp_calls = [
248+
(path, None if not hash_algo else info.hashes[hash_algo])
249+
]
250+
251+
self.assertListEqual(self.sim.fetch_tracker.targets, exp_calls)
252+
self.sim.fetch_tracker.targets.clear()
253+
finally:
254+
self.teardown_subtest()
233255

234256

235257
if __name__ == "__main__":
258+
if "--dump" in sys.argv:
259+
TestConsistentSnapshot.dump_dir = tempfile.mkdtemp()
260+
print(
261+
f"Repository Simulator dumps in {TestConsistentSnapshot.dump_dir}"
262+
)
263+
sys.argv.remove("--dump")
236264

237265
utils.configure_test_logging(sys.argv)
238266
unittest.main()

0 commit comments

Comments
 (0)