chore(vscode): update to 1.53.2

These conflicts will be resolved in the following commits. We do it this way so
that PR review is possible.
This commit is contained in:
Joe Previte
2021-02-25 11:27:27 -07:00
1900 changed files with 83066 additions and 64589 deletions

View File

@@ -6,7 +6,6 @@
import { mapFind } from 'vs/base/common/arrays';
import { disposableTimeout } from 'vs/base/common/async';
import { CancellationToken } from 'vs/base/common/cancellation';
import { throttle } from 'vs/base/common/decorators';
import { Emitter } from 'vs/base/common/event';
import { once } from 'vs/base/common/functional';
import { DisposableStore, IDisposable } from 'vs/base/common/lifecycle';
@@ -14,25 +13,35 @@ import { isDefined } from 'vs/base/common/types';
import { URI, UriComponents } from 'vs/base/common/uri';
import { generateUuid } from 'vs/base/common/uuid';
import { ExtHostTestingResource, ExtHostTestingShape, MainContext, MainThreadTestingShape } from 'vs/workbench/api/common/extHost.protocol';
import { ExtHostDocumentData } from 'vs/workbench/api/common/extHostDocumentData';
import { IExtHostDocumentsAndEditors } from 'vs/workbench/api/common/extHostDocumentsAndEditors';
import { IExtHostRpcService } from 'vs/workbench/api/common/extHostRpcService';
import { TestItem } from 'vs/workbench/api/common/extHostTypeConverters';
import { Disposable, RequiredTestItem } from 'vs/workbench/api/common/extHostTypes';
import { Disposable } from 'vs/workbench/api/common/extHostTypes';
import { IExtHostWorkspace } from 'vs/workbench/api/common/extHostWorkspace';
import { AbstractIncrementalTestCollection, EMPTY_TEST_RESULT, IncrementalChangeCollector, IncrementalTestCollectionItem, InternalTestItem, RunTestForProviderRequest, RunTestsResult, TestDiffOpType, TestsDiff } from 'vs/workbench/contrib/testing/common/testCollection';
import { OwnedTestCollection, SingleUseTestCollection } from 'vs/workbench/contrib/testing/common/ownedTestCollection';
import { AbstractIncrementalTestCollection, EMPTY_TEST_RESULT, IncrementalChangeCollector, IncrementalTestCollectionItem, InternalTestItem, InternalTestItemWithChildren, InternalTestResults, RunTestForProviderRequest, RunTestsResult, TestDiffOpType, TestIdWithProvider, TestsDiff } from 'vs/workbench/contrib/testing/common/testCollection';
import type * as vscode from 'vscode';
const getTestSubscriptionKey = (resource: ExtHostTestingResource, uri: URI) => `${resource}:${uri.toString()}`;
export class ExtHostTesting implements ExtHostTestingShape {
private readonly resultsChangedEmitter = new Emitter<void>();
private readonly providers = new Map<string, vscode.TestProvider>();
private readonly proxy: MainThreadTestingShape;
private readonly ownedTests = new OwnedTestCollection();
private readonly testSubscriptions = new Map<string, { collection: SingleUseTestCollection, store: IDisposable }>();
private readonly testSubscriptions = new Map<string, {
collection: SingleUseTestCollection;
store: IDisposable;
subscribeFn: (id: string, provider: vscode.TestProvider) => void;
}>();
private workspaceObservers: WorkspaceFolderTestObserverFactory;
private textDocumentObservers: TextDocumentTestObserverFactory;
public onLastResultsChanged = this.resultsChangedEmitter.event;
public lastResults?: vscode.TestResults;
constructor(@IExtHostRpcService rpc: IExtHostRpcService, @IExtHostDocumentsAndEditors private readonly documents: IExtHostDocumentsAndEditors, @IExtHostWorkspace private readonly workspace: IExtHostWorkspace) {
this.proxy = rpc.getProxy(MainContext.MainThreadTesting);
this.workspaceObservers = new WorkspaceFolderTestObserverFactory(this.proxy);
@@ -47,6 +56,14 @@ export class ExtHostTesting implements ExtHostTestingShape {
this.providers.set(providerId, provider);
this.proxy.$registerTestProvider(providerId);
// give the ext a moment to register things rather than synchronously invoking within activate()
const toSubscribe = [...this.testSubscriptions.keys()];
setTimeout(() => {
for (const subscription of toSubscribe) {
this.testSubscriptions.get(subscription)?.subscribeFn(providerId, provider);
}
}, 0);
return new Disposable(() => {
this.providers.delete(providerId);
this.proxy.$unregisterTestProvider(providerId);
@@ -70,7 +87,7 @@ export class ExtHostTesting implements ExtHostTestingShape {
/**
* Implements vscode.test.runTests
*/
public async runTests(req: vscode.TestRunOptions<vscode.TestItem>) {
public async runTests(req: vscode.TestRunOptions<vscode.TestItem>, token = CancellationToken.None) {
await this.proxy.$runTests({
tests: req.tests
// Find workspace items first, then owned tests, then document tests.
@@ -82,14 +99,27 @@ export class ExtHostTesting implements ExtHostTestingShape {
.filter(isDefined)
.map(item => ({ providerId: item.providerId, testId: item.id })),
debug: req.debug
});
}, token);
}
/**
* Updates test results shown to extensions.
* @override
*/
public $publishTestResults(results: InternalTestResults): void {
const convert = (item: InternalTestItemWithChildren): vscode.RequiredTestItem =>
({ ...TestItem.toShallow(item.item), children: item.children.map(convert) });
this.lastResults = { tests: results.tests.map(convert) };
this.resultsChangedEmitter.fire();
}
/**
* Handles a request to read tests for a file, or workspace.
* @override
*/
public $subscribeToTests(resource: ExtHostTestingResource, uriComponents: UriComponents) {
public async $subscribeToTests(resource: ExtHostTestingResource, uriComponents: UriComponents) {
const uri = URI.revive(uriComponents);
const subscriptionKey = getTestSubscriptionKey(resource, uri);
if (this.testSubscriptions.has(subscriptionKey)) {
@@ -98,12 +128,29 @@ export class ExtHostTesting implements ExtHostTestingShape {
let method: undefined | ((p: vscode.TestProvider) => vscode.TestHierarchy<vscode.TestItem> | undefined);
if (resource === ExtHostTestingResource.TextDocument) {
const document = this.documents.getDocument(uri);
let document = this.documents.getDocument(uri);
// we can ask to subscribe to tests before the documents are populated in
// the extension host. Try to wait.
if (!document) {
const store = new DisposableStore();
document = await new Promise<ExtHostDocumentData | undefined>(resolve => {
store.add(disposableTimeout(() => resolve(undefined), 5000));
store.add(this.documents.onDidAddDocuments(e => {
const data = e.find(data => data.document.uri.toString() === uri.toString());
if (data) { resolve(data); }
}));
}).finally(() => store.dispose());
}
if (document) {
method = p => p.createDocumentTestHierarchy?.(document.document);
const folder = await this.workspace.getWorkspaceFolder2(uri, false);
method = p => p.createDocumentTestHierarchy
? p.createDocumentTestHierarchy(document!.document)
: this.createDefaultDocumentTestHierarchy(p, document!.document, folder);
}
} else {
const folder = this.workspace.getWorkspaceFolder(uri, false);
const folder = await this.workspace.getWorkspaceFolder2(uri, false);
if (folder) {
method = p => p.createWorkspaceTestHierarchy?.(folder);
}
@@ -113,24 +160,34 @@ export class ExtHostTesting implements ExtHostTestingShape {
return;
}
const disposable = new DisposableStore();
const collection = disposable.add(this.ownedTests.createForHierarchy(diff => this.proxy.$publishDiff(resource, uriComponents, diff)));
for (const [id, provider] of this.providers) {
const subscribeFn = (id: string, provider: vscode.TestProvider) => {
try {
const hierarchy = method(provider);
const hierarchy = method!(provider);
if (!hierarchy) {
continue;
return;
}
collection.pushDiff([TestDiffOpType.DeltaDiscoverComplete, 1]);
disposable.add(hierarchy);
collection.addRoot(hierarchy.root, id);
Promise.resolve(hierarchy.discoveredInitialTests).then(() => collection.pushDiff([TestDiffOpType.DeltaDiscoverComplete, -1]));
hierarchy.onDidChangeTest(e => collection.onItemChange(e, id));
} catch (e) {
console.error(e);
}
};
const disposable = new DisposableStore();
const collection = disposable.add(this.ownedTests.createForHierarchy(diff => this.proxy.$publishDiff(resource, uriComponents, diff)));
for (const [id, provider] of this.providers) {
subscribeFn(id, provider);
}
this.testSubscriptions.set(subscriptionKey, { store: disposable, collection });
// note: we don't increment the root count initially -- this is done by the
// main thread, incrementing once per extension host. We just push the
// diff to signal that roots have been discovered.
collection.pushDiff([TestDiffOpType.DeltaRootsComplete, -1]);
this.testSubscriptions.set(subscriptionKey, { store: disposable, collection, subscribeFn });
}
/**
@@ -162,217 +219,193 @@ export class ExtHostTesting implements ExtHostTestingShape {
* providers to be run.
* @override
*/
public async $runTestsForProvider(req: RunTestForProviderRequest): Promise<RunTestsResult> {
public async $runTestsForProvider(req: RunTestForProviderRequest, cancellation: CancellationToken): Promise<RunTestsResult> {
const provider = this.providers.get(req.providerId);
if (!provider || !provider.runTests) {
return EMPTY_TEST_RESULT;
}
const tests = req.ids.map(id => this.ownedTests.getTestById(id)?.actual).filter(isDefined);
const tests = req.ids.map(id => this.ownedTests.getTestById(id)?.actual)
.filter(isDefined)
// Only send the actual TestItem's to the user to run.
.map(t => t instanceof TestItemFilteredWrapper ? t.actual : t);
if (!tests.length) {
return EMPTY_TEST_RESULT;
}
await provider.runTests({ tests, debug: req.debug }, CancellationToken.None);
return EMPTY_TEST_RESULT;
}
}
const keyMap: { [K in keyof Omit<RequiredTestItem, 'children'>]: null } = {
label: null,
location: null,
state: null,
debuggable: null,
description: null,
runnable: null
};
const simpleProps = Object.keys(keyMap) as ReadonlyArray<keyof typeof keyMap>;
const itemEqualityComparator = (a: vscode.TestItem) => {
const values: unknown[] = [];
for (const prop of simpleProps) {
values.push(a[prop]);
}
return (b: vscode.TestItem) => {
for (let i = 0; i < simpleProps.length; i++) {
if (values[i] !== b[simpleProps[i]]) {
return false;
try {
await provider.runTests({ tests, debug: req.debug }, cancellation);
for (const { collection } of this.testSubscriptions.values()) {
collection.flushDiff(); // ensure all states are updated
}
return EMPTY_TEST_RESULT;
} catch (e) {
console.error(e); // so it appears to attached debuggers
throw e;
}
}
public $lookupTest(req: TestIdWithProvider): Promise<InternalTestItem | undefined> {
const owned = this.ownedTests.getTestById(req.testId);
if (!owned) {
return Promise.resolve(undefined);
}
return true;
};
};
/**
* @private
*/
export interface OwnedCollectionTestItem extends InternalTestItem {
actual: vscode.TestItem;
previousChildren: Set<string>;
previousEquals: (v: vscode.TestItem) => boolean;
}
/**
* @private
*/
export class OwnedTestCollection {
protected readonly testIdToInternal = new Map<string, OwnedCollectionTestItem>();
/**
* Gets test information by ID, if it was defined and still exists in this
* extension host.
*/
public getTestById(id: string) {
return this.testIdToInternal.get(id);
const { actual, previousChildren, previousEquals, ...item } = owned;
return Promise.resolve(item);
}
/**
* Creates a new test collection for a specific hierarchy for a workspace
* or document observation.
*/
public createForHierarchy(publishDiff: (diff: TestsDiff) => void = () => undefined) {
return new SingleUseTestCollection(this.testIdToInternal, publishDiff);
}
}
/**
* Maintains tests created and registered for a single set of hierarchies
* for a workspace or document.
* @private
*/
export class SingleUseTestCollection implements IDisposable {
protected readonly testItemToInternal = new Map<vscode.TestItem, OwnedCollectionTestItem>();
protected diff: TestsDiff = [];
private disposed = false;
constructor(private readonly testIdToInternal: Map<string, OwnedCollectionTestItem>, private readonly publishDiff: (diff: TestsDiff) => void) { }
/**
* Adds a new root node to the collection.
*/
public addRoot(item: vscode.TestItem, providerId: string) {
this.addItem(item, providerId, null);
this.throttleSendDiff();
}
/**
* Gets test information by its reference, if it was defined and still exists
* in this extension host.
*/
public getTestByReference(item: vscode.TestItem) {
return this.testItemToInternal.get(item);
}
/**
* Should be called when an item change is fired on the test provider.
*/
public onItemChange(item: vscode.TestItem, providerId: string) {
const existing = this.testItemToInternal.get(item);
if (!existing) {
if (!this.disposed) {
console.warn(`Received a TestProvider.onDidChangeTest for a test that wasn't seen before as a child.`);
}
private createDefaultDocumentTestHierarchy(provider: vscode.TestProvider, document: vscode.TextDocument, folder: vscode.WorkspaceFolder | undefined): vscode.TestHierarchy<vscode.TestItem> | undefined {
if (!folder) {
return;
}
this.addItem(item, providerId, existing.parent);
this.throttleSendDiff();
}
/**
* Gets a diff of all changes that have been made, and clears the diff queue.
*/
public collectDiff() {
const diff = this.diff;
this.diff = [];
return diff;
}
public dispose() {
for (const item of this.testItemToInternal.values()) {
this.testIdToInternal.delete(item.id);
const workspaceHierarchy = provider.createWorkspaceTestHierarchy?.(folder);
if (!workspaceHierarchy) {
return;
}
this.testIdToInternal.clear();
this.diff = [];
this.disposed = true;
}
const onDidChangeTest = new Emitter<vscode.TestItem>();
workspaceHierarchy.onDidChangeTest(node => {
const wrapper = TestItemFilteredWrapper.getWrapperForTestItem(node, document);
const previouslySeen = wrapper.hasNodeMatchingFilter;
protected getId(): string {
return generateUuid();
}
if (previouslySeen) {
// reset cache and get whether you can currently see the TestItem.
wrapper.reset();
const currentlySeen = wrapper.hasNodeMatchingFilter;
private addItem(actual: vscode.TestItem, providerId: string, parent: string | null) {
let internal = this.testItemToInternal.get(actual);
if (!internal) {
internal = {
actual,
id: this.getId(),
parent,
item: TestItem.from(actual),
providerId,
previousChildren: new Set(),
previousEquals: itemEqualityComparator(actual),
};
if (currentlySeen) {
onDidChangeTest.fire(wrapper);
return;
}
this.testItemToInternal.set(actual, internal);
this.testIdToInternal.set(internal.id, internal);
this.diff.push([TestDiffOpType.Add, { id: internal.id, parent, providerId, item: internal.item }]);
} else if (!internal.previousEquals(actual)) {
internal.item = TestItem.from(actual);
internal.previousEquals = itemEqualityComparator(actual);
this.diff.push([TestDiffOpType.Update, { id: internal.id, parent, providerId, item: internal.item }]);
}
// If there are children, track which ones are deleted
// and recursively and/update them.
if (actual.children) {
const deletedChildren = internal.previousChildren;
const currentChildren = new Set<string>();
for (const child of actual.children) {
const c = this.addItem(child, providerId, internal.id);
deletedChildren.delete(c.id);
currentChildren.add(c.id);
// Fire the event to say that the current visible parent has changed.
onDidChangeTest.fire(wrapper.visibleParent);
return;
}
for (const child of deletedChildren) {
this.removeItembyId(child);
const previousParent = wrapper.visibleParent;
wrapper.reset();
const currentlySeen = wrapper.hasNodeMatchingFilter;
// It wasn't previously seen and isn't currently seen so
// nothing has actually changed.
if (!currentlySeen) {
return;
}
internal.previousChildren = currentChildren;
}
// The test is now visible so we need to refresh the cache
// of the previous visible parent and fire that it has changed.
previousParent.reset();
onDidChangeTest.fire(previousParent);
});
return {
root: TestItemFilteredWrapper.getWrapperForTestItem(workspaceHierarchy.root, document),
dispose: () => {
onDidChangeTest.dispose();
TestItemFilteredWrapper.removeFilter(document);
},
onDidChangeTest: onDidChangeTest.event
};
}
}
return internal;
/*
* A class which wraps a vscode.TestItem that provides the ability to filter a TestItem's children
* to only the children that are located in a certain vscode.Uri.
*/
export class TestItemFilteredWrapper implements vscode.TestItem {
private static wrapperMap = new WeakMap<vscode.TextDocument, WeakMap<vscode.TestItem, TestItemFilteredWrapper>>();
public static removeFilter(document: vscode.TextDocument): void {
this.wrapperMap.delete(document);
}
private removeItembyId(id: string) {
this.diff.push([TestDiffOpType.Remove, id]);
const queue = [this.testIdToInternal.get(id)];
while (queue.length) {
const item = queue.pop();
if (!item) {
continue;
}
this.testIdToInternal.delete(item.id);
this.testItemToInternal.delete(item.actual);
for (const child of item.previousChildren) {
queue.push(this.testIdToInternal.get(child));
}
// Wraps the TestItem specified in a TestItemFilteredWrapper and pulls from a cache if it already exists.
public static getWrapperForTestItem(item: vscode.TestItem, filterDocument: vscode.TextDocument, parent?: TestItemFilteredWrapper): TestItemFilteredWrapper {
let innerMap = this.wrapperMap.get(filterDocument);
if (innerMap?.has(item)) {
return innerMap.get(item)!;
}
if (!innerMap) {
innerMap = new WeakMap<vscode.TestItem, TestItemFilteredWrapper>();
this.wrapperMap.set(filterDocument, innerMap);
}
const w = new TestItemFilteredWrapper(item, filterDocument, parent);
innerMap.set(item, w);
return w;
}
@throttle(200)
protected throttleSendDiff() {
const diff = this.collectDiff();
if (diff.length) {
this.publishDiff(diff);
public get label() {
return this.actual.label;
}
public get debuggable() {
return this.actual.debuggable;
}
public get description() {
return this.actual.description;
}
public get location() {
return this.actual.location;
}
public get runnable() {
return this.actual.runnable;
}
public get state() {
return this.actual.state;
}
public get children() {
// We only want children that match the filter.
return this.getWrappedChildren().filter(child => child.hasNodeMatchingFilter);
}
public get visibleParent(): TestItemFilteredWrapper {
return this.hasNodeMatchingFilter ? this : this.parent!.visibleParent;
}
private matchesFilter: boolean | undefined;
// Determines if the TestItem matches the filter. This would be true if:
// 1. We don't have a parent (because the root is the workspace root node)
// 2. The URI of the current node matches the filter URI
// 3. Some child of the current node matches the filter URI
public get hasNodeMatchingFilter(): boolean {
if (this.matchesFilter === undefined) {
this.matchesFilter = !this.parent
|| this.actual.location?.uri.toString() === this.filterDocument.uri.toString()
|| this.getWrappedChildren().some(child => child.hasNodeMatchingFilter);
}
return this.matchesFilter;
}
// Reset the cache of whether or not you can see a node from a particular node
// up to it's visible parent.
public reset(): void {
if (this !== this.visibleParent) {
this.parent?.reset();
}
this.matchesFilter = undefined;
}
private constructor(public readonly actual: vscode.TestItem, private filterDocument: vscode.TextDocument, private readonly parent?: TestItemFilteredWrapper) {
this.getWrappedChildren();
}
private getWrappedChildren() {
return this.actual.children?.map(t => TestItemFilteredWrapper.getWrapperForTestItem(t, this.filterDocument, this)) || [];
}
}
@@ -382,7 +415,7 @@ export class SingleUseTestCollection implements IDisposable {
interface MirroredCollectionTestItem extends IncrementalTestCollectionItem {
revived: vscode.TestItem;
depth: number;
wrapped?: vscode.TestItem;
wrapped?: vscode.RequiredTestItem;
}
class MirroredChangeCollector extends IncrementalChangeCollector<MirroredCollectionTestItem> {
@@ -411,7 +444,7 @@ class MirroredChangeCollector extends IncrementalChangeCollector<MirroredCollect
* @override
*/
public update(node: MirroredCollectionTestItem): void {
Object.assign(node.revived, TestItem.to(node.item));
Object.assign(node.revived, TestItem.toShallow(node.item));
if (!this.added.has(node)) {
this.updated.add(node);
}
@@ -545,8 +578,8 @@ export class MirroredTestCollection extends AbstractIncrementalTestCollection<Mi
/**
* Translates the item IDs to TestItems for exposure to extensions.
*/
public getAllAsTestItem(itemIds: Iterable<string>): vscode.TestItem[] {
let output: vscode.TestItem[] = [];
public getAllAsTestItem(itemIds: Iterable<string>): vscode.RequiredTestItem[] {
let output: vscode.RequiredTestItem[] = [];
for (const itemId of itemIds) {
const item = this.items.get(itemId);
if (item) {
@@ -577,7 +610,7 @@ export class MirroredTestCollection extends AbstractIncrementalTestCollection<Mi
* @override
*/
protected createItem(item: InternalTestItem, parent?: MirroredCollectionTestItem): MirroredCollectionTestItem {
return { ...item, revived: TestItem.to(item.item), depth: parent ? parent.depth + 1 : 0, children: new Set() };
return { ...item, revived: TestItem.toShallow(item.item), depth: parent ? parent.depth + 1 : 0, children: new Set() };
}
/**
@@ -590,9 +623,9 @@ export class MirroredTestCollection extends AbstractIncrementalTestCollection<Mi
/**
* Gets the public test item instance for the given mirrored record.
*/
public getPublicTestItem(item: MirroredCollectionTestItem): vscode.TestItem {
public getPublicTestItem(item: MirroredCollectionTestItem): vscode.RequiredTestItem {
if (!item.wrapped) {
item.wrapped = new ExtHostTestItem(item, this);
item.wrapped = new TestItemFromMirror(item, this);
}
return item.wrapped;
@@ -605,10 +638,11 @@ const getMirroredItemId = (item: vscode.TestItem) => {
const MirroredItemId = Symbol('MirroredItemId');
class ExtHostTestItem implements vscode.TestItem, RequiredTestItem {
class TestItemFromMirror implements vscode.RequiredTestItem {
readonly #internal: MirroredCollectionTestItem;
readonly #collection: MirroredTestCollection;
public get id() { return this.#internal.revived.id!; }
public get label() { return this.#internal.revived.label; }
public get description() { return this.#internal.revived.description; }
public get state() { return this.#internal.revived.state; }
@@ -627,14 +661,18 @@ class ExtHostTestItem implements vscode.TestItem, RequiredTestItem {
}
public toJSON() {
const serialized: RequiredTestItem = {
const serialized: vscode.RequiredTestItem & TestIdWithProvider = {
id: this.id,
label: this.label,
description: this.description,
state: this.state,
location: this.location,
runnable: this.runnable,
debuggable: this.debuggable,
children: this.children.map(c => (c as ExtHostTestItem).toJSON()),
children: this.children.map(c => (c as TestItemFromMirror).toJSON()),
providerId: this.#internal.providerId,
testId: this.#internal.id,
};
return serialized;
@@ -655,6 +693,7 @@ abstract class AbstractTestObserverFactory {
const resourceKey = resourceUri.toString();
const resource = this.resources.get(resourceKey) ?? this.createObserverData(resourceUri);
resource.pendingDeletion?.dispose();
resource.observers++;
return {
@@ -778,16 +817,9 @@ class TextDocumentTestObserverFactory extends AbstractTestObserverFactory {
const uriString = resourceUri.toString();
this.diffListeners.set(uriString, onDiff);
const disposeListener = this.documents.onDidRemoveDocuments(evt => {
if (evt.some(delta => delta.document.uri.toString() === uriString)) {
this.unlisten(resourceUri);
}
});
this.proxy.$subscribeToDiffs(ExtHostTestingResource.TextDocument, resourceUri);
return new Disposable(() => {
this.proxy.$unsubscribeFromDiffs(ExtHostTestingResource.TextDocument, resourceUri);
disposeListener.dispose();
this.diffListeners.delete(uriString);
});
}