55
66'use strict' ;
77
8- declare var global : any ;
9-
108/* tslint:disable no-require-imports */
119
1210import * as fs from 'fs' ;
1311import * as glob from 'glob' ;
1412import * as paths from 'path' ;
13+ import Mocha = require( 'mocha' ) ;
14+ import { ITestRunnerOptions , CoverageRunner } from '../coverage' ;
1515
16- const istanbul = require ( 'istanbul' ) ;
17- const Mocha = require ( 'mocha' ) ;
18- const remapIstanbul = require ( 'remap-istanbul' ) ;
16+ declare var global : any ;
1917
2018// Linux: prevent a weird NPE when mocha on Linux requires the window size from the TTY
2119// Since we are not running in a tty environment, we just implement the method statically
@@ -25,10 +23,11 @@ if (!tty.getWindowSize) {
2523 return [ 80 , 75 ] ;
2624 } ;
2725}
28- const config = {
26+
27+ const config : Mocha . MochaOptions = {
2928 reporter : 'mocha-jenkins-reporter' ,
3029 ui : 'tdd' ,
31- color : true ,
30+ useColors : true ,
3231 timeout : 15000
3332} ;
3433
@@ -43,12 +42,6 @@ function configure(mochaOpts: any): void {
4342}
4443exports . configure = configure ;
4544
46- function _mkDirIfExists ( dir : string ) : void {
47- if ( ! fs . existsSync ( dir ) ) {
48- fs . mkdirSync ( dir ) ;
49- }
50- }
51-
5245function _readCoverOptions ( testsRoot : string ) : ITestRunnerOptions | undefined {
5346 const coverConfigPath = paths . join ( testsRoot , '..' , '..' , '..' , 'coverconfig.json' ) ;
5447 console . log ( "coverage config location" , coverConfigPath ) ;
@@ -59,18 +52,16 @@ function _readCoverOptions(testsRoot: string): ITestRunnerOptions | undefined {
5952 return undefined ;
6053}
6154
62- function run ( testsRoot1 : string , clb : any ) : any {
63- return new Promise ( ( c , e ) => { // Glob test files
64- // Read configuration for the coverage file
65- const testsRoot = paths . resolve ( __dirname ) ;
66- const coverOptions = _readCoverOptions ( testsRoot ) ;
67- let coverageRunner ;
68- if ( coverOptions && coverOptions . enabled ) {
69- console . log ( 'coverage enabled!' ) ;
70- // Setup coverage pre-test, including post-test hook to report
71- coverageRunner = new CoverageRunner ( coverOptions , testsRoot ) ;
72- coverageRunner . setupCoverage ( ) ;
73- }
55+ function run ( ) : any {
56+ return new Promise ( ( c , e ) => {
57+ const testsRoot = paths . resolve ( __dirname ) ;
58+ const coverOptions = _readCoverOptions ( testsRoot ) ;
59+ let coverageRunner ;
60+ if ( coverOptions && coverOptions . enabled ) {
61+ console . log ( 'coverage enabled!' ) ;
62+ coverageRunner = new CoverageRunner ( coverOptions , testsRoot ) ;
63+ coverageRunner . setupCoverage ( ) ;
64+ }
7465 glob ( '**/**.test.js' , { cwd : testsRoot } , ( error , files ) : any => {
7566 if ( error ) {
7667 return e ( error ) ;
@@ -95,137 +86,4 @@ function run(testsRoot1: string, clb: any): any {
9586 } ) ;
9687 } ) ;
9788}
98- exports . run = run ;
99-
100- interface ITestRunnerOptions {
101- enabled ?: boolean ;
102- relativeCoverageDir : string ;
103- relativeSourcePath : string ;
104- ignorePatterns : string [ ] ;
105- includePid ?: boolean ;
106- reports ?: string [ ] ;
107- verbose ?: boolean ;
108- }
109-
110- class CoverageRunner {
111-
112- private coverageVar : string = `$$cov_${ new Date ( ) . getTime ( ) } $$` ;
113- private transformer : any = undefined ;
114- private matchFn : any = undefined ;
115- private instrumenter : any = undefined ;
116-
117- constructor ( private options : ITestRunnerOptions , private testsRoot : string ) {
118- if ( ! options . relativeSourcePath ) {
119- return ;
120- }
121- }
122-
123- public setupCoverage ( ) : void {
124- // Set up Code Coverage, hooking require so that instrumented code is returned
125- const self = this ;
126- self . instrumenter = new istanbul . Instrumenter ( { coverageVariable : self . coverageVar } ) ;
127- const sourceRoot = paths . join ( self . testsRoot , self . options . relativeSourcePath ) ;
128-
129- // Glob source files
130- const srcFiles = glob . sync ( '**/**.js' , {
131- cwd : sourceRoot ,
132- ignore : self . options . ignorePatterns
133- } ) ;
134-
135- // Create a match function - taken from the run-with-cover.js in istanbul.
136- const decache = require ( 'decache' ) ;
137- const fileMap : any = { } ;
138- srcFiles . forEach ( ( file ) => {
139- const fullPath = paths . join ( sourceRoot , file ) ;
140- fileMap [ fullPath ] = true ;
141-
142- // On Windows, extension is loaded pre-test hooks and this mean we lose
143- // our chance to hook the Require call. In order to instrument the code
144- // we have to decache the JS file so on next load it gets instrumented.
145- // This doesn't impact tests, but is a concern if we had some integration
146- // tests that relied on VSCode accessing our module since there could be
147- // some shared global state that we lose.
148- decache ( fullPath ) ;
149- } ) ;
150-
151- self . matchFn = ( file : string ) : boolean => fileMap [ file ] ;
152- self . matchFn . files = Object . keys ( fileMap ) ;
153-
154- // Hook up to the Require function so that when this is called, if any of our source files
155- // are required, the instrumented version is pulled in instead. These instrumented versions
156- // write to a global coverage variable with hit counts whenever they are accessed
157- self . transformer = self . instrumenter . instrumentSync . bind ( self . instrumenter ) ;
158- const hookOpts = { verbose : true , extensions : [ '.js' ] } ;
159- istanbul . hook . hookRequire ( self . matchFn , self . transformer , hookOpts ) ;
160-
161- // initialize the global variable to stop mocha from complaining about leaks
162- global [ self . coverageVar ] = { } ;
163- }
164-
165- /**
166- * Writes a coverage report.
167- * Note that as this is called in the process exit callback, all calls must be synchronous.
168- *
169- * @returns {void }
170- *
171- * @memberOf CoverageRunner
172- */
173- public reportCoverage ( ) : void {
174- const self = this ;
175- istanbul . hook . unhookRequire ( ) ;
176- let cov : any ;
177- if ( typeof global [ self . coverageVar ] === 'undefined' || Object . keys ( global [ self . coverageVar ] ) . length === 0 ) {
178- console . error ( 'No coverage information was collected, exit without writing coverage information' ) ;
179- return ;
180- } else {
181- cov = global [ self . coverageVar ] ;
182- }
183-
184- // TODO consider putting this under a conditional flag
185- // Files that are not touched by code ran by the test runner is manually instrumented, to
186- // illustrate the missing coverage.
187- self . matchFn . files . forEach ( ( file : any ) => {
188- if ( cov [ file ] ) {
189- return ;
190- }
191- self . transformer ( fs . readFileSync ( file , 'utf-8' ) , file ) ;
192-
193- // When instrumenting the code, istanbul will give each FunctionDeclaration a value of 1 in coverState.s,
194- // presumably to compensate for function hoisting. We need to reset this, as the function was not hoisted,
195- // as it was never loaded.
196- Object . keys ( self . instrumenter . coverState . s ) . forEach ( ( key ) => {
197- self . instrumenter . coverState . s [ key ] = 0 ;
198- } ) ;
199-
200- cov [ file ] = self . instrumenter . coverState ;
201- } ) ;
202-
203- // TODO Allow config of reporting directory with
204- const reportingDir = paths . join ( self . testsRoot , self . options . relativeCoverageDir ) ;
205- const includePid = self . options . includePid ;
206- const pidExt = includePid ? ( '-' + process . pid ) : '' ;
207- const coverageFile = paths . resolve ( reportingDir , `coverage${ pidExt } .json` ) ;
208-
209- // yes, do this again since some test runners could clean the dir initially created
210- _mkDirIfExists ( reportingDir ) ;
211-
212- fs . writeFileSync ( coverageFile , JSON . stringify ( cov ) , 'utf8' ) ;
213-
214- const remappedCollector = remapIstanbul . remap ( cov , {
215- warn : ( warning : any ) => {
216- // We expect some warnings as any JS file without a typescript mapping will cause this.
217- // By default, we'll skip printing these to the console as it clutters it up
218- if ( self . options . verbose ) {
219- console . warn ( warning ) ;
220- }
221- }
222- } ) ;
223-
224- const reporter = new istanbul . Reporter ( undefined , reportingDir ) ;
225- const reportTypes = ( self . options . reports instanceof Array ) ? self . options . reports : [ 'lcov' ] ;
226- reporter . addAll ( reportTypes ) ;
227- reporter . write ( remappedCollector , true , ( ) => {
228- console . log ( `reports written to ${ reportingDir } ` ) ;
229- } ) ;
230- }
231- }
89+ exports . run = run ;
0 commit comments