diff --git a/scripts/test-integration.bat b/scripts/test-integration.bat index 309bfd91e33..0260a781d8d 100644 --- a/scripts/test-integration.bat +++ b/scripts/test-integration.bat @@ -74,7 +74,7 @@ if %errorlevel% neq 0 exit /b %errorlevel% echo. echo ### TypeScript tests -call "%INTEGRATION_TEST_ELECTRON_PATH%" %~dp0\..\extensions\typescript-language-features\test-workspace --extensionDevelopmentPath=%~dp0\..\extensions\typescript-language-features --extensionTestsPath=%~dp0\..\extensions\typescript-language-features\out\test\unit --enable-proposed-api=vscode.typescript-language-features %API_TESTS_EXTRA_ARGS% +call "%INTEGRATION_TEST_ELECTRON_PATH%" %~dp0\..\extensions\typescript-language-features\test-workspace --extensionDevelopmentPath=%~dp0\..\extensions\typescript-language-features --extensionTestsPath=%~dp0\..\extensions\typescript-language-features\out\test\unit %API_TESTS_EXTRA_ARGS% if %errorlevel% neq 0 exit /b %errorlevel% echo. @@ -92,7 +92,7 @@ echo ### Git tests for /f "delims=" %%i in ('node -p "require('fs').realpathSync.native(require('os').tmpdir())"') do set TEMPDIR=%%i set GITWORKSPACE=%TEMPDIR%\git-%RANDOM% mkdir %GITWORKSPACE% -call "%INTEGRATION_TEST_ELECTRON_PATH%" %GITWORKSPACE% --extensionDevelopmentPath=%~dp0\..\extensions\git --extensionTestsPath=%~dp0\..\extensions\git\out\test --enable-proposed-api=vscode.git %API_TESTS_EXTRA_ARGS% +call "%INTEGRATION_TEST_ELECTRON_PATH%" %GITWORKSPACE% --extensionDevelopmentPath=%~dp0\..\extensions\git --extensionTestsPath=%~dp0\..\extensions\git\out\test %API_TESTS_EXTRA_ARGS% if %errorlevel% neq 0 exit /b %errorlevel% echo. diff --git a/scripts/test-integration.sh b/scripts/test-integration.sh index f0d239a470d..8ddfa7e9eae 100755 --- a/scripts/test-integration.sh +++ b/scripts/test-integration.sh @@ -93,7 +93,7 @@ kill_app echo echo "### TypeScript tests" echo -"$INTEGRATION_TEST_ELECTRON_PATH" $LINUX_EXTRA_ARGS $ROOT/extensions/typescript-language-features/test-workspace --enable-proposed-api=vscode.typescript-language-features --extensionDevelopmentPath=$ROOT/extensions/typescript-language-features --extensionTestsPath=$ROOT/extensions/typescript-language-features/out/test/unit $API_TESTS_EXTRA_ARGS +"$INTEGRATION_TEST_ELECTRON_PATH" $LINUX_EXTRA_ARGS $ROOT/extensions/typescript-language-features/test-workspace --extensionDevelopmentPath=$ROOT/extensions/typescript-language-features --extensionTestsPath=$ROOT/extensions/typescript-language-features/out/test/unit $API_TESTS_EXTRA_ARGS kill_app echo @@ -111,7 +111,7 @@ kill_app echo echo "### Git tests" echo -"$INTEGRATION_TEST_ELECTRON_PATH" $LINUX_EXTRA_ARGS $(mktemp -d 2>/dev/null) --enable-proposed-api=vscode.git --extensionDevelopmentPath=$ROOT/extensions/git --extensionTestsPath=$ROOT/extensions/git/out/test $API_TESTS_EXTRA_ARGS +"$INTEGRATION_TEST_ELECTRON_PATH" $LINUX_EXTRA_ARGS $(mktemp -d 2>/dev/null) --extensionDevelopmentPath=$ROOT/extensions/git --extensionTestsPath=$ROOT/extensions/git/out/test $API_TESTS_EXTRA_ARGS kill_app echo diff --git a/scripts/test-remote-integration.bat b/scripts/test-remote-integration.bat index 396737b831e..1c04b2e392c 100644 --- a/scripts/test-remote-integration.bat +++ b/scripts/test-remote-integration.bat @@ -64,10 +64,10 @@ if "%INTEGRATION_TEST_ELECTRON_PATH%"=="" ( set ELECTRON_ENABLE_STACK_DUMPING=1 :: Tests in the extension host running from built version (both client and server) - call "%INTEGRATION_TEST_ELECTRON_PATH%" --folder-uri=%REMOTE_VSCODE%/vscode-api-tests/testWorkspace --extensionDevelopmentPath=%REMOTE_VSCODE%/vscode-api-tests --extensionTestsPath=%REMOTE_VSCODE%/vscode-api-tests/out/singlefolder-tests %API_TESTS_EXTRA_ARGS% --extensions-dir=%EXT_PATH% --enable-proposed-api=vscode.vscode-test-resolver --enable-proposed-api=vscode.vscode-api-tests --enable-proposed-api=vscode.image-preview + call "%INTEGRATION_TEST_ELECTRON_PATH%" --folder-uri=%REMOTE_VSCODE%/vscode-api-tests/testWorkspace --extensionDevelopmentPath=%REMOTE_VSCODE%/vscode-api-tests --extensionTestsPath=%REMOTE_VSCODE%/vscode-api-tests/out/singlefolder-tests %API_TESTS_EXTRA_ARGS% --extensions-dir=%EXT_PATH% --enable-proposed-api=vscode.vscode-test-resolver --enable-proposed-api=vscode.vscode-api-tests if %errorlevel% neq 0 exit /b %errorlevel% - call "%INTEGRATION_TEST_ELECTRON_PATH%" --file-uri=%REMOTE_VSCODE%/vscode-api-tests/testworkspace.code-workspace --extensionDevelopmentPath=%REMOTE_VSCODE%/vscode-api-tests --extensionTestsPath=%REMOTE_VSCODE%/vscode-api-tests/out/workspace-tests %API_TESTS_EXTRA_ARGS% --extensions-dir=%EXT_PATH% --enable-proposed-api=vscode.vscode-test-resolver --enable-proposed-api=vscode.vscode-api-tests --enable-proposed-api=vscode.image-preview + call "%INTEGRATION_TEST_ELECTRON_PATH%" --file-uri=%REMOTE_VSCODE%/vscode-api-tests/testworkspace.code-workspace --extensionDevelopmentPath=%REMOTE_VSCODE%/vscode-api-tests --extensionTestsPath=%REMOTE_VSCODE%/vscode-api-tests/out/workspace-tests %API_TESTS_EXTRA_ARGS% --extensions-dir=%EXT_PATH% --enable-proposed-api=vscode.vscode-test-resolver --enable-proposed-api=vscode.vscode-api-tests if %errorlevel% neq 0 exit /b %errorlevel% ) diff --git a/scripts/test-remote-integration.sh b/scripts/test-remote-integration.sh index 48f10d5b2c4..e2212317d3b 100755 --- a/scripts/test-remote-integration.sh +++ b/scripts/test-remote-integration.sh @@ -63,7 +63,7 @@ else export ELECTRON_ENABLE_LOGGING=1 # Running from a build, we need to enable the vscode-test-resolver extension - EXTRA_INTEGRATION_TEST_ARGUMENTS="--extensions-dir=$EXT_PATH --enable-proposed-api=vscode.vscode-test-resolver --enable-proposed-api=vscode.vscode-api-tests --enable-proposed-api=vscode.image-preview --enable-proposed-api=vscode.git --enable-proposed-api=vscode.markdown-language-features" + EXTRA_INTEGRATION_TEST_ARGUMENTS="--extensions-dir=$EXT_PATH --enable-proposed-api=vscode.vscode-test-resolver --enable-proposed-api=vscode.vscode-api-tests" echo "Storing crash reports into '$VSCODECRASHDIR'." echo "Storing log files into '$VSCODELOGSDIR'." @@ -105,7 +105,7 @@ kill_app echo echo "### TypeScript tests" echo -"$INTEGRATION_TEST_ELECTRON_PATH" $LINUX_EXTRA_ARGS --folder-uri=$REMOTE_VSCODE/typescript-language-features/test-workspace --enable-proposed-api=vscode.typescript-language-features --extensionDevelopmentPath=$REMOTE_VSCODE/typescript-language-features --extensionTestsPath=$REMOTE_VSCODE/typescript-language-features/out/test/unit $API_TESTS_EXTRA_ARGS $EXTRA_INTEGRATION_TEST_ARGUMENTS +"$INTEGRATION_TEST_ELECTRON_PATH" $LINUX_EXTRA_ARGS --folder-uri=$REMOTE_VSCODE/typescript-language-features/test-workspace --extensionDevelopmentPath=$REMOTE_VSCODE/typescript-language-features --extensionTestsPath=$REMOTE_VSCODE/typescript-language-features/out/test/unit $API_TESTS_EXTRA_ARGS $EXTRA_INTEGRATION_TEST_ARGUMENTS kill_app echo diff --git a/scripts/test-web-integration.bat b/scripts/test-web-integration.bat index 6f778843e05..99bb16b7d5e 100644 --- a/scripts/test-web-integration.bat +++ b/scripts/test-web-integration.bat @@ -68,5 +68,5 @@ echo ### Git tests for /f "delims=" %%i in ('node -p "require('fs').realpathSync.native(require('os').tmpdir())"') do set TEMPDIR=%%i set GITWORKSPACE=%TEMPDIR%\git-%RANDOM% mkdir %GITWORKSPACE% -call node .\test\integration\browser\out\index.js --workspacePath=%GITWORKSPACE% --extensionDevelopmentPath=.\extensions\git --extensionTestsPath=.\extensions\git\out\test --enable-proposed-api=vscode.git %* +call node .\test\integration\browser\out\index.js --workspacePath=%GITWORKSPACE% --extensionDevelopmentPath=.\extensions\git --extensionTestsPath=.\extensions\git\out\test %* if %errorlevel% neq 0 exit /b %errorlevel% diff --git a/scripts/test-web-integration.sh b/scripts/test-web-integration.sh index f1b6c7683ac..8f05929fdc4 100755 --- a/scripts/test-web-integration.sh +++ b/scripts/test-web-integration.sh @@ -63,10 +63,10 @@ node test/integration/browser/out/index.js --workspacePath $ROOT/extensions/emme echo echo "### Git tests" echo -node test/integration/browser/out/index.js --workspacePath $(mktemp -d 2>/dev/null) --enable-proposed-api=vscode.git --extensionDevelopmentPath=$ROOT/extensions/git --extensionTestsPath=$ROOT/extensions/git/out/test "$@" +node test/integration/browser/out/index.js --workspacePath $(mktemp -d 2>/dev/null) --extensionDevelopmentPath=$ROOT/extensions/git --extensionTestsPath=$ROOT/extensions/git/out/test "$@" echo echo "### Ipynb tests" echo -node test/integration/browser/out/index.js --workspacePath $(mktemp -d 2>/dev/null) --enable-proposed-api=vscode.ipynb --extensionDevelopmentPath=$ROOT/extensions/ipynb --extensionTestsPath=$ROOT/extensions/ipynb/out/test "$@" +node test/integration/browser/out/index.js --workspacePath $(mktemp -d 2>/dev/null) --extensionDevelopmentPath=$ROOT/extensions/ipynb --extensionTestsPath=$ROOT/extensions/ipynb/out/test "$@" diff --git a/src/vs/platform/extensionManagement/common/extensionsScannerService.ts b/src/vs/platform/extensionManagement/common/extensionsScannerService.ts index 8a8746587f1..93936e42335 100644 --- a/src/vs/platform/extensionManagement/common/extensionsScannerService.ts +++ b/src/vs/platform/extensionManagement/common/extensionsScannerService.ts @@ -23,7 +23,7 @@ import { URI } from 'vs/base/common/uri'; import { localize } from 'vs/nls'; import { IEnvironmentService } from 'vs/platform/environment/common/environment'; import { Metadata } from 'vs/platform/extensionManagement/common/extensionManagement'; -import { computeTargetPlatform, ExtensionKey, getExtensionId, getGalleryExtensionId } from 'vs/platform/extensionManagement/common/extensionManagementUtil'; +import { areSameExtensions, computeTargetPlatform, ExtensionKey, getExtensionId, getGalleryExtensionId } from 'vs/platform/extensionManagement/common/extensionManagementUtil'; import { ExtensionType, ExtensionIdentifier, IExtensionManifest, TargetPlatform, IExtensionIdentifier, IRelaxedExtensionManifest, UNDEFINED_PUBLISHER, IExtensionDescription, BUILTIN_MANIFEST_CACHE_FILE, USER_MANIFEST_CACHE_FILE, MANIFEST_CACHE_FOLDER } from 'vs/platform/extensions/common/extensions'; import { validateExtensionManifest } from 'vs/platform/extensions/common/extensionValidator'; import { FileOperationResult, IFileService, toFileOperationResult } from 'vs/platform/files/common/files'; @@ -118,7 +118,7 @@ export interface IExtensionsScannerService { scanAllExtensions(scanOptions: ScanOptions): Promise; scanSystemExtensions(scanOptions: ScanOptions): Promise; scanUserExtensions(scanOptions: ScanOptions): Promise; - scanExtensionsUnderDevelopment(scanOptions: ScanOptions): Promise; + scanExtensionsUnderDevelopment(scanOptions: ScanOptions, existingExtensions: IScannedExtension[]): Promise; scanExistingExtension(extensionLocation: URI, extensionType: ExtensionType, scanOptions: ScanOptions): Promise; scanOneOrMultipleExtensions(extensionLocation: URI, extensionType: ExtensionType, scanOptions: ScanOptions): Promise; @@ -163,11 +163,11 @@ export abstract class AbstractExtensionsScannerService extends Disposable implem } async scanAllExtensions(scanOptions: ScanOptions): Promise { - const [system, user, development] = await Promise.all([ + const [system, user] = await Promise.all([ this.scanSystemExtensions(scanOptions), this.scanUserExtensions(scanOptions), - this.scanExtensionsUnderDevelopment(scanOptions), ]); + const development = await this.scanExtensionsUnderDevelopment(scanOptions, [...system, ...user]); return this.dedupExtensions([...system, ...user, ...development], await this.getTargetPlatform(), true); } @@ -189,10 +189,19 @@ export abstract class AbstractExtensionsScannerService extends Disposable implem return extensions; } - async scanExtensionsUnderDevelopment(scanOptions: ScanOptions): Promise { + async scanExtensionsUnderDevelopment(scanOptions: ScanOptions, existingExtensions: IScannedExtension[]): Promise { if (this.environmentService.isExtensionDevelopment && this.environmentService.extensionDevelopmentLocationURI) { const extensions = (await Promise.all(this.environmentService.extensionDevelopmentLocationURI.filter(extLoc => extLoc.scheme === Schemas.file) - .map(async extensionDevelopmentLocationURI => this.extensionsScanner.scanOneOrMultipleExtensions((await this.createExtensionScannerInput(extensionDevelopmentLocationURI, ExtensionType.User, true, scanOptions.language)))))) + .map(async extensionDevelopmentLocationURI => { + const input = await this.createExtensionScannerInput(extensionDevelopmentLocationURI, ExtensionType.User, true, scanOptions.language, false /* do not validate */); + const extensions = await this.extensionsScanner.scanOneOrMultipleExtensions(input); + return extensions.map(extension => { + // Override the extension type from the existing extensions + extension.type = existingExtensions.find(e => areSameExtensions(e.identifier, extension.identifier))?.type ?? extension.type; + // Validate the extension + return this.extensionsScanner.validate(extension, input); + }); + }))) .flat(); return this.applyScanOptions(extensions, scanOptions, true); } @@ -327,7 +336,7 @@ export abstract class AbstractExtensionsScannerService extends Disposable implem } } - private async createExtensionScannerInput(location: URI, type: ExtensionType, excludeObsolete: boolean, language: string | undefined): Promise { + private async createExtensionScannerInput(location: URI, type: ExtensionType, excludeObsolete: boolean, language: string | undefined, validate: boolean = true): Promise { const translations = await this.getTranslations(language ?? platform.language); let mtime: number | undefined; try { @@ -343,6 +352,7 @@ export abstract class AbstractExtensionsScannerService extends Disposable implem mtime, type, excludeObsolete, + validate, this.productService.version, this.productService.date, this.productService.commit, @@ -361,6 +371,7 @@ class ExtensionScannerInput { public readonly mtime: number | undefined, public readonly type: ExtensionType, public readonly excludeObsolete: boolean, + public readonly validate: boolean, public readonly productVersion: string, public readonly productDate: string | undefined, public readonly productCommit: string | undefined, @@ -386,6 +397,7 @@ class ExtensionScannerInput { && a.mtime === b.mtime && a.type === b.type && a.excludeObsolete === b.excludeObsolete + && a.validate === b.validate && a.productVersion === b.productVersion && a.productDate === b.productDate && a.productCommit === b.productCommit @@ -431,7 +443,7 @@ class ExtensionsScanner extends Disposable { if (input.type === ExtensionType.User && basename(c.resource).indexOf('.') === 0) { return null; } - const extensionScannerInput = new ExtensionScannerInput(c.resource, input.mtime, input.type, input.excludeObsolete, input.productVersion, input.productDate, input.productCommit, input.devMode, input.language, input.translations); + const extensionScannerInput = new ExtensionScannerInput(c.resource, input.mtime, input.type, input.excludeObsolete, input.validate, input.productVersion, input.productDate, input.productCommit, input.devMode, input.language, input.translations); const extension = await this.scanExtension(extensionScannerInput); return extension && !obsolete[ExtensionKey.create(extension).toString()] ? extension : null; })); @@ -440,7 +452,7 @@ class ExtensionsScanner extends Disposable { return []; } - async scanOneOrMultipleExtensions(input: ExtensionScannerInput): Promise { + async scanOneOrMultipleExtensions(input: ExtensionScannerInput): Promise { try { if (await this.fileService.exists(joinPath(input.location, 'package.json'))) { const extension = await this.scanExtension(input); @@ -468,16 +480,8 @@ class ExtensionsScanner extends Disposable { const identifier = metadata?.id ? { id, uuid: metadata.id } : { id }; const type = metadata?.isSystem ? ExtensionType.System : input.type; const isBuiltin = type === ExtensionType.System || !!metadata?.isBuiltin; - const validations = validateExtensionManifest(input.productVersion, input.productDate, input.location, manifest, isBuiltin); - let isValid = true; - for (const [severity, message] of validations) { - if (severity === Severity.Error) { - isValid = false; - this.logService.error(this.formatMessage(input.location, message)); - } - } manifest = await this.translateManifest(input.location, manifest, ExtensionScannerInput.createNlsConfiguration(input)); - return { + const extension = { type, identifier, manifest, @@ -485,9 +489,10 @@ class ExtensionsScanner extends Disposable { isBuiltin, targetPlatform: metadata?.targetPlatform ?? TargetPlatform.UNDEFINED, metadata, - isValid, - validations + isValid: true, + validations: [] }; + return input.validate ? this.validate(extension, input) : extension; } } catch (e) { if (input.type !== ExtensionType.System) { @@ -497,6 +502,20 @@ class ExtensionsScanner extends Disposable { return null; } + validate(extension: IRelaxedScannedExtension, input: ExtensionScannerInput): IRelaxedScannedExtension { + let isValid = true; + const validations = validateExtensionManifest(input.productVersion, input.productDate, input.location, extension.manifest, extension.isBuiltin); + for (const [severity, message] of validations) { + if (severity === Severity.Error) { + isValid = false; + this.logService.error(this.formatMessage(input.location, message)); + } + } + extension.isValid = isValid; + extension.validations = validations; + return extension; + } + private async scanExtensionManifest(extensionLocation: URI): Promise { const manifestLocation = joinPath(extensionLocation, 'package.json'); let content; diff --git a/src/vs/workbench/services/extensions/electron-sandbox/cachedExtensionScanner.ts b/src/vs/workbench/services/extensions/electron-sandbox/cachedExtensionScanner.ts index f15839b061e..852ea7702e9 100644 --- a/src/vs/workbench/services/extensions/electron-sandbox/cachedExtensionScanner.ts +++ b/src/vs/workbench/services/extensions/electron-sandbox/cachedExtensionScanner.ts @@ -50,21 +50,15 @@ export class CachedExtensionScanner { } private async _scanInstalledExtensions(): Promise<{ system: IExtensionDescription[]; user: IExtensionDescription[]; development: IExtensionDescription[] }> { - const language = platform.language; - - const builtinExtensions = this._extensionsScannerService.scanSystemExtensions({ language, useCache: true, checkControlFile: true }) - .then(scannedExtensions => scannedExtensions.map(e => toExtensionDescription(e, false))); - - const userExtensions = this._extensionsScannerService.scanUserExtensions({ language, useCache: true }) - .then(scannedExtensions => scannedExtensions.map(e => toExtensionDescription(e, false))); - - const developedExtensions = this._extensionsScannerService.scanExtensionsUnderDevelopment({ language }) - .then(scannedExtensions => scannedExtensions.map(e => toExtensionDescription(e, true))); - - return Promise.all([builtinExtensions, userExtensions, developedExtensions]).then((extensionDescriptions: IExtensionDescription[][]) => { - const system = extensionDescriptions[0]; - const user = extensionDescriptions[1]; - const development = extensionDescriptions[2]; + try { + const language = platform.language; + const [scannedSystemExtensions, scannedUserExtensions] = await Promise.all([ + this._extensionsScannerService.scanSystemExtensions({ language, useCache: true, checkControlFile: true }), + this._extensionsScannerService.scanUserExtensions({ language, useCache: true })]); + const scannedDevelopedExtensions = await this._extensionsScannerService.scanExtensionsUnderDevelopment({ language }, [...scannedSystemExtensions, ...scannedUserExtensions]); + const system = scannedSystemExtensions.map(e => toExtensionDescription(e, false)); + const user = scannedUserExtensions.map(e => toExtensionDescription(e, false)); + const development = scannedDevelopedExtensions.map(e => toExtensionDescription(e, true)); const disposable = this._extensionsScannerService.onDidChangeCache(() => { disposable.dispose(); this._notificationService.prompt( @@ -78,11 +72,11 @@ export class CachedExtensionScanner { }); timeout(5000).then(() => disposable.dispose()); return { system, user, development }; - }).then(undefined, err => { + } catch (err) { this._logService.error(`Error scanning installed extensions:`); this._logService.error(err); return { system: [], user: [], development: [] }; - }); + } } }