Files
ArtPlayer/test/danmuku-mask-failures.test.js

260 lines
12 KiB
JavaScript

import assert from 'node:assert/strict'
import { spawnSync } from 'node:child_process'
import fs from 'node:fs'
import process from 'node:process'
// eslint-disable-next-line test/no-import-node-test -- Historical runner includes fatal isolated Node rejection probes.
import test from 'node:test'
import { danmukuMaskEnvironment, danmukuMaskHistorical, flushMask } from './helpers/danmuku-mask.js'
// These assertions describe frozen historical defects, never candidate acceptance.
const implementations = await danmukuMaskHistorical()
const detailed = implementations.filter(item => ['frozen-source', 'published-1.1.0-main'].includes(item.name))
test('Historical SDK probes replace exactly five call sites and retain verified input identities', () => {
const expected = JSON.parse(fs.readFileSync('refactor/baselines/danmuku-mask-failures-validation.json', 'utf8'))
const actual = implementations.map(({ code: _code, ...identity }) => identity)
assert.deepEqual(actual, expected.inputs, 'SDK seam transformations must remain exactly reproducible')
assert.equal(implementations.length, 7)
for (const item of implementations) {
assert.match(item.inputSha256, /^[a-f0-9]{64}$/u)
assert.match(item.transformedSha256, /^[a-f0-9]{64}$/u)
if (!item.source) {
assert.match(item.archiveSha256, /^[a-f0-9]{64}$/u)
assert.match(item.probeSha256, /^[a-f0-9]{64}$/u)
assert.deepEqual(item.counts, { setBackend: 2, createSegmenter: 1, toBinaryMask: 1, drawMask: 1 })
assert.equal(item.replacements.length, 5)
}
}
})
for (const implementation of implementations) {
test(`${implementation.name}: historical stop during initialization is undone by late initialization`, async () => {
const env = danmukuMaskEnvironment(implementation, { pendingInit: true })
const pending = env.result.start()
await flushMask()
assert.equal(env.initGates.length, 1)
env.result.stop()
assert.equal(env.style.maskImage, 'none')
env.initGates[0].resolve()
await pending
await flushMask()
assert.equal(env.draws.length, 1)
assert.equal(env.style.maskImage, 'url(data:image/png;base64,historical-mask)')
assert.equal(env.frames.size, 1)
env.result.stop()
})
test(`${implementation.name}: historical inference completion after destroy writes a mask and revives RAF`, async () => {
const env = danmukuMaskEnvironment(implementation, { pendingSegment: true })
await env.result.start()
await flushMask()
assert.equal(env.segmentGates.length, 1)
env.destroy()
env.segmentGates[0].resolve([{}])
await flushMask()
assert.equal(env.draws.length, 1)
assert.equal(env.style.maskImage, 'url(data:image/png;base64,historical-mask)')
assert.equal(env.frames.size, 1)
assert.equal(env.created[0].disposed, 0)
assert.deepEqual([...env.listeners].map(([name, callbacks]) => [name, callbacks.size]), [['ready', 1], ['destroy', 1]])
env.result.stop()
})
test(`${implementation.name}: historical repeated start leaves a second RAF chain after stop`, async () => {
const env = danmukuMaskEnvironment(implementation)
await env.result.start()
await flushMask()
await env.result.start()
await flushMask()
assert.equal(env.created.length, 1)
assert.equal(env.frames.size, 2)
env.result.stop()
assert.equal(env.frames.size, 1)
await env.frame()
assert.equal(env.frames.size, 1)
assert.equal(env.style.maskImage, 'url(data:image/png;base64,historical-mask)')
env.result.stop()
})
}
for (const implementation of detailed) {
for (const action of ['stop', 'destroy']) {
test(`${implementation.name}: historical ${action} during initialization does not dispose the late model`, async () => {
const env = danmukuMaskEnvironment(implementation, { pendingInit: true })
const pending = env.result.start()
await flushMask()
if (action === 'stop')
env.result.stop()
else env.destroy()
env.initGates[0].resolve()
await pending
await flushMask()
assert.equal(env.created[0].disposed, 0)
assert.equal(env.frames.size, 1)
assert.equal(env.style.maskMode, 'alpha')
env.result.stop()
})
}
test(`${implementation.name}: historical stop during inference cannot prevent its pending write`, async () => {
const env = danmukuMaskEnvironment(implementation, { pendingSegment: true })
await env.result.start()
env.result.stop()
assert.deepEqual(env.writes, ['none'])
env.segmentGates[0].resolve([{}])
await flushMask()
assert.deepEqual(env.writes, ['none', 'url(data:image/png;base64,historical-mask)'])
assert.equal(env.frames.size, 1)
env.result.stop()
})
test(`${implementation.name}: historical concurrent starts initialize two models and lose disposal ownership`, async () => {
const env = danmukuMaskEnvironment(implementation, { pendingInit: true })
const first = env.result.start()
const second = env.result.start()
await flushMask()
assert.equal(env.created.length, 2)
for (const gate of env.initGates) gate.resolve()
await Promise.all([first, second])
await flushMask()
assert.equal(env.frames.size, 2)
env.destroy()
assert.equal(env.frames.size, 1)
assert.deepEqual(env.created.map(instance => instance.disposed), [0, 0])
})
test(`${implementation.name}: historical inference rejection logs once per frame and keeps retrying`, async () => {
const error = new Error('controlled inference failure')
const env = danmukuMaskEnvironment(implementation, { segmentError: error })
await env.result.start()
await flushMask()
assert.equal(env.frames.size, 1)
assert.equal(env.logs.length, 1)
assert.deepEqual(env.logs[0], ['error', 'Error in segmentBody:', error])
assert.equal(env.draws.length, 0)
await env.frame()
assert.equal(env.logs.length, 2)
assert.equal(env.frames.size, 1)
env.settings.segmentError = null
await env.frame()
assert.equal(env.draws.length, 1)
env.result.stop()
})
test(`${implementation.name}: historical fulfilled false WebGL selection does not trigger CPU fallback`, async () => {
const env = danmukuMaskEnvironment(implementation, { backendResult: { webgl: false } })
await env.result.start()
await flushMask()
assert.deepEqual(env.backends, ['webgl'])
assert.equal(env.created.length, 1)
assert.equal(env.logs.length, 0)
env.result.stop()
})
test(`${implementation.name}: historical rejected WebGL selection tries CPU and ignores its fulfilled boolean`, async () => {
for (const cpu of [true, false]) {
const env = danmukuMaskEnvironment(implementation, { backendError: { webgl: new Error('no WebGL') }, backendResult: { cpu } })
await env.result.start()
await flushMask()
assert.deepEqual(env.backends, ['webgl', 'cpu'])
assert.deepEqual(env.logs, [['warn', 'WebGL backend not available, falling back to CPU', 'no WebGL']])
assert.equal(env.created.length, 1)
env.result.stop()
}
})
test(`${implementation.name}: historical CPU rejection rejects explicit start before creating a model`, async () => {
const error = new Error('controlled CPU failure')
const env = danmukuMaskEnvironment(implementation, { backendError: { webgl: new Error('no WebGL'), cpu: error } })
await assert.rejects(env.result.start(), failure => failure === error)
assert.deepEqual(env.backends, ['webgl', 'cpu'])
assert.equal(env.created.length, 0)
assert.equal(env.frames.size, 0)
})
test(`${implementation.name}: historical model initialization rejection resolves start and polls without a model`, async () => {
const error = new Error('controlled model init failure')
const env = danmukuMaskEnvironment(implementation, { initError: error })
assert.equal(await env.result.start(), undefined)
assert.deepEqual(env.logs, [['error', 'Error initializing segmenter:', error]])
assert.equal(env.frames.size, 1)
await env.frame()
assert.equal(env.created.length, 1)
assert.equal(env.inferences.length, 0)
env.result.stop()
})
test(`${implementation.name}: historical missing danmuku rejects start after allocating the model and makes stop throw`, async () => {
const env = danmukuMaskEnvironment(implementation, { noDanmuku: true })
await assert.rejects(env.result.start(), error => error.name === 'TypeError' && error.message.includes('style'))
assert.throws(() => env.result.stop(), error => error.name === 'TypeError' && error.message.includes('style'))
assert.equal(env.created.length, 1)
assert.equal(env.created[0].disposed, 0)
})
test(`${implementation.name}: historical unavailable 2D context logs read failures and keeps the frame loop`, async () => {
const env = danmukuMaskEnvironment(implementation, { noContext: true })
await env.result.start()
await flushMask()
assert.equal(env.logs.length, 1)
assert.equal(env.logs[0][2].name, 'TypeError')
assert.match(env.logs[0][2].message, /getImageData/u)
assert.equal(env.frames.size, 1)
assert.equal(env.writes.length, 0)
env.result.stop()
})
test(`${implementation.name}: historical canvas allocation failure rejects start but leaves the initialized model owned`, async () => {
const error = new Error('controlled canvas allocation failure')
const env = danmukuMaskEnvironment(implementation, { canvasError: error })
await assert.rejects(env.result.start(), failure => failure === error)
assert.equal(env.created.length, 1)
assert.equal(env.created[0].disposed, 0)
assert.equal(env.frames.size, 0)
env.destroy()
assert.equal(env.created[0].disposed, 0)
})
test(`${implementation.name}: historical canvas read security failure is logged and retried without clearing the previous mask`, async () => {
const error = new Error('controlled canvas taint')
error.name = 'SecurityError'
const env = danmukuMaskEnvironment(implementation, { readError: error })
await env.result.start()
await flushMask()
assert.equal(env.logs[0][2], error)
assert.equal(env.style.maskImage, 'url(prior-mask)')
await env.frame()
assert.equal(env.logs.length, 2)
env.result.stop()
})
test(`${implementation.name}: historical zero RAF id survives stop and retained ready listeners restart after destroy`, async () => {
const env = danmukuMaskEnvironment(implementation, { firstFrame: 0, paused: true })
await env.result.start()
assert.deepEqual([...env.frames.keys()], [0])
env.destroy()
assert.deepEqual([...env.frames.keys()], [0])
assert.deepEqual(env.cancelled, [])
env.emit('ready')
await flushMask()
assert.equal(env.frames.size, 2)
assert.equal(env.created[0].disposed, 0)
})
for (const scenario of ['ready-backend', 'missing-video']) {
test(`${implementation.name}: historical ${scenario} unhandled rejection is observed only in a fatal isolated child`, () => {
const script = `import { danmukuMaskHistorical, danmukuMaskEnvironment, flushMask } from './test/helpers/danmuku-mask.js';
const implementation = (await danmukuMaskHistorical()).find(item => item.name === ${JSON.stringify(implementation.name)});
const settings = ${scenario === 'ready-backend' ? '{backendError: {webgl: new Error("no WebGL"), cpu: new Error("MASK02_READY_BACKEND_REJECTION")}}' : '{noVideo: true}'};
const env = danmukuMaskEnvironment(implementation, settings);
${scenario === 'ready-backend' ? 'env.emit("ready");' : 'await env.result.start();'}
await flushMask();`
const child = spawnSync(process.execPath, ['--unhandled-rejections=strict', '--input-type=module', '-e', script], { encoding: 'utf8', timeout: 15000 })
assert.equal(child.error, undefined)
assert.equal(child.status, 1)
assert.match(child.stderr, scenario === 'ready-backend' ? /MASK02_READY_BACKEND_REJECTION/u : /Cannot read properties of undefined \(reading 'paused'\)/u)
assert.doesNotMatch(child.stderr, /AssertionError|Model network access is forbidden/u)
})
}
}