async OSC/DCS registering/dispose tests on parser instance

This commit is contained in:
Jörg Breitbart
2021-02-25 15:41:10 +01:00
parent 26502114d4
commit bce3269e44
2 changed files with 192 additions and 27 deletions
+181 -20
View File
@@ -102,7 +102,7 @@ class TestEscapeSequenceParser extends EscapeSequenceParser {
public trackedStack: IParserStackState[] = [];
public parse(data: Uint32Array, length: number, promiseResult?: boolean): void | Promise<boolean> {
const result = super.parse(data, length, promiseResult);
if (result && this._trackStack) {
if (result instanceof Promise && this._trackStack) {
this.trackedStack.push({ ...this.parseStack });
}
return result;
@@ -1742,9 +1742,9 @@ async function throwsAsync(fn: () => Promise<any>, message?: string | undefined)
}
describe('EscapeSequenceParser - async', () => {
// sequences: SGR 1;31 | hello SP | ESC %G | wor | ESC E | ld! | SGR 0 | EXE \r\n | $> | OSC 1;foo=bar ST
// sequences: SGR 1;31 | hello SP | ESC %G | wor | ESC E | ld! | SGR 0 | EXE \r\n | $> | DCS 1;2 a [xyz] ST | OSC 1;foo=bar ST | FIN
// needed handlers: CSI m, PRINT, ESC %G, ESC E, EXE \r, EXE \n, OSC 1
const INPUT = '\x1b[1;31mhello \x1b%Gwor\x1bEld!\x1b[0m\r\n$>\x1b]1;foo=bar\x1b\\';
const INPUT = '\x1b[1;31mhello \x1b%Gwor\x1bEld!\x1b[0m\r\n$>\x1bP1;2axyz\x1b\\\x1b]1;foo=bar\x1b\\FIN';
let RESULT: any[];
let parser: TestEscapeSequenceParser;
const callstack: any[] = [];
@@ -1764,7 +1764,9 @@ describe('EscapeSequenceParser - async', () => {
['EXE \r'],
['EXE \n'],
['PRINT', '$>'],
['OSC 1', 'foo=bar']
['DCS a', ['xyz', [1, 2]]],
['OSC 1', 'foo=bar'],
['PRINT', 'FIN']
];
parser = new TestEscapeSequenceParser();
parser.reset();
@@ -1786,9 +1788,10 @@ describe('EscapeSequenceParser - async', () => {
parser.setExecuteHandler('\r', () => { callstack.push(['EXE \r']); return true; });
parser.setExecuteHandler('\n', () => { callstack.push(['EXE \n']); return true; });
parser.registerOscHandler(1, new OscHandler(data => { callstack.push(['OSC 1', data]); return true; }));
parser.registerDcsHandler({final: 'a'}, new DcsHandler((data, params) => { callstack.push(['DCS a', [data, params.toArray()]]); return true;}));
});
it('sync handlers keep parsed in sync mode', () => {
it('sync handlers keep being parsed in sync mode', () => {
// note: if we have only sync handlers, a parse call should never return anything
assert.equal(!parseSync(parser, INPUT), true);
assert.equal(parser.parseStack.state, ParserStackType.NONE); // not paused
@@ -1819,7 +1822,8 @@ describe('EscapeSequenceParser - async', () => {
parser.registerEscHandler({ final: 'E' }, async () => { callstack.push(['ESC E']); return true; });
parser.setExecuteHandler('\r', () => { callstack.push(['EXE \r']); return true; });
parser.setExecuteHandler('\n', () => { callstack.push(['EXE \n']); return true; });
parser.registerOscHandler(1, new OscHandler(data => { callstack.push(['OSC 1', data]); return true; }));
parser.registerOscHandler(1, new OscHandler(async data => { callstack.push(['OSC 1', data]); return true; }));
parser.registerDcsHandler({final: 'a'}, new DcsHandler(async (data, params) => { callstack.push(['DCS a', [data, params.toArray()]]); return true;}));
});
it('sync parse call does not work anymore', () => {
@@ -1849,7 +1853,12 @@ describe('EscapeSequenceParser - async', () => {
await parseP(parser, INPUT);
assert.deepEqual(callstack, RESULT);
evalStackSaves(parser.trackedStack, [
[6, ParserStackType.CSI, 0], [15, ParserStackType.ESC, 0], [20, ParserStackType.ESC, 0], [27, ParserStackType.CSI, 0]
[6, ParserStackType.CSI, 0],
[15, ParserStackType.ESC, 0],
[20, ParserStackType.ESC, 0],
[27, ParserStackType.CSI, 0],
[41, ParserStackType.DCS, 0],
[54, ParserStackType.OSC, 0]
]);
});
it('correct result on chunked awaited parse calls', async () => {
@@ -1874,7 +1883,11 @@ describe('EscapeSequenceParser - async', () => {
['EXE \n'],
['PRINT', '$'],
['PRINT', '>'],
['OSC 1', 'foo=bar']
['DCS a', ['xyz', [1, 2]]],
['OSC 1', 'foo=bar'],
['PRINT', 'F'],
['PRINT', 'I'],
['PRINT', 'N']
];
// split to single char input
@@ -1888,7 +1901,9 @@ describe('EscapeSequenceParser - async', () => {
[0, ParserStackType.CSI, 0],
[0, ParserStackType.ESC, 0],
[0, ParserStackType.ESC, 0],
[0, ParserStackType.CSI, 0]
[0, ParserStackType.CSI, 0],
[0, ParserStackType.DCS, 0],
[0, ParserStackType.OSC, 0]
]);
});
it('multiple async SGR handlers', async () => {
@@ -1906,7 +1921,9 @@ describe('EscapeSequenceParser - async', () => {
[15, ParserStackType.ESC, 0],
[20, ParserStackType.ESC, 0],
[27, ParserStackType.CSI, 1],
[27, ParserStackType.CSI, 0]
[27, ParserStackType.CSI, 0],
[41, ParserStackType.DCS, 0],
[54, ParserStackType.OSC, 0]
]);
clearAccu();
// after dispose we should be back to RESULT
@@ -1917,7 +1934,9 @@ describe('EscapeSequenceParser - async', () => {
[6, ParserStackType.CSI, 0],
[15, ParserStackType.ESC, 0],
[20, ParserStackType.ESC, 0],
[27, ParserStackType.CSI, 0]
[27, ParserStackType.CSI, 0],
[41, ParserStackType.DCS, 0],
[54, ParserStackType.OSC, 0]
]);
clearAccu();
@@ -1933,7 +1952,9 @@ describe('EscapeSequenceParser - async', () => {
[6, ParserStackType.CSI, 1],
[15, ParserStackType.ESC, 0],
[20, ParserStackType.ESC, 0],
[27, ParserStackType.CSI, 1]
[27, ParserStackType.CSI, 1],
[41, ParserStackType.DCS, 0],
[54, ParserStackType.OSC, 0]
]);
clearAccu();
// after dispose we should be back to RESULT
@@ -1944,7 +1965,9 @@ describe('EscapeSequenceParser - async', () => {
[6, ParserStackType.CSI, 0],
[15, ParserStackType.ESC, 0],
[20, ParserStackType.ESC, 0],
[27, ParserStackType.CSI, 0]
[27, ParserStackType.CSI, 0],
[41, ParserStackType.DCS, 0],
[54, ParserStackType.OSC, 0]
]);
});
it('multiple async ESC handlers', async () => {
@@ -1960,7 +1983,9 @@ describe('EscapeSequenceParser - async', () => {
[15, ParserStackType.ESC, 0],
[20, ParserStackType.ESC, 1],
[20, ParserStackType.ESC, 0],
[27, ParserStackType.CSI, 0]
[27, ParserStackType.CSI, 0],
[41, ParserStackType.DCS, 0],
[54, ParserStackType.OSC, 0]
]);
clearAccu();
// after dispose we should be back to RESULT
@@ -1971,7 +1996,9 @@ describe('EscapeSequenceParser - async', () => {
[6, ParserStackType.CSI, 0],
[15, ParserStackType.ESC, 0],
[20, ParserStackType.ESC, 0],
[27, ParserStackType.CSI, 0]
[27, ParserStackType.CSI, 0],
[41, ParserStackType.DCS, 0],
[54, ParserStackType.OSC, 0]
]);
clearAccu();
@@ -1986,7 +2013,9 @@ describe('EscapeSequenceParser - async', () => {
[6, ParserStackType.CSI, 0],
[15, ParserStackType.ESC, 0],
[20, ParserStackType.ESC, 1],
[27, ParserStackType.CSI, 0]
[27, ParserStackType.CSI, 0],
[41, ParserStackType.DCS, 0],
[54, ParserStackType.OSC, 0]
]);
clearAccu();
// after dispose we should be back to RESULT
@@ -1997,7 +2026,9 @@ describe('EscapeSequenceParser - async', () => {
[6, ParserStackType.CSI, 0],
[15, ParserStackType.ESC, 0],
[20, ParserStackType.ESC, 0],
[27, ParserStackType.CSI, 0]
[27, ParserStackType.CSI, 0],
[41, ParserStackType.DCS, 0],
[54, ParserStackType.OSC, 0]
]);
});
it('sync/async SGR mixed', async () => {
@@ -2020,7 +2051,9 @@ describe('EscapeSequenceParser - async', () => {
[15, ParserStackType.ESC, 0],
[20, ParserStackType.ESC, 0],
[27, ParserStackType.CSI, 2],
[27, ParserStackType.CSI, 0]
[27, ParserStackType.CSI, 0],
[41, ParserStackType.DCS, 0],
[54, ParserStackType.OSC, 0]
]);
clearAccu();
// dispose SGR2 (sync one)
@@ -2039,7 +2072,9 @@ describe('EscapeSequenceParser - async', () => {
[15, ParserStackType.ESC, 0],
[20, ParserStackType.ESC, 0],
[27, ParserStackType.CSI, 1],
[27, ParserStackType.CSI, 0]
[27, ParserStackType.CSI, 0],
[41, ParserStackType.DCS, 0],
[54, ParserStackType.OSC, 0]
]);
clearAccu();
// dispose SGR3 (async one)
@@ -2050,8 +2085,134 @@ describe('EscapeSequenceParser - async', () => {
[6, ParserStackType.CSI, 0],
[15, ParserStackType.ESC, 0],
[20, ParserStackType.ESC, 0],
[27, ParserStackType.CSI, 0]
[27, ParserStackType.CSI, 0],
[41, ParserStackType.DCS, 0],
[54, ParserStackType.OSC, 0]
]);
});
it('multiple async OSC handlers', async () => {
// register with fallback
const OSC2 = parser.registerOscHandler(1, new OscHandler(async data => { callstack.push(['2# OSC 1', data]); return false; }));
await parseP(parser, INPUT);
for (let i = 0; i < callstack.length; ++i) {
const entry = callstack[i];
if (entry[0] === '2# OSC 1') assert.equal(callstack[i + 1][0], 'OSC 1', 'Should fallback to original handler');
}
evalStackSaves(parser.trackedStack, [
[6, ParserStackType.CSI, 0],
[15, ParserStackType.ESC, 0],
[20, ParserStackType.ESC, 0],
[27, ParserStackType.CSI, 0],
[41, ParserStackType.DCS, 0],
[54, ParserStackType.OSC, 0],
[54, ParserStackType.OSC, 0]
]);
clearAccu();
// after dispose we should be back to RESULT
OSC2.dispose();
await parseP(parser, INPUT);
assert.deepEqual(callstack, RESULT, 'Should not call custom handler');
evalStackSaves(parser.trackedStack, [
[6, ParserStackType.CSI, 0],
[15, ParserStackType.ESC, 0],
[20, ParserStackType.ESC, 0],
[27, ParserStackType.CSI, 0],
[41, ParserStackType.DCS, 0],
[54, ParserStackType.OSC, 0]
]);
clearAccu();
// register without fallback
const OSC22 = parser.registerOscHandler(1, new OscHandler(async data => { callstack.push(['2# OSC 1', data]); return true; }));
await parseP(parser, INPUT);
for (let i = 0; i < callstack.length; ++i) {
const entry = callstack[i];
if (entry[0] === '2# OSC 1') assert.notEqual(callstack[i + 1][0], 'OSC 1', 'Should fallback to original handler');
}
evalStackSaves(parser.trackedStack, [
[6, ParserStackType.CSI, 0],
[15, ParserStackType.ESC, 0],
[20, ParserStackType.ESC, 0],
[27, ParserStackType.CSI, 0],
[41, ParserStackType.DCS, 0],
[54, ParserStackType.OSC, 0]
]);
clearAccu();
// after dispose we should be back to RESULT
OSC22.dispose();
await parseP(parser, INPUT);
assert.deepEqual(callstack, RESULT, 'Should not call custom handler');
evalStackSaves(parser.trackedStack, [
[6, ParserStackType.CSI, 0],
[15, ParserStackType.ESC, 0],
[20, ParserStackType.ESC, 0],
[27, ParserStackType.CSI, 0],
[41, ParserStackType.DCS, 0],
[54, ParserStackType.OSC, 0]
]);
clearAccu();
});
it('multiple async DCS handlers', async () => {
// register with fallback
const DCS2 = parser.registerDcsHandler({final: 'a'}, new DcsHandler(async (data, params) => { callstack.push(['#2 DCS a', [data, params.toArray()]]); return false;}));
await parseP(parser, INPUT);
for (let i = 0; i < callstack.length; ++i) {
const entry = callstack[i];
if (entry[0] === '2# DCS a') assert.equal(callstack[i + 1][0], 'DCS a', 'Should fallback to original handler');
}
evalStackSaves(parser.trackedStack, [
[6, ParserStackType.CSI, 0],
[15, ParserStackType.ESC, 0],
[20, ParserStackType.ESC, 0],
[27, ParserStackType.CSI, 0],
[41, ParserStackType.DCS, 0],
[41, ParserStackType.DCS, 0],
[54, ParserStackType.OSC, 0]
]);
clearAccu();
// after dispose we should be back to RESULT
DCS2.dispose();
await parseP(parser, INPUT);
assert.deepEqual(callstack, RESULT, 'Should not call custom handler');
evalStackSaves(parser.trackedStack, [
[6, ParserStackType.CSI, 0],
[15, ParserStackType.ESC, 0],
[20, ParserStackType.ESC, 0],
[27, ParserStackType.CSI, 0],
[41, ParserStackType.DCS, 0],
[54, ParserStackType.OSC, 0]
]);
clearAccu();
// register without fallback
const DCS22 = parser.registerDcsHandler({final: 'a'}, new DcsHandler(async (data, params) => { callstack.push(['#2 DCS a', [data, params.toArray()]]); return true;}));
await parseP(parser, INPUT);
for (let i = 0; i < callstack.length; ++i) {
const entry = callstack[i];
if (entry[0] === '2# DCS a') assert.notEqual(callstack[i + 1][0], 'DCS a', 'Should fallback to original handler');
}
evalStackSaves(parser.trackedStack, [
[6, ParserStackType.CSI, 0],
[15, ParserStackType.ESC, 0],
[20, ParserStackType.ESC, 0],
[27, ParserStackType.CSI, 0],
[41, ParserStackType.DCS, 0],
[54, ParserStackType.OSC, 0]
]);
clearAccu();
// after dispose we should be back to RESULT
DCS22.dispose();
await parseP(parser, INPUT);
assert.deepEqual(callstack, RESULT, 'Should not call custom handler');
evalStackSaves(parser.trackedStack, [
[6, ParserStackType.CSI, 0],
[15, ParserStackType.ESC, 0],
[20, ParserStackType.ESC, 0],
[27, ParserStackType.CSI, 0],
[41, ParserStackType.DCS, 0],
[54, ParserStackType.OSC, 0]
]);
clearAccu();
});
});
});
+11 -7
View File
@@ -492,7 +492,7 @@ export class EscapeSequenceParser extends Disposable implements IEscapeSequenceP
* --> Maybe easier: Give up on non-mutating rule for async handlers...
* (needs explanation in docs about exact executor/thenable/worker execution contexts)
*
* Example for proper parsing of multiple chunks:
* Boilerplate for proper parsing of multiple chunks with async handlers:
*
* ```typescript
* async function parseMultipleChunks(chunks: Uint32Array[]): Promise<void> {
@@ -518,10 +518,14 @@ export class EscapeSequenceParser extends Disposable implements IEscapeSequenceP
if (promiseResult === undefined || this._parseStack.state === ParserStackType.FAIL) {
/**
* Reject further parsing on improper continuation after pausing.
* This will happen with sync parse calls not awaiting a returned promise.
* It is a really bad condition with screwed up execution order,
* therefore we exit hard with an exception.
* FIXME: Do we need a method to escape from this broken parser state? (hard to achieve properly)
* This is a really bad condition with screwed up execution order and messed up terminal state,
* therefore we exit hard with an exception and reject any further parsing.
*
* Note: With `Terminal.write` usage this exception should never occur, as the top level
* calls are guaranteed to handle async conditions properly. If you ever encounter this
* exception in your terminal integration it indicates, that you injected data chunks to
* `InputHandler.parse` or `EscapeSequenceParser.parse` synchronously without waiting for
* continuation of a running async handler.
*/
this._parseStack.state = ParserStackType.FAIL;
throw new Error('improper continuation due to previous async handler, giving up parsing');
@@ -578,13 +582,13 @@ export class EscapeSequenceParser extends Disposable implements IEscapeSequenceP
if (handlerResult = this._oscParser.end(code !== 0x18 && code !== 0x1a, promiseResult)) {
return handlerResult;
}
if (code === 0x1b) transition |= ParserState.ESCAPE;
if (code === 0x1b) this._parseStack.transition |= ParserState.ESCAPE;
this._params.reset();
this._params.addParam(0); // ZDM
this._collect = 0;
break;
}
// cleanup before continuing with the main loop
// cleanup before continuing with the main sync loop
this._parseStack.state = ParserStackType.NONE;
start = this._parseStack.chunkPos + 1;
this.precedingCodepoint = 0;