Skip to content

Commit 0201050

Browse files
committed
test: add test for fixed instrumentation
references #1158
1 parent ec947d1 commit 0201050

File tree

1 file changed

+165
-55
lines changed

1 file changed

+165
-55
lines changed

tests/unit/lib/executors/Node.ts

Lines changed: 165 additions & 55 deletions
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,4 @@
1-
import { spy, SinonSpy, stub } from 'sinon';
1+
import { createSandbox, spy, stub, SinonSpy } from 'sinon';
22
import { Task, deepMixin, isPromiseLike } from '@theintern/common';
33

44
import { Config } from 'src/lib/common/config';
@@ -10,10 +10,15 @@ import { testProperty } from '../../../support/unit/executor';
1010
const mockRequire = intern.getPlugin<mocking.MockRequire>('mockRequire');
1111

1212
registerSuite('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

Comments
 (0)