diff --git a/test/helpers.ts b/test/helpers.ts index 90d6898b..57600802 100644 --- a/test/helpers.ts +++ b/test/helpers.ts @@ -238,6 +238,24 @@ export function createSession( return envelope.result.sessionId; } +// An interactive bash for a test. `--norc --noprofile` keep it from reading +// ~/.bashrc and ~/.profile: a startup file that prompts, prints or ends the +// shell breaks the commands that the test types (a mise "Trust them?" prompt +// swallowed one). +export const HERMETIC_BASH = ['/bin/bash', '--norc', '--noprofile', '-i']; + +// Like createSession with HERMETIC_BASH. HISTFILE=/dev/null keeps the shell +// from reading or writing ~/.bash_history. +export function createBashSession( + testHome: string, + options: CreateSessionOptions = {}, +): string { + return createSession(testHome, HERMETIC_BASH, { + ...options, + env: { HISTFILE: '/dev/null', ...options.env }, + }); +} + export function destroySession(testHome: string, sessionId: string): void { if (sessionId.length === 0) { return; diff --git a/test/integration/batch.test.ts b/test/integration/batch.test.ts index 33a7bc9a..00075822 100644 --- a/test/integration/batch.test.ts +++ b/test/integration/batch.test.ts @@ -11,6 +11,7 @@ import type { CommandErrorEnvelope } from '../../src/protocol/envelope.js'; import { cleanupHome, + createBashSession, createSession, destroySession, runCli, @@ -170,7 +171,7 @@ describe('batch command integration', { timeout: 45_000 }, () => { }); it('reads the step array from --file', async () => { - sessionId = createSession(testHome, ['/bin/bash']); + sessionId = createBashSession(testHome); await sleep(500); const stepsPath = join(testHome, 'plan.json'); diff --git a/test/integration/run.test.ts b/test/integration/run.test.ts index 2193c31a..f827c723 100644 --- a/test/integration/run.test.ts +++ b/test/integration/run.test.ts @@ -13,6 +13,7 @@ import { afterEach, beforeEach, describe, expect, it } from 'vitest'; import { cleanupHome, + createBashSession, createSession, destroySession, readEvents, @@ -122,7 +123,7 @@ describe('run command integration', { timeout: 45_000 }, () => { }); it('returns immediately with --no-wait', async () => { - sessionId = createSession(testHome, ['/bin/bash']); + sessionId = createBashSession(testHome); await sleep(500); const result = runCli( @@ -148,7 +149,7 @@ describe('run command integration', { timeout: 45_000 }, () => { }); it('reads command from --file', async () => { - sessionId = createSession(testHome, ['/bin/bash']); + sessionId = createBashSession(testHome); await sleep(500); const scriptPath = join(testHome, 'test-script.sh'); @@ -171,7 +172,7 @@ describe('run command integration', { timeout: 45_000 }, () => { }); it('handles multiline command input', async () => { - sessionId = createSession(testHome, ['/bin/bash']); + sessionId = createBashSession(testHome); await sleep(1000); const result = runCli( @@ -198,7 +199,7 @@ describe('run command integration', { timeout: 45_000 }, () => { }); it('waits for the command to complete before reporting success', async () => { - sessionId = createSession(testHome, ['/bin/bash']); + sessionId = createBashSession(testHome); await sleep(1000); const start = Date.now(); @@ -247,7 +248,7 @@ describe('run command integration', { timeout: 45_000 }, () => { }); it('records input_run event in the event log', async () => { - sessionId = createSession(testHome, ['/bin/bash']); + sessionId = createBashSession(testHome); await sleep(500); const result = runCli( @@ -329,7 +330,7 @@ describe('run command integration', { timeout: 45_000 }, () => { }); it('returns timedOut when run completion is not observed within timeout', async () => { - sessionId = createSession(testHome, ['/bin/bash']); + sessionId = createBashSession(testHome); await sleep(500); const result = runCli( @@ -506,13 +507,9 @@ describe('run command integration', { timeout: 45_000 }, () => { ); it('does not log postamble cursor controls when shell echo is disabled', async () => { - sessionId = createSession( - testHome, - ['/bin/bash', '--noprofile', '--norc'], - { - cols: WIDE_ARTIFACT_PROOF_COLS, - }, - ); + sessionId = createBashSession(testHome, { + cols: WIDE_ARTIFACT_PROOF_COLS, + }); await sleep(1000); const disableEchoResult = runCli( @@ -590,7 +587,7 @@ describe('run command integration', { timeout: 45_000 }, () => { }); it('records structured run completion without leaking sentinel text to artifacts', async () => { - sessionId = createSession(testHome, ['/bin/bash'], { + sessionId = createBashSession(testHome, { cols: WIDE_ARTIFACT_PROOF_COLS, }); await sleep(1000); diff --git a/test/integration/slow-replay.test.ts b/test/integration/slow-replay.test.ts index f7717fc3..65be9c82 100644 --- a/test/integration/slow-replay.test.ts +++ b/test/integration/slow-replay.test.ts @@ -11,6 +11,7 @@ import { cleanupHome, createSession, destroySession, + HERMETIC_BASH, readEvents, sleep, } from '../helpers.js'; @@ -209,7 +210,7 @@ describe( // The host default renderer is ghostty-web, so that the catch-up replay // after the run uses the slow backend. await createSlowSession( - ['/bin/bash'], + HERMETIC_BASH, { AGENT_TTY_RENDERER: 'ghostty-web', AGENT_TTY_TEST_REPLAY_MS_PER_EVENT: '100',