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'; 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:(.*)$/; /** * the constructor for TapParser */ constructor(public fileName: string) {} 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]); console.log( `${logPrefixes.TapPrefix} ${cs(`Expecting ${this.expectedTests} tests!`, 'blue')}` ); // initiating first TapResult this._getNewTapTestResult(); } else if (this.pretaskRegex.test(logLine)) { logLineIsTapProtocol = true; const pretaskContentMatch = this.pretaskRegex.exec(logLine); if (pretaskContentMatch && pretaskContentMatch[1]) { console.log(`${logPrefixes.TapPretaskPrefix} 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) { console.log( `${logPrefixes.TapErrorPrefix} Something is strange! Test Ids are not equal!` ); } this.activeTapTestResult.setTestResult(testOk); if (testOk) { console.log( logPrefixes.TapPrefix, `${cs(`T${testId} ${plugins.figures.tick}`, 'green')} ${plugins.figures.arrowRight} ` + cs(testSubject, 'blue') + ` | ${cs(`${testDuration} ms`, 'orange')}` ); } else { console.log( logPrefixes.TapPrefix, `${cs(`T${testId} ${plugins.figures.cross}`, 'red')} ${plugins.figures.arrowRight} ` + cs(testSubject, 'blue') + ` | ${cs(`${testDuration} ms`, 'orange')}` ); } } if (!logLineIsTapProtocol) { if (this.activeTapTestResult) { this.activeTapTestResult.addLogLine(logLine); } console.log(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) { console.log( `${logPrefixes.TapPrefix} ${cs( `${this.receivedTests} out of ${this.expectedTests} Tests completed!`, 'green' )}` ); } else { console.log( `${logPrefixes.TapErrorPrefix} ${cs( `Only ${this.receivedTests} out of ${this.expectedTests} completed!`, 'red' )}` ); } if (!this.expectedTests) { console.log(cs('Error: No tests were defined. Therefore the testfile failed!', 'red')); } else if (this.expectedTests !== this.receivedTests) { console.log( cs( 'Error: The amount of received tests and expectedTests is unequal! Therefore the testfile failed', 'red' ) ); } else if (this.getErrorTests().length === 0) { console.log(`${logPrefixes.TapPrefix} ${cs(`All tests are successfull!!!`, 'green')}`); } else { console.log( `${logPrefixes.TapPrefix} ${cs( `${this.getErrorTests().length} tests threw an error!!!`, 'red' )}` ); } } }