import { ChildProcess } from 'child_process'; import { coloredString as cs } from '@push.rocks/consolecolor'; // ============ // combines different tap test files to an overall result // ============ import * as plugins from './tstest.plugins.js'; import { TapTestResult } from './tstest.classes.tap.testresult.js'; import * as logPrefixes from './tstest.logprefixes.js'; import { TsTestLogger } from './tstest.logging.js'; export class TapParser { testStore: TapTestResult[] = []; expectedTestsRegex = /([0-9]*)\.\.([0-9]*)$/; expectedTests: number; receivedTests: number; testStatusRegex = /(ok|not\sok)\s([0-9]+)\s-\s(.*)\s#\stime=(.*)ms$/; activeTapTestResult: TapTestResult; pretaskRegex = /^::__PRETASK:(.*)$/; private logger: TsTestLogger; /** * the constructor for TapParser */ constructor(public fileName: string, logger?: TsTestLogger) { this.logger = logger; } private _getNewTapTestResult() { this.activeTapTestResult = new TapTestResult(this.testStore.length + 1); } private _processLog(logChunk: Buffer | string) { if (Buffer.isBuffer(logChunk)) { logChunk = logChunk.toString(); } const logLineArray = logChunk.split('\n'); if (logLineArray[logLineArray.length - 1] === '') { logLineArray.pop(); } // lets parse the log information for (const logLine of logLineArray) { let logLineIsTapProtocol = false; if (!this.expectedTests && this.expectedTestsRegex.test(logLine)) { logLineIsTapProtocol = true; const regexResult = this.expectedTestsRegex.exec(logLine); this.expectedTests = parseInt(regexResult[2]); if (this.logger) { this.logger.tapOutput(`Expecting ${this.expectedTests} tests!`); } // initiating first TapResult this._getNewTapTestResult(); } else if (this.pretaskRegex.test(logLine)) { logLineIsTapProtocol = true; const pretaskContentMatch = this.pretaskRegex.exec(logLine); if (pretaskContentMatch && pretaskContentMatch[1]) { if (this.logger) { this.logger.tapOutput(`Pretask -> ${pretaskContentMatch[1]}: Success.`); } } } else if (this.testStatusRegex.test(logLine)) { logLineIsTapProtocol = true; const regexResult = this.testStatusRegex.exec(logLine); const testId = parseInt(regexResult[2]); const testOk = (() => { if (regexResult[1] === 'ok') { return true; } return false; })(); const testSubject = regexResult[3]; const testDuration = parseInt(regexResult[4]); // test for protocol error if (testId !== this.activeTapTestResult.id) { if (this.logger) { this.logger.error('Something is strange! Test Ids are not equal!'); } } this.activeTapTestResult.setTestResult(testOk); if (testOk) { if (this.logger) { this.logger.testResult(testSubject, true, testDuration); } } else { if (this.logger) { this.logger.testResult(testSubject, false, testDuration); } } } if (!logLineIsTapProtocol) { if (this.activeTapTestResult) { this.activeTapTestResult.addLogLine(logLine); } if (this.logger) { this.logger.tapOutput(logLine); } } if (this.activeTapTestResult && this.activeTapTestResult.testSettled) { this.testStore.push(this.activeTapTestResult); this._getNewTapTestResult(); } } } /** * returns all tests that are not completed */ public getUncompletedTests() { // TODO: } /** * returns all tests that threw an error */ public getErrorTests() { return this.testStore.filter((tapTestArg) => { return !tapTestArg.testOk; }); } /** * returns a test overview as string */ getTestOverviewAsString() { let overviewString = ''; for (const test of this.testStore) { if (overviewString !== '') { overviewString += ' | '; } if (test.testOk) { overviewString += cs(`T${test.id} ${plugins.figures.tick}`, 'green'); } else { overviewString += cs(`T${test.id} ${plugins.figures.cross}`, 'red'); } } return overviewString; } /** * handles a tap process * @param childProcessArg */ public async handleTapProcess(childProcessArg: ChildProcess) { const done = plugins.smartpromise.defer(); childProcessArg.stdout.on('data', (data) => { this._processLog(data); }); childProcessArg.stderr.on('data', (data) => { this._processLog(data); }); childProcessArg.on('exit', async () => { await this.evaluateFinalResult(); done.resolve(); }); await done.promise; } public async handleTapLog(tapLog: string) { this._processLog(tapLog); } public async evaluateFinalResult() { this.receivedTests = this.testStore.length; // check wether all tests ran if (this.expectedTests === this.receivedTests) { if (this.logger) { this.logger.tapOutput(`${this.receivedTests} out of ${this.expectedTests} Tests completed!`); } } else { if (this.logger) { this.logger.error(`Only ${this.receivedTests} out of ${this.expectedTests} completed!`); } } if (!this.expectedTests) { if (this.logger) { this.logger.error('No tests were defined. Therefore the testfile failed!'); } } else if (this.expectedTests !== this.receivedTests) { if (this.logger) { this.logger.error('The amount of received tests and expectedTests is unequal! Therefore the testfile failed'); } } else if (this.getErrorTests().length === 0) { if (this.logger) { this.logger.tapOutput('All tests are successfull!!!'); this.logger.testFileEnd(this.receivedTests, 0, 0); } } else { if (this.logger) { this.logger.tapOutput(`${this.getErrorTests().length} tests threw an error!!!`, true); this.logger.testFileEnd(this.receivedTests - this.getErrorTests().length, this.getErrorTests().length, 0); } } } }