add tests for the document symbol provider

This commit is contained in:
Johannes Rieken
2015-11-25 12:02:15 +01:00
parent b370792f68
commit bd93ccb864
6 changed files with 242 additions and 189 deletions

View File

@@ -8,6 +8,7 @@ import URI from 'vs/base/common/uri';
import Event, {Emitter} from 'vs/base/common/event';
import Severity from 'vs/base/common/severity';
import {TPromise} from 'vs/base/common/winjs.base';
import {onUnexpectedError} from 'vs/base/common/errors';
import {sequence} from 'vs/base/common/async';
import {Range as EditorRange} from 'vs/editor/common/core/range';
import {IDisposable} from 'vs/base/common/lifecycle';
@@ -113,8 +114,8 @@ export class ExtHostLanguageFeatures {
registerDocumentSymbolProvider(selector: vscode.DocumentSelector, provider: vscode.DocumentSymbolProvider): vscode.Disposable {
const handle = ExtHostLanguageFeatures._handlePool++;
this._proxy.$registerOutlineSupport(handle, selector);
this._adapter[handle] = new OutlineSupportAdapter(this._documents, provider);
this._proxy.$registerOutlineSupport(handle, selector);
return this._createDisposable(handle);
}
@@ -131,17 +132,17 @@ export class ExtHostLanguageFeatures {
export class MainThreadLanguageFeatures {
private _proxy: ExtHostLanguageFeatures;
private _disposables: { [handle: number]: IDisposable; } = Object.create(null);
private _registrations: { [handle: number]: IDisposable; } = Object.create(null);
constructor( @IThreadService threadService: IThreadService) {
this._proxy = threadService.getRemotable(ExtHostLanguageFeatures);
}
$unregister(handle: number): TPromise<any> {
let d = this._disposables[handle];
if (d) {
d.dispose();
delete this._disposables[handle];
let registration = this._registrations[handle];
if (registration) {
registration.dispose();
delete this._registrations[handle];
}
return undefined;
}
@@ -154,7 +155,7 @@ export class MainThreadLanguageFeatures {
return this._proxy.$getOutline(handle, resource);
}
});
this._disposables[handle] = disposable;
this._registrations[handle] = disposable;
return undefined;
}
}

View File

@@ -527,7 +527,10 @@ export class GotoSymbolHandler extends QuickOpenHandler {
return TPromise.as(this.outlineToModelCache[modelId]);
}
return getOutlineEntries(<IModel>model).then(outline => {
return getOutlineEntries({
uri: (<IModel> model).getAssociatedResource(),
language: (<IModel>model).getModeId()
}).then(outline => {
let model = new OutlineModel(outline, this.toQuickOpenEntries(outline));

View File

@@ -0,0 +1,142 @@
/*---------------------------------------------------------------------------------------------
* Copyright (c) Microsoft Corporation. All rights reserved.
* Licensed under the MIT License. See License.txt in the project root for license information.
*--------------------------------------------------------------------------------------------*/
'use strict';
import * as assert from 'assert';
import {setUnexpectedErrorHandler, errorHandler} from 'vs/base/common/errors';
import {create} from 'vs/base/common/types';
import URI from 'vs/base/common/uri';
import {TPromise} from 'vs/base/common/winjs.base';
import {PluginHostDocument} from 'vs/workbench/api/common/pluginHostDocuments';
import * as phTypes from 'vs/workbench/api/common/pluginHostTypes';
import {Range as CodeEditorRange} from 'vs/editor/common/core/range';
import * as EditorCommon from 'vs/editor/common/editorCommon';
import threadService from './testThreadService'
import {ExtHostLanguageFeatures, MainThreadLanguageFeatures} from 'vs/workbench/api/common/extHostLanguageFeatures';
import {PluginHostCommands, MainThreadCommands} from 'vs/workbench/api/common/pluginHostCommands';
import {PluginHostModelService} from 'vs/workbench/api/common/pluginHostDocuments';
import {SyncDescriptor0} from 'vs/platform/instantiation/common/descriptors';
import {OutlineRegistry, getOutlineEntries} from 'vs/editor/contrib/quickOpen/common/quickOpen';
import {LanguageSelector, ModelLike} from 'vs/editor/common/modes/languageSelector';
let model: ModelLike;
let extHost: ExtHostLanguageFeatures;
let mainThread: MainThreadLanguageFeatures;
let disposables: vscode.Disposable[] = [];
let originalErrorHandler: (e: any) => any;
suite('ExtHostLanguageFeatures', function() {
suiteSetup(() => {
originalErrorHandler = errorHandler.getUnexpectedErrorHandler();
setUnexpectedErrorHandler(() => { });
model = {
language: 'far',
uri: URI.parse('far://testing/file.a')
};
threadService.getRemotable(PluginHostModelService)._acceptModelAdd({
isDirty: false,
versionId: 1,
modeId: 'far',
url: model.uri,
value: {
EOL: '\n',
lines: [
'This is the first line',
'This is the second line',
'This is the third line',
],
BOM: '',
length: -1
},
})
threadService.getRemotable(PluginHostCommands);
threadService.getRemotable(MainThreadCommands);
mainThread = threadService.getRemotable(MainThreadLanguageFeatures);
extHost = threadService.getRemotable(ExtHostLanguageFeatures);
});
suiteTeardown(() => {
setUnexpectedErrorHandler(originalErrorHandler);
});
teardown(function(done) {
while (disposables.length) {
disposables.pop().dispose();
}
threadService.sync()
.then(() => done(), err => done(err));
});
test('DocumentSymbols, register/deregister', function(done) {
assert.equal(OutlineRegistry.all(model).length, 0);
let d1 = extHost.registerDocumentSymbolProvider('far', <vscode.DocumentSymbolProvider>{
provideDocumentSymbols() {
return [];
}
});
threadService.sync().then(() => {
assert.equal(OutlineRegistry.all(model).length, 1);
d1.dispose();
threadService.sync().then(() => {
done();
});
});
});
test('DocumentSymbols, evil provider', function(done) {
disposables.push(extHost.registerDocumentSymbolProvider('far', <vscode.DocumentSymbolProvider>{
provideDocumentSymbols(): any {
throw new Error('evil document symbol provider');
}
}));
disposables.push(extHost.registerDocumentSymbolProvider('far', <vscode.DocumentSymbolProvider>{
provideDocumentSymbols(): any {
return [new phTypes.SymbolInformation('test', phTypes.SymbolKind.Field, new phTypes.Range(0, 0, 0, 0))];
}
}));
threadService.sync().then(() => {
getOutlineEntries(model).then(value => {
assert.equal(value.entries.length, 1);
done();
}, err => {
done(err);
});
});
});
test('DocumentSymbols, data conversion', function(done) {
disposables.push(extHost.registerDocumentSymbolProvider('far', <vscode.DocumentSymbolProvider>{
provideDocumentSymbols(): any {
return [new phTypes.SymbolInformation('test', phTypes.SymbolKind.Field, new phTypes.Range(0, 0, 0, 0))];
}
}));
threadService.sync().then(() => {
getOutlineEntries(model).then(value => {
assert.equal(value.entries.length, 1);
let entry = value.entries[0];
assert.equal(entry.label, 'test');
assert.deepEqual(entry.range, { startLineNumber: 1, startColumn: 1, endLineNumber: 1, endColumn: 1 });
done();
}, err => {
done(err);
});
});
});
});

View File

@@ -1,177 +0,0 @@
/*---------------------------------------------------------------------------------------------
* Copyright (c) Microsoft Corporation. All rights reserved.
* Licensed under the MIT License. See License.txt in the project root for license information.
*--------------------------------------------------------------------------------------------*/
'use strict';
import * as assert from 'assert';
import {create} from 'vs/base/common/types';
import URI from 'vs/base/common/uri';
import {TPromise} from 'vs/base/common/winjs.base';
import {PluginHostDocument} from 'vs/workbench/api/common/pluginHostDocuments';
import * as phTypes from 'vs/workbench/api/common/pluginHostTypes';
import {Range as CodeEditorRange} from 'vs/editor/common/core/range';
import * as EditorCommon from 'vs/editor/common/editorCommon';
import {NullThreadService} from 'vs/platform/test/common/nullThreadService'
import * as LF from 'vs/workbench/api/common/languageFeatures';
import {PluginHostCommands, MainThreadCommands} from 'vs/workbench/api/common/pluginHostCommands';
import {PluginHostModelService} from 'vs/workbench/api/common/pluginHostDocuments';
import {SyncDescriptor0} from 'vs/platform/instantiation/common/descriptors';
import {OutlineRegistry} from 'vs/editor/contrib/quickOpen/common/quickOpen';
import {LanguageSelector, ModelLike} from 'vs/editor/common/modes/languageSelector';
class ThreadService extends NullThreadService {
protected _registerAndInstantiateMainProcessActor<T>(id: string, descriptor: SyncDescriptor0<T>): T {
let instance: any;
return this._getOrCreateProxyInstance({
callOnRemote: (proxyId: string, path: string, args: any[]): TPromise<any> => {
if (!instance) {
instance = create(descriptor.ctor, this);
}
try {
let result = (<Function>instance[path]).apply(instance, args);
return TPromise.is(result) ? result : TPromise.as(result);
} catch (err) {
return TPromise.wrapError(err);
}
}
}, id, descriptor)
}
protected _registerAndInstantiatePluginHostActor<T>(id: string, descriptor: SyncDescriptor0<T>): T {
return this._getOrCreateLocalInstance(id, descriptor);
}
}
let threadService: ThreadService;
let model: ModelLike = { language: 'far', uri: URI.parse('far://testing/file.a') };
let extHost: LF.ExtensionHostDocumentSymbols;
let mainHost: LF.MainThreadDocumentSymbols;
suite('ExtHostLanguageFeatures', function() {
suiteSetup(() => {
threadService = new ThreadService();
let documents = threadService.getRemotable(PluginHostModelService);
documents._acceptModelAdd({
isDirty: false,
versionId: 1,
modeId: 'far',
url: model.uri,
value: {
EOL: '\n',
lines: [
'This is the first line',
'This is the second line',
'This is the third line',
],
BOM: '',
length: -1
},
})
threadService.getRemotable(PluginHostCommands);
threadService.getRemotable(MainThreadCommands);
threadService.getRemotable(LF.MainThreadDocumentSymbols);
extHost = new LF.ExtensionHostDocumentSymbols(threadService);
mainHost = threadService.getRemotable(LF.MainThreadDocumentSymbols);
});
test('DocumentSymbols, register/deregister', function() {
// register
assert.equal(OutlineRegistry.all(model).length, 0);
let disposable = extHost.register('far', {
provideDocumentSymbols() {
return [];
}
});
assert.equal(OutlineRegistry.all(model).length, 1);
// deregister
disposable.dispose();
assert.equal(OutlineRegistry.all(model).length, 0);
// all extension host provider appear as one
disposable = extHost.register('far', {
provideDocumentSymbols() {
return [];
}
});
let disposable2 = extHost.register('far', {
provideDocumentSymbols() {
return [];
}
});
assert.equal(OutlineRegistry.all(model).length, 1);
disposable.dispose();
assert.equal(OutlineRegistry.all(model).length, 1);
disposable2.dispose();
assert.equal(OutlineRegistry.all(model).length, 0);
});
test('DocumentSymbols, evil provider', function(done) {
let disposable = extHost.register('far', {
provideDocumentSymbols():any {
throw new Error('ddd');
}
});
let disposable2 = extHost.register('far', {
provideDocumentSymbols():any {
return [
new phTypes.SymbolInformation('boo', phTypes.SymbolKind.Field, new phTypes.Range(0, 0, 0, 0))
];
}
});
mainHost.getOutline(model.uri).then(result => {
assert.equal(result.length, 1);
done();
disposable.dispose();
disposable2.dispose();
}, err => {
done(err);
});
});
test('DocumentSymbols, data conversion', function(done) {
let d = extHost.register('far', {
provideDocumentSymbols():any {
return [
new phTypes.SymbolInformation('boo',
phTypes.SymbolKind.Field,
new phTypes.Range(0, 0, 0, 0),
model.uri,
'far')
];
}
});
mainHost.getOutline(model.uri).then(result => {
assert.equal(result.length, 1);
let entry = result[0];
assert.equal(entry.label, 'boo');
assert.equal(entry.containerLabel, 'far');
assert.equal(entry.children, undefined);
assert.deepEqual(entry.range, { startLineNumber: 1, startColumn: 1, endLineNumber: 1, endColumn: 1 });
d.dispose();
done();
}, err => {
done(err);
});
});
});

View File

@@ -0,0 +1,84 @@
/*---------------------------------------------------------------------------------------------
* Copyright (c) Microsoft Corporation. All rights reserved.
* Licensed under the MIT License. See License.txt in the project root for license information.
*--------------------------------------------------------------------------------------------*/
'use strict';
import {NullThreadService} from 'vs/platform/test/common/nullThreadService';
import {create} from 'vs/base/common/types';
import {SyncDescriptor0} from 'vs/platform/instantiation/common/descriptors';
import {TPromise} from 'vs/base/common/winjs.base';
export class TestThreadService extends NullThreadService {
private _callCountValue: number = 0;
private _idle: TPromise<any>;
private _completeIdle: Function;
private get _callCount(): number {
return this._callCountValue;
}
private set _callCount(value:number) {
this._callCountValue = value;
if (this._callCountValue === 0) {
this._completeIdle();
this._idle = undefined;
}
}
sync(): TPromise<any> {
if (this._callCount === 0) {
return TPromise.as(undefined);
}
if (!this._idle) {
this._idle = new TPromise<any>((c, e) => {
this._completeIdle = c;
}, function() {
// no cancel
});
}
return this._idle;
}
protected _registerAndInstantiateMainProcessActor<T>(id: string, descriptor: SyncDescriptor0<T>): T {
let instance: any;
return this._getOrCreateProxyInstance({
callOnRemote: (proxyId: string, path: string, args: any[]): TPromise<any> => {
this._callCount++;
return TPromise.timeout(0).then(() => {
if (!instance) {
instance = create(descriptor.ctor, this);
}
let p: TPromise<any>;
try {
let result = (<Function>instance[path]).apply(instance, args);
p = TPromise.is(result) ? result : TPromise.as(result);
} catch (err) {
p = TPromise.wrapError(err);
}
return p.then(result => {
this._callCount--;
return result;
}, err => {
this._callCount--;
return TPromise.wrapError(err);
});
});
}
}, id, descriptor)
}
protected _registerAndInstantiatePluginHostActor<T>(id: string, descriptor: SyncDescriptor0<T>): T {
return this._getOrCreateLocalInstance(id, descriptor);
}
}
const Instance = new TestThreadService();
export default Instance;