mirror of
https://github.com/4gray/iptvnator.git
synced 2026-10-11 02:46:16 -08:00
fix(packaging): improve Linux runtime smoke diagnostics
This commit is contained in:
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';
|
||||
+74
@@ -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);
|
||||
|
||||
Reference in new issue
Block a user