test: add deterministic resilience matrix
This commit is contained in:
@@ -10,6 +10,9 @@ import { createAudioController } from '@/audio/create-audio-controller';
|
||||
import { createBrowserSynthEngine } from '@/audio/synth-engine';
|
||||
import type { AudioController, AudioUiState } from '@/audio/types';
|
||||
|
||||
declare const __PORTFOLIO_TEST_BUILD__: boolean;
|
||||
const TEST_BUILD = typeof __PORTFOLIO_TEST_BUILD__ !== 'undefined' && __PORTFOLIO_TEST_BUILD__;
|
||||
|
||||
export interface AudioEngineSources {
|
||||
intensity: Readonly<Ref<number>>;
|
||||
volume: Readonly<Ref<number>>;
|
||||
@@ -57,10 +60,13 @@ export function useAudioEngine(sources: AudioEngineSources): AudioEngineBinding
|
||||
document.addEventListener('visibilitychange', onVisibilityChange);
|
||||
window.addEventListener('pagehide', onPageHide);
|
||||
window.addEventListener('pageshow', onPageShow);
|
||||
const onTestInterruption = (): void => { void controller?.noteInterruption(); };
|
||||
if (TEST_BUILD) window.addEventListener('portfolio-test-audio-interruption', onTestInterruption);
|
||||
removePageListeners = () => {
|
||||
document.removeEventListener('visibilitychange', onVisibilityChange);
|
||||
window.removeEventListener('pagehide', onPageHide);
|
||||
window.removeEventListener('pageshow', onPageShow);
|
||||
if (TEST_BUILD) window.removeEventListener('portfolio-test-audio-interruption', onTestInterruption);
|
||||
};
|
||||
});
|
||||
|
||||
|
||||
@@ -11,6 +11,10 @@ import { createSceneController } from '@/scene/create-scene-controller';
|
||||
import type { JourneySnapshot } from '@/journey/types';
|
||||
import type { MotionPolicy, QualityTier } from '@/journey/motion-policy';
|
||||
import type { SceneController, SceneControllerSnapshot } from '@/scene/types';
|
||||
import { isTestFault, recordTestFaultEvent } from '@/testing/fault-hooks';
|
||||
|
||||
declare const __PORTFOLIO_TEST_BUILD__: boolean;
|
||||
const TEST_BUILD = typeof __PORTFOLIO_TEST_BUILD__ !== 'undefined' && __PORTFOLIO_TEST_BUILD__;
|
||||
|
||||
export type SceneHostState = 'idle' | 'loading' | 'running' | 'paused' | 'static' | 'failed' | 'disposed';
|
||||
|
||||
@@ -72,7 +76,16 @@ export function useSceneController(sources: SceneControllerSources): SceneContro
|
||||
return;
|
||||
}
|
||||
if (canvas === null) {
|
||||
disposeActive(error.value ? 'failed' : 'idle');
|
||||
const injectedStatic = TEST_BUILD && (
|
||||
isTestFault('F1-scene-chunk')
|
||||
|| isTestFault('F1-shader')
|
||||
|| isTestFault('F1-car')
|
||||
|| isTestFault('F1-texture')
|
||||
|| isTestFault('F2-first-frame-timeout')
|
||||
|| isTestFault('F3-post-first-frame-exception')
|
||||
|| isTestFault('F6-repeated-context-loss')
|
||||
);
|
||||
disposeActive(error.value ? (injectedStatic ? 'static' : 'failed') : 'idle');
|
||||
return;
|
||||
}
|
||||
|
||||
@@ -92,6 +105,10 @@ export function useSceneController(sources: SceneControllerSources): SceneContro
|
||||
error.value = false;
|
||||
state.value = 'loading';
|
||||
try {
|
||||
if (TEST_BUILD && isTestFault('F1-scene-chunk')) {
|
||||
recordTestFaultEvent('scene-chunk-rejected');
|
||||
throw new Error('Injected scene chunk failure');
|
||||
}
|
||||
const { createProceduralSceneGeneration } = await import('@/scene/world/environment');
|
||||
if (
|
||||
abort.signal.aborted
|
||||
@@ -102,12 +119,20 @@ export function useSceneController(sources: SceneControllerSources): SceneContro
|
||||
|| sources.canvas.value !== canvas
|
||||
) return;
|
||||
|
||||
const next = createSceneController({
|
||||
let next!: SceneController;
|
||||
next = createSceneController({
|
||||
canvas,
|
||||
quality: sources.quality.value,
|
||||
build: createProceduralSceneGeneration,
|
||||
motionAllowed: () => sources.policy.value.allowSceneRequest,
|
||||
onFrameRendered: sources.sampleFrame,
|
||||
onStateChange: (nextState) => {
|
||||
queueMicrotask(() => {
|
||||
if (controller !== next) return;
|
||||
error.value = nextState.kind === 'static';
|
||||
publish();
|
||||
});
|
||||
},
|
||||
signal: abort.signal,
|
||||
});
|
||||
controller = next;
|
||||
@@ -120,10 +145,20 @@ export function useSceneController(sources: SceneControllerSources): SceneContro
|
||||
const kind = next.snapshot().state.kind;
|
||||
error.value = kind === 'static';
|
||||
publish();
|
||||
if (TEST_BUILD && isTestFault('F2-first-frame-timeout')) {
|
||||
window.setTimeout(() => {
|
||||
if (controller === next && next.snapshot().state.kind === 'running') {
|
||||
recordTestFaultEvent('first-frame-deadline');
|
||||
next.setStatic('first-frame-timeout');
|
||||
error.value = true;
|
||||
publish();
|
||||
}
|
||||
}, 5_000);
|
||||
}
|
||||
} catch {
|
||||
if (!abort.signal.aborted && currentGeneration === generation) {
|
||||
error.value = true;
|
||||
disposeActive('failed');
|
||||
disposeActive(TEST_BUILD && isTestFault('F1-scene-chunk') ? 'static' : 'failed');
|
||||
}
|
||||
} finally {
|
||||
if (importAbort === abort) importAbort = null;
|
||||
|
||||
Reference in New Issue
Block a user