mirror of
https://github.com/Microsoft/vscode
synced 2024-10-30 11:10:48 +00:00
a0b548807a
This adds an `assertHeap` function that can be used in tests. It takes a heap snapshot, and asserts the state of classes in memory. This works in Node and the Electron sandbox, but is a no-op in the browser. Snapshots are process asynchronously and will report failures at the end of the suite. This method should be used sparingly (e.g. once at the end of a suite to ensure nothing leaked before), as gathering a heap snapshot is fairly slow, at least until V8 11.5.130 (https://v8.dev/blog/speeding-up-v8-heap-snapshots). When used, the function will ensure the test has a minimum timeout duration of 20s to avoid immediate failures. It takes options containing a mapping of class names, and assertion functions to run on the number of retained instances of that class. For example: ```ts assertSnapshot({ classes: { ShouldNeverLeak: count => assert.strictEqual(count, 0), SomeSingleton: count => assert(count <= 1), } }); ``` Closes https://github.com/microsoft/vscode/issues/191920
85 lines
2.4 KiB
JavaScript
85 lines
2.4 KiB
JavaScript
/*---------------------------------------------------------------------------------------------
|
|
* Copyright (c) Microsoft Corporation. All rights reserved.
|
|
* Licensed under the MIT License. See License.txt in the project root for license information.
|
|
*--------------------------------------------------------------------------------------------*/
|
|
|
|
//@ts-check
|
|
|
|
// note: we use a fork here since we can't make a worker from the renderer process
|
|
|
|
const { fork } = require('child_process');
|
|
const workerData = process.env.SNAPSHOT_WORKER_DATA;
|
|
const fs = require('fs');
|
|
const { pathToFileURL } = require('url');
|
|
|
|
if (!workerData) {
|
|
const { join } = require('path');
|
|
const { tmpdir } = require('os');
|
|
|
|
exports.takeSnapshotAndCountClasses = async (/** @type string */currentTest, /** @type string[] */ classes) => {
|
|
const cleanTitle = currentTest.replace(/[^\w]+/g, '-');
|
|
const file = join(tmpdir(), `vscode-test-snap-${cleanTitle}.heapsnapshot`);
|
|
|
|
if (typeof process.takeHeapSnapshot !== 'function') {
|
|
// node.js:
|
|
const inspector = require('inspector');
|
|
const session = new inspector.Session();
|
|
session.connect();
|
|
|
|
const fd = fs.openSync(file, 'w');
|
|
await new Promise((resolve, reject) => {
|
|
session.on('HeapProfiler.addHeapSnapshotChunk', (m) => {
|
|
fs.writeSync(fd, m.params.chunk);
|
|
});
|
|
|
|
session.post('HeapProfiler.takeHeapSnapshot', null, (err) => {
|
|
session.disconnect();
|
|
fs.closeSync(fd);
|
|
if (err) {
|
|
reject(err);
|
|
} else {
|
|
resolve();
|
|
}
|
|
});
|
|
});
|
|
} else {
|
|
// electron exposes this nice method for us:
|
|
process.takeHeapSnapshot(file);
|
|
}
|
|
|
|
const worker = fork(__filename, {
|
|
env: {
|
|
...process.env,
|
|
SNAPSHOT_WORKER_DATA: JSON.stringify({
|
|
path: file,
|
|
classes,
|
|
})
|
|
}
|
|
});
|
|
|
|
const promise = new Promise((resolve, reject) => {
|
|
worker.on('message', (/** @type any */msg) => {
|
|
if ('err' in msg) {
|
|
reject(new Error(msg.err));
|
|
} else {
|
|
resolve(msg.counts);
|
|
}
|
|
worker.kill();
|
|
});
|
|
});
|
|
|
|
return { done: promise, file: pathToFileURL(file) };
|
|
};
|
|
} else {
|
|
const { path, classes } = JSON.parse(workerData);
|
|
const { decode_bytes } = require('@vscode/v8-heap-parser');
|
|
|
|
fs.promises.readFile(path)
|
|
.then(buf => decode_bytes(buf))
|
|
.then(graph => graph.get_class_counts(classes))
|
|
.then(
|
|
counts => process.send({ counts: Array.from(counts) }),
|
|
err => process.send({ err: String(err.stack || err) })
|
|
);
|
|
|
|
}
|