perf(static-sites): parallelize Bunny transfers
This commit is contained in:
parent
d9ea6cf9b7
commit
10132c10e3
4 changed files with 143 additions and 7 deletions
|
|
@ -4,6 +4,7 @@ import json
|
|||
from pathlib import Path
|
||||
import sys
|
||||
import tempfile
|
||||
import threading
|
||||
import unittest
|
||||
from unittest import mock
|
||||
|
||||
|
|
@ -15,11 +16,13 @@ sys.modules[SPEC.name] = static_site
|
|||
SPEC.loader.exec_module(static_site)
|
||||
|
||||
DeployError = static_site.DeployError
|
||||
apply_bunny_plan = static_site.apply_bunny_plan
|
||||
bunny_plan = static_site.bunny_plan
|
||||
collect_files = static_site.collect_files
|
||||
manifest_for = static_site.manifest_for
|
||||
parse_manifest = static_site.parse_manifest
|
||||
purge_bunny_gateway = static_site.purge_bunny_gateway
|
||||
run_concurrently = static_site.run_concurrently
|
||||
|
||||
|
||||
def write_site(root: Path) -> None:
|
||||
|
|
@ -80,6 +83,56 @@ class StaticSiteTests(unittest.TestCase):
|
|||
remote = manifest_for(files)["files"]
|
||||
self.assertEqual(bunny_plan(files, remote), ([], []))
|
||||
|
||||
def test_bunny_transfers_use_multiple_workers(self) -> None:
|
||||
barrier = threading.Barrier(4)
|
||||
completed: list[int] = []
|
||||
|
||||
def transfer(item: int) -> None:
|
||||
barrier.wait(timeout=2)
|
||||
completed.append(item)
|
||||
|
||||
run_concurrently([1, 2, 3, 4], transfer, workers=4)
|
||||
|
||||
self.assertCountEqual(completed, [1, 2, 3, 4])
|
||||
|
||||
def test_bunny_transfer_order_keeps_documents_and_manifest_safe(self) -> None:
|
||||
with tempfile.TemporaryDirectory() as directory:
|
||||
root = Path(directory)
|
||||
write_site(root)
|
||||
(root / "about.html").write_text("<h1>About</h1>")
|
||||
files = collect_files(root)
|
||||
events: list[tuple[str, str]] = []
|
||||
storage = mock.Mock()
|
||||
storage.upload.side_effect = lambda item: events.append(
|
||||
("upload", item.path)
|
||||
)
|
||||
storage.delete.side_effect = lambda path: events.append(("delete", path))
|
||||
storage.upload_manifest.side_effect = lambda _manifest: events.append(
|
||||
("manifest", static_site.MANIFEST_NAME)
|
||||
)
|
||||
|
||||
apply_bunny_plan(
|
||||
storage,
|
||||
files,
|
||||
["assets/app.js", "about.html", "index.html"],
|
||||
["stale.css"],
|
||||
workers=2,
|
||||
)
|
||||
|
||||
asset_position = events.index(("upload", "assets/app.js"))
|
||||
document_positions = {
|
||||
events.index(("upload", "about.html")),
|
||||
events.index(("upload", "index.html")),
|
||||
}
|
||||
delete_position = events.index(("delete", "stale.css"))
|
||||
self.assertTrue(
|
||||
all(asset_position < position for position in document_positions)
|
||||
)
|
||||
self.assertTrue(
|
||||
all(position < delete_position for position in document_positions)
|
||||
)
|
||||
self.assertEqual(events[-1], ("manifest", static_site.MANIFEST_NAME))
|
||||
|
||||
def test_gateway_requires_a_plain_https_origin(self) -> None:
|
||||
invalid = (
|
||||
"http://purge.example",
|
||||
|
|
@ -115,6 +168,25 @@ class StaticSiteTests(unittest.TestCase):
|
|||
sleep.assert_called_once_with(1)
|
||||
self.assertEqual(urlopen.call_count, 2)
|
||||
|
||||
@mock.patch.object(static_site.time, "sleep")
|
||||
@mock.patch.object(static_site.urllib.request, "urlopen")
|
||||
def test_storage_retries_transient_connection_errors(
|
||||
self, urlopen: mock.Mock, sleep: mock.Mock
|
||||
) -> None:
|
||||
response = mock.MagicMock()
|
||||
response.__enter__.return_value.read.return_value = b"manifest"
|
||||
urlopen.side_effect = [
|
||||
static_site.urllib.error.URLError("connection reset"),
|
||||
response,
|
||||
]
|
||||
storage = static_site.BunnyStorage(
|
||||
"example", "storage-password", "storage.example"
|
||||
)
|
||||
|
||||
self.assertEqual(storage.get_manifest(), b"manifest")
|
||||
sleep.assert_called_once_with(1)
|
||||
self.assertEqual(urlopen.call_count, 2)
|
||||
|
||||
@mock.patch.object(static_site.urllib.request, "urlopen")
|
||||
def test_gateway_reuses_storage_password(self, urlopen: mock.Mock) -> None:
|
||||
urlopen.return_value.__enter__.return_value = object()
|
||||
|
|
|
|||
Loading…
Reference in a new issue