test(browser): keep the reset scratch database in /dev/shm when there is one (#384)
Throttled runs still waited on the runner's disk while creating and rewriting the scratch database, whose pages were under writeback.
This commit is contained in:
@@ -20,8 +20,10 @@ instead of being overwritten (``PRAGMA data_version``).
|
||||
from __future__ import annotations
|
||||
|
||||
import hashlib
|
||||
import os
|
||||
import shutil
|
||||
import sqlite3
|
||||
import tempfile
|
||||
import weakref
|
||||
from pathlib import Path
|
||||
from typing import Any, Callable, TypeVar
|
||||
@@ -32,6 +34,8 @@ T = TypeVar("T")
|
||||
# proof, tests/browser/reset_equivalence.py, compares both ways).
|
||||
SEED_ON_LIVE_ENV = "PM_TEST_SEED_ON_LIVE"
|
||||
SCRATCH_SCHEMA = "reset_seed"
|
||||
# Linux's shared-memory tmpfs (present in the Playwright image and on runners).
|
||||
MEMORY_ROOT = Path("/dev/shm")
|
||||
|
||||
|
||||
def _quote(name: str) -> str:
|
||||
@@ -100,9 +104,18 @@ class _SeedApi:
|
||||
class ScratchSeeder:
|
||||
"""Seed into a scratch copy and publish the changed tables in one commit."""
|
||||
|
||||
def __init__(self, api: Any, directory: Path, *, on_live: bool = False) -> None:
|
||||
def __init__(
|
||||
self,
|
||||
api: Any,
|
||||
directory: Path,
|
||||
*,
|
||||
on_live: bool = False,
|
||||
memory_root: Path | None = MEMORY_ROOT,
|
||||
) -> None:
|
||||
self._api = api
|
||||
self._directory = Path(directory)
|
||||
self._fallback = Path(directory)
|
||||
self._memory_root = memory_root
|
||||
self._directory: Path | None = None
|
||||
self._on_live = on_live
|
||||
self._scratch = None
|
||||
self._unsynced: "weakref.WeakSet[sqlite3.Connection]" = weakref.WeakSet()
|
||||
@@ -137,7 +150,7 @@ class ScratchSeeder:
|
||||
if self._scratch is None:
|
||||
from database.operations import PromptDatabase
|
||||
|
||||
self._directory.mkdir(parents=True, exist_ok=True)
|
||||
self._directory = self._scratch_directory()
|
||||
self._scratch = PromptDatabase(str(self._directory / "seed.db"))
|
||||
return self._scratch
|
||||
|
||||
@@ -201,4 +214,18 @@ class ScratchSeeder:
|
||||
if self._scratch is not None:
|
||||
self._scratch.close_all()
|
||||
self._scratch = None
|
||||
shutil.rmtree(self._directory, ignore_errors=True)
|
||||
if self._directory is not None:
|
||||
shutil.rmtree(self._directory, ignore_errors=True)
|
||||
self._directory = None
|
||||
|
||||
def _scratch_directory(self) -> Path:
|
||||
"""A folder in memory (tmpfs) where there is one, else ``directory``.
|
||||
|
||||
Even unsynced, scratch writes on the runner's disk wait whenever the
|
||||
kernel is writing back the pages they rewrite.
|
||||
"""
|
||||
root = self._memory_root
|
||||
if root is not None and root.is_dir() and os.access(root, os.W_OK):
|
||||
return Path(tempfile.mkdtemp(prefix="pm-reset-", dir=root))
|
||||
self._fallback.mkdir(parents=True, exist_ok=True)
|
||||
return self._fallback
|
||||
|
||||
@@ -70,7 +70,7 @@ class TestScratchSeeder(unittest.TestCase):
|
||||
self.addCleanup(assert_closed, self, self.tmp.name)
|
||||
self.live = self.database("live.db")
|
||||
self.api = types.SimpleNamespace(db=self.live, marker="live api")
|
||||
self.seeder = ScratchSeeder(self.api, self.root / "scratch")
|
||||
self.seeder = ScratchSeeder(self.api, self.root / "scratch", memory_root=None)
|
||||
self.addCleanup(self.seeder.close)
|
||||
# Rows from "earlier tests": AUTOINCREMENT positions have moved on.
|
||||
for index in range(5):
|
||||
@@ -229,7 +229,9 @@ class TestScratchSeeder(unittest.TestCase):
|
||||
self.assertEqual(conn.execute("PRAGMA foreign_key_check").fetchall(), [])
|
||||
|
||||
def test_on_live_mode_runs_the_seed_on_the_live_api(self):
|
||||
seeder = ScratchSeeder(self.api, self.root / "unused", on_live=True)
|
||||
seeder = ScratchSeeder(
|
||||
self.api, self.root / "unused", on_live=True, memory_root=self.root
|
||||
)
|
||||
self.addCleanup(seeder.close)
|
||||
expected = self.oracle(reseed)
|
||||
seen = []
|
||||
@@ -240,6 +242,19 @@ class TestScratchSeeder(unittest.TestCase):
|
||||
self.assertEqual(digests(self.live), expected)
|
||||
self.assertFalse((self.root / "unused").exists())
|
||||
|
||||
def test_the_scratch_lives_under_the_memory_root_when_there_is_one(self):
|
||||
memory = self.root / "memory"
|
||||
memory.mkdir()
|
||||
seeder = ScratchSeeder(self.api, self.root / "fallback", memory_root=memory)
|
||||
self.addCleanup(seeder.close)
|
||||
expected = self.oracle(reseed)
|
||||
seeder.run(reseed)
|
||||
self.assertEqual(digests(self.live), expected)
|
||||
self.assertEqual(len(list(memory.glob("pm-reset-*/seed.db"))), 1)
|
||||
self.assertFalse((self.root / "fallback").exists())
|
||||
seeder.close()
|
||||
self.assertEqual(list(memory.iterdir()), [])
|
||||
|
||||
def test_close_releases_the_scratch_files(self):
|
||||
self.seeder.run(reseed)
|
||||
self.seeder.close()
|
||||
|
||||
Reference in New Issue
Block a user