mirror of
https://github.com/QwenLM/qwen-code.git
synced 2026-05-01 21:20:44 +00:00
fix(core): compare default shell output by logical wraps
This commit is contained in:
parent
b30bfa2020
commit
9bdeb71a19
2 changed files with 95 additions and 20 deletions
|
|
@ -167,6 +167,21 @@ const expectNormalizedWindowsPathEnv = (env: NodeJS.ProcessEnv) => {
|
|||
expect(env['Path']).toBeUndefined();
|
||||
};
|
||||
|
||||
const waitForDataEventCount = async (
|
||||
onOutputEventMock: Mock<(event: ShellOutputEvent) => void>,
|
||||
expectedCount: number,
|
||||
) => {
|
||||
for (let attempt = 0; attempt < 20; attempt++) {
|
||||
const dataEvents = onOutputEventMock.mock.calls.filter(
|
||||
([event]) => event.type === 'data',
|
||||
);
|
||||
if (dataEvents.length >= expectedCount) {
|
||||
return;
|
||||
}
|
||||
await new Promise((resolve) => setTimeout(resolve, 0));
|
||||
}
|
||||
};
|
||||
|
||||
describe('ShellExecutionService', () => {
|
||||
let mockPtyProcess: EventEmitter & {
|
||||
pid: number;
|
||||
|
|
@ -835,6 +850,47 @@ describe('ShellExecutionService', () => {
|
|||
);
|
||||
});
|
||||
|
||||
it('does not re-emit default plain live output when only soft-wrap segmentation changes', async () => {
|
||||
const abortController = new AbortController();
|
||||
const handle = await ShellExecutionService.execute(
|
||||
'narrow-output',
|
||||
'/test/dir',
|
||||
onOutputEventMock,
|
||||
abortController.signal,
|
||||
true,
|
||||
{
|
||||
...shellExecutionConfig,
|
||||
terminalWidth: 4,
|
||||
terminalHeight: 4,
|
||||
showColor: false,
|
||||
disableDynamicLineTrimming: false,
|
||||
},
|
||||
);
|
||||
|
||||
await new Promise((resolve) => process.nextTick(resolve));
|
||||
mockPtyProcess.onData.mock.calls[0][0]('abcdefgh');
|
||||
await waitForDataEventCount(onOutputEventMock, 1);
|
||||
|
||||
ShellExecutionService.resizePty(handle.pid!, 2, 4);
|
||||
mockPtyProcess.onData.mock.calls[0][0]('\r');
|
||||
mockPtyProcess.onExit.mock.calls[0][0]({ exitCode: 0, signal: null });
|
||||
await handle.result;
|
||||
|
||||
const dataEvents = onOutputEventMock.mock.calls.filter(
|
||||
([event]) => event.type === 'data',
|
||||
);
|
||||
expect(dataEvents).toHaveLength(1);
|
||||
const firstDataEvent = dataEvents[0][0];
|
||||
if (firstDataEvent.type !== 'data') {
|
||||
throw new Error('Expected a shell data event.');
|
||||
}
|
||||
const chunk = firstDataEvent.chunk as AnsiOutput;
|
||||
expect(chunk.map((line) => line[0]?.text).filter(Boolean)).toEqual([
|
||||
'abcd',
|
||||
'efgh',
|
||||
]);
|
||||
});
|
||||
|
||||
it('should handle multi-line output correctly when showColor is false', async () => {
|
||||
await simulateExecution(
|
||||
'ls --color=auto',
|
||||
|
|
|
|||
|
|
@ -205,6 +205,40 @@ const areAnsiOutputsEqual = (
|
|||
});
|
||||
};
|
||||
|
||||
const createPlainAnsiLine = (text: string) => [
|
||||
{
|
||||
text,
|
||||
bold: false,
|
||||
italic: false,
|
||||
underline: false,
|
||||
dim: false,
|
||||
inverse: false,
|
||||
fg: '',
|
||||
bg: '',
|
||||
},
|
||||
];
|
||||
|
||||
const serializePlainViewportToAnsiOutput = (
|
||||
terminal: pkg.Terminal,
|
||||
unwrapWrappedLines = false,
|
||||
): AnsiOutput => {
|
||||
const buffer = terminal.buffer.active;
|
||||
const lines: AnsiOutput = [];
|
||||
|
||||
for (let y = 0; y < terminal.rows; y++) {
|
||||
const line = buffer.getLine(buffer.viewportY + y);
|
||||
const lineContent = line ? line.translateToString(true) : '';
|
||||
|
||||
if (unwrapWrappedLines && line?.isWrapped && lines.length > 0) {
|
||||
lines[lines.length - 1][0].text += lineContent;
|
||||
} else {
|
||||
lines.push(createPlainAnsiLine(lineContent));
|
||||
}
|
||||
}
|
||||
|
||||
return lines;
|
||||
};
|
||||
|
||||
interface ProcessCleanupStrategy {
|
||||
killPty(pid: number, pty: ActivePty): void;
|
||||
killChildProcesses(pids: Set<number>): void;
|
||||
|
|
@ -615,26 +649,11 @@ export class ShellExecutionService {
|
|||
{ unwrapWrappedLines: true },
|
||||
);
|
||||
} else {
|
||||
const buffer = headlessTerminal.buffer.active;
|
||||
const lines: AnsiOutput = [];
|
||||
for (let y = 0; y < headlessTerminal.rows; y++) {
|
||||
const line = buffer.getLine(buffer.viewportY + y);
|
||||
const lineContent = line ? line.translateToString(true) : '';
|
||||
lines.push([
|
||||
{
|
||||
text: lineContent,
|
||||
bold: false,
|
||||
italic: false,
|
||||
underline: false,
|
||||
dim: false,
|
||||
inverse: false,
|
||||
fg: '',
|
||||
bg: '',
|
||||
},
|
||||
]);
|
||||
}
|
||||
newOutput = lines;
|
||||
newOutputComparison = lines;
|
||||
newOutput = serializePlainViewportToAnsiOutput(headlessTerminal);
|
||||
newOutputComparison = serializePlainViewportToAnsiOutput(
|
||||
headlessTerminal,
|
||||
true,
|
||||
);
|
||||
}
|
||||
|
||||
const trimmedOutput = trimTrailingEmptyAnsiLines(newOutput);
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue