Skip to content
Closed
Show file tree
Hide file tree
Changes from 2 commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
16 changes: 15 additions & 1 deletion tests/repository_simulator.py
Original file line number Diff line number Diff line change
Expand Up @@ -48,7 +48,7 @@
import os
import tempfile
from collections import OrderedDict
from dataclasses import dataclass
from dataclasses import dataclass, field
from datetime import datetime, timedelta
from typing import Dict, Iterator, List, Optional, Tuple
from urllib import parse
Expand Down Expand Up @@ -81,6 +81,14 @@
SPEC_VER = ".".join(SPECIFICATION_VERSION)


@dataclass
class FetchCounter:
"""Fetcher counter for metadata and targets."""

metadata: list = field(default_factory=list)
targets: list = field(default_factory=list)

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Suggested change
metadata: list = field(default_factory=list)
targets: list = field(default_factory=list)
metadata: List[Tuple[str, Optional[int]]] = field(default_factory=list)
targets: List[Tuple[str, Optional[str]]] = field(default_factory=list)

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I know my suggestion is ugly but if we want to be strict about types ... I guess it's fine until it appears only once.



@dataclass
class RepositoryTarget:
"""Contains actual target data and the related target metadata."""
Expand Down Expand Up @@ -116,6 +124,8 @@ def __init__(self) -> None:
self.dump_dir: Optional[str] = None
self.dump_version = 0

self.fetch_tracker: FetchCounter = FetchCounter()

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Suggested change
self.fetch_tracker: FetchCounter = FetchCounter()
self.fetch_tracker = FetchCounter()

I think mypy and the code editors are smart enough to infer the type in this case.


now = datetime.utcnow()
self.safe_expiry = now.replace(microsecond=0) + timedelta(days=30)

Expand Down Expand Up @@ -229,6 +239,8 @@ def _fetch_target(

If hash is None, then consistent_snapshot is not used.
"""
self.fetch_tracker.targets.append((target_path, target_hash))

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

We append to the fetch_tracker.targets all fetch requests. Should we append-only successful ones?

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

We want to track the all requests coming from the client and I think this is the correct place.


repo_target = self.target_files.get(target_path)
if repo_target is None:
raise FetcherHTTPError(f"No target {target_path}", 404)
Expand All @@ -248,6 +260,8 @@ def _fetch_metadata(

If version is None, non-versioned metadata is being requested.
"""
self.fetch_tracker.metadata.append((role, version))

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

We append to the fetch_tracker.metadata all fetch requests. Should we append-only successful ones?


if role == "root":
# return a version previously serialized in publish_root()
if version is None or version > len(self.signed_roots):
Expand Down
83 changes: 39 additions & 44 deletions tests/test_updater_consistent_snapshot.py
Original file line number Diff line number Diff line change
Expand Up @@ -90,19 +90,19 @@ def _assert_targets_files_exist(self, filenames: Iterable[str]) -> None:
"consistent_snaphot disabled": {
"consistent_snapshot": False,
"calls": [
call("root", 3),
call("timestamp", None),
call("snapshot", None),
call("targets", None),
("root", 3),
("timestamp", None),
("snapshot", None),
("targets", None),
],
},
"consistent_snaphot enabled": {
"consistent_snapshot": True,
"calls": [
call("root", 3),
call("timestamp", None),
call("snapshot", 1),
call("targets", 1),
("root", 3),
("timestamp", None),
("snapshot", 1),
("targets", 1),
],
},
}
Expand All @@ -117,15 +117,14 @@ def test_top_level_roles_update(self, test_case_data: Dict[str, Any]):
sim = self._init_repo(consistent_snapshot)
updater = self._init_updater(sim)

with patch.object(
sim, "_fetch_metadata", wraps=sim._fetch_metadata
) as wrapped_fetch:
updater.refresh()
# cleanup fetch tracker metadata
sim.fetch_tracker.metadata.clear()
updater.refresh()

# metadata files are fetched with the expected version (or None)
self.assertListEqual(wrapped_fetch.call_args_list, expected_calls)
# metadata files are always persisted without a version prefix
self._assert_metadata_files_exist(TOP_LEVEL_ROLE_NAMES)
# metadata files are fetched with the expected version (or None)
self.assertListEqual(sim.fetch_tracker.metadata, expected_calls)
# metadata files are always persisted without a version prefix
self._assert_metadata_files_exist(TOP_LEVEL_ROLE_NAMES)

self._cleanup_dir(self.metadata_dir)

Expand All @@ -147,7 +146,7 @@ def test_delegated_roles_update(self, test_case_data: Dict[str, Any]):
consistent_snapshot: bool = test_case_data["consistent_snapshot"]
expected_version: Optional[int] = test_case_data["expected_version"]
rolenames = ["role1", "..", "."]
expected_calls = [call(role, expected_version) for role in rolenames]
expected_calls = [(role, expected_version) for role in rolenames]

sim = self._init_repo(consistent_snapshot)
# Add new delegated targets
Expand All @@ -157,17 +156,17 @@ def test_delegated_roles_update(self, test_case_data: Dict[str, Any]):
sim.add_delegation("targets", role, targets, False, ["*"], None)
sim.update_snapshot()
updater = self._init_updater(sim)

updater.refresh()

with patch.object(
sim, "_fetch_metadata", wraps=sim._fetch_metadata
) as wrapped_fetch:
# trigger updater to fetch the delegated metadata
updater.get_targetinfo("anything")
# metadata files are fetched with the expected version (or None)
self.assertListEqual(wrapped_fetch.call_args_list, expected_calls)
# metadata files are always persisted without a version prefix
self._assert_metadata_files_exist(rolenames)
# cleanup fetch tracker metadata
sim.fetch_tracker.metadata.clear()
# trigger updater to fetch the delegated metadata
updater.get_targetinfo("anything")
# metadata files are fetched with the expected version (or None)
self.assertListEqual(sim.fetch_tracker.metadata, expected_calls)
# metadata files are always persisted without a version prefix
self._assert_metadata_files_exist(rolenames)

self._cleanup_dir(self.metadata_dir)

Expand All @@ -176,16 +175,19 @@ def test_delegated_roles_update(self, test_case_data: Dict[str, Any]):
"consistent_snapshot": False,
"prefix_targets": True,
"hash_algo": None,
"targetpaths": ["file", "file.txt", "..file.ext", "f.le"],
},
"consistent_snaphot enabled without prefixed targets": {
"consistent_snapshot": True,
"prefix_targets": False,
"hash_algo": None,
"targetpaths": ["file", "file.txt", "..file.ext", "f.le"],
},
"consistent_snaphot enabled with prefixed targets": {
"consistent_snapshot": True,
"prefix_targets": True,
"hash_algo": "sha256",
"targetpaths": ["file", "file.txt", "..file.ext", "f.le"],
},
}

Expand All @@ -197,36 +199,29 @@ def test_download_targets(self, test_case_data: Dict[str, Any]):
consistent_snapshot: bool = test_case_data["consistent_snapshot"]
prefix_targets_with_hash: bool = test_case_data["prefix_targets"]
hash_algo: Optional[str] = test_case_data["hash_algo"]
targetpaths = ["file", "file.txt", "..file.ext", "f.le"]
targetpaths: list = test_case_data["targetpaths"]

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Suggested change
targetpaths: list = test_case_data["targetpaths"]
targetpaths: List[str] = test_case_data["targetpaths"]


sim = self._init_repo(consistent_snapshot, prefix_targets_with_hash)
# Add targets to repository
for targetpath in targetpaths:
sim.targets.version += 1
sim.add_target("targets", b"content", targetpath)

sim.update_snapshot()

updater = self._init_updater(sim)
updater.config.prefix_targets_with_hash = prefix_targets_with_hash
updater.refresh()

with patch.object(
sim, "_fetch_target", wraps=sim._fetch_target
) as wrapped_fetch_target:

for targetpath in targetpaths:
info = updater.get_targetinfo(targetpath)
updater.download_target(info)
expected_prefix = (
None if not hash_algo else info.hashes[hash_algo]
)
# files are fetched with the expected hash prefix (or None)
wrapped_fetch_target.assert_called_once_with(
info.path, expected_prefix
)
# target files are always persisted without hash prefix
self._assert_targets_files_exist([info.path])

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I think we want to keep this check?

wrapped_fetch_target.reset_mock()
expected_result = []
for targetpath in targetpaths:
info = updater.get_targetinfo(targetpath)
updater.download_target(info)
expected_prefix = None if not hash_algo else info.hashes[hash_algo]
expected_result.append((targetpath, expected_prefix))

# files are fetched with the expected hash prefix (or None)
self.assertListEqual(sim.fetch_tracker.targets, expected_result)

self._cleanup_dir(self.targets_dir)

Expand Down