1- import { spy , SinonSpy , stub } from 'sinon' ;
1+ import { createSandbox , spy , stub , SinonSpy } from 'sinon' ;
22import { Task , deepMixin , isPromiseLike } from '@theintern/common' ;
33
44import { Config } from 'src/lib/common/config' ;
@@ -10,10 +10,15 @@ import { testProperty } from '../../../support/unit/executor';
1010const mockRequire = intern . getPlugin < mocking . MockRequire > ( 'mockRequire' ) ;
1111
1212registerSuite ( 'lib/executors/Node' , function ( ) {
13- function createExecutor ( config ?: Partial < Config > ) {
13+ const sandbox = createSandbox ( ) ;
14+
15+ function createExecutor (
16+ config ?: Partial < Config > ,
17+ loader ?: ( mods : string [ ] ) => Promise < void >
18+ ) {
1419 const executor = new Node ( config ) ;
15- executor . registerLoader ( ( _options : any ) => ( _modules : string [ ] ) =>
16- Promise . resolve ( )
20+ executor . registerLoader (
21+ ( _options : any ) => loader || ( ( ) => Promise . resolve ( ) )
1722 ) ;
1823 return executor ;
1924 }
@@ -61,26 +66,16 @@ registerSuite('lib/executors/Node', function() {
6166 }
6267 }
6368
64- class MockInstrumenter {
65- private fileCoverage :
66- | {
67- code : string ;
68- filename : string ;
69- // tslint:disable-next-line:indent
70- }
71- | undefined ;
72-
73- instrumentSync ( code : string , filename : string ) {
74- this . fileCoverage = { code, filename } ;
69+ const mockInstrumenter = {
70+ instrumentSync : sandbox . spy ( ( code : string , filename : string ) => {
71+ mockInstrumenter . lastFileCoverage . returns ( { code, filename } ) ;
7572 return `instrumented: ${ code } ` ;
76- }
73+ } ) ,
7774
78- lastSourceMap ( ) { }
75+ lastSourceMap : ( ) => { } ,
7976
80- lastFileCoverage ( ) {
81- return this . fileCoverage ;
82- }
83- }
77+ lastFileCoverage : sandbox . stub ( ) . returns ( undefined )
78+ } ;
8479
8580 class MockServer {
8681 constructor ( ) {
@@ -147,19 +142,21 @@ registerSuite('lib/executors/Node', function() {
147142
148143 class MockMapStore {
149144 mockName = 'mapStore' ;
150- data = { } ;
151- registerMap ( ) { }
145+ data : Record < string , unknown > = { } ;
146+ registerMap ( filename : string , data : unknown ) {
147+ this . data [ filename ] = data ;
148+ }
152149 }
153150
154151 const mockConsole = {
155- log : spy ( ( ..._args : any [ ] ) => { } ) ,
156- warn : spy ( ( ..._args : any [ ] ) => { } ) ,
157- error : spy ( ( ..._args : any [ ] ) => { } )
152+ log : sandbox . spy ( ( ..._args : any [ ] ) => { } ) ,
153+ warn : sandbox . spy ( ( ..._args : any [ ] ) => { } ) ,
154+ error : sandbox . spy ( ( ..._args : any [ ] ) => { } )
158155 } ;
159156
160157 const mockChai = {
161158 assert : 'assert' ,
162- should : spy ( ( ) => 'should' )
159+ should : sandbox . spy ( ( ) => 'should' )
163160 } ;
164161
165162 const mockFs = {
@@ -199,8 +196,8 @@ registerSuite('lib/executors/Node', function() {
199196 process : {
200197 cwd : ( ) => '' ,
201198 env : { } ,
202- exit : spy ( ( ..._args : any [ ] ) => { } ) ,
203- on : spy ( ( ..._args : any [ ] ) => { } ) ,
199+ exit : sandbox . spy ( ( ..._args : any [ ] ) => { } ) ,
200+ on : sandbox . spy ( ( ..._args : any [ ] ) => { } ) ,
204201 stdout : process . stdout
205202 }
206203 } ;
@@ -213,7 +210,7 @@ registerSuite('lib/executors/Node', function() {
213210 }
214211 }
215212
216- const mockTsNodeRegister = spy ( ) ;
213+ const mockTsNodeRegister = sandbox . spy ( ) ;
217214
218215 const mockNodeUtil = {
219216 expandFiles ( files : string | string [ ] ) {
@@ -225,7 +222,20 @@ registerSuite('lib/executors/Node', function() {
225222 readSourceMap ( ) {
226223 return { } ;
227224 } ,
228- transpileSource : spy ( )
225+ transpileSource : sandbox . spy ( )
226+ } ;
227+
228+ type IstanbulMatcher = ( filename : string ) => boolean ;
229+ type IstanbulHook = ( code : string , opts : { filename : string } ) => boolean ;
230+
231+ const mockIstanbulHook = {
232+ hookRunInThisContext : sandbox . spy (
233+ ( _matcher : IstanbulMatcher , _hook : IstanbulHook ) => undefined
234+ ) ,
235+ hookRequire : sandbox . spy (
236+ ( _matcher : IstanbulMatcher , _hook : IstanbulHook ) => undefined
237+ ) ,
238+ unhookRunInThisContext : sandbox . spy ( )
229239 } ;
230240
231241 let executor : _Node ;
@@ -274,14 +284,10 @@ registerSuite('lib/executors/Node', function() {
274284 return new MockCoverageMap ( ) ;
275285 }
276286 } ,
277- 'istanbul-lib-hook' : {
278- hookRunInThisContext ( ) { } ,
279- hookRequire ( ) { } ,
280- unhookRunInThisContext ( ) { }
281- } ,
287+ 'istanbul-lib-hook' : mockIstanbulHook ,
282288 'istanbul-lib-instrument' : {
283289 createInstrumenter ( ) {
284- return new MockInstrumenter ( ) ;
290+ return mockInstrumenter ;
285291 } ,
286292 readInitialCoverage ( code : string ) {
287293 return { coverageData : `covered: ${ code } ` } ;
@@ -332,12 +338,7 @@ registerSuite('lib/executors/Node', function() {
332338
333339 afterEach ( ) {
334340 require . extensions [ '.ts' ] = tsExtension ;
335- mockTsNodeRegister . resetHistory ( ) ;
336- mockConsole . log . resetHistory ( ) ;
337- mockConsole . warn . resetHistory ( ) ;
338- mockConsole . error . resetHistory ( ) ;
339- mockGlobal . process . on . resetHistory ( ) ;
340- mockNodeUtil . transpileSource . resetHistory ( ) ;
341+ sandbox . resetHistory ( ) ;
341342 } ,
342343
343344 tests : {
@@ -1040,7 +1041,7 @@ registerSuite('lib/executors/Node', function() {
10401041 } ) ;
10411042 } ,
10421043
1043- 'full coverage' ( ) {
1044+ async 'full coverage' ( ) {
10441045 fsData [ 'foo.js' ] = 'foo' ;
10451046 fsData [ 'bar.js' ] = 'bar' ;
10461047 executor . configure ( < any > {
@@ -1050,18 +1051,127 @@ registerSuite('lib/executors/Node', function() {
10501051 coverage : [ 'foo.js' , 'bar.js' ]
10511052 } ) ;
10521053
1053- return executor . run ( ) . then ( ( ) => {
1054- const map : MockCoverageMap = executor . coverageMap as any ;
1055- assert . isTrue ( map . addFileCoverage . calledTwice ) ;
1056- assert . deepEqual ( map . addFileCoverage . args [ 0 ] [ 0 ] , {
1057- code : 'foo' ,
1058- filename : 'foo.js'
1059- } ) ;
1060- assert . deepEqual ( map . addFileCoverage . args [ 1 ] [ 0 ] , {
1061- code : 'bar' ,
1062- filename : 'bar.js'
1063- } ) ;
1054+ await executor . run ( ) ;
1055+
1056+ const map : MockCoverageMap = executor . coverageMap as any ;
1057+ assert . equal ( map . addFileCoverage . callCount , 2 ) ;
1058+ assert . deepEqual ( map . addFileCoverage . args [ 0 ] [ 0 ] , {
1059+ code : 'foo' ,
1060+ filename : 'foo.js'
1061+ } ) ;
1062+ assert . deepEqual ( map . addFileCoverage . args [ 1 ] [ 0 ] , {
1063+ code : 'bar' ,
1064+ filename : 'bar.js'
1065+ } ) ;
1066+
1067+ assert . equal (
1068+ mockIstanbulHook . hookRequire . callCount ,
1069+ 1 ,
1070+ 'expected require hook to be setup'
1071+ ) ;
1072+ assert . equal (
1073+ mockIstanbulHook . hookRunInThisContext . callCount ,
1074+ 1 ,
1075+ 'expected runInThisContext hook to be setup'
1076+ ) ;
1077+ } ,
1078+
1079+ async instrumentation ( ) {
1080+ fsData [ 'foo.js' ] = 'if (foo) {}' ;
1081+ fsData [ 'bar.js' ] = 'if (bar) {}' ;
1082+ let loadResolver : ( ) => void ;
1083+ let loadRejector : ( reason : Error ) => void ;
1084+ const loadPromise = new Promise ( ( resolve , reject ) => {
1085+ loadResolver = resolve ;
1086+ loadRejector = reject ;
10641087 } ) ;
1088+
1089+ const exec = createExecutor (
1090+ {
1091+ suites : [ 'foo.js' ] ,
1092+ coverage : [ 'foo.js' , 'bar.js' ]
1093+ } ,
1094+ ( modules : string [ ] ) => {
1095+ try {
1096+ const mod = modules [ 0 ] ;
1097+
1098+ // Check that the hook matchers both say to instrument the
1099+ // module
1100+ const requireMatcher = mockIstanbulHook . hookRequire . getCall ( 0 )
1101+ . args [ 0 ] ;
1102+ const runInContextMatcher = mockIstanbulHook . hookRequire . getCall (
1103+ 0
1104+ ) . args [ 0 ] ;
1105+ assert (
1106+ requireMatcher ( mod ) ,
1107+ 'expected matcher for un-required file to return true'
1108+ ) ;
1109+ assert (
1110+ runInContextMatcher ( mod ) ,
1111+ 'expected matcher for un-required file to return true'
1112+ ) ;
1113+
1114+ // Run the require hook, which should instrument the module
1115+ const requireHook = mockIstanbulHook . hookRequire . getCall ( 0 )
1116+ . args [ 1 ] ;
1117+ requireHook ( fsData [ mod ] , { filename : mod } ) ;
1118+
1119+ // The require hook should instrument the module
1120+ assert . equal (
1121+ mockInstrumenter . instrumentSync . callCount ,
1122+ 1 ,
1123+ 'instrumenter should only have been called once'
1124+ ) ;
1125+
1126+ // Once the file is instrumented, both hook matchers shoudl
1127+ // return false so that we don't try to instrument the file
1128+ // again
1129+ assert (
1130+ ! requireMatcher ( mod ) ,
1131+ 'expected matcher for required file to return false'
1132+ ) ;
1133+ assert (
1134+ ! runInContextMatcher ( mod ) ,
1135+ 'expected matcher for required file to return false'
1136+ ) ;
1137+
1138+ loadResolver ( ) ;
1139+ } catch ( error ) {
1140+ loadRejector ( error ) ;
1141+ }
1142+
1143+ return Promise . resolve ( ) ;
1144+ }
1145+ ) ;
1146+
1147+ // Nothing should have been instrumented before the executor is run
1148+ assert . equal (
1149+ mockInstrumenter . instrumentSync . callCount ,
1150+ 0 ,
1151+ 'instrumenter should not have been called yet'
1152+ ) ;
1153+
1154+ await exec . run ( ) ;
1155+ await loadPromise ;
1156+
1157+ assert . equal (
1158+ mockIstanbulHook . hookRequire . callCount ,
1159+ 1 ,
1160+ 'expected require hook to be setup'
1161+ ) ;
1162+ assert . equal (
1163+ mockIstanbulHook . hookRunInThisContext . callCount ,
1164+ 1 ,
1165+ 'expected runInThisContext hook to be setup'
1166+ ) ;
1167+
1168+ // At the end of testing, any covered but unloaded modules should be
1169+ // instrumented, so we should see another call to instrumentSync
1170+ assert . equal (
1171+ mockInstrumenter . instrumentSync . callCount ,
1172+ 2 ,
1173+ 'instrumenter should have been called for uncovered file'
1174+ ) ;
10651175 } ,
10661176
10671177 cancel ( ) {
0 commit comments