2019-11-19 05:18:28 +03:00
/ * *
* Copyright 2017 Google Inc . All rights reserved .
*
* Licensed under the Apache License , Version 2.0 ( the "License" ) ;
* you may not use this file except in compliance with the License .
* You may obtain a copy of the License at
*
* http : //www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing , software
* distributed under the License is distributed on an "AS IS" BASIS ,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND , either express or implied .
* See the License for the specific language governing permissions and
* limitations under the License .
* /
2020-02-21 09:55:39 +03:00
const fs = require ( 'fs' ) ;
const colors = require ( 'colors/safe' ) ;
const { MatchError } = require ( './Matchers.js' ) ;
2019-11-19 05:18:28 +03:00
class Reporter {
constructor ( runner , options = { } ) {
const {
showSlowTests = 3 ,
2020-03-03 01:57:09 +03:00
showMarkedAsFailingTests = Infinity ,
2019-11-19 05:18:28 +03:00
verbose = false ,
summary = true ,
} = options ;
2020-02-21 09:55:39 +03:00
this . _filePathToLines = new Map ( ) ;
2019-11-19 05:18:28 +03:00
this . _runner = runner ;
this . _showSlowTests = showSlowTests ;
2020-03-03 01:57:09 +03:00
this . _showMarkedAsFailingTests = showMarkedAsFailingTests ;
2019-11-19 05:18:28 +03:00
this . _verbose = verbose ;
this . _summary = summary ;
this . _testCounter = 0 ;
runner . on ( 'started' , this . _onStarted . bind ( this ) ) ;
runner . on ( 'finished' , this . _onFinished . bind ( this ) ) ;
runner . on ( 'teststarted' , this . _onTestStarted . bind ( this ) ) ;
runner . on ( 'testfinished' , this . _onTestFinished . bind ( this ) ) ;
}
_onStarted ( runnableTests ) {
this . _testCounter = 0 ;
this . _timestamp = Date . now ( ) ;
const allTests = this . _runner . tests ( ) ;
2020-02-21 09:55:39 +03:00
if ( allTests . length === runnableTests . length ) {
console . log ( ` Running all ${ colors . yellow ( runnableTests . length ) } tests on ${ colors . yellow ( this . _runner . parallel ( ) ) } worker ${ this . _runner . parallel ( ) > 1 ? 's' : '' } : \n ` ) ;
} else {
2020-03-12 04:30:43 +03:00
console . log ( ` Running ${ colors . yellow ( runnableTests . length ) } focused tests out of total ${ colors . yellow ( allTests . length ) } on ${ colors . yellow ( this . _runner . parallel ( ) ) } worker ${ this . _runner . parallel ( ) > 1 ? 's' : '' } ` ) ;
console . log ( '' ) ;
2020-03-16 19:04:55 +03:00
const focusedSuites = this . _runner . focusedSuites ( ) . map ( suite => ( {
2020-03-27 08:47:13 +03:00
id : suite . location ( ) . filePath + ':' + suite . location ( ) . lineNumber + ':' + suite . location ( ) . columnNumber ,
fullName : suite . fullName ( ) ,
location : suite . location ( ) ,
2020-03-16 19:04:55 +03:00
} ) ) ;
const focusedTests = this . _runner . focusedTests ( ) . map ( test => ( {
2020-03-27 08:47:13 +03:00
id : test . location ( ) . filePath + ':' + test . location ( ) . lineNumber + ':' + test . location ( ) . columnNumber ,
fullName : test . fullName ( ) ,
location : test . location ( ) ,
2020-03-16 19:04:55 +03:00
} ) ) ;
const focusedEntities = new Map ( [
... focusedSuites . map ( suite => ( [ suite . id , suite ] ) ) ,
... focusedTests . map ( test => ( [ test . id , test ] ) ) ,
] ) ;
if ( focusedEntities . size ) {
2020-03-12 04:30:43 +03:00
console . log ( 'Focused Suites and Tests:' ) ;
2020-03-16 19:04:55 +03:00
const entities = [ ... focusedEntities . values ( ) ] ;
for ( let i = 0 ; i < entities . length ; ++ i )
console . log ( ` ${ i + 1 } ) ${ entities [ i ] . fullName } ( ${ formatLocation ( entities [ i ] . location ) } ) ` ) ;
2020-03-12 04:30:43 +03:00
console . log ( '' ) ;
}
2020-02-21 09:55:39 +03:00
}
2019-11-19 05:18:28 +03:00
}
2020-03-13 03:32:53 +03:00
_printFailedResult ( result ) {
console . log ( colors . red ( ` ## ${ result . result . toUpperCase ( ) } ## ` ) ) ;
if ( result . message ) {
console . log ( 'Message:' ) ;
console . log ( ` ${ colors . red ( result . message ) } ` ) ;
2019-11-19 05:18:28 +03:00
}
2020-03-13 03:32:53 +03:00
for ( let i = 0 ; i < result . errors . length ; i ++ ) {
const { message , error , workerId , tests } = result . errors [ i ] ;
console . log ( ` \n ${ colors . magenta ( 'NON-TEST ERROR #' + i ) } : ${ message } ` ) ;
if ( error && error . stack )
console . log ( padLines ( error . stack , 2 ) ) ;
const lastTests = tests . slice ( tests . length - Math . min ( 10 , tests . length ) ) ;
if ( lastTests . length )
console . log ( ` WORKER STATE ` ) ;
for ( let j = 0 ; j < lastTests . length ; j ++ )
this . _printVerboseTestResult ( j , lastTests [ j ] , workerId ) ;
2019-11-19 05:18:28 +03:00
}
2020-02-21 09:55:39 +03:00
console . log ( '' ) ;
console . log ( '' ) ;
2019-11-19 05:18:28 +03:00
}
2020-03-13 03:32:53 +03:00
_onFinished ( result ) {
2019-11-19 05:18:28 +03:00
this . _printTestResults ( ) ;
2020-03-13 03:32:53 +03:00
if ( ! result . ok ( ) )
this . _printFailedResult ( result ) ;
process . exitCode = result . exitCode ;
2019-11-19 05:18:28 +03:00
}
_printTestResults ( ) {
// 2 newlines after completing all tests.
console . log ( '\n' ) ;
const failedTests = this . _runner . failedTests ( ) ;
if ( this . _summary && failedTests . length > 0 ) {
console . log ( '\nFailures:' ) ;
for ( let i = 0 ; i < failedTests . length ; ++ i ) {
const test = failedTests [ i ] ;
2020-02-21 09:55:39 +03:00
this . _printVerboseTestResult ( i + 1 , test ) ;
2019-11-19 05:18:28 +03:00
console . log ( '' ) ;
}
}
const skippedTests = this . _runner . skippedTests ( ) ;
2020-03-03 01:57:09 +03:00
const markedAsFailingTests = this . _runner . markedAsFailingTests ( ) ;
if ( this . _showMarkedAsFailingTests && this . _summary && markedAsFailingTests . length ) {
if ( markedAsFailingTests . length > 0 ) {
console . log ( '\nMarked as failing:' ) ;
markedAsFailingTests . slice ( 0 , this . _showMarkedAsFailingTests ) . forEach ( ( test , index ) => {
2020-03-27 08:47:13 +03:00
console . log ( ` ${ index + 1 } ) ${ test . fullName ( ) } ( ${ formatLocation ( test . location ( ) ) } ) ` ) ;
2019-12-19 01:32:20 +03:00
} ) ;
}
2020-03-03 01:57:09 +03:00
if ( this . _showMarkedAsFailingTests < markedAsFailingTests . length ) {
2019-12-19 01:32:20 +03:00
console . log ( '' ) ;
2020-03-03 01:57:09 +03:00
console . log ( ` ... and ${ colors . yellow ( markedAsFailingTests . length - this . _showMarkedAsFailingTests ) } more marked as failing tests ... ` ) ;
2019-11-19 05:18:28 +03:00
}
}
if ( this . _showSlowTests ) {
const slowTests = this . _runner . passedTests ( ) . sort ( ( a , b ) => {
const aDuration = a . endTimestamp - a . startTimestamp ;
const bDuration = b . endTimestamp - b . startTimestamp ;
return bDuration - aDuration ;
} ) . slice ( 0 , this . _showSlowTests ) ;
console . log ( ` \n Slowest tests: ` ) ;
for ( let i = 0 ; i < slowTests . length ; ++ i ) {
const test = slowTests [ i ] ;
const duration = test . endTimestamp - test . startTimestamp ;
2020-03-27 08:47:13 +03:00
console . log ( ` ( ${ i + 1 } ) ${ colors . yellow ( ( duration / 1000 ) + 's' ) } - ${ test . fullName ( ) } ( ${ formatLocation ( test . location ( ) ) } ) ` ) ;
2019-11-19 05:18:28 +03:00
}
}
const tests = this . _runner . tests ( ) ;
const executedTests = tests . filter ( test => test . result ) ;
2020-03-03 01:57:09 +03:00
const okTestsLength = executedTests . length - failedTests . length - markedAsFailingTests . length - skippedTests . length ;
2019-11-19 05:18:28 +03:00
let summaryText = '' ;
2020-03-03 01:57:09 +03:00
if ( failedTests . length || markedAsFailingTests . length ) {
2020-02-21 09:55:39 +03:00
const summary = [ ` ok - ${ colors . green ( okTestsLength ) } ` ] ;
2019-11-19 05:18:28 +03:00
if ( failedTests . length )
2020-02-21 09:55:39 +03:00
summary . push ( ` failed - ${ colors . red ( failedTests . length ) } ` ) ;
2020-03-03 01:57:09 +03:00
if ( markedAsFailingTests . length )
summary . push ( ` marked as failing - ${ colors . yellow ( markedAsFailingTests . length ) } ` ) ;
2019-11-19 05:18:28 +03:00
if ( skippedTests . length )
2020-02-21 09:55:39 +03:00
summary . push ( ` skipped - ${ colors . yellow ( skippedTests . length ) } ` ) ;
summaryText = ` ( ${ summary . join ( ', ' ) } ) ` ;
2019-11-19 05:18:28 +03:00
}
2020-02-21 09:55:39 +03:00
console . log ( ` \n Ran ${ executedTests . length } ${ summaryText } of ${ tests . length } test ${ tests . length > 1 ? 's' : '' } ` ) ;
2019-11-19 05:18:28 +03:00
const milliseconds = Date . now ( ) - this . _timestamp ;
const seconds = milliseconds / 1000 ;
2020-02-21 09:55:39 +03:00
console . log ( ` Finished in ${ colors . yellow ( seconds ) } seconds ` ) ;
2019-11-19 05:18:28 +03:00
}
_onTestStarted ( test , workerId ) {
}
_onTestFinished ( test , workerId ) {
if ( this . _verbose ) {
++ this . _testCounter ;
2020-02-21 09:55:39 +03:00
this . _printVerboseTestResult ( this . _testCounter , test , workerId ) ;
2019-11-19 05:18:28 +03:00
} else {
if ( test . result === 'ok' )
2020-03-03 01:57:09 +03:00
process . stdout . write ( colors . green ( '\u00B7' ) ) ;
2019-11-19 05:18:28 +03:00
else if ( test . result === 'skipped' )
2020-03-03 01:57:09 +03:00
process . stdout . write ( colors . yellow ( '\u00B7' ) ) ;
else if ( test . result === 'markedAsFailing' )
process . stdout . write ( colors . yellow ( '\u00D7' ) ) ;
2019-11-19 05:18:28 +03:00
else if ( test . result === 'failed' )
2020-02-21 09:55:39 +03:00
process . stdout . write ( colors . red ( 'F' ) ) ;
2019-11-19 05:18:28 +03:00
else if ( test . result === 'crashed' )
2020-02-21 09:55:39 +03:00
process . stdout . write ( colors . red ( 'C' ) ) ;
2019-11-19 05:18:28 +03:00
else if ( test . result === 'terminated' )
2020-02-21 09:55:39 +03:00
process . stdout . write ( colors . magenta ( '.' ) ) ;
2019-11-19 05:18:28 +03:00
else if ( test . result === 'timedout' )
2020-02-21 09:55:39 +03:00
process . stdout . write ( colors . red ( 'T' ) ) ;
}
}
_printVerboseTestResult ( resultIndex , test , workerId = undefined ) {
let prefix = ` ${ resultIndex } ) ` ;
if ( this . _runner . parallel ( ) > 1 && workerId !== undefined )
prefix += ' ' + colors . gray ( ` [worker = ${ workerId } ] ` ) ;
if ( test . result === 'ok' ) {
2020-03-27 08:47:13 +03:00
console . log ( ` ${ prefix } ${ colors . green ( '[OK]' ) } ${ test . fullName ( ) } ( ${ formatLocation ( test . location ( ) ) } ) ` ) ;
2020-02-21 09:55:39 +03:00
} else if ( test . result === 'terminated' ) {
2020-03-27 08:47:13 +03:00
console . log ( ` ${ prefix } ${ colors . magenta ( '[TERMINATED]' ) } ${ test . fullName ( ) } ( ${ formatLocation ( test . location ( ) ) } ) ` ) ;
2020-02-21 09:55:39 +03:00
} else if ( test . result === 'crashed' ) {
2020-03-27 08:47:13 +03:00
console . log ( ` ${ prefix } ${ colors . red ( '[CRASHED]' ) } ${ test . fullName ( ) } ( ${ formatLocation ( test . location ( ) ) } ) ` ) ;
2020-02-21 09:55:39 +03:00
} else if ( test . result === 'skipped' ) {
2020-03-03 01:57:09 +03:00
} else if ( test . result === 'markedAsFailing' ) {
2020-03-27 08:47:13 +03:00
console . log ( ` ${ prefix } ${ colors . yellow ( '[MARKED AS FAILING]' ) } ${ test . fullName ( ) } ( ${ formatLocation ( test . location ( ) ) } ) ` ) ;
2020-02-21 09:55:39 +03:00
} else if ( test . result === 'timedout' ) {
2020-03-27 08:47:13 +03:00
console . log ( ` ${ prefix } ${ colors . red ( ` [TIMEOUT ${ test . timeout ( ) } ms] ` ) } ${ test . fullName ( ) } ( ${ formatLocation ( test . location ( ) ) } ) ` ) ;
2020-03-11 00:48:13 +03:00
if ( test . output ) {
console . log ( ' Output:' ) ;
2020-03-24 01:08:02 +03:00
for ( const line of test . output )
console . log ( ' ' + line ) ;
2020-03-11 00:48:13 +03:00
}
2020-02-21 09:55:39 +03:00
} else if ( test . result === 'failed' ) {
2020-03-27 08:47:13 +03:00
console . log ( ` ${ prefix } ${ colors . red ( '[FAIL]' ) } ${ test . fullName ( ) } ( ${ formatLocation ( test . location ( ) ) } ) ` ) ;
2020-02-21 09:55:39 +03:00
if ( test . error instanceof MatchError ) {
let lines = this . _filePathToLines . get ( test . error . location . filePath ) ;
if ( ! lines ) {
try {
lines = fs . readFileSync ( test . error . location . filePath , 'utf8' ) . split ( '\n' ) ;
} catch ( e ) {
lines = [ ] ;
}
this . _filePathToLines . set ( test . error . location . filePath , lines ) ;
}
const lineNumber = test . error . location . lineNumber ;
if ( lineNumber < lines . length ) {
const lineNumberLength = ( lineNumber + 1 + '' ) . length ;
2020-03-27 08:47:13 +03:00
const FROM = Math . max ( test . location ( ) . lineNumber - 1 , lineNumber - 5 ) ;
2020-02-21 09:55:39 +03:00
const snippet = lines . slice ( FROM , lineNumber ) . map ( ( line , index ) => ` ${ ( FROM + index + 1 + '' ) . padStart ( lineNumberLength , ' ' ) } | ${ line } ` ) . join ( '\n' ) ;
const pointer = ` ` + ' ' . repeat ( lineNumberLength ) + ' ' + '~' . repeat ( test . error . location . columnNumber - 1 ) + '^' ;
console . log ( '\n' + snippet + '\n' + colors . grey ( pointer ) + '\n' ) ;
}
console . log ( padLines ( test . error . formatter ( ) , 4 ) ) ;
console . log ( '' ) ;
} else {
console . log ( ' Message:' ) ;
2020-03-13 03:32:53 +03:00
let message = '' + ( test . error . message || test . error ) ;
if ( test . error . stack && message . includes ( test . error . stack ) )
message = message . substring ( 0 , message . indexOf ( test . error . stack ) ) ;
if ( message )
console . log ( ` ${ colors . red ( message ) } ` ) ;
2020-02-21 09:55:39 +03:00
if ( test . error . stack ) {
console . log ( ' Stack:' ) ;
let stack = test . error . stack ;
// Highlight first test location, if any.
2020-03-27 08:47:13 +03:00
const match = stack . match ( new RegExp ( test . location ( ) . filePath + ':(\\d+):(\\d+)' ) ) ;
2020-02-21 09:55:39 +03:00
if ( match ) {
const [ , line , column ] = match ;
2020-03-27 08:47:13 +03:00
const fileName = ` ${ test . location ( ) . fileName } : ${ line } : ${ column } ` ;
2020-02-21 09:55:39 +03:00
stack = stack . substring ( 0 , match . index ) + stack . substring ( match . index ) . replace ( fileName , colors . yellow ( fileName ) ) ;
}
console . log ( padLines ( stack , 4 ) ) ;
}
}
if ( test . output ) {
console . log ( ' Output:' ) ;
2020-03-24 01:08:02 +03:00
for ( const line of test . output )
console . log ( ' ' + line ) ;
2020-02-21 09:55:39 +03:00
}
2019-11-19 05:18:28 +03:00
}
}
}
2020-02-21 09:55:39 +03:00
function formatLocation ( location ) {
2019-11-19 05:18:28 +03:00
if ( ! location )
return '' ;
2020-02-21 09:55:39 +03:00
return colors . yellow ( ` ${ location . fileName } : ${ location . lineNumber } : ${ location . columnNumber } ` ) ;
}
function padLines ( text , spaces = 0 ) {
const indent = ' ' . repeat ( spaces ) ;
return text . split ( '\n' ) . map ( line => indent + line ) . join ( '\n' ) ;
2019-11-19 05:18:28 +03:00
}
module . exports = Reporter ;