test(performance): [ENG-13] enforce measured teardown observation minimum

This commit is contained in:
Harvey Zhao committed 2026-09-14 16:02:05 +08:00
1 parent 23a4f22b72
commit 48a7f1ecb0
11 files changed
+22120 -12

No files matched your search

+38 -3
View File
@@ -7,7 +7,7 @@ import { test } from 'node:test'
import { fileURLToPath } from 'node:url'
import { hash } from '../refactor/scripts/releases.mjs'
import { verifyPerformanceArtifacts } from '../scripts/performance-artifacts.mjs'
import { performanceHtml, performanceScript } from '../scripts/performance-fixture.mjs'
import { performanceHtml, performanceScript, waitForObservation } from '../scripts/performance-fixture.mjs'
import { validatePairedPerformance } from '../scripts/performance-report.mjs'
const baseline = JSON.parse(fs.readFileSync(new URL('../refactor/baselines/performance.json', import.meta.url), 'utf8')).runs[0]
@@ -44,6 +44,7 @@ test('paired performance requires comparable cohorts, real playback and strict c
report => report.runs[1].measurements.resources[0].after.timers.push({ kind: 'interval' }),
report => report.runs[1].measurements.resources[0].after.lateCallbacks.push({ kind: 'timeout' }),
report => report.runs[1].measurements.resources[0].after.lateResizeEvents++,
report => report.runs[1].measurements.resources[0].after.observedMs = 349,
]) {
const report = fixture()
mutate(report)
@@ -60,17 +61,51 @@ test('paired performance requires comparable cohorts, real playback and strict c
assert(comparison.reviewRequired && comparison.groups.every(group => group.reviewSignals.some(signal => signal.metric === 'constructorMs')))
})
test('performance adapter changes only delivery while retaining the frozen measurement procedure', () => {
test('performance adapter preserves frozen measurements except delivery and enforcing the observation minimum', () => {
const frozen = fs.readFileSync(new URL('../refactor/fixtures/performance.js', import.meta.url), 'utf8')
const adapted = performanceScript()
const before = 'const response = await fetch(\'/reports/performance\', { method: \'POST\', headers: { \'Content-Type\': \'application/json\' }, body: JSON.stringify(report) })'
const after = 'window.artplayerPerformanceReport = report; const response = { ok: true }'
assert.equal(adapted.replace(after, before), frozen)
const prefix = `${waitForObservation.toString()}\n`
assert(adapted.startsWith(prefix))
assert.equal(adapted.slice(prefix.length).replace(after, before).replace('await waitForObservation(probe.wait, now, waitStarted, 350)', 'await probe.wait(350)'), frozen)
assert.match(performanceHtml('candidate'), /src="\/candidate\/artplayer.js"/)
assert.match(performanceHtml('published'), /src="\/published\/artplayer-plugin-chapter.js"/)
assert.throws(() => performanceHtml('../uncompiled'))
})
test('resource observation re-arms after an early timer without rounding up evidence', async () => {
let clock = 1000
const delays = []
await waitForObservation(async (delay) => {
delays.push(delay)
clock += delays.length === 1 ? 349 : 1
}, () => clock, 1000, 350)
assert.deepEqual(delays, [350, 1])
assert.equal(clock - 1000, 350)
})
test('resource observation retains overshoot and does not wait again after the minimum', async () => {
let clock = 1000
const delays = []
const wait = async (delay) => {
delays.push(delay)
clock += 364
}
await waitForObservation(wait, () => clock, 1000, 350)
assert.deepEqual(delays, [350])
assert.equal(clock - 1000, 364)
await waitForObservation(wait, () => clock, 1000, 350)
assert.deepEqual(delays, [350])
})
test('resource observation propagates timer failures rather than certifying an incomplete window', async () => {
const failure = new Error('timer unavailable')
await assert.rejects(waitForObservation(async () => {
throw failure
}, () => 0, 0, 350), error => error === failure)
})
test('performance refuses stale sources, build tools and altered installed bundles', () => {
const cache = fileURLToPath(new URL('../refactor/.cache/performance/', import.meta.url))
fs.mkdirSync(cache, { recursive: true })
+12 -2
View File
@@ -9,8 +9,9 @@ worker, no trace/video recording and the same media and viewport within a pair.
Each engine runs three alternating published/candidate pairs. Each side retains
one warm-up and five timed samples for both core and core + chapter, followed by
separate instrumented resource probes. `scripts/performance-fixture.mjs` adapts
only the frozen BASE-06 fixture's script URLs and report transport. Its original
files remain unchanged and their existing baseline tests still run.
the frozen BASE-06 fixture's script URLs, report transport and enforcement of its
minimum observation window. The frozen files and all timed samples remain
unchanged, and their existing baseline tests still run.
The candidate must come from an isolated package installation. Artifact hashes,
source snapshots, package inputs and build tools are checked before measurement;
@@ -28,3 +29,12 @@ A successful test proves the measurement contract and candidate resource cleanup
approval. CORE-22 and release reviews must resolve the corresponding risks before
publishing. These counters do not measure native heap/GPU memory, real mobile
hardware or screen-reader behavior. See `refactor/coverage-performance.md`.
## Observation window
The current performance fixture adapter enforces the existing 350 ms teardown
observation using the measured clock. It re-arms the probe's original timer if
the nominal delay returns before that minimum, without rounding up reported
time or relaxing validation. This applies equally to both variants and leaves
the frozen BASE-06 fixture and all timed samples unchanged. See
`test/performance-report.test.js` and `refactor/changes/2026-09-14-ENG-13-observation-window.md`.