fix(packaging): improve Linux runtime smoke diagnostics

This commit is contained in:
4gray committed 2026-07-18 12:40:33 +02:00
1 parent 33573ceee4
commit 4302aa4058
11 files changed
+179 -11

No files matched your search

@@ -11,5 +11,6 @@ export type {
EmbeddedMpvFrameCopyRuntimeFileSystem,
EmbeddedMpvFrameCopyRuntimeMode,
EmbeddedMpvFrameCopyRuntimeResult,
EmbeddedMpvHelperRuntimeProbeFailureReason,
} from './embedded-mpv-frame-copy-runtime/types';
export type { LinuxFrameCopyHelperLaunchFileSystem } from './embedded-mpv-frame-copy-runtime/helper-launch';
@@ -123,4 +123,78 @@ describe('embedded-mpv frame-copy helper failures', () => {
expect.objectContaining({ usable: false, reason })
);
});
it.each([
'mpv-create-failed',
'mpv-initialize-failed',
'gl-context-create-failed',
'gl-context-bind-failed',
'mpv-render-context-failed',
'shared-memory-create-failed',
'shared-memory-initialize-failed',
])('preserves the allowlisted helper reason %s', (helperReason) => {
const fixture = createFixture(context.rootDir);
context.spawnRuntimeProbe.mockReturnValue({
status: 1,
signal: null,
stdout: `${JSON.stringify({
protocol: 1,
usable: false,
reason: helperReason,
...(helperReason === 'mpv-create-failed'
? {}
: {
detail: 'diagnostic detail is intentionally not propagated',
}),
})}\n`,
stderr: '',
});
expect(context.createProbe()(fixture.helperPath)).toEqual({
usable: false,
reason: 'helper-probe-failed',
helperReason,
});
});
it.each([
['malformed JSON', 'not-json\n'],
[
'multiple lines',
'{"protocol":1,"usable":false,"reason":"mpv-create-failed"}\nignored\n',
],
[
'wrong protocol',
'{"protocol":2,"usable":false,"reason":"mpv-create-failed"}\n',
],
[
'wrong usable value',
'{"protocol":1,"usable":true,"reason":"mpv-create-failed"}\n',
],
[
'non-allowlisted reason',
'{"protocol":1,"usable":false,"reason":"loader-injected"}\n',
],
[
'unexpected field',
'{"protocol":1,"usable":false,"reason":"mpv-create-failed","extra":true}\n',
],
[
'invalid detail',
'{"protocol":1,"usable":false,"reason":"mpv-create-failed","detail":1}\n',
],
])('does not propagate a helper reason from %s', (_label, stdout) => {
const fixture = createFixture(context.rootDir);
context.spawnRuntimeProbe.mockReturnValue({
status: 1,
signal: null,
stdout,
stderr: '',
});
expect(context.createProbe()(fixture.helperPath)).toEqual({
usable: false,
reason: 'helper-probe-failed',
});
});
});
@@ -8,11 +8,13 @@ import {
} from './contracts';
import { createLinuxFrameCopyHelperLaunch } from './helper-launch';
import { validatePackage } from './package-validator';
import { EMBEDDED_MPV_HELPER_RUNTIME_PROBE_FAILURE_REASONS } from './types';
import type {
EmbeddedMpvFrameCopyManifestContract,
EmbeddedMpvFrameCopyRuntimeDependencies,
EmbeddedMpvFrameCopyRuntimeFileSystem,
EmbeddedMpvFrameCopyRuntimeResult,
EmbeddedMpvHelperRuntimeProbeFailureReason,
ValidManifest,
ValidatedPackage,
} from './types';
@@ -25,6 +27,45 @@ import {
isValidationFailure,
} from './validation-primitives';
const HELPER_RUNTIME_PROBE_FAILURE_REASON_ALLOWLIST = new Set<string>(
Object.values(EMBEDDED_MPV_HELPER_RUNTIME_PROBE_FAILURE_REASONS)
);
function parseFailedProbeReason(
stdout: unknown
): EmbeddedMpvHelperRuntimeProbeFailureReason | null {
if (typeof stdout !== 'string' || !/^[^\r\n]+\n$/.test(stdout)) {
return null;
}
let parsed: unknown;
try {
parsed = JSON.parse(stdout.slice(0, -1));
} catch {
return null;
}
if (!isObject(parsed)) {
return null;
}
const hasDetail = Object.prototype.hasOwnProperty.call(parsed, 'detail');
const fields = hasDetail
? ['detail', 'protocol', 'reason', 'usable']
: ['protocol', 'reason', 'usable'];
if (
!hasExactFields(parsed, fields) ||
parsed.protocol !== RUNTIME_PROBE_PROTOCOL ||
parsed.usable !== false ||
typeof parsed.reason !== 'string' ||
!HELPER_RUNTIME_PROBE_FAILURE_REASON_ALLOWLIST.has(parsed.reason) ||
(hasDetail &&
(typeof parsed.detail !== 'string' || parsed.detail.length === 0))
) {
return null;
}
return parsed.reason as EmbeddedMpvHelperRuntimeProbeFailureReason;
}
function parseSuccessfulProbe(
stdout: unknown,
manifest: ValidManifest
@@ -116,7 +157,14 @@ function runHelperProbe(
return failure('helper-probe-signaled');
}
if (result.status !== 0) {
return failure('helper-probe-failed');
const helperReason = parseFailedProbeReason(result.stdout);
return helperReason
? {
usable: false,
reason: 'helper-probe-failed',
helperReason,
}
: failure('helper-probe-failed');
}
return parseSuccessfulProbe(result.stdout, runtimePackage.manifest);
}
@@ -1,6 +1,19 @@
import type { spawnSync as nodeSpawnSync } from 'child_process';
import type * as nodeFileSystem from 'fs';
export const EMBEDDED_MPV_HELPER_RUNTIME_PROBE_FAILURE_REASONS = {
MPV_CREATE_FAILED: 'mpv-create-failed',
MPV_INITIALIZE_FAILED: 'mpv-initialize-failed',
GL_CONTEXT_CREATE_FAILED: 'gl-context-create-failed',
GL_CONTEXT_BIND_FAILED: 'gl-context-bind-failed',
MPV_RENDER_CONTEXT_FAILED: 'mpv-render-context-failed',
SHARED_MEMORY_CREATE_FAILED: 'shared-memory-create-failed',
SHARED_MEMORY_INITIALIZE_FAILED: 'shared-memory-initialize-failed',
} as const;
export type EmbeddedMpvHelperRuntimeProbeFailureReason =
(typeof EMBEDDED_MPV_HELPER_RUNTIME_PROBE_FAILURE_REASONS)[keyof typeof EMBEDDED_MPV_HELPER_RUNTIME_PROBE_FAILURE_REASONS];
export type EmbeddedMpvFrameCopyRuntimeFailureReason =
| 'unsupported-platform'
| 'unsupported-architecture'
@@ -41,6 +54,7 @@ export type EmbeddedMpvFrameCopyRuntimeResult =
| {
usable: false;
reason: EmbeddedMpvFrameCopyRuntimeFailureReason;
helperReason?: EmbeddedMpvHelperRuntimeProbeFailureReason;
};
export interface EmbeddedMpvFrameCopyRuntimeFileSystem {
@@ -86,10 +86,11 @@ describe('embedded MPV runtime diagnostic', () => {
expect(harness.continueStartup).not.toHaveBeenCalled();
});
it('prints the unavailable reason as one JSON line, exits nonzero, and skips startup', () => {
it('prints the unavailable helper reason as one JSON line, exits nonzero, and skips startup', () => {
const availability: FrameCopyRuntimeAvailability = {
usable: false,
reason: 'helper-probe-failed',
helperReason: 'gl-context-create-failed',
};
const harness = createHarness(availability);
@@ -100,7 +101,7 @@ describe('embedded MPV runtime diagnostic', () => {
expect(harness.getRuntimeAvailability).toHaveBeenCalledTimes(1);
expect(harness.writeStdout).toHaveBeenCalledWith(
'{"usable":false,"reason":"helper-probe-failed"}\n'
'{"usable":false,"reason":"helper-probe-failed","helperReason":"gl-context-create-failed"}\n'
);
expect(harness.writeStdout).toHaveBeenCalledTimes(1);
expect(harness.exit).toHaveBeenCalledWith(1);