From 56f7e93590406bfaf886fa4a5654295a42bf1de2 Mon Sep 17 00:00:00 2001 From: Sandeep Somavarapu Date: Wed, 22 May 2019 03:28:21 +0200 Subject: [PATCH] add remote configuration tests --- .../configuration/browser/configuration.ts | 20 +- .../browser/configurationService.ts | 6 +- .../configurationService.test.ts | 192 +++++++++++++++++- .../workbench/test/workbenchTestServices.ts | 30 +++ 4 files changed, 242 insertions(+), 6 deletions(-) diff --git a/src/vs/workbench/services/configuration/browser/configuration.ts b/src/vs/workbench/services/configuration/browser/configuration.ts index b091f4451ff..c93d9d6e675 100644 --- a/src/vs/workbench/services/configuration/browser/configuration.ts +++ b/src/vs/workbench/services/configuration/browser/configuration.ts @@ -29,6 +29,7 @@ export class RemoteUserConfiguration extends Disposable { private readonly _cachedConfiguration: CachedUserConfiguration; private readonly _configurationFileService: IConfigurationFileService; private _userConfiguration: UserConfiguration | CachedUserConfiguration; + private _userConfigurationInitializationPromise: Promise | null = null; private readonly _onDidChangeConfiguration: Emitter = this._register(new Emitter()); public readonly onDidChangeConfiguration: Event = this._onDidChangeConfiguration.event; @@ -46,7 +47,8 @@ export class RemoteUserConfiguration extends Disposable { if (environment) { const userConfiguration = this._register(new UserConfiguration(environment.settingsPath, MACHINE_SCOPES, this._configurationFileService)); this._register(userConfiguration.onDidChangeConfiguration(configurationModel => this.onDidUserConfigurationChange(configurationModel))); - const configurationModel = await userConfiguration.initialize(); + this._userConfigurationInitializationPromise = userConfiguration.initialize(); + const configurationModel = await this._userConfigurationInitializationPromise; this._userConfiguration.dispose(); this._userConfiguration = userConfiguration; this.onDidUserConfigurationChange(configurationModel); @@ -54,8 +56,20 @@ export class RemoteUserConfiguration extends Disposable { }); } - initialize(): Promise { - return this._userConfiguration.initialize(); + async initialize(): Promise { + if (this._userConfiguration instanceof UserConfiguration) { + return this._userConfiguration.initialize(); + } + + // Initialize cached configuration + let configurationModel = await this._userConfiguration.initialize(); + if (this._userConfigurationInitializationPromise) { + // Use user configuration + configurationModel = await this._userConfigurationInitializationPromise; + this._userConfigurationInitializationPromise = null; + } + + return configurationModel; } reload(): Promise { diff --git a/src/vs/workbench/services/configuration/browser/configurationService.ts b/src/vs/workbench/services/configuration/browser/configurationService.ts index a8b346d524d..a0f96ef21a2 100644 --- a/src/vs/workbench/services/configuration/browser/configurationService.ts +++ b/src/vs/workbench/services/configuration/browser/configurationService.ts @@ -543,8 +543,10 @@ export class WorkspaceService extends Disposable implements IConfigurationServic } private onRemoteUserConfigurationChanged(userConfiguration: ConfigurationModel): void { - const keys = this._configuration.compareAndUpdateRemoteUserConfiguration(userConfiguration); - this.triggerConfigurationChange(keys, ConfigurationTarget.USER); + if (this._configuration) { + const keys = this._configuration.compareAndUpdateRemoteUserConfiguration(userConfiguration); + this.triggerConfigurationChange(keys, ConfigurationTarget.USER); + } } private onWorkspaceConfigurationChanged(): Promise { diff --git a/src/vs/workbench/services/configuration/test/electron-browser/configurationService.test.ts b/src/vs/workbench/services/configuration/test/electron-browser/configurationService.test.ts index e35f5b0de41..758148b8541 100644 --- a/src/vs/workbench/services/configuration/test/electron-browser/configurationService.test.ts +++ b/src/vs/workbench/services/configuration/test/electron-browser/configurationService.test.ts @@ -22,7 +22,7 @@ import { ConfigurationEditingErrorCode } from 'vs/workbench/services/configurati import { IFileService } from 'vs/platform/files/common/files'; import { IWorkspaceContextService, WorkbenchState, IWorkspaceFoldersChangeEvent } from 'vs/platform/workspace/common/workspace'; import { ConfigurationTarget, IConfigurationService, IConfigurationChangeEvent } from 'vs/platform/configuration/common/configuration'; -import { workbenchInstantiationService, TestTextFileService } from 'vs/workbench/test/workbenchTestServices'; +import { workbenchInstantiationService, TestTextFileService, RemoteFileSystemProvider } from 'vs/workbench/test/workbenchTestServices'; import { TestInstantiationService } from 'vs/platform/instantiation/test/common/instantiationServiceMock'; import { ITextFileService } from 'vs/workbench/services/textfile/common/textfiles'; import { ITextModelService } from 'vs/editor/common/services/resolverService'; @@ -42,6 +42,8 @@ import { NullLogService } from 'vs/platform/log/common/log'; import { DiskFileSystemProvider } from 'vs/workbench/services/files/node/diskFileSystemProvider'; import { ConfigurationCache } from 'vs/workbench/services/configuration/node/configurationCache'; import { ConfigurationFileService } from 'vs/workbench/services/configuration/node/configurationFileService'; +import { IRemoteAgentEnvironment } from 'vs/platform/remote/common/remoteAgentEnvironment'; +import { IConfigurationCache } from 'vs/workbench/services/configuration/common/configuration'; class SettingsTestEnvironmentService extends EnvironmentService { @@ -1408,6 +1410,194 @@ suite('WorkspaceConfigurationService-Multiroot', () => { assert.equal(actual.workspace, undefined); }); }); + + test('configuration of newly added folder is available on configuration change event', async () => { + const workspaceService = testObject; + const uri = workspaceService.getWorkspace().folders[1].uri; + await workspaceService.removeFolders([uri]); + fs.writeFileSync(path.join(uri.fsPath, '.vscode', 'settings.json'), '{ "configurationService.workspace.testResourceSetting": "workspaceFolderValue" }'); + + return new Promise((c, e) => { + testObject.onDidChangeConfiguration(() => { + try { + assert.equal(testObject.getValue('configurationService.workspace.testResourceSetting', { resource: uri }), 'workspaceFolderValue'); + c(); + } catch (error) { + e(error); + } + }); + workspaceService.addFolders([{ uri }]); + }); + }); +}); + +suite('WorkspaceConfigurationService - Remote Folder', () => { + + let workspaceName = `testWorkspace${uuid.generateUuid()}`, parentResource: string, workspaceDir: string, testObject: WorkspaceService, globalSettingsFile: string, remoteSettingsFile: string, instantiationService: TestInstantiationService, resolveRemoteEnvironment: () => void; + const remoteAuthority = 'configuraiton-tests'; + const configurationRegistry = Registry.as(ConfigurationExtensions.Configuration); + const diskFileSystemProvider = new DiskFileSystemProvider(new NullLogService()); + + suiteSetup(() => { + configurationRegistry.registerConfiguration({ + 'id': '_test', + 'type': 'object', + 'properties': { + 'configurationService.folder.applicationSetting': { + 'type': 'string', + 'default': 'isSet', + scope: ConfigurationScope.APPLICATION + }, + 'configurationService.folder.machineSetting': { + 'type': 'string', + 'default': 'isSet', + scope: ConfigurationScope.MACHINE + }, + 'configurationService.folder.testSetting': { + 'type': 'string', + 'default': 'isSet', + scope: ConfigurationScope.RESOURCE + } + } + }); + }); + + setup(() => { + return setUpFolderWorkspace(workspaceName) + .then(({ parentDir, folderDir }) => { + + parentResource = parentDir; + workspaceDir = folderDir; + globalSettingsFile = path.join(parentDir, 'settings.json'); + remoteSettingsFile = path.join(parentDir, 'remote-settings.json'); + + instantiationService = workbenchInstantiationService(); + const environmentService = new SettingsTestEnvironmentService(parseArgs(process.argv), process.execPath, globalSettingsFile); + const remoteEnvironmentPromise = new Promise>(c => resolveRemoteEnvironment = () => c({ settingsPath: URI.file(remoteSettingsFile).with({ scheme: Schemas.vscodeRemote, authority: remoteAuthority }) })); + const remoteAgentService = instantiationService.stub(IRemoteAgentService, >{ getEnvironment: () => remoteEnvironmentPromise }); + const fileService = new FileService(new NullLogService()); + fileService.registerProvider(Schemas.file, diskFileSystemProvider); + const configurationFileService = new ConfigurationFileService(); + configurationFileService.fileService = fileService; + const configurationCache: IConfigurationCache = { read: () => Promise.resolve(''), write: () => Promise.resolve(), remove: () => Promise.resolve() }; + testObject = new WorkspaceService({ userSettingsResource: URI.file(environmentService.appSettingsPath), configurationCache, remoteAuthority }, configurationFileService, remoteAgentService); + instantiationService.stub(IWorkspaceContextService, testObject); + instantiationService.stub(IConfigurationService, testObject); + instantiationService.stub(IEnvironmentService, environmentService); + instantiationService.stub(IFileService, fileService); + }); + }); + + async function initialize(): Promise { + await testObject.initialize(convertToWorkspacePayload(URI.file(workspaceDir))); + instantiationService.stub(ITextFileService, instantiationService.createInstance(TestTextFileService)); + instantiationService.stub(ITextModelService, instantiationService.createInstance(TextModelResolverService)); + testObject.acquireInstantiationService(instantiationService); + } + + function registerRemtoeFileSystemProvider(): void { + instantiationService.get(IFileService).registerProvider(Schemas.vscodeRemote, new RemoteFileSystemProvider(diskFileSystemProvider, remoteAuthority)); + } + + teardown(() => { + if (testObject) { + (testObject).dispose(); + } + if (parentResource) { + return pfs.rimraf(parentResource, pfs.RimRafMode.MOVE); + } + return undefined; + }); + + test('remote settings override globals', async () => { + fs.writeFileSync(remoteSettingsFile, '{ "configurationService.folder.machineSetting": "remoteValue" }'); + registerRemtoeFileSystemProvider(); + resolveRemoteEnvironment(); + await initialize(); + assert.equal(testObject.getValue('configurationService.folder.machineSetting'), 'remoteValue'); + }); + + test('remote settings override globals after remote environment is resolved', async () => { + fs.writeFileSync(remoteSettingsFile, '{ "configurationService.folder.machineSetting": "remoteValue" }'); + registerRemtoeFileSystemProvider(); + await initialize(); + const promise = new Promise((c, e) => { + testObject.onDidChangeConfiguration(event => { + try { + assert.equal(event.source, ConfigurationTarget.USER); + assert.deepEqual(event.affectedKeys, ['configurationService.folder.machineSetting']); + assert.equal(testObject.getValue('configurationService.folder.machineSetting'), 'remoteValue'); + c(); + } catch (error) { + e(error); + } + }); + }); + resolveRemoteEnvironment(); + return promise; + }); + + test('remote settings override globals after remote provider is registered', async () => { + fs.writeFileSync(remoteSettingsFile, '{ "configurationService.folder.machineSetting": "remoteValue" }'); + resolveRemoteEnvironment(); + await initialize(); + const promise = new Promise((c, e) => { + testObject.onDidChangeConfiguration(event => { + try { + assert.equal(event.source, ConfigurationTarget.USER); + assert.deepEqual(event.affectedKeys, ['configurationService.folder.machineSetting']); + assert.equal(testObject.getValue('configurationService.folder.machineSetting'), 'remoteValue'); + c(); + } catch (error) { + e(error); + } + }); + }); + registerRemtoeFileSystemProvider(); + return promise; + }); + + test('remote settings override globals after remote provider is registered and remote environment is resolved', async () => { + fs.writeFileSync(remoteSettingsFile, '{ "configurationService.folder.machineSetting": "remoteValue" }'); + await initialize(); + const promise = new Promise((c, e) => { + testObject.onDidChangeConfiguration(event => { + try { + assert.equal(event.source, ConfigurationTarget.USER); + assert.deepEqual(event.affectedKeys, ['configurationService.folder.machineSetting']); + assert.equal(testObject.getValue('configurationService.folder.machineSetting'), 'remoteValue'); + c(); + } catch (error) { + e(error); + } + }); + }); + resolveRemoteEnvironment(); + registerRemtoeFileSystemProvider(); + return promise; + }); + + test('update remote settings', async () => { + registerRemtoeFileSystemProvider(); + resolveRemoteEnvironment(); + await initialize(); + assert.equal(testObject.getValue('configurationService.folder.machineSetting'), 'isSet'); + const promise = new Promise((c, e) => { + testObject.onDidChangeConfiguration(event => { + try { + assert.equal(event.source, ConfigurationTarget.USER); + assert.deepEqual(event.affectedKeys, ['configurationService.folder.machineSetting']); + assert.equal(testObject.getValue('configurationService.folder.machineSetting'), 'remoteValue'); + c(); + } catch (error) { + e(error); + } + }); + }); + fs.writeFileSync(remoteSettingsFile, '{ "configurationService.folder.machineSetting": "remoteValue" }'); + return promise; + }); + }); function getWorkspaceId(configPath: URI): string { diff --git a/src/vs/workbench/test/workbenchTestServices.ts b/src/vs/workbench/test/workbenchTestServices.ts index b917b7ac10a..99e06ec62c2 100644 --- a/src/vs/workbench/test/workbenchTestServices.ts +++ b/src/vs/workbench/test/workbenchTestServices.ts @@ -83,6 +83,7 @@ import { IWorkbenchEnvironmentService } from 'vs/workbench/services/environment/ import { WorkbenchEnvironmentService } from 'vs/workbench/services/environment/node/environmentService'; import { VSBuffer, VSBufferReadable } from 'vs/base/common/buffer'; import { BrowserTextFileService } from 'vs/workbench/services/textfile/browser/textFileService'; +import { Schemas } from 'vs/base/common/network'; export function createFileInput(instantiationService: IInstantiationService, resource: URI): FileEditorInput { return instantiationService.createInstance(FileEditorInput, resource, undefined, undefined); @@ -1597,4 +1598,33 @@ export class NullFileSystemProvider implements IFileSystemProvider { close?(fd: number): Promise { return Promise.resolve(undefined!); } read?(fd: number, pos: number, data: Uint8Array, offset: number, length: number): Promise { return Promise.resolve(undefined!); } write?(fd: number, pos: number, data: Uint8Array, offset: number, length: number): Promise { return Promise.resolve(undefined!); } +} + +export class RemoteFileSystemProvider implements IFileSystemProvider { + + constructor(private readonly diskFileSystemProvider: IFileSystemProvider, private readonly remoteAuthority: string) { } + + readonly capabilities: FileSystemProviderCapabilities = this.diskFileSystemProvider.capabilities; + readonly onDidChangeCapabilities: Event = this.diskFileSystemProvider.onDidChangeCapabilities; + + readonly onDidChangeFile: Event = Event.map(this.diskFileSystemProvider.onDidChangeFile, changes => changes.map(c => { c.resource = c.resource.with({ scheme: Schemas.vscodeRemote, authority: this.remoteAuthority }); return c; })); + watch(resource: URI, opts: IWatchOptions): IDisposable { return this.diskFileSystemProvider.watch(this.toFileResource(resource), opts); } + + stat(resource: URI): Promise { return this.diskFileSystemProvider.stat(this.toFileResource(resource)); } + mkdir(resource: URI): Promise { return this.diskFileSystemProvider.mkdir(this.toFileResource(resource)); } + readdir(resource: URI): Promise<[string, FileType][]> { return this.diskFileSystemProvider.readdir(this.toFileResource(resource)); } + delete(resource: URI, opts: FileDeleteOptions): Promise { return this.diskFileSystemProvider.delete(this.toFileResource(resource), opts); } + + rename(from: URI, to: URI, opts: FileOverwriteOptions): Promise { return this.diskFileSystemProvider.rename(this.toFileResource(from), this.toFileResource(to), opts); } + copy(from: URI, to: URI, opts: FileOverwriteOptions): Promise { return this.diskFileSystemProvider.copy!(this.toFileResource(from), this.toFileResource(to), opts); } + + readFile(resource: URI): Promise { return this.diskFileSystemProvider.readFile!(this.toFileResource(resource)); } + writeFile(resource: URI, content: Uint8Array, opts: FileWriteOptions): Promise { return this.diskFileSystemProvider.writeFile!(this.toFileResource(resource), content, opts); } + + open(resource: URI, opts: FileOpenOptions): Promise { return this.diskFileSystemProvider.open!(this.toFileResource(resource), opts); } + close(fd: number): Promise { return this.diskFileSystemProvider.close!(fd); } + read(fd: number, pos: number, data: Uint8Array, offset: number, length: number): Promise { return this.diskFileSystemProvider.read!(fd, pos, data, offset, length); } + write(fd: number, pos: number, data: Uint8Array, offset: number, length: number): Promise { return this.diskFileSystemProvider.write!(fd, pos, data, offset, length); } + + private toFileResource(resource: URI): URI { return resource.with({ scheme: Schemas.file, authority: '' }); } } \ No newline at end of file