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

@@ -9,7 +9,7 @@
process.env.MOCHA_COLORS = '1'; // Force colors (note that this must come before any mocha imports)
const assert = require('assert');
const mocha = require('mocha');
const Mocha = require('mocha');
const path = require('path');
const fs = require('fs');
const glob = require('glob');
@@ -64,6 +64,14 @@ function main() {
globalThis._VSCODE_PRODUCT_JSON = require(`${REPO_ROOT}/product.json`);
globalThis._VSCODE_PACKAGE_JSON = require(`${REPO_ROOT}/package.json`);
// Test file operations that are common across platforms. Used for test infra, namely snapshot tests
Object.assign(globalThis, {
__readFileInTests: (/** @type {string} */ path) => fs.promises.readFile(path, 'utf-8'),
__writeFileInTests: (/** @type {string} */ path, /** @type {BufferEncoding} */ contents) => fs.promises.writeFile(path, contents),
__readDirInTests: (/** @type {string} */ path) => fs.promises.readdir(path),
__unlinkInTests: (/** @type {string} */ path) => fs.promises.unlink(path),
__mkdirPInTests: (/** @type {string} */ path) => fs.promises.mkdir(path, { recursive: true }),
});
process.on('uncaughtException', function (e) {
console.error(e.stack || e);
@@ -127,7 +135,24 @@ function main() {
return write.apply(process.stderr, args);
};
/** @type { (callback:(err:any)=>void)=>void } */
const runner = new Mocha({
ui: 'tdd'
});
/**
* @param {string[]} modules
*/
async function loadModules(modules) {
for (const file of modules) {
runner.suite.emit(Mocha.Suite.constants.EVENT_FILE_PRE_REQUIRE, globalThis, file, runner);
const m = await new Promise((resolve, reject) => loader([file], resolve, reject));
runner.suite.emit(Mocha.Suite.constants.EVENT_FILE_REQUIRE, m, file, runner);
runner.suite.emit(Mocha.Suite.constants.EVENT_FILE_POST_REQUIRE, globalThis, file, runner);
}
}
/** @type { null|((callback:(err:any)=>void)=>void) } */
let loadFunc = null;
if (argv.runGlob) {
@@ -140,7 +165,7 @@ function main() {
return test.replace(/(\.js)|(\.d\.ts)|(\.js\.map)$/, '');
});
loader(modulesToLoad, () => cb(null), cb);
loadModules(modulesToLoad).then(() => cb(null), cb);
};
glob(argv.runGlob, { cwd: src }, function (err, files) { doRun(files); });
@@ -153,7 +178,7 @@ function main() {
return path.relative(src, path.resolve(test)).replace(/(\.js)|(\.js\.map)$/, '').replace(/\\/g, '/');
});
loadFunc = (cb) => {
loader(modulesToLoad, () => cb(null), cb);
loadModules(modulesToLoad).then(() => cb(null), cb);
};
} else {
loadFunc = (cb) => {
@@ -165,7 +190,7 @@ function main() {
modules.push(file.replace(/\.js$/, ''));
}
}
loader(modules, function () { cb(null); }, cb);
loadModules(modules).then(() => cb(null), cb);
});
};
}
@@ -180,7 +205,7 @@ function main() {
if (!argv.run && !argv.runGlob) {
// set up last test
mocha.suite('Loader', function () {
Mocha.suite('Loader', function () {
test('should not explode while loading', function () {
assert.ok(!didErr, 'should not explode while loading');
});
@@ -189,7 +214,7 @@ function main() {
// report failing test for every unexpected error during any of the tests
const unexpectedErrors = [];
mocha.suite('Errors', function () {
Mocha.suite('Errors', function () {
test('should not have unexpected errors in tests', function () {
if (unexpectedErrors.length) {
unexpectedErrors.forEach(function (stack) {
@@ -210,7 +235,7 @@ function main() {
});
// fire up mocha
mocha.run();
runner.run(failures => process.exit(failures ? 1 : 0));
});
});
}