mirror of
https://github.com/4gray/iptvnator.git
synced 2026-10-12 12:06:16 -08:00
test(packaging): trace Snap EGL probe failures
This commit is contained in:
1 parent
a575af78bf
commit
318fc32d91
10 files changed
+166
-9
No files matched your search
@@ -1003,6 +1003,9 @@ jobs:
|
||||
'$1 == "shared-memory" && $2 == "iptvnator:shared-memory" && $3 == ":shared-memory" { found=1 } END { exit !found }'
|
||||
xvfb-run -a env \
|
||||
LIBGL_ALWAYS_SOFTWARE=1 \
|
||||
IPTVNATOR_TRACE_PLAYER=1 \
|
||||
EGL_LOG_LEVEL=debug \
|
||||
LIBGL_DEBUG=verbose \
|
||||
__EGL_VENDOR_LIBRARY_FILENAMES=/tmp/hostile-egl-vendor.json \
|
||||
GBM_BACKEND=/tmp/hostile-gbm \
|
||||
MESA_LOADER_DRIVER_OVERRIDE=/tmp/hostile-dri \
|
||||
|
||||
@@ -70,7 +70,7 @@ IPTVNATOR_TRACE_STARTUP=1 nx serve electron-backend
|
||||
- `IPTVNATOR_TRACE_DB=1` traces DB worker requests and request-scoped DB events
|
||||
- `IPTVNATOR_TRACE_SQL=1` traces SQLite statements in the main process and DB worker
|
||||
- `IPTVNATOR_TRACE_WINDOW=1` traces BrowserWindow lifecycle and unresponsive events
|
||||
- `IPTVNATOR_TRACE_PLAYER=1` traces external-player launch/reuse/polling debug output
|
||||
- `IPTVNATOR_TRACE_PLAYER=1` traces external-player activity and bounded Embedded MPV runtime-probe stderr
|
||||
- `IPTVNATOR_TRACE_RENDERER_CONSOLE=1` mirrors renderer console output into the Electron terminal
|
||||
|
||||
- GPU/compositor debugging:
|
||||
@@ -280,8 +280,13 @@ Key files:
|
||||
keeps top-level reason `helper-probe-failed`; `helperReason` is present only
|
||||
for an exact protocol-v1 line carrying a fixed allowlisted reason, and its
|
||||
optional `helperDetail` must be 1–1024 printable ASCII characters. Invalid
|
||||
detail suppresses both helper fields. Any loader failure remains a stable
|
||||
native-view fallback, never a flag-enabled success.
|
||||
detail suppresses both helper fields. With `IPTVNATOR_TRACE_PLAYER=1`, a
|
||||
non-empty helper stderr capture is emitted separately as one JSON-escaped
|
||||
stderr line whose `stderr` field is limited to 16,384 characters and whose
|
||||
`truncated` field is always explicit; trace-write failure cannot change the
|
||||
capability result. Installed-Snap CI enables Mesa EGL/GL diagnostics through
|
||||
this bounded channel. Any loader failure remains a stable native-view
|
||||
fallback, never a flag-enabled success.
|
||||
- In the exact packaged Flatpak `/app` context, reconstruct only Freedesktop
|
||||
Platform 24.08's immutable `__EGL_EXTERNAL_PLATFORM_CONFIG_DIRS`; its GL
|
||||
extension loader path comes from the sandbox cache. Flatpak CI must invoke
|
||||
|
||||
@@ -127,7 +127,7 @@ Useful narrower flags:
|
||||
- `IPTVNATOR_TRACE_DB=1` traces DB worker requests and DB progress events
|
||||
- `IPTVNATOR_TRACE_SQL=1` traces SQLite statements in both main and worker connections
|
||||
- `IPTVNATOR_TRACE_WINDOW=1` traces BrowserWindow navigation/load lifecycle
|
||||
- `IPTVNATOR_TRACE_PLAYER=1` traces external-player launch/reuse/polling debug output
|
||||
- `IPTVNATOR_TRACE_PLAYER=1` traces external-player activity and bounded Embedded MPV runtime-probe stderr
|
||||
- `IPTVNATOR_TRACE_RENDERER_CONSOLE=1` mirrors renderer console logs into the Electron terminal
|
||||
|
||||
For GPU/compositor debugging:
|
||||
@@ -671,8 +671,13 @@ engine` (restart required) or
|
||||
helper exit keeps top-level reason `helper-probe-failed`; `helperReason` is
|
||||
present only for an exact protocol-v1 line carrying a fixed allowlisted
|
||||
reason, and its optional `helperDetail` must be 1–1024 printable ASCII
|
||||
characters. Invalid detail suppresses both helper fields. The exact packaged
|
||||
Flatpak `/app` context reconstructs only Freedesktop Platform 24.08's immutable
|
||||
characters. Invalid detail suppresses both helper fields. With
|
||||
`IPTVNATOR_TRACE_PLAYER=1`, non-empty helper stderr is emitted separately as
|
||||
one JSON-escaped stderr line with a 16,384-character `stderr` limit and an
|
||||
explicit `truncated` field; trace-write failure cannot change availability.
|
||||
Installed-Snap CI enables Mesa EGL/GL diagnostics through this bounded
|
||||
channel. The exact packaged Flatpak `/app` context reconstructs only
|
||||
Freedesktop Platform 24.08's immutable
|
||||
`__EGL_EXTERNAL_PLATFORM_CONFIG_DIRS`; its CI smoke invokes that
|
||||
application-level probe instead of the helper directly. Bundled
|
||||
Linux packages carry hash-validated
|
||||
|
||||
+82
@@ -253,4 +253,86 @@ describe('embedded-mpv frame-copy helper failures', () => {
|
||||
});
|
||||
}
|
||||
);
|
||||
|
||||
it('does not trace helper stderr without the exact player trace flag', () => {
|
||||
const fixture = createFixture(context.rootDir);
|
||||
context.spawnRuntimeProbe.mockReturnValue({
|
||||
status: 1,
|
||||
signal: null,
|
||||
stdout: '{"protocol":1,"usable":false,"reason":"gl-context-create-failed"}\n',
|
||||
stderr: 'libEGL debug output\n',
|
||||
});
|
||||
|
||||
expect(context.createProbe()(fixture.helperPath)).toEqual({
|
||||
usable: false,
|
||||
reason: 'helper-probe-failed',
|
||||
helperReason: 'gl-context-create-failed',
|
||||
});
|
||||
expect(context.writeRuntimeProbeStderr).not.toHaveBeenCalled();
|
||||
});
|
||||
|
||||
it('traces helper stderr as one JSON-escaped line when player tracing is enabled', () => {
|
||||
const fixture = createFixture(context.rootDir);
|
||||
const helperStderr =
|
||||
'libEGL warning: vendor "mesa"\nfailed path: C:\\driver';
|
||||
context.spawnRuntimeProbe.mockReturnValue({
|
||||
status: 1,
|
||||
signal: null,
|
||||
stdout: '{"protocol":1,"usable":false,"reason":"gl-context-create-failed"}\n',
|
||||
stderr: helperStderr,
|
||||
});
|
||||
|
||||
expect(
|
||||
context.createProbe({
|
||||
env: {
|
||||
PATH: '/usr/bin',
|
||||
IPTVNATOR_TRACE_PLAYER: '1',
|
||||
},
|
||||
})(fixture.helperPath)
|
||||
).toEqual({
|
||||
usable: false,
|
||||
reason: 'helper-probe-failed',
|
||||
helperReason: 'gl-context-create-failed',
|
||||
});
|
||||
expect(context.writeRuntimeProbeStderr).toHaveBeenCalledWith(
|
||||
`${JSON.stringify({
|
||||
event: 'embedded-mpv-helper-runtime-probe-stderr',
|
||||
stderr: helperStderr,
|
||||
truncated: false,
|
||||
})}\n`
|
||||
);
|
||||
expect(context.writeRuntimeProbeStderr).toHaveBeenCalledTimes(1);
|
||||
expect(
|
||||
context.writeRuntimeProbeStderr.mock.calls[0][0].split('\n')
|
||||
).toHaveLength(2);
|
||||
});
|
||||
|
||||
it('bounds traced helper stderr to 16384 characters and reports truncation', () => {
|
||||
const fixture = createFixture(context.rootDir);
|
||||
const helperStderr = `${'x'.repeat(16_384)}discarded`;
|
||||
context.spawnRuntimeProbe.mockReturnValue({
|
||||
status: 1,
|
||||
signal: null,
|
||||
stdout: '{"protocol":1,"usable":false,"reason":"gl-context-create-failed"}\n',
|
||||
stderr: helperStderr,
|
||||
});
|
||||
|
||||
context.createProbe({
|
||||
env: {
|
||||
PATH: '/usr/bin',
|
||||
IPTVNATOR_TRACE_PLAYER: '1',
|
||||
},
|
||||
})(fixture.helperPath);
|
||||
|
||||
const trace = JSON.parse(
|
||||
context.writeRuntimeProbeStderr.mock.calls[0][0]
|
||||
);
|
||||
expect(trace).toEqual({
|
||||
event: 'embedded-mpv-helper-runtime-probe-stderr',
|
||||
stderr: 'x'.repeat(16_384),
|
||||
truncated: true,
|
||||
});
|
||||
expect(trace.stderr).toHaveLength(16_384);
|
||||
expect(context.writeRuntimeProbeStderr).toHaveBeenCalledTimes(1);
|
||||
});
|
||||
});
|
||||
@@ -30,6 +30,9 @@ import {
|
||||
const HELPER_RUNTIME_PROBE_FAILURE_REASON_ALLOWLIST = new Set<string>(
|
||||
Object.values(EMBEDDED_MPV_HELPER_RUNTIME_PROBE_FAILURE_REASONS)
|
||||
);
|
||||
const HELPER_RUNTIME_PROBE_STDERR_LIMIT = 16_384;
|
||||
const HELPER_RUNTIME_PROBE_STDERR_EVENT =
|
||||
'embedded-mpv-helper-runtime-probe-stderr';
|
||||
|
||||
interface ParsedFailedProbe {
|
||||
helperReason: EmbeddedMpvHelperRuntimeProbeFailureReason;
|
||||
@@ -130,6 +133,31 @@ function parseSuccessfulProbe(
|
||||
};
|
||||
}
|
||||
|
||||
function traceHelperProbeStderr(
|
||||
stderr: unknown,
|
||||
dependencies: EmbeddedMpvFrameCopyRuntimeDependencies
|
||||
): void {
|
||||
if (
|
||||
dependencies.env.IPTVNATOR_TRACE_PLAYER !== '1' ||
|
||||
typeof stderr !== 'string' ||
|
||||
stderr.length === 0
|
||||
) {
|
||||
return;
|
||||
}
|
||||
|
||||
const boundedStderr = stderr.slice(0, HELPER_RUNTIME_PROBE_STDERR_LIMIT);
|
||||
const output = `${JSON.stringify({
|
||||
event: HELPER_RUNTIME_PROBE_STDERR_EVENT,
|
||||
stderr: boundedStderr,
|
||||
truncated: stderr.length > boundedStderr.length,
|
||||
})}\n`;
|
||||
try {
|
||||
dependencies.writeStderr(output);
|
||||
} catch {
|
||||
// Opt-in diagnostics must never change the fail-closed probe result.
|
||||
}
|
||||
}
|
||||
|
||||
function runHelperProbe(
|
||||
runtimePackage: ValidatedPackage,
|
||||
dependencies: EmbeddedMpvFrameCopyRuntimeDependencies
|
||||
@@ -157,6 +185,7 @@ function runHelperProbe(
|
||||
} catch {
|
||||
return failure('helper-probe-spawn-error');
|
||||
}
|
||||
traceHelperProbeStderr(result.stderr, dependencies);
|
||||
|
||||
if (
|
||||
result.error &&
|
||||
@@ -207,6 +236,9 @@ export function createEmbeddedMpvFrameCopyRuntimeProbe(
|
||||
env: process.env,
|
||||
fileSystem: defaultFileSystem,
|
||||
spawnSync: nodeSpawnSync,
|
||||
writeStderr: (output) => {
|
||||
nodeFileSystem.writeSync(process.stderr.fd, output);
|
||||
},
|
||||
...overrides,
|
||||
};
|
||||
const resultCache = new Map<string, EmbeddedMpvFrameCopyRuntimeResult>();
|
||||
|
||||
+4
@@ -18,6 +18,7 @@ import { SUCCESS_OUTPUT } from './runtime.spec-data';
|
||||
export interface RuntimeTestContext {
|
||||
rootDir: string;
|
||||
spawnRuntimeProbe: jest.Mock;
|
||||
writeRuntimeProbeStderr: jest.Mock<void, [string]>;
|
||||
fileSystem: EmbeddedMpvFrameCopyRuntimeDependencies['fileSystem'];
|
||||
createProbe(
|
||||
overrides?: Partial<EmbeddedMpvFrameCopyRuntimeDependencies>
|
||||
@@ -33,6 +34,7 @@ export function createRuntimeTestContext(): RuntimeTestContext {
|
||||
stdout: SUCCESS_OUTPUT,
|
||||
stderr: '',
|
||||
}));
|
||||
const writeRuntimeProbeStderr = jest.fn<void, [string]>();
|
||||
const fileSystem: EmbeddedMpvFrameCopyRuntimeDependencies['fileSystem'] = {
|
||||
accessSync: jest.fn((filePath: string, mode: number) =>
|
||||
accessSync(filePath, mode)
|
||||
@@ -44,6 +46,7 @@ export function createRuntimeTestContext(): RuntimeTestContext {
|
||||
const context: RuntimeTestContext = {
|
||||
rootDir,
|
||||
spawnRuntimeProbe,
|
||||
writeRuntimeProbeStderr,
|
||||
fileSystem,
|
||||
createProbe(
|
||||
overrides: Partial<EmbeddedMpvFrameCopyRuntimeDependencies> = {}
|
||||
@@ -59,6 +62,7 @@ export function createRuntimeTestContext(): RuntimeTestContext {
|
||||
},
|
||||
fileSystem: context.fileSystem,
|
||||
spawnSync: context.spawnRuntimeProbe as typeof spawnSync,
|
||||
writeStderr: context.writeRuntimeProbeStderr,
|
||||
...overrides,
|
||||
});
|
||||
},
|
||||
|
||||
@@ -71,6 +71,7 @@ export interface EmbeddedMpvFrameCopyRuntimeDependencies {
|
||||
env: NodeJS.ProcessEnv;
|
||||
fileSystem: EmbeddedMpvFrameCopyRuntimeFileSystem;
|
||||
spawnSync: typeof nodeSpawnSync;
|
||||
writeStderr(output: string): void;
|
||||
}
|
||||
|
||||
export interface RuntimeFile {
|
||||
|
||||
@@ -282,7 +282,12 @@ allowlisted helper reason as `helperReason`. An optional `helperDetail` is
|
||||
copied only from the same exact line when it contains 1–1024 printable ASCII
|
||||
characters; an invalid detail rejects both helper fields. Malformed,
|
||||
multi-line, wrong-protocol, or unknown failure output never reaches either
|
||||
field.
|
||||
field. When `IPTVNATOR_TRACE_PLAYER=1`, the probe also emits non-empty captured
|
||||
helper stderr separately as one JSON line. JSON escaping keeps embedded
|
||||
newlines on that single line, the `stderr` field is limited to the first 16,384
|
||||
characters, and the `truncated` boolean is always present. A missing flag,
|
||||
empty capture, or trace-writer failure produces no trace and never changes the
|
||||
cached availability result or the application diagnostic's stdout protocol.
|
||||
|
||||
The startup probe and every playback helper session use the same sanitized
|
||||
loader environment selected by the validated manifest's cached `runtimeMode`.
|
||||
@@ -304,6 +309,10 @@ GL extension `add-ld-path` is supplied through the sandbox loader cache, so no
|
||||
ambient `LD_LIBRARY_PATH` is needed. Flatpak CI runs the application-level
|
||||
`--embedded-mpv-runtime-probe`; direct helper execution is only a package
|
||||
layout check and cannot substitute for the real gate.
|
||||
The installed-Snap smoke enables `IPTVNATOR_TRACE_PLAYER=1`,
|
||||
`EGL_LOG_LEVEL=debug`, and `LIBGL_DEBUG=verbose`, so GLVND/Mesa loader failures
|
||||
remain observable through the bounded stderr record while the same hostile
|
||||
ambient-path assertions and fail-closed application gate stay active.
|
||||
Inside a genuine Snap mount, filtered absolute `SNAP_LIBRARY_PATH` entries below
|
||||
`/var/lib/snapd/lib/gl` follow `native/lib`. The exact
|
||||
`$SNAP/graphics/usr/lib/x86_64-linux-gnu` content-provider roots come next,
|
||||
|
||||
@@ -236,14 +236,20 @@ application diagnostic retains `helper-probe-failed` as the top-level reason
|
||||
for nonzero helper exits and adds `helperReason` only when the helper emitted
|
||||
one exact protocol-v1 line with a fixed allowlisted reason. Its optional
|
||||
`helperDetail` is restricted to 1–1024 printable ASCII characters; invalid
|
||||
detail suppresses both helper fields. The
|
||||
detail suppresses both helper fields. With `IPTVNATOR_TRACE_PLAYER=1`, non-empty
|
||||
captured helper stderr is written separately as one JSON-escaped stderr line:
|
||||
its `stderr` field contains at most the first 16,384 characters and its
|
||||
`truncated` boolean is always explicit. Empty captures, disabled tracing, and
|
||||
trace-writer failures do not emit a record or alter availability. The
|
||||
installed-Snap probe therefore tests the private shared-memory confinement
|
||||
needed by playback rather than only loader and graphics startup.
|
||||
Packaging CI invokes the same gate through
|
||||
`snap run iptvnator --embedded-mpv-runtime-probe`. This packaging-only
|
||||
application switch runs before BrowserWindow startup, emits one availability
|
||||
JSON line, and returns zero only for a usable runtime; it never directly loads
|
||||
libmpv in Electron.
|
||||
libmpv in Electron. The installed-Snap smoke adds `EGL_LOG_LEVEL=debug` and
|
||||
`LIBGL_DEBUG=verbose` under that bounded trace channel to expose GLVND/Mesa
|
||||
loader failures without weakening the hostile-environment gate.
|
||||
|
||||
Electron Builder excludes `electron-backend/native{,/**/*}` from `app.asar`.
|
||||
Only `afterPack` writes the profile-normalized
|
||||
|
||||
@@ -709,6 +709,16 @@ test('Snap verifier preserves fail-closed status while exposing captured diagnos
|
||||
assert.ok(gnomeConnectionAssertion < sharedMemoryConnectionAssertion);
|
||||
assert.ok(sharedMemoryConnectionAssertion < successfulRuntimeProbe);
|
||||
assert.match(snapStep, /snap run iptvnator --embedded-mpv-runtime-probe/);
|
||||
for (const traceSelector of [
|
||||
'IPTVNATOR_TRACE_PLAYER=1',
|
||||
'EGL_LOG_LEVEL=debug',
|
||||
'LIBGL_DEBUG=verbose',
|
||||
]) {
|
||||
assert.match(
|
||||
snapStep.slice(secondGraphicsConnect),
|
||||
new RegExp(traceSelector)
|
||||
);
|
||||
}
|
||||
for (const hostileOverride of [
|
||||
'__EGL_VENDOR_LIBRARY_FILENAMES',
|
||||
'GBM_BACKEND',
|
||||
|
||||
Reference in new issue
Block a user