mirror of
https://github.com/paperclipai/paperclip.git
synced 2026-11-11 05:11:49 +01:00
test: retain early Playwright timeout outcomes in ingestion reports
This commit is contained in:
1 parent
81c9ffdf03
commit
7388e24d8d
4 files changed
+50
-8
No files matched your search
@@ -20,6 +20,25 @@ test('real frozen Playwright tiny poll exposes the matcher deadline shape used b
|
||||
// claim runtime qualification of H5's extended application path.
|
||||
});
|
||||
|
||||
test('real frozen Playwright can report its configured timeout before the nominal deadline', async context => {
|
||||
const require = createRequire(import.meta.url), { expect } = require('@playwright/test');
|
||||
assert.equal(require('@playwright/test/package.json').version, '1.62.1');
|
||||
const timeoutMs = 1200, intervalMs = 1500, startedAtMs = Date.now(), started = performance.now();
|
||||
let observations = 0, failure;
|
||||
try {
|
||||
await expect.poll(async () => { observations++; return 0; }, { timeout: timeoutMs, intervals: [intervalMs] }).toBeGreaterThanOrEqual(1);
|
||||
} catch (error) { failure = error; }
|
||||
const assessedAtMs = Date.now(), elapsedMs = performance.now() - started;
|
||||
assert.equal(failure?.matcherResult?.name, 'toBeGreaterThanOrEqual');
|
||||
assert.equal(failure.matcherResult.pass, false);
|
||||
assert.ok(failure.message.includes(`Timeout ${timeoutMs}ms exceeded while waiting on the predicate`));
|
||||
assert.equal(observations, 1);
|
||||
assert.ok(assessedAtMs < startedAtMs + timeoutMs, 'The real poll ends before its nominal deadline when its next interval would cross it');
|
||||
context.diagnostic(JSON.stringify({ scope: 'Small real-library poll only; no app or120s runtime qualification',
|
||||
playwrightVersion: '1.62.1', timeoutMs, intervalMs, startedAtMs, nominalDeadlineMs: startedAtMs + timeoutMs,
|
||||
assessedAtMs, elapsedMs, observations, matcherName: failure.matcherResult.name, matcherPassed: failure.matcherResult.pass }));
|
||||
});
|
||||
|
||||
test('H5 requires explicit mode and leaves default H4c selected', () => {
|
||||
assert.equal(h5Enabled(undefined), false); assert.equal(h5Enabled(H5_MODE), true);
|
||||
assert.throws(() => h5Enabled('almost-h5'));
|
||||
@@ -33,19 +52,20 @@ test('first successful observation uses only the original wait with no collectio
|
||||
assert.equal(value.observation.complete, true); assert.equal(value.observation.completedAtMs, 1000);
|
||||
});
|
||||
|
||||
test('known120s miss remains failed while the same observation finishes before600s', async () => {
|
||||
test('known120s miss assessed before its nominal deadline stays failed and uses only the remaining600s observation', async () => {
|
||||
let clock = 1000, count = 30; const calls = [], records = [];
|
||||
const value = await observeStreaming({ readCount: async () => count, target: 512, now: () => clock,
|
||||
onOutcome: value => records.push(structuredClone(value)),
|
||||
poll: async (read, milliseconds) => {
|
||||
calls.push(milliseconds); await read();
|
||||
if (calls.length === 1) { clock += milliseconds; throw timeout(milliseconds); }
|
||||
if (calls.length === 1) { clock += milliseconds - 750; throw timeout(milliseconds); }
|
||||
count = 512; clock += 90000; await read();
|
||||
} });
|
||||
assert.deepEqual(calls, [120000, 480000]);
|
||||
assert.deepEqual(calls, [120000, 480750]);
|
||||
assert.equal(value.original.status, 'failed'); assert.equal(value.original.lastObservedCount, 30);
|
||||
assert.equal(value.original.lastObservedAtMs, 1000); assert.equal(value.original.deadlineMs, 121000);
|
||||
assert.equal(value.observation.completedAtMs, 211000); assert.equal(records[0].observation.complete, false);
|
||||
assert.equal(value.original.assessedAtMs, 120250); assert.ok(value.original.assessedAtMs < value.original.deadlineMs);
|
||||
assert.equal(value.observation.completedAtMs, 210250); assert.equal(records[0].observation.complete, false);
|
||||
assert.equal(value.original.reason, 'original_streaming_deadline');
|
||||
});
|
||||
|
||||
|
||||
@@ -1,5 +1,5 @@
|
||||
import assert from "node:assert/strict";
|
||||
import { H5_MODE, H5_PROTOCOL, H5_LIMITS, H5_WORKLOAD_SHA256, H5_BASELINE_REVISIONS, originalTerminal } from "../tests/e2e/fixtures/native-ingestion-h5.mjs";
|
||||
import { H5_MODE, H5_PROTOCOL, H5_LIMITS, H5_WORKLOAD_SHA256, H5_BASELINE_REVISIONS, originalTerminal, assessH5Arm, requireH5Candidate } from "../tests/e2e/fixtures/native-ingestion-h5.mjs";
|
||||
import { spawnSync } from "node:child_process";
|
||||
import { createHash } from "node:crypto";
|
||||
import { mkdtemp, readFile, rm, writeFile } from "node:fs/promises";
|
||||
@@ -199,6 +199,17 @@ function h5Report(revision, loadedLatency) {
|
||||
return report;
|
||||
}
|
||||
|
||||
test("H5 assessor retains an original SLA failure assessed before its nominal streaming deadline", () => {
|
||||
const report = h5Report("B0", 180000), streaming = report.cases[3].h5.streaming.original;
|
||||
streaming.assessedAtMs = streaming.deadlineMs - 750;
|
||||
streaming.lastObservedAtMs = streaming.assessedAtMs - 1;
|
||||
const outcome = assessH5Arm(report, { status: "passed", retry: 0, errors: [], timeoutMs: H5_LIMITS.fullInvocationMs,
|
||||
startedAt: "2026-11-03T00:00:00.000Z", durationMs: 500000 });
|
||||
assert.equal(outcome.originalSlaPassed, false);
|
||||
assert.deepEqual(outcome.misses, ["burst512:streaming120"]);
|
||||
assert.throws(() => requireH5Candidate("final-b2", outcome, outcome), /every original SLA/);
|
||||
});
|
||||
|
||||
test("H5 CLI retains baseline SLA failure, rejects final SLA misses and requires complete owning evidence", async () => {
|
||||
const directory = await mkdtemp(path.join(os.tmpdir(), "native-ingestion-h5-gates-"));
|
||||
const script = new URL("./compare-native-ingestion-browser.ts", import.meta.url);
|
||||
|
||||
@@ -45,8 +45,8 @@ is added. Fake-plugin streaming remains separate from real-cloud Stop proof.
|
||||
The first120s poll is the original matcher. Only that exact known matcher
|
||||
deadline can extend observation. Page/API errors, other assertions, terminal
|
||||
failures and cleanup failures never become an allowed SLA miss. There is no
|
||||
collection wait when markers are available. The600s ceiling is absolute, so a
|
||||
120s miss leaves at most480s additional observation, not another600s.
|
||||
collection wait when markers are available. The600s ceiling is absolute from
|
||||
the original wait start; continued observation uses only its remaining budget.
|
||||
|
||||
Record the original wait start/deadline, actual last count and its observation
|
||||
time, assessment time and error before continuing. Keep the original failure
|
||||
@@ -54,6 +54,14 @@ after completion. The original120s clock starts before the common source epoch;
|
||||
it is not silently rebased to epoch+120s. Derive a separate paint-deadline count
|
||||
from retained2RAF timestamps; do not mislabel it as the original poll's count.
|
||||
|
||||
The recorded `deadlineMs` is the nominal configured boundary and
|
||||
`assessedAtMs` is the actual matcher return time. Frozen Playwright1.62.1 can
|
||||
report its timeout before that nominal boundary when the next polling interval
|
||||
would cross it. Its original matcher outcome, known timeout error and incomplete
|
||||
count establish the original failure; timestamp ordering against the nominal
|
||||
deadline does not change that outcome. Retain both raw times and continue only
|
||||
until the unchanged absolute600s observation ceiling.
|
||||
|
||||
If120s fails, original terminal processing was **not reached**. Later180s
|
||||
terminal observations are separately labeled and cannot make the original
|
||||
protocol pass. Keep epoch→all-terminal drainage unshortened. An additional
|
||||
|
||||
@@ -58,6 +58,8 @@ export async function observeStreaming({ readCount, poll, target, now = Date.now
|
||||
deadlineError = error;
|
||||
}
|
||||
if (readError) throw readError;
|
||||
// deadlineMs is the nominal configured boundary. Playwright can report its
|
||||
// timeout earlier when its next polling interval would cross that boundary.
|
||||
const original = { status: deadlineError ? 'failed' : 'passed', reason: deadlineError ? 'original_streaming_deadline' : null,
|
||||
startedAtMs, deadlineMs: startedAtMs + ORIGINAL_BUDGETS.streamingMs, assessedAtMs: now(),
|
||||
target, lastObservedCount, lastObservedAtMs, error: deadlineError?.message ?? null };
|
||||
@@ -119,7 +121,8 @@ export function assessH5Arm(report, owner) {
|
||||
if (streaming.status === 'failed') {
|
||||
assert.equal(streaming.reason, 'original_streaming_deadline');
|
||||
assert.ok(streaming.lastObservedCount < streaming.target);
|
||||
assert.ok(streaming.assessedAtMs >= streaming.deadlineMs);
|
||||
// The original matcher outcome is authoritative; assessedAtMs retains
|
||||
// the actual return time even when it precedes the nominal deadlineMs.
|
||||
assert.equal(item.stop, false, 'Control readiness cannot inherit a streaming SLA miss');
|
||||
assert.ok(streaming.error.includes('Timeout 120000ms exceeded while waiting on the predicate'));
|
||||
assert.equal(value.terminalOriginal.status, 'not-reached');
|
||||
|
||||
Reference in new issue
Block a user