diff --git a/src/Terminal2.test.ts b/src/Terminal2.test.ts index 11728cd95..178a669d0 100644 --- a/src/Terminal2.test.ts +++ b/src/Terminal2.test.ts @@ -1,45 +1,126 @@ /** - * Copyright (c) 2016 The xterm.js authors. All rights reserved. + * Copyright (c) 2019 The xterm.js authors. All rights reserved. * @license MIT - * - * This file contains integration tests for xterm.js. */ import * as glob from 'glob'; -import * as fs from 'fs'; -import * as os from 'os'; import * as path from 'path'; +import * as os from 'os'; +import * as fs from 'fs'; import * as pty from 'node-pty'; import { Terminal } from './Terminal'; -import { IViewport } from './Types'; -import { CellData } from 'common/buffer/CellData'; -import { WHITESPACE_CELL_CHAR } from 'common/buffer/Constants'; -class TestTerminal extends Terminal { - innerWrite(): void { this._innerWrite(); } +// all test files expect terminal in 80x25 +const COLS = 80; +const ROWS = 25; + +const TESTFILES = glob.sync('**/escape_sequence_files/*.in', { cwd: path.join(__dirname, '..')}); +const SKIP_FILES = [ + // 't0008-BS.in', + // 't0014-CAN.in', + // 't0015-SUB.in', + // 't0017-SD.in', + // 't0035-HVP.in', + // 't0050-ICH.in', + // 't0051-IL.in', + // 't0052-DL.in', + // 't0055-EL.in', + // 't0056-ED.in', + // 't0060-DECSC.in', + // 't0061-CSI_s.in', + 't0070-DECSTBM_LF.in', // lineFeed not working correctly + 't0071-DECSTBM_IND.in', + 't0072-DECSTBM_NEL.in', + // 't0074-DECSTBM_SU_SD.in', + 't0075-DECSTBM_CUU_CUD.in', + 't0076-DECSTBM_IL_DL.in', // not working due to lineFeed + 't0077-DECSTBM_quirks.in', + // 't0080-HT.in', + // 't0082-HTS.in', + // 't0083-CHT.in', + 't0084-CBT.in', + // 't0090-alt_screen.in', + // 't0091-alt_screen_ED3.in', + // 't0092-alt_screen_DECSC.in', + // 't0100-IRM.in', + 't0101-NLM.in', + 't0103-reverse_wrap.in', + 't0504-vim.in' + + // vttest related files + // 't0300-vttest1.in' +]; +if (os.platform() === 'darwin') { + // These are failing on macOS only (termios related?) + SKIP_FILES.push( + 't0003-line_wrap.in', + 't0005-CR.in', + 't0009-NEL.in', + 't0503-zsh_ls_color.in' + ); } +// filter skipFilenames +const FILES = TESTFILES.filter(value => SKIP_FILES.indexOf(value.split('/').slice(-1)[0]) === -1); -let primitivePty: any; -// fake sychronous pty write - read -// we just pipe the data from slave to master as a child program would do -// pty.js opens pipe fds with O_NONBLOCK -// just wait 10ms instead of setting fds to blocking mode -function ptyWriteRead(data: string, cb: (result: string) => void): void { - fs.writeSync(primitivePty.slave, data); - setTimeout(() => { - const b = new Buffer(64000); - const bytes = fs.readSync(primitivePty.master, b, 0, 64000, null); - cb(b.toString('utf8', 0, bytes)); +describe('Escape Sequence Files', function(): void { + this.timeout(20000); + + let ptyTerm: any = null; + let slaveEnd: any = null; + let term: Terminal; + let customHandler: any = null; + + before(() => { + ptyTerm = (pty as any).open({cols: COLS, rows: ROWS}); + slaveEnd = ptyTerm._slave; + term = new Terminal({cols: COLS, rows: ROWS}); + ptyTerm._master.on('data', (data: string) => term.write(data)); }); -} -// make sure raw pty is at x=0 and has no pending data -function ptyReset(cb: (result: string) => void): void { - ptyWriteRead('\r\n', cb); -} + after(() => { + ptyTerm.end(); + }); + + FILES.forEach(filename => { + it(filename.split('/').slice(-1)[0], async () => { + // reset terminal and handler + if (customHandler) { + customHandler.dispose(); + } + slaveEnd.write('\r\n'); + term.reset(); + slaveEnd.write('\x1bc\x1b[H'); + + // register handler to trigger viewport scraping, wait for it to finish + let content = ''; + await new Promise(resolve => { + customHandler = term.addOscHandler(12345, () => { + // grab terminal viewport content + content = terminalToString(term); + resolve(); + return true; + }); + // write file to slave + slaveEnd.write(fs.readFileSync(filename, 'utf8')); + // trigger custom sequence + slaveEnd.write('\x1b]12345;\x07'); + }); + + // compare with expected output (right trimmed) + const expected = fs.readFileSync(filename.split('.')[0] + '.text', 'utf8'); + const expectedRightTrimmed = expected.split('\n').map(l => l.replace(/\s+$/, '')).join('\n'); + if (content !== expectedRightTrimmed) { + throw new Error(formatError(fs.readFileSync(filename, 'utf8'), content, expected)); + } + }); + }); +}); + +/** + * Helpers + */ -/* debug helpers */ // generate colorful noisy output to compare xterm and emulator cell states function formatError(input: string, output: string, expected: string): string { function addLineNumber(start: number, color: string): (s: string) => string { @@ -51,10 +132,10 @@ function formatError(input: string, output: string, expected: string): string { } const line80 = '12345678901234567890123456789012345678901234567890123456789012345678901234567890'; let s = ''; - s += '\n\x1b[34m' + JSON.stringify(input); - s += '\n\x1b[33m ' + line80 + '\n'; + s += `\n\x1b[34m${JSON.stringify(input)}`; + s += `\n\x1b[33m ${line80}\n`; s += output.split('\n').map(addLineNumber(0, '\x1b[31m')).join('\n'); - s += '\n\x1b[33m ' + line80 + '\n'; + s += `\n\x1b[33m ${line80}\n`; s += expected.split('\n').map(addLineNumber(0, '\x1b[32m')).join('\n'); return s; } @@ -64,10 +145,7 @@ function terminalToString(term: Terminal): string { let result = ''; let lineText = ''; for (let line = term.buffer.ybase; line < term.buffer.ybase + term.rows; line++) { - lineText = ''; - for (let cell = 0; cell < term.cols; ++cell) { - lineText += term.buffer.lines.get(line).loadCell(cell, new CellData()).getChars() || WHITESPACE_CELL_CHAR; - } + lineText = term.buffer.lines.get(line).translateToString(true); // rtrim empty cells as xterm does lineText = lineText.replace(/\s+$/, ''); result += lineText; @@ -75,114 +153,3 @@ function terminalToString(term: Terminal): string { } return result; } - -// Skip tests on Windows since pty.open isn't supported -if (os.platform() !== 'win32') { - const consoleLog = console.log; - - // expect files need terminal at 80x25! - const cols = 80; - const rows = 25; - - /** some helpers for pty interaction */ - // we need a pty in between to get the termios decorations - // for the basic test cases a raw pty device is enough - primitivePty = (pty).native.open(cols, rows); - - /** tests */ - describe('xterm output comparison', function(): void { - this.timeout(10000); - let xterm: TestTerminal; - - beforeEach(() => { - xterm = new TestTerminal({ cols: cols, rows: rows }); - xterm.refresh = () => {}; - xterm.viewport = { - syncScrollArea: () => {} - }; - }); - - // omit stack trace for escape sequence files - Error.stackTraceLimit = 0; - const files = glob.sync('**/escape_sequence_files/*.in', { cwd: path.join(__dirname, '..')}); - // only successful tests for now - const skipFilename = [ - // 't0008-BS.in', - // 't0014-CAN.in', - // 't0015-SUB.in', - // 't0017-SD.in', - // 't0035-HVP.in', - // 't0050-ICH.in', - // 't0051-IL.in', - // 't0052-DL.in', - // 't0055-EL.in', - // 't0056-ED.in', - // 't0060-DECSC.in', - // 't0061-CSI_s.in', - 't0070-DECSTBM_LF.in', // lineFeed not working correctly - 't0071-DECSTBM_IND.in', - 't0072-DECSTBM_NEL.in', - // 't0074-DECSTBM_SU_SD.in', - 't0075-DECSTBM_CUU_CUD.in', - 't0076-DECSTBM_IL_DL.in', // not working due to lineFeed - 't0077-DECSTBM_quirks.in', - // 't0080-HT.in', - // 't0082-HTS.in', - // 't0083-CHT.in', - 't0084-CBT.in', - // 't0090-alt_screen.in', - // 't0091-alt_screen_ED3.in', - // 't0092-alt_screen_DECSC.in', - // 't0100-IRM.in', - 't0101-NLM.in', - 't0103-reverse_wrap.in', - 't0504-vim.in' - - // vttest related files - // 't0300-vttest1.in' - ]; - if (os.platform() === 'darwin') { - // These are failing on macOS only - skipFilename.push( - 't0003-line_wrap.in', - 't0005-CR.in', - 't0009-NEL.in', - 't0503-zsh_ls_color.in' - ); - } - for (let i = 0; i < files.length; i++) { - if (skipFilename.indexOf(files[i].split('/').slice(-1)[0]) >= 0) { - continue; - } - ((filename: string) => { - const inFile = fs.readFileSync(filename, 'utf8'); - it(filename.split('/').slice(-1)[0], done => { - ptyReset(() => { - ptyWriteRead(inFile, fromPty => { - // uncomment this to get log from terminal - // console.log = function(){}; - - // Perform a synchronous .write(data) - xterm.writeBuffer.push(fromPty); - xterm.innerWrite(); - - const fromEmulator = terminalToString(xterm); - console.log = consoleLog; - const expected = fs.readFileSync(filename.split('.')[0] + '.text', 'utf8'); - - // Some of the tests have whitespace on the right of lines, we trim all the linex - // from xterm.js so ignore this for now at least. - const expectedRightTrimmed = expected.split('\n').map(l => l.replace(/\s+$/, '')).join('\n'); - if (fromEmulator !== expectedRightTrimmed) { - // uncomment to get noisy output - throw new Error(formatError(inFile, fromEmulator, expected)); - // throw new Error('mismatch'); - } - done(); - }); - }); - }); - })(files[i]); - } - }); -}