import * as path from 'path' ; import * as fs from 'fs-extra' ; import * as Mocha from 'mocha' ; import * as glob from 'glob' ; import { VSCodeBlockchainOutputAdapter } from '../extension/logging/VSCodeBlockchainOutputAdapter' ; import { TestUtil } from './TestUtil' ; const istanbul = require ( 'istanbul' ) ; const remapIstanbul = require ( 'remap-istanbul' ) ; export async function run ( testsRoot , cb ) { console . log ( 'running tests' ) ; const tty = require ( 'tty' ) ; if ( ! tty . getWindowSize ) { tty . getWindowSize = ( ) : number [ ] => { return [ 80 , 75 ] ; } ; } VSCodeBlockchainOutputAdapter . instance ( ) . setConsole ( true ) ; await fs . remove ( TestUtil . EXTENSION_TEST_DIR ) ; const filePath = path . join ( __dirname , '..' , '..' , 'unit-tests.xml' ) ; const mocha = new Mocha ( { ui : 'bdd' , useColors : true , timeout : 120000 , reporter : 'mocha-multi-reporters' , reporterOptions : { reporterEnabled : 'mocha-junit-reporter, spec' , mochaJunitReporterReporterOptions : { mochaFile : filePath } } } ) ; function _readCoverOptions ( ) { const coverConfigPath = path . join ( testsRoot , '..' , '..' , 'coverconfig.json' ) ; let coverConfig ; if ( fs . existsSync ( coverConfigPath ) ) { const configContent = fs . readFileSync ( coverConfigPath , 'utf-8' ) ; coverConfig = JSON . parse ( configContent ) ; } return coverConfig ; } try { if ( process . env . WITHOUTCOVERAGE !== 'true' ) { console . log ( 'setting up coverage' ) ; const coverOptions = _readCoverOptions ( ) ; if ( coverOptions && coverOptions . enabled ) { const coverageRunner = new CoverageRunner ( coverOptions , testsRoot ) ; coverageRunner . setupCoverage ( ) ; } } else { console . log ( 'coverage is disabled' ) ; } console . log ( 'finding files' , testsRoot ) ; const files = glob . sync ( '**/**.test.js' , { cwd : testsRoot } ) ; console . log ( 'found lots of files' ) ; let failedTests = 0 ; function runMocha ( ) { return new Promise ( ( resolve , error ) : any => { mocha . run ( ( failures ) => { failedTests += failures ; if ( failures > 0 ) { error ( new Error ( `${ failures } tests failed.` ) ) ; } else { resolve ( ) ; } } ) ; } ) ; } for ( const file of files ) { console . log ( 'adding file' , file ) ; mocha . addFile ( path . resolve ( testsRoot , file ) ) ; } try { await runMocha ( ) ; } catch ( err ) { cb ( err ) ; } finally { await TestUtil . restoreAll ( ) ; } cb ( null , failedTests ) ; } catch ( error ) { console . log ( error ) ; cb ( error ) ; } } interface ITestRunnerOptions { enabled ? : boolean ; relativeCoverageDir : string ; relativeSourcePath : string ; ignorePatterns : string [ ] ; includePid ? : boolean ; reports ? : string [ ] ; verbose ? : boolean ; } class CoverageRunner { private coverageVar : string = '$$cov_' + new Date ( ) . getTime ( ) + '$$' ; private transformer : any = undefined ; private matchFn : any = undefined ; private instrumenter : any = undefined ; constructor ( private options , private testsRoot ) { } public setupCoverage ( ) { const self = this ; self . instrumenter = new istanbul . Instrumenter ( { coverageVariable : self . coverageVar } ) ; const sourceRoot = path . join ( self . testsRoot , self . options . relativeSourcePath ) ; const srcFiles = glob . sync ( '**/*.js' , { cwd : sourceRoot , ignore : self . options . ignorePatterns , } ) ; const decache = require ( 'decache' ) ; const fileMap = { } ; srcFiles . forEach ( ( file ) => { const fullPath = path . join ( sourceRoot , file ) ; fileMap [ fullPath ] = true ; decache ( fullPath ) ; } ) ; self . matchFn = ( file ) : boolean => { return fileMap [ file ] ; } ; self . matchFn . files = Object . keys ( fileMap ) ; self . transformer = self . instrumenter . instrumentSync . bind ( self . instrumenter ) ; const hookOpts = { verbose : false , extensions : [ '.js' ] } ; istanbul . hook . hookRequire ( self . matchFn , self . transformer , hookOpts ) ; global [ self . coverageVar ] = { } ; process . on ( 'exit' , ( ) => { self . reportCoverage ( ) ; } ) ; } returns { void } memberOf public reportCoverage ( ) { const self = this ; istanbul . hook . unhookRequire ( ) ; let cov ; if ( typeof global [ self . coverageVar ] === 'undefined' || Object . keys ( global [ self . coverageVar ] ) . length === 0 ) { console . error ( 'No coverage information was collected, exit without writing coverage information' ) ; return ; } else { cov = global [ self . coverageVar ] ; } self . matchFn . files . forEach ( ( file ) => { if ( ! cov [ file ] ) { self . transformer ( fs . readFileSync ( file , 'utf-8' ) , file ) ; Object . keys ( self . instrumenter . coverState . s ) . forEach ( ( key ) => { self . instrumenter . coverState . s [ key ] = 0 ; } ) ; cov [ file ] = self . instrumenter . coverState ; } } ) ; const reportingDir = path . join ( self . testsRoot , self . options . relativeCoverageDir ) ; const includePid = self . options . includePid ; const pidExt = includePid ? ( '-' + process . pid ) : '' ; const coverageFile = path . resolve ( reportingDir , 'coverage' + pidExt + '.json' ) ; this . mkDirIfExists ( reportingDir ) ; fs . writeFileSync ( coverageFile , JSON . stringify ( cov ) , 'utf8' ) ; const remappedCollector = remapIstanbul . remap ( cov , { warn : ( warning ) : void => { if ( self . options . verbose ) { console . warn ( warning ) ; } } } ) ; const reporter = new istanbul . Reporter ( undefined , reportingDir ) ; const reportTypes = ( self . options . reports instanceof Array ) ? self . options . reports : [ 'lcov' ] ; reporter . addAll ( reportTypes ) ; reporter . write ( remappedCollector , true , ( ) => { console . log ( `reports written to ${ reportingDir }` ) ; } ) ; } private mkDirIfExists ( dir ) { if ( ! fs . existsSync ( dir ) ) { fs . mkdirSync ( dir ) ; } } }