222 lines
5.7 KiB
TypeScript
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(); |