From f707a1abe5ff975bbd2ef10e7728458b7f7dc677 Mon Sep 17 00:00:00 2001 From: Vito Sansevero Date: Tue, 27 Oct 2026 07:14:49 -0700 Subject: [PATCH] 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. --- tests/browser/fast_reset.py | 35 ++++++++++++++++++++++++++++---- tests/test_browser_fast_reset.py | 19 +++++++++++++++-- 2 files changed, 48 insertions(+), 6 deletions(-) diff --git a/tests/browser/fast_reset.py b/tests/browser/fast_reset.py index 49f6ac4b..7baa7789 100644 --- a/tests/browser/fast_reset.py +++ b/tests/browser/fast_reset.py @@ -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 diff --git a/tests/test_browser_fast_reset.py b/tests/test_browser_fast_reset.py index 61834d8d..d773a0f3 100644 --- a/tests/test_browser_fast_reset.py +++ b/tests/test_browser_fast_reset.py @@ -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()