eng: add support for snapshot tests (#190444)

* eng: add support for snapshot tests

This adds Jest-like support for snapshot testing.
Developers can do something like:

```js
await assertSnapshot(myComplexObject)
```

The first time this is run, the snapshot expectation file is written
to a `__snapshots__` directory beside the test file. Subsequent runs
will compare the object to the snapshot, and fail if it doesn't match.

You can see an example of this in the test for snapshots themselves!

After a successful run, any unused snapshots are cleaned up. On a failed
run, a gitignored `.actual` snapshot file is created beside the
snapshot for easy processing and inspection.

Shortly I will do some integration with the selfhost test extension to
allow developers to easily update snapshots from the vscode UI.

For #189680

cc @ulugbekna @hediet

* fix async stacktraces getting clobbered

* random fixes

* comment out leak detector, for now

* add option to snapshot file extension
This commit is contained in:
Connor Peet
2023-08-15 12:03:51 -07:00
committed by GitHub
parent ee823a18e4
commit 6a847ba6d1
22 changed files with 504 additions and 46 deletions

View File

@@ -5,8 +5,9 @@
/*eslint-env mocha*/
const fs = require('fs');
(function () {
const fs = require('fs');
const originals = {};
let logging = false;
let withStacks = false;
@@ -79,6 +80,15 @@ globalThis._VSCODE_NODE_MODULES = new Proxy(Object.create(null), { get: (_target
globalThis._VSCODE_PRODUCT_JSON = (require.__$__nodeRequire ?? require)('../../../product.json');
globalThis._VSCODE_PACKAGE_JSON = (require.__$__nodeRequire ?? require)('../../../package.json');
// Test file operations that are common across platforms. Used for test infra, namely snapshot tests
Object.assign(globalThis, {
__readFileInTests: path => fs.promises.readFile(path, 'utf-8'),
__writeFileInTests: (path, contents) => fs.promises.writeFile(path, contents),
__readDirInTests: path => fs.promises.readdir(path),
__unlinkInTests: path => fs.promises.unlink(path),
__mkdirPInTests: path => fs.promises.mkdir(path, { recursive: true }),
});
const _tests_glob = '**/test/**/*.test.js';
let loader;
let _out;
@@ -121,6 +131,15 @@ function loadWorkbenchTestingUtilsModule() {
});
}
async function loadModules(modules) {
for (const file of modules) {
mocha.suite.emit(Mocha.Suite.constants.EVENT_FILE_PRE_REQUIRE, globalThis, file, mocha);
const m = await new Promise((resolve, reject) => loader.require([file], resolve, reject));
mocha.suite.emit(Mocha.Suite.constants.EVENT_FILE_REQUIRE, m, file, mocha);
mocha.suite.emit(Mocha.Suite.constants.EVENT_FILE_POST_REQUIRE, globalThis, file, mocha);
}
}
function loadTestModules(opts) {
if (opts.run) {
@@ -130,9 +149,7 @@ function loadTestModules(opts) {
file = file.replace(/\.ts$/, '.js');
return path.relative(_out, file).replace(/\.js$/, '');
});
return new Promise((resolve, reject) => {
loader.require(modules, resolve, reject);
});
return loadModules(modules);
}
const pattern = opts.runGlob || _tests_glob;
@@ -146,11 +163,7 @@ function loadTestModules(opts) {
const modules = files.map(file => file.replace(/\.js$/, ''));
resolve(modules);
});
}).then(modules => {
return new Promise((resolve, reject) => {
loader.require(modules, resolve, reject);
});
});
}).then(loadModules);
}
function loadTests(opts) {
@@ -239,6 +252,7 @@ function serializeError(err) {
return {
message: err.message,
stack: err.stack,
snapshotPath: err.snapshotPath,
actual: safeStringify({ value: err.actual }),
expected: safeStringify({ value: err.expected }),
uncaught: err.uncaught,