ci(portability): keep Windows test files on the work disk (#487)

The Windows runner's TEMP is on C:, where a flush took about 6 ms and a test
database's create-to-delete about 111 ms; on D: the same took 0.5 ms and
23 ms. The job now points TEMP/TMP at RUNNER_TEMP and lists the 40 slowest
tests. The fake HTTPS handlers get a TLS context that reads no certificate
store, and the missing-share tests name localhost instead of an unknown
server. The temporary diagnostics are removed.
This commit is contained in:
Vito Sansevero
2026-10-29 10:14:00 -07:00
parent 094ee71804
commit bbfc981b3d
7 changed files with 42 additions and 144 deletions
+11 -14
View File
@@ -51,22 +51,19 @@ jobs:
python -m pip install --upgrade pip
pip install -r requirements-dev.txt
- name: DIAG 487 microbench (temporary)
if: matrix.python-version == '3.12'
run: python diag_487.py
- name: DIAG 487 profile (temporary)
if: matrix.python-version == '3.12'
run: python diag_487_profile.py tests.test_database tests.test_review_schema tests.test_api_admin tests.test_lora_utils
# The runner's TEMP is on C:, the OS disk, where a flush took about 6 ms
# and a test database's create-to-delete about 111 ms; on the D: work
# disk the same took 0.5 ms and 23 ms (macOS: 19 ms). Tests write their
# databases under TEMP, so it moves to the runner's temp folder on D:
# (measured in #487, run 40915866444).
- name: Keep test files on the work disk (Windows)
if: runner.os == 'Windows'
shell: pwsh
run: |
"TEMP=$env:RUNNER_TEMP" >> $env:GITHUB_ENV
"TMP=$env:RUNNER_TEMP" >> $env:GITHUB_ENV
- name: Run tests
env:
PM_TEST_SLOWEST: "40"
run: python -m tests.run_suite -v
- name: DIAG 487 microbench without Defender realtime (temporary)
if: always() && matrix.python-version == '3.12' && runner.os == 'Windows'
shell: pwsh
run: |
Set-MpPreference -DisableRealtimeMonitoring $true
python diag_487.py
+11
View File
@@ -251,6 +251,17 @@ the process exits non-zero.
stacks.
- When the watchdog fires under coverage, that run's coverage data is lost; the
job fails anyway and the dump is what matters.
- `PM_TEST_SLOWEST=N` (off by default) lists the N slowest tests after the
summary, each timed from its `start:` line to the next one like the hang
limit. The Windows and macOS jobs (`portability.yml`) set it to 40, so every
run shows where its time went (#487).
- On the Windows runners `TEMP` sits on the OS disk C:, where a flush took about
6 ms and a test database's create-to-delete about 111 ms (D: 0.5 ms and 23 ms,
macOS 19 ms). `portability.yml` points `TEMP`/`TMP` at `RUNNER_TEMP` on D:
before the suite. A test that waits on a fixed busy timeout, a network name
(`\\server\share`: use `\\localhost\<missing share>`) or a default TLS
context (it reads the Windows certificate store) costs seconds there; the
slowest list shows it.
Every job in `test.yml`, `code-quality.yml` and `viewerjs-check.yml` has
`timeout-minutes` (`tests/vendor/ci-workflow.test.mjs` checks it). The rule is
-108
View File
@@ -1,108 +0,0 @@
"""Temporary #487 diagnostics (removed before merge)."""
import logging
import os
import shutil
import sqlite3
import statistics
import sys
import tempfile
import time
logging.disable(logging.CRITICAL)
sys.path.insert(0, os.getcwd())
from database import models # noqa: E402
from database.models import PromptModel # noqa: E402
from database.operations import PromptDatabase # noqa: E402
def bench(label, fn, n):
times = []
for i in range(n):
t = time.perf_counter()
fn(i)
times.append((time.perf_counter() - t) * 1000)
print(
f" {label:44s} median {statistics.median(times):8.2f} ms"
f" mean {statistics.mean(times):8.2f} ms"
)
def run(base):
d = tempfile.mkdtemp(dir=base)
print(f"== base {base}")
def small(i):
p = os.path.join(d, f"f{i}.txt")
with open(p, "wb") as h:
h.write(b"x" * 4096)
os.unlink(p)
def fsync(i):
p = os.path.join(d, f"s{i}.txt")
with open(p, "wb") as h:
h.write(b"x" * 4096)
h.flush()
os.fsync(h.fileno())
os.unlink(p)
def raw(sync):
def f(i):
p = os.path.join(d, f"r{sync}{i}.db")
c = sqlite3.connect(p)
c.execute("PRAGMA journal_mode=WAL")
c.execute(f"PRAGMA synchronous={sync}")
c.execute("CREATE TABLE t(x)")
c.execute("INSERT INTO t VALUES(1)")
c.commit()
c.close()
for s in ("", "-wal", "-shm"):
if os.path.exists(p + s):
os.unlink(p + s)
return f
def pdb(i):
p = os.path.join(d, f"p{i}.db")
db = PromptDatabase(p)
db.save_prompt(
text=f"t{i}",
category=None,
tags=[],
rating=None,
notes=None,
prompt_hash=str(i),
)
PromptModel.close_all_instances()
for s in ("", "-wal", "-shm"):
if os.path.exists(p + s):
os.unlink(p + s)
def mkrm(i):
t = tempfile.mkdtemp(dir=d)
open(os.path.join(t, "a"), "w").close()
shutil.rmtree(t)
bench("create+write+close+unlink 4k file", small, 100)
bench("same + fsync", fsync, 50)
bench("mkdtemp + file + rmtree", mkrm, 100)
bench("raw sqlite WAL db NORMAL", raw("NORMAL"), 30)
bench("raw sqlite WAL db OFF", raw("OFF"), 30)
bench("PromptDatabase create+save+close+unlink", pdb, 20)
orig = models.configure_connection
def off(conn):
orig(conn)
conn.execute("PRAGMA synchronous = OFF")
models.configure_connection = off
bench(" same with synchronous=OFF", lambda i: pdb(1000 + i), 20)
models.configure_connection = orig
shutil.rmtree(d, ignore_errors=True)
bases = [tempfile.gettempdir()]
if os.environ.get("RUNNER_TEMP"):
bases.append(os.environ["RUNNER_TEMP"])
for b in bases:
run(b)
-18
View File
@@ -1,18 +0,0 @@
"""Temporary #487 profile of a few modules (removed before merge)."""
import cProfile
import logging
import pstats
import sys
import unittest
logging.disable(logging.CRITICAL)
names = sys.argv[1:]
suite = unittest.defaultTestLoader.loadTestsFromNames(names)
pr = cProfile.Profile()
pr.enable()
unittest.TextTestRunner(stream=open(__import__("os").devnull, "w")).run(suite)
pr.disable()
st = pstats.Stats(pr)
st.sort_stats("tottime").print_stats(30)
st.sort_stats("cumtime").print_stats(60)
+3 -1
View File
@@ -45,7 +45,9 @@ SEEDED_PATHS = (
"/srv/someone/renders",
"C:\\Users\\someone\\Documents\\renders",
"D:/Users/someone/prompts.db",
"\\\\nas\\share\\someone\\renders",
# A share on this machine: on Windows the report resolves every source,
# and an unknown server name made that wait about 2.9 s (#487).
"\\\\localhost\\pm-no-such-share\\someone\\renders",
)
_WINDOWS_ABSOLUTE = re.compile(r"^[A-Za-z]:[\\/]")
TOP_KEYS = {
+5 -1
View File
@@ -844,8 +844,12 @@ class FolderGuardTests(unittest.TestCase):
def test_missing_folders_add_nothing(self):
self.assertIsNone(layout_module._guard_folder(str(self.tmp / "x"), "/home"))
# A missing share on this machine: an unknown server name made
# Windows wait about 2.8 s for the network (#487).
self.assertIsNone(
layout_module._guard_folder("\\\\server\\share\\loras", str(self.tmp))
layout_module._guard_folder(
"\\\\localhost\\pm-no-such-share\\loras", str(self.tmp)
)
)
self.assertEqual(
layout_module._guard_folder(str(self.tmp), "/nonexistent-home"),
+12 -2
View File
@@ -9,6 +9,7 @@ import email
import json
import os
import shutil
import ssl
import sys
import tempfile
import threading
@@ -669,6 +670,15 @@ class TestStaleDownloadSweep(unittest.TestCase):
self.assertTrue(old.exists())
def _unused_tls_context():
"""A TLS context for the fake handlers, which never open a connection.
HTTPSHandler() builds a default context, and on Windows that reads the
system certificate store: about 0.14 s per handler (#487).
"""
return ssl.SSLContext(ssl.PROTOCOL_TLS_CLIENT)
class _FakeHTTPSHandler(urllib.request.HTTPSHandler):
"""Serves canned responses through a real urllib opener, recording requests.
@@ -677,7 +687,7 @@ class _FakeHTTPSHandler(urllib.request.HTTPSHandler):
"""
def __init__(self, redirect_to):
super().__init__()
super().__init__(context=_unused_tls_context())
self.redirect_to = redirect_to
self.requests = []
@@ -745,7 +755,7 @@ class _ChainHTTPSHandler(urllib.request.HTTPSHandler):
urllib opener, recording every request; an unknown URL answers 404."""
def __init__(self, routes):
super().__init__()
super().__init__(context=_unused_tls_context())
self.routes = routes
self.requests = []