mirror of
https://github.com/kevinveenbirkenbach/computer-playbook.git
synced 2026-11-08 11:46:35 +00:00
fix(web-app-docs): let the end-to-end round finish
The docs round ended the same way twice: seven specs passed, four never ran,
and the suite was killed at its 89-minute cap after an hour and a half. Three
separate causes, each of which hid the next.
A waiter could outlive the suite that contained it. The specs budget a poll
against the role's run timeout, but Playwright is handed that timeout minus a
60-second buffer, so the global timeout fires first. When it does, Playwright
counts the in-flight test, and every test behind it, as "did not run", which
names nothing and reads like a suite that was merely slow. resolveTimeout now
caps what it returns at the budget the suite actually has, so an exhausted
waiter reports which predicate it was waiting on.
What it was waiting on could never arrive. 59e7b0a2a7 retargeted the yaml page
from /latest to /deployed, but /deployed is built from DOCS_SNAPSHOT_PATHS and
that list does not carry compose.yml, while /latest is built from a full git
archive and does. A servable version whose file does not resolve answers 302,
not 404 and not 202, and the poll rejects redirects, so it waited 88 minutes
for a page the version could not produce.
With the suite finishing, the CLI test's own failure became reachable: it reads
the published instructions from /latest, and /latest is stale. It still serves
web-app-sphinx and desk-zoom, names this repository renamed away, so ten roles
have no page there at all and web-app-docs is one of them. It now reads the
version the run just deployed, which is what a post-deploy test should replay.
Measured on the stack: /deployed/roles/web-app-docs/README.html answers 200
with the production block, /latest answers 302, and the published index lists
275 of 282 roles. The round now passes nine specs in 17.5 minutes.
Only five of the 1512 resolveTimeout call sites across 87 roles are large
enough for the cap to reach: three here and two in web-app-openwebui, whose
suite has not been re-run.
This commit is contained in:
1 parent
230d9a1e5e
commit
a490b28af6
5 files changed
+64
-5
No files matched your search
@@ -30,13 +30,30 @@ function onionTimeoutMultiplier() {
|
||||
return Number.isFinite(m) && m > 0 ? m : 5;
|
||||
}
|
||||
|
||||
/**
|
||||
* Milliseconds held back from the suite budget so a waiter that exhausts its
|
||||
* timeout reports which predicate it was waiting on. Without it the global
|
||||
* timeout fires first and Playwright counts the in-flight test, and every
|
||||
* test behind it, as "did not run" — naming nothing.
|
||||
*/
|
||||
const SUITE_RESERVE_MS = 60_000;
|
||||
|
||||
function suiteBudgetMs() {
|
||||
const budget = Number(process.env.INFINITO_PLAYWRIGHT_GLOBAL_TIMEOUT_MS);
|
||||
return Number.isFinite(budget) && budget > 0 ? budget : 0;
|
||||
}
|
||||
|
||||
/**
|
||||
* Effective timeout (ms) for the current run: `base * TIMEOUT_FACTOR`, times the
|
||||
* onion multiplier when the target is a `.onion` service.
|
||||
* onion multiplier when the target is a `.onion` service, capped so no single
|
||||
* waiter can outlive the suite that contains it.
|
||||
*/
|
||||
function resolveTimeout(baseMs) {
|
||||
const onionMult = isOnionTarget() ? onionTimeoutMultiplier() : 1;
|
||||
return Math.round(baseMs * timeoutFactor() * onionMult);
|
||||
const scaled = Math.round(baseMs * timeoutFactor() * onionMult);
|
||||
const budget = suiteBudgetMs();
|
||||
if (budget <= SUITE_RESERVE_MS) return scaled;
|
||||
return Math.min(scaled, budget - SUITE_RESERVE_MS);
|
||||
}
|
||||
|
||||
module.exports = {
|
||||
@@ -44,5 +61,6 @@ module.exports = {
|
||||
isSplitRealmOidc,
|
||||
timeoutFactor,
|
||||
onionTimeoutMultiplier,
|
||||
suiteBudgetMs,
|
||||
resolveTimeout,
|
||||
};
|
||||
@@ -21,7 +21,7 @@ import urllib.error
|
||||
import urllib.request
|
||||
from html.parser import HTMLParser
|
||||
|
||||
VERSION = "latest"
|
||||
VERSION = "deployed"
|
||||
HEADING = "production"
|
||||
BUILDING = 202
|
||||
|
||||
|
||||
@@ -29,6 +29,7 @@ DOCS_SNAPSHOT_PATHS:
|
||||
- ansible.cfg
|
||||
- assets
|
||||
- cli
|
||||
- compose.yml
|
||||
- default.env
|
||||
- docs
|
||||
- group_vars
|
||||
|
||||
@@ -102,6 +102,40 @@ class TestTimeoutsHelper(unittest.TestCase):
|
||||
)
|
||||
self.assertIn("RESULT:60000", out)
|
||||
|
||||
def test_a_waiter_may_not_outlive_the_suite_that_contains_it(self):
|
||||
"""The onion run that starved web-app-docs: base 2_700_000 at
|
||||
multiplier 2 asked for 5_400_000 against a 5_340_000 suite budget, so
|
||||
the global timeout fired first and every remaining spec was reported
|
||||
"did not run" without naming the waiter."""
|
||||
out = self._run(
|
||||
"t.resolveTimeout(2700000)",
|
||||
{
|
||||
"CANONICAL_DOMAIN": '"docs.abc123.onion"',
|
||||
"PLAYWRIGHT_TIMEOUT_FACTOR": "1",
|
||||
"PLAYWRIGHT_ONION_TIMEOUT_MULTIPLIER": "2",
|
||||
"INFINITO_PLAYWRIGHT_GLOBAL_TIMEOUT_MS": "5340000", # nocheck: the playwright role passes this into the sidecar container, it is not a .env key
|
||||
},
|
||||
)
|
||||
self.assertIn("RESULT:5280000", out)
|
||||
|
||||
def test_a_timeout_inside_the_budget_is_left_alone(self):
|
||||
out = self._run(
|
||||
"t.resolveTimeout(30000)",
|
||||
{
|
||||
"CANONICAL_DOMAIN": '"docs.abc123.onion"',
|
||||
"PLAYWRIGHT_ONION_TIMEOUT_MULTIPLIER": "2",
|
||||
"INFINITO_PLAYWRIGHT_GLOBAL_TIMEOUT_MS": "5340000", # nocheck: the playwright role passes this into the sidecar container, it is not a .env key
|
||||
},
|
||||
)
|
||||
self.assertIn("RESULT:60000", out)
|
||||
|
||||
def test_without_a_declared_budget_nothing_is_capped(self):
|
||||
out = self._run(
|
||||
"t.resolveTimeout(5400000)",
|
||||
{"CANONICAL_DOMAIN": '"x.infinito.test"'},
|
||||
)
|
||||
self.assertIn("RESULT:5400000", out)
|
||||
|
||||
def test_is_onion_target_detects_quoted_and_bare(self):
|
||||
self.assertIn(
|
||||
"RESULT:true",
|
||||
|
||||
@@ -53,10 +53,16 @@ class TestBlock(unittest.TestCase):
|
||||
|
||||
|
||||
class TestPageUrl(unittest.TestCase):
|
||||
def test_it_addresses_the_latest_version(self) -> None:
|
||||
def test_it_addresses_the_deployed_working_tree(self) -> None:
|
||||
"""`latest` is the newest commit, which need not carry this role.
|
||||
|
||||
A role renamed or added since that commit has no page there, and the
|
||||
test then reports a missing production block for a page the version
|
||||
never had. The deployed working tree is the one the run just built.
|
||||
"""
|
||||
self.assertEqual(
|
||||
extract.page_url("http://host:8036/", "web-app-x"),
|
||||
"http://host:8036/latest/roles/web-app-x/README.html",
|
||||
"http://host:8036/deployed/roles/web-app-x/README.html",
|
||||
)
|
||||
|
||||
|
||||
|
||||
Reference in new issue
Block a user