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 .
* /
const RED _COLOR = '\x1b[31m' ;
const GREEN _COLOR = '\x1b[32m' ;
2020-02-08 04:16:26 +03:00
const GRAY _COLOR = '\x1b[90m' ;
2019-11-19 05:18:28 +03:00
const YELLOW _COLOR = '\x1b[33m' ;
const MAGENTA _COLOR = '\x1b[35m' ;
const RESET _COLOR = '\x1b[0m' ;
class Reporter {
constructor ( runner , options = { } ) {
const {
projectFolder = null ,
showSlowTests = 3 ,
2019-12-19 01:32:20 +03:00
showSkippedTests = Infinity ,
2019-11-19 05:18:28 +03:00
verbose = false ,
summary = true ,
} = options ;
this . _runner = runner ;
this . _projectFolder = projectFolder ;
this . _showSlowTests = showSlowTests ;
2019-12-19 01:32:20 +03:00
this . _showSkippedTests = showSkippedTests ;
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 ) ) ;
this . _workersState = new Map ( ) ;
}
_onStarted ( runnableTests ) {
this . _testCounter = 0 ;
this . _timestamp = Date . now ( ) ;
const allTests = this . _runner . tests ( ) ;
if ( allTests . length === runnableTests . length )
console . log ( ` Running all ${ YELLOW _COLOR } ${ runnableTests . length } ${ RESET _COLOR } tests on ${ YELLOW _COLOR } ${ this . _runner . parallel ( ) } ${ RESET _COLOR } worker(s): \n ` ) ;
else
console . log ( ` Running ${ YELLOW _COLOR } ${ runnableTests . length } ${ RESET _COLOR } focused tests out of total ${ YELLOW _COLOR } ${ allTests . length } ${ RESET _COLOR } on ${ YELLOW _COLOR } ${ this . _runner . parallel ( ) } ${ RESET _COLOR } worker(s): \n ` ) ;
}
_printTermination ( result , message , error ) {
console . log ( ` ${ RED _COLOR } ## ${ result . toUpperCase ( ) } ## ${ RESET _COLOR } ` ) ;
console . log ( 'Message:' ) ;
console . log ( ` ${ RED _COLOR } ${ message } ${ RESET _COLOR } ` ) ;
if ( error && error . stack ) {
console . log ( 'Stack:' ) ;
console . log ( error . stack . split ( '\n' ) . map ( line => ' ' + line ) . join ( '\n' ) ) ;
}
console . log ( 'WORKERS STATE' ) ;
const workerIds = Array . from ( this . _workersState . keys ( ) ) ;
workerIds . sort ( ( a , b ) => a - b ) ;
for ( const workerId of workerIds ) {
const { isRunning , test } = this . _workersState . get ( workerId ) ;
let description = '' ;
if ( isRunning )
description = ` ${ YELLOW _COLOR } RUNNING ${ RESET _COLOR } ` ;
else if ( test . result === 'ok' )
description = ` ${ GREEN _COLOR } OK ${ RESET _COLOR } ` ;
else if ( test . result === 'skipped' )
description = ` ${ YELLOW _COLOR } SKIPPED ${ RESET _COLOR } ` ;
else if ( test . result === 'failed' )
description = ` ${ RED _COLOR } FAILED ${ RESET _COLOR } ` ;
else if ( test . result === 'crashed' )
description = ` ${ RED _COLOR } CRASHED ${ RESET _COLOR } ` ;
else if ( test . result === 'timedout' )
description = ` ${ RED _COLOR } TIMEDOUT ${ RESET _COLOR } ` ;
else if ( test . result === 'terminated' )
description = ` ${ MAGENTA _COLOR } TERMINATED ${ RESET _COLOR } ` ;
else
description = ` ${ RED _COLOR } <UNKNOWN> ${ RESET _COLOR } ` ;
console . log ( ` ${ workerId } : [ ${ description } ] ${ test . fullName } ( ${ formatTestLocation ( test ) } ) ` ) ;
}
process . exitCode = 2 ;
}
_onFinished ( { result , terminationError , terminationMessage } ) {
this . _printTestResults ( ) ;
if ( terminationMessage || terminationError )
this . _printTermination ( result , terminationMessage , terminationError ) ;
process . exitCode = result === 'ok' ? 0 : 1 ;
}
_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 ] ;
console . log ( ` ${ i + 1 } ) ${ test . fullName } ( ${ formatTestLocation ( test ) } ) ` ) ;
if ( test . result === 'timedout' ) {
console . log ( ' Message:' ) ;
console . log ( ` ${ RED _COLOR } Timeout Exceeded ${ this . _runner . timeout ( ) } ms ${ RESET _COLOR } ` ) ;
} else if ( test . result === 'crashed' ) {
console . log ( ' Message:' ) ;
console . log ( ` ${ RED _COLOR } CRASHED ${ RESET _COLOR } ` ) ;
} else {
console . log ( ' Message:' ) ;
console . log ( ` ${ RED _COLOR } ${ test . error . message || test . error } ${ RESET _COLOR } ` ) ;
console . log ( ' Stack:' ) ;
if ( test . error . stack )
console . log ( this . _beautifyStack ( test . error . stack ) ) ;
}
if ( test . output ) {
console . log ( ' Output:' ) ;
console . log ( test . output . split ( '\n' ) . map ( line => ' ' + line ) . join ( '\n' ) ) ;
}
console . log ( '' ) ;
}
}
const skippedTests = this . _runner . skippedTests ( ) ;
2019-12-19 01:32:20 +03:00
if ( this . _showSkippedTests && this . _summary && skippedTests . length ) {
if ( skippedTests . length > 0 ) {
console . log ( '\nSkipped:' ) ;
skippedTests . slice ( 0 , this . _showSkippedTests ) . forEach ( ( test , index ) => {
console . log ( ` ${ index + 1 } ) ${ test . fullName } ( ${ formatTestLocation ( test ) } ) ` ) ;
} ) ;
}
if ( this . _showSkippedTests < skippedTests . length ) {
console . log ( '' ) ;
console . log ( ` ... and ${ YELLOW _COLOR } ${ skippedTests . length - this . _showSkippedTests } ${ RESET _COLOR } more skipped 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 ;
console . log ( ` ( ${ i + 1 } ) ${ YELLOW _COLOR } ${ duration / 1000 } s ${ RESET _COLOR } - ${ test . fullName } ( ${ formatTestLocation ( test ) } ) ` ) ;
}
}
const tests = this . _runner . tests ( ) ;
const executedTests = tests . filter ( test => test . result ) ;
const okTestsLength = executedTests . length - failedTests . length - skippedTests . length ;
let summaryText = '' ;
if ( failedTests . length || skippedTests . length ) {
const summary = [ ` ok - ${ GREEN _COLOR } ${ okTestsLength } ${ RESET _COLOR } ` ] ;
if ( failedTests . length )
summary . push ( ` failed - ${ RED _COLOR } ${ failedTests . length } ${ RESET _COLOR } ` ) ;
if ( skippedTests . length )
summary . push ( ` skipped - ${ YELLOW _COLOR } ${ skippedTests . length } ${ RESET _COLOR } ` ) ;
summaryText = ` ( ${ summary . join ( ', ' ) } ) ` ;
}
console . log ( ` \n Ran ${ executedTests . length } ${ summaryText } of ${ tests . length } test(s) ` ) ;
const milliseconds = Date . now ( ) - this . _timestamp ;
const seconds = milliseconds / 1000 ;
console . log ( ` Finished in ${ YELLOW _COLOR } ${ seconds } ${ RESET _COLOR } seconds ` ) ;
}
_beautifyStack ( stack ) {
if ( ! this . _projectFolder )
return stack ;
const lines = stack . split ( '\n' ) . map ( line => ' ' + line ) ;
// Find last stack line that include testrunner code.
let index = 0 ;
while ( index < lines . length && ! lines [ index ] . includes ( _ _dirname ) )
++ index ;
while ( index < lines . length && lines [ index ] . includes ( _ _dirname ) )
++ index ;
if ( index >= lines . length )
return stack ;
const line = lines [ index ] ;
const fromIndex = line . lastIndexOf ( this . _projectFolder ) + this . _projectFolder . length ;
2019-12-19 00:55:17 +03:00
let toIndex = line . lastIndexOf ( ')' ) ;
if ( toIndex === - 1 )
toIndex = line . length ;
2019-11-19 05:18:28 +03:00
lines [ index ] = line . substring ( 0 , fromIndex ) + YELLOW _COLOR + line . substring ( fromIndex , toIndex ) + RESET _COLOR + line . substring ( toIndex ) ;
return lines . join ( '\n' ) ;
}
_onTestStarted ( test , workerId ) {
this . _workersState . set ( workerId , { test , isRunning : true } ) ;
}
_onTestFinished ( test , workerId ) {
this . _workersState . set ( workerId , { test , isRunning : false } ) ;
if ( this . _verbose ) {
++ this . _testCounter ;
2020-02-08 04:16:26 +03:00
let prefix = ` ${ this . _testCounter } ) ` ;
if ( this . _runner . parallel ( ) > 1 )
prefix += ` ${ GRAY _COLOR } [worker = ${ workerId } ] ${ RESET _COLOR } ` ;
2019-11-19 05:18:28 +03:00
if ( test . result === 'ok' ) {
2020-02-08 04:16:26 +03:00
console . log ( ` ${ prefix } ${ GREEN _COLOR } [ OK ] ${ RESET _COLOR } ${ test . fullName } ( ${ formatTestLocation ( test ) } ) ` ) ;
2019-11-19 05:18:28 +03:00
} else if ( test . result === 'terminated' ) {
2020-02-08 04:16:26 +03:00
console . log ( ` ${ prefix } ${ MAGENTA _COLOR } [ TERMINATED ] ${ RESET _COLOR } ${ test . fullName } ( ${ formatTestLocation ( test ) } ) ` ) ;
2019-11-19 05:18:28 +03:00
} else if ( test . result === 'crashed' ) {
2020-02-08 04:16:26 +03:00
console . log ( ` ${ prefix } ${ RED _COLOR } [ CRASHED ] ${ RESET _COLOR } ${ test . fullName } ( ${ formatTestLocation ( test ) } ) ` ) ;
2019-11-19 05:18:28 +03:00
} else if ( test . result === 'skipped' ) {
2020-02-08 04:16:26 +03:00
console . log ( ` ${ prefix } ${ YELLOW _COLOR } [SKIP] ${ RESET _COLOR } ${ test . fullName } ( ${ formatTestLocation ( test ) } ) ` ) ;
2019-11-19 05:18:28 +03:00
} else if ( test . result === 'failed' ) {
2020-02-08 04:16:26 +03:00
console . log ( ` ${ prefix } ${ RED _COLOR } [FAIL] ${ RESET _COLOR } ${ test . fullName } ( ${ formatTestLocation ( test ) } ) ` ) ;
2019-11-19 05:18:28 +03:00
console . log ( ' Message:' ) ;
console . log ( ` ${ RED _COLOR } ${ test . error . message || test . error } ${ RESET _COLOR } ` ) ;
console . log ( ' Stack:' ) ;
if ( test . error . stack )
console . log ( this . _beautifyStack ( test . error . stack ) ) ;
if ( test . output ) {
console . log ( ' Output:' ) ;
console . log ( test . output . split ( '\n' ) . map ( line => ' ' + line ) . join ( '\n' ) ) ;
}
} else if ( test . result === 'timedout' ) {
2020-02-08 04:16:26 +03:00
console . log ( ` ${ prefix } ${ RED _COLOR } [TIME] ${ RESET _COLOR } ${ test . fullName } ( ${ formatTestLocation ( test ) } ) ` ) ;
2019-11-19 05:18:28 +03:00
console . log ( ' Message:' ) ;
console . log ( ` ${ RED _COLOR } Timeout Exceeded ${ this . _runner . timeout ( ) } ms ${ RESET _COLOR } ` ) ;
}
} else {
if ( test . result === 'ok' )
process . stdout . write ( ` ${ GREEN _COLOR } . ${ RESET _COLOR } ` ) ;
else if ( test . result === 'skipped' )
process . stdout . write ( ` ${ YELLOW _COLOR } * ${ RESET _COLOR } ` ) ;
else if ( test . result === 'failed' )
process . stdout . write ( ` ${ RED _COLOR } F ${ RESET _COLOR } ` ) ;
else if ( test . result === 'crashed' )
process . stdout . write ( ` ${ RED _COLOR } C ${ RESET _COLOR } ` ) ;
else if ( test . result === 'terminated' )
process . stdout . write ( ` ${ MAGENTA _COLOR } . ${ RESET _COLOR } ` ) ;
else if ( test . result === 'timedout' )
process . stdout . write ( ` ${ RED _COLOR } T ${ RESET _COLOR } ` ) ;
}
}
}
function formatTestLocation ( test ) {
const location = test . location ;
if ( ! location )
return '' ;
return ` ${ YELLOW _COLOR } ${ location . fileName } : ${ location . lineNumber } : ${ location . columnNumber } ${ RESET _COLOR } ` ;
}
module . exports = Reporter ;