Skip to content

Commit 997f9fe

Browse files
[Mockery replacement] - Replaced mockery with similar adapter (#968)
* [Mockery replacement] - Replaced mockery with similar adapter - Replaced Mockery test-lib since it has vulnerabilities and is no longer maintained - Replaced with self-written adapter inspired by mockery for backward compatibility with tasks. - All methods which are not used in the tasks are not implemented since they shouldn't be used separately from toolrunner. - Added unit Tests - Task-Lib version was bumped to 5.0.0-preview - The lib was fully tested with all node-based tasks from azure-pipelines-task-lib repository * [Mockery replacement] - Replaced mockery with similar adapter - Replaced Mockery test-lib since it has vulnerabilities and is no longer maintained - Replaced with self-written adapter inspired by mockery for backward compatibility with tasks. - All methods which are not used in the tasks are not implemented since they shouldn't be used separately from toolrunner. - Added unit Tests - Task-Lib version was bumped to 5.0.0-preview - The lib was fully tested with all node-based tasks from azure-pipelines-task-lib repository * [Mockery replacement] - Replaced mockery with similar adapter - Changed preview version
1 parent 44e727d commit 997f9fe

10 files changed

+598
-24
lines changed

node/lib-mocker.ts

Lines changed: 213 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,213 @@
1+
const m = require('module');
2+
3+
export interface MockOptions {
4+
useCleanCache?: boolean,
5+
warnOnReplace?: boolean,
6+
warnOnUnregistered?: boolean
7+
};
8+
9+
let registeredMocks = {};
10+
let registeredAllowables = new Set<string>();
11+
let originalLoader: Function | null = null;
12+
let originalCache: Record<string, any> = {};
13+
let options: MockOptions = {};
14+
let defaultOptions: MockOptions = {
15+
useCleanCache: false,
16+
warnOnReplace: true,
17+
warnOnUnregistered: true
18+
};
19+
20+
function _getEffectiveOptions(opts: MockOptions): MockOptions {
21+
var options: MockOptions = {};
22+
23+
Object.keys(defaultOptions).forEach(function (key) {
24+
if (opts && opts.hasOwnProperty(key)) {
25+
options[key] = opts[key];
26+
} else {
27+
options[key] = defaultOptions[key];
28+
}
29+
});
30+
return options;
31+
}
32+
33+
/*
34+
* Loader function that used when hooking is enabled.
35+
* if the requested module is registered as a mock, return the mock.
36+
* otherwise, invoke the original loader + put warning in the output.
37+
*/
38+
function _hookedLoader(request: string, parent, isMain: boolean) {
39+
if (!originalLoader) {
40+
throw new Error("Loader has not been hooked");
41+
}
42+
43+
if (registeredMocks.hasOwnProperty(request)) {
44+
return registeredMocks[request];
45+
}
46+
47+
if (!registeredAllowables.has(request) && options.warnOnUnregistered) {
48+
console.warn("WARNING: loading non-allowed module: " + request);
49+
}
50+
51+
return originalLoader(request, parent, isMain);
52+
}
53+
54+
55+
/**
56+
* Remove references to modules in the cache from
57+
* their parents' children.
58+
*/
59+
function _removeParentReferences(): void {
60+
Object.keys(m._cache).forEach(function (k) {
61+
if (k.indexOf('\.node') === -1) {
62+
// don't touch native modules, because they're special
63+
const mod = m._cache[k];
64+
const idx = mod?.parent?.children.indexOf(mod);
65+
if (idx > -1) {
66+
mod.parent.children.splice(idx, 1);
67+
}
68+
}
69+
});
70+
}
71+
72+
/*
73+
* Starting in node 0.12 node won't reload native modules
74+
* The reason is that native modules can register themselves to be loaded automatically
75+
* This will re-populate the cache with the native modules that have not been mocked
76+
*/
77+
function _repopulateNative(): void {
78+
Object.keys(originalCache).forEach(function (k) {
79+
if (k.indexOf('\.node') > -1 && !m._cache[k]) {
80+
m._cache[k] = originalCache[k];
81+
}
82+
});
83+
}
84+
85+
/*
86+
* Enable function, hooking the Node loader with options.
87+
*/
88+
export function enable(opts: MockOptions): void {
89+
if (originalLoader) {
90+
// Already hooked
91+
return;
92+
}
93+
94+
options = _getEffectiveOptions(opts);
95+
96+
if (options.useCleanCache) {
97+
originalCache = m._cache;
98+
m._cache = {};
99+
_repopulateNative();
100+
}
101+
102+
originalLoader = m._load;
103+
m._load = _hookedLoader;
104+
}
105+
106+
/*
107+
* Disables mock loading, reverting to normal 'require' behaviour.
108+
*/
109+
export function disable(): void {
110+
if (!originalLoader) return;
111+
112+
if (options.useCleanCache) {
113+
Object.keys(m._cache).forEach(function (k) {
114+
if (k.indexOf('\.node') > -1 && !originalCache[k]) {
115+
originalCache[k] = m._cache[k];
116+
}
117+
});
118+
_removeParentReferences();
119+
m._cache = originalCache;
120+
originalCache = {};
121+
}
122+
123+
m._load = originalLoader;
124+
originalLoader = null;
125+
}
126+
127+
/*
128+
* If the clean cache option is in effect, reset the module cache to an empty
129+
* state. Calling this function when the clean cache option is not in effect
130+
* will have no ill effects, but will do nothing.
131+
*/
132+
export function resetCache(): void {
133+
if (options.useCleanCache && originalCache) {
134+
_removeParentReferences();
135+
m._cache = {};
136+
_repopulateNative();
137+
}
138+
}
139+
140+
/*
141+
* Enable or disable warnings to the console when previously registered mocks are replaced.
142+
*/
143+
export function warnOnReplace(enable: boolean): void {
144+
options.warnOnReplace = enable;
145+
}
146+
147+
/*
148+
* Enable or disable warnings to the console when modules are loaded that have
149+
* not been registered as a mock.
150+
*/
151+
export function warnOnUnregistered(enable: boolean): void {
152+
options.warnOnUnregistered = enable;
153+
}
154+
155+
/*
156+
* Register a mock object for the specified module.
157+
*/
158+
export function registerMock(mod: string, mock): void {
159+
if (options.warnOnReplace && registeredMocks.hasOwnProperty(mod)) {
160+
console.warn("WARNING: Replacing existing mock for module: " + mod);
161+
}
162+
registeredMocks[mod] = mock;
163+
}
164+
165+
/*
166+
* Deregister a mock object for the specified module.
167+
*/
168+
export function deregisterMock(mod: string): void {
169+
if (registeredMocks.hasOwnProperty(mod)) {
170+
delete registeredMocks[mod];
171+
}
172+
}
173+
174+
/*
175+
* Deregister all mocks.
176+
*/
177+
export function deregisterAll(): void {
178+
registeredMocks = {};
179+
registeredAllowables = new Set();
180+
}
181+
182+
/*
183+
Register a module as 'allowed'.
184+
This will allow the module to be loaded without mock otherwise a warning would be thrown.
185+
*/
186+
export function registerAllowable(mod: string): void {
187+
registeredAllowables.add(mod);
188+
}
189+
190+
/*
191+
* Register an array of 'allowed' modules.
192+
*/
193+
export function registerAllowables(mods: string[]): void {
194+
mods.forEach((mod) => registerAllowable(mod));
195+
}
196+
197+
/*
198+
* Deregister a module as 'allowed'.
199+
*/
200+
export function deregisterAllowable(mod: string): void {
201+
if (registeredAllowables.hasOwnProperty(mod)) {
202+
registeredAllowables.delete(mod);
203+
}
204+
}
205+
206+
/*
207+
* Deregister an array of modules as 'allowed'.
208+
*/
209+
export function deregisterAllowables(mods) {
210+
mods.forEach(function (mod) {
211+
deregisterAllowable(mod);
212+
});
213+
}

node/mock-run.ts

Lines changed: 5 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,6 @@
11
import ma = require('./mock-answer');
2-
import mockery = require('mockery');
32
import im = require('./internal');
3+
import mocker = require('./lib-mocker');
44

55
export class TaskMockRunner {
66
constructor(taskPath: string) {
@@ -46,7 +46,7 @@ export class TaskMockRunner {
4646
*/
4747
public registerMock(modName: string, mod: any): void {
4848
this._moduleCount++;
49-
mockery.registerMock(modName, mod);
49+
mocker.registerMock(modName, mod);
5050
}
5151

5252
/**
@@ -69,9 +69,9 @@ export class TaskMockRunner {
6969
* @returns void
7070
*/
7171
public run(noMockTask?: boolean): void {
72-
// determine whether to enable mockery
72+
// determine whether to enable mocker
7373
if (!noMockTask || this._moduleCount) {
74-
mockery.enable({warnOnUnregistered: false});
74+
mocker.enable({warnOnUnregistered: false});
7575
}
7676

7777
// answers and exports not compatible with "noMockTask" mode
@@ -92,7 +92,7 @@ export class TaskMockRunner {
9292
tlm[key] = this._exports[key];
9393
});
9494

95-
mockery.registerMock('azure-pipelines-task-lib/task', tlm);
95+
mocker.registerMock('azure-pipelines-task-lib/task', tlm);
9696
}
9797

9898
// run it

node/package-lock.json

Lines changed: 1 addition & 12 deletions
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

node/package.json

Lines changed: 1 addition & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,6 @@
11
{
22
"name": "azure-pipelines-task-lib",
3-
"version": "4.4.0",
3+
"version": "5.0.0-preview.0",
44
"description": "Azure Pipelines Task SDK",
55
"main": "./task.js",
66
"typings": "./task.d.ts",
@@ -28,7 +28,6 @@
2828
"homepage": "https://github.com/Microsoft/azure-pipelines-task-lib",
2929
"dependencies": {
3030
"minimatch": "3.0.5",
31-
"mockery": "^2.1.0",
3231
"q": "^1.5.1",
3332
"semver": "^5.1.0",
3433
"shelljs": "^0.8.5",
@@ -38,7 +37,6 @@
3837
"devDependencies": {
3938
"@types/minimatch": "3.0.3",
4039
"@types/mocha": "^9.1.1",
41-
"@types/mockery": "^1.4.29",
4240
"@types/node": "^16.11.39",
4341
"@types/q": "^1.5.4",
4442
"@types/semver": "^7.3.4",

node/test/fakeModules/fakemodule1.ts

Lines changed: 7 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,7 @@
1+
export function testFuncLibrary() {
2+
return 'testFuncLibrary';
3+
}
4+
5+
export function otherFuncLibrary() {
6+
return 'otherFuncLibrary';
7+
}

node/test/fakeModules/fakemodule2.ts

Lines changed: 7 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,7 @@
1+
export function testFuncLibrary2() {
2+
return 'testFuncLibrary2';
3+
}
4+
5+
export function otherFuncLibrary2() {
6+
return 'otherFuncLibrary2';
7+
}

node/test/internalhelpertests.ts

Lines changed: 1 addition & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -7,7 +7,7 @@ import * as path from 'path';
77
import * as testutil from './testutil';
88
import * as tl from '../_build/task';
99
import * as im from '../_build/internal';
10-
import * as mockery from 'mockery'
10+
import * as mockery from '../_build/lib-mocker'
1111

1212
describe('Internal Path Helper Tests', function () {
1313

@@ -404,7 +404,6 @@ describe('Internal Path Helper Tests', function () {
404404
});
405405

406406
it('ReportMissingStrings', (done) => {
407-
408407
mockery.registerAllowable('../_build/internal')
409408
const fsMock = {
410409
statSync: function (path) { return null; }

0 commit comments

Comments
 (0)