@jsenv/snapshot 2.4.3 → 2.5.1
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/package.json +1 -1
- package/src/function_side_effects/function_side_effects_snapshot.js +23 -22
- package/src/function_side_effects/hook_into_method.js +261 -0
- package/src/function_side_effects/spy_console_calls.js +41 -31
- package/src/function_side_effects/spy_filesystem_calls.js +69 -131
- package/src/function_side_effects/spy_method.js +0 -139
package/package.json
CHANGED
|
@@ -2,10 +2,12 @@ import { writeFileSync } from "@jsenv/filesystem";
|
|
|
2
2
|
import {
|
|
3
3
|
ensurePathnameTrailingSlash,
|
|
4
4
|
urlToExtension,
|
|
5
|
-
urlToFilename,
|
|
6
5
|
urlToRelativeUrl,
|
|
7
6
|
} from "@jsenv/urls";
|
|
8
|
-
import {
|
|
7
|
+
import {
|
|
8
|
+
takeDirectorySnapshot,
|
|
9
|
+
takeFileSnapshot,
|
|
10
|
+
} from "../filesystem_snapshot.js";
|
|
9
11
|
import { replaceFluctuatingValues } from "../replace_fluctuating_values.js";
|
|
10
12
|
import { collectFunctionSideEffects } from "./function_side_effects_collector.js";
|
|
11
13
|
import {
|
|
@@ -26,9 +28,8 @@ const consoleEffectsDefault = {
|
|
|
26
28
|
export const snapshotFunctionSideEffects = (
|
|
27
29
|
fn,
|
|
28
30
|
fnFileUrl,
|
|
29
|
-
|
|
31
|
+
sideEffectFileRelativeUrl,
|
|
30
32
|
{
|
|
31
|
-
sideEffectFileBasename,
|
|
32
33
|
rootDirectoryUrl = new URL("./", fnFileUrl),
|
|
33
34
|
consoleEffects = true,
|
|
34
35
|
filesystemEffects = true,
|
|
@@ -40,18 +41,8 @@ export const snapshotFunctionSideEffects = (
|
|
|
40
41
|
if (filesystemEffects === true) {
|
|
41
42
|
filesystemEffects = {};
|
|
42
43
|
}
|
|
43
|
-
const
|
|
44
|
-
|
|
45
|
-
fnFileUrl,
|
|
46
|
-
);
|
|
47
|
-
const sideEffectDirectorySnapshot = takeDirectorySnapshot(
|
|
48
|
-
sideEffectDirectoryUrl,
|
|
49
|
-
);
|
|
50
|
-
if (sideEffectFileBasename === undefined) {
|
|
51
|
-
sideEffectFileBasename = `${urlToFilename(sideEffectDirectoryUrl)}_side_effects`;
|
|
52
|
-
}
|
|
53
|
-
const sideEffectFilename = `${sideEffectFileBasename}.md`;
|
|
54
|
-
const sideEffectFileUrl = new URL(sideEffectFilename, sideEffectDirectoryUrl);
|
|
44
|
+
const sideEffectFileUrl = new URL(sideEffectFileRelativeUrl, fnFileUrl);
|
|
45
|
+
const sideEffectFileSnapshot = takeFileSnapshot(sideEffectFileUrl);
|
|
55
46
|
const callbackSet = new Set();
|
|
56
47
|
const sideEffectDetectors = [
|
|
57
48
|
...(consoleEffects
|
|
@@ -114,16 +105,27 @@ export const snapshotFunctionSideEffects = (
|
|
|
114
105
|
const { preserve, outDirectory } = filesystemEffects;
|
|
115
106
|
if (outDirectory) {
|
|
116
107
|
const fsEffectsOutDirectoryUrl = ensurePathnameTrailingSlash(
|
|
117
|
-
new URL(outDirectory,
|
|
108
|
+
new URL(outDirectory, sideEffectFileUrl),
|
|
109
|
+
);
|
|
110
|
+
const fsEffectsOutDirectorySnapshot = takeDirectorySnapshot(
|
|
111
|
+
fsEffectsOutDirectoryUrl,
|
|
118
112
|
);
|
|
119
113
|
const fsEffectsOutDirectoryRelativeUrl = urlToRelativeUrl(
|
|
120
114
|
fsEffectsOutDirectoryUrl,
|
|
121
115
|
sideEffectFileUrl,
|
|
122
116
|
);
|
|
117
|
+
const writeFileCallbackSet = new Set();
|
|
118
|
+
callbackSet.add(() => {
|
|
119
|
+
for (const writeFileCallback of writeFileCallbackSet) {
|
|
120
|
+
writeFileCallback();
|
|
121
|
+
}
|
|
122
|
+
writeFileCallbackSet.clear();
|
|
123
|
+
fsEffectsOutDirectorySnapshot.compare();
|
|
124
|
+
});
|
|
123
125
|
writeFile = (url, content) => {
|
|
124
126
|
const relativeUrl = urlToRelativeUrl(url, fnFileUrl);
|
|
125
127
|
const toUrl = new URL(relativeUrl, fsEffectsOutDirectoryUrl);
|
|
126
|
-
|
|
128
|
+
writeFileCallbackSet.add(() => {
|
|
127
129
|
writeFileSync(toUrl, content);
|
|
128
130
|
});
|
|
129
131
|
addSideEffect({
|
|
@@ -172,16 +174,15 @@ export const snapshotFunctionSideEffects = (
|
|
|
172
174
|
]
|
|
173
175
|
: []),
|
|
174
176
|
];
|
|
175
|
-
|
|
176
177
|
const onSideEffectsCollected = (sideEffects) => {
|
|
177
178
|
for (const callback of callbackSet) {
|
|
178
179
|
callback();
|
|
179
180
|
}
|
|
180
181
|
callbackSet.clear();
|
|
181
|
-
|
|
182
|
-
|
|
182
|
+
sideEffectFileSnapshot.update(renderSideEffects(sideEffects), {
|
|
183
|
+
mockFluctuatingValues: false,
|
|
184
|
+
});
|
|
183
185
|
};
|
|
184
|
-
|
|
185
186
|
const returnValue = collectFunctionSideEffects(fn, sideEffectDetectors, {
|
|
186
187
|
rootDirectoryUrl,
|
|
187
188
|
});
|
|
@@ -0,0 +1,261 @@
|
|
|
1
|
+
const jsenvMethodProxySymbol = Symbol.for("jsenv_method_proxy");
|
|
2
|
+
|
|
3
|
+
export const hookIntoMethod = (
|
|
4
|
+
object,
|
|
5
|
+
method,
|
|
6
|
+
initCallback,
|
|
7
|
+
{ execute = METHOD_EXECUTION_STANDARD } = {},
|
|
8
|
+
) => {
|
|
9
|
+
const current = object[method];
|
|
10
|
+
const jsenvSymbolValue = current[jsenvMethodProxySymbol];
|
|
11
|
+
let addInitCallback;
|
|
12
|
+
let removeInitCallback;
|
|
13
|
+
if (jsenvSymbolValue) {
|
|
14
|
+
addInitCallback = jsenvSymbolValue.addInitCallback;
|
|
15
|
+
removeInitCallback = jsenvSymbolValue.removeInitCallback;
|
|
16
|
+
} else {
|
|
17
|
+
const original = current;
|
|
18
|
+
let allWantsToPreventOriginalCall;
|
|
19
|
+
let hookExecuting;
|
|
20
|
+
const initCallbackSet = new Set();
|
|
21
|
+
const callHooks = (hookCallbackSet, ...args) => {
|
|
22
|
+
hookExecuting = true;
|
|
23
|
+
for (const hookCallback of hookCallbackSet) {
|
|
24
|
+
hookCallback(...args);
|
|
25
|
+
}
|
|
26
|
+
hookExecuting = false;
|
|
27
|
+
hookCallbackSet.clear();
|
|
28
|
+
};
|
|
29
|
+
|
|
30
|
+
const proxy = function (...args) {
|
|
31
|
+
if (hookExecuting) {
|
|
32
|
+
// when a spy is executing
|
|
33
|
+
// if it calls the method himself
|
|
34
|
+
// then we want this call to go trough
|
|
35
|
+
// and others spy should not know about it
|
|
36
|
+
return original.call(this, ...args);
|
|
37
|
+
}
|
|
38
|
+
allWantsToPreventOriginalCall = undefined;
|
|
39
|
+
const returnPromiseCallbackSet = new Set();
|
|
40
|
+
const returnCallbackSet = new Set();
|
|
41
|
+
const catchCallbackSet = new Set();
|
|
42
|
+
const finallyCallbackSet = new Set();
|
|
43
|
+
hookExecuting = true;
|
|
44
|
+
for (const initCallback of initCallbackSet) {
|
|
45
|
+
if (initCallback.disabled) {
|
|
46
|
+
continue;
|
|
47
|
+
}
|
|
48
|
+
const hooks = initCallback(...args) || {};
|
|
49
|
+
if (hooks.preventOriginalCall) {
|
|
50
|
+
if (allWantsToPreventOriginalCall === undefined) {
|
|
51
|
+
allWantsToPreventOriginalCall = true;
|
|
52
|
+
}
|
|
53
|
+
} else {
|
|
54
|
+
allWantsToPreventOriginalCall = false;
|
|
55
|
+
}
|
|
56
|
+
if (hooks.returnPromise) {
|
|
57
|
+
returnPromiseCallbackSet.add(hooks.returnPromise);
|
|
58
|
+
}
|
|
59
|
+
if (hooks.return) {
|
|
60
|
+
returnCallbackSet.add(hooks.return);
|
|
61
|
+
}
|
|
62
|
+
if (hooks.catch) {
|
|
63
|
+
catchCallbackSet.add(hooks.catch);
|
|
64
|
+
}
|
|
65
|
+
if (hooks.finally) {
|
|
66
|
+
finallyCallbackSet.add(hooks.catch);
|
|
67
|
+
}
|
|
68
|
+
}
|
|
69
|
+
hookExecuting = false;
|
|
70
|
+
const onCatch = (valueThrown) => {
|
|
71
|
+
returnCallbackSet.clear();
|
|
72
|
+
callHooks(catchCallbackSet, valueThrown);
|
|
73
|
+
};
|
|
74
|
+
const onReturn = (...values) => {
|
|
75
|
+
returnPromiseCallbackSet.clear();
|
|
76
|
+
catchCallbackSet.clear();
|
|
77
|
+
callHooks(returnCallbackSet, ...values);
|
|
78
|
+
};
|
|
79
|
+
const onReturnPromise = () => {
|
|
80
|
+
callHooks(returnPromiseCallbackSet);
|
|
81
|
+
};
|
|
82
|
+
const onFinally = () => {
|
|
83
|
+
callHooks(finallyCallbackSet);
|
|
84
|
+
};
|
|
85
|
+
if (allWantsToPreventOriginalCall) {
|
|
86
|
+
onReturn(undefined);
|
|
87
|
+
onFinally();
|
|
88
|
+
return undefined;
|
|
89
|
+
}
|
|
90
|
+
return execute({
|
|
91
|
+
original,
|
|
92
|
+
thisValue: this,
|
|
93
|
+
args,
|
|
94
|
+
onCatch,
|
|
95
|
+
onReturn,
|
|
96
|
+
onReturnPromise,
|
|
97
|
+
onFinally,
|
|
98
|
+
});
|
|
99
|
+
};
|
|
100
|
+
addInitCallback = (initCallback) => {
|
|
101
|
+
if (initCallbackSet.size === 0) {
|
|
102
|
+
object[method] = proxy;
|
|
103
|
+
}
|
|
104
|
+
initCallbackSet.add(initCallback);
|
|
105
|
+
};
|
|
106
|
+
removeInitCallback = (initCallback) => {
|
|
107
|
+
initCallbackSet.delete(initCallback);
|
|
108
|
+
if (initCallbackSet.size === 0) {
|
|
109
|
+
object[method] = original;
|
|
110
|
+
}
|
|
111
|
+
};
|
|
112
|
+
proxy[jsenvMethodProxySymbol] = {
|
|
113
|
+
addInitCallback,
|
|
114
|
+
removeInitCallback,
|
|
115
|
+
original,
|
|
116
|
+
};
|
|
117
|
+
object[method] = proxy;
|
|
118
|
+
}
|
|
119
|
+
addInitCallback(initCallback);
|
|
120
|
+
const hook = {
|
|
121
|
+
disable: () => {
|
|
122
|
+
initCallback.disabled = true;
|
|
123
|
+
},
|
|
124
|
+
enable: () => {
|
|
125
|
+
initCallback.disabled = false;
|
|
126
|
+
},
|
|
127
|
+
remove: () => {
|
|
128
|
+
removeInitCallback(initCallback);
|
|
129
|
+
},
|
|
130
|
+
};
|
|
131
|
+
return hook;
|
|
132
|
+
};
|
|
133
|
+
export const METHOD_EXECUTION_STANDARD = ({
|
|
134
|
+
original,
|
|
135
|
+
thisValue,
|
|
136
|
+
args,
|
|
137
|
+
onCatch,
|
|
138
|
+
onFinally,
|
|
139
|
+
onReturnPromise,
|
|
140
|
+
onReturn,
|
|
141
|
+
}) => {
|
|
142
|
+
let valueReturned;
|
|
143
|
+
let thrown = false;
|
|
144
|
+
let valueThrown;
|
|
145
|
+
try {
|
|
146
|
+
valueReturned = original.call(thisValue, ...args);
|
|
147
|
+
} catch (e) {
|
|
148
|
+
thrown = true;
|
|
149
|
+
valueThrown = e;
|
|
150
|
+
}
|
|
151
|
+
if (thrown) {
|
|
152
|
+
onCatch(valueThrown);
|
|
153
|
+
onFinally();
|
|
154
|
+
throw valueThrown;
|
|
155
|
+
}
|
|
156
|
+
if (valueReturned && typeof valueReturned.then === "function") {
|
|
157
|
+
onReturnPromise();
|
|
158
|
+
valueReturned.then(
|
|
159
|
+
(valueResolved) => {
|
|
160
|
+
onReturn(valueResolved);
|
|
161
|
+
onFinally();
|
|
162
|
+
},
|
|
163
|
+
(valueRejected) => {
|
|
164
|
+
onCatch(valueRejected);
|
|
165
|
+
onFinally();
|
|
166
|
+
},
|
|
167
|
+
);
|
|
168
|
+
return valueReturned;
|
|
169
|
+
}
|
|
170
|
+
onReturn(valueReturned);
|
|
171
|
+
onFinally();
|
|
172
|
+
return valueReturned;
|
|
173
|
+
};
|
|
174
|
+
export const METHOD_EXECUTION_NODE_CALLBACK = ({
|
|
175
|
+
original,
|
|
176
|
+
thisValue,
|
|
177
|
+
args,
|
|
178
|
+
onCatch,
|
|
179
|
+
onFinally,
|
|
180
|
+
onReturnPromise,
|
|
181
|
+
onReturn,
|
|
182
|
+
}) => {
|
|
183
|
+
const lastArgIndex = args.length - 1;
|
|
184
|
+
const lastArg = args[lastArgIndex];
|
|
185
|
+
const installProxyAndCall = (originalCallback, installCallbackProxy) => {
|
|
186
|
+
const callbackProxy = function (...callbackArgs) {
|
|
187
|
+
uninstallCallbackProxy();
|
|
188
|
+
const [error, ...remainingArgs] = callbackArgs;
|
|
189
|
+
if (error) {
|
|
190
|
+
onCatch(error);
|
|
191
|
+
originalCallback.call(this, ...callbackArgs);
|
|
192
|
+
onFinally();
|
|
193
|
+
return;
|
|
194
|
+
}
|
|
195
|
+
onReturn(...remainingArgs);
|
|
196
|
+
originalCallback.call(this, ...callbackArgs);
|
|
197
|
+
onFinally();
|
|
198
|
+
};
|
|
199
|
+
const uninstallCallbackProxy = installCallbackProxy(callbackProxy);
|
|
200
|
+
try {
|
|
201
|
+
return original.call(thisValue, ...args);
|
|
202
|
+
} catch (e) {
|
|
203
|
+
onCatch(e);
|
|
204
|
+
onFinally();
|
|
205
|
+
throw e;
|
|
206
|
+
}
|
|
207
|
+
};
|
|
208
|
+
if (typeof lastArg === "function") {
|
|
209
|
+
return installProxyAndCall(lastArg, (callbackProxy) => {
|
|
210
|
+
args[lastArgIndex] = callbackProxy;
|
|
211
|
+
return () => {
|
|
212
|
+
// useless because are a copy of the args
|
|
213
|
+
// so the mutation we do above ( args[lastArgIndex] =)
|
|
214
|
+
// cannot be important for the method being proxied
|
|
215
|
+
args[lastArgIndex] = lastArg;
|
|
216
|
+
};
|
|
217
|
+
});
|
|
218
|
+
}
|
|
219
|
+
if (lastArg && typeof lastArg === "object") {
|
|
220
|
+
if (lastArg.context && typeof lastArg.context.callback === "function") {
|
|
221
|
+
const originalCallback = lastArg.context.callback;
|
|
222
|
+
return installProxyAndCall(originalCallback, (callbackProxy) => {
|
|
223
|
+
lastArg.context.callback = callbackProxy;
|
|
224
|
+
return () => {
|
|
225
|
+
lastArg.context.callback = originalCallback;
|
|
226
|
+
};
|
|
227
|
+
});
|
|
228
|
+
}
|
|
229
|
+
if (typeof lastArg.oncomplete === "function") {
|
|
230
|
+
const originalCallback = lastArg.oncomplete;
|
|
231
|
+
return installProxyAndCall(originalCallback, (callbackProxy) => {
|
|
232
|
+
lastArg.oncomplete = callbackProxy;
|
|
233
|
+
return () => {
|
|
234
|
+
lastArg.oncomplete = originalCallback;
|
|
235
|
+
};
|
|
236
|
+
});
|
|
237
|
+
}
|
|
238
|
+
}
|
|
239
|
+
return METHOD_EXECUTION_STANDARD({
|
|
240
|
+
original,
|
|
241
|
+
thisValue,
|
|
242
|
+
args,
|
|
243
|
+
onCatch,
|
|
244
|
+
onFinally,
|
|
245
|
+
onReturnPromise,
|
|
246
|
+
onReturn,
|
|
247
|
+
});
|
|
248
|
+
};
|
|
249
|
+
|
|
250
|
+
export const disableHooksWhileCalling = (fn, hookToDisableArray) => {
|
|
251
|
+
for (const toDisable of hookToDisableArray) {
|
|
252
|
+
toDisable.disable();
|
|
253
|
+
}
|
|
254
|
+
try {
|
|
255
|
+
return fn();
|
|
256
|
+
} finally {
|
|
257
|
+
for (const toEnable of hookToDisableArray) {
|
|
258
|
+
toEnable.enable();
|
|
259
|
+
}
|
|
260
|
+
}
|
|
261
|
+
};
|
|
@@ -1,46 +1,56 @@
|
|
|
1
|
-
import {
|
|
1
|
+
import { hookIntoMethod } from "./hook_into_method.js";
|
|
2
2
|
|
|
3
3
|
export const spyConsoleCalls = (
|
|
4
4
|
{ error, warn, info, log, trace },
|
|
5
5
|
{ preventConsoleSideEffects },
|
|
6
6
|
) => {
|
|
7
7
|
const restoreCallbackSet = new Set();
|
|
8
|
-
const
|
|
9
|
-
|
|
10
|
-
|
|
11
|
-
|
|
12
|
-
|
|
8
|
+
const errorHook = hookIntoMethod(console, "error", (message) => {
|
|
9
|
+
return {
|
|
10
|
+
preventOriginalCall: preventConsoleSideEffects,
|
|
11
|
+
return: () => {
|
|
12
|
+
error(message);
|
|
13
|
+
},
|
|
14
|
+
};
|
|
13
15
|
});
|
|
14
|
-
const
|
|
15
|
-
|
|
16
|
-
|
|
17
|
-
|
|
18
|
-
|
|
16
|
+
const warnHook = hookIntoMethod(console, "warn", (message) => {
|
|
17
|
+
return {
|
|
18
|
+
preventOriginalCall: preventConsoleSideEffects,
|
|
19
|
+
return: () => {
|
|
20
|
+
warn(message);
|
|
21
|
+
},
|
|
22
|
+
};
|
|
19
23
|
});
|
|
20
|
-
const
|
|
21
|
-
|
|
22
|
-
|
|
23
|
-
|
|
24
|
-
|
|
24
|
+
const infoHook = hookIntoMethod(console, "info", (message) => {
|
|
25
|
+
return {
|
|
26
|
+
preventOriginalCall: preventConsoleSideEffects,
|
|
27
|
+
return: () => {
|
|
28
|
+
info(message);
|
|
29
|
+
},
|
|
30
|
+
};
|
|
25
31
|
});
|
|
26
|
-
const
|
|
27
|
-
|
|
28
|
-
|
|
29
|
-
|
|
30
|
-
|
|
32
|
+
const logHook = hookIntoMethod(console, "log", (message) => {
|
|
33
|
+
return {
|
|
34
|
+
preventOriginalCall: preventConsoleSideEffects,
|
|
35
|
+
return: () => {
|
|
36
|
+
log(message);
|
|
37
|
+
},
|
|
38
|
+
};
|
|
31
39
|
});
|
|
32
|
-
const
|
|
33
|
-
|
|
34
|
-
|
|
35
|
-
|
|
36
|
-
|
|
40
|
+
const traceHook = hookIntoMethod(console, "trace", (message) => {
|
|
41
|
+
return {
|
|
42
|
+
preventOriginalCall: preventConsoleSideEffects,
|
|
43
|
+
return: () => {
|
|
44
|
+
trace(message);
|
|
45
|
+
},
|
|
46
|
+
};
|
|
37
47
|
});
|
|
38
48
|
restoreCallbackSet.add(() => {
|
|
39
|
-
|
|
40
|
-
|
|
41
|
-
|
|
42
|
-
|
|
43
|
-
|
|
49
|
+
errorHook.remove();
|
|
50
|
+
warnHook.remove();
|
|
51
|
+
infoHook.remove();
|
|
52
|
+
logHook.remove();
|
|
53
|
+
traceHook.remove();
|
|
44
54
|
});
|
|
45
55
|
return {
|
|
46
56
|
restore: () => {
|
|
@@ -6,7 +6,11 @@
|
|
|
6
6
|
import { removeDirectorySync, removeFileSync } from "@jsenv/filesystem";
|
|
7
7
|
import { readFileSync, statSync, writeFileSync } from "node:fs";
|
|
8
8
|
import { pathToFileURL } from "node:url";
|
|
9
|
-
import {
|
|
9
|
+
import {
|
|
10
|
+
disableHooksWhileCalling,
|
|
11
|
+
hookIntoMethod,
|
|
12
|
+
METHOD_EXECUTION_NODE_CALLBACK,
|
|
13
|
+
} from "./hook_into_method.js";
|
|
10
14
|
|
|
11
15
|
export const spyFilesystemCalls = (
|
|
12
16
|
{
|
|
@@ -65,167 +69,101 @@ export const spyFilesystemCalls = (
|
|
|
65
69
|
};
|
|
66
70
|
const restoreCallbackSet = new Set();
|
|
67
71
|
|
|
68
|
-
const
|
|
69
|
-
return
|
|
72
|
+
const getFileStateWithinHook = (fileUrl) => {
|
|
73
|
+
return disableHooksWhileCalling(
|
|
70
74
|
() => getFileState(fileUrl),
|
|
71
|
-
[
|
|
75
|
+
[openHook, closeHook],
|
|
72
76
|
);
|
|
73
77
|
};
|
|
74
78
|
|
|
75
|
-
const
|
|
79
|
+
const mkdirHook = hookIntoMethod(
|
|
76
80
|
_internalFs,
|
|
77
81
|
"mkdir",
|
|
78
|
-
(directoryPath
|
|
82
|
+
(directoryPath) => {
|
|
79
83
|
const directoryUrl = pathToFileURL(directoryPath);
|
|
80
|
-
if (callback) {
|
|
81
|
-
const stateBefore = getDirectoryState(directoryPath);
|
|
82
|
-
const oncomplete = callback.oncomplete;
|
|
83
|
-
callback.oncomplete = (error, fd) => {
|
|
84
|
-
if (error) {
|
|
85
|
-
oncomplete.call(callback, error);
|
|
86
|
-
} else {
|
|
87
|
-
fileDescriptorPathMap.set(fd, directoryPath);
|
|
88
|
-
oncomplete.call(callback);
|
|
89
|
-
onWriteDirectoryDone(directoryUrl, stateBefore, { found: true });
|
|
90
|
-
}
|
|
91
|
-
};
|
|
92
|
-
return mkdirSpy.callOriginal();
|
|
93
|
-
}
|
|
94
84
|
const stateBefore = getDirectoryState(directoryPath);
|
|
95
|
-
|
|
96
|
-
|
|
97
|
-
|
|
85
|
+
return {
|
|
86
|
+
return: (fd) => {
|
|
87
|
+
fileDescriptorPathMap.set(fd, directoryPath);
|
|
88
|
+
onWriteDirectoryDone(directoryUrl, stateBefore, { found: true });
|
|
89
|
+
},
|
|
90
|
+
};
|
|
98
91
|
},
|
|
92
|
+
{ execute: METHOD_EXECUTION_NODE_CALLBACK },
|
|
99
93
|
);
|
|
100
|
-
const
|
|
94
|
+
const openHook = hookIntoMethod(
|
|
101
95
|
_internalFs,
|
|
102
96
|
"open",
|
|
103
|
-
(filePath
|
|
104
|
-
const stateBefore =
|
|
97
|
+
(filePath) => {
|
|
98
|
+
const stateBefore = getFileStateWithinHook(filePath);
|
|
105
99
|
filesystemStateInfoMap.set(filePath, stateBefore);
|
|
106
|
-
|
|
107
|
-
|
|
108
|
-
|
|
109
|
-
callback.context.callback = function (...args) {
|
|
110
|
-
callback.context.callback = original;
|
|
111
|
-
const [error, fd] = args;
|
|
112
|
-
if (error) {
|
|
113
|
-
original.call(this, ...args);
|
|
114
|
-
} else {
|
|
115
|
-
if (typeof fd === "number") {
|
|
116
|
-
fileDescriptorPathMap.set(fd, filePath);
|
|
117
|
-
} else {
|
|
118
|
-
// it's a buffer (happens for readFile)
|
|
119
|
-
}
|
|
120
|
-
original.call(this, ...args);
|
|
121
|
-
}
|
|
122
|
-
};
|
|
123
|
-
openSpy.callOriginal();
|
|
124
|
-
return;
|
|
125
|
-
}
|
|
126
|
-
const original = callback.oncomplete;
|
|
127
|
-
callback.oncomplete = function (...args) {
|
|
128
|
-
callback.oncomplete = original;
|
|
129
|
-
const [error, fd] = args;
|
|
130
|
-
if (error) {
|
|
131
|
-
original.call(this, ...args);
|
|
132
|
-
} else {
|
|
100
|
+
return {
|
|
101
|
+
return: (fd) => {
|
|
102
|
+
if (typeof fd === "number") {
|
|
133
103
|
fileDescriptorPathMap.set(fd, filePath);
|
|
134
|
-
|
|
104
|
+
} else {
|
|
105
|
+
// it's a buffer (happens for readFile)
|
|
135
106
|
}
|
|
136
|
-
}
|
|
137
|
-
|
|
138
|
-
return;
|
|
139
|
-
}
|
|
140
|
-
const fd = openSpy.callOriginal();
|
|
141
|
-
if (typeof fd === "number") {
|
|
142
|
-
fileDescriptorPathMap.set(fd, filePath);
|
|
143
|
-
} else {
|
|
144
|
-
// it's a buffer (happens for readFile)
|
|
145
|
-
}
|
|
107
|
+
},
|
|
108
|
+
};
|
|
146
109
|
},
|
|
110
|
+
{ execute: METHOD_EXECUTION_NODE_CALLBACK },
|
|
147
111
|
);
|
|
148
|
-
const
|
|
112
|
+
const closeHook = hookIntoMethod(
|
|
149
113
|
_internalFs,
|
|
150
114
|
"close",
|
|
151
|
-
(fileDescriptor
|
|
152
|
-
|
|
153
|
-
|
|
154
|
-
|
|
155
|
-
|
|
156
|
-
|
|
157
|
-
|
|
158
|
-
|
|
159
|
-
|
|
160
|
-
fileDescriptorPathMap.delete(fileDescriptor);
|
|
161
|
-
return;
|
|
162
|
-
}
|
|
163
|
-
const fileUrl = pathToFileURL(filePath);
|
|
164
|
-
if (callback) {
|
|
165
|
-
if (callback.context) {
|
|
166
|
-
const original = callback.context.callback;
|
|
167
|
-
callback.context.callback = function (...args) {
|
|
168
|
-
callback.context.callback = original;
|
|
169
|
-
const [error] = args;
|
|
170
|
-
if (error) {
|
|
171
|
-
original.call(this, ...args);
|
|
172
|
-
} else {
|
|
173
|
-
original.call(this, ...args);
|
|
174
|
-
fileDescriptorPathMap.delete(fileDescriptor);
|
|
175
|
-
filesystemStateInfoMap.delete(filePath);
|
|
176
|
-
const stateAfter = getFileStateWithinSpy(fileUrl);
|
|
177
|
-
readFile(fileUrl);
|
|
178
|
-
onWriteFileDone(fileUrl, stateBefore, stateAfter);
|
|
179
|
-
}
|
|
180
|
-
};
|
|
181
|
-
closeSpy.callOriginal();
|
|
182
|
-
return;
|
|
183
|
-
}
|
|
184
|
-
const original = callback.oncomplete;
|
|
185
|
-
callback.oncomplete = function (...args) {
|
|
186
|
-
callback.oncomplete = original;
|
|
187
|
-
const [error] = args;
|
|
188
|
-
if (error) {
|
|
189
|
-
original.call(this, ...args);
|
|
190
|
-
} else {
|
|
191
|
-
original.call(this, ...args);
|
|
115
|
+
(fileDescriptor) => {
|
|
116
|
+
return {
|
|
117
|
+
return: (buffer) => {
|
|
118
|
+
const filePath = fileDescriptorPathMap.get(fileDescriptor);
|
|
119
|
+
if (!filePath) {
|
|
120
|
+
return;
|
|
121
|
+
}
|
|
122
|
+
const stateBefore = filesystemStateInfoMap.get(filePath);
|
|
123
|
+
if (!stateBefore) {
|
|
192
124
|
fileDescriptorPathMap.delete(fileDescriptor);
|
|
193
|
-
|
|
194
|
-
const stateAfter = getFileStateWithinSpy(fileUrl);
|
|
195
|
-
onWriteFileDone(fileUrl, stateBefore, stateAfter);
|
|
125
|
+
return;
|
|
196
126
|
}
|
|
197
|
-
|
|
198
|
-
|
|
199
|
-
|
|
200
|
-
|
|
201
|
-
|
|
202
|
-
|
|
203
|
-
|
|
204
|
-
|
|
205
|
-
|
|
127
|
+
const fileUrl = pathToFileURL(filePath);
|
|
128
|
+
if (buffer) {
|
|
129
|
+
readFile(fileUrl);
|
|
130
|
+
}
|
|
131
|
+
fileDescriptorPathMap.delete(fileDescriptor);
|
|
132
|
+
filesystemStateInfoMap.delete(filePath);
|
|
133
|
+
const stateAfter = getFileStateWithinHook(fileUrl);
|
|
134
|
+
onWriteFileDone(fileUrl, stateBefore, stateAfter);
|
|
135
|
+
},
|
|
136
|
+
};
|
|
206
137
|
},
|
|
138
|
+
{ execute: METHOD_EXECUTION_NODE_CALLBACK },
|
|
207
139
|
);
|
|
208
|
-
const
|
|
140
|
+
const writeFileUtf8Hook = hookIntoMethod(
|
|
209
141
|
_internalFs,
|
|
210
142
|
"writeFileUtf8",
|
|
211
143
|
(filePath) => {
|
|
212
144
|
const fileUrl = pathToFileURL(filePath);
|
|
213
|
-
const stateBefore =
|
|
214
|
-
|
|
215
|
-
|
|
216
|
-
|
|
145
|
+
const stateBefore = getFileStateWithinHook(fileUrl);
|
|
146
|
+
return {
|
|
147
|
+
return: () => {
|
|
148
|
+
const stateAfter = getFileStateWithinHook(fileUrl);
|
|
149
|
+
onWriteFileDone(fileUrl, stateBefore, stateAfter);
|
|
150
|
+
},
|
|
151
|
+
};
|
|
217
152
|
},
|
|
218
153
|
);
|
|
219
|
-
const
|
|
220
|
-
|
|
221
|
-
|
|
154
|
+
const unlinkHook = hookIntoMethod(_internalFs, "unlink", (filePath) => {
|
|
155
|
+
return {
|
|
156
|
+
return: () => {
|
|
157
|
+
removeFile(filePath); // TODO eventually split in removeFile/removeDirectory
|
|
158
|
+
},
|
|
159
|
+
};
|
|
222
160
|
});
|
|
223
161
|
restoreCallbackSet.add(() => {
|
|
224
|
-
|
|
225
|
-
|
|
226
|
-
|
|
227
|
-
|
|
228
|
-
|
|
162
|
+
mkdirHook.remove();
|
|
163
|
+
openHook.remove();
|
|
164
|
+
closeHook.remove();
|
|
165
|
+
writeFileUtf8Hook.remove();
|
|
166
|
+
unlinkHook.remove();
|
|
229
167
|
});
|
|
230
168
|
return {
|
|
231
169
|
restore: () => {
|
|
@@ -1,139 +0,0 @@
|
|
|
1
|
-
const jsenvSpySymbol = Symbol.for("jsenv_spy");
|
|
2
|
-
|
|
3
|
-
export const spyMethod = (object, method, spyCallback) => {
|
|
4
|
-
const current = object[method];
|
|
5
|
-
const jsenvSpySymbolValue = current[jsenvSpySymbol];
|
|
6
|
-
let addCallback;
|
|
7
|
-
let removeCallback;
|
|
8
|
-
let callOriginal;
|
|
9
|
-
let onOriginalCall;
|
|
10
|
-
let preventOriginalCall;
|
|
11
|
-
if (jsenvSpySymbolValue) {
|
|
12
|
-
addCallback = jsenvSpySymbolValue.addCallback;
|
|
13
|
-
removeCallback = jsenvSpySymbolValue.removeCallback;
|
|
14
|
-
callOriginal = jsenvSpySymbolValue.callOriginal;
|
|
15
|
-
onOriginalCall = jsenvSpySymbolValue.onOriginalCall;
|
|
16
|
-
preventOriginalCall = jsenvSpySymbolValue.preventOriginalCall;
|
|
17
|
-
} else {
|
|
18
|
-
const original = current;
|
|
19
|
-
let currentThis;
|
|
20
|
-
let currentArgs;
|
|
21
|
-
let originalCalled = false;
|
|
22
|
-
let originalReturnValue;
|
|
23
|
-
let preventOriginalCallCalled;
|
|
24
|
-
let spyExecuting;
|
|
25
|
-
let someSpyUsedCallOriginal;
|
|
26
|
-
let allSpyUsedPreventOriginalCall;
|
|
27
|
-
onOriginalCall = (returnValue) => {
|
|
28
|
-
originalCalled = true;
|
|
29
|
-
originalReturnValue = returnValue;
|
|
30
|
-
};
|
|
31
|
-
callOriginal = () => {
|
|
32
|
-
if (someSpyUsedCallOriginal) {
|
|
33
|
-
return originalReturnValue;
|
|
34
|
-
}
|
|
35
|
-
someSpyUsedCallOriginal = true;
|
|
36
|
-
onOriginalCall(original.call(currentThis, ...currentArgs));
|
|
37
|
-
return originalReturnValue;
|
|
38
|
-
};
|
|
39
|
-
preventOriginalCall = () => {
|
|
40
|
-
preventOriginalCallCalled = true;
|
|
41
|
-
};
|
|
42
|
-
const spyCallbackSet = new Set();
|
|
43
|
-
const spy = function (...args) {
|
|
44
|
-
if (spyExecuting) {
|
|
45
|
-
// when a spy is executing
|
|
46
|
-
// if it calls the method himself
|
|
47
|
-
// then we want this call to go trough
|
|
48
|
-
// and others spy should not know about it
|
|
49
|
-
onOriginalCall(original.call(this, ...args));
|
|
50
|
-
return originalReturnValue;
|
|
51
|
-
}
|
|
52
|
-
spyExecuting = true;
|
|
53
|
-
originalCalled = false;
|
|
54
|
-
currentThis = this;
|
|
55
|
-
currentArgs = args;
|
|
56
|
-
someSpyUsedCallOriginal = false;
|
|
57
|
-
allSpyUsedPreventOriginalCall = undefined;
|
|
58
|
-
for (const spyCallback of spyCallbackSet) {
|
|
59
|
-
if (spyCallback.disabled) {
|
|
60
|
-
continue;
|
|
61
|
-
}
|
|
62
|
-
try {
|
|
63
|
-
spyCallback(...args);
|
|
64
|
-
} finally {
|
|
65
|
-
if (preventOriginalCallCalled) {
|
|
66
|
-
preventOriginalCallCalled = false;
|
|
67
|
-
if (allSpyUsedPreventOriginalCall === undefined) {
|
|
68
|
-
allSpyUsedPreventOriginalCall = true;
|
|
69
|
-
}
|
|
70
|
-
} else {
|
|
71
|
-
allSpyUsedPreventOriginalCall = false;
|
|
72
|
-
}
|
|
73
|
-
}
|
|
74
|
-
}
|
|
75
|
-
spyExecuting = false;
|
|
76
|
-
if (!someSpyUsedCallOriginal && !allSpyUsedPreventOriginalCall) {
|
|
77
|
-
callOriginal();
|
|
78
|
-
}
|
|
79
|
-
currentThis = null;
|
|
80
|
-
currentArgs = null;
|
|
81
|
-
if (originalCalled) {
|
|
82
|
-
originalCalled = false;
|
|
83
|
-
const value = originalReturnValue;
|
|
84
|
-
originalReturnValue = undefined;
|
|
85
|
-
return value;
|
|
86
|
-
}
|
|
87
|
-
return undefined;
|
|
88
|
-
};
|
|
89
|
-
addCallback = (spyCallback) => {
|
|
90
|
-
if (spyCallbackSet.size === 0) {
|
|
91
|
-
object[method] = spy;
|
|
92
|
-
}
|
|
93
|
-
spyCallbackSet.add(spyCallback);
|
|
94
|
-
};
|
|
95
|
-
removeCallback = (spyCallback) => {
|
|
96
|
-
spyCallbackSet.delete(spyCallback);
|
|
97
|
-
if (spyCallbackSet.size === 0) {
|
|
98
|
-
object[method] = original;
|
|
99
|
-
}
|
|
100
|
-
};
|
|
101
|
-
spy[jsenvSpySymbol] = {
|
|
102
|
-
addCallback,
|
|
103
|
-
removeCallback,
|
|
104
|
-
original,
|
|
105
|
-
callOriginal,
|
|
106
|
-
onOriginalCall,
|
|
107
|
-
preventOriginalCall,
|
|
108
|
-
};
|
|
109
|
-
object[method] = spy;
|
|
110
|
-
}
|
|
111
|
-
addCallback(spyCallback);
|
|
112
|
-
const spyHooks = {
|
|
113
|
-
callOriginal,
|
|
114
|
-
preventOriginalCall,
|
|
115
|
-
disable: () => {
|
|
116
|
-
spyCallback.disabled = true;
|
|
117
|
-
},
|
|
118
|
-
enable: () => {
|
|
119
|
-
spyCallback.disabled = false;
|
|
120
|
-
},
|
|
121
|
-
remove: () => {
|
|
122
|
-
removeCallback(spyCallback);
|
|
123
|
-
},
|
|
124
|
-
};
|
|
125
|
-
return spyHooks;
|
|
126
|
-
};
|
|
127
|
-
|
|
128
|
-
export const disableSpiesWhileCalling = (fn, spyToDisableArray) => {
|
|
129
|
-
for (const spyToDisable of spyToDisableArray) {
|
|
130
|
-
spyToDisable.disable();
|
|
131
|
-
}
|
|
132
|
-
try {
|
|
133
|
-
return fn();
|
|
134
|
-
} finally {
|
|
135
|
-
for (const spyToEnable of spyToDisableArray) {
|
|
136
|
-
spyToEnable.enable();
|
|
137
|
-
}
|
|
138
|
-
}
|
|
139
|
-
};
|