add state and transition tests

This commit is contained in:
Jörg Breitbart
2026-04-24 16:19:02 +02:00
parent ee7d84bdd3
commit 849a9a53ca
2 changed files with 243 additions and 6 deletions
+243 -2
View File
@@ -3,7 +3,7 @@
* @license MIT
*/
import { IParsingState, IParams, ParamsArray, IOscParser, IOscHandler, OscFallbackHandlerType, IFunctionIdentifier, IParserStackState, ParserStackType, ResumableHandlersType } from 'common/parser/Types';
import { IParsingState, IParams, ParamsArray, IOscParser, IOscHandler, OscFallbackHandlerType, IFunctionIdentifier, IParserStackState, ParserStackType } from 'common/parser/Types';
import { EscapeSequenceParser, TransitionTable, VT500_TRANSITION_TABLE } from 'common/parser/EscapeSequenceParser';
import { assert } from 'chai';
import { StringToUtf32, stringFromCodePoint, utf32ToString } from 'common/input/TextDecoder';
@@ -145,6 +145,15 @@ const testTerminal: any = {
},
actionDCSUnhook(success: boolean): void {
this.calls.push(['dcs unhook', success]);
},
actionAPCStart(): void {
this.calls.push(['apc start']);
},
actionAPCPut(s: string): void {
this.calls.push(['apc put', s]);
},
actionAPCEnd(success: boolean): void {
this.calls.push(['apc end', success]);
}
};
@@ -200,6 +209,18 @@ testParser.setDcsHandlerFallback((collectAndFlag, action, payload) => {
testTerminal.actionDCSUnhook(payload);
}
});
testParser.setApcHandlerFallback((collectAndFlag, action, payload) => {
switch (action) {
case 'START':
testTerminal.actionAPCStart(payload);
break;
case 'PUT':
testTerminal.actionAPCPut(payload);
break;
case 'END':
testTerminal.actionAPCEnd(payload);
}
});
// translate string based parse calls into typed array based
@@ -280,7 +301,7 @@ describe('EscapeSequenceParser', () => {
const exceptions: { [key: number]: { [key: string]: any[] } } = {
8: { '\x18': [], '\x1a': [] }, // abort OSC_STRING
13: { '\x18': [['dcs unhook', false]], '\x1a': [['dcs unhook', false]] }, // abort DCS_PASSTHROUGH
16: { '\x18': [], '\x1a': [] } // abort APC_PASSTHROUGH
16: { '\x18': [['apc end', false]], '\x1a': [['apc end', false]] } // abort APC_PASSTHROUGH
};
parser.reset();
testTerminal.clear();
@@ -1023,6 +1044,134 @@ describe('EscapeSequenceParser', () => {
parser.reset();
testTerminal.clear();
});
it('state APC_ENTRY', () => {
parser.reset();
// C0
parse(parser, '\x1b_');
assert.equal(parser.currentState, ParserState.APC_ENTRY);
parser.reset();
// C1
for (state in states) {
parser.currentState = state;
parse(parser, '\x9f');
assert.equal(parser.currentState, ParserState.APC_ENTRY);
parser.reset();
}
});
it('state APC_ENTRY ignore rules', () => {
parser.reset();
const ignored = [
'\x00', '\x01', '\x02', '\x03', '\x04', '\x05', '\x06', '\x07', '\x08',
'\x09', '\x0a', '\x0b', '\x0c', '\x0d', '\x0e', '\x0f', '\x10', '\x11',
'\x12', '\x13', '\x14', '\x15', '\x16', '\x17', '\x19', '\x1c', '\x1d', '\x1e', '\x1f', '\x7f'];
for (let i = 0; i < ignored.length; ++i) {
parser.currentState = ParserState.APC_ENTRY;
parse(parser, ignored[i]);
assert.equal(parser.currentState, ParserState.APC_ENTRY);
parser.reset();
}
});
it('trans APC_ENTRY --> APC_INTERMEDIATE with collect action', () => {
parser.reset();
const collect = r(0x20, 0x30);
for (let i = 0; i < collect.length; ++i) {
parser.currentState = ParserState.APC_ENTRY;
parse(parser, collect[i]);
assert.equal(parser.currentState, ParserState.APC_INTERMEDIATE);
assert.equal(parser.collect, collect[i]);
parser.reset();
}
});
it('trans APC_ENTRY --> APC_PASSTHROUGH with start', () => {
parser.reset();
testTerminal.clear();
const collect = r(0x30, 0x7f);
for (let i = 0; i < collect.length; ++i) {
parser.currentState = ParserState.APC_ENTRY;
parse(parser, collect[i]);
assert.equal(parser.currentState, ParserState.APC_PASSTHROUGH);
testTerminal.compare([['apc start']]);
parser.reset();
testTerminal.clear();
}
});
it('trans APC_INTERMEDIATE --> APC_PASSTHROUGH with start', () => {
parser.reset();
testTerminal.clear();
const collect = r(0x30, 0x7f);
for (let i = 0; i < collect.length; ++i) {
parser.currentState = ParserState.APC_INTERMEDIATE;
parse(parser, collect[i]);
assert.equal(parser.currentState, ParserState.APC_PASSTHROUGH);
testTerminal.compare([['apc start']]);
parser.reset();
testTerminal.clear();
}
});
it('state APC_INTERMEDIATE ignore rules', () => {
parser.reset();
const ignored = [
'\x00', '\x01', '\x02', '\x03', '\x04', '\x05', '\x06', '\x07', '\x08',
'\x09', '\x0a', '\x0b', '\x0c', '\x0d', '\x0e', '\x0f', '\x10', '\x11',
'\x12', '\x13', '\x14', '\x15', '\x16', '\x17', '\x19', '\x1c', '\x1d', '\x1e', '\x1f', '\x7f'];
for (let i = 0; i < ignored.length; ++i) {
parser.currentState = ParserState.APC_INTERMEDIATE;
parse(parser, ignored[i]);
assert.equal(parser.currentState, ParserState.APC_INTERMEDIATE);
parser.reset();
}
});
it('state APC_PASSTHROUGH put action', () => {
parser.reset();
testTerminal.clear();
let puts = r(0x08, 0x0e);
puts = puts.concat(r(0x20, 0x7f));
for (let i = 0; i < puts.length; ++i) {
parser.currentState = ParserState.APC_PASSTHROUGH;
parse(parser, puts[i]);
assert.equal(parser.currentState, ParserState.APC_PASSTHROUGH);
testTerminal.compare([['apc put', puts[i]]]);
parser.reset();
testTerminal.clear();
}
});
it('state APC_PASSTHROUGH ignore rules', () => {
parser.reset();
testTerminal.clear();
let puts = r(0x00, 0x08);
puts = puts.concat(r(0x0e, 0x18));
puts.push('\x19');
puts = puts.concat(r(0x1c, 0x20));
puts.push('\x7f');
for (let i = 0; i < puts.length; ++i) {
parser.currentState = ParserState.APC_PASSTHROUGH;
parse(parser, puts[i]);
assert.equal(parser.currentState, ParserState.APC_PASSTHROUGH);
testTerminal.compare([]);
parser.reset();
testTerminal.clear();
}
});
it('trans APC_PASSTHROUGH --> GROUND|ESCAPE with end action', () => {
parser.reset();
testTerminal.clear();
const collect = ['\x9c', '\x18', '\x1a']; // ST - true, CAN & SUB - false
for (let i = 0; i < collect.length; ++i) {
parser.currentState = ParserState.APC_PASSTHROUGH;
parse(parser, collect[i]);
assert.equal(parser.currentState, ParserState.GROUND);
testTerminal.compare([['apc end', collect[i] === '\x9c']]);
parser.reset();
testTerminal.clear();
}
// ESC end
parser.currentState = ParserState.APC_PASSTHROUGH;
parse(parser, '\x1b');
assert.equal(parser.currentState, ParserState.ESCAPE);
testTerminal.compare([['apc end', true]]);
parser.reset();
testTerminal.clear();
});
});
function test(s: string, value: any, noReset: any): void {
@@ -1088,6 +1237,72 @@ describe('EscapeSequenceParser', () => {
['print', 'defg']
], null);
});
it('single APC', () => {
test('\x1b_X3+$aäbc;däe\x9c', [
['apc start'],
//['apc put', '3+$aäbc;däe'],
['apc put', '3'],
['apc put', '+'],
['apc put', '$'],
['apc put', 'a'],
['apc put', 'ä'],
['apc put', 'b'],
['apc put', 'c'],
['apc put', ';'],
['apc put', 'd'],
['apc put', 'ä'],
['apc put', 'e'],
['apc end', true]
], null);
});
it('multi APC', () => {
test('\x1b_Xabc;de', [
['apc start'],
//['dcs put', 'abc;de']
['apc put', 'a'],
['apc put', 'b'],
['apc put', 'c'],
['apc put', ';'],
['apc put', 'd'],
['apc put', 'e'],
], null);
testTerminal.clear();
test('abc\x9c', [
//['apc put', 'abc'],
['apc put', 'a'],
['apc put', 'b'],
['apc put', 'c'],
['apc end', true]
], true);
});
it('print + DCS(C1) + print', () => {
test('abc\x9fAbc;de\x9cxyz', [
['print', 'abc'],
['apc start'],
//['apc put', 'bc;de'],
['apc put', 'b'],
['apc put', 'c'],
['apc put', ';'],
['apc put', 'd'],
['apc put', 'e'],
['apc end', true],
['print', 'xyz']
], null);
});
it('print + DCS(C0) + print', () => {
test('abc\x1b_Abc;de\x1b\\xyz', [
['print', 'abc'],
['apc start'],
//['apc put', 'bc;de'],
['apc put', 'b'],
['apc put', 'c'],
['apc put', ';'],
['apc put', 'd'],
['apc put', 'e'],
['apc end', true],
['print', 'xyz']
], null);
});
it('error recovery', () => {
test('\x1b[1€abcdefg\x9b<;c', [
['print', 'abcdefg'],
@@ -1134,6 +1349,32 @@ describe('EscapeSequenceParser', () => {
['dcs unhook', false] // false for abort
], null);
});
it('CAN should abort APC', () => {
test('abc\x9fXbc;de\x18', [
['print', 'abc'],
['apc start'],
//['apc put', 'bc;de'],
['apc put', 'b'],
['apc put', 'c'],
['apc put', ';'],
['apc put', 'd'],
['apc put', 'e'],
['apc end', false] // false for abort
], null);
});
it('SUB should abort APC', () => {
test('abc\x9fXbc;de\x1a', [
['print', 'abc'],
['apc start'],
//['apc put', 'bc;de'],
['apc put', 'b'],
['apc put', 'c'],
['apc put', ';'],
['apc put', 'd'],
['apc put', 'e'],
['apc end', false] // false for abort
], null);
});
it('CAN should abort OSC', () => {
test('\x1b]0;abc123€öäü\x18', [
['osc', '0;abc123€öäü, success: false']
@@ -179,22 +179,18 @@ export const VT500_TRANSITION_TABLE = (function (): TransitionTable {
table.add(0x50, ParserState.ESCAPE, ParserAction.CLEAR, ParserState.DCS_ENTRY);
table.addMany(EXECUTABLES, ParserState.DCS_ENTRY, ParserAction.IGNORE, ParserState.DCS_ENTRY);
table.add(0x7f, ParserState.DCS_ENTRY, ParserAction.IGNORE, ParserState.DCS_ENTRY);
table.addMany(r(0x1c, 0x20), ParserState.DCS_ENTRY, ParserAction.IGNORE, ParserState.DCS_ENTRY);
table.addMany(r(0x20, 0x30), ParserState.DCS_ENTRY, ParserAction.COLLECT, ParserState.DCS_INTERMEDIATE);
table.addMany(r(0x30, 0x3c), ParserState.DCS_ENTRY, ParserAction.PARAM, ParserState.DCS_PARAM);
table.addMany([0x3c, 0x3d, 0x3e, 0x3f], ParserState.DCS_ENTRY, ParserAction.COLLECT, ParserState.DCS_PARAM);
table.addMany(EXECUTABLES, ParserState.DCS_IGNORE, ParserAction.IGNORE, ParserState.DCS_IGNORE);
table.addMany(r(0x20, 0x80), ParserState.DCS_IGNORE, ParserAction.IGNORE, ParserState.DCS_IGNORE);
table.addMany(r(0x1c, 0x20), ParserState.DCS_IGNORE, ParserAction.IGNORE, ParserState.DCS_IGNORE);
table.addMany(EXECUTABLES, ParserState.DCS_PARAM, ParserAction.IGNORE, ParserState.DCS_PARAM);
table.add(0x7f, ParserState.DCS_PARAM, ParserAction.IGNORE, ParserState.DCS_PARAM);
table.addMany(r(0x1c, 0x20), ParserState.DCS_PARAM, ParserAction.IGNORE, ParserState.DCS_PARAM);
table.addMany(r(0x30, 0x3c), ParserState.DCS_PARAM, ParserAction.PARAM, ParserState.DCS_PARAM);
table.addMany([0x3c, 0x3d, 0x3e, 0x3f], ParserState.DCS_PARAM, ParserAction.IGNORE, ParserState.DCS_IGNORE);
table.addMany(r(0x20, 0x30), ParserState.DCS_PARAM, ParserAction.COLLECT, ParserState.DCS_INTERMEDIATE);
table.addMany(EXECUTABLES, ParserState.DCS_INTERMEDIATE, ParserAction.IGNORE, ParserState.DCS_INTERMEDIATE);
table.add(0x7f, ParserState.DCS_INTERMEDIATE, ParserAction.IGNORE, ParserState.DCS_INTERMEDIATE);
table.addMany(r(0x1c, 0x20), ParserState.DCS_INTERMEDIATE, ParserAction.IGNORE, ParserState.DCS_INTERMEDIATE);
table.addMany(r(0x20, 0x30), ParserState.DCS_INTERMEDIATE, ParserAction.COLLECT, ParserState.DCS_INTERMEDIATE);
table.addMany(r(0x30, 0x40), ParserState.DCS_INTERMEDIATE, ParserAction.IGNORE, ParserState.DCS_IGNORE);
table.addMany(r(0x40, 0x7f), ParserState.DCS_INTERMEDIATE, ParserAction.DCS_HOOK, ParserState.DCS_PASSTHROUGH);