1'use strict'; 2 3// Flags: --expose-internals 4 5const common = require('../common'); 6const stream = require('stream'); 7const REPL = require('internal/repl'); 8const assert = require('assert'); 9const fs = require('fs'); 10const path = require('path'); 11const { inspect } = require('util'); 12 13common.skipIfDumbTerminal(); 14 15const tmpdir = require('../common/tmpdir'); 16tmpdir.refresh(); 17 18process.throwDeprecation = true; 19process.on('warning', common.mustNotCall()); 20 21const defaultHistoryPath = path.join(tmpdir.path, '.node_repl_history'); 22 23// Create an input stream specialized for testing an array of actions 24class ActionStream extends stream.Stream { 25 run(data) { 26 const _iter = data[Symbol.iterator](); 27 const doAction = () => { 28 const next = _iter.next(); 29 if (next.done) { 30 // Close the repl. Note that it must have a clean prompt to do so. 31 this.emit('keypress', '', { ctrl: true, name: 'd' }); 32 return; 33 } 34 const action = next.value; 35 36 if (typeof action === 'object') { 37 this.emit('keypress', '', action); 38 } else { 39 this.emit('data', `${action}`); 40 } 41 setImmediate(doAction); 42 }; 43 doAction(); 44 } 45 resume() {} 46 pause() {} 47} 48ActionStream.prototype.readable = true; 49 50// Mock keys 51const ENTER = { name: 'enter' }; 52const UP = { name: 'up' }; 53const DOWN = { name: 'down' }; 54const LEFT = { name: 'left' }; 55const RIGHT = { name: 'right' }; 56const DELETE = { name: 'delete' }; 57const BACKSPACE = { name: 'backspace' }; 58const WORD_LEFT = { name: 'left', ctrl: true }; 59const WORD_RIGHT = { name: 'right', ctrl: true }; 60const GO_TO_END = { name: 'end' }; 61const DELETE_WORD_LEFT = { name: 'backspace', ctrl: true }; 62const SIGINT = { name: 'c', ctrl: true }; 63const ESCAPE = { name: 'escape', meta: true }; 64 65const prompt = '> '; 66const WAIT = '€'; 67 68const prev = process.features.inspector; 69 70let completions = 0; 71 72const tests = [ 73 { // Creates few history to navigate for 74 env: { NODE_REPL_HISTORY: defaultHistoryPath }, 75 test: [ 'let ab = 45', ENTER, 76 '555 + 909', ENTER, 77 'let autocompleteMe = 123', ENTER, 78 '{key : {key2 :[] }}', ENTER, 79 'Array(100).fill(1).map((e, i) => i ** i)', LEFT, LEFT, DELETE, 80 '2', ENTER], 81 expected: [], 82 clean: false 83 }, 84 { 85 env: { NODE_REPL_HISTORY: defaultHistoryPath }, 86 test: [UP, UP, UP, UP, UP, UP, DOWN, DOWN, DOWN, DOWN, DOWN, DOWN], 87 expected: [prompt, 88 `${prompt}Array(100).fill(1).map((e, i) => i ** 2)`, 89 prev && '\n// [ 0, 1, 4, 9, 16, 25, 36, 49, 64, 81, 100, 121, ' + 90 '144, 169, 196, 225, 256, 289, 324, 361, 400, 441, 484, 529,' + 91 ' 576, 625, 676, 729, 784, 841, 900, 961, 1024, 1089, 1156, ' + 92 '1225, 1296, 1369, 1444, 1521, 1600, 1681, 1764, 1849, 1936,' + 93 ' 2025, 2116, 2209,...', 94 `${prompt}{key : {key2 :[] }}`, 95 prev && '\n// { key: { key2: [] } }', 96 `${prompt}let autocompleteMe = 123`, 97 `${prompt}555 + 909`, 98 prev && '\n// 1464', 99 `${prompt}let ab = 45`, 100 prompt, 101 `${prompt}let ab = 45`, 102 `${prompt}555 + 909`, 103 prev && '\n// 1464', 104 `${prompt}let autocompleteMe = 123`, 105 `${prompt}{key : {key2 :[] }}`, 106 prev && '\n// { key: { key2: [] } }', 107 `${prompt}Array(100).fill(1).map((e, i) => i ** 2)`, 108 prev && '\n// [ 0, 1, 4, 9, 16, 25, 36, 49, 64, 81, 100, 121, ' + 109 '144, 169, 196, 225, 256, 289, 324, 361, 400, 441, 484, 529,' + 110 ' 576, 625, 676, 729, 784, 841, 900, 961, 1024, 1089, 1156, ' + 111 '1225, 1296, 1369, 1444, 1521, 1600, 1681, 1764, 1849, 1936,' + 112 ' 2025, 2116, 2209,...', 113 prompt].filter((e) => typeof e === 'string'), 114 clean: false 115 }, 116 { // Creates more history entries to navigate through. 117 env: { NODE_REPL_HISTORY: defaultHistoryPath }, 118 test: [ 119 '555 + 909', ENTER, // Add a duplicate to the history set. 120 'const foo = true', ENTER, 121 '555n + 111n', ENTER, 122 '5 + 5', ENTER, 123 '55 - 13 === 42', ENTER, 124 ], 125 expected: [], 126 clean: false 127 }, 128 { 129 env: { NODE_REPL_HISTORY: defaultHistoryPath }, 130 checkTotal: true, 131 preview: false, 132 showEscapeCodes: true, 133 test: [ 134 '55', UP, UP, UP, UP, UP, UP, UP, ENTER, 135 ], 136 expected: [ 137 '\x1B[1G', '\x1B[0J', prompt, '\x1B[3G', 138 // '55' 139 '5', '5', 140 // UP 141 '\x1B[1G', '\x1B[0J', 142 '> 55 - 13 === 42', '\x1B[17G', 143 // UP - skipping 5 + 5 144 '\x1B[1G', '\x1B[0J', 145 '> 555n + 111n', '\x1B[14G', 146 // UP - skipping const foo = true 147 '\x1B[1G', '\x1B[0J', 148 '> 555 + 909', '\x1B[12G', 149 // UP, UP 150 // UPs at the end of the history reset the line to the original input. 151 '\x1B[1G', '\x1B[0J', 152 '> 55', '\x1B[5G', 153 // ENTER 154 '\r\n', '55\n', 155 '\x1B[1G', '\x1B[0J', 156 '> ', '\x1B[3G', 157 '\r\n', 158 ], 159 clean: true 160 }, 161 { 162 env: { NODE_REPL_HISTORY: defaultHistoryPath }, 163 skip: !process.features.inspector, 164 test: [ 165 // あ is a full width character with a length of one. 166 // is a full width character with a length of two. 167 // is a half width character with the length of two. 168 // '\u0301', '0x200D', '\u200E' are zero width characters. 169 `const x1 = '${'あ'.repeat(124)}'`, ENTER, // Fully visible 170 ENTER, 171 `const y1 = '${'あ'.repeat(125)}'`, ENTER, // Cut off 172 ENTER, 173 `const x2 = '${''.repeat(124)}'`, ENTER, // Fully visible 174 ENTER, 175 `const y2 = '${''.repeat(125)}'`, ENTER, // Cut off 176 ENTER, 177 `const x3 = '${''.repeat(248)}'`, ENTER, // Fully visible 178 ENTER, 179 `const y3 = '${''.repeat(249)}'`, ENTER, // Cut off 180 ENTER, 181 `const x4 = 'a${'\u0301'.repeat(1000)}'`, ENTER, // á 182 ENTER, 183 `const ${'veryLongName'.repeat(30)} = 'I should be previewed'`, 184 ENTER, 185 'const e = new RangeError("visible\\ninvisible")', 186 ENTER, 187 'e', 188 ENTER, 189 'veryLongName'.repeat(30), 190 ENTER, 191 `${'\x1B[90m \x1B[39m'.repeat(229)} aut`, 192 ESCAPE, 193 ENTER, 194 `${' '.repeat(230)} aut`, 195 ESCAPE, 196 ENTER, 197 ], 198 expected: [], 199 clean: false 200 }, 201 { 202 env: { NODE_REPL_HISTORY: defaultHistoryPath }, 203 columns: 250, 204 checkTotal: true, 205 showEscapeCodes: true, 206 skip: !process.features.inspector, 207 test: [ 208 UP, 209 UP, 210 UP, 211 WORD_LEFT, 212 UP, 213 BACKSPACE, 214 'x1', 215 BACKSPACE, 216 '2', 217 BACKSPACE, 218 '3', 219 BACKSPACE, 220 '4', 221 DELETE_WORD_LEFT, 222 'y1', 223 BACKSPACE, 224 '2', 225 BACKSPACE, 226 '3', 227 SIGINT, 228 ], 229 // A = Cursor n up 230 // B = Cursor n down 231 // C = Cursor n forward 232 // D = Cursor n back 233 // G = Cursor to column n 234 // J = Erase in screen; 0 = right; 1 = left; 2 = total 235 // K = Erase in line; 0 = right; 1 = left; 2 = total 236 expected: [ 237 // 0. Start 238 '\x1B[1G', '\x1B[0J', 239 prompt, '\x1B[3G', 240 // 1. UP 241 // This exceeds the maximum columns (250): 242 // Whitespace + prompt + ' // '.length + 'autocompleteMe'.length 243 // 230 + 2 + 4 + 14 244 '\x1B[1G', '\x1B[0J', 245 `${prompt}${' '.repeat(230)} aut`, '\x1B[237G', 246 ' // ocompleteMe', '\x1B[237G', 247 '\n// 123', '\x1B[237G', 248 '\x1B[1A', '\x1B[1B', '\x1B[2K', '\x1B[1A', 249 '\x1B[0K', 250 // 2. UP 251 '\x1B[1G', '\x1B[0J', 252 `${prompt}${' '.repeat(229)} aut`, '\x1B[236G', 253 ' // ocompleteMe', '\x1B[236G', 254 '\n// 123', '\x1B[236G', 255 '\x1B[1A', '\x1B[1B', '\x1B[2K', '\x1B[1A', 256 // Preview cleanup 257 '\x1B[0K', 258 // 3. UP 259 '\x1B[1G', '\x1B[0J', 260 // 'veryLongName'.repeat(30).length === 360 261 // prompt.length === 2 262 // 360 % 250 + 2 === 112 (+1) 263 `${prompt}${'veryLongName'.repeat(30)}`, '\x1B[113G', 264 // "// 'I should be previewed'".length + 86 === 112 (+1) 265 "\n// 'I should be previewed'", '\x1B[113G', '\x1B[1A', 266 // Preview cleanup 267 '\x1B[1B', '\x1B[2K', '\x1B[1A', 268 // 4. WORD LEFT 269 // Almost identical as above. Just one extra line. 270 // Math.floor(360 / 250) === 1 271 '\x1B[1A', 272 '\x1B[1G', '\x1B[0J', 273 `${prompt}${'veryLongName'.repeat(30)}`, '\x1B[3G', '\x1B[1A', 274 '\x1B[1B', "\n// 'I should be previewed'", '\x1B[3G', '\x1B[2A', 275 // Preview cleanup 276 '\x1B[2B', '\x1B[2K', '\x1B[2A', 277 // 5. UP 278 '\x1B[1G', '\x1B[0J', 279 `${prompt}e`, '\x1B[4G', 280 // '// RangeError: visible'.length - 19 === 3 (+1) 281 '\n// RangeError: visible', '\x1B[4G', '\x1B[1A', 282 // Preview cleanup 283 '\x1B[1B', '\x1B[2K', '\x1B[1A', 284 // 6. Backspace 285 '\x1B[1G', '\x1B[0J', 286 '> ', '\x1B[3G', 'x', '1', 287 `\n// '${'あ'.repeat(124)}'`, 288 '\x1B[5G', '\x1B[1A', 289 '\x1B[1B', '\x1B[2K', '\x1B[1A', 290 '\x1B[1G', '\x1B[0J', 291 '> x', '\x1B[4G', '2', 292 `\n// '${''.repeat(124)}'`, 293 '\x1B[5G', '\x1B[1A', 294 '\x1B[1B', '\x1B[2K', '\x1B[1A', 295 '\x1B[1G', '\x1B[0J', 296 '> x', '\x1B[4G', '3', 297 `\n// '${''.repeat(248)}'`, 298 '\x1B[5G', '\x1B[1A', 299 '\x1B[1B', '\x1B[2K', '\x1B[1A', 300 '\x1B[1G', '\x1B[0J', 301 '> x', '\x1B[4G', '4', 302 `\n// 'a${'\u0301'.repeat(1000)}'`, 303 '\x1B[5G', '\x1B[1A', 304 '\x1B[1B', '\x1B[2K', '\x1B[1A', 305 '\x1B[1G', '\x1B[0J', 306 '> ', '\x1B[3G', 'y', '1', 307 `\n// '${'あ'.repeat(121)}...`, 308 '\x1B[5G', '\x1B[1A', 309 '\x1B[1B', '\x1B[2K', '\x1B[1A', 310 '\x1B[1G', '\x1B[0J', 311 '> y', '\x1B[4G', '2', 312 `\n// '${''.repeat(121)}...`, 313 '\x1B[5G', '\x1B[1A', 314 '\x1B[1B', '\x1B[2K', '\x1B[1A', 315 '\x1B[1G', '\x1B[0J', 316 '> y', '\x1B[4G', '3', 317 `\n// '${''.repeat(242)}...`, 318 '\x1B[5G', '\x1B[1A', 319 '\x1B[1B', '\x1B[2K', '\x1B[1A', 320 '\r\n', 321 '\x1B[1G', '\x1B[0J', 322 '> ', '\x1B[3G', 323 '\r\n', 324 ], 325 clean: true 326 }, 327 { 328 env: { NODE_REPL_HISTORY: defaultHistoryPath }, 329 showEscapeCodes: true, 330 skip: !process.features.inspector, 331 checkTotal: true, 332 test: [ 333 'au', 334 't', 335 RIGHT, 336 BACKSPACE, 337 LEFT, 338 LEFT, 339 'A', 340 BACKSPACE, 341 GO_TO_END, 342 BACKSPACE, 343 WORD_LEFT, 344 WORD_RIGHT, 345 ESCAPE, 346 ENTER, 347 UP, 348 LEFT, 349 ENTER, 350 UP, 351 ENTER, 352 ], 353 // C = Cursor n forward 354 // D = Cursor n back 355 // G = Cursor to column n 356 // J = Erase in screen; 0 = right; 1 = left; 2 = total 357 // K = Erase in line; 0 = right; 1 = left; 2 = total 358 expected: [ 359 // 0. 360 // 'a' 361 '\x1B[1G', '\x1B[0J', prompt, '\x1B[3G', 'a', 362 // 'u' 363 'u', ' // tocompleteMe', '\x1B[5G', 364 '\n// 123', '\x1B[5G', 365 '\x1B[1A', '\x1B[1B', '\x1B[2K', '\x1B[1A', 366 // 't' - Cleanup 367 '\x1B[0K', 368 't', ' // ocompleteMe', '\x1B[6G', 369 '\n// 123', '\x1B[6G', 370 '\x1B[1A', '\x1B[1B', '\x1B[2K', '\x1B[1A', 371 // 1. Right. Cleanup 372 '\x1B[0K', 373 'ocompleteMe', 374 '\n// 123', '\x1B[17G', 375 '\x1B[1A', '\x1B[1B', '\x1B[2K', '\x1B[1A', 376 // 2. Backspace. Refresh 377 '\x1B[1G', '\x1B[0J', `${prompt}autocompleteM`, '\x1B[16G', 378 // Autocomplete and refresh? 379 ' // e', '\x1B[16G', 380 '\n// 123', '\x1B[16G', 381 '\x1B[1A', '\x1B[1B', '\x1B[2K', '\x1B[1A', 382 // 3. Left. Cleanup 383 '\x1B[0K', 384 '\x1B[1D', '\x1B[16G', ' // e', '\x1B[15G', 385 // 4. Left. Cleanup 386 '\x1B[16G', '\x1B[0K', '\x1B[15G', 387 '\x1B[1D', '\x1B[16G', ' // e', '\x1B[14G', 388 // 5. 'A' - Cleanup 389 '\x1B[16G', '\x1B[0K', '\x1B[14G', 390 // Refresh 391 '\x1B[1G', '\x1B[0J', `${prompt}autocompletAeM`, '\x1B[15G', 392 // 6. Backspace. Refresh 393 '\x1B[1G', '\x1B[0J', `${prompt}autocompleteM`, 394 '\x1B[14G', '\x1B[16G', ' // e', 395 '\x1B[14G', '\x1B[16G', ' // e', 396 '\x1B[14G', '\x1B[16G', 397 // 7. Go to end. Cleanup 398 '\x1B[0K', '\x1B[14G', '\x1B[2C', 399 'e', 400 '\n// 123', '\x1B[17G', 401 '\x1B[1A', '\x1B[1B', '\x1B[2K', '\x1B[1A', 402 // 8. Backspace. Refresh 403 '\x1B[1G', '\x1B[0J', `${prompt}autocompleteM`, '\x1B[16G', 404 // Autocomplete 405 ' // e', '\x1B[16G', 406 '\n// 123', '\x1B[16G', 407 '\x1B[1A', '\x1B[1B', '\x1B[2K', '\x1B[1A', 408 // 9. Word left. Cleanup 409 '\x1B[0K', '\x1B[13D', '\x1B[16G', ' // e', '\x1B[3G', '\x1B[16G', 410 // 10. Word right. Cleanup 411 '\x1B[0K', '\x1B[3G', '\x1B[13C', ' // e', '\x1B[16G', 412 '\n// 123', '\x1B[16G', 413 '\x1B[1A', '\x1B[1B', '\x1B[2K', '\x1B[1A', 414 // 11. ESCAPE 415 '\x1B[0K', 416 // 12. ENTER 417 '\r\n', 418 'Uncaught ReferenceError: autocompleteM is not defined\n', 419 '\x1B[1G', '\x1B[0J', 420 // 13. UP 421 prompt, '\x1B[3G', '\x1B[1G', '\x1B[0J', 422 `${prompt}autocompleteM`, '\x1B[16G', 423 ' // e', '\x1B[16G', 424 '\n// 123', '\x1B[16G', 425 '\x1B[1A', '\x1B[1B', '\x1B[2K', '\x1B[1A', 426 // 14. LEFT 427 '\x1B[0K', '\x1B[1D', '\x1B[16G', 428 ' // e', '\x1B[15G', '\x1B[16G', 429 // 15. ENTER 430 '\x1B[0K', '\x1B[15G', '\x1B[1C', 431 '\r\n', 432 'Uncaught ReferenceError: autocompleteM is not defined\n', 433 '\x1B[1G', '\x1B[0J', 434 prompt, '\x1B[3G', 435 // 16. UP 436 '\x1B[1G', '\x1B[0J', 437 `${prompt}autocompleteM`, '\x1B[16G', 438 ' // e', '\x1B[16G', 439 '\n// 123', '\x1B[16G', 440 '\x1B[1A', '\x1B[1B', '\x1B[2K', '\x1B[1A', 441 '\x1B[0K', 442 // 17. ENTER 443 'e', '\r\n', 444 '123\n', 445 '\x1B[1G', '\x1B[0J', 446 prompt, '\x1B[3G', 447 '\r\n', 448 ], 449 clean: true 450 }, 451 { 452 // Check changed inspection defaults. 453 env: { NODE_REPL_HISTORY: defaultHistoryPath }, 454 skip: !process.features.inspector, 455 test: [ 456 'util.inspect.replDefaults.showHidden', 457 ENTER, 458 ], 459 expected: [], 460 clean: false 461 }, 462 { 463 env: { NODE_REPL_HISTORY: defaultHistoryPath }, 464 skip: !process.features.inspector, 465 checkTotal: true, 466 test: [ 467 '[ ]', 468 WORD_LEFT, 469 WORD_LEFT, 470 UP, 471 ' = true', 472 ENTER, 473 '[ ]', 474 ENTER, 475 ], 476 expected: [ 477 prompt, 478 '[', ' ', ']', 479 '\n// []', '\n// []', '\n// []', 480 '> util.inspect.replDefaults.showHidden', 481 '\n// false', 482 ' ', '=', ' ', 't', 'r', 'u', 'e', 483 'true\n', 484 '> ', '[', ' ', ']', 485 '\n// [ [length]: 0 ]', 486 '[ [length]: 0 ]\n', 487 '> ', 488 ], 489 clean: true 490 }, 491 { 492 // Check that the completer ignores completions that are outdated. 493 env: { NODE_REPL_HISTORY: defaultHistoryPath }, 494 completer(line, callback) { 495 if (line.endsWith(WAIT)) { 496 if (completions++ === 0) { 497 callback(null, [[`${WAIT}WOW`], line]); 498 } else { 499 setTimeout(callback, 1000, null, [[`${WAIT}WOW`], line]).unref(); 500 } 501 } else { 502 callback(null, [[' Always visible'], line]); 503 } 504 }, 505 skip: !process.features.inspector, 506 test: [ 507 WAIT, // The first call is awaited before new input is triggered! 508 BACKSPACE, 509 's', 510 BACKSPACE, 511 WAIT, // The second call is not awaited. It won't trigger the preview. 512 BACKSPACE, 513 's', 514 BACKSPACE, 515 ], 516 expected: [ 517 prompt, 518 WAIT, 519 ' // WOW', 520 prompt, 521 's', 522 ' // Always visible', 523 prompt, 524 WAIT, 525 prompt, 526 's', 527 ' // Always visible', 528 prompt, 529 ], 530 clean: true 531 }, 532 { 533 env: { NODE_REPL_HISTORY: defaultHistoryPath }, 534 test: (function*() { 535 // Deleting Array iterator should not break history feature. 536 // 537 // Using a generator function instead of an object to allow the test to 538 // keep iterating even when Array.prototype[Symbol.iterator] has been 539 // deleted. 540 yield 'const ArrayIteratorPrototype ='; 541 yield ' Object.getPrototypeOf(Array.prototype[Symbol.iterator]());'; 542 yield ENTER; 543 yield 'const {next} = ArrayIteratorPrototype;'; 544 yield ENTER; 545 yield 'const realArrayIterator = Array.prototype[Symbol.iterator];'; 546 yield ENTER; 547 yield 'delete Array.prototype[Symbol.iterator];'; 548 yield ENTER; 549 yield 'delete ArrayIteratorPrototype.next;'; 550 yield ENTER; 551 yield UP; 552 yield UP; 553 yield DOWN; 554 yield DOWN; 555 yield 'fu'; 556 yield 'n'; 557 yield RIGHT; 558 yield BACKSPACE; 559 yield LEFT; 560 yield LEFT; 561 yield 'A'; 562 yield BACKSPACE; 563 yield GO_TO_END; 564 yield BACKSPACE; 565 yield WORD_LEFT; 566 yield WORD_RIGHT; 567 yield ESCAPE; 568 yield ENTER; 569 yield 'Array.proto'; 570 yield RIGHT; 571 yield '.pu'; 572 yield ENTER; 573 yield 'ArrayIteratorPrototype.next = next;'; 574 yield ENTER; 575 yield 'Array.prototype[Symbol.iterator] = realArrayIterator;'; 576 yield ENTER; 577 })(), 578 expected: [], 579 clean: false 580 }, 581 { 582 env: { NODE_REPL_HISTORY: defaultHistoryPath }, 583 test: ['const util = {}', ENTER, 584 'ut', RIGHT, ENTER], 585 expected: [ 586 prompt, ...'const util = {}', 587 'undefined\n', 588 prompt, ...'ut', ...(prev ? [' // il', '\n// {}', 589 'il', '\n// {}'] : [' // il', 'il']), 590 '{}\n', 591 prompt, 592 ], 593 clean: false 594 }, 595 { 596 env: { NODE_REPL_HISTORY: defaultHistoryPath }, 597 test: [ 598 'const utilDesc = Reflect.getOwnPropertyDescriptor(globalThis, "util")', 599 ENTER, 600 'globalThis.util = {}', ENTER, 601 'ut', RIGHT, ENTER, 602 'Reflect.defineProperty(globalThis, "util", utilDesc)', ENTER], 603 expected: [ 604 prompt, ...'const utilDesc = ' + 605 'Reflect.getOwnPropertyDescriptor(globalThis, "util")', 606 'undefined\n', 607 prompt, ...'globalThis.util = {}', 608 '{}\n', 609 prompt, ...'ut', ' // il', 'il', 610 '{}\n', 611 prompt, ...'Reflect.defineProperty(globalThis, "util", utilDesc)', 612 'true\n', 613 prompt, 614 ], 615 clean: false 616 }, 617 { 618 // Test that preview should not be removed when pressing ESCAPE key 619 env: { NODE_REPL_HISTORY: defaultHistoryPath }, 620 skip: !process.features.inspector, 621 test: [ 622 '1+1', 623 ESCAPE, 624 ENTER, 625 ], 626 expected: [ 627 prompt, ...'1+1', 628 '\n// 2', 629 '\n// 2', 630 '2\n', 631 prompt, 632 ], 633 clean: false 634 }, 635]; 636const numtests = tests.length; 637 638const runTestWrap = common.mustCall(runTest, numtests); 639 640function cleanupTmpFile() { 641 try { 642 // Write over the file, clearing any history 643 fs.writeFileSync(defaultHistoryPath, ''); 644 } catch (err) { 645 if (err.code === 'ENOENT') return true; 646 throw err; 647 } 648 return true; 649} 650 651function runTest() { 652 const opts = tests.shift(); 653 if (!opts) return; // All done 654 655 const { expected, skip } = opts; 656 657 // Test unsupported on platform. 658 if (skip) { 659 setImmediate(runTestWrap, true); 660 return; 661 } 662 const lastChunks = []; 663 let i = 0; 664 665 REPL.createInternalRepl(opts.env, { 666 input: new ActionStream(), 667 output: new stream.Writable({ 668 write(chunk, _, next) { 669 const output = chunk.toString(); 670 671 if (!opts.showEscapeCodes && 672 (output[0] === '\x1B' || /^[\r\n]+$/.test(output))) { 673 return next(); 674 } 675 676 lastChunks.push(output); 677 678 if (expected.length && !opts.checkTotal) { 679 try { 680 assert.strictEqual(output, expected[i]); 681 } catch (e) { 682 console.error(`Failed test # ${numtests - tests.length}`); 683 console.error('Last outputs: ' + inspect(lastChunks, { 684 breakLength: 5, colors: true 685 })); 686 throw e; 687 } 688 // TODO(BridgeAR): Auto close on last chunk! 689 i++; 690 } 691 692 next(); 693 } 694 }), 695 completer: opts.completer, 696 prompt, 697 useColors: false, 698 preview: opts.preview, 699 terminal: true 700 }, function(err, repl) { 701 if (err) { 702 console.error(`Failed test # ${numtests - tests.length}`); 703 throw err; 704 } 705 706 repl.once('close', () => { 707 if (opts.clean) 708 cleanupTmpFile(); 709 710 if (opts.checkTotal) { 711 assert.deepStrictEqual(lastChunks, expected); 712 } else if (expected.length !== i) { 713 console.error(tests[numtests - tests.length - 1]); 714 throw new Error(`Failed test # ${numtests - tests.length}`); 715 } 716 717 setImmediate(runTestWrap, true); 718 }); 719 720 if (opts.columns) { 721 Object.defineProperty(repl, 'columns', { 722 value: opts.columns, 723 enumerable: true 724 }); 725 } 726 repl.input.run(opts.test); 727 }); 728} 729 730// run the tests 731runTest(); 732