diff --git a/package-lock.json b/package-lock.json index 878983cb6..b8ee77a4c 100644 --- a/package-lock.json +++ b/package-lock.json @@ -1,6 +1,6 @@ { "name": "csharp", - "version": "1.14.0-beta2", + "version": "1.14.0-beta3", "lockfileVersion": 1, "requires": true, "dependencies": { @@ -93,11 +93,6 @@ "integrity": "sha1-tDLdM1i2NM914eRmQ2gkBTPB3b4=", "dev": true }, - "applicationinsights": { - "version": "0.18.0", - "resolved": "https://registry.npmjs.org/applicationinsights/-/applicationinsights-0.18.0.tgz", - "integrity": "sha1-Fi67SKODQIvE3kTbMrQXMH9Fu8E=" - }, "archy": { "version": "1.0.0", "resolved": "https://registry.npmjs.org/archy/-/archy-1.0.0.tgz", @@ -397,6 +392,14 @@ "integrity": "sha512-jWAvZu1BV8tL3pj0iosBECzzHEg+XB1zSnMjJGX83bGi/1GlGdDO7J/A0sbBBS6KJT0FVqZIzZW9C6WLiMkHpQ==", "dev": true }, + "chai-as-promised": { + "version": "7.1.1", + "resolved": "https://registry.npmjs.org/chai-as-promised/-/chai-as-promised-7.1.1.tgz", + "integrity": "sha512-azL6xMoi+uxu6z4rhWQ1jbdUhOMhis2PvscD/xjLqNMkv3BPPp2JyyuTHOrf9BOosGpNQ11v6BKv/g57RXbiaA==", + "requires": { + "check-error": "1.0.2" + } + }, "chai-fs": { "version": "2.0.0", "resolved": "https://registry.npmjs.org/chai-fs/-/chai-fs-2.0.0.tgz", @@ -423,8 +426,7 @@ "check-error": { "version": "1.0.2", "resolved": "https://registry.npmjs.org/check-error/-/check-error-1.0.2.tgz", - "integrity": "sha1-V00xLt2Iu13YkS6Sht1sCu1KrII=", - "dev": true + "integrity": "sha1-V00xLt2Iu13YkS6Sht1sCu1KrII=" }, "cheerio": { "version": "1.0.0-rc.2", @@ -697,6 +699,19 @@ "fs-exists-sync": "0.1.0" } }, + "diagnostic-channel": { + "version": "0.2.0", + "resolved": "https://registry.npmjs.org/diagnostic-channel/-/diagnostic-channel-0.2.0.tgz", + "integrity": "sha1-zJmvlhLCP7H/8TYSxy8sv6qNWhc=", + "requires": { + "semver": "5.4.1" + } + }, + "diagnostic-channel-publishers": { + "version": "0.2.1", + "resolved": "https://registry.npmjs.org/diagnostic-channel-publishers/-/diagnostic-channel-publishers-0.2.1.tgz", + "integrity": "sha1-ji1geottef6IC1SLxYzGvrKIxPM=" + }, "diff": { "version": "3.2.0", "resolved": "https://registry.npmjs.org/diff/-/diff-3.2.0.tgz", @@ -5127,12 +5142,23 @@ "integrity": "sha512-e1EUy/5npqa0NlAwRCUu8A9LnVRf6tkwiPQcCLyUFCC9o2GxcAqH5Va4mqXDoxQ58ar3zODivKQeRb3z1KH7WA==" }, "vscode-extension-telemetry": { - "version": "0.0.8", - "resolved": "https://registry.npmjs.org/vscode-extension-telemetry/-/vscode-extension-telemetry-0.0.8.tgz", - "integrity": "sha1-ImG/+Ya2aQpvH3RqRaxb0fhdKeA=", + "version": "0.0.11", + "resolved": "https://registry.npmjs.org/vscode-extension-telemetry/-/vscode-extension-telemetry-0.0.11.tgz", + "integrity": "sha512-P1ALLofywtfpQy9TB5Sx2edp80fHXby+CGG5pq8P1vPL2zKIUjYy3eK8mFHCOGeljTf2PTTmXJ98DeBV0kCafQ==", "requires": { - "applicationinsights": "0.18.0", - "winreg": "1.2.3" + "applicationinsights": "1.0.1" + }, + "dependencies": { + "applicationinsights": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/applicationinsights/-/applicationinsights-1.0.1.tgz", + "integrity": "sha1-U0Rrgw/o1dYZ7uKieLMdPSUDCSc=", + "requires": { + "diagnostic-channel": "0.2.0", + "diagnostic-channel-publishers": "0.2.1", + "zone.js": "0.7.6" + } + } } }, "vscode-nls": { @@ -5161,11 +5187,6 @@ "isexe": "2.0.0" } }, - "winreg": { - "version": "1.2.3", - "resolved": "https://registry.npmjs.org/winreg/-/winreg-1.2.3.tgz", - "integrity": "sha1-k60RayaW2ofVj3JlqPzqUlSpZdU=" - }, "wolfy87-eventemitter": { "version": "5.2.4", "resolved": "https://registry.npmjs.org/wolfy87-eventemitter/-/wolfy87-eventemitter-5.2.4.tgz", @@ -5219,6 +5240,11 @@ "requires": { "buffer-crc32": "0.2.13" } + }, + "zone.js": { + "version": "0.7.6", + "resolved": "https://registry.npmjs.org/zone.js/-/zone.js-0.7.6.tgz", + "integrity": "sha1-+7w50+AmHQmG8boGMG6zrrDSIAk=" } } } diff --git a/package.json b/package.json index 5c300a0dd..e168b3d1e 100644 --- a/package.json +++ b/package.json @@ -66,6 +66,7 @@ "chai": "4.1.2", "chai-arrays": "2.0.0", "chai-fs": "2.0.0", + "chai-as-promised": "7.1.1", "cross-env": "5.1.1", "del": "3.0.0", "gulp": "3.9.1", @@ -93,7 +94,8 @@ "architectures": [ "x86" ], - "installTestPath": "./.omnisharp/OmniSharp.exe" + "installTestPath": "./.omnisharp/OmniSharp.exe", + "platformId": "win-x86" }, { "description": "OmniSharp for Windows (.NET 4.6 / x64)", @@ -106,7 +108,8 @@ "architectures": [ "x86_64" ], - "installTestPath": "./.omnisharp/OmniSharp.exe" + "installTestPath": "./.omnisharp/OmniSharp.exe", + "platformId": "win-x64" }, { "description": "OmniSharp for OSX", @@ -120,7 +123,8 @@ "./mono.osx", "./run" ], - "installTestPath": "./.omnisharp/mono.osx" + "installTestPath": "./.omnisharp/mono.osx", + "platformId": "osx" }, { "description": "OmniSharp for Linux (x86)", @@ -138,7 +142,8 @@ "./mono.linux-x86", "./run" ], - "installTestPath": "./.omnisharp/mono.linux-x86" + "installTestPath": "./.omnisharp/mono.linux-x86", + "platformId": "linux-x86" }, { "description": "OmniSharp for Linux (x64)", @@ -155,7 +160,8 @@ "./mono.linux-x86_64", "./run" ], - "installTestPath": "./.omnisharp/mono.linux-x86_64" + "installTestPath": "./.omnisharp/mono.linux-x86_64", + "platformId": "linux-x64" }, { "description": ".NET Core Debugger (Windows / x64)", @@ -454,7 +460,7 @@ "null" ], "default": null, - "description": "Specifies the full path to the OmniSharp server." + "description": "Specifies the path to OmniSharp. This can be the absolute path to an OmniSharp executable, a specific version number, or \"latest\". If a version number or \"latest\" is specified, the appropriate version of OmniSharp will be downloaded on your behalf." }, "omnisharp.useMono": { "type": "boolean", @@ -2590,4 +2596,4 @@ } ] } -} \ No newline at end of file +} diff --git a/src/OmnisharpDownload.Helper.ts b/src/OmnisharpDownload.Helper.ts new file mode 100644 index 000000000..997ade140 --- /dev/null +++ b/src/OmnisharpDownload.Helper.ts @@ -0,0 +1,74 @@ +/*--------------------------------------------------------------------------------------------- +* Copyright (c) Microsoft Corporation. All rights reserved. +* Licensed under the MIT License. See License.txt in the project root for license information. +*--------------------------------------------------------------------------------------------*/ +import * as vscode from 'vscode'; +import { Status, PackageError } from './packages'; +import { PlatformInformation } from './platform'; +import { Logger } from './logger'; +import TelemetryReporter from 'vscode-extension-telemetry'; + +export function GetNetworkDependencies() { + const config = vscode.workspace.getConfiguration(); + const proxy = config.get('http.proxy'); + const strictSSL = config.get('http.proxyStrictSSL', true); + return { Proxy: proxy, StrictSSL: strictSSL }; +} + +export function SetStatus() { + let statusItem = vscode.window.createStatusBarItem(vscode.StatusBarAlignment.Right); + let status: Status = { + setMessage: text => { + statusItem.text = text; + statusItem.show(); + }, + setDetail: text => { + statusItem.tooltip = text; + statusItem.show(); + } + }; + + return { StatusItem: statusItem, Status: status }; +} + +export function ReportInstallationError(logger: Logger, error, telemetryProps: any, installationStage: string) { + let errorMessage: string; + if (error instanceof PackageError) { + // we can log the message in a PackageError to telemetry as we do not put PII in PackageError messages + telemetryProps['error.message'] = error.message; + if (error.innerError) { + errorMessage = error.innerError.toString(); + } + else { + errorMessage = error.message; + } + if (error.pkg) { + telemetryProps['error.packageUrl'] = error.pkg.url; + } + } + else { + // do not log raw errorMessage in telemetry as it is likely to contain PII. + errorMessage = error.toString(); + } + + logger.appendLine(); + logger.appendLine(`Failed at stage: ${installationStage}`); + logger.appendLine(errorMessage); +} + +export function SendInstallationTelemetry(logger: Logger, reporter: TelemetryReporter, telemetryProps: any, installationStage: string, platformInfo: PlatformInformation) { + telemetryProps['installStage'] = installationStage; + telemetryProps['platform.architecture'] = platformInfo.architecture; + telemetryProps['platform.platform'] = platformInfo.platform; + if (platformInfo.distribution) { + telemetryProps['platform.distribution'] = platformInfo.distribution.toTelemetryString(); + } + if (reporter) { + reporter.sendTelemetryEvent('Acquisition', telemetryProps); + } + + logger.appendLine(); + installationStage = ''; + logger.appendLine('Finished'); + logger.appendLine(); +} \ No newline at end of file diff --git a/src/main.ts b/src/main.ts index 7301d7904..ac6ffe997 100644 --- a/src/main.ts +++ b/src/main.ts @@ -32,7 +32,7 @@ export async function activate(context: vscode.ExtensionContext): Promise<{ init let runtimeDependenciesExist = await ensureRuntimeDependencies(extension, logger, reporter); // activate language services - let omniSharpPromise = OmniSharp.activate(context, reporter, _channel); + let omniSharpPromise = OmniSharp.activate(context, reporter, _channel, logger, extension.packageJSON); // register JSON completion & hover providers for project.json context.subscriptions.push(addJSONProviders()); diff --git a/src/omnisharp/OmnisharpDownloader.ts b/src/omnisharp/OmnisharpDownloader.ts new file mode 100644 index 000000000..50af5323e --- /dev/null +++ b/src/omnisharp/OmnisharpDownloader.ts @@ -0,0 +1,97 @@ +/*--------------------------------------------------------------------------------------------- + * Copyright (c) Microsoft Corporation. All rights reserved. + * Licensed under the MIT License. See License.txt in the project root for license information. + *--------------------------------------------------------------------------------------------*/ + +import * as vscode from 'vscode'; +import { PackageManager, Package, Status } from '../packages'; +import { PlatformInformation } from '../platform'; +import { Logger } from '../logger'; +import TelemetryReporter from 'vscode-extension-telemetry'; +import { GetPackagesFromVersion, GetVersionFilePackage } from './OmnisharpPackageCreator'; +import { SetStatus, ReportInstallationError, SendInstallationTelemetry, GetNetworkDependencies } from '../OmnisharpDownload.Helper'; + +export class OmnisharpDownloader { + private status: Status; + private statusItem: vscode.StatusBarItem; + private proxy: string; + private strictSSL: boolean; + private packageManager: PackageManager; + private telemetryProps: any; + + public constructor( + private channel: vscode.OutputChannel, + private logger: Logger, + private packageJSON: any, + private platformInfo: PlatformInformation, + private reporter?: TelemetryReporter) { + + let statusObject = SetStatus(); + this.status = statusObject.Status; + this.statusItem = statusObject.StatusItem; + + let networkObject = GetNetworkDependencies(); + this.proxy = networkObject.Proxy; + this.strictSSL = networkObject.StrictSSL; + + this.telemetryProps = {}; + this.packageManager = new PackageManager(this.platformInfo, this.packageJSON); + } + + public async DownloadAndInstallOmnisharp(version: string, serverUrl: string, installPath: string) { + this.logger.append('Installing Omnisharp Packages...'); + this.logger.appendLine(); + this.channel.show(); + + let installationStage = ''; + + if (this.reporter) { + this.reporter.sendTelemetryEvent("AcquisitionStart"); + } + + try { + this.logger.appendLine(`Platform: ${this.platformInfo.toString()}`); + this.logger.appendLine(); + + installationStage = 'getPackageInfo'; + let packages: Package[] = GetPackagesFromVersion(version, this.packageJSON.runtimeDependencies, serverUrl, installPath); + + installationStage = 'downloadPackages'; + + // Specify the packages that the package manager needs to download + this.packageManager.SetVersionPackagesForDownload(packages); + await this.packageManager.DownloadPackages(this.logger, this.status, this.proxy, this.strictSSL); + + this.logger.appendLine(); + + installationStage = 'installPackages'; + await this.packageManager.InstallPackages(this.logger, this.status); + + installationStage = 'completeSuccess'; + } + catch (error) { + ReportInstallationError(this.logger, error, this.telemetryProps, installationStage); + throw error;// throw the error up to the server + } + finally { + SendInstallationTelemetry(this.logger, this.reporter, this.telemetryProps, installationStage, this.platformInfo); + this.statusItem.dispose(); + } + } + + public async GetLatestVersion(serverUrl: string, latestVersionFileServerPath: string): Promise { + let installationStage = 'getLatestVersionInfoFile'; + try { + this.logger.appendLine('Getting latest build information...'); + this.logger.appendLine(); + //The package manager needs a package format to download, hence we form a package for the latest version file + let filePackage = GetVersionFilePackage(serverUrl, latestVersionFileServerPath); + //Fetch the latest version information from the file + return await this.packageManager.GetLatestVersionFromFile(this.logger, this.status, this.proxy, this.strictSSL, filePackage); + } + catch (error) { + ReportInstallationError(this.logger, error, this.telemetryProps, installationStage); + throw error; + } + } +} diff --git a/src/omnisharp/OmnisharpManager.ts b/src/omnisharp/OmnisharpManager.ts new file mode 100644 index 000000000..e60b1f6b1 --- /dev/null +++ b/src/omnisharp/OmnisharpManager.ts @@ -0,0 +1,74 @@ +/*--------------------------------------------------------------------------------------------- + * Copyright (c) Microsoft Corporation. All rights reserved. + * Licensed under the MIT License. See License.txt in the project root for license information. + *--------------------------------------------------------------------------------------------*/ + +import * as util from '../common'; +import * as path from 'path'; +import * as semver from 'semver'; +import * as vscode from 'vscode'; +import { Logger } from '../logger'; +import { OmnisharpDownloader } from './OmnisharpDownloader'; +import TelemetryReporter from 'vscode-extension-telemetry'; +import { PlatformInformation } from '../platform'; + +export class OmnisharpManager { + public constructor( + private channel: vscode.OutputChannel, + private logger: Logger, + private packageJSON: any, + private reporter?: TelemetryReporter) { + } + + public async GetOmnisharpPath(omnisharpPath: string, useMono: boolean, serverUrl: string, latestVersionFileServerPath: string, installPath: string, extensionPath: string, platformInfo: PlatformInformation): Promise { + // Looks at the options path, installs the dependencies and returns the path to be loaded by the omnisharp server + let downloader = new OmnisharpDownloader(this.channel, this.logger, this.packageJSON, platformInfo, this.reporter); + if (path.isAbsolute(omnisharpPath)) { + if (await util.fileExists(omnisharpPath)) { + return omnisharpPath; + } + else { + throw new Error('The system could not find the specified path'); + } + } + else if (omnisharpPath == "latest") { + return await this.InstallLatestAndReturnLaunchPath(downloader, useMono, serverUrl, latestVersionFileServerPath, installPath, extensionPath, platformInfo); + } + + //If the path is neither a valid path on disk not the string "latest", treat it as a version + return await this.InstallVersionAndReturnLaunchPath(downloader, omnisharpPath, useMono, serverUrl, installPath, extensionPath, platformInfo); + } + + private async InstallLatestAndReturnLaunchPath(downloader: OmnisharpDownloader, useMono: boolean, serverUrl: string, latestVersionFileServerPath: string, installPath: string, extensionPath: string, platformInfo: PlatformInformation) { + let version = await downloader.GetLatestVersion(serverUrl, latestVersionFileServerPath); + return await this.InstallVersionAndReturnLaunchPath(downloader, version, useMono, serverUrl, installPath, extensionPath, platformInfo); + } + + private async InstallVersionAndReturnLaunchPath(downloader: OmnisharpDownloader, version: string, useMono: boolean, serverUrl: string, installPath: string, extensionPath: string, platformInfo: PlatformInformation) { + if (semver.valid(version)) { + await downloader.DownloadAndInstallOmnisharp(version, serverUrl, installPath); + return GetLaunchPathForVersion(platformInfo, version, installPath, extensionPath, useMono); + } + else { + throw new Error(`Invalid omnisharp version - ${version}`); + } + } +} + +function GetLaunchPathForVersion(platformInfo: PlatformInformation, version: string, installPath: string, extensionPath: string, useMono: boolean) { + if (!version) { + throw new Error('Invalid Version'); + } + + let basePath = path.resolve(extensionPath, installPath, version); + + if (platformInfo.isWindows()) { + return path.join(basePath, 'OmniSharp.exe'); + } + if (useMono) { + return path.join(basePath, 'omnisharp', 'OmniSharp.exe'); + } + + return path.join(basePath, 'run'); +} + diff --git a/src/omnisharp/OmnisharpPackageCreator.ts b/src/omnisharp/OmnisharpPackageCreator.ts new file mode 100644 index 000000000..fb0d6bb2d --- /dev/null +++ b/src/omnisharp/OmnisharpPackageCreator.ts @@ -0,0 +1,61 @@ +/*--------------------------------------------------------------------------------------------- + * Copyright (c) Microsoft Corporation. All rights reserved. + * Licensed under the MIT License. See License.txt in the project root for license information. + *--------------------------------------------------------------------------------------------*/ + +import { Package } from "../packages"; + +export function GetPackagesFromVersion(version: string, runTimeDependencies: Package[], serverUrl: string, installPath: string): Package[] { + if (!version) { + throw new Error('Invalid version'); + } + + let versionPackages = new Array(); + for (let inputPackage of runTimeDependencies) { + if (inputPackage.platformId) { + versionPackages.push(SetBinaryAndGetPackage(inputPackage, serverUrl, version, installPath)); + } + } + + return versionPackages; +} + +export function SetBinaryAndGetPackage(inputPackage: Package, serverUrl: string, version: string, installPath: string): Package { + let installBinary: string; + if (inputPackage.platformId == "win-x86" || inputPackage.platformId == "win-x64") { + installBinary = "OmniSharp.exe"; + } + else if (inputPackage.platformId == "osx") { + installBinary = "mono.osx"; + } + else if (inputPackage.platformId == "linux-x86") { + installBinary = "mono.linux-x86"; + } + else if (inputPackage.platformId == "linux-x64") { + installBinary = "mono.linux-x86_64"; + } + + return GetPackage(inputPackage, serverUrl, version, installPath, installBinary); +} + +function GetPackage(inputPackage: Package, serverUrl: string, version: string, installPath: string, installBinary: string): Package { + if (!version) { + throw new Error('Invalid version'); + } + + let versionPackage = {...inputPackage, + "description": `${inputPackage.description}, Version = ${version}`, + "url": `${serverUrl}/releases/${version}/omnisharp-${inputPackage.platformId}.zip`, + "installPath": `${installPath}/${version}`, + "installTestPath": `./${installPath}/${version}/${installBinary}` + }; + + return versionPackage; +} + +export function GetVersionFilePackage(serverUrl: string, pathInServer: string): Package { + return { + "description": "Latest version information file", + "url": `${serverUrl}/${pathInServer}` + }; +} diff --git a/src/omnisharp/extension.ts b/src/omnisharp/extension.ts index 968b836b4..9c0301f73 100644 --- a/src/omnisharp/extension.ts +++ b/src/omnisharp/extension.ts @@ -32,10 +32,11 @@ import WorkspaceSymbolProvider from '../features/workspaceSymbolProvider'; import forwardChanges from '../features/changeForwarding'; import registerCommands from '../features/commands'; import reportStatus from '../features/status'; +import { Logger } from '../logger'; export let omnisharp: OmniSharpServer; -export function activate(context: vscode.ExtensionContext, reporter: TelemetryReporter, channel: vscode.OutputChannel) { +export function activate(context: vscode.ExtensionContext, reporter: TelemetryReporter, channel: vscode.OutputChannel, logger: Logger, packageJSON: any) { const documentSelector: vscode.DocumentSelector = { language: 'csharp', scheme: 'file' // only files from disk @@ -43,7 +44,7 @@ export function activate(context: vscode.ExtensionContext, reporter: TelemetryRe const options = Options.Read(); - const server = new OmniSharpServer(reporter); + const server = new OmniSharpServer(reporter, logger, channel, packageJSON); omnisharp = server; const advisor = new Advisor(server); // create before server is started const disposables: vscode.Disposable[] = []; diff --git a/src/omnisharp/launcher.ts b/src/omnisharp/launcher.ts index 2a70ac273..3c00a6bdb 100644 --- a/src/omnisharp/launcher.ts +++ b/src/omnisharp/launcher.ts @@ -46,7 +46,7 @@ export function findLaunchTargets(): Thenable { const options = Options.Read(); return vscode.workspace.findFiles( - /*include*/ '{**/*.sln,**/*.csproj,**/project.json,**/*.csx,**/*.cake}', + /*include*/ '{**/*.sln,**/*.csproj,**/project.json,**/*.csx,**/*.cake}', /*exclude*/ '{**/node_modules/**,**/.git/**,**/bower_components/**}', /*maxResults*/ options.maxProjectResults) .then(resourcesToLaunchTargets); @@ -88,8 +88,7 @@ function resourcesToLaunchTargets(resources: vscode.Uri[]): LaunchTarget[] { let targets: LaunchTarget[] = []; - workspaceFolderToUriMap.forEach((resources, folderIndex) => - { + workspaceFolderToUriMap.forEach((resources, folderIndex) => { let hasCsProjFiles = false, hasSlnFile = false, hasProjectJson = false, @@ -98,15 +97,15 @@ function resourcesToLaunchTargets(resources: vscode.Uri[]): LaunchTarget[] { hasCake = false; hasCsProjFiles = resources.some(isCSharpProject); - + let folder = vscode.workspace.workspaceFolders[folderIndex]; let folderPath = folder.uri.fsPath; - + resources.forEach(resource => { // Add .sln files if there are .csproj files if (hasCsProjFiles && isSolution(resource)) { hasSlnFile = true; - + targets.push({ label: path.basename(resource.fsPath), description: vscode.workspace.asRelativePath(path.dirname(resource.fsPath)), @@ -115,13 +114,13 @@ function resourcesToLaunchTargets(resources: vscode.Uri[]): LaunchTarget[] { kind: LaunchTargetKind.Solution }); } - + // Add project.json files if (isProjectJson(resource)) { const dirname = path.dirname(resource.fsPath); hasProjectJson = true; hasProjectJsonAtRoot = hasProjectJsonAtRoot || dirname === folderPath; - + targets.push({ label: path.basename(resource.fsPath), description: vscode.workspace.asRelativePath(path.dirname(resource.fsPath)), @@ -130,18 +129,18 @@ function resourcesToLaunchTargets(resources: vscode.Uri[]): LaunchTarget[] { kind: LaunchTargetKind.ProjectJson }); } - + // Discover if there is any CSX file if (!hasCSX && isCsx(resource)) { hasCSX = true; } - + // Discover if there is any Cake file if (!hasCake && isCake(resource)) { hasCake = true; } }); - + // Add the root folder under the following circumstances: // * If there are .csproj files, but no .sln file, and none in the root. // * If there are project.json files, but none in the root. @@ -154,7 +153,7 @@ function resourcesToLaunchTargets(resources: vscode.Uri[]): LaunchTarget[] { kind: LaunchTargetKind.Folder }); } - + // if we noticed any CSX file(s), add a single CSX-specific target pointing at the root folder if (hasCSX) { targets.push({ @@ -165,7 +164,7 @@ function resourcesToLaunchTargets(resources: vscode.Uri[]): LaunchTarget[] { kind: LaunchTargetKind.Csx }); } - + // if we noticed any Cake file(s), add a single Cake-specific target pointing at the root folder if (hasCake) { targets.push({ @@ -207,9 +206,9 @@ export interface LaunchResult { usingMono: boolean; } -export function launchOmniSharp(cwd: string, args: string[]): Promise { +export function launchOmniSharp(cwd: string, args: string[], launchPath: string): Promise { return new Promise((resolve, reject) => { - launch(cwd, args) + launch(cwd, args, launchPath) .then(result => { // async error - when target not not ENEOT result.process.on('error', err => { @@ -225,12 +224,11 @@ export function launchOmniSharp(cwd: string, args: string[]): Promise { +function launch(cwd: string, args: string[], launchPath: string): Promise { return PlatformInformation.GetCurrent().then(platformInfo => { const options = Options.Read(); - if (options.useEditorFormattingSettings) - { + if (options.useEditorFormattingSettings) { let globalConfig = vscode.workspace.getConfiguration(); let csharpConfig = vscode.workspace.getConfiguration('[csharp]'); @@ -239,18 +237,18 @@ function launch(cwd: string, args: string[]): Promise { args.push(`formattingOptions:indentationSize=${getConfigurationValue(globalConfig, csharpConfig, 'editor.tabSize', 4)}`); } - // If the user has provide a path to OmniSharp, we'll use that. - if (options.path) { + // If the user has provided an absolute path or the specified version has been installed successfully, we'll use the path. + if (launchPath) { if (platformInfo.isWindows()) { - return launchWindows(options.path, cwd, args); + return launchWindows(launchPath, cwd, args); } // If we're launching on macOS/Linux, we have two possibilities: // 1. Launch using Mono // 2. Launch process directly (e.g. a 'run' script) return options.useMono - ? launchNixMono(options.path, cwd, args) - : launchNix(options.path, cwd, args); + ? launchNixMono(launchPath, cwd, args) + : launchNix(launchPath, cwd, args); } // If the user has not provided a path, we'll use the locally-installed OmniSharp @@ -274,11 +272,11 @@ function launch(cwd: string, args: string[]): Promise { function getConfigurationValue(globalConfig: vscode.WorkspaceConfiguration, csharpConfig: vscode.WorkspaceConfiguration, configurationPath: string, defaultValue: any): any { - + if (csharpConfig[configurationPath] != undefined) { return csharpConfig[configurationPath]; } - + return globalConfig.get(configurationPath, defaultValue); } @@ -287,7 +285,7 @@ function launchWindows(launchPath: string, cwd: string, args: string[]): LaunchR const hasSpaceWithoutQuotes = /^[^"].* .*[^"]/; return hasSpaceWithoutQuotes.test(arg) ? `"${arg}"` - : arg.replace("&","^&"); + : arg.replace("&", "^&"); } let argsCopy = args.slice(0); // create copy of args diff --git a/src/omnisharp/server.ts b/src/omnisharp/server.ts index bd3b9c8a9..6de6e4c38 100644 --- a/src/omnisharp/server.ts +++ b/src/omnisharp/server.ts @@ -19,6 +19,8 @@ import * as protocol from './protocol'; import * as utils from '../common'; import * as vscode from 'vscode'; import { setTimeout } from 'timers'; +import { OmnisharpManager } from './OmnisharpManager'; +import { PlatformInformation } from '../platform'; enum ServerState { Starting, @@ -58,6 +60,9 @@ module Events { } const TelemetryReportingDelay = 2 * 60 * 1000; // two minutes +const serverUrl = "https://roslynomnisharp.blob.core.windows.net"; +const installPath = ".omnisharp/experimental"; +const latestVersionFileServerPath = 'releases/versioninfo.txt'; export class OmniSharpServer { @@ -82,7 +87,11 @@ export class OmniSharpServer { private _serverProcess: ChildProcess; private _options: Options; - constructor(reporter: TelemetryReporter) { + private _csharpLogger: Logger; + private _csharpChannel: vscode.OutputChannel; + private _packageJSON: any; + + constructor(reporter: TelemetryReporter, csharpLogger?: Logger, csharpChannel?: vscode.OutputChannel, packageJSON?: any) { this._reporter = reporter; this._channel = vscode.window.createOutputChannel('OmniSharp Log'); @@ -93,17 +102,20 @@ export class OmniSharpServer { : new Logger(message => { }); this._requestQueue = new RequestQueueCollection(logger, 8, request => this._makeRequest(request)); + this._csharpLogger = csharpLogger; + this._csharpChannel = csharpChannel; + this._packageJSON = packageJSON; } public isRunning(): boolean { return this._state === ServerState.Started; } - public async waitForEmptyEventQueue() : Promise { + public async waitForEmptyEventQueue(): Promise { while (!this._requestQueue.isEmpty()) { let p = new Promise((resolve) => setTimeout(resolve, 100)); await p; - } + } } private _getState(): ServerState { @@ -238,14 +250,14 @@ export class OmniSharpServer { // --- start, stop, and connect - private _start(launchTarget: LaunchTarget): Promise { + private async _start(launchTarget: LaunchTarget): Promise { this._setState(ServerState.Starting); this._launchTarget = launchTarget; const solutionPath = launchTarget.target; const cwd = path.dirname(solutionPath); this._options = Options.Read(); - + let args = [ '-s', solutionPath, '--hostPID', process.pid.toString(), @@ -259,6 +271,22 @@ export class OmniSharpServer { args.push('--debug'); } + let launchPath: string; + if (this._options.path) { + try { + let extensionPath = utils.getExtensionPath(); + let manager = new OmnisharpManager(this._csharpChannel, this._csharpLogger, this._packageJSON, this._reporter); + let platformInfo = await PlatformInformation.GetCurrent(); + launchPath = await manager.GetOmnisharpPath(this._options.path, this._options.useMono, serverUrl, latestVersionFileServerPath, installPath, extensionPath, platformInfo); + } + catch (error) { + this._logger.appendLine('Error occured in loading omnisharp from omnisharp.path'); + this._logger.appendLine(`Could not start the server due to ${error.toString()}`); + this._logger.appendLine(); + return; + } + } + this._logger.appendLine(`Starting OmniSharp server at ${new Date().toLocaleString()}`); this._logger.increaseIndent(); this._logger.appendLine(`Target: ${solutionPath}`); @@ -267,9 +295,9 @@ export class OmniSharpServer { this._fireEvent(Events.BeforeServerStart, solutionPath); - return launchOmniSharp(cwd, args).then(value => { + return launchOmniSharp(cwd, args, launchPath).then(value => { if (value.usingMono) { - this._logger.appendLine(`OmniSharp server started wth Mono`); + this._logger.appendLine(`OmniSharp server started with Mono`); } else { this._logger.appendLine(`OmniSharp server started`); diff --git a/src/packages.ts b/src/packages.ts index 2e3592d35..a16a9b38d 100644 --- a/src/packages.ts +++ b/src/packages.ts @@ -24,6 +24,7 @@ export interface Package { architectures: string[]; binaries: string[]; tmpFile: tmp.SynchrounousResult; + platformId?: string; // Path to use to test if the package has already been installed installTestPath?: string; @@ -36,9 +37,9 @@ export interface Status { export class PackageError extends Error { // Do not put PII (personally identifiable information) in the 'message' field as it will be logged to telemetry - constructor(public message: string, - public pkg: Package = null, - public innerError: any = null) { + constructor(public message: string, + public pkg: Package = null, + public innerError: any = null) { super(message); } } @@ -74,14 +75,11 @@ export class PackageManager { resolve(this.allPackages); } else if (this.packageJSON.runtimeDependencies) { - this.allPackages = this.packageJSON.runtimeDependencies; + this.allPackages = JSON.parse(JSON.stringify(this.packageJSON.runtimeDependencies)); + //Copying the packages by value and not by reference so that there are no side effects // Convert relative binary paths to absolute - for (let pkg of this.allPackages) { - if (pkg.binaries) { - pkg.binaries = pkg.binaries.map(value => path.resolve(getBaseInstallPath(pkg), value)); - } - } + resolvePackageBinaries(this.allPackages); resolve(this.allPackages); } @@ -107,6 +105,37 @@ export class PackageManager { }); }); } + + public SetVersionPackagesForDownload(packages: Package[]) { + this.allPackages = packages; + resolvePackageBinaries(this.allPackages); + } + + public async GetLatestVersionFromFile(logger: Logger, status: Status, proxy: string, strictSSL: boolean, filePackage: Package): Promise { + try { + let latestVersion: string; + await maybeDownloadPackage(filePackage, logger, status, proxy, strictSSL); + if (filePackage.tmpFile) { + latestVersion = fs.readFileSync(filePackage.tmpFile.name, 'utf8'); + //Delete the temporary file created + filePackage.tmpFile.removeCallback(); + } + + return latestVersion; + } + catch (error) { + throw new Error(`Could not download the latest version file due to ${error.toString()}`); + } + } +} + +function resolvePackageBinaries(packages: Package[]) { + // Convert relative binary paths to absolute + for (let pkg of packages) { + if (pkg.binaries) { + pkg.binaries = pkg.binaries.map(value => path.resolve(getBaseInstallPath(pkg), value)); + } + } } function getBaseInstallPath(pkg: Package): string { @@ -139,7 +168,7 @@ function downloadPackage(pkg: Package, logger: Logger, status: Status, proxy: st status = status || getNoopStatus(); logger.append(`Downloading package '${pkg.description}' `); - + status.setMessage("$(cloud-download) Downloading packages"); status.setDetail(`Downloading package '${pkg.description}'...`); @@ -199,7 +228,7 @@ function downloadFile(urlString: string, pkg: Package, logger: Logger, status: S logger.appendLine(`failed (error code '${response.statusCode}')`); return reject(new PackageError(response.statusCode.toString(), pkg)); } - + // Downloading - hook up events let packageSize = parseInt(response.headers['content-length'], 10); let downloadedBytes = 0; @@ -338,7 +367,7 @@ function installPackage(pkg: Package, logger: Logger, status?: Status): Promise< }); } -function doesPackageTestPathExist(pkg: Package) : Promise { +function doesPackageTestPathExist(pkg: Package): Promise { const testPath = getPackageTestPath(pkg); if (testPath) { return util.fileExists(testPath); @@ -347,7 +376,7 @@ function doesPackageTestPathExist(pkg: Package) : Promise { } } -function getPackageTestPath(pkg: Package) : string { +function getPackageTestPath(pkg: Package): string { if (pkg.installTestPath) { return path.join(util.getExtensionPath(), pkg.installTestPath); } else { diff --git a/test/integrationTests/testAssets/singleCsproj/obj/Debug/netcoreapp2.0/singleCsproj.csproj.CoreCompileInputs.cache b/test/integrationTests/testAssets/singleCsproj/obj/Debug/netcoreapp2.0/singleCsproj.csproj.CoreCompileInputs.cache index 2c37f2978..0cb96c1c5 100644 --- a/test/integrationTests/testAssets/singleCsproj/obj/Debug/netcoreapp2.0/singleCsproj.csproj.CoreCompileInputs.cache +++ b/test/integrationTests/testAssets/singleCsproj/obj/Debug/netcoreapp2.0/singleCsproj.csproj.CoreCompileInputs.cache @@ -1 +1 @@ -a42255fc669f86623d73c2e9ad48ccb8310c65c0 +ffcdec535c6ed9f5449ad17c7f1a870da5e60671 diff --git a/test/unitTests/OmnisharpDownloader.test.ts b/test/unitTests/OmnisharpDownloader.test.ts new file mode 100644 index 000000000..af276f096 --- /dev/null +++ b/test/unitTests/OmnisharpDownloader.test.ts @@ -0,0 +1,179 @@ +/*--------------------------------------------------------------------------------------------- + * Copyright (c) Microsoft Corporation. All rights reserved. + * Licensed under the MIT License. See License.txt in the project root for license information. + *--------------------------------------------------------------------------------------------*/ + +import * as vscode from 'vscode'; +import * as path from 'path'; +import * as util from '../../src/common'; +import { should } from 'chai'; +import { Logger } from '../../src/logger'; +import { OmnisharpDownloader } from '../../src/omnisharp/OmnisharpDownloader'; +import { rimraf } from 'async-file'; +import { PlatformInformation } from '../../src/platform'; + +const tmp = require('tmp'); +const chai = require("chai"); +chai.use(require("chai-as-promised")); +let expect = chai.expect; + +suite("DownloadAndInstallExperimentalVersion : Gets the version packages, downloads and installs them", () => { + let tmpDir = null; + const version = "1.2.3"; + const downloader = GetTestOmnisharpDownloader(); + const serverUrl = "https://roslynomnisharp.blob.core.windows.net"; + const installPath = ".omnisharp/experimental/"; + + setup(() => { + tmpDir = tmp.dirSync(); + util.setExtensionPath(tmpDir.name); + }); + + test('Throws error if request is made for a version that doesnot exist on the server', () => { + expect(downloader.DownloadAndInstallOmnisharp("1.00000001.0000", serverUrl, installPath)).to.be.rejectedWith(Error); + }); + + test('Packages are downloaded from the specified server url and installed at the specified path', async () => { + /* Download a test package that conatins a install_check_1.2.3.txt file and check whether the + file appears at the expected path */ + await downloader.DownloadAndInstallOmnisharp(version, serverUrl, installPath); + let exists = await util.fileExists(path.resolve(tmpDir.name, installPath, version, `install_check_1.2.3.txt`)); + exists.should.equal(true); + }); + + teardown(async () => { + if (tmpDir) { + await rimraf(tmpDir.name); + } + + tmpDir = null; + }); +}); + +function GetTestOmnisharpDownloader() { + let channel = vscode.window.createOutputChannel('Experiment Channel'); + let logger = new Logger(text => channel.append(text)); + return new OmnisharpDownloader(channel, logger, GetTestPackageJSON(), new PlatformInformation("win32", "x86"), null); +} + +//Since we need only the runtime dependencies of packageJSON for the downloader create a testPackageJSON +//with just that +export function GetTestPackageJSON() { + let testpackageJSON = { + "runtimeDependencies": [ + { + "description": "OmniSharp for Windows (.NET 4.6 / x86)", + "url": "https://download.visualstudio.microsoft.com/download/pr/100505823/5804b7d3b5eeb7e4ae812a7cff03bd52/omnisharp-win-x86-1.28.0.zip", + "fallbackUrl": "https://omnisharpdownload.blob.core.windows.net/ext/omnisharp-win-x86-1.28.0.zip", + "installPath": ".omnisharp", + "platforms": [ + "win32" + ], + "architectures": [ + "x86" + ], + "installTestPath": "./.omnisharp/OmniSharp.exe", + "platformId": "win-x86" + }, + { + "description": "OmniSharp for Windows (.NET 4.6 / x64)", + "url": "https://download.visualstudio.microsoft.com/download/pr/100505821/c570a9e20dbf7172f79850babd058872/omnisharp-win-x64-1.28.0.zip", + "fallbackUrl": "https://omnisharpdownload.blob.core.windows.net/ext/omnisharp-win-x64-1.28.0.zip", + "installPath": ".omnisharp", + "platforms": [ + "win32" + ], + "architectures": [ + "x86_64" + ], + "installTestPath": "./.omnisharp/OmniSharp.exe", + "platformId": "win-x64" + }, + { + "description": "OmniSharp for OSX", + "url": "https://download.visualstudio.microsoft.com/download/pr/100505818/6b99c6a86da3221919158ca0f36a3e45/omnisharp-osx-1.28.0.zip", + "fallbackUrl": "https://omnisharpdownload.blob.core.windows.net/ext/omnisharp-osx-1.28.0.zip", + "installPath": ".omnisharp", + "platforms": [ + "darwin" + ], + "binaries": [ + "./mono.osx", + "./run" + ], + "installTestPath": "./.omnisharp/mono.osx", + "platformId": "osx" + }, + { + "description": "OmniSharp for Linux (x86)", + "url": "https://download.visualstudio.microsoft.com/download/pr/100505817/b710ec9c2bedc0cfdb57da82da166c47/omnisharp-linux-x86-1.28.0.zip", + "fallbackUrl": "https://omnisharpdownload.blob.core.windows.net/ext/omnisharp-linux-x86-1.28.0.zip", + "installPath": ".omnisharp", + "platforms": [ + "linux" + ], + "architectures": [ + "x86", + "i686" + ], + "binaries": [ + "./mono.linux-x86", + "./run" + ], + "installTestPath": "./.omnisharp/mono.linux-x86", + "platformId": "linux-x86" + }, + { + "description": "OmniSharp for Linux (x64)", + "url": "https://download.visualstudio.microsoft.com/download/pr/100505485/3f8a10409240decebb8a3189429f3fdf/omnisharp-linux-x64-1.28.0.zip", + "fallbackUrl": "https://omnisharpdownload.blob.core.windows.net/ext/omnisharp-linux-x64-1.28.0.zip", + "installPath": ".omnisharp", + "platforms": [ + "linux" + ], + "architectures": [ + "x86_64" + ], + "binaries": [ + "./mono.linux-x86_64", + "./run" + ], + "installTestPath": "./.omnisharp/mono.linux-x86_64", + "platformId": "linux-x64" + }, + { + "description": "OmniSharp for Test OS(architecture)", + "url": "https://download.visualstudio.microsoft.com/download/pr/100505485/3f8a10409240decebb8a3189429f3fdf/omnisharp-os-architecture-version.zip", + "fallbackUrl": "https://omnisharpdownload.blob.core.windows.net/ext/omnisharp-os-architecture-version.zip", + "installPath": ".omnisharp", + "platforms": [ + "platform1" + ], + "architectures": [ + "architecture" + ], + "binaries": [ + "./binary1", + "./binary2" + ], + "installTestPath": "./.omnisharp/binary", + "platformId": "os-architecture" + }, + { + "description": "Non omnisharp package without platformId", + "url": "https://download.visualstudio.microsoft.com/download/pr/100317420/a30d7e11bc435433d297adc824ee837f/coreclr-debug-win7-x64.zip", + "fallbackUrl": "https://vsdebugger.blob.core.windows.net/coreclr-debug-1-14-4/coreclr-debug-win7-x64.zip", + "installPath": ".debugger", + "platforms": [ + "win32" + ], + "architectures": [ + "x86_64" + ], + "installTestPath": "./.debugger/vsdbg-ui.exe" + } + ] + }; + + return testpackageJSON; +} diff --git a/test/unitTests/OmnisharpManager.test.ts b/test/unitTests/OmnisharpManager.test.ts new file mode 100644 index 000000000..494bb0878 --- /dev/null +++ b/test/unitTests/OmnisharpManager.test.ts @@ -0,0 +1,112 @@ +/*--------------------------------------------------------------------------------------------- + * Copyright (c) Microsoft Corporation. All rights reserved. + * Licensed under the MIT License. See License.txt in the project root for license information. + *--------------------------------------------------------------------------------------------*/ + +import * as path from 'path'; +import * as vscode from 'vscode'; +import * as util from '../../src/common'; +import { should } from "chai"; +import { PlatformInformation } from "../../src/platform"; +import { Logger } from '../../src/logger'; +import { rimraf } from 'async-file'; +import { GetTestPackageJSON } from './OmnisharpDownloader.test'; +import { OmnisharpManager } from '../../src/omnisharp/OmnisharpManager'; + +const chai = require("chai"); +chai.use(require("chai-as-promised")); +let expect = chai.expect; + +const tmp = require('tmp'); + +suite('GetExperimentalOmnisharpPath : Returns Omnisharp experiment path depending on the path and useMono option', () => { + const platformInfo = new PlatformInformation("win32", "x86"); + const serverUrl = "https://roslynomnisharp.blob.core.windows.net"; + const installPath = ".omnisharp/experimental"; + const versionFilepathInServer = "releases/testVersionInfo.txt"; + const useMono = false; + const manager = GetTestOmnisharpManager(); + let extensionPath: string; + let tmpDir: any; + let tmpFile: any; + + suiteSetup(() => should()); + + setup(() => { + tmpDir = tmp.dirSync(); + extensionPath = tmpDir.name; + util.setExtensionPath(tmpDir.name); + }); + + test('Throws error if the path is neither an absolute path nor a valid semver, nor the string "latest"', async () => { + expect(manager.GetOmnisharpPath("Some incorrect path", useMono, serverUrl, versionFilepathInServer, installPath, extensionPath, platformInfo)).to.be.rejectedWith(Error); + }); + + test('Throws error when the specified path is null', async () => { + expect(manager.GetOmnisharpPath(null, useMono, serverUrl, versionFilepathInServer, installPath, extensionPath, platformInfo)).to.be.rejectedWith(Error); + }); + + test('Throws error when the specified path is empty', async () => { + expect(manager.GetOmnisharpPath("", useMono, serverUrl, versionFilepathInServer, installPath, extensionPath, platformInfo)).to.be.rejectedWith(Error); + }); + + test('Throws error when the specified path is an invalid semver', async () => { + expect(manager.GetOmnisharpPath("a.b.c", useMono, serverUrl, versionFilepathInServer, installPath, extensionPath, platformInfo)).to.be.rejectedWith(Error); + }); + + test('Returns the same path if absolute path to an existing file is passed', async () => { + tmpFile = tmp.fileSync(); + let omnisharpPath = await manager.GetOmnisharpPath(tmpFile.name, useMono, serverUrl, versionFilepathInServer, installPath, extensionPath, platformInfo); + omnisharpPath.should.equal(tmpFile.name); + }); + + test('Installs the latest version and returns the launch path based on the version and platform', async () => { + let omnisharpPath = await manager.GetOmnisharpPath("latest", useMono, serverUrl, versionFilepathInServer, installPath, extensionPath, platformInfo); + omnisharpPath.should.equal(path.resolve(extensionPath, `.omnisharp/experimental/1.2.3/OmniSharp.exe`)); + }); + + test('Installs the test version and returns the launch path based on the version and platform', async () => { + let omnisharpPath = await manager.GetOmnisharpPath("1.2.3", useMono, serverUrl, versionFilepathInServer, installPath, extensionPath, platformInfo); + omnisharpPath.should.equal(path.resolve(extensionPath, `.omnisharp/experimental/1.2.3/OmniSharp.exe`)); + }); + + test('Downloads package from given url and installs them at the specified path', async () => { + let launchPath = await manager.GetOmnisharpPath("1.2.3", useMono, serverUrl, versionFilepathInServer, installPath, extensionPath, platformInfo); + let exists = await util.fileExists(path.resolve(extensionPath, `.omnisharp/experimental/1.2.3/install_check_1.2.3.txt`)); + exists.should.equal(true); + }); + + test('Downloads package and returns launch path based on platform - Not using mono on Linux ', async () => { + let launchPath = await manager.GetOmnisharpPath("1.2.3", useMono, serverUrl, versionFilepathInServer, installPath, extensionPath, new PlatformInformation("linux", "x64")); + launchPath.should.equal(path.resolve(extensionPath, '.omnisharp/experimental/1.2.3/run')); + }); + + test('Downloads package and returns launch path based on platform - Using mono on Linux ', async () => { + let launchPath = await manager.GetOmnisharpPath("1.2.3", true, serverUrl, versionFilepathInServer, installPath, extensionPath, new PlatformInformation("linux", "x64")); + launchPath.should.equal(path.resolve(extensionPath, '.omnisharp/experimental/1.2.3/omnisharp/OmniSharp.exe')); + }); + + test('Downloads package and returns launch path based on install path ', async () => { + let launchPath = await manager.GetOmnisharpPath("1.2.3", true, serverUrl, versionFilepathInServer, "installHere", extensionPath, platformInfo); + launchPath.should.equal(path.resolve(extensionPath, 'installHere/1.2.3/OmniSharp.exe')); + }); + + teardown(async () => { + if (tmpDir) { + await rimraf(tmpDir.name); + } + + if (tmpFile) { + tmpFile.removeCallback(); + } + + tmpFile = null; + tmpDir = null; + }); +}); + +function GetTestOmnisharpManager() { + let channel = vscode.window.createOutputChannel('Experiment Channel'); + let logger = new Logger(text => channel.append(text)); + return new OmnisharpManager(channel, logger, GetTestPackageJSON(), null); +} diff --git a/test/unitTests/OmnisharpPackageCreator.test.ts b/test/unitTests/OmnisharpPackageCreator.test.ts new file mode 100644 index 000000000..02cbdaee0 --- /dev/null +++ b/test/unitTests/OmnisharpPackageCreator.test.ts @@ -0,0 +1,209 @@ +/*--------------------------------------------------------------------------------------------- + * Copyright (c) Microsoft Corporation. All rights reserved. + * Licensed under the MIT License. See License.txt in the project root for license information. + *--------------------------------------------------------------------------------------------*/ + +import { assert, should, expect } from "chai"; +import { Package } from "../../src/packages"; +import { GetTestPackageJSON } from "./OmnisharpDownloader.test"; +import { SetBinaryAndGetPackage, GetPackagesFromVersion, GetVersionFilePackage } from "../../src/omnisharp/OmnisharpPackageCreator"; + +suite("GetOmnisharpPackage : Output package depends on the input package and other input parameters like serverUrl", () => { + + let serverUrl: string; + let version: string; + let installPath: string; + let inputPackages: any; + + suiteSetup(() => { + serverUrl = "http://serverUrl"; + version = "0.0.0"; + installPath = "testPath"; + let packageJSON = GetTestPackageJSON(); + inputPackages = (packageJSON.runtimeDependencies); + should(); + }); + + test('Throws exception if version is empty', () => { + let testPackage = inputPackages.find(element => (element.platformId && element.platformId == "os-architecture")); + let fn = function () { SetBinaryAndGetPackage(testPackage, serverUrl, "", installPath); }; + expect(fn).to.throw('Invalid version'); + }); + + test('Throws exception if version is null', () => { + let testPackage = inputPackages.find(element => (element.platformId && element.platformId == "os-architecture")); + let fn = function () { SetBinaryAndGetPackage(testPackage, serverUrl, null, installPath);}; + expect(fn).to.throw('Invalid version'); + }); + + test('Architectures, binaries and platforms do not change', () => { + let testPackage = inputPackages.find(element => (element.platformId && element.platformId == "os-architecture")); + let resultPackage = SetBinaryAndGetPackage(testPackage, serverUrl, version, installPath); + + resultPackage.architectures.should.equal(testPackage.architectures); + assert.equal(resultPackage.binaries, testPackage.binaries); + resultPackage.platforms.should.equal(testPackage.platforms); + }); + + test('Version information is appended to the description', () => { + let testPackage = inputPackages.find(element => (element.platformId && element.platformId == "os-architecture")); + let resultPackage = SetBinaryAndGetPackage(testPackage, serverUrl, "1.2.3", installPath); + + resultPackage.description.should.equal(`${testPackage.description}, Version = 1.2.3`); + }); + + test('Download url is calculated using server url and version', () => { + let testPackage = inputPackages.find(element => (element.platformId && element.platformId == "os-architecture")); + let resultPackage = SetBinaryAndGetPackage(testPackage, "http://someurl", "1.1.1", installPath); + resultPackage.url.should.equal("http://someurl/releases/1.1.1/omnisharp-os-architecture.zip"); + }); + + test('Install path is calculated using the specified path and version', () => { + let testPackage = inputPackages.find(element => (element.platformId && element.platformId == "os-architecture")); + let resultPackage = SetBinaryAndGetPackage(testPackage, serverUrl, "1.2.3", "experimentPath"); + resultPackage.installPath.should.equal("experimentPath/1.2.3"); + }); + + test('Install test path is calculated using specified path, version and ends with Omnisharp.exe - Windows(x86)', () => { + let testPackage = inputPackages.find(element => (element.platformId && element.platformId == "win-x86")); + let resultPackage = SetBinaryAndGetPackage(testPackage, serverUrl, "1.2.3", "experimentPath"); + resultPackage.installTestPath.should.equal("./experimentPath/1.2.3/OmniSharp.exe"); + }); + + test('Install test path is calculated using specified path, version and ends with Omnisharp.exe - Windows(x64)', () => { + let testPackage = inputPackages.find(element => (element.platformId && element.platformId == "win-x64")); + let resultPackage = SetBinaryAndGetPackage(testPackage, serverUrl, "1.2.3", "experimentPath"); + resultPackage.installTestPath.should.equal("./experimentPath/1.2.3/OmniSharp.exe"); + }); + + test('Install test path is calculated using specified path, version and ends with mono.osx - OSX', () => { + let testPackage = inputPackages.find(element => (element.platformId && element.platformId == "osx")); + let resultPackage = SetBinaryAndGetPackage(testPackage, serverUrl, "1.2.3", "experimentPath"); + resultPackage.installTestPath.should.equal("./experimentPath/1.2.3/mono.osx"); + }); + + test('Install test path is calculated using specified path, version and ends with mono.linux-x86 - Linux(x86)', () => { + let testPackage = inputPackages.find(element => (element.platformId && element.platformId == "linux-x86")); + let resultPackage = SetBinaryAndGetPackage(testPackage, serverUrl, "1.2.3", "experimentPath"); + resultPackage.installTestPath.should.equal("./experimentPath/1.2.3/mono.linux-x86"); + }); + + test('Install test path is calculated using specified path, version and ends with mono.linux-x86_64 - Linux(x64)', () => { + let testPackage = inputPackages.find(element => (element.platformId && element.platformId == "linux-x64")); + let resultPackage = SetBinaryAndGetPackage(testPackage, serverUrl, "1.2.3", "experimentPath"); + resultPackage.installTestPath.should.equal("./experimentPath/1.2.3/mono.linux-x86_64"); + }); +}); + +suite('GetPackagesFromVersion : Gets the experimental omnisharp packages from a set of input packages', () => { + + const serverUrl = "http://serverUrl"; + const installPath = "testPath"; + let inputPackages : any; + + suiteSetup(() => { + inputPackages = (GetTestPackageJSON().runtimeDependencies); + should(); + }); + + test('Throws exception if the version is null', () => { + let version: string = null; + let fn = function () { GetPackagesFromVersion(version, inputPackages, serverUrl, installPath); }; + expect(fn).to.throw('Invalid version'); + }); + + test('Throws exception if the version is empty', () => { + let version = ""; + let fn = function () { GetPackagesFromVersion(version, inputPackages, serverUrl, installPath); }; + expect(fn).to.throw('Invalid version'); + }); + + test('Returns experiment packages with install test path depending on install path and version', () => { + let inputPackages = [ + { + "description": "OmniSharp for Windows (.NET 4.6 / x64)", + "url": "https://download.visualstudio.microsoft.com/download/pr/100505821/c570a9e20dbf7172f79850babd058872/omnisharp-win-x64-1.28.0.zip", + "fallbackUrl": "https://omnisharpdownload.blob.core.windows.net/ext/omnisharp-win-x64-1.28.0.zip", + "installPath": ".omnisharp", + "platforms": [ + "win32" + ], + "architectures": [ + "x86_64" + ], + "installTestPath": "./.omnisharp/OmniSharp.exe", + "platformId": "win-x64" + }, + { + "description": "OmniSharp for OSX", + "url": "https://download.visualstudio.microsoft.com/download/pr/100505818/6b99c6a86da3221919158ca0f36a3e45/omnisharp-osx-1.28.0.zip", + "fallbackUrl": "https://omnisharpdownload.blob.core.windows.net/ext/omnisharp-osx-1.28.0.zip", + "installPath": ".omnisharp", + "platforms": [ + "darwin" + ], + "binaries": [ + "./mono.osx", + "./run" + ], + "installTestPath": "./.omnisharp/mono.osx", + "platformId": "osx" + }, + ]; + + let outPackages = GetPackagesFromVersion("1.1.1", inputPackages, serverUrl, "experimentPath"); + outPackages.length.should.equal(2); + outPackages[0].installTestPath.should.equal("./experimentPath/1.1.1/OmniSharp.exe"); + outPackages[1].installTestPath.should.equal("./experimentPath/1.1.1/mono.osx"); + }); + + test('Returns only omnisharp packages with experimentalIds', () => { + let version = "0.0.0"; + let inputPackages = [ + { + "description": "OmniSharp for Windows (.NET 4.6 / x64)", + "url": "https://download.visualstudio.microsoft.com/download/pr/100505821/c570a9e20dbf7172f79850babd058872/omnisharp-win-x64-1.28.0.zip", + "fallbackUrl": "https://omnisharpdownload.blob.core.windows.net/ext/omnisharp-win-x64-1.28.0.zip", + "installPath": ".omnisharp", + "platforms": [ + "win32" + ], + "architectures": [ + "x86_64" + ], + "installTestPath": "./.omnisharp/OmniSharp.exe", + "platformId": "win-x64" + }, + { + "description": "Some other package - no experimental id", + "url": "https://download.visualstudio.microsoft.com/download/pr/100505818/6b99c6a86da3221919158ca0f36a3e45/omnisharp-osx-1.28.0.zip", + "fallbackUrl": "https://omnisharpdownload.blob.core.windows.net/ext/omnisharp-osx-1.28.0.zip", + "installPath": ".omnisharp", + "platforms": [ + "darwin" + ], + "binaries": [ + "./mono.osx", + "./run" + ], + "installTestPath": "./.omnisharp/mono.osx", + }, + ]; + + let outPackages = GetPackagesFromVersion(version, inputPackages, serverUrl, installPath); + outPackages.length.should.equal(1); + outPackages[0].platformId.should.equal("win-x64"); + }); +}); + +suite('GetVersionFilePackage : Gives the package for the latest file download', () => { + test('Contains the expected description', () => { + let testPackage = GetVersionFilePackage("someUrl", "somePath"); + expect(testPackage.description).to.equal('Latest version information file'); + }); + + test('Contains the url based on serverUrl and the pathInServer', () => { + let testPackage = GetVersionFilePackage("someUrl", "somePath"); + expect(testPackage.url).to.equal('someUrl/somePath'); + }); +}); \ No newline at end of file