Report known Linux GUI diagnostics without hiding unexpected failures

Retain raw Electron stderr and classify only bounded baseline D-Bus, font-cache and documented Sharp/GLib signatures on Linux. Unknown output, warning loops, timeouts and failed GUI cases remain fatal. Run diagnostic contract regressions in every native platform lane.

Validation: 34 owning cases passed; macOS native lane passed 1,258 tests and five real GUI checks with zero GUI diagnostic findings. Fixed-range synchronization postcheck passed. The initial Linux jobs passed 1,221 native tests and all five GUI assertions but failed on unclassified environment diagnostics; their full logs are retained and compared with the successful main baseline.
This commit is contained in:
wyt committed 2026-09-11 11:36:14 +08:00
1 parent c834f42984
commit a7d4d2541a
4 files changed
+92 -4

No files matched your search

+1
View File
@@ -20,6 +20,7 @@ if (!['win32', 'darwin', 'linux'].includes(process.platform)) {
// runs after dependency provisioning in the platform-specific Windows and
// Linux lanes because it requires downloaded FFmpeg, model, and native payloads.
const commonSuites = [
'test/main/util/inspect-canary-logs.test.ts',
'src/core-agent/test/oauth-flow.test.ts',
'src/core-agent/test/tools.test.ts',
'src/core-agent/test/process-session.test.ts',
+32
View File
@@ -0,0 +1,32 @@
// Linux source builds retain these diagnostics in the successful main baseline
// (GitHub Actions run 34550610580). They are warnings, not clean-runtime evidence.
// Sharp documents the GLib collision at https://sharp.pixelplumbing.com/install/#electron-and-linux.
const chromium = String.raw`\[\d+:\d+/\d+\.\d+:ERROR:`;
const knownLinux = [
['dbus-address', new RegExp(`^${chromium}dbus/bus\\.cc:\\d+\\] Failed to connect to the bus: Could not parse server address: Unknown address type \\(examples of valid types are "tcp" and on UNIX "unix"\\)$`), 20],
['dbus-owner', new RegExp(`^${chromium}dbus/object_proxy\\.cc:\\d+\\] Failed to call method: org\\.freedesktop\\.DBus\\.NameHasOwner: object_path= /org/freedesktop/DBus: unknown error type: ?$`), 10],
['sharp-linux', /^\(node:\d+\) \[SharpElectronLinux\] Warning: Binaries provided by Electron for use on Linux may be incompatible with sharp - see https:\/\/sharp\.pixelplumbing\.com\/install#electron-and-linux$/, 1],
['sharp-trace', /^\(Use `electron --trace-warnings \.\.\.` to show where the warning was created\)$/, 1],
['glib-sharp', new RegExp(`^${chromium}content/browser/browser_main_loop\\.cc:\\d+\\] GLib-GObject: g_object_(?:ref|unref): assertion 'G_IS_OBJECT \\(object\\)' failed$`), 100],
['system-font-mtime', /^Unable to revert mtime: \/usr\/share\/fonts(?:\/truetype(?:\/(?:dejavu|lato|liberation|noto))?)?$/, 20],
];
export function reviewInspectStderr(stderr, platform = process.platform) {
const warnings = {};
let unexpected = 0;
for (const line of stderr.split(/\r?\n/)) {
if (!line.trim()) continue;
const match = platform === 'linux' && knownLinux.find(([, pattern]) => pattern.test(line));
if (!match) { unexpected++; continue; }
const [name, , limit] = match;
warnings[name] = (warnings[name] || 0) + 1;
if (warnings[name] > limit) unexpected++;
}
// A trace hint or GLib error without the specific Sharp warning is unexplained.
if (!warnings['sharp-linux']) unexpected += (warnings['glib-sharp'] || 0) + (warnings['sharp-trace'] || 0);
return { warnings, unexpected };
}
export function inspectCanaryExitCode(code, timedOut, report) {
return code || (timedOut || report.unexpected ? 1 : 0);
}
+15 -4
View File
@@ -6,6 +6,7 @@ import * as fs from 'node:fs';
import os from 'node:os';
import path from 'node:path';
import { fileURLToPath } from 'node:url';
import { reviewInspectStderr, inspectCanaryExitCode } from './inspect-canary-logs.mjs';
const here = path.dirname(fileURLToPath(import.meta.url));
const pcDir = path.resolve(here, '../..');
@@ -22,9 +23,15 @@ const child = spawn(require('electron'), [path.join(here, 'electron-main.mjs')],
cwd: pcDir, env, stdio: ['ignore', 'pipe', 'pipe'], windowsHide: true,
});
let stderrBytes = 0;
let stderrText = '';
let timedOut = false;
child.stdout.on('data', chunk => process.stdout.write(chunk));
child.stderr.on('data', chunk => { stderrBytes += chunk.length; process.stderr.write(chunk); });
child.stderr.setEncoding('utf8');
child.stderr.on('data', chunk => {
stderrBytes += Buffer.byteLength(chunk);
if (stderrBytes <= 1024 * 1024) stderrText += chunk;
process.stderr.write(chunk);
});
const stop = () => child.kill('SIGTERM');
const signals = ['SIGINT', 'SIGTERM', 'SIGHUP'];
for (const signal of signals) process.on(signal, stop);
@@ -35,9 +42,13 @@ try {
child.once('close', code => resolve(code ?? 1));
});
if (timedOut) process.stderr.write('[inspect-canary] GUI host exceeded the 180s deadline\n');
// All child output is retained above. Unclassified stderr fails closed.
if (stderrBytes) process.stderr.write('[inspect-canary] process log review failed: unexpected stderr\n');
process.exitCode = code || (timedOut || stderrBytes ? 1 : 0);
// All child output stays visible. Only bounded, reviewed Linux baseline
// diagnostics receive warning classifications; unknown output fails closed.
const report = reviewInspectStderr(stderrText);
if (stderrBytes > 1024 * 1024) report.unexpected++;
process.stdout.write(`[inspect-canary] process log review: ${JSON.stringify(report)}\n`);
if (report.unexpected) process.stderr.write('[inspect-canary] process log review failed: unexpected stderr\n');
process.exitCode = inspectCanaryExitCode(code, timedOut, report);
} finally {
clearTimeout(timeout);
for (const signal of signals) process.off(signal, stop);
@@ -0,0 +1,44 @@
import { describe, expect, it } from 'vitest';
import { reviewInspectStderr, inspectCanaryExitCode } from '../../gui/inspect-canary-logs.mjs';
const sharp = '(node:4439) [SharpElectronLinux] Warning: Binaries provided by Electron for use on Linux may be incompatible with sharp - see https://sharp.pixelplumbing.com/install#electron-and-linux';
const glib = "[4439:0911/032713.835182:ERROR:content/browser/browser_main_loop.cc:274] GLib-GObject: g_object_ref: assertion 'G_IS_OBJECT (object)' failed";
const dbus = '[4439:0911/032713.202218:ERROR:dbus/bus.cc:405] Failed to connect to the bus: Could not parse server address: Unknown address type (examples of valid types are "tcp" and on UNIX "unix")';
const owner = '[4439:0911/032710.232165:ERROR:dbus/object_proxy.cc:572] Failed to call method: org.freedesktop.DBus.NameHasOwner: object_path= /org/freedesktop/DBus: unknown error type: ';
const trace = '(Use `electron --trace-warnings ...` to show where the warning was created)';
describe('GUI canary process diagnostics', () => {
it('reports reviewed Linux baseline diagnostics separately from functional success', () => {
const report = reviewInspectStderr([dbus, owner, sharp, trace, glib, 'Unable to revert mtime: /usr/share/fonts/truetype/noto'].join('\r\n'), 'linux');
expect(report).toEqual({ unexpected: 0, warnings: {
'dbus-address': 1, 'dbus-owner': 1, 'sharp-linux': 1, 'sharp-trace': 1, 'glib-sharp': 1, 'system-font-mtime': 1,
} });
expect(inspectCanaryExitCode(0, false, report)).toBe(0);
expect(inspectCanaryExitCode(2, false, report)).toBe(2);
expect(inspectCanaryExitCode(0, true, report)).toBe(1);
});
it.each(['darwin', 'win32'])('keeps Linux diagnostics fatal on %s', platform => {
expect(reviewInspectStderr(sharp, platform).unexpected).toBe(1);
});
it.each([
'GUI test Electron runner failed: Error', 'Segmentation fault',
'Unable to revert mtime: /private/user-file', `${glib} and another error`,
dbus.replace('Could not parse server address', 'Permission denied'),
])('rejects unknown output and near matches: %s', message => {
const report = reviewInspectStderr(`${sharp}\n${message}`, 'linux');
expect(report.unexpected).toBeGreaterThan(0);
expect(inspectCanaryExitCode(0, false, report)).toBe(1);
});
it('rejects an unbounded warning loop and unexplained GLib or trace output', () => {
expect(reviewInspectStderr(`${sharp}\n${Array(101).fill(glib).join('\n')}`, 'linux').unexpected).toBe(1);
expect(reviewInspectStderr(glib, 'linux').unexpected).toBe(1);
expect(reviewInspectStderr(trace, 'linux').unexpected).toBe(1);
});
it('accepts a clean process without platform exceptions', () => {
expect(reviewInspectStderr('\n', 'darwin')).toEqual({ warnings: {}, unexpected: 0 });
});
});