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' ) ;
2020-05-01 20:09:45 +03:00
const path = require ( 'path' ) ;
2020-02-21 09:55:39 +03:00
const colors = require ( 'colors/safe' ) ;
const { MatchError } = require ( './Matchers.js' ) ;
2019-11-19 05:18:28 +03:00
class Reporter {
2020-04-07 03:21:42 +03:00
constructor ( delegate , options = { } ) {
2019-11-19 05:18:28 +03:00
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 ,
2020-05-05 01:15:51 +03:00
lineBreak = 0 ,
2019-11-19 05:18:28 +03:00
} = options ;
2020-02-21 09:55:39 +03:00
this . _filePathToLines = new Map ( ) ;
2020-04-07 03:21:42 +03:00
this . _delegate = delegate ;
2019-11-19 05:18:28 +03:00
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 ;
2020-05-05 01:15:51 +03:00
this . _lineBreak = lineBreak ;
2019-11-19 05:18:28 +03:00
this . _testCounter = 0 ;
}
2020-04-04 01:47:25 +03:00
onStarted ( testRuns ) {
2019-11-19 05:18:28 +03:00
this . _testCounter = 0 ;
this . _timestamp = Date . now ( ) ;
2020-05-01 20:09:45 +03:00
if ( ! this . _delegate . hasFocusedTestsOrSuitesOrFiles ( ) ) {
2020-04-07 03:21:42 +03:00
console . log ( ` Running all ${ colors . yellow ( testRuns . length ) } tests on ${ colors . yellow ( this . _delegate . parallel ( ) ) } worker ${ this . _delegate . parallel ( ) > 1 ? 's' : '' } : \n ` ) ;
2020-02-21 09:55:39 +03:00
} else {
2020-04-07 03:21:42 +03:00
console . log ( ` Running ${ colors . yellow ( testRuns . length ) } focused tests out of total ${ colors . yellow ( this . _delegate . testCount ( ) ) } on ${ colors . yellow ( this . _delegate . parallel ( ) ) } worker ${ this . _delegate . parallel ( ) > 1 ? 's' : '' } ` ) ;
2020-03-12 04:30:43 +03:00
console . log ( '' ) ;
2020-05-01 20:09:45 +03:00
const focusedFilePaths = this . _delegate . focusedFilePaths ( ) ;
if ( focusedFilePaths . length ) {
console . log ( 'Focused Files:' ) ;
for ( let i = 0 ; i < focusedFilePaths . length ; ++ i )
console . log ( ` ${ i + 1 } ) ${ colors . yellow ( path . basename ( focusedFilePaths [ i ] ) ) } ` ) ;
console . log ( '' ) ;
}
2020-03-29 00:25:57 +03:00
const focusedEntities = [
2020-04-07 03:21:42 +03:00
... this . _delegate . focusedSuites ( ) ,
... this . _delegate . focusedTests ( ) ,
2020-03-29 00:25:57 +03:00
] ;
2020-05-01 20:09:45 +03:00
2020-03-29 00:25:57 +03:00
if ( focusedEntities . length ) {
2020-03-12 04:30:43 +03:00
console . log ( 'Focused Suites and Tests:' ) ;
2020-03-29 00:25:57 +03:00
for ( let i = 0 ; i < focusedEntities . length ; ++ i )
console . log ( ` ${ i + 1 } ) ${ focusedEntities [ i ] . fullName ( ) } ( ${ formatLocation ( focusedEntities [ 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 ++ ) {
2020-03-29 00:25:57 +03:00
const { message , error , runs } = result . errors [ i ] ;
2020-03-13 03:32:53 +03:00
console . log ( ` \n ${ colors . magenta ( 'NON-TEST ERROR #' + i ) } : ${ message } ` ) ;
if ( error && error . stack )
console . log ( padLines ( error . stack , 2 ) ) ;
2020-03-29 00:25:57 +03:00
const lastRuns = runs . slice ( runs . length - Math . min ( 10 , runs . length ) ) ;
if ( lastRuns . length )
2020-03-13 03:32:53 +03:00
console . log ( ` WORKER STATE ` ) ;
2020-03-29 00:25:57 +03:00
for ( let j = 0 ; j < lastRuns . length ; j ++ )
this . _printVerboseTestRunResult ( j , lastRuns [ j ] ) ;
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-04-04 01:47:25 +03:00
onFinished ( result ) {
2020-03-29 00:25:57 +03:00
this . _printTestResults ( result ) ;
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
}
2020-03-29 00:25:57 +03:00
_printTestResults ( result ) {
2019-11-19 05:18:28 +03:00
// 2 newlines after completing all tests.
console . log ( '\n' ) ;
2020-03-29 00:25:57 +03:00
const runs = result . runs ;
const failedRuns = runs . filter ( run => run . isFailure ( ) ) ;
const executedRuns = runs . filter ( run => run . result ( ) ) ;
const okRuns = runs . filter ( run => run . ok ( ) ) ;
const skippedRuns = runs . filter ( run => run . result ( ) === 'skipped' ) ;
const markedAsFailingRuns = runs . filter ( run => run . result ( ) === 'markedAsFailing' ) ;
if ( this . _summary && failedRuns . length > 0 ) {
2019-11-19 05:18:28 +03:00
console . log ( '\nFailures:' ) ;
2020-03-29 00:25:57 +03:00
for ( let i = 0 ; i < failedRuns . length ; ++ i ) {
this . _printVerboseTestRunResult ( i + 1 , failedRuns [ i ] ) ;
2019-11-19 05:18:28 +03:00
console . log ( '' ) ;
}
}
2020-03-29 00:25:57 +03:00
if ( this . _showMarkedAsFailingTests && this . _summary && markedAsFailingRuns . length ) {
if ( markedAsFailingRuns . length > 0 ) {
2020-03-03 01:57:09 +03:00
console . log ( '\nMarked as failing:' ) ;
2020-03-29 00:25:57 +03:00
markedAsFailingRuns . slice ( 0 , this . _showMarkedAsFailingTests ) . forEach ( ( testRun , index ) => {
console . log ( ` ${ index + 1 } ) ${ testRun . test ( ) . fullName ( ) } ( ${ formatLocation ( testRun . test ( ) . location ( ) ) } ) ` ) ;
2019-12-19 01:32:20 +03:00
} ) ;
}
2020-03-29 00:25:57 +03:00
if ( this . _showMarkedAsFailingTests < markedAsFailingRuns . length ) {
2019-12-19 01:32:20 +03:00
console . log ( '' ) ;
2020-03-29 00:25:57 +03:00
console . log ( ` ... and ${ colors . yellow ( markedAsFailingRuns . length - this . _showMarkedAsFailingTests ) } more marked as failing tests ... ` ) ;
2019-11-19 05:18:28 +03:00
}
}
if ( this . _showSlowTests ) {
2020-03-29 00:25:57 +03:00
const slowRuns = okRuns . sort ( ( a , b ) => b . duration ( ) - a . duration ( ) ) . slice ( 0 , this . _showSlowTests ) ;
2019-11-19 05:18:28 +03:00
console . log ( ` \n Slowest tests: ` ) ;
2020-03-29 00:25:57 +03:00
for ( let i = 0 ; i < slowRuns . length ; ++ i ) {
const run = slowRuns [ i ] ;
console . log ( ` ( ${ i + 1 } ) ${ colors . yellow ( ( run . duration ( ) / 1000 ) + 's' ) } - ${ run . test ( ) . fullName ( ) } ( ${ formatLocation ( run . test ( ) . location ( ) ) } ) ` ) ;
2019-11-19 05:18:28 +03:00
}
}
let summaryText = '' ;
2020-03-29 00:25:57 +03:00
if ( failedRuns . length || markedAsFailingRuns . length ) {
const summary = [ ` ok - ${ colors . green ( okRuns . length ) } ` ] ;
if ( failedRuns . length )
summary . push ( ` failed - ${ colors . red ( failedRuns . length ) } ` ) ;
if ( markedAsFailingRuns . length )
summary . push ( ` marked as failing - ${ colors . yellow ( markedAsFailingRuns . length ) } ` ) ;
if ( skippedRuns . length )
summary . push ( ` skipped - ${ colors . yellow ( skippedRuns . length ) } ` ) ;
2020-02-21 09:55:39 +03:00
summaryText = ` ( ${ summary . join ( ', ' ) } ) ` ;
2019-11-19 05:18:28 +03:00
}
2020-03-29 00:25:57 +03:00
console . log ( ` \n Ran ${ executedRuns . length } ${ summaryText } of ${ runs . length } test ${ runs . 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
}
2020-04-04 01:47:25 +03:00
onTestRunStarted ( testRun ) {
2019-11-19 05:18:28 +03:00
}
2020-04-04 01:47:25 +03:00
onTestRunFinished ( testRun ) {
2020-05-05 01:15:51 +03:00
++ this . _testCounter ;
2019-11-19 05:18:28 +03:00
if ( this . _verbose ) {
2020-03-29 00:25:57 +03:00
this . _printVerboseTestRunResult ( this . _testCounter , testRun ) ;
2019-11-19 05:18:28 +03:00
} else {
2020-03-29 00:25:57 +03:00
if ( testRun . result ( ) === 'ok' )
2020-03-03 01:57:09 +03:00
process . stdout . write ( colors . green ( '\u00B7' ) ) ;
2020-03-29 00:25:57 +03:00
else if ( testRun . result ( ) === 'skipped' )
2020-03-03 01:57:09 +03:00
process . stdout . write ( colors . yellow ( '\u00B7' ) ) ;
2020-03-29 00:25:57 +03:00
else if ( testRun . result ( ) === 'markedAsFailing' )
2020-03-03 01:57:09 +03:00
process . stdout . write ( colors . yellow ( '\u00D7' ) ) ;
2020-03-29 00:25:57 +03:00
else if ( testRun . result ( ) === 'failed' )
2020-02-21 09:55:39 +03:00
process . stdout . write ( colors . red ( 'F' ) ) ;
2020-03-29 00:25:57 +03:00
else if ( testRun . result ( ) === 'crashed' )
2020-02-21 09:55:39 +03:00
process . stdout . write ( colors . red ( 'C' ) ) ;
2020-03-29 00:25:57 +03:00
else if ( testRun . result ( ) === 'terminated' )
2020-02-21 09:55:39 +03:00
process . stdout . write ( colors . magenta ( '.' ) ) ;
2020-03-29 00:25:57 +03:00
else if ( testRun . result ( ) === 'timedout' )
2020-02-21 09:55:39 +03:00
process . stdout . write ( colors . red ( 'T' ) ) ;
2020-05-05 01:15:51 +03:00
if ( this . _lineBreak && ! ( this . _testCounter % this . _lineBreak ) )
process . stdout . write ( '\n' ) ;
2020-02-21 09:55:39 +03:00
}
}
2020-03-29 00:25:57 +03:00
_printVerboseTestRunResult ( resultIndex , testRun ) {
const test = testRun . test ( ) ;
2020-02-21 09:55:39 +03:00
let prefix = ` ${ resultIndex } ) ` ;
2020-04-07 03:21:42 +03:00
if ( this . _delegate . parallel ( ) > 1 )
2020-03-29 00:25:57 +03:00
prefix += ' ' + colors . gray ( ` [worker = ${ testRun . workerId ( ) } ] ` ) ;
if ( testRun . result ( ) === 'ok' ) {
2020-03-27 08:47:13 +03:00
console . log ( ` ${ prefix } ${ colors . green ( '[OK]' ) } ${ test . fullName ( ) } ( ${ formatLocation ( test . location ( ) ) } ) ` ) ;
2020-03-29 00:25:57 +03:00
} else if ( testRun . result ( ) === 'terminated' ) {
2020-03-27 08:47:13 +03:00
console . log ( ` ${ prefix } ${ colors . magenta ( '[TERMINATED]' ) } ${ test . fullName ( ) } ( ${ formatLocation ( test . location ( ) ) } ) ` ) ;
2020-03-29 00:25:57 +03:00
} else if ( testRun . result ( ) === 'crashed' ) {
2020-03-27 08:47:13 +03:00
console . log ( ` ${ prefix } ${ colors . red ( '[CRASHED]' ) } ${ test . fullName ( ) } ( ${ formatLocation ( test . location ( ) ) } ) ` ) ;
2020-03-29 00:25:57 +03:00
} else if ( testRun . result ( ) === 'skipped' ) {
} else if ( testRun . result ( ) === 'markedAsFailing' ) {
2020-03-27 08:47:13 +03:00
console . log ( ` ${ prefix } ${ colors . yellow ( '[MARKED AS FAILING]' ) } ${ test . fullName ( ) } ( ${ formatLocation ( test . location ( ) ) } ) ` ) ;
2020-03-29 00:25:57 +03:00
} else if ( testRun . 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-04-07 03:21:42 +03:00
const output = testRun . output ( ) ;
if ( output . length ) {
2020-03-11 00:48:13 +03:00
console . log ( ' Output:' ) ;
2020-04-07 03:21:42 +03:00
for ( const line of output )
2020-03-24 01:08:02 +03:00
console . log ( ' ' + line ) ;
2020-03-11 00:48:13 +03:00
}
2020-03-29 00:25:57 +03:00
} else if ( testRun . result ( ) === 'failed' ) {
2020-03-27 08:47:13 +03:00
console . log ( ` ${ prefix } ${ colors . red ( '[FAIL]' ) } ${ test . fullName ( ) } ( ${ formatLocation ( test . location ( ) ) } ) ` ) ;
2020-03-29 00:25:57 +03:00
if ( testRun . error ( ) instanceof MatchError ) {
2020-03-30 07:38:30 +03:00
const location = testRun . error ( ) . location ;
let lines = this . _filePathToLines . get ( location . filePath ( ) ) ;
2020-02-21 09:55:39 +03:00
if ( ! lines ) {
try {
2020-03-30 07:38:30 +03:00
lines = fs . readFileSync ( location . filePath ( ) , 'utf8' ) . split ( '\n' ) ;
2020-02-21 09:55:39 +03:00
} catch ( e ) {
lines = [ ] ;
}
2020-03-30 07:38:30 +03:00
this . _filePathToLines . set ( location . filePath ( ) , lines ) ;
2020-02-21 09:55:39 +03:00
}
2020-03-30 07:38:30 +03:00
const lineNumber = location . lineNumber ( ) ;
2020-02-21 09:55:39 +03:00
if ( lineNumber < lines . length ) {
const lineNumberLength = ( lineNumber + 1 + '' ) . length ;
2020-06-05 02:43:48 +03:00
const FROM = Math . max ( 0 , 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' ) ;
2020-03-30 07:38:30 +03:00
const pointer = ` ` + ' ' . repeat ( lineNumberLength ) + ' ' + '~' . repeat ( location . columnNumber ( ) - 1 ) + '^' ;
2020-02-21 09:55:39 +03:00
console . log ( '\n' + snippet + '\n' + colors . grey ( pointer ) + '\n' ) ;
}
2020-03-29 00:25:57 +03:00
console . log ( padLines ( testRun . error ( ) . formatter ( ) , 4 ) ) ;
2020-02-21 09:55:39 +03:00
console . log ( '' ) ;
} else {
console . log ( ' Message:' ) ;
2020-03-29 00:25:57 +03:00
let message = '' + ( testRun . error ( ) . message || testRun . error ( ) ) ;
if ( testRun . error ( ) . stack && message . includes ( testRun . error ( ) . stack ) )
message = message . substring ( 0 , message . indexOf ( testRun . error ( ) . stack ) ) ;
2020-03-13 03:32:53 +03:00
if ( message )
console . log ( ` ${ colors . red ( message ) } ` ) ;
2020-03-29 00:25:57 +03:00
if ( testRun . error ( ) . stack ) {
2020-02-21 09:55:39 +03:00
console . log ( ' Stack:' ) ;
2020-03-29 00:25:57 +03:00
let stack = testRun . error ( ) . stack ;
2020-02-21 09:55:39 +03:00
// Highlight first test location, if any.
2020-03-30 07:38:30 +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-30 07:38:30 +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 ) ) ;
}
}
2020-04-07 03:21:42 +03:00
const output = testRun . output ( ) ;
if ( output . length ) {
2020-02-21 09:55:39 +03:00
console . log ( ' Output:' ) ;
2020-04-07 03:21:42 +03:00
for ( const line of output )
2020-03-24 01:08:02 +03:00
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-03-30 07:38:30 +03:00
return colors . yellow ( ` ${ location . toDetailedString ( ) } ` ) ;
2020-02-21 09:55:39 +03:00
}
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 ;