Files
smartshell/test/test.errorHandling.ts

222 lines
5.7 KiB
TypeScript

import { expect, tap } from '@git.zone/tstest/tapbundle';
import * as smartshell from '../ts/index.js';
tap.test('should handle EPIPE errors gracefully', async () => {
const testSmartshell = new smartshell.Smartshell({
executor: 'bash',
sourceFilePaths: [],
});
const streaming = await testSmartshell.execStreamingInteractiveControl('head -n 1');
// Send more data after head exits (will cause EPIPE)
await streaming.sendLine('Line 1');
// This should not throw even though head has exited
let errorThrown = false;
try {
await streaming.sendLine('Line 2');
await streaming.sendLine('Line 3');
} catch (error) {
errorThrown = true;
// EPIPE or destroyed stdin is expected
}
const result = await streaming.finalPromise;
expect(result.exitCode).toEqual(0);
expect(result.stdout).toContain('Line 1');
});
tap.test('should handle strict mode with non-zero exit codes', async () => {
const testSmartshell = new smartshell.Smartshell({
executor: 'bash',
sourceFilePaths: [],
});
let errorThrown = false;
let errorMessage = '';
try {
await testSmartshell.execStrict('exit 42');
} catch (error) {
errorThrown = true;
errorMessage = error.message;
}
expect(errorThrown).toBeTrue();
expect(errorMessage).toContain('exited with code 42');
});
tap.test('should handle strict mode with signal termination', async () => {
const testSmartshell = new smartshell.Smartshell({
executor: 'bash',
sourceFilePaths: [],
});
let errorThrown = false;
let errorMessage = '';
try {
// Use execSpawn with strict mode and kill it
const result = testSmartshell.execSpawn('sleep', ['10'], {
strict: true,
timeout: 100 // Will cause SIGTERM
});
await result;
} catch (error) {
errorThrown = true;
errorMessage = error.message;
}
expect(errorThrown).toBeTrue();
expect(errorMessage).toContain('terminated by signal');
});
tap.test('execAndWaitForLine with timeout should reject properly', async () => {
const testSmartshell = new smartshell.Smartshell({
executor: 'bash',
sourceFilePaths: [],
});
let errorThrown = false;
let errorMessage = '';
try {
await testSmartshell.execAndWaitForLine(
'sleep 5 && echo "Too late"',
/Too late/,
false,
{ timeout: 100 }
);
} catch (error) {
errorThrown = true;
errorMessage = error.message;
}
expect(errorThrown).toBeTrue();
expect(errorMessage).toContain('Timeout waiting for pattern');
});
tap.test('execAndWaitForLine with terminateOnMatch should stop process', async () => {
const testSmartshell = new smartshell.Smartshell({
executor: 'bash',
sourceFilePaths: [],
});
const start = Date.now();
await testSmartshell.execAndWaitForLine(
'echo "Match this" && sleep 5',
/Match this/,
false,
{ terminateOnMatch: true }
);
const duration = Date.now() - start;
// Should terminate immediately after match, not wait for sleep
expect(duration).toBeLessThan(2000);
});
tap.test('should handle process ending without matching pattern', async () => {
const testSmartshell = new smartshell.Smartshell({
executor: 'bash',
sourceFilePaths: [],
});
let errorThrown = false;
let errorMessage = '';
try {
await testSmartshell.execAndWaitForLine(
'echo "Wrong text"',
/Never appears/,
false
);
} catch (error) {
errorThrown = true;
errorMessage = error.message;
}
expect(errorThrown).toBeTrue();
expect(errorMessage).toContain('Process ended without matching pattern');
});
tap.test('passthrough unpipe should handle destroyed stdin gracefully', async () => {
const testSmartshell = new smartshell.Smartshell({
executor: 'bash',
sourceFilePaths: [],
});
// This should complete without throwing even if stdin operations fail
const result = await testSmartshell.execPassthrough('echo "Test passthrough unpipe"');
expect(result.exitCode).toEqual(0);
expect(result.stdout).toContain('Test passthrough unpipe');
});
tap.test('should handle write after stream destroyed', async () => {
const testSmartshell = new smartshell.Smartshell({
executor: 'bash',
sourceFilePaths: [],
});
const interactive = await testSmartshell.execInteractiveControl('true'); // Exits immediately
// Wait for process to exit
await interactive.finalPromise;
// Try to write after process has exited
let errorThrown = false;
try {
await interactive.sendLine('This should fail');
} catch (error) {
errorThrown = true;
expect(error.message).toContain('destroyed');
}
expect(errorThrown).toBeTrue();
});
tap.test('debug mode should log additional information', async () => {
const testSmartshell = new smartshell.Smartshell({
executor: 'bash',
sourceFilePaths: [],
});
// Capture console.log output
const originalLog = console.log;
let debugOutput = '';
console.log = (msg: string) => {
debugOutput += msg + '\n';
};
try {
const streaming = await testSmartshell.execSpawnStreaming('echo', ['Debug test'], {
debug: true
});
await streaming.terminate();
await streaming.finalPromise;
} finally {
console.log = originalLog;
}
// Should have logged debug messages
expect(debugOutput).toContain('[smartshell]');
});
tap.test('custom environment variables should be passed correctly', async () => {
const testSmartshell = new smartshell.Smartshell({
executor: 'bash',
sourceFilePaths: [],
});
const result = await testSmartshell.execSpawn('bash', ['-c', 'echo $CUSTOM_VAR'], {
env: {
...process.env,
CUSTOM_VAR: 'test_value_123'
}
});
expect(result.exitCode).toEqual(0);
expect(result.stdout).toContain('test_value_123');
});
export default tap.start();