mirror of
https://github.com/microsoft/vscode.git
synced 2026-02-15 07:28:05 +00:00
esm - port over unit test changes (#225711)
This commit is contained in:
@@ -7,6 +7,9 @@ parameters:
|
||||
type: boolean
|
||||
- name: VSCODE_RUN_SMOKE_TESTS
|
||||
type: boolean
|
||||
- name: VSCODE_BUILD_ESM
|
||||
type: boolean
|
||||
default: false
|
||||
|
||||
steps:
|
||||
- script: yarn npm-run-all -lp "electron $(VSCODE_ARCH)" "playwright-install"
|
||||
@@ -17,34 +20,56 @@ steps:
|
||||
|
||||
- ${{ if eq(parameters.VSCODE_RUN_UNIT_TESTS, true) }}:
|
||||
- ${{ if eq(parameters.VSCODE_QUALITY, 'oss') }}:
|
||||
- script: ./scripts/test.sh --tfs "Unit Tests"
|
||||
displayName: Run unit tests (Electron)
|
||||
timeoutInMinutes: 15
|
||||
|
||||
- script: yarn test-node
|
||||
displayName: Run unit tests (node.js)
|
||||
timeoutInMinutes: 15
|
||||
|
||||
- script: yarn test-browser-no-install --sequential --browser chromium --browser webkit --tfs "Browser Unit Tests"
|
||||
env:
|
||||
DEBUG: "*browser*"
|
||||
displayName: Run unit tests (Browser, Chromium & Webkit)
|
||||
timeoutInMinutes: 30
|
||||
- ${{ if eq(parameters.VSCODE_BUILD_ESM, true) }}:
|
||||
- script: ./scripts/test-esm.sh --tfs "Unit Tests"
|
||||
displayName: Run unit tests (Electron) [ESM]
|
||||
timeoutInMinutes: 15
|
||||
- script: yarn test-node-esm
|
||||
displayName: Run unit tests (node.js) [ESM]
|
||||
timeoutInMinutes: 15
|
||||
- script: yarn test-browser-esm-no-install --sequential --browser chromium --browser webkit --tfs "Browser Unit Tests"
|
||||
env:
|
||||
DEBUG: "*browser*"
|
||||
displayName: Run unit tests (Browser, Chromium & Webkit) [ESM]
|
||||
timeoutInMinutes: 30
|
||||
- ${{ if eq(parameters.VSCODE_BUILD_ESM, false) }}:
|
||||
- script: ./scripts/test.sh --tfs "Unit Tests"
|
||||
displayName: Run unit tests (Electron)
|
||||
timeoutInMinutes: 15
|
||||
- script: yarn test-node
|
||||
displayName: Run unit tests (node.js)
|
||||
timeoutInMinutes: 15
|
||||
- script: yarn test-browser-no-install --sequential --browser chromium --browser webkit --tfs "Browser Unit Tests"
|
||||
env:
|
||||
DEBUG: "*browser*"
|
||||
displayName: Run unit tests (Browser, Chromium & Webkit)
|
||||
timeoutInMinutes: 30
|
||||
|
||||
- ${{ if ne(parameters.VSCODE_QUALITY, 'oss') }}:
|
||||
- script: ./scripts/test.sh --build --tfs "Unit Tests"
|
||||
displayName: Run unit tests (Electron)
|
||||
timeoutInMinutes: 15
|
||||
|
||||
- script: yarn test-node --build
|
||||
displayName: Run unit tests (node.js)
|
||||
timeoutInMinutes: 15
|
||||
|
||||
- script: yarn test-browser-no-install --sequential --build --browser chromium --browser webkit --tfs "Browser Unit Tests"
|
||||
env:
|
||||
DEBUG: "*browser*"
|
||||
displayName: Run unit tests (Browser, Chromium & Webkit)
|
||||
timeoutInMinutes: 30
|
||||
- ${{ if eq(parameters.VSCODE_BUILD_ESM, true) }}:
|
||||
- script: ./scripts/test-esm.sh --build --tfs "Unit Tests"
|
||||
displayName: Run unit tests (Electron) [ESM]
|
||||
timeoutInMinutes: 15
|
||||
- script: yarn test-node-esm --build
|
||||
displayName: Run unit tests (node.js) [ESM]
|
||||
timeoutInMinutes: 15
|
||||
- script: yarn test-browser-esm-no-install --sequential --build --browser chromium --browser webkit --tfs "Browser Unit Tests"
|
||||
env:
|
||||
DEBUG: "*browser*"
|
||||
displayName: Run unit tests (Browser, Chromium & Webkit) [ESM]
|
||||
timeoutInMinutes: 30
|
||||
- ${{ if eq(parameters.VSCODE_BUILD_ESM, false) }}:
|
||||
- script: ./scripts/test.sh --build --tfs "Unit Tests"
|
||||
displayName: Run unit tests (Electron)
|
||||
timeoutInMinutes: 15
|
||||
- script: yarn test-node --build
|
||||
displayName: Run unit tests (node.js)
|
||||
timeoutInMinutes: 15
|
||||
- script: yarn test-browser-no-install --sequential --build --browser chromium --browser webkit --tfs "Browser Unit Tests"
|
||||
env:
|
||||
DEBUG: "*browser*"
|
||||
displayName: Run unit tests (Browser, Chromium & Webkit)
|
||||
timeoutInMinutes: 30
|
||||
|
||||
- ${{ if eq(parameters.VSCODE_RUN_INTEGRATION_TESTS, true) }}:
|
||||
- script: |
|
||||
|
||||
@@ -10,6 +10,9 @@ parameters:
|
||||
- name: PUBLISH_TASK_NAME
|
||||
type: string
|
||||
default: PublishPipelineArtifact@0
|
||||
- name: VSCODE_BUILD_ESM
|
||||
type: boolean
|
||||
default: false
|
||||
|
||||
steps:
|
||||
- script: yarn npm-run-all -lp "electron $(VSCODE_ARCH)" "playwright-install"
|
||||
@@ -33,36 +36,61 @@ steps:
|
||||
|
||||
- ${{ if eq(parameters.VSCODE_RUN_UNIT_TESTS, true) }}:
|
||||
- ${{ if eq(parameters.VSCODE_QUALITY, 'oss') }}:
|
||||
- script: ./scripts/test.sh --tfs "Unit Tests"
|
||||
env:
|
||||
DISPLAY: ":10"
|
||||
displayName: Run unit tests (Electron)
|
||||
timeoutInMinutes: 15
|
||||
- ${{ if eq(parameters.VSCODE_BUILD_ESM, true) }}:
|
||||
- script: ./scripts/test-esm.sh --tfs "Unit Tests"
|
||||
env:
|
||||
DISPLAY: ":10"
|
||||
displayName: Run unit tests (Electron) [ESM]
|
||||
timeoutInMinutes: 15
|
||||
- script: yarn test-node-esm
|
||||
displayName: Run unit tests (node.js) [ESM]
|
||||
timeoutInMinutes: 15
|
||||
- script: yarn test-browser-esm-no-install --browser chromium --tfs "Browser Unit Tests"
|
||||
env:
|
||||
DEBUG: "*browser*"
|
||||
displayName: Run unit tests (Browser, Chromium) [ESM]
|
||||
timeoutInMinutes: 15
|
||||
- ${{ if eq(parameters.VSCODE_BUILD_ESM, false) }}:
|
||||
- script: ./scripts/test.sh --tfs "Unit Tests"
|
||||
env:
|
||||
DISPLAY: ":10"
|
||||
displayName: Run unit tests (Electron)
|
||||
timeoutInMinutes: 15
|
||||
- script: yarn test-node
|
||||
displayName: Run unit tests (node.js)
|
||||
timeoutInMinutes: 15
|
||||
- script: yarn test-browser-no-install --browser chromium --tfs "Browser Unit Tests"
|
||||
env:
|
||||
DEBUG: "*browser*"
|
||||
displayName: Run unit tests (Browser, Chromium)
|
||||
timeoutInMinutes: 15
|
||||
|
||||
- script: yarn test-node
|
||||
displayName: Run unit tests (node.js)
|
||||
timeoutInMinutes: 15
|
||||
|
||||
- script: yarn test-browser-no-install --browser chromium --tfs "Browser Unit Tests"
|
||||
env:
|
||||
DEBUG: "*browser*"
|
||||
displayName: Run unit tests (Browser, Chromium)
|
||||
timeoutInMinutes: 15
|
||||
|
||||
- ${{ if ne(parameters.VSCODE_QUALITY, 'oss') }}:
|
||||
- script: ./scripts/test.sh --build --tfs "Unit Tests"
|
||||
displayName: Run unit tests (Electron)
|
||||
timeoutInMinutes: 15
|
||||
|
||||
- script: yarn test-node --build
|
||||
displayName: Run unit tests (node.js)
|
||||
timeoutInMinutes: 15
|
||||
|
||||
- script: yarn test-browser-no-install --build --browser chromium --tfs "Browser Unit Tests"
|
||||
env:
|
||||
DEBUG: "*browser*"
|
||||
displayName: Run unit tests (Browser, Chromium)
|
||||
timeoutInMinutes: 15
|
||||
- ${{ if eq(parameters.VSCODE_BUILD_ESM, true) }}:
|
||||
- script: ./scripts/test-esm.sh --build --tfs "Unit Tests"
|
||||
displayName: Run unit tests (Electron) [ESM]
|
||||
timeoutInMinutes: 15
|
||||
- script: yarn test-node-esm --build
|
||||
displayName: Run unit tests (node.js) [ESM]
|
||||
timeoutInMinutes: 15
|
||||
- script: yarn test-browser-esm-no-install --build --browser chromium --tfs "Browser Unit Tests"
|
||||
env:
|
||||
DEBUG: "*browser*"
|
||||
displayName: Run unit tests (Browser, Chromium) [ESM]
|
||||
timeoutInMinutes: 15
|
||||
- ${{ if eq(parameters.VSCODE_BUILD_ESM, false) }}:
|
||||
- script: ./scripts/test.sh --build --tfs "Unit Tests"
|
||||
displayName: Run unit tests (Electron)
|
||||
timeoutInMinutes: 15
|
||||
- script: yarn test-node --build
|
||||
displayName: Run unit tests (node.js)
|
||||
timeoutInMinutes: 15
|
||||
- script: yarn test-browser-no-install --build --browser chromium --tfs "Browser Unit Tests"
|
||||
env:
|
||||
DEBUG: "*browser*"
|
||||
displayName: Run unit tests (Browser, Chromium)
|
||||
timeoutInMinutes: 15
|
||||
|
||||
- ${{ if eq(parameters.VSCODE_RUN_INTEGRATION_TESTS, true) }}:
|
||||
- script: |
|
||||
|
||||
@@ -12,6 +12,9 @@ parameters:
|
||||
- name: PUBLISH_TASK_NAME
|
||||
type: string
|
||||
default: PublishPipelineArtifact@0
|
||||
- name: VSCODE_BUILD_ESM
|
||||
type: boolean
|
||||
default: false
|
||||
|
||||
steps:
|
||||
- powershell: yarn npm-run-all -lp "electron $(VSCODE_ARCH)" "playwright-install"
|
||||
@@ -22,30 +25,48 @@ steps:
|
||||
|
||||
- ${{ if eq(parameters.VSCODE_RUN_UNIT_TESTS, true) }}:
|
||||
- ${{ if eq(parameters.VSCODE_QUALITY, 'oss') }}:
|
||||
- powershell: .\scripts\test.bat --tfs "Unit Tests"
|
||||
displayName: Run unit tests (Electron)
|
||||
timeoutInMinutes: 15
|
||||
|
||||
- powershell: yarn test-node
|
||||
displayName: Run unit tests (node.js)
|
||||
timeoutInMinutes: 15
|
||||
|
||||
- powershell: node test/unit/browser/index.js --sequential --browser chromium --tfs "Browser Unit Tests"
|
||||
displayName: Run unit tests (Browser, Chromium)
|
||||
timeoutInMinutes: 20
|
||||
- ${{ if eq(parameters.VSCODE_BUILD_ESM, true) }}:
|
||||
- powershell: .\scripts\test-esm.bat --tfs "Unit Tests"
|
||||
displayName: Run unit tests (Electron) [ESM]
|
||||
timeoutInMinutes: 15
|
||||
- powershell: yarn test-node-esm
|
||||
displayName: Run unit tests (node.js) [ESM]
|
||||
timeoutInMinutes: 15
|
||||
- powershell: node test/unit/browser/index.esm.js --sequential --browser chromium --tfs "Browser Unit Tests"
|
||||
displayName: Run unit tests (Browser, Chromium) [ESM]
|
||||
timeoutInMinutes: 20
|
||||
- ${{ if eq(parameters.VSCODE_BUILD_ESM, false) }}:
|
||||
- powershell: .\scripts\test.bat --tfs "Unit Tests"
|
||||
displayName: Run unit tests (Electron)
|
||||
timeoutInMinutes: 15
|
||||
- powershell: yarn test-node
|
||||
displayName: Run unit tests (node.js)
|
||||
timeoutInMinutes: 15
|
||||
- powershell: node test/unit/browser/index.js --sequential --browser chromium --tfs "Browser Unit Tests"
|
||||
displayName: Run unit tests (Browser, Chromium)
|
||||
timeoutInMinutes: 20
|
||||
|
||||
- ${{ if ne(parameters.VSCODE_QUALITY, 'oss') }}:
|
||||
- powershell: .\scripts\test.bat --build --tfs "Unit Tests"
|
||||
displayName: Run unit tests (Electron)
|
||||
timeoutInMinutes: 15
|
||||
|
||||
- powershell: yarn test-node --build
|
||||
displayName: Run unit tests (node.js)
|
||||
timeoutInMinutes: 15
|
||||
|
||||
- powershell: yarn test-browser-no-install --sequential --build --browser chromium --tfs "Browser Unit Tests"
|
||||
displayName: Run unit tests (Browser, Chromium)
|
||||
timeoutInMinutes: 20
|
||||
- ${{ if eq(parameters.VSCODE_BUILD_ESM, true) }}:
|
||||
- powershell: .\scripts\test-esm.bat --build --tfs "Unit Tests"
|
||||
displayName: Run unit tests (Electron) [ESM]
|
||||
timeoutInMinutes: 15
|
||||
- script: yarn test-node-esm --build
|
||||
displayName: Run unit tests (node.js) [ESM]
|
||||
timeoutInMinutes: 15
|
||||
- powershell: yarn test-browser-esm-no-install --sequential --build --browser chromium --tfs "Browser Unit Tests"
|
||||
displayName: Run unit tests (Browser, Chromium) [ESM]
|
||||
timeoutInMinutes: 20
|
||||
- ${{ if eq(parameters.VSCODE_BUILD_ESM, false) }}:
|
||||
- powershell: .\scripts\test.bat --build --tfs "Unit Tests"
|
||||
displayName: Run unit tests (Electron)
|
||||
timeoutInMinutes: 15
|
||||
- powershell: yarn test-node --build
|
||||
displayName: Run unit tests (node.js)
|
||||
timeoutInMinutes: 15
|
||||
- powershell: yarn test-browser-no-install --sequential --build --browser chromium --tfs "Browser Unit Tests"
|
||||
displayName: Run unit tests (Browser, Chromium)
|
||||
timeoutInMinutes: 20
|
||||
|
||||
- ${{ if eq(parameters.VSCODE_RUN_INTEGRATION_TESTS, true) }}:
|
||||
- powershell: |
|
||||
|
||||
@@ -11,8 +11,11 @@
|
||||
"scripts": {
|
||||
"test": "echo Please run any of the test scripts from the scripts folder.",
|
||||
"test-browser": "npx playwright install && node test/unit/browser/index.js",
|
||||
"test-browser-esm": "npx playwright install && node test/unit/browser/index.esm.js",
|
||||
"test-browser-no-install": "node test/unit/browser/index.js",
|
||||
"test-browser-esm-no-install": "node test/unit/browser/index.esm.js",
|
||||
"test-node": "mocha test/unit/node/index.js --delay --ui=tdd --timeout=5000 --exit",
|
||||
"test-node-esm": "mocha test/unit/node/index.mjs --delay --ui=tdd --timeout=5000 --exit",
|
||||
"test-extension": "vscode-test",
|
||||
"preinstall": "node build/npm/preinstall.js",
|
||||
"postinstall": "node build/npm/postinstall.js",
|
||||
|
||||
31
scripts/test-esm.bat
Normal file
31
scripts/test-esm.bat
Normal file
@@ -0,0 +1,31 @@
|
||||
@echo off
|
||||
setlocal
|
||||
|
||||
set ELECTRON_RUN_AS_NODE=
|
||||
|
||||
pushd %~dp0\..
|
||||
|
||||
:: Get Code.exe location
|
||||
for /f "tokens=2 delims=:," %%a in ('findstr /R /C:"\"nameShort\":.*" product.json') do set NAMESHORT=%%~a
|
||||
set NAMESHORT=%NAMESHORT: "=%
|
||||
set NAMESHORT=%NAMESHORT:"=%.exe
|
||||
set CODE=".build\electron\%NAMESHORT%"
|
||||
|
||||
:: Download Electron if needed
|
||||
call node build\lib\electron.js
|
||||
if %errorlevel% neq 0 node .\node_modules\gulp\bin\gulp.js electron
|
||||
|
||||
:: Run tests
|
||||
set ELECTRON_ENABLE_LOGGING=1
|
||||
%CODE% .\test\unit\electron\index.esm.js --crash-reporter-directory=%~dp0\..\.build\crashes %*
|
||||
|
||||
popd
|
||||
|
||||
endlocal
|
||||
|
||||
:: app.exit(0) is exiting with code 255 in Electron 1.7.4.
|
||||
:: See https://github.com/microsoft/vscode/issues/28582
|
||||
echo errorlevel: %errorlevel%
|
||||
if %errorlevel% == 255 set errorlevel=0
|
||||
|
||||
exit /b %errorlevel%
|
||||
43
scripts/test-esm.sh
Executable file
43
scripts/test-esm.sh
Executable file
@@ -0,0 +1,43 @@
|
||||
#!/usr/bin/env bash
|
||||
set -e
|
||||
|
||||
if [[ "$OSTYPE" == "darwin"* ]]; then
|
||||
realpath() { [[ $1 = /* ]] && echo "$1" || echo "$PWD/${1#./}"; }
|
||||
ROOT=$(dirname $(dirname $(realpath "$0")))
|
||||
else
|
||||
ROOT=$(dirname $(dirname $(readlink -f $0)))
|
||||
# --disable-dev-shm-usage: when run on docker containers where size of /dev/shm
|
||||
# partition < 64MB which causes OOM failure for chromium compositor that uses the partition for shared memory
|
||||
LINUX_EXTRA_ARGS="--disable-dev-shm-usage"
|
||||
fi
|
||||
|
||||
cd $ROOT
|
||||
|
||||
if [[ "$OSTYPE" == "darwin"* ]]; then
|
||||
NAME=`node -p "require('./product.json').nameLong"`
|
||||
CODE="./.build/electron/$NAME.app/Contents/MacOS/Electron"
|
||||
else
|
||||
NAME=`node -p "require('./product.json').applicationName"`
|
||||
CODE=".build/electron/$NAME"
|
||||
fi
|
||||
|
||||
VSCODECRASHDIR=$ROOT/.build/crashes
|
||||
|
||||
# Node modules
|
||||
test -d node_modules || yarn
|
||||
|
||||
# Get electron
|
||||
yarn electron
|
||||
|
||||
# Unit Tests
|
||||
if [[ "$OSTYPE" == "darwin"* ]]; then
|
||||
cd $ROOT ; ulimit -n 4096 ; \
|
||||
ELECTRON_ENABLE_LOGGING=1 \
|
||||
"$CODE" \
|
||||
test/unit/electron/index.esm.js --crash-reporter-directory=$VSCODECRASHDIR "$@"
|
||||
else
|
||||
cd $ROOT ; \
|
||||
ELECTRON_ENABLE_LOGGING=1 \
|
||||
"$CODE" \
|
||||
test/unit/electron/index.esm.js --crash-reporter-directory=$VSCODECRASHDIR $LINUX_EXTRA_ARGS "$@"
|
||||
fi
|
||||
498
test/unit/assert-esm.js
Normal file
498
test/unit/assert-esm.js
Normal file
@@ -0,0 +1,498 @@
|
||||
/*---------------------------------------------------------------------------------------------
|
||||
* Copyright (c) Microsoft Corporation. All rights reserved.
|
||||
* Licensed under the MIT License. See License.txt in the project root for license information.
|
||||
*--------------------------------------------------------------------------------------------*/
|
||||
|
||||
|
||||
// UTILITY
|
||||
|
||||
// Object.create compatible in IE
|
||||
const create = Object.create || function (p) {
|
||||
if (!p) { throw Error('no type'); }
|
||||
function f() { }
|
||||
f.prototype = p;
|
||||
return new f();
|
||||
};
|
||||
|
||||
// UTILITY
|
||||
var util = {
|
||||
inherits: function (ctor, superCtor) {
|
||||
ctor.super_ = superCtor;
|
||||
ctor.prototype = create(superCtor.prototype, {
|
||||
constructor: {
|
||||
value: ctor,
|
||||
enumerable: false,
|
||||
writable: true,
|
||||
configurable: true
|
||||
}
|
||||
});
|
||||
},
|
||||
isArray: function (ar) {
|
||||
return Array.isArray(ar);
|
||||
},
|
||||
isBoolean: function (arg) {
|
||||
return typeof arg === 'boolean';
|
||||
},
|
||||
isNull: function (arg) {
|
||||
return arg === null;
|
||||
},
|
||||
isNullOrUndefined: function (arg) {
|
||||
return arg == null;
|
||||
},
|
||||
isNumber: function (arg) {
|
||||
return typeof arg === 'number';
|
||||
},
|
||||
isString: function (arg) {
|
||||
return typeof arg === 'string';
|
||||
},
|
||||
isSymbol: function (arg) {
|
||||
return typeof arg === 'symbol';
|
||||
},
|
||||
isUndefined: function (arg) {
|
||||
return arg === undefined;
|
||||
},
|
||||
isRegExp: function (re) {
|
||||
return util.isObject(re) && util.objectToString(re) === '[object RegExp]';
|
||||
},
|
||||
isObject: function (arg) {
|
||||
return typeof arg === 'object' && arg !== null;
|
||||
},
|
||||
isDate: function (d) {
|
||||
return util.isObject(d) && util.objectToString(d) === '[object Date]';
|
||||
},
|
||||
isError: function (e) {
|
||||
return isObject(e) &&
|
||||
(objectToString(e) === '[object Error]' || e instanceof Error);
|
||||
},
|
||||
isFunction: function (arg) {
|
||||
return typeof arg === 'function';
|
||||
},
|
||||
isPrimitive: function (arg) {
|
||||
return arg === null ||
|
||||
typeof arg === 'boolean' ||
|
||||
typeof arg === 'number' ||
|
||||
typeof arg === 'string' ||
|
||||
typeof arg === 'symbol' || // ES6 symbol
|
||||
typeof arg === 'undefined';
|
||||
},
|
||||
objectToString: function (o) {
|
||||
return Object.prototype.toString.call(o);
|
||||
}
|
||||
};
|
||||
|
||||
const pSlice = Array.prototype.slice;
|
||||
|
||||
// From https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Object/keys
|
||||
const Object_keys = typeof Object.keys === 'function' ? Object.keys : (function () {
|
||||
const hasOwnProperty = Object.prototype.hasOwnProperty,
|
||||
hasDontEnumBug = !({ toString: null }).propertyIsEnumerable('toString'),
|
||||
dontEnums = [
|
||||
'toString',
|
||||
'toLocaleString',
|
||||
'valueOf',
|
||||
'hasOwnProperty',
|
||||
'isPrototypeOf',
|
||||
'propertyIsEnumerable',
|
||||
'constructor'
|
||||
],
|
||||
dontEnumsLength = dontEnums.length;
|
||||
|
||||
return function (obj) {
|
||||
if (typeof obj !== 'object' && (typeof obj !== 'function' || obj === null)) {
|
||||
throw new TypeError('Object.keys called on non-object');
|
||||
}
|
||||
|
||||
let result = [], prop, i;
|
||||
|
||||
for (prop in obj) {
|
||||
if (hasOwnProperty.call(obj, prop)) {
|
||||
result.push(prop);
|
||||
}
|
||||
}
|
||||
|
||||
if (hasDontEnumBug) {
|
||||
for (i = 0; i < dontEnumsLength; i++) {
|
||||
if (hasOwnProperty.call(obj, dontEnums[i])) {
|
||||
result.push(dontEnums[i]);
|
||||
}
|
||||
}
|
||||
}
|
||||
return result;
|
||||
};
|
||||
})();
|
||||
|
||||
// 1. The assert module provides functions that throw
|
||||
// AssertionError's when particular conditions are not met. The
|
||||
// assert module must conform to the following interface.
|
||||
|
||||
const assert = ok;
|
||||
|
||||
// 2. The AssertionError is defined in assert.
|
||||
// new assert.AssertionError({ message: message,
|
||||
// actual: actual,
|
||||
// expected: expected })
|
||||
|
||||
assert.AssertionError = function AssertionError(options) {
|
||||
this.name = 'AssertionError';
|
||||
this.actual = options.actual;
|
||||
this.expected = options.expected;
|
||||
this.operator = options.operator;
|
||||
if (options.message) {
|
||||
this.message = options.message;
|
||||
this.generatedMessage = false;
|
||||
} else {
|
||||
this.message = getMessage(this);
|
||||
this.generatedMessage = true;
|
||||
}
|
||||
const stackStartFunction = options.stackStartFunction || fail;
|
||||
if (Error.captureStackTrace) {
|
||||
Error.captureStackTrace(this, stackStartFunction);
|
||||
} else {
|
||||
// try to throw an error now, and from the stack property
|
||||
// work out the line that called in to assert.js.
|
||||
try {
|
||||
this.stack = (new Error).stack.toString();
|
||||
} catch (e) { }
|
||||
}
|
||||
};
|
||||
|
||||
// assert.AssertionError instanceof Error
|
||||
util.inherits(assert.AssertionError, Error);
|
||||
|
||||
function replacer(key, value) {
|
||||
if (util.isUndefined(value)) {
|
||||
return '' + value;
|
||||
}
|
||||
if (util.isNumber(value) && (isNaN(value) || !isFinite(value))) {
|
||||
return value.toString();
|
||||
}
|
||||
if (util.isFunction(value) || util.isRegExp(value)) {
|
||||
return value.toString();
|
||||
}
|
||||
return value;
|
||||
}
|
||||
|
||||
function truncate(s, n) {
|
||||
if (util.isString(s)) {
|
||||
return s.length < n ? s : s.slice(0, n);
|
||||
} else {
|
||||
return s;
|
||||
}
|
||||
}
|
||||
|
||||
function getMessage(self) {
|
||||
return truncate(JSON.stringify(self.actual, replacer), 128) + ' ' +
|
||||
self.operator + ' ' +
|
||||
truncate(JSON.stringify(self.expected, replacer), 128);
|
||||
}
|
||||
|
||||
// At present only the three keys mentioned above are used and
|
||||
// understood by the spec. Implementations or sub modules can pass
|
||||
// other keys to the AssertionError's constructor - they will be
|
||||
// ignored.
|
||||
|
||||
// 3. All of the following functions must throw an AssertionError
|
||||
// when a corresponding condition is not met, with a message that
|
||||
// may be undefined if not provided. All assertion methods provide
|
||||
// both the actual and expected values to the assertion error for
|
||||
// display purposes.
|
||||
|
||||
export function fail(actual, expected, message, operator, stackStartFunction) {
|
||||
throw new assert.AssertionError({
|
||||
message: message,
|
||||
actual: actual,
|
||||
expected: expected,
|
||||
operator: operator,
|
||||
stackStartFunction: stackStartFunction
|
||||
});
|
||||
}
|
||||
|
||||
// EXTENSION! allows for well behaved errors defined elsewhere.
|
||||
assert.fail = fail;
|
||||
|
||||
// 4. Pure assertion tests whether a value is truthy, as determined
|
||||
// by !!guard.
|
||||
// assert.ok(guard, message_opt);
|
||||
// This statement is equivalent to assert.equal(true, !!guard,
|
||||
// message_opt);. To test strictly for the value true, use
|
||||
// assert.strictEqual(true, guard, message_opt);.
|
||||
|
||||
export function ok(value, message) {
|
||||
if (!value) { fail(value, true, message, '==', assert.ok); }
|
||||
}
|
||||
assert.ok = ok;
|
||||
|
||||
// 5. The equality assertion tests shallow, coercive equality with
|
||||
// ==.
|
||||
// assert.equal(actual, expected, message_opt);
|
||||
|
||||
assert.equal = function equal(actual, expected, message) {
|
||||
if (actual != expected) { fail(actual, expected, message, '==', assert.equal); }
|
||||
};
|
||||
|
||||
// 6. The non-equality assertion tests for whether two objects are not equal
|
||||
// with != assert.notEqual(actual, expected, message_opt);
|
||||
|
||||
assert.notEqual = function notEqual(actual, expected, message) {
|
||||
if (actual == expected) {
|
||||
fail(actual, expected, message, '!=', assert.notEqual);
|
||||
}
|
||||
};
|
||||
|
||||
// 7. The equivalence assertion tests a deep equality relation.
|
||||
// assert.deepEqual(actual, expected, message_opt);
|
||||
|
||||
assert.deepEqual = function deepEqual(actual, expected, message) {
|
||||
if (!_deepEqual(actual, expected, false)) {
|
||||
fail(actual, expected, message, 'deepEqual', assert.deepEqual);
|
||||
}
|
||||
};
|
||||
|
||||
assert.deepStrictEqual = function deepStrictEqual(actual, expected, message) {
|
||||
if (!_deepEqual(actual, expected, true)) {
|
||||
fail(actual, expected, message, 'deepStrictEqual', assert.deepStrictEqual);
|
||||
}
|
||||
};
|
||||
|
||||
function _deepEqual(actual, expected, strict) {
|
||||
// 7.1. All identical values are equivalent, as determined by ===.
|
||||
if (actual === expected) {
|
||||
return true;
|
||||
// } else if (actual instanceof Buffer && expected instanceof Buffer) {
|
||||
// return compare(actual, expected) === 0;
|
||||
|
||||
// 7.2. If the expected value is a Date object, the actual value is
|
||||
// equivalent if it is also a Date object that refers to the same time.
|
||||
} else if (util.isDate(actual) && util.isDate(expected)) {
|
||||
return actual.getTime() === expected.getTime();
|
||||
|
||||
// 7.3 If the expected value is a RegExp object, the actual value is
|
||||
// equivalent if it is also a RegExp object with the same source and
|
||||
// properties (`global`, `multiline`, `lastIndex`, `ignoreCase`).
|
||||
} else if (util.isRegExp(actual) && util.isRegExp(expected)) {
|
||||
return actual.source === expected.source &&
|
||||
actual.global === expected.global &&
|
||||
actual.multiline === expected.multiline &&
|
||||
actual.lastIndex === expected.lastIndex &&
|
||||
actual.ignoreCase === expected.ignoreCase;
|
||||
|
||||
// 7.4. Other pairs that do not both pass typeof value == 'object',
|
||||
// equivalence is determined by ==.
|
||||
} else if ((actual === null || typeof actual !== 'object') &&
|
||||
(expected === null || typeof expected !== 'object')) {
|
||||
return strict ? actual === expected : actual == expected;
|
||||
|
||||
// 7.5 For all other Object pairs, including Array objects, equivalence is
|
||||
// determined by having the same number of owned properties (as verified
|
||||
// with Object.prototype.hasOwnProperty.call), the same set of keys
|
||||
// (although not necessarily the same order), equivalent values for every
|
||||
// corresponding key, and an identical 'prototype' property. Note: this
|
||||
// accounts for both named and indexed properties on Arrays.
|
||||
} else {
|
||||
return objEquiv(actual, expected, strict);
|
||||
}
|
||||
}
|
||||
|
||||
function isArguments(object) {
|
||||
return Object.prototype.toString.call(object) == '[object Arguments]';
|
||||
}
|
||||
|
||||
function objEquiv(a, b, strict) {
|
||||
if (a === null || a === undefined || b === null || b === undefined) { return false; }
|
||||
// if one is a primitive, the other must be same
|
||||
if (util.isPrimitive(a) || util.isPrimitive(b)) { return a === b; }
|
||||
if (strict && Object.getPrototypeOf(a) !== Object.getPrototypeOf(b)) { return false; }
|
||||
const aIsArgs = isArguments(a),
|
||||
bIsArgs = isArguments(b);
|
||||
if ((aIsArgs && !bIsArgs) || (!aIsArgs && bIsArgs)) { return false; }
|
||||
if (aIsArgs) {
|
||||
a = pSlice.call(a);
|
||||
b = pSlice.call(b);
|
||||
return _deepEqual(a, b, strict);
|
||||
}
|
||||
let ka = Object.keys(a),
|
||||
kb = Object.keys(b),
|
||||
key, i;
|
||||
// having the same number of owned properties (keys incorporates
|
||||
// hasOwnProperty)
|
||||
if (ka.length !== kb.length) { return false; }
|
||||
//the same set of keys (although not necessarily the same order),
|
||||
ka.sort();
|
||||
kb.sort();
|
||||
//~~~cheap key test
|
||||
for (i = ka.length - 1; i >= 0; i--) {
|
||||
if (ka[i] !== kb[i]) { return false; }
|
||||
}
|
||||
//equivalent values for every corresponding key, and
|
||||
//~~~possibly expensive deep test
|
||||
for (i = ka.length - 1; i >= 0; i--) {
|
||||
key = ka[i];
|
||||
if (!_deepEqual(a[key], b[key], strict)) { return false; }
|
||||
}
|
||||
return true;
|
||||
}
|
||||
|
||||
// 8. The non-equivalence assertion tests for any deep inequality.
|
||||
// assert.notDeepEqual(actual, expected, message_opt);
|
||||
|
||||
assert.notDeepEqual = function notDeepEqual(actual, expected, message) {
|
||||
if (_deepEqual(actual, expected, false)) {
|
||||
fail(actual, expected, message, 'notDeepEqual', assert.notDeepEqual);
|
||||
}
|
||||
};
|
||||
|
||||
assert.notDeepStrictEqual = notDeepStrictEqual;
|
||||
export function notDeepStrictEqual(actual, expected, message) {
|
||||
if (_deepEqual(actual, expected, true)) {
|
||||
fail(actual, expected, message, 'notDeepStrictEqual', notDeepStrictEqual);
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
// 9. The strict equality assertion tests strict equality, as determined by ===.
|
||||
// assert.strictEqual(actual, expected, message_opt);
|
||||
|
||||
assert.strictEqual = function strictEqual(actual, expected, message) {
|
||||
if (actual !== expected) {
|
||||
fail(actual, expected, message, '===', assert.strictEqual);
|
||||
}
|
||||
};
|
||||
|
||||
// 10. The strict non-equality assertion tests for strict inequality, as
|
||||
// determined by !==. assert.notStrictEqual(actual, expected, message_opt);
|
||||
|
||||
assert.notStrictEqual = function notStrictEqual(actual, expected, message) {
|
||||
if (actual === expected) {
|
||||
fail(actual, expected, message, '!==', assert.notStrictEqual);
|
||||
}
|
||||
};
|
||||
|
||||
function expectedException(actual, expected) {
|
||||
if (!actual || !expected) {
|
||||
return false;
|
||||
}
|
||||
|
||||
if (Object.prototype.toString.call(expected) == '[object RegExp]') {
|
||||
return expected.test(actual);
|
||||
} else if (actual instanceof expected) {
|
||||
return true;
|
||||
} else if (expected.call({}, actual) === true) {
|
||||
return true;
|
||||
}
|
||||
|
||||
return false;
|
||||
}
|
||||
|
||||
function _throws(shouldThrow, block, expected, message) {
|
||||
let actual;
|
||||
|
||||
if (typeof block !== 'function') {
|
||||
throw new TypeError('block must be a function');
|
||||
}
|
||||
|
||||
if (typeof expected === 'string') {
|
||||
message = expected;
|
||||
expected = null;
|
||||
}
|
||||
|
||||
try {
|
||||
block();
|
||||
} catch (e) {
|
||||
actual = e;
|
||||
}
|
||||
|
||||
message = (expected && expected.name ? ' (' + expected.name + ').' : '.') +
|
||||
(message ? ' ' + message : '.');
|
||||
|
||||
if (shouldThrow && !actual) {
|
||||
fail(actual, expected, 'Missing expected exception' + message);
|
||||
}
|
||||
|
||||
if (!shouldThrow && expectedException(actual, expected)) {
|
||||
fail(actual, expected, 'Got unwanted exception' + message);
|
||||
}
|
||||
|
||||
if ((shouldThrow && actual && expected &&
|
||||
!expectedException(actual, expected)) || (!shouldThrow && actual)) {
|
||||
throw actual;
|
||||
}
|
||||
}
|
||||
|
||||
// 11. Expected to throw an error:
|
||||
// assert.throws(block, Error_opt, message_opt);
|
||||
|
||||
assert.throws = function (block, /*optional*/error, /*optional*/message) {
|
||||
_throws.apply(this, [true].concat(pSlice.call(arguments)));
|
||||
};
|
||||
|
||||
// EXTENSION! This is annoying to write outside this module.
|
||||
assert.doesNotThrow = function (block, /*optional*/message) {
|
||||
_throws.apply(this, [false].concat(pSlice.call(arguments)));
|
||||
};
|
||||
|
||||
assert.ifError = function (err) { if (err) { throw err; } };
|
||||
|
||||
function checkIsPromise(obj) {
|
||||
return (obj !== null && typeof obj === 'object' &&
|
||||
typeof obj.then === 'function' &&
|
||||
typeof obj.catch === 'function');
|
||||
}
|
||||
|
||||
const NO_EXCEPTION_SENTINEL = {};
|
||||
async function waitForActual(promiseFn) {
|
||||
let resultPromise;
|
||||
if (typeof promiseFn === 'function') {
|
||||
// Return a rejected promise if `promiseFn` throws synchronously.
|
||||
resultPromise = promiseFn();
|
||||
// Fail in case no promise is returned.
|
||||
if (!checkIsPromise(resultPromise)) {
|
||||
throw new Error('ERR_INVALID_RETURN_VALUE: promiseFn did not return Promise. ' + resultPromise);
|
||||
}
|
||||
} else if (checkIsPromise(promiseFn)) {
|
||||
resultPromise = promiseFn;
|
||||
} else {
|
||||
throw new Error('ERR_INVALID_ARG_TYPE: promiseFn is not Function or Promise. ' + promiseFn);
|
||||
}
|
||||
|
||||
try {
|
||||
await resultPromise;
|
||||
} catch (e) {
|
||||
return e;
|
||||
}
|
||||
return NO_EXCEPTION_SENTINEL;
|
||||
}
|
||||
|
||||
function expectsError(shouldHaveError, actual, message) {
|
||||
if (shouldHaveError && actual === NO_EXCEPTION_SENTINEL) {
|
||||
fail(undefined, 'Error', `Missing expected rejection${message ? ': ' + message : ''}`)
|
||||
} else if (!shouldHaveError && actual !== NO_EXCEPTION_SENTINEL) {
|
||||
fail(actual, undefined, `Got unexpected rejection (${actual.message})${message ? ': ' + message : ''}`)
|
||||
}
|
||||
}
|
||||
|
||||
assert.rejects = async function rejects(promiseFn, message) {
|
||||
expectsError(true, await waitForActual(promiseFn), message);
|
||||
};
|
||||
|
||||
assert.doesNotReject = async function doesNotReject(fn, message) {
|
||||
expectsError(false, await waitForActual(fn), message);
|
||||
};
|
||||
|
||||
// ESM export
|
||||
export default assert;
|
||||
export const AssertionError = assert.AssertionError
|
||||
// export const fail = assert.fail
|
||||
// export const ok = assert.ok
|
||||
export const equal = assert.equal
|
||||
export const notEqual = assert.notEqual
|
||||
export const deepEqual = assert.deepEqual
|
||||
export const deepStrictEqual = assert.deepStrictEqual
|
||||
export const notDeepEqual = assert.notDeepEqual
|
||||
// export const notDeepStrictEqual = assert.notDeepStrictEqual
|
||||
export const strictEqual = assert.strictEqual
|
||||
export const notStrictEqual = assert.notStrictEqual
|
||||
export const throws = assert.throws
|
||||
export const doesNotThrow = assert.doesNotThrow
|
||||
export const ifError = assert.ifError
|
||||
export const rejects = assert.rejects
|
||||
export const doesNotReject = assert.doesNotReject
|
||||
421
test/unit/browser/index.esm.js
Normal file
421
test/unit/browser/index.esm.js
Normal file
@@ -0,0 +1,421 @@
|
||||
/*---------------------------------------------------------------------------------------------
|
||||
* Copyright (c) Microsoft Corporation. All rights reserved.
|
||||
* Licensed under the MIT License. See License.txt in the project root for license information.
|
||||
*--------------------------------------------------------------------------------------------*/
|
||||
|
||||
//@ts-check
|
||||
'use strict';
|
||||
|
||||
const path = require('path');
|
||||
const glob = require('glob');
|
||||
const events = require('events');
|
||||
const mocha = require('mocha');
|
||||
const createStatsCollector = require('../../../node_modules/mocha/lib/stats-collector');
|
||||
const MochaJUnitReporter = require('mocha-junit-reporter');
|
||||
const url = require('url');
|
||||
const minimatch = require('minimatch');
|
||||
const fs = require('fs');
|
||||
const playwright = require('@playwright/test');
|
||||
const { applyReporter } = require('../reporter');
|
||||
const yaserver = require('yaserver');
|
||||
const http = require('http');
|
||||
const { randomBytes } = require('crypto');
|
||||
const minimist = require('minimist');
|
||||
const { promisify } = require('node:util');
|
||||
|
||||
/**
|
||||
* @type {{
|
||||
* run: string;
|
||||
* grep: string;
|
||||
* runGlob: string;
|
||||
* browser: string;
|
||||
* reporter: string;
|
||||
* 'reporter-options': string;
|
||||
* tfs: string;
|
||||
* build: boolean;
|
||||
* debug: boolean;
|
||||
* sequential: boolean;
|
||||
* help: boolean;
|
||||
* }}
|
||||
*/
|
||||
const args = minimist(process.argv.slice(2), {
|
||||
boolean: ['build', 'debug', 'sequential', 'help'],
|
||||
string: ['run', 'grep', 'runGlob', 'browser', 'reporter', 'reporter-options', 'tfs'],
|
||||
default: {
|
||||
build: false,
|
||||
browser: ['chromium', 'firefox', 'webkit'],
|
||||
reporter: process.platform === 'win32' ? 'list' : 'spec',
|
||||
'reporter-options': ''
|
||||
},
|
||||
alias: {
|
||||
grep: ['g', 'f'],
|
||||
runGlob: ['glob', 'runGrep'],
|
||||
debug: ['debug-browser'],
|
||||
help: 'h'
|
||||
},
|
||||
describe: {
|
||||
build: 'run with build output (out-build)',
|
||||
run: 'only run tests matching <relative_file_path>',
|
||||
grep: 'only run tests matching <pattern>',
|
||||
debug: 'do not run browsers headless',
|
||||
sequential: 'only run suites for a single browser at a time',
|
||||
browser: 'browsers in which tests should run',
|
||||
reporter: 'the mocha reporter',
|
||||
'reporter-options': 'the mocha reporter options',
|
||||
tfs: 'tfs',
|
||||
help: 'show the help'
|
||||
}
|
||||
});
|
||||
|
||||
if (args.help) {
|
||||
console.log(`Usage: node ${process.argv[1]} [options]
|
||||
|
||||
Options:
|
||||
--build run with build output (out-build)
|
||||
--run <relative_file_path> only run tests matching <relative_file_path>
|
||||
--grep, -g, -f <pattern> only run tests matching <pattern>
|
||||
--debug, --debug-browser do not run browsers headless
|
||||
--sequential only run suites for a single browser at a time
|
||||
--browser <browser> browsers in which tests should run
|
||||
--reporter <reporter> the mocha reporter
|
||||
--reporter-options <reporter-options> the mocha reporter options
|
||||
--tfs <tfs> tfs
|
||||
--help, -h show the help`);
|
||||
process.exit(0);
|
||||
}
|
||||
|
||||
const isDebug = !!args.debug;
|
||||
|
||||
const withReporter = (function () {
|
||||
if (args.tfs) {
|
||||
{
|
||||
return (browserType, runner) => {
|
||||
new mocha.reporters.Spec(runner);
|
||||
new MochaJUnitReporter(runner, {
|
||||
reporterOptions: {
|
||||
testsuitesTitle: `${args.tfs} ${process.platform}`,
|
||||
mochaFile: process.env.BUILD_ARTIFACTSTAGINGDIRECTORY ? path.join(process.env.BUILD_ARTIFACTSTAGINGDIRECTORY, `test-results/${process.platform}-${process.arch}-${browserType}-${args.tfs.toLowerCase().replace(/[^\w]/g, '-')}-results.xml`) : undefined
|
||||
}
|
||||
});
|
||||
};
|
||||
}
|
||||
} else {
|
||||
return (_, runner) => applyReporter(runner, args);
|
||||
}
|
||||
})();
|
||||
|
||||
const outdir = args.build ? 'out-build' : 'out';
|
||||
const rootDir = path.resolve(__dirname, '..', '..', '..');
|
||||
const out = path.join(rootDir, `${outdir}`);
|
||||
|
||||
function ensureIsArray(a) {
|
||||
return Array.isArray(a) ? a : [a];
|
||||
}
|
||||
|
||||
const testModules = (async function () {
|
||||
|
||||
const excludeGlob = '**/{node,electron-sandbox,electron-main}/**/*.test.js';
|
||||
let isDefaultModules = true;
|
||||
let promise;
|
||||
|
||||
if (args.run) {
|
||||
// use file list (--run)
|
||||
isDefaultModules = false;
|
||||
promise = Promise.resolve(ensureIsArray(args.run).map(file => {
|
||||
file = file.replace(/^src/, 'out');
|
||||
file = file.replace(/\.ts$/, '.js');
|
||||
return path.relative(out, file);
|
||||
}));
|
||||
|
||||
} else {
|
||||
// glob patterns (--glob)
|
||||
const defaultGlob = '**/*.test.js';
|
||||
const pattern = args.runGlob || defaultGlob;
|
||||
isDefaultModules = pattern === defaultGlob;
|
||||
|
||||
promise = new Promise((resolve, reject) => {
|
||||
glob(pattern, { cwd: out }, (err, files) => {
|
||||
if (err) {
|
||||
reject(err);
|
||||
} else {
|
||||
resolve(files);
|
||||
}
|
||||
});
|
||||
});
|
||||
}
|
||||
|
||||
return promise.then(files => {
|
||||
const modules = [];
|
||||
for (const file of files) {
|
||||
if (!minimatch(file, excludeGlob)) {
|
||||
modules.push(file.replace(/\.js$/, ''));
|
||||
|
||||
} else if (!isDefaultModules) {
|
||||
console.warn(`DROPPONG ${file} because it cannot be run inside a browser`);
|
||||
}
|
||||
}
|
||||
return modules;
|
||||
});
|
||||
})();
|
||||
|
||||
function consoleLogFn(msg) {
|
||||
const type = msg.type();
|
||||
const candidate = console[type];
|
||||
if (candidate) {
|
||||
return candidate;
|
||||
}
|
||||
|
||||
if (type === 'warning') {
|
||||
return console.warn;
|
||||
}
|
||||
|
||||
return console.log;
|
||||
}
|
||||
|
||||
async function createServer() {
|
||||
// Demand a prefix to avoid issues with other services on the
|
||||
// machine being able to access the test server.
|
||||
const prefix = '/' + randomBytes(16).toString('hex');
|
||||
const serveStatic = await yaserver.createServer({ rootDir });
|
||||
|
||||
/** Handles a request for a remote method call, invoking `fn` and returning the result */
|
||||
const remoteMethod = async (req, response, fn) => {
|
||||
const params = await new Promise((resolve, reject) => {
|
||||
const body = [];
|
||||
req.on('data', chunk => body.push(chunk));
|
||||
req.on('end', () => resolve(JSON.parse(Buffer.concat(body).toString())));
|
||||
req.on('error', reject);
|
||||
});
|
||||
try {
|
||||
const result = await fn(...params);
|
||||
response.writeHead(200, { 'Content-Type': 'application/json' });
|
||||
response.end(JSON.stringify(result));
|
||||
} catch (err) {
|
||||
response.writeHead(500);
|
||||
response.end(err.message);
|
||||
}
|
||||
};
|
||||
|
||||
const server = http.createServer((request, response) => {
|
||||
if (!request.url?.startsWith(prefix)) {
|
||||
return response.writeHead(404).end();
|
||||
}
|
||||
|
||||
// rewrite the URL so the static server can handle the request correctly
|
||||
request.url = request.url.slice(prefix.length);
|
||||
|
||||
function massagePath(p) {
|
||||
// TODO@jrieken FISHY but it enables snapshot
|
||||
// in ESM browser tests
|
||||
p = String(p).replace(/\\/g, '/').replace(prefix, rootDir);
|
||||
return p;
|
||||
}
|
||||
|
||||
switch (request.url) {
|
||||
case '/remoteMethod/__readFileInTests':
|
||||
return remoteMethod(request, response, p => fs.promises.readFile(massagePath(p), 'utf-8'));
|
||||
case '/remoteMethod/__writeFileInTests':
|
||||
return remoteMethod(request, response, (p, contents) => fs.promises.writeFile(massagePath(p), contents));
|
||||
case '/remoteMethod/__readDirInTests':
|
||||
return remoteMethod(request, response, p => fs.promises.readdir(massagePath(p)));
|
||||
case '/remoteMethod/__unlinkInTests':
|
||||
return remoteMethod(request, response, p => fs.promises.unlink(massagePath(p)));
|
||||
case '/remoteMethod/__mkdirPInTests':
|
||||
return remoteMethod(request, response, p => fs.promises.mkdir(massagePath(p), { recursive: true }));
|
||||
default:
|
||||
return serveStatic.handle(request, response);
|
||||
}
|
||||
});
|
||||
|
||||
return new Promise((resolve, reject) => {
|
||||
server.listen(0, 'localhost', () => {
|
||||
resolve({
|
||||
dispose: () => server.close(),
|
||||
// @ts-ignore
|
||||
url: `http://localhost:${server.address().port}${prefix}`
|
||||
});
|
||||
});
|
||||
server.on('error', reject);
|
||||
});
|
||||
}
|
||||
|
||||
async function runTestsInBrowser(testModules, browserType) {
|
||||
const server = await createServer();
|
||||
const browser = await playwright[browserType].launch({ headless: !Boolean(args.debug), devtools: Boolean(args.debug) });
|
||||
const context = await browser.newContext();
|
||||
const page = await context.newPage();
|
||||
const target = new URL(server.url + '/test/unit/browser/renderer.esm.html');
|
||||
target.searchParams.set('baseUrl', url.pathToFileURL(path.join(rootDir, 'src')).toString());
|
||||
if (args.build) {
|
||||
target.searchParams.set('build', 'true');
|
||||
}
|
||||
if (process.env.BUILD_ARTIFACTSTAGINGDIRECTORY) {
|
||||
target.searchParams.set('ci', 'true');
|
||||
}
|
||||
|
||||
// append CSS modules as query-param
|
||||
await promisify(require('glob'))('**/*.css', { cwd: out }).then(async cssModules => {
|
||||
const cssData = await new Response((await new Response(cssModules.join(',')).blob()).stream().pipeThrough(new CompressionStream('gzip'))).arrayBuffer();
|
||||
target.searchParams.set('_devCssData', Buffer.from(cssData).toString('base64'));
|
||||
});
|
||||
|
||||
const emitter = new events.EventEmitter();
|
||||
await page.exposeFunction('mocha_report', (type, data1, data2) => {
|
||||
emitter.emit(type, data1, data2);
|
||||
});
|
||||
|
||||
await page.goto(target.href);
|
||||
|
||||
if (args.build) {
|
||||
const nlsMessages = await fs.promises.readFile(path.join(out, 'nls.messages.json'), 'utf8');
|
||||
await page.evaluate(value => {
|
||||
// when running from `out-build`, ensure to load the default
|
||||
// messages file, because all `nls.localize` calls have their
|
||||
// english values removed and replaced by an index.
|
||||
// @ts-ignore
|
||||
globalThis._VSCODE_NLS_MESSAGES = JSON.parse(value);
|
||||
}, nlsMessages);
|
||||
}
|
||||
|
||||
page.on('console', async msg => {
|
||||
consoleLogFn(msg)(msg.text(), await Promise.all(msg.args().map(async arg => await arg.jsonValue())));
|
||||
});
|
||||
|
||||
withReporter(browserType, new EchoRunner(emitter, browserType.toUpperCase()));
|
||||
|
||||
// collection failures for console printing
|
||||
const failingModuleIds = [];
|
||||
const failingTests = [];
|
||||
emitter.on('fail', (test, err) => {
|
||||
failingTests.push({ title: test.fullTitle, message: err.message });
|
||||
|
||||
if (err.stack) {
|
||||
const regex = /(vs\/.*\.test)\.js/;
|
||||
for (const line of String(err.stack).split('\n')) {
|
||||
const match = regex.exec(line);
|
||||
if (match) {
|
||||
failingModuleIds.push(match[1]);
|
||||
return;
|
||||
}
|
||||
}
|
||||
}
|
||||
});
|
||||
|
||||
try {
|
||||
// @ts-expect-error
|
||||
await page.evaluate(opts => loadAndRun(opts), {
|
||||
modules: testModules,
|
||||
grep: args.grep,
|
||||
});
|
||||
} catch (err) {
|
||||
console.error(err);
|
||||
}
|
||||
if (!isDebug) {
|
||||
server?.dispose();
|
||||
await browser.close();
|
||||
}
|
||||
|
||||
if (failingTests.length > 0) {
|
||||
let res = `The followings tests are failing:\n - ${failingTests.map(({ title, message }) => `${title} (reason: ${message})`).join('\n - ')}`;
|
||||
|
||||
if (failingModuleIds.length > 0) {
|
||||
res += `\n\nTo DEBUG, open ${browserType.toUpperCase()} and navigate to ${target.href}?${failingModuleIds.map(module => `m=${module}`).join('&')}`;
|
||||
}
|
||||
|
||||
return `${res}\n`;
|
||||
}
|
||||
}
|
||||
|
||||
class EchoRunner extends events.EventEmitter {
|
||||
|
||||
constructor(event, title = '') {
|
||||
super();
|
||||
createStatsCollector(this);
|
||||
event.on('start', () => this.emit('start'));
|
||||
event.on('end', () => this.emit('end'));
|
||||
event.on('suite', (suite) => this.emit('suite', EchoRunner.deserializeSuite(suite, title)));
|
||||
event.on('suite end', (suite) => this.emit('suite end', EchoRunner.deserializeSuite(suite, title)));
|
||||
event.on('test', (test) => this.emit('test', EchoRunner.deserializeRunnable(test)));
|
||||
event.on('test end', (test) => this.emit('test end', EchoRunner.deserializeRunnable(test)));
|
||||
event.on('hook', (hook) => this.emit('hook', EchoRunner.deserializeRunnable(hook)));
|
||||
event.on('hook end', (hook) => this.emit('hook end', EchoRunner.deserializeRunnable(hook)));
|
||||
event.on('pass', (test) => this.emit('pass', EchoRunner.deserializeRunnable(test)));
|
||||
event.on('fail', (test, err) => this.emit('fail', EchoRunner.deserializeRunnable(test, title), EchoRunner.deserializeError(err)));
|
||||
event.on('pending', (test) => this.emit('pending', EchoRunner.deserializeRunnable(test)));
|
||||
}
|
||||
|
||||
static deserializeSuite(suite, titleExtra) {
|
||||
return {
|
||||
root: suite.root,
|
||||
suites: suite.suites,
|
||||
tests: suite.tests,
|
||||
title: titleExtra && suite.title ? `${suite.title} - /${titleExtra}/` : suite.title,
|
||||
titlePath: () => suite.titlePath,
|
||||
fullTitle: () => suite.fullTitle,
|
||||
timeout: () => suite.timeout,
|
||||
retries: () => suite.retries,
|
||||
slow: () => suite.slow,
|
||||
bail: () => suite.bail
|
||||
};
|
||||
}
|
||||
|
||||
static deserializeRunnable(runnable, titleExtra) {
|
||||
return {
|
||||
title: runnable.title,
|
||||
fullTitle: () => titleExtra && runnable.fullTitle ? `${runnable.fullTitle} - /${titleExtra}/` : runnable.fullTitle,
|
||||
titlePath: () => runnable.titlePath,
|
||||
async: runnable.async,
|
||||
slow: () => runnable.slow,
|
||||
speed: runnable.speed,
|
||||
duration: runnable.duration,
|
||||
currentRetry: () => runnable.currentRetry,
|
||||
};
|
||||
}
|
||||
|
||||
static deserializeError(err) {
|
||||
const inspect = err.inspect;
|
||||
err.inspect = () => inspect;
|
||||
return err;
|
||||
}
|
||||
}
|
||||
|
||||
testModules.then(async modules => {
|
||||
|
||||
// run tests in selected browsers
|
||||
const browserTypes = Array.isArray(args.browser)
|
||||
? args.browser : [args.browser];
|
||||
|
||||
let messages = [];
|
||||
let didFail = false;
|
||||
|
||||
try {
|
||||
if (args.sequential) {
|
||||
for (const browserType of browserTypes) {
|
||||
messages.push(await runTestsInBrowser(modules, browserType));
|
||||
}
|
||||
} else {
|
||||
messages = await Promise.all(browserTypes.map(async browserType => {
|
||||
return await runTestsInBrowser(modules, browserType);
|
||||
}));
|
||||
}
|
||||
} catch (err) {
|
||||
console.error(err);
|
||||
if (!isDebug) {
|
||||
process.exit(1);
|
||||
}
|
||||
}
|
||||
|
||||
// aftermath
|
||||
for (const msg of messages) {
|
||||
if (msg) {
|
||||
didFail = true;
|
||||
console.log(msg);
|
||||
}
|
||||
}
|
||||
if (!isDebug) {
|
||||
process.exit(didFail ? 1 : 0);
|
||||
}
|
||||
|
||||
}).catch(err => {
|
||||
console.error(err);
|
||||
});
|
||||
247
test/unit/browser/renderer.esm.html
Normal file
247
test/unit/browser/renderer.esm.html
Normal file
@@ -0,0 +1,247 @@
|
||||
<html>
|
||||
|
||||
<head>
|
||||
<meta charset="utf-8">
|
||||
<title>VSCode Tests</title>
|
||||
<link href="../../../node_modules/mocha/mocha.css" rel="stylesheet" />
|
||||
</head>
|
||||
|
||||
<body>
|
||||
<div id="mocha"></div>
|
||||
<script src="../../../node_modules/mocha/mocha.js"></script>
|
||||
<script>
|
||||
// !!! DO NOT CHANGE !!!
|
||||
// Our unit tests may run in environments without
|
||||
// display (e.g. from builds) and tests may by
|
||||
// accident bring up native dialogs or even open
|
||||
// windows. This we cannot allow as it may crash
|
||||
// the test run.
|
||||
// !!! DO NOT CHANGE !!!
|
||||
window.open = function () { throw new Error('window.open() is not supported in tests!'); };
|
||||
window.alert = function () { throw new Error('window.alert() is not supported in tests!'); }
|
||||
window.confirm = function () { throw new Error('window.confirm() is not supported in tests!'); }
|
||||
|
||||
// Ignore uncaught cancelled promise errors
|
||||
window.addEventListener('unhandledrejection', e => {
|
||||
const name = e && e.reason && e.reason.name;
|
||||
|
||||
if (name === 'Canceled') {
|
||||
e.preventDefault();
|
||||
e.stopPropagation();
|
||||
}
|
||||
});
|
||||
|
||||
const urlParams = new URLSearchParams(window.location.search);
|
||||
const isCI = urlParams.get('ci');
|
||||
|
||||
mocha.setup({
|
||||
ui: 'tdd',
|
||||
timeout: isCI ? 30000 : 5000
|
||||
});
|
||||
</script>
|
||||
|
||||
<script>
|
||||
const isBuild = urlParams.get('build');
|
||||
const tasks =[];
|
||||
|
||||
// configure loader
|
||||
const baseUrl = window.location.href;
|
||||
|
||||
|
||||
// generate import map
|
||||
const importMapParent = document.currentScript.parentNode;
|
||||
const importMap = {
|
||||
imports: {
|
||||
vs: new URL(`../../../${!!isBuild ? 'out-build' : 'out'}/vs`, baseUrl).href,
|
||||
assert: new URL('../assert-esm.js', baseUrl).href,
|
||||
sinon: new URL('../../../node_modules/sinon/pkg/sinon-esm.js', baseUrl).href,
|
||||
'sinon-test': new URL('../../../node_modules/sinon-test/dist/sinon-test-es.js', baseUrl).href,
|
||||
'@xterm/xterm': new URL('../../../node_modules/@xterm/xterm/lib/xterm.js', baseUrl).href,
|
||||
'@vscode/iconv-lite-umd': new URL('../../../node_modules/@vscode/iconv-lite-umd/lib/iconv-lite-umd.js', baseUrl).href,
|
||||
jschardet: new URL('../../../node_modules/jschardet/dist/jschardet.min.js', baseUrl).href
|
||||
}
|
||||
}
|
||||
|
||||
// ---- CSS tricks
|
||||
|
||||
const cssDataBase64 = urlParams.get('_devCssData');
|
||||
if (typeof cssDataBase64 === 'string') {
|
||||
|
||||
const style = document.createElement('style');
|
||||
style.type = 'text/css';
|
||||
document.head.appendChild(style);
|
||||
|
||||
globalThis._VSCODE_CSS_LOAD = function (url) {
|
||||
// debugger;
|
||||
style.sheet.insertRule(`@import url(${url});`);
|
||||
};
|
||||
|
||||
const cssData = Uint8Array.from(atob(cssDataBase64), c => c.charCodeAt(0));
|
||||
tasks.push(new Response(new Blob([cssData], {type:'application/octet-binary'}).stream().pipeThrough(new DecompressionStream('gzip'))).text().then(value => {
|
||||
const cssModules = value.split(',');
|
||||
for (const cssModule of cssModules) {
|
||||
const cssUrl = new URL(`../../../out/${cssModule}`, baseUrl).href;
|
||||
const jsSrc = `globalThis._VSCODE_CSS_LOAD('${cssUrl}');\n`;
|
||||
const blob = new Blob([jsSrc], { type: 'application/javascript' });
|
||||
importMap.imports[cssUrl] = URL.createObjectURL(blob);
|
||||
}
|
||||
}).catch(err => {
|
||||
console.error(err);
|
||||
}));
|
||||
}
|
||||
|
||||
const initPromise = Promise.allSettled(tasks).then(() => {
|
||||
|
||||
const rawImportMap = JSON.stringify(importMap, undefined, 2);
|
||||
const importMapScript = document.createElement('script');
|
||||
importMapScript.type = 'importmap';
|
||||
importMapScript.textContent = rawImportMap;
|
||||
importMapParent.appendChild(importMapScript);
|
||||
|
||||
}).then(() => {
|
||||
const bootstrapScript = document.createElement('script');
|
||||
bootstrapScript.type = 'module';
|
||||
bootstrapScript.textContent = document.getElementById('bootstrap').textContent
|
||||
document.getElementById('bootstrap').remove();
|
||||
document.body.append(bootstrapScript);
|
||||
});
|
||||
|
||||
// set up require
|
||||
|
||||
globalThis._VSCODE_FILE_ROOT = new URL('../../../src', baseUrl).href;
|
||||
globalThis.require = {
|
||||
paths: {
|
||||
xterm: new URL('../../../node_modules/xterm', baseUrl).href,
|
||||
'@vscode/iconv-lite-umd': new URL('../../../node_modules/@vscode/iconv-lite-umd', baseUrl).href,
|
||||
jschardet: new URL('../../../node_modules/jschardet', baseUrl).href
|
||||
}
|
||||
}
|
||||
</script>
|
||||
<script>
|
||||
function serializeSuite(suite) {
|
||||
return {
|
||||
root: suite.root,
|
||||
suites: suite.suites.map(serializeSuite),
|
||||
tests: suite.tests.map(serializeRunnable),
|
||||
title: suite.title,
|
||||
fullTitle: suite.fullTitle(),
|
||||
titlePath: suite.titlePath(),
|
||||
timeout: suite.timeout(),
|
||||
retries: suite.retries(),
|
||||
slow: suite.slow(),
|
||||
bail: suite.bail()
|
||||
};
|
||||
}
|
||||
function serializeRunnable(runnable) {
|
||||
return {
|
||||
title: runnable.title,
|
||||
titlePath: runnable.titlePath(),
|
||||
fullTitle: runnable.fullTitle(),
|
||||
async: runnable.async,
|
||||
slow: runnable.slow(),
|
||||
speed: runnable.speed,
|
||||
duration: runnable.duration,
|
||||
currentRetry: runnable.currentRetry(),
|
||||
};
|
||||
}
|
||||
function serializeError(err) {
|
||||
return {
|
||||
message: err.message,
|
||||
stack: err.stack,
|
||||
actual: err.actual,
|
||||
expected: err.expected,
|
||||
uncaught: err.uncaught,
|
||||
showDiff: err.showDiff,
|
||||
inspect: typeof err.inspect === 'function' ? err.inspect() : ''
|
||||
};
|
||||
}
|
||||
function PlaywrightReporter(runner) {
|
||||
runner.on('start', () => window.mocha_report('start'));
|
||||
runner.on('end', () => window.mocha_report('end'));
|
||||
runner.on('suite', suite => window.mocha_report('suite', serializeSuite(suite)));
|
||||
runner.on('suite end', suite => window.mocha_report('suite end', serializeSuite(suite)));
|
||||
runner.on('test', test => window.mocha_report('test', serializeRunnable(test)));
|
||||
runner.on('test end', test => window.mocha_report('test end', serializeRunnable(test)));
|
||||
runner.on('hook', hook => window.mocha_report('hook', serializeRunnable(hook)));
|
||||
runner.on('hook end', hook => window.mocha_report('hook end', serializeRunnable(hook)));
|
||||
runner.on('pass', test => window.mocha_report('pass', serializeRunnable(test)));
|
||||
runner.on('fail', (test, err) => window.mocha_report('fail', serializeRunnable(test), serializeError(err)));
|
||||
runner.on('pending', test => window.mocha_report('pending', serializeRunnable(test)));
|
||||
};
|
||||
|
||||
const remoteMethods = [
|
||||
'__readFileInTests',
|
||||
'__writeFileInTests',
|
||||
'__readDirInTests',
|
||||
'__unlinkInTests',
|
||||
'__mkdirPInTests',
|
||||
];
|
||||
|
||||
for (const method of remoteMethods) {
|
||||
const prefix = window.location.pathname.split('/')[1];
|
||||
globalThis[method] = async (...args) => {
|
||||
const res = await fetch(`/${prefix}/remoteMethod/${method}`, {
|
||||
body: JSON.stringify(args),
|
||||
method: 'POST',
|
||||
headers: { 'Content-Type': 'application/json' }
|
||||
});
|
||||
|
||||
return res.json();
|
||||
}
|
||||
}
|
||||
|
||||
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) => import(`../../../out/${file}.js`).then(resolve, err => {
|
||||
console.log("BAD " + file + JSON.stringify(err, undefined, '\t'));
|
||||
resolve({});
|
||||
}));
|
||||
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);
|
||||
}
|
||||
}
|
||||
|
||||
let _resolveTestData;
|
||||
let _resolveTestRun;
|
||||
globalThis._VSCODE_TEST_RUN = new Promise(resolve => _resolveTestData = resolve)
|
||||
|
||||
window.loadAndRun = async function loadAndRun(data, manual = false) {
|
||||
_resolveTestData({data, manual})
|
||||
return new Promise(resolve => _resolveTestRun = resolve);
|
||||
}
|
||||
|
||||
const modules = new URL(window.location.href).searchParams.getAll('m');
|
||||
if (Array.isArray(modules) && modules.length > 0) {
|
||||
console.log('MANUALLY running tests', modules);
|
||||
|
||||
loadAndRun(modules, true).then(() => console.log('done'), err => console.log(err));
|
||||
}
|
||||
</script>
|
||||
|
||||
|
||||
<script type="text" id="bootstrap">
|
||||
|
||||
const {data: {modules, grep}, manual} = await globalThis._VSCODE_TEST_RUN
|
||||
|
||||
// load
|
||||
await loadModules(modules);
|
||||
|
||||
// run
|
||||
await new Promise((resolve, reject) => {
|
||||
if (grep) {
|
||||
mocha.grep(grep);
|
||||
}
|
||||
|
||||
if (!manual) {
|
||||
mocha.reporter(PlaywrightReporter);
|
||||
}
|
||||
mocha.run(failCount => resolve(failCount === 0));
|
||||
});
|
||||
|
||||
_resolveTestRun();
|
||||
|
||||
</script>
|
||||
</body>
|
||||
|
||||
</html>
|
||||
358
test/unit/electron/index.esm.js
Normal file
358
test/unit/electron/index.esm.js
Normal file
@@ -0,0 +1,358 @@
|
||||
/*---------------------------------------------------------------------------------------------
|
||||
* Copyright (c) Microsoft Corporation. All rights reserved.
|
||||
* Licensed under the MIT License. See License.txt in the project root for license information.
|
||||
*--------------------------------------------------------------------------------------------*/
|
||||
|
||||
//@ts-check
|
||||
'use strict';
|
||||
|
||||
// mocha disables running through electron by default. Note that this must
|
||||
// come before any mocha imports.
|
||||
process.env.MOCHA_COLORS = '1';
|
||||
|
||||
const { app, BrowserWindow, ipcMain, crashReporter, session } = require('electron');
|
||||
const product = require('../../../product.json');
|
||||
const { tmpdir } = require('os');
|
||||
const { existsSync, mkdirSync } = require('fs');
|
||||
const path = require('path');
|
||||
const mocha = require('mocha');
|
||||
const events = require('events');
|
||||
const MochaJUnitReporter = require('mocha-junit-reporter');
|
||||
const url = require('url');
|
||||
const net = require('net');
|
||||
const createStatsCollector = require('mocha/lib/stats-collector');
|
||||
const { applyReporter, importMochaReporter } = require('../reporter');
|
||||
|
||||
const minimist = require('minimist');
|
||||
|
||||
/**
|
||||
* @type {{
|
||||
* grep: string;
|
||||
* run: string;
|
||||
* runGlob: string;
|
||||
* dev: boolean;
|
||||
* reporter: string;
|
||||
* 'reporter-options': string;
|
||||
* 'waitServer': string;
|
||||
* timeout: string;
|
||||
* 'crash-reporter-directory': string;
|
||||
* tfs: string;
|
||||
* build: boolean;
|
||||
* coverage: boolean;
|
||||
* coveragePath: string;
|
||||
* coverageFormats: string | string[];
|
||||
* 'per-test-coverage': boolean;
|
||||
* help: boolean;
|
||||
* }}
|
||||
*/
|
||||
const args = minimist(process.argv.slice(2), {
|
||||
string: ['grep', 'run', 'runGlob', 'reporter', 'reporter-options', 'waitServer', 'timeout', 'crash-reporter-directory', 'tfs', 'coveragePath', 'coverageFormats'],
|
||||
boolean: ['build', 'coverage', 'help', 'dev', 'per-test-coverage'],
|
||||
alias: {
|
||||
'grep': ['g', 'f'],
|
||||
'runGlob': ['glob', 'runGrep'],
|
||||
'dev': ['dev-tools', 'devTools'],
|
||||
'help': 'h'
|
||||
},
|
||||
default: {
|
||||
'reporter': 'spec',
|
||||
'reporter-options': ''
|
||||
}
|
||||
});
|
||||
|
||||
if (args.help) {
|
||||
console.log(`Usage: node ${process.argv[1]} [options]
|
||||
|
||||
Options:
|
||||
--grep, -g, -f <pattern> only run tests matching <pattern>
|
||||
--run <file> only run tests from <file>
|
||||
--runGlob, --glob, --runGrep <file_pattern> only run tests matching <file_pattern>
|
||||
--build run with build output (out-build)
|
||||
--coverage generate coverage report
|
||||
--per-test-coverage generate a per-test V8 coverage report, only valid with the full-json-stream reporter
|
||||
--dev, --dev-tools, --devTools <window> open dev tools, keep window open, reuse app data
|
||||
--reporter <reporter> the mocha reporter (default: "spec")
|
||||
--reporter-options <options> the mocha reporter options (default: "")
|
||||
--waitServer <port> port to connect to and wait before running tests
|
||||
--timeout <ms> timeout for tests
|
||||
--crash-reporter-directory <path> crash reporter directory
|
||||
--tfs <url> TFS server URL
|
||||
--help, -h show the help`);
|
||||
process.exit(0);
|
||||
}
|
||||
|
||||
let crashReporterDirectory = args['crash-reporter-directory'];
|
||||
if (crashReporterDirectory) {
|
||||
crashReporterDirectory = path.normalize(crashReporterDirectory);
|
||||
|
||||
if (!path.isAbsolute(crashReporterDirectory)) {
|
||||
console.error(`The path '${crashReporterDirectory}' specified for --crash-reporter-directory must be absolute.`);
|
||||
app.exit(1);
|
||||
}
|
||||
|
||||
if (!existsSync(crashReporterDirectory)) {
|
||||
try {
|
||||
mkdirSync(crashReporterDirectory);
|
||||
} catch (error) {
|
||||
console.error(`The path '${crashReporterDirectory}' specified for --crash-reporter-directory does not seem to exist or cannot be created.`);
|
||||
app.exit(1);
|
||||
}
|
||||
}
|
||||
|
||||
// Crashes are stored in the crashDumps directory by default, so we
|
||||
// need to change that directory to the provided one
|
||||
console.log(`Found --crash-reporter-directory argument. Setting crashDumps directory to be '${crashReporterDirectory}'`);
|
||||
app.setPath('crashDumps', crashReporterDirectory);
|
||||
|
||||
crashReporter.start({
|
||||
companyName: 'Microsoft',
|
||||
productName: process.env['VSCODE_DEV'] ? `${product.nameShort} Dev` : product.nameShort,
|
||||
uploadToServer: false,
|
||||
compress: true
|
||||
});
|
||||
}
|
||||
|
||||
if (!args.dev) {
|
||||
app.setPath('userData', path.join(tmpdir(), `vscode-tests-${Date.now()}`));
|
||||
}
|
||||
|
||||
function deserializeSuite(suite) {
|
||||
return {
|
||||
root: suite.root,
|
||||
suites: suite.suites,
|
||||
tests: suite.tests,
|
||||
title: suite.title,
|
||||
titlePath: () => suite.titlePath,
|
||||
fullTitle: () => suite.fullTitle,
|
||||
timeout: () => suite.timeout,
|
||||
retries: () => suite.retries,
|
||||
slow: () => suite.slow,
|
||||
bail: () => suite.bail
|
||||
};
|
||||
}
|
||||
|
||||
function deserializeRunnable(runnable) {
|
||||
return {
|
||||
title: runnable.title,
|
||||
titlePath: () => runnable.titlePath,
|
||||
fullTitle: () => runnable.fullTitle,
|
||||
async: runnable.async,
|
||||
slow: () => runnable.slow,
|
||||
speed: runnable.speed,
|
||||
duration: runnable.duration,
|
||||
currentRetry: () => runnable.currentRetry
|
||||
};
|
||||
}
|
||||
|
||||
function deserializeError(err) {
|
||||
const inspect = err.inspect;
|
||||
err.inspect = () => inspect;
|
||||
// Unfortunately, mocha rewrites and formats err.actual/err.expected.
|
||||
// This formatting is hard to reverse, so err.*JSON includes the unformatted value.
|
||||
if (err.actual) {
|
||||
err.actual = JSON.parse(err.actual).value;
|
||||
err.actualJSON = err.actual;
|
||||
}
|
||||
if (err.expected) {
|
||||
err.expected = JSON.parse(err.expected).value;
|
||||
err.expectedJSON = err.expected;
|
||||
}
|
||||
return err;
|
||||
}
|
||||
|
||||
class IPCRunner extends events.EventEmitter {
|
||||
|
||||
constructor(win) {
|
||||
super();
|
||||
|
||||
this.didFail = false;
|
||||
this.didEnd = false;
|
||||
|
||||
ipcMain.on('start', () => this.emit('start'));
|
||||
ipcMain.on('end', () => {
|
||||
this.didEnd = true;
|
||||
this.emit('end');
|
||||
});
|
||||
ipcMain.on('suite', (e, suite) => this.emit('suite', deserializeSuite(suite)));
|
||||
ipcMain.on('suite end', (e, suite) => this.emit('suite end', deserializeSuite(suite)));
|
||||
ipcMain.on('test', (e, test) => this.emit('test', deserializeRunnable(test)));
|
||||
ipcMain.on('test end', (e, test) => this.emit('test end', deserializeRunnable(test)));
|
||||
ipcMain.on('hook', (e, hook) => this.emit('hook', deserializeRunnable(hook)));
|
||||
ipcMain.on('hook end', (e, hook) => this.emit('hook end', deserializeRunnable(hook)));
|
||||
ipcMain.on('pass', (e, test) => this.emit('pass', deserializeRunnable(test)));
|
||||
ipcMain.on('fail', (e, test, err) => {
|
||||
this.didFail = true;
|
||||
this.emit('fail', deserializeRunnable(test), deserializeError(err));
|
||||
});
|
||||
ipcMain.on('pending', (e, test) => this.emit('pending', deserializeRunnable(test)));
|
||||
|
||||
ipcMain.handle('startCoverage', async () => {
|
||||
win.webContents.debugger.attach();
|
||||
await win.webContents.debugger.sendCommand('Debugger.enable');
|
||||
await win.webContents.debugger.sendCommand('Profiler.enable');
|
||||
await win.webContents.debugger.sendCommand('Profiler.startPreciseCoverage', {
|
||||
detailed: true,
|
||||
allowTriggeredUpdates: false,
|
||||
});
|
||||
});
|
||||
|
||||
const coverageScriptsReported = new Set();
|
||||
ipcMain.handle('snapshotCoverage', async (_, test) => {
|
||||
const coverage = await win.webContents.debugger.sendCommand('Profiler.takePreciseCoverage');
|
||||
await Promise.all(coverage.result.map(async (r) => {
|
||||
if (!coverageScriptsReported.has(r.scriptId)) {
|
||||
coverageScriptsReported.add(r.scriptId);
|
||||
const src = await win.webContents.debugger.sendCommand('Debugger.getScriptSource', { scriptId: r.scriptId });
|
||||
r.source = src.scriptSource;
|
||||
}
|
||||
}));
|
||||
|
||||
if (!test) {
|
||||
this.emit('coverage init', coverage);
|
||||
} else {
|
||||
this.emit('coverage increment', test, coverage);
|
||||
}
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
app.on('ready', () => {
|
||||
|
||||
// needed when loading resources from the renderer, e.g xterm.js or the encoding lib
|
||||
session.defaultSession.protocol.registerFileProtocol('vscode-file', (request, callback) => {
|
||||
const path = new URL(request.url).pathname;
|
||||
callback({ path });
|
||||
});
|
||||
|
||||
ipcMain.on('error', (_, err) => {
|
||||
if (!args.dev) {
|
||||
console.error(err);
|
||||
app.exit(1);
|
||||
}
|
||||
});
|
||||
|
||||
// We need to provide a basic `ISandboxConfiguration`
|
||||
// for our preload script to function properly because
|
||||
// some of our types depend on it (e.g. product.ts).
|
||||
ipcMain.handle('vscode:test-vscode-window-config', async () => {
|
||||
return {
|
||||
product: {
|
||||
version: '1.x.y',
|
||||
nameShort: 'Code - OSS Dev',
|
||||
nameLong: 'Code - OSS Dev',
|
||||
applicationName: 'code-oss',
|
||||
dataFolderName: '.vscode-oss',
|
||||
urlProtocol: 'code-oss',
|
||||
}
|
||||
};
|
||||
});
|
||||
|
||||
// No-op since invoke the IPC as part of IIFE in the preload.
|
||||
ipcMain.handle('vscode:fetchShellEnv', event => { });
|
||||
|
||||
const win = new BrowserWindow({
|
||||
height: 600,
|
||||
width: 800,
|
||||
show: false,
|
||||
webPreferences: {
|
||||
preload: path.join(__dirname, '..', '..', '..', 'src', 'vs', 'base', 'parts', 'sandbox', 'electron-sandbox', 'preload.js'), // ensure similar environment as VSCode as tests may depend on this
|
||||
additionalArguments: [`--vscode-window-config=vscode:test-vscode-window-config`],
|
||||
nodeIntegration: true,
|
||||
contextIsolation: false,
|
||||
enableWebSQL: false,
|
||||
spellcheck: false
|
||||
}
|
||||
});
|
||||
|
||||
win.webContents.on('did-finish-load', () => {
|
||||
if (args.dev) {
|
||||
win.show();
|
||||
win.webContents.openDevTools();
|
||||
}
|
||||
|
||||
if (args.waitServer) {
|
||||
waitForServer(Number(args.waitServer)).then(sendRun);
|
||||
} else {
|
||||
sendRun();
|
||||
}
|
||||
});
|
||||
|
||||
async function waitForServer(port) {
|
||||
let timeout;
|
||||
let socket;
|
||||
|
||||
return new Promise(resolve => {
|
||||
socket = net.connect(port, '127.0.0.1');
|
||||
socket.on('error', e => {
|
||||
console.error('error connecting to waitServer', e);
|
||||
resolve(undefined);
|
||||
});
|
||||
|
||||
socket.on('close', () => {
|
||||
resolve(undefined);
|
||||
});
|
||||
|
||||
timeout = setTimeout(() => {
|
||||
console.error('timed out waiting for before starting tests debugger');
|
||||
resolve(undefined);
|
||||
}, 15000);
|
||||
}).finally(() => {
|
||||
if (socket) {
|
||||
socket.end();
|
||||
}
|
||||
clearTimeout(timeout);
|
||||
});
|
||||
}
|
||||
|
||||
function sendRun() {
|
||||
win.webContents.send('run', args);
|
||||
}
|
||||
|
||||
const target = url.pathToFileURL(path.join(__dirname, 'renderer.esm.html'));
|
||||
target.searchParams.set('argv', JSON.stringify(args));
|
||||
win.loadURL(target.href);
|
||||
|
||||
const runner = new IPCRunner(win);
|
||||
createStatsCollector(runner);
|
||||
|
||||
// Handle renderer crashes, #117068
|
||||
win.webContents.on('render-process-gone', (evt, details) => {
|
||||
if (!runner.didEnd) {
|
||||
console.error(`Renderer process crashed with: ${JSON.stringify(details)}`);
|
||||
app.exit(1);
|
||||
}
|
||||
});
|
||||
|
||||
const reporters = [];
|
||||
|
||||
if (args.tfs) {
|
||||
reporters.push(
|
||||
new mocha.reporters.Spec(runner),
|
||||
new MochaJUnitReporter(runner, {
|
||||
reporterOptions: {
|
||||
testsuitesTitle: `${args.tfs} ${process.platform}`,
|
||||
mochaFile: process.env.BUILD_ARTIFACTSTAGINGDIRECTORY ? path.join(process.env.BUILD_ARTIFACTSTAGINGDIRECTORY, `test-results/${process.platform}-${process.arch}-${args.tfs.toLowerCase().replace(/[^\w]/g, '-')}-results.xml`) : undefined
|
||||
}
|
||||
}),
|
||||
);
|
||||
} else {
|
||||
// mocha patches symbols to use windows escape codes, but it seems like
|
||||
// Electron mangles these in its output.
|
||||
if (process.platform === 'win32') {
|
||||
Object.assign(importMochaReporter('base').symbols, {
|
||||
ok: '+',
|
||||
err: 'X',
|
||||
dot: '.',
|
||||
});
|
||||
}
|
||||
|
||||
reporters.push(applyReporter(runner, args));
|
||||
}
|
||||
|
||||
if (!args.dev) {
|
||||
ipcMain.on('all done', async () => {
|
||||
await Promise.all(reporters.map(r => r.drain?.()));
|
||||
app.exit(runner.didFail ? 1 : 0);
|
||||
});
|
||||
}
|
||||
});
|
||||
136
test/unit/electron/renderer.esm.html
Normal file
136
test/unit/electron/renderer.esm.html
Normal file
@@ -0,0 +1,136 @@
|
||||
<html>
|
||||
|
||||
<head>
|
||||
<meta charset="utf-8">
|
||||
<title>VSCode Tests</title>
|
||||
<link href="../../../node_modules/mocha/mocha.css" rel="stylesheet" />
|
||||
|
||||
</head>
|
||||
|
||||
<body>
|
||||
<div id="mocha"></div>
|
||||
<script src="../../../node_modules/mocha/mocha.js"></script>
|
||||
|
||||
<script>
|
||||
|
||||
// !!! DO NOT CHANGE !!!
|
||||
// Our unit tests may run in environments without
|
||||
// display (e.g. from builds) and tests may by
|
||||
// accident bring up native dialogs or even open
|
||||
// windows. This we cannot allow as it may crash
|
||||
// the test run.
|
||||
// !!! DO NOT CHANGE !!!
|
||||
window.open = function () { throw new Error('window.open() is not supported in tests!'); };
|
||||
window.alert = function () { throw new Error('window.alert() is not supported in tests!'); }
|
||||
window.confirm = function () { throw new Error('window.confirm() is not supported in tests!'); }
|
||||
|
||||
// Ignore uncaught cancelled promise errors
|
||||
window.addEventListener('unhandledrejection', e => {
|
||||
const name = e && e.reason && e.reason.name;
|
||||
|
||||
if (name === 'Canceled') {
|
||||
e.preventDefault();
|
||||
e.stopPropagation();
|
||||
}
|
||||
});
|
||||
|
||||
mocha.setup({
|
||||
ui: 'tdd',
|
||||
timeout: 5000,
|
||||
forbidOnly: typeof process.env['BUILD_ARTIFACTSTAGINGDIRECTORY'] === 'string' // disallow .only() when running on build machine
|
||||
});
|
||||
</script>
|
||||
<script>
|
||||
const urlParams = new URLSearchParams(window.location.search);
|
||||
const argv = JSON.parse(urlParams.get('argv'));
|
||||
|
||||
const outdir = argv.build ? 'out-build' : 'out';
|
||||
const basePath = require('path').join(__dirname, `../../../${outdir}/`);
|
||||
const baseUrl = require('url').pathToFileURL(basePath);
|
||||
|
||||
// Tests run in a renderer that IS node-enabled. Wo we will encounter imports for node-modules which isn't
|
||||
// supported by blink/chromium. To work around this, we generate an import map that maps all node modules
|
||||
// to a blob that exports all properties of the module as named exports and the module itself as default.
|
||||
|
||||
function asRequireBlobUri(name) {
|
||||
let _mod;
|
||||
try {
|
||||
_mod = require(name);
|
||||
|
||||
} catch (err) {
|
||||
// These are somewhat expected because of platform differences (windows vs mac vs linux) or because of node/electron binary differences
|
||||
// console.error(`[ESM] Failed to require ${name}`);
|
||||
// console.error(err);
|
||||
return undefined;
|
||||
}
|
||||
|
||||
// export all properties and the module itself as default
|
||||
let jsSrc = `const _mod = require('${name}')\n`;
|
||||
for (const name of Object.keys(_mod)) {
|
||||
jsSrc += `export const ${name} = _mod['${name}'];\n`;
|
||||
}
|
||||
jsSrc += `export default _mod;\n`;
|
||||
|
||||
const blob = new Blob([jsSrc], { type: 'application/javascript' });
|
||||
const url = URL.createObjectURL(blob);
|
||||
return url;
|
||||
}
|
||||
|
||||
// generate import map
|
||||
const importMap = {
|
||||
imports: {
|
||||
assert: new URL('../test/unit/assert-esm.js', baseUrl).href,
|
||||
sinon: new URL('../node_modules/sinon/pkg/sinon-esm.js', baseUrl).href,
|
||||
'sinon-test': new URL('../node_modules/sinon-test/dist/sinon-test-es.js', baseUrl).href,
|
||||
'electron': asRequireBlobUri('electron'),
|
||||
}
|
||||
};
|
||||
|
||||
|
||||
const builtin = require('module').builtinModules.filter(mod => !mod.startsWith('_') && !mod.startsWith('electron/js2c/')).sort();
|
||||
const dependencies = Object.keys(require('../../../package.json').dependencies).sort();
|
||||
const optionalDependencies = Object.keys(require('../../../package.json').optionalDependencies).sort();
|
||||
const dependenciesRemote = Object.keys(require('../../../remote/package.json').dependencies).sort();
|
||||
|
||||
for (const name of new Set([].concat(builtin, dependencies, optionalDependencies, dependenciesRemote))) {
|
||||
|
||||
const url = asRequireBlobUri(name);
|
||||
if(!url) {
|
||||
continue;
|
||||
}
|
||||
|
||||
importMap.imports[name] = url;
|
||||
}
|
||||
|
||||
//2: CSS modules
|
||||
const style = document.createElement('style');
|
||||
style.type = 'text/css';
|
||||
document.head.appendChild(style);
|
||||
|
||||
globalThis._VSCODE_CSS_LOAD = function (url) {
|
||||
style.sheet.insertRule(`@import url(${url});`);
|
||||
};
|
||||
|
||||
const { promisify } = require('util');
|
||||
globalThis._VSCODE_TEST_INIT = promisify(require('glob'))('**/*.css', { cwd: basePath }).then(cssModules => {
|
||||
for (const cssModule of cssModules) {
|
||||
const cssUrl = new URL(cssModule, baseUrl).href;
|
||||
const jsSrc = `globalThis._VSCODE_CSS_LOAD('${cssUrl}');\n`;
|
||||
const blob = new Blob([jsSrc], { type: 'application/javascript' });
|
||||
importMap.imports[cssUrl] = URL.createObjectURL(blob);
|
||||
}
|
||||
|
||||
}).then(() => {
|
||||
|
||||
const rawImportMap = JSON.stringify(importMap, undefined, 2);
|
||||
const importMapScript = document.createElement('script');
|
||||
importMapScript.type = 'importmap';
|
||||
importMapScript.textContent = rawImportMap;
|
||||
document.head.append(importMapScript);
|
||||
});
|
||||
|
||||
</script>
|
||||
<script src="./renderer.esm.js"></script>
|
||||
</body>
|
||||
|
||||
</html>
|
||||
366
test/unit/electron/renderer.esm.js
Normal file
366
test/unit/electron/renderer.esm.js
Normal file
@@ -0,0 +1,366 @@
|
||||
/*---------------------------------------------------------------------------------------------
|
||||
* Copyright (c) Microsoft Corporation. All rights reserved.
|
||||
* Licensed under the MIT License. See License.txt in the project root for license information.
|
||||
*--------------------------------------------------------------------------------------------*/
|
||||
|
||||
// @ts-check
|
||||
|
||||
/*eslint-env mocha*/
|
||||
|
||||
const fs = require('fs');
|
||||
|
||||
(function () {
|
||||
const originals = {};
|
||||
let logging = false;
|
||||
let withStacks = false;
|
||||
|
||||
globalThis.beginLoggingFS = (_withStacks) => {
|
||||
logging = true;
|
||||
withStacks = _withStacks || false;
|
||||
};
|
||||
globalThis.endLoggingFS = () => {
|
||||
logging = false;
|
||||
withStacks = false;
|
||||
};
|
||||
|
||||
function createSpy(element, cnt) {
|
||||
return function (...args) {
|
||||
if (logging) {
|
||||
console.log(`calling ${element}: ` + args.slice(0, cnt).join(',') + (withStacks ? (`\n` + new Error().stack.split('\n').slice(2).join('\n')) : ''));
|
||||
}
|
||||
return originals[element].call(this, ...args);
|
||||
};
|
||||
}
|
||||
|
||||
function intercept(element, cnt) {
|
||||
originals[element] = fs[element];
|
||||
fs[element] = createSpy(element, cnt);
|
||||
}
|
||||
|
||||
[
|
||||
['realpathSync', 1],
|
||||
['readFileSync', 1],
|
||||
['openSync', 3],
|
||||
['readSync', 1],
|
||||
['closeSync', 1],
|
||||
['readFile', 2],
|
||||
['mkdir', 1],
|
||||
['lstat', 1],
|
||||
['stat', 1],
|
||||
['watch', 1],
|
||||
['readdir', 1],
|
||||
['access', 2],
|
||||
['open', 2],
|
||||
['write', 1],
|
||||
['fdatasync', 1],
|
||||
['close', 1],
|
||||
['read', 1],
|
||||
['unlink', 1],
|
||||
['rmdir', 1],
|
||||
].forEach((element) => {
|
||||
intercept(element[0], element[1]);
|
||||
});
|
||||
})();
|
||||
|
||||
const { ipcRenderer } = require('electron');
|
||||
const assert = require('assert');
|
||||
const path = require('path');
|
||||
const glob = require('glob');
|
||||
const util = require('util');
|
||||
const coverage = require('../coverage');
|
||||
const { pathToFileURL } = require('url');
|
||||
|
||||
// Disabled custom inspect. See #38847
|
||||
if (util.inspect && util.inspect['defaultOptions']) {
|
||||
util.inspect['defaultOptions'].customInspect = false;
|
||||
}
|
||||
|
||||
// VSCODE_GLOBALS: node_modules
|
||||
globalThis._VSCODE_NODE_MODULES = new Proxy(Object.create(null), { get: (_target, mod) => require(String(mod)) });
|
||||
|
||||
// VSCODE_GLOBALS: package/product.json
|
||||
globalThis._VSCODE_PRODUCT_JSON = require('../../../product.json');
|
||||
globalThis._VSCODE_PACKAGE_JSON = 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 }),
|
||||
});
|
||||
|
||||
function initNls(opts) {
|
||||
if (opts.build) {
|
||||
// when running from `out-build`, ensure to load the default
|
||||
// messages file, because all `nls.localize` calls have their
|
||||
// english values removed and replaced by an index.
|
||||
// VSCODE_GLOBALS: NLS
|
||||
globalThis._VSCODE_NLS_MESSAGES = require(`../../../out-build/nls.messages.json`);
|
||||
}
|
||||
}
|
||||
const _tests_glob = '**/test/**/*.test.js';
|
||||
let loader;
|
||||
let _out;
|
||||
const _loaderErrors = [];
|
||||
|
||||
function initLoader(opts) {
|
||||
// debugger;
|
||||
const outdir = opts.build ? 'out-build' : 'out';
|
||||
_out = path.join(__dirname, `../../../${outdir}`);
|
||||
|
||||
const baseUrl = pathToFileURL(path.join(__dirname, `../../../${outdir}/`));
|
||||
globalThis._VSCODE_FILE_ROOT = baseUrl.href;
|
||||
|
||||
// set loader
|
||||
/**
|
||||
* @param {string[]} modules
|
||||
* @param {(...args:any[]) => void} callback
|
||||
*/
|
||||
function esmRequire(modules, callback, errorback) {
|
||||
const tasks = modules.map(mod => {
|
||||
|
||||
const url = new URL(`./${mod}.js`, baseUrl).href;
|
||||
return import(url).catch(err => {
|
||||
console.log(mod, url);
|
||||
console.log(err);
|
||||
_loaderErrors.push(err);
|
||||
throw err;
|
||||
});
|
||||
});
|
||||
|
||||
Promise.all(tasks).then(modules => callback(...modules)).catch(errorback);
|
||||
}
|
||||
|
||||
loader = { require: esmRequire };
|
||||
}
|
||||
|
||||
function createCoverageReport(opts) {
|
||||
if (opts.coverage) {
|
||||
return coverage.createReport(opts.run || opts.runGlob);
|
||||
}
|
||||
return Promise.resolve(undefined);
|
||||
}
|
||||
|
||||
function loadWorkbenchTestingUtilsModule() {
|
||||
return new Promise((resolve, reject) => {
|
||||
loader.require(['vs/workbench/test/common/utils'], resolve, reject);
|
||||
});
|
||||
}
|
||||
|
||||
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) {
|
||||
const files = Array.isArray(opts.run) ? opts.run : [opts.run];
|
||||
const modules = files.map(file => {
|
||||
file = file.replace(/^src/, 'out');
|
||||
file = file.replace(/\.ts$/, '.js');
|
||||
return path.relative(_out, file).replace(/\.js$/, '');
|
||||
});
|
||||
return loadModules(modules);
|
||||
}
|
||||
|
||||
const pattern = opts.runGlob || _tests_glob;
|
||||
|
||||
return new Promise((resolve, reject) => {
|
||||
glob(pattern, { cwd: _out }, (err, files) => {
|
||||
if (err) {
|
||||
reject(err);
|
||||
return;
|
||||
}
|
||||
const modules = files.map(file => file.replace(/\.js$/, ''));
|
||||
resolve(modules);
|
||||
});
|
||||
}).then(loadModules);
|
||||
}
|
||||
|
||||
function loadTests(opts) {
|
||||
|
||||
const _unexpectedErrors = [];
|
||||
|
||||
// collect unexpected errors
|
||||
loader.require(['vs/base/common/errors'], function (errors) {
|
||||
errors.setUnexpectedErrorHandler(function (err) {
|
||||
let stack = (err ? err.stack : null);
|
||||
if (!stack) {
|
||||
stack = new Error().stack;
|
||||
}
|
||||
|
||||
_unexpectedErrors.push((err && err.message ? err.message : err) + '\n' + stack);
|
||||
});
|
||||
});
|
||||
|
||||
return loadWorkbenchTestingUtilsModule().then((workbenchTestingModule) => {
|
||||
const assertCleanState = workbenchTestingModule.assertCleanState;
|
||||
|
||||
suite('Tests are using suiteSetup and setup correctly', () => {
|
||||
test('assertCleanState - check that registries are clean at the start of test running', () => {
|
||||
assertCleanState();
|
||||
});
|
||||
});
|
||||
|
||||
return loadTestModules(opts).then(() => {
|
||||
suite('Unexpected Errors & Loader Errors', function () {
|
||||
test('should not have unexpected errors', function () {
|
||||
const errors = _unexpectedErrors.concat(_loaderErrors);
|
||||
if (errors.length) {
|
||||
errors.forEach(function (stack) {
|
||||
console.error('');
|
||||
console.error(stack);
|
||||
});
|
||||
assert.ok(false, errors.join());
|
||||
}
|
||||
});
|
||||
|
||||
test('assertCleanState - check that registries are clean and objects are disposed at the end of test running', () => {
|
||||
assertCleanState();
|
||||
});
|
||||
});
|
||||
});
|
||||
});
|
||||
}
|
||||
|
||||
function serializeSuite(suite) {
|
||||
return {
|
||||
root: suite.root,
|
||||
suites: suite.suites.map(serializeSuite),
|
||||
tests: suite.tests.map(serializeRunnable),
|
||||
title: suite.title,
|
||||
fullTitle: suite.fullTitle(),
|
||||
titlePath: suite.titlePath(),
|
||||
timeout: suite.timeout(),
|
||||
retries: suite.retries(),
|
||||
slow: suite.slow(),
|
||||
bail: suite.bail()
|
||||
};
|
||||
}
|
||||
|
||||
function serializeRunnable(runnable) {
|
||||
return {
|
||||
title: runnable.title,
|
||||
fullTitle: runnable.fullTitle(),
|
||||
titlePath: runnable.titlePath(),
|
||||
async: runnable.async,
|
||||
slow: runnable.slow(),
|
||||
speed: runnable.speed,
|
||||
duration: runnable.duration
|
||||
};
|
||||
}
|
||||
|
||||
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,
|
||||
showDiff: err.showDiff,
|
||||
inspect: typeof err.inspect === 'function' ? err.inspect() : ''
|
||||
};
|
||||
}
|
||||
|
||||
function safeStringify(obj) {
|
||||
const seen = new Set();
|
||||
return JSON.stringify(obj, (key, value) => {
|
||||
if (value === undefined) {
|
||||
return '[undefined]';
|
||||
}
|
||||
|
||||
if (isObject(value) || Array.isArray(value)) {
|
||||
if (seen.has(value)) {
|
||||
return '[Circular]';
|
||||
} else {
|
||||
seen.add(value);
|
||||
}
|
||||
}
|
||||
return value;
|
||||
});
|
||||
}
|
||||
|
||||
function isObject(obj) {
|
||||
// The method can't do a type cast since there are type (like strings) which
|
||||
// are subclasses of any put not positvely matched by the function. Hence type
|
||||
// narrowing results in wrong results.
|
||||
return typeof obj === 'object'
|
||||
&& obj !== null
|
||||
&& !Array.isArray(obj)
|
||||
&& !(obj instanceof RegExp)
|
||||
&& !(obj instanceof Date);
|
||||
}
|
||||
|
||||
class IPCReporter {
|
||||
|
||||
constructor(runner) {
|
||||
runner.on('start', () => ipcRenderer.send('start'));
|
||||
runner.on('end', () => ipcRenderer.send('end'));
|
||||
runner.on('suite', suite => ipcRenderer.send('suite', serializeSuite(suite)));
|
||||
runner.on('suite end', suite => ipcRenderer.send('suite end', serializeSuite(suite)));
|
||||
runner.on('test', test => ipcRenderer.send('test', serializeRunnable(test)));
|
||||
runner.on('test end', test => ipcRenderer.send('test end', serializeRunnable(test)));
|
||||
runner.on('hook', hook => ipcRenderer.send('hook', serializeRunnable(hook)));
|
||||
runner.on('hook end', hook => ipcRenderer.send('hook end', serializeRunnable(hook)));
|
||||
runner.on('pass', test => ipcRenderer.send('pass', serializeRunnable(test)));
|
||||
runner.on('fail', (test, err) => ipcRenderer.send('fail', serializeRunnable(test), serializeError(err)));
|
||||
runner.on('pending', test => ipcRenderer.send('pending', serializeRunnable(test)));
|
||||
}
|
||||
}
|
||||
|
||||
function runTests(opts) {
|
||||
// this *must* come before loadTests, or it doesn't work.
|
||||
if (opts.timeout !== undefined) {
|
||||
mocha.timeout(opts.timeout);
|
||||
}
|
||||
|
||||
return loadTests(opts).then(() => {
|
||||
|
||||
if (opts.grep) {
|
||||
mocha.grep(opts.grep);
|
||||
}
|
||||
|
||||
if (!opts.dev) {
|
||||
mocha.reporter(IPCReporter);
|
||||
}
|
||||
|
||||
const runner = mocha.run(() => {
|
||||
createCoverageReport(opts).then(() => {
|
||||
ipcRenderer.send('all done');
|
||||
});
|
||||
});
|
||||
|
||||
if (opts.dev) {
|
||||
runner.on('fail', (test, err) => {
|
||||
|
||||
console.error(test.fullTitle());
|
||||
console.error(err.stack);
|
||||
});
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
ipcRenderer.on('run', async (_e, opts) => {
|
||||
initNls(opts);
|
||||
initLoader(opts);
|
||||
|
||||
await Promise.resolve(globalThis._VSCODE_TEST_INIT);
|
||||
|
||||
try {
|
||||
await runTests(opts);
|
||||
} catch (err) {
|
||||
if (typeof err !== 'string') {
|
||||
err = JSON.stringify(err);
|
||||
}
|
||||
console.error(err);
|
||||
ipcRenderer.send('error', err);
|
||||
}
|
||||
});
|
||||
249
test/unit/node/index.mjs
Normal file
249
test/unit/node/index.mjs
Normal file
@@ -0,0 +1,249 @@
|
||||
/*---------------------------------------------------------------------------------------------
|
||||
* Copyright (c) Microsoft Corporation. All rights reserved.
|
||||
* Licensed under the MIT License. See License.txt in the project root for license information.
|
||||
*--------------------------------------------------------------------------------------------*/
|
||||
|
||||
//@ts-check
|
||||
'use strict';
|
||||
|
||||
process.env.MOCHA_COLORS = '1'; // Force colors (note that this must come before any mocha imports)
|
||||
|
||||
import * as assert from 'assert';
|
||||
import Mocha from 'mocha';
|
||||
import * as path from 'path';
|
||||
import * as fs from 'fs';
|
||||
import glob from 'glob';
|
||||
import minimatch from 'minimatch';
|
||||
// const coverage = require('../coverage');
|
||||
import minimist from 'minimist';
|
||||
// const { takeSnapshotAndCountClasses } = require('../analyzeSnapshot');
|
||||
import * as module from 'module';
|
||||
import { fileURLToPath, pathToFileURL } from 'url';
|
||||
|
||||
/**
|
||||
* @type {{ build: boolean; run: string; runGlob: string; coverage: boolean; help: boolean; coverageFormats: string | string[]; coveragePath: string; }}
|
||||
*/
|
||||
const args = minimist(process.argv.slice(2), {
|
||||
boolean: ['build', 'coverage', 'help'],
|
||||
string: ['run', 'coveragePath', 'coverageFormats'],
|
||||
alias: {
|
||||
h: 'help'
|
||||
},
|
||||
default: {
|
||||
build: false,
|
||||
coverage: false,
|
||||
help: false
|
||||
},
|
||||
description: {
|
||||
build: 'Run from out-build',
|
||||
run: 'Run a single file',
|
||||
coverage: 'Generate a coverage report',
|
||||
coveragePath: 'Path to coverage report to generate',
|
||||
coverageFormats: 'Coverage formats to generate',
|
||||
help: 'Show help'
|
||||
}
|
||||
});
|
||||
|
||||
if (args.help) {
|
||||
console.log(`Usage: node test/unit/node/index [options]
|
||||
|
||||
Options:
|
||||
--build Run from out-build
|
||||
--run <file> Run a single file
|
||||
--coverage Generate a coverage report
|
||||
--help Show help`);
|
||||
process.exit(0);
|
||||
}
|
||||
|
||||
const TEST_GLOB = '**/test/**/*.test.js';
|
||||
|
||||
const excludeGlobs = [
|
||||
'**/{browser,electron-sandbox,electron-main}/**/*.test.js',
|
||||
'**/vs/platform/environment/test/node/nativeModules.test.js', // native modules are compiled against Electron and this test would fail with node.js
|
||||
'**/vs/base/parts/storage/test/node/storage.test.js', // same as above, due to direct dependency to sqlite native module
|
||||
'**/vs/workbench/contrib/testing/test/**' // flaky (https://github.com/microsoft/vscode/issues/137853)
|
||||
];
|
||||
|
||||
const REPO_ROOT = fileURLToPath(new URL('../../../', import.meta.url));
|
||||
const out = args.build ? 'out-build' : 'out';
|
||||
const src = path.join(REPO_ROOT, out);
|
||||
const baseUrl = pathToFileURL(src);
|
||||
|
||||
//@ts-ignore
|
||||
const majorRequiredNodeVersion = `v${/^target\s+"([^"]+)"$/m.exec(fs.readFileSync(path.join(REPO_ROOT, 'remote', '.yarnrc'), 'utf8'))[1]}`.substring(0, 3);
|
||||
const currentMajorNodeVersion = process.version.substring(0, 3);
|
||||
if (majorRequiredNodeVersion !== currentMajorNodeVersion) {
|
||||
console.error(`node.js unit tests require a major node.js version of ${majorRequiredNodeVersion} (your version is: ${currentMajorNodeVersion})`);
|
||||
process.exit(1);
|
||||
}
|
||||
|
||||
function main() {
|
||||
|
||||
// VSCODE_GLOBALS: node_modules
|
||||
const _require = module.createRequire(import.meta.url);
|
||||
globalThis._VSCODE_NODE_MODULES = new Proxy(Object.create(null), { get: (_target, mod) => _require(String(mod)) });
|
||||
|
||||
// VSCODE_GLOBALS: package/product.json
|
||||
globalThis._VSCODE_PRODUCT_JSON = _require(`${REPO_ROOT}/product.json`);
|
||||
globalThis._VSCODE_PACKAGE_JSON = _require(`${REPO_ROOT}/package.json`);
|
||||
|
||||
// VSCODE_GLOBALS: file root
|
||||
globalThis._VSCODE_FILE_ROOT = baseUrl.href;
|
||||
|
||||
if (args.build) {
|
||||
// when running from `out-build`, ensure to load the default
|
||||
// messages file, because all `nls.localize` calls have their
|
||||
// english values removed and replaced by an index.
|
||||
globalThis._VSCODE_NLS_MESSAGES = _require(`${REPO_ROOT}/${out}/nls.messages.json`);
|
||||
}
|
||||
|
||||
// Test file operations that are common across platforms. Used for test infra, namely snapshot tests
|
||||
Object.assign(globalThis, {
|
||||
// __analyzeSnapshotInTests: takeSnapshotAndCountClasses,
|
||||
__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);
|
||||
});
|
||||
|
||||
/**
|
||||
* @param modules
|
||||
* @param onLoad
|
||||
* @param onError
|
||||
*/
|
||||
const loader = function (modules, onLoad, onError) {
|
||||
|
||||
modules = modules.filter(mod => {
|
||||
if (mod.endsWith('css.build.test')) {
|
||||
// AMD ONLY, ignore for ESM
|
||||
return false;
|
||||
}
|
||||
return true;
|
||||
});
|
||||
|
||||
const loads = modules.map(mod => import(`${baseUrl}/${mod}.js`).catch(err => {
|
||||
console.error(`FAILED to load ${mod} as ${baseUrl}/${mod}.js`);
|
||||
throw err;
|
||||
}));
|
||||
Promise.all(loads).then(onLoad, onError);
|
||||
};
|
||||
|
||||
|
||||
let didErr = false;
|
||||
const write = process.stderr.write;
|
||||
process.stderr.write = function (...args) {
|
||||
didErr = didErr || !!args[0];
|
||||
return write.apply(process.stderr, args);
|
||||
};
|
||||
|
||||
|
||||
const runner = new Mocha({
|
||||
ui: 'tdd'
|
||||
});
|
||||
|
||||
/**
|
||||
* @param 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 (args.runGlob) {
|
||||
loadFunc = (cb) => {
|
||||
const doRun = /** @param tests */(tests) => {
|
||||
const modulesToLoad = tests.map(test => {
|
||||
if (path.isAbsolute(test)) {
|
||||
test = path.relative(src, path.resolve(test));
|
||||
}
|
||||
|
||||
return test.replace(/(\.js)|(\.d\.ts)|(\.js\.map)$/, '');
|
||||
});
|
||||
loadModules(modulesToLoad).then(() => cb(null), cb);
|
||||
};
|
||||
|
||||
glob(args.runGlob, { cwd: src }, function (err, files) { doRun(files); });
|
||||
};
|
||||
} else if (args.run) {
|
||||
const tests = (typeof args.run === 'string') ? [args.run] : args.run;
|
||||
const modulesToLoad = tests.map(function (test) {
|
||||
test = test.replace(/^src/, 'out');
|
||||
test = test.replace(/\.ts$/, '.js');
|
||||
return path.relative(src, path.resolve(test)).replace(/(\.js)|(\.js\.map)$/, '').replace(/\\/g, '/');
|
||||
});
|
||||
loadFunc = (cb) => {
|
||||
loadModules(modulesToLoad).then(() => cb(null), cb);
|
||||
};
|
||||
} else {
|
||||
loadFunc = (cb) => {
|
||||
glob(TEST_GLOB, { cwd: src }, function (err, files) {
|
||||
/** @type {string[]} */
|
||||
const modules = [];
|
||||
for (const file of files) {
|
||||
if (!excludeGlobs.some(excludeGlob => minimatch(file, excludeGlob))) {
|
||||
modules.push(file.replace(/\.js$/, ''));
|
||||
}
|
||||
}
|
||||
loadModules(modules).then(() => cb(null), cb);
|
||||
});
|
||||
};
|
||||
}
|
||||
|
||||
loadFunc(function (err) {
|
||||
if (err) {
|
||||
console.error(err);
|
||||
return process.exit(1);
|
||||
}
|
||||
|
||||
process.stderr.write = write;
|
||||
|
||||
if (!args.run && !args.runGlob) {
|
||||
// set up last test
|
||||
Mocha.suite('Loader', function () {
|
||||
test('should not explode while loading', function () {
|
||||
assert.ok(!didErr, `should not explode while loading: ${didErr}`);
|
||||
});
|
||||
});
|
||||
}
|
||||
|
||||
// report failing test for every unexpected error during any of the tests
|
||||
const unexpectedErrors = [];
|
||||
Mocha.suite('Errors', function () {
|
||||
test('should not have unexpected errors in tests', function () {
|
||||
if (unexpectedErrors.length) {
|
||||
unexpectedErrors.forEach(function (stack) {
|
||||
console.error('');
|
||||
console.error(stack);
|
||||
});
|
||||
|
||||
assert.ok(false);
|
||||
}
|
||||
});
|
||||
});
|
||||
|
||||
// replace the default unexpected error handler to be useful during tests
|
||||
import(`${baseUrl}/vs/base/common/errors.js`).then(errors => {
|
||||
errors.setUnexpectedErrorHandler(function (err) {
|
||||
const stack = (err && err.stack) || (new Error().stack);
|
||||
unexpectedErrors.push((err && err.message ? err.message : err) + '\n' + stack);
|
||||
});
|
||||
|
||||
// fire up mocha
|
||||
runner.run(failures => process.exit(failures ? 1 : 0));
|
||||
});
|
||||
});
|
||||
}
|
||||
|
||||
main();
|
||||
Reference in New Issue
Block a user