vscode/test/unit/analyzeSnapshot.js
Connor Peet a0b548807a
eng: add assertHeap method for memory assertions (#198334)
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
2023-11-15 10:41:22 -08:00

86 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) })
);
}