mirror of
https://github.com/garrytan/gstack.git
synced 2026-09-09 14:38:59 +02:00
fix(test): per-FILE Chromium profiles for the nine in-process launcher files
Completes the profile-isolation work: the per-shard CHROMIUM_PROFILE stopped cross-shard kills; these nine files launch in-process persistent contexts and could still collide with a lingering daemon a sibling file spawned on the SAME shard profile. Each now scopes a mkdtemp profile via beforeAll/afterAll (the module-scope-tripwire-safe pattern), cleaned up per file. All nine green solo and in combined runs, except the pre-existing commands+snapshot pairing — proven identical WITH and WITHOUT these edits (baseline receipts) — which is the daemon-lifecycle follow-up now extended in TODOS with this session's receipts. Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
This commit is contained in:
@@ -514,6 +514,18 @@ coverage fill. Remaining, in rough priority order:
|
||||
post-migration flake data exists (the Codex outside-voice's "green means
|
||||
green is not delivered while paid stays advisory" point — correct, and
|
||||
deliberately a branch-protection decision, not repo YAML). Effort S.
|
||||
- **P2 — browse daemon lifecycle vs in-suite browsers (top remaining free-suite
|
||||
flake).** The post-#994 daemon deliberately outlives its parent and lingers
|
||||
across test FILES in a shard process; a later file's browser use can then
|
||||
fight it ('[browse] FATAL: Chromium process crashed' + 5s element-wait
|
||||
timeouts). Receipts: commands+snapshot in one bun process fails identically
|
||||
WITH and WITHOUT per-file CHROMIUM_PROFILE isolation (pre-existing; PR
|
||||
#2721 triage), and CI shard 1 on d9b78b5a died at model-overlay-sonnet-5
|
||||
after a daemon-spawning file. Per-shard + per-file profile isolation
|
||||
(landed) removed the cross-shard kills; the intra-shard daemon handoff
|
||||
needs a real design: tests that spawn the daemon should stop it in
|
||||
afterAll, or the daemon should detect a foreign CHROMIUM_PROFILE env and
|
||||
refuse reuse. Effort M.
|
||||
- **P2 — browse daemon /tmp-namespace hardening.** Every file-path transport
|
||||
to the daemon (eval <file>, load-html --from-file, pdf output, upload,
|
||||
cookie-import) assumes client and daemon share one /tmp view; a sandboxed
|
||||
|
||||
@@ -5,7 +5,10 @@
|
||||
* newtab/closetab handling, and batch validation.
|
||||
*/
|
||||
|
||||
import { describe, test, expect, beforeAll, afterAll } from 'bun:test';
|
||||
import * as fs from 'fs';
|
||||
import * as path from 'path';
|
||||
import * as os from 'os';
|
||||
import { afterAll, beforeAll, describe, expect, test } from 'bun:test';
|
||||
import { startTestServer } from './test-server';
|
||||
import { BrowserManager } from '../src/browser-manager';
|
||||
|
||||
@@ -62,6 +65,24 @@ import { handleMetaCommand } from '../src/meta-commands';
|
||||
import { handleSnapshot } from '../src/snapshot';
|
||||
import { READ_COMMANDS, WRITE_COMMANDS } from '../src/commands';
|
||||
|
||||
// Per-FILE Chromium profile: this file launches an in-process persistent
|
||||
// context (BrowserManager.launch()), and sharing a profile dir with the
|
||||
// long-lived browse daemon a sibling file may have spawned kills one side's
|
||||
// Chromium (ProcessSingleton on user-data-dir). Scoped via hooks, never
|
||||
// module scope (see test/gstack-home-module-scope.test.ts's rationale).
|
||||
const ORIGINAL_CHROMIUM_PROFILE = process.env.CHROMIUM_PROFILE;
|
||||
let CHROMIUM_PROFILE_DIR: string | undefined;
|
||||
beforeAll(() => {
|
||||
CHROMIUM_PROFILE_DIR = fs.mkdtempSync(path.join(os.tmpdir(), 'gstack-test-profile-'));
|
||||
process.env.CHROMIUM_PROFILE = CHROMIUM_PROFILE_DIR;
|
||||
});
|
||||
afterAll(() => {
|
||||
if (ORIGINAL_CHROMIUM_PROFILE === undefined) delete process.env.CHROMIUM_PROFILE;
|
||||
else process.env.CHROMIUM_PROFILE = ORIGINAL_CHROMIUM_PROFILE;
|
||||
if (CHROMIUM_PROFILE_DIR) { try { fs.rmSync(CHROMIUM_PROFILE_DIR, { recursive: true, force: true }); } catch {} }
|
||||
});
|
||||
|
||||
|
||||
const handleReadCommand = (cmd: string, args: string[], b: BrowserManager) =>
|
||||
_handleReadCommand(cmd, args, b.getActiveSession());
|
||||
const handleWriteCommand = (cmd: string, args: string[], b: BrowserManager) =>
|
||||
|
||||
@@ -5,7 +5,8 @@
|
||||
* A real browse server is started and commands are sent via the CLI HTTP interface.
|
||||
*/
|
||||
|
||||
import { describe, test, expect, beforeAll, afterAll } from 'bun:test';
|
||||
import * as os from 'os';
|
||||
import { afterAll, beforeAll, describe, expect, test } from 'bun:test';
|
||||
import { startTestServer } from './test-server';
|
||||
import { BrowserManager } from '../src/browser-manager';
|
||||
import { resolveServerScript } from '../src/cli';
|
||||
@@ -18,6 +19,24 @@ import * as fs from 'fs';
|
||||
import { spawn } from 'child_process';
|
||||
import * as path from 'path';
|
||||
|
||||
// Per-FILE Chromium profile: this file launches an in-process persistent
|
||||
// context (BrowserManager.launch()), and sharing a profile dir with the
|
||||
// long-lived browse daemon a sibling file may have spawned kills one side's
|
||||
// Chromium (ProcessSingleton on user-data-dir). Scoped via hooks, never
|
||||
// module scope (see test/gstack-home-module-scope.test.ts's rationale).
|
||||
const ORIGINAL_CHROMIUM_PROFILE = process.env.CHROMIUM_PROFILE;
|
||||
let CHROMIUM_PROFILE_DIR: string | undefined;
|
||||
beforeAll(() => {
|
||||
CHROMIUM_PROFILE_DIR = fs.mkdtempSync(path.join(os.tmpdir(), 'gstack-test-profile-'));
|
||||
process.env.CHROMIUM_PROFILE = CHROMIUM_PROFILE_DIR;
|
||||
});
|
||||
afterAll(() => {
|
||||
if (ORIGINAL_CHROMIUM_PROFILE === undefined) delete process.env.CHROMIUM_PROFILE;
|
||||
else process.env.CHROMIUM_PROFILE = ORIGINAL_CHROMIUM_PROFILE;
|
||||
if (CHROMIUM_PROFILE_DIR) { try { fs.rmSync(CHROMIUM_PROFILE_DIR, { recursive: true, force: true }); } catch {} }
|
||||
});
|
||||
|
||||
|
||||
// Thin wrappers that bridge old test calls (bm as 3rd arg) to new signatures (session + bm)
|
||||
const handleReadCommand = (cmd: string, args: string[], b: BrowserManager) =>
|
||||
_handleReadCommand(cmd, args, b.getActiveSession(), b);
|
||||
|
||||
@@ -10,7 +10,8 @@
|
||||
* No LLM involved — this is a deterministic functional test.
|
||||
*/
|
||||
|
||||
import { describe, test, expect, beforeAll, afterAll } from 'bun:test';
|
||||
import * as os from 'os';
|
||||
import { afterAll, beforeAll, describe, expect, test } from 'bun:test';
|
||||
import { BrowserManager } from '../src/browser-manager';
|
||||
import { handleReadCommand as _handleReadCommand } from '../src/read-commands';
|
||||
import { handleWriteCommand as _handleWriteCommand } from '../src/write-commands';
|
||||
@@ -23,6 +24,24 @@ import { generateCompareHtml } from '../../design/src/compare';
|
||||
import * as fs from 'fs';
|
||||
import * as path from 'path';
|
||||
|
||||
// Per-FILE Chromium profile: this file launches an in-process persistent
|
||||
// context (BrowserManager.launch()), and sharing a profile dir with the
|
||||
// long-lived browse daemon a sibling file may have spawned kills one side's
|
||||
// Chromium (ProcessSingleton on user-data-dir). Scoped via hooks, never
|
||||
// module scope (see test/gstack-home-module-scope.test.ts's rationale).
|
||||
const ORIGINAL_CHROMIUM_PROFILE = process.env.CHROMIUM_PROFILE;
|
||||
let CHROMIUM_PROFILE_DIR: string | undefined;
|
||||
beforeAll(() => {
|
||||
CHROMIUM_PROFILE_DIR = fs.mkdtempSync(path.join(os.tmpdir(), 'gstack-test-profile-'));
|
||||
process.env.CHROMIUM_PROFILE = CHROMIUM_PROFILE_DIR;
|
||||
});
|
||||
afterAll(() => {
|
||||
if (ORIGINAL_CHROMIUM_PROFILE === undefined) delete process.env.CHROMIUM_PROFILE;
|
||||
else process.env.CHROMIUM_PROFILE = ORIGINAL_CHROMIUM_PROFILE;
|
||||
if (CHROMIUM_PROFILE_DIR) { try { fs.rmSync(CHROMIUM_PROFILE_DIR, { recursive: true, force: true }); } catch {} }
|
||||
});
|
||||
|
||||
|
||||
// QUARANTINED (opt-in via GSTACK_COMPARE_BOARD_TESTS=1): all 16 tests fail
|
||||
// identically on origin/main v1.64.1.0, solo, on dev machines — verified per
|
||||
// the blame protocol during the 2026-08 test-infra pass. Main's own CI lane
|
||||
|
||||
@@ -11,7 +11,8 @@
|
||||
* 7. Chain security (domain + tab enforcement)
|
||||
*/
|
||||
|
||||
import { describe, test, expect, beforeAll, afterAll, beforeEach } from 'bun:test';
|
||||
import * as os from 'os';
|
||||
import { afterAll, beforeAll, beforeEach, describe, expect, test } from 'bun:test';
|
||||
import * as fs from 'fs';
|
||||
import * as path from 'path';
|
||||
import { startTestServer } from './test-server';
|
||||
@@ -25,6 +26,24 @@ import {
|
||||
} from '../src/content-security';
|
||||
import { generateInstructionBlock } from '../src/cli';
|
||||
|
||||
// Per-FILE Chromium profile: this file launches an in-process persistent
|
||||
// context (BrowserManager.launch()), and sharing a profile dir with the
|
||||
// long-lived browse daemon a sibling file may have spawned kills one side's
|
||||
// Chromium (ProcessSingleton on user-data-dir). Scoped via hooks, never
|
||||
// module scope (see test/gstack-home-module-scope.test.ts's rationale).
|
||||
const ORIGINAL_CHROMIUM_PROFILE = process.env.CHROMIUM_PROFILE;
|
||||
let CHROMIUM_PROFILE_DIR: string | undefined;
|
||||
beforeAll(() => {
|
||||
CHROMIUM_PROFILE_DIR = fs.mkdtempSync(path.join(os.tmpdir(), 'gstack-test-profile-'));
|
||||
process.env.CHROMIUM_PROFILE = CHROMIUM_PROFILE_DIR;
|
||||
});
|
||||
afterAll(() => {
|
||||
if (ORIGINAL_CHROMIUM_PROFILE === undefined) delete process.env.CHROMIUM_PROFILE;
|
||||
else process.env.CHROMIUM_PROFILE = ORIGINAL_CHROMIUM_PROFILE;
|
||||
if (CHROMIUM_PROFILE_DIR) { try { fs.rmSync(CHROMIUM_PROFILE_DIR, { recursive: true, force: true }); } catch {} }
|
||||
});
|
||||
|
||||
|
||||
// Source-level tests
|
||||
const SERVER_SRC = fs.readFileSync(path.join(import.meta.dir, '../src/server.ts'), 'utf-8');
|
||||
const CLI_SRC = fs.readFileSync(path.join(import.meta.dir, '../src/cli.ts'), 'utf-8');
|
||||
|
||||
@@ -8,11 +8,32 @@
|
||||
* the framework's own validator still reports a mismatch.
|
||||
*/
|
||||
|
||||
import { describe, test, expect, beforeAll, afterAll } from 'bun:test';
|
||||
import * as fs from 'fs';
|
||||
import * as path from 'path';
|
||||
import * as os from 'os';
|
||||
import { afterAll, beforeAll, describe, expect, test } from 'bun:test';
|
||||
import { startTestServer } from './test-server';
|
||||
import { BrowserManager } from '../src/browser-manager';
|
||||
import { handleWriteCommand as _handleWriteCommand } from '../src/write-commands';
|
||||
|
||||
// Per-FILE Chromium profile: this file launches an in-process persistent
|
||||
// context (BrowserManager.launch()), and sharing a profile dir with the
|
||||
// long-lived browse daemon a sibling file may have spawned kills one side's
|
||||
// Chromium (ProcessSingleton on user-data-dir). Scoped via hooks, never
|
||||
// module scope (see test/gstack-home-module-scope.test.ts's rationale).
|
||||
const ORIGINAL_CHROMIUM_PROFILE = process.env.CHROMIUM_PROFILE;
|
||||
let CHROMIUM_PROFILE_DIR: string | undefined;
|
||||
beforeAll(() => {
|
||||
CHROMIUM_PROFILE_DIR = fs.mkdtempSync(path.join(os.tmpdir(), 'gstack-test-profile-'));
|
||||
process.env.CHROMIUM_PROFILE = CHROMIUM_PROFILE_DIR;
|
||||
});
|
||||
afterAll(() => {
|
||||
if (ORIGINAL_CHROMIUM_PROFILE === undefined) delete process.env.CHROMIUM_PROFILE;
|
||||
else process.env.CHROMIUM_PROFILE = ORIGINAL_CHROMIUM_PROFILE;
|
||||
if (CHROMIUM_PROFILE_DIR) { try { fs.rmSync(CHROMIUM_PROFILE_DIR, { recursive: true, force: true }); } catch {} }
|
||||
});
|
||||
|
||||
|
||||
const handleWriteCommand = (cmd: string, args: string[], b: BrowserManager) =>
|
||||
_handleWriteCommand(cmd, args, b.getActiveSession(), b);
|
||||
|
||||
|
||||
@@ -5,12 +5,33 @@
|
||||
* Integration tests cover the full handoff flow with real Playwright browsers.
|
||||
*/
|
||||
|
||||
import { describe, test, expect, beforeAll, afterAll } from 'bun:test';
|
||||
import * as fs from 'fs';
|
||||
import * as path from 'path';
|
||||
import * as os from 'os';
|
||||
import { afterAll, beforeAll, describe, expect, test } from 'bun:test';
|
||||
import { startTestServer } from './test-server';
|
||||
import { BrowserManager, type BrowserState } from '../src/browser-manager';
|
||||
import { handleWriteCommand as _handleWriteCommand } from '../src/write-commands';
|
||||
import { handleMetaCommand } from '../src/meta-commands';
|
||||
|
||||
// Per-FILE Chromium profile: this file launches an in-process persistent
|
||||
// context (BrowserManager.launch()), and sharing a profile dir with the
|
||||
// long-lived browse daemon a sibling file may have spawned kills one side's
|
||||
// Chromium (ProcessSingleton on user-data-dir). Scoped via hooks, never
|
||||
// module scope (see test/gstack-home-module-scope.test.ts's rationale).
|
||||
const ORIGINAL_CHROMIUM_PROFILE = process.env.CHROMIUM_PROFILE;
|
||||
let CHROMIUM_PROFILE_DIR: string | undefined;
|
||||
beforeAll(() => {
|
||||
CHROMIUM_PROFILE_DIR = fs.mkdtempSync(path.join(os.tmpdir(), 'gstack-test-profile-'));
|
||||
process.env.CHROMIUM_PROFILE = CHROMIUM_PROFILE_DIR;
|
||||
});
|
||||
afterAll(() => {
|
||||
if (ORIGINAL_CHROMIUM_PROFILE === undefined) delete process.env.CHROMIUM_PROFILE;
|
||||
else process.env.CHROMIUM_PROFILE = ORIGINAL_CHROMIUM_PROFILE;
|
||||
if (CHROMIUM_PROFILE_DIR) { try { fs.rmSync(CHROMIUM_PROFILE_DIR, { recursive: true, force: true }); } catch {} }
|
||||
});
|
||||
|
||||
|
||||
const handleWriteCommand = (cmd: string, args: string[], b: BrowserManager) =>
|
||||
_handleWriteCommand(cmd, args, b.getActiveSession(), b);
|
||||
|
||||
|
||||
@@ -20,12 +20,30 @@
|
||||
* CI). To prime: `bun run browse/src/sidebar-agent.ts` for ~30s and kill it.
|
||||
*/
|
||||
|
||||
import { describe, test, expect, beforeAll, afterAll } from 'bun:test';
|
||||
import { afterAll, beforeAll, describe, expect, test } from 'bun:test';
|
||||
import * as fs from 'fs';
|
||||
import * as os from 'os';
|
||||
import * as path from 'path';
|
||||
import { startTestServer } from './test-server';
|
||||
import { BrowserManager } from '../src/browser-manager';
|
||||
|
||||
// Per-FILE Chromium profile: this file launches an in-process persistent
|
||||
// context (BrowserManager.launch()), and sharing a profile dir with the
|
||||
// long-lived browse daemon a sibling file may have spawned kills one side's
|
||||
// Chromium (ProcessSingleton on user-data-dir). Scoped via hooks, never
|
||||
// module scope (see test/gstack-home-module-scope.test.ts's rationale).
|
||||
const ORIGINAL_CHROMIUM_PROFILE = process.env.CHROMIUM_PROFILE;
|
||||
let CHROMIUM_PROFILE_DIR: string | undefined;
|
||||
beforeAll(() => {
|
||||
CHROMIUM_PROFILE_DIR = fs.mkdtempSync(path.join(os.tmpdir(), 'gstack-test-profile-'));
|
||||
process.env.CHROMIUM_PROFILE = CHROMIUM_PROFILE_DIR;
|
||||
});
|
||||
afterAll(() => {
|
||||
if (ORIGINAL_CHROMIUM_PROFILE === undefined) delete process.env.CHROMIUM_PROFILE;
|
||||
else process.env.CHROMIUM_PROFILE = ORIGINAL_CHROMIUM_PROFILE;
|
||||
if (CHROMIUM_PROFILE_DIR) { try { fs.rmSync(CHROMIUM_PROFILE_DIR, { recursive: true, force: true }); } catch {} }
|
||||
});
|
||||
|
||||
import {
|
||||
markHiddenElements,
|
||||
getCleanTextWithStripping,
|
||||
|
||||
@@ -15,7 +15,7 @@
|
||||
* shutdown, and the gate is BROWSE_PERSIST_STATE (default off).
|
||||
*/
|
||||
|
||||
import { describe, test, expect, afterAll } from 'bun:test';
|
||||
import { afterAll, beforeAll, describe, expect, test } from 'bun:test';
|
||||
import { canRevokeWrites } from '../../test/helpers/fs-caps';
|
||||
import * as fs from 'fs';
|
||||
import * as os from 'os';
|
||||
@@ -26,6 +26,24 @@ import {
|
||||
} from '../src/session-persist';
|
||||
import type { BrowserState } from '../src/browser-manager';
|
||||
|
||||
// Per-FILE Chromium profile: this file launches an in-process persistent
|
||||
// context (BrowserManager.launch()), and sharing a profile dir with the
|
||||
// long-lived browse daemon a sibling file may have spawned kills one side's
|
||||
// Chromium (ProcessSingleton on user-data-dir). Scoped via hooks, never
|
||||
// module scope (see test/gstack-home-module-scope.test.ts's rationale).
|
||||
const ORIGINAL_CHROMIUM_PROFILE = process.env.CHROMIUM_PROFILE;
|
||||
let CHROMIUM_PROFILE_DIR: string | undefined;
|
||||
beforeAll(() => {
|
||||
CHROMIUM_PROFILE_DIR = fs.mkdtempSync(path.join(os.tmpdir(), 'gstack-test-profile-'));
|
||||
process.env.CHROMIUM_PROFILE = CHROMIUM_PROFILE_DIR;
|
||||
});
|
||||
afterAll(() => {
|
||||
if (ORIGINAL_CHROMIUM_PROFILE === undefined) delete process.env.CHROMIUM_PROFILE;
|
||||
else process.env.CHROMIUM_PROFILE = ORIGINAL_CHROMIUM_PROFILE;
|
||||
if (CHROMIUM_PROFILE_DIR) { try { fs.rmSync(CHROMIUM_PROFILE_DIR, { recursive: true, force: true }); } catch {} }
|
||||
});
|
||||
|
||||
|
||||
const tmpRoot = fs.mkdtempSync(path.join(os.tmpdir(), 'browse-persist-'));
|
||||
afterAll(() => { fs.rmSync(tmpRoot, { recursive: true, force: true }); });
|
||||
|
||||
|
||||
@@ -5,7 +5,9 @@
|
||||
* ref invalidation on navigation, and ref resolution in commands.
|
||||
*/
|
||||
|
||||
import { describe, test, expect, beforeAll, afterAll } from 'bun:test';
|
||||
import * as path from 'path';
|
||||
import * as os from 'os';
|
||||
import { afterAll, beforeAll, describe, expect, test } from 'bun:test';
|
||||
import { startTestServer } from './test-server';
|
||||
import { BrowserManager } from '../src/browser-manager';
|
||||
import { handleReadCommand as _handleReadCommand } from '../src/read-commands';
|
||||
@@ -13,6 +15,24 @@ import { handleWriteCommand as _handleWriteCommand } from '../src/write-commands
|
||||
import { handleMetaCommand } from '../src/meta-commands';
|
||||
import * as fs from 'fs';
|
||||
|
||||
// Per-FILE Chromium profile: this file launches an in-process persistent
|
||||
// context (BrowserManager.launch()), and sharing a profile dir with the
|
||||
// long-lived browse daemon a sibling file may have spawned kills one side's
|
||||
// Chromium (ProcessSingleton on user-data-dir). Scoped via hooks, never
|
||||
// module scope (see test/gstack-home-module-scope.test.ts's rationale).
|
||||
const ORIGINAL_CHROMIUM_PROFILE = process.env.CHROMIUM_PROFILE;
|
||||
let CHROMIUM_PROFILE_DIR: string | undefined;
|
||||
beforeAll(() => {
|
||||
CHROMIUM_PROFILE_DIR = fs.mkdtempSync(path.join(os.tmpdir(), 'gstack-test-profile-'));
|
||||
process.env.CHROMIUM_PROFILE = CHROMIUM_PROFILE_DIR;
|
||||
});
|
||||
afterAll(() => {
|
||||
if (ORIGINAL_CHROMIUM_PROFILE === undefined) delete process.env.CHROMIUM_PROFILE;
|
||||
else process.env.CHROMIUM_PROFILE = ORIGINAL_CHROMIUM_PROFILE;
|
||||
if (CHROMIUM_PROFILE_DIR) { try { fs.rmSync(CHROMIUM_PROFILE_DIR, { recursive: true, force: true }); } catch {} }
|
||||
});
|
||||
|
||||
|
||||
const handleReadCommand = (cmd: string, args: string[], b: BrowserManager) =>
|
||||
_handleReadCommand(cmd, args, b.getActiveSession(), b);
|
||||
const handleWriteCommand = (cmd: string, args: string[], b: BrowserManager) =>
|
||||
|
||||
Reference in New Issue
Block a user