2024-09-11 02:37:36 +00:00
/ * - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
* Copyright ( c ) Microsoft Corporation . All rights reserved .
* Licensed under the MIT License . See License . txt in the project root for license information .
* -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- * /
/*eslint-env mocha*/
2025-03-01 02:01:53 +00:00
// @ts-check
2024-09-11 02:37:36 +00:00
const fs = require ( 'fs' ) ;
( function ( ) {
const originals = { } ;
let logging = false ;
let withStacks = false ;
2024-09-24 04:46:08 +00:00
globalThis . beginLoggingFS = ( _withStacks ) => {
2024-09-11 02:37:36 +00:00
logging = true ;
withStacks = _withStacks || false ;
} ;
2024-09-24 04:46:08 +00:00
globalThis . endLoggingFS = ( ) => {
2024-09-11 02:37:36 +00:00
logging = false ;
withStacks = false ;
} ;
function createSpy ( element , cnt ) {
return function ( ... args ) {
if ( logging ) {
2025-03-01 02:01:53 +00:00
console . log ( ` calling ${ element } : ` + args . slice ( 0 , cnt ) . join ( ',' ) + ( withStacks ? ( ` \n ` + new Error ( ) . stack ? . split ( '\n' ) . slice ( 2 ) . join ( '\n' ) ) : '' ) ) ;
2024-09-11 02:37:36 +00:00
}
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' ) ;
2024-09-24 04:46:08 +00:00
const { pathToFileURL } = require ( 'url' ) ;
2024-09-11 02:37:36 +00:00
// Disabled custom inspect. See #38847
if ( util . inspect && util . inspect [ 'defaultOptions' ] ) {
util . inspect [ 'defaultOptions' ] . customInspect = false ;
}
// VSCODE_GLOBALS: package/product.json
2024-09-24 04:46:08 +00:00
globalThis . _VSCODE _PRODUCT _JSON = require ( '../../../product.json' ) ;
globalThis . _VSCODE _PACKAGE _JSON = require ( '../../../package.json' ) ;
2024-09-11 02:37:36 +00:00
// Test file operations that are common across platforms. Used for test infra, namely snapshot tests
Object . assign ( globalThis , {
_ _readFileInTests : path => fs . promises . readFile ( path , 'utf-8' ) ,
_ _writeFileInTests : ( path , contents ) => fs . promises . writeFile ( path , contents ) ,
_ _readDirInTests : path => fs . promises . readdir ( path ) ,
_ _unlinkInTests : path => fs . promises . unlink ( path ) ,
_ _mkdirPInTests : path => fs . promises . mkdir ( path , { recursive : true } ) ,
} ) ;
const IS _CI = ! ! process . env . BUILD _ARTIFACTSTAGINGDIRECTORY ;
const _tests _glob = '**/test/**/*.test.js' ;
2025-03-01 02:01:53 +00:00
/ * *
* Loads one or N modules .
* @ type { {
* ( module : string | string [ ] ) : Promise < any > | Promise < any [ ] > ;
* _out : string ;
* } }
* /
let loadFn ;
2024-09-24 04:46:08 +00:00
const _loaderErrors = [ ] ;
2024-09-11 02:37:36 +00:00
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.
2024-09-24 04:46:08 +00:00
globalThis . _VSCODE _NLS _MESSAGES = require ( ` ../../../out-build/nls.messages.json ` ) ;
2024-09-11 02:37:36 +00:00
}
}
2025-03-01 02:01:53 +00:00
function initLoadFn ( opts ) {
2024-09-11 02:37:36 +00:00
const outdir = opts . build ? 'out-build' : 'out' ;
2025-03-01 02:01:53 +00:00
const out = path . join ( _ _dirname , ` ../../../ ${ outdir } ` ) ;
2024-09-11 02:37:36 +00:00
2024-09-24 04:46:08 +00:00
const baseUrl = pathToFileURL ( path . join ( _ _dirname , ` ../../../ ${ outdir } / ` ) ) ;
globalThis . _VSCODE _FILE _ROOT = baseUrl . href ;
// set loader
2025-03-01 02:01:53 +00:00
function importModules ( modules ) {
const moduleArray = Array . isArray ( modules ) ? modules : [ modules ] ;
const tasks = moduleArray . map ( mod => {
2024-09-24 04:46:08 +00:00
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 ;
} ) ;
} ) ;
2024-09-11 02:37:36 +00:00
2025-03-01 02:01:53 +00:00
return Array . isArray ( modules )
? Promise . all ( tasks )
: tasks [ 0 ] ;
2024-09-11 02:37:36 +00:00
}
2025-03-01 02:01:53 +00:00
importModules . _out = out ;
loadFn = importModules ;
2024-09-11 02:37:36 +00:00
}
2025-03-01 02:01:53 +00:00
async function createCoverageReport ( opts ) {
if ( ! opts . coverage ) {
return undefined ;
2024-09-11 02:37:36 +00:00
}
2025-03-01 02:01:53 +00:00
return coverage . createReport ( opts . run || opts . runGlob ) ;
2024-09-11 02:37:36 +00:00
}
async function loadModules ( modules ) {
for ( const file of modules ) {
mocha . suite . emit ( Mocha . Suite . constants . EVENT _FILE _PRE _REQUIRE , globalThis , file , mocha ) ;
2025-03-01 02:01:53 +00:00
const m = await loadFn ( file ) ;
2024-09-11 02:37:36 +00:00
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 ) ;
}
}
2025-03-01 02:01:53 +00:00
const globAsync = util . promisify ( glob ) ;
async function loadTestModules ( opts ) {
2024-09-11 02:37:36 +00:00
if ( opts . run ) {
const files = Array . isArray ( opts . run ) ? opts . run : [ opts . run ] ;
const modules = files . map ( file => {
file = file . replace ( /^src[\\/]/ , '' ) ;
return file . replace ( /\.[jt]s$/ , '' ) ;
} ) ;
return loadModules ( modules ) ;
}
const pattern = opts . runGlob || _tests _glob ;
2025-03-01 02:01:53 +00:00
const files = await globAsync ( pattern , { cwd : loadFn . _out } ) ;
const modules = files . map ( file => file . replace ( /\.js$/ , '' ) ) ;
return loadModules ( modules ) ;
2024-09-11 02:37:36 +00:00
}
/** @type Mocha.Test */
let currentTest ;
async function loadTests ( opts ) {
//#region Unexpected Output
const _allowedTestOutput = [
/The vm module of Node\.js is deprecated in the renderer process and will be removed./ ,
] ;
// allow snapshot mutation messages locally
if ( ! IS _CI ) {
_allowedTestOutput . push ( /Creating new snapshot in/ ) ;
_allowedTestOutput . push ( /Deleting [0-9]+ old snapshots/ ) ;
}
const perTestCoverage = opts [ 'per-test-coverage' ] ? await PerTestCoverage . init ( ) : undefined ;
const _allowedTestsWithOutput = new Set ( [
'creates a snapshot' , // self-testing
'validates a snapshot' , // self-testing
'cleans up old snapshots' , // self-testing
'issue #149412: VS Code hangs when bad semantic token data is received' , // https://github.com/microsoft/vscode/issues/192440
'issue #134973: invalid semantic tokens should be handled better' , // https://github.com/microsoft/vscode/issues/192440
'issue #148651: VSCode UI process can hang if a semantic token with negative values is returned by language service' , // https://github.com/microsoft/vscode/issues/192440
'issue #149130: vscode freezes because of Bracket Pair Colorization' , // https://github.com/microsoft/vscode/issues/192440
'property limits' , // https://github.com/microsoft/vscode/issues/192443
'Error events' , // https://github.com/microsoft/vscode/issues/192443
'fetch returns keybinding with user first if title and id matches' , //
'throw ListenerLeakError'
] ) ;
const _allowedSuitesWithOutput = new Set ( [
2025-03-01 02:01:53 +00:00
'InlineChatController'
2024-09-11 02:37:36 +00:00
] ) ;
let _testsWithUnexpectedOutput = false ;
for ( const consoleFn of [ console . log , console . error , console . info , console . warn , console . trace , console . debug ] ) {
console [ consoleFn . name ] = function ( msg ) {
if ( ! currentTest ) {
consoleFn . apply ( console , arguments ) ;
2025-03-01 02:01:53 +00:00
} else if ( ! _allowedTestOutput . some ( a => a . test ( msg ) ) && ! _allowedTestsWithOutput . has ( currentTest . title ) && ! _allowedSuitesWithOutput . has ( currentTest . parent ? . title ? ? '' ) ) {
2024-09-11 02:37:36 +00:00
_testsWithUnexpectedOutput = true ;
consoleFn . apply ( console , arguments ) ;
}
} ;
}
//#endregion
//#region Unexpected / Loader Errors
const _unexpectedErrors = [ ] ;
const _allowedTestsWithUnhandledRejections = new Set ( [
// Lifecycle tests
'onWillShutdown - join with error is handled' ,
'onBeforeShutdown - veto with error is treated as veto' ,
'onBeforeShutdown - final veto with error is treated as veto' ,
// Search tests
'Search Model: Search reports timed telemetry on search when error is called'
] ) ;
2025-03-01 02:01:53 +00:00
const errors = await loadFn ( 'vs/base/common/errors' ) ;
const onUnexpectedError = function ( err ) {
if ( err . name === 'Canceled' ) {
return ; // ignore canceled errors that are common
}
2024-09-11 02:37:36 +00:00
2025-03-01 02:01:53 +00:00
let stack = ( err ? err . stack : null ) ;
if ( ! stack ) {
stack = new Error ( ) . stack ;
}
2024-09-11 02:37:36 +00:00
2025-03-01 02:01:53 +00:00
_unexpectedErrors . push ( ( err && err . message ? err . message : err ) + '\n' + stack ) ;
} ;
2024-09-11 02:37:36 +00:00
2025-03-01 02:01:53 +00:00
process . on ( 'uncaughtException' , error => onUnexpectedError ( error ) ) ;
process . on ( 'unhandledRejection' , ( reason , promise ) => {
onUnexpectedError ( reason ) ;
promise . catch ( ( ) => { } ) ;
} ) ;
window . addEventListener ( 'unhandledrejection' , event => {
event . preventDefault ( ) ; // Do not log to test output, we show an error later when test ends
event . stopPropagation ( ) ;
2024-09-11 02:37:36 +00:00
2025-03-01 02:01:53 +00:00
if ( ! _allowedTestsWithUnhandledRejections . has ( currentTest . title ) ) {
onUnexpectedError ( event . reason ) ;
}
2024-09-11 02:37:36 +00:00
} ) ;
2025-03-01 02:01:53 +00:00
errors . setUnexpectedErrorHandler ( onUnexpectedError ) ;
2024-09-11 02:37:36 +00:00
//#endregion
2025-03-01 02:01:53 +00:00
const { assertCleanState } = await loadFn ( 'vs/workbench/test/common/utils' ) ;
2024-09-11 02:37:36 +00:00
2025-03-01 02:01:53 +00:00
suite ( 'Tests are using suiteSetup and setup correctly' , ( ) => {
test ( 'assertCleanState - check that registries are clean at the start of test running' , ( ) => {
assertCleanState ( ) ;
2024-09-11 02:37:36 +00:00
} ) ;
2025-03-01 02:01:53 +00:00
} ) ;
2024-09-11 02:37:36 +00:00
2025-03-01 02:01:53 +00:00
setup ( async ( ) => {
await perTestCoverage ? . startTest ( ) ;
} ) ;
2024-09-11 02:37:36 +00:00
2025-03-01 02:01:53 +00:00
teardown ( async ( ) => {
await perTestCoverage ? . finishTest ( currentTest . file , currentTest . fullTitle ( ) ) ;
2024-09-11 02:37:36 +00:00
2025-03-01 02:01:53 +00:00
// should not have unexpected output
if ( _testsWithUnexpectedOutput && ! opts . dev ) {
assert . ok ( false , 'Error: Unexpected console output in test run. Please ensure no console.[log|error|info|warn] usage in tests or runtime errors.' ) ;
}
2024-09-11 02:37:36 +00:00
2025-03-01 02:01:53 +00:00
// should not have unexpected errors
const errors = _unexpectedErrors . concat ( _loaderErrors ) ;
if ( errors . length ) {
const msg = [ ] ;
for ( const error of errors ) {
console . error ( ` Error: Test run should not have unexpected errors: \n ${ error } ` ) ;
msg . push ( String ( error ) )
2024-09-11 02:37:36 +00:00
}
2025-03-01 02:01:53 +00:00
assert . ok ( false , ` Error: Test run should not have unexpected errors: \n ${ msg . join ( '\n' ) } ` ) ;
}
} ) ;
2024-09-11 02:37:36 +00:00
2025-03-01 02:01:53 +00:00
suiteTeardown ( ( ) => { // intentionally not in teardown because some tests only cleanup in suiteTeardown
2024-09-11 02:37:36 +00:00
2025-03-01 02:01:53 +00:00
// should have cleaned up in registries
assertCleanState ( ) ;
2024-09-11 02:37:36 +00:00
} ) ;
2025-03-01 02:01:53 +00:00
return loadTestModules ( opts ) ;
2024-09-11 02:37:36 +00:00
}
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 ) ) ) ;
}
}
2025-03-01 02:01:53 +00:00
const $globalThis = globalThis ;
const setTimeout0IsFaster = ( typeof $globalThis . postMessage === 'function' && ! $globalThis . importScripts ) ;
/ * *
* See https : //html.spec.whatwg.org/multipage/timers-and-user-prompts.html#:~:text=than%204%2C%20then-,set%20timeout%20to%204,-.
*
* Works similarly to ` setTimeout(0) ` but doesn ' t suffer from the 4 ms artificial delay
* that browsers set when the nesting level is > 5.
* /
const setTimeout0 = ( ( ) => {
if ( setTimeout0IsFaster ) {
const pending = [ ] ;
$globalThis . addEventListener ( 'message' , ( e ) => {
if ( e . data && e . data . vscodeScheduleAsyncWork ) {
for ( let i = 0 , len = pending . length ; i < len ; i ++ ) {
const candidate = pending [ i ] ;
if ( candidate . id === e . data . vscodeScheduleAsyncWork ) {
pending . splice ( i , 1 ) ;
candidate . callback ( ) ;
return ;
}
}
}
} ) ;
let lastId = 0 ;
return ( callback ) => {
const myId = ++ lastId ;
pending . push ( {
id : myId ,
callback : callback
} ) ;
$globalThis . postMessage ( { vscodeScheduleAsyncWork : myId } , '*' ) ;
} ;
}
return ( callback ) => setTimeout ( callback ) ;
} ) ( ) ;
async function runTests ( opts ) {
// @ts-expect-error
Mocha . Runner . immediately = setTimeout0 ;
mocha . setup ( {
ui : 'tdd' ,
// @ts-expect-error
reporter : opts . dev ? 'html' : IPCReporter ,
grep : opts . grep ,
timeout : opts . timeout ? ? ( IS _CI ? 30000 : 5000 ) ,
forbidOnly : IS _CI // disallow .only() when running on build machine
} ) ;
2024-09-11 02:37:36 +00:00
// this *must* come before loadTests, or it doesn't work.
if ( opts . timeout !== undefined ) {
mocha . timeout ( opts . timeout ) ;
}
2025-03-01 02:01:53 +00:00
await loadTests ( opts ) ;
2024-09-11 02:37:36 +00:00
2025-03-01 02:01:53 +00:00
const runner = mocha . run ( async ( ) => {
await createCoverageReport ( opts )
ipcRenderer . send ( 'all done' ) ;
} ) ;
2024-09-11 02:37:36 +00:00
2025-03-01 02:01:53 +00:00
runner . on ( 'test' , test => currentTest = test ) ;
2024-09-11 02:37:36 +00:00
2025-03-01 02:01:53 +00:00
if ( opts . dev ) {
runner . on ( 'fail' , ( test , err ) => {
console . error ( test . fullTitle ( ) ) ;
console . error ( err . stack ) ;
2024-09-11 02:37:36 +00:00
} ) ;
2025-03-01 02:01:53 +00:00
}
2024-09-11 02:37:36 +00:00
}
2024-09-24 04:46:08 +00:00
ipcRenderer . on ( 'run' , async ( _e , opts ) => {
2024-09-11 02:37:36 +00:00
initNls ( opts ) ;
2025-03-01 02:01:53 +00:00
initLoadFn ( opts ) ;
2024-09-24 04:46:08 +00:00
await Promise . resolve ( globalThis . _VSCODE _TEST _INIT ) ;
try {
await runTests ( opts ) ;
} catch ( err ) {
2024-09-11 02:37:36 +00:00
if ( typeof err !== 'string' ) {
err = JSON . stringify ( err ) ;
}
console . error ( err ) ;
ipcRenderer . send ( 'error' , err ) ;
2024-09-24 04:46:08 +00:00
}
2024-09-11 02:37:36 +00:00
} ) ;
class PerTestCoverage {
static async init ( ) {
await ipcRenderer . invoke ( 'startCoverage' ) ;
return new PerTestCoverage ( ) ;
}
async startTest ( ) {
if ( ! this . didInit ) {
this . didInit = true ;
await ipcRenderer . invoke ( 'snapshotCoverage' ) ;
}
}
async finishTest ( file , fullTitle ) {
await ipcRenderer . invoke ( 'snapshotCoverage' , { file , fullTitle } ) ;
}
}