mirror of
https://github.com/microsoft/vscode.git
synced 2026-04-22 17:48:56 +01:00
testing: smarter change event
This commit is contained in:
@@ -19,7 +19,7 @@ import { IExtHostRpcService } from 'vs/workbench/api/common/extHostRpcService';
|
||||
import { TestItem } from 'vs/workbench/api/common/extHostTypeConverters';
|
||||
import { Disposable } from 'vs/workbench/api/common/extHostTypes';
|
||||
import { IExtHostWorkspace } from 'vs/workbench/api/common/extHostWorkspace';
|
||||
import { AbstractIncrementalTestCollection, EMPTY_TEST_RESULT, IncrementalTestCollectionItem, InternalTestItem, RunTestForProviderRequest, RunTestsResult, TestDiffOpType, TestsDiff } from 'vs/workbench/contrib/testing/common/testCollection';
|
||||
import { AbstractIncrementalTestCollection, EMPTY_TEST_RESULT, IncrementalChangeCollector, IncrementalTestCollectionItem, InternalTestItem, RunTestForProviderRequest, RunTestsResult, TestDiffOpType, TestsDiff } from 'vs/workbench/contrib/testing/common/testCollection';
|
||||
import type * as vscode from 'vscode';
|
||||
|
||||
const getTestSubscriptionKey = (resource: ExtHostTestingResource, uri: URI) => `${resource}:${uri.toString()}`;
|
||||
@@ -378,27 +378,160 @@ export class SingleUseTestCollection implements IDisposable {
|
||||
*/
|
||||
interface MirroredCollectionTestItem extends IncrementalTestCollectionItem {
|
||||
revived: vscode.TestItem;
|
||||
depth: number;
|
||||
wrapped?: vscode.TestItem;
|
||||
}
|
||||
|
||||
class MirroredChangeCollector extends IncrementalChangeCollector<MirroredCollectionTestItem> {
|
||||
private readonly added = new Set<MirroredCollectionTestItem>();
|
||||
private readonly updated = new Set<MirroredCollectionTestItem>();
|
||||
private readonly removed = new Set<MirroredCollectionTestItem>();
|
||||
|
||||
private readonly alreadyRemoved = new Set<string>();
|
||||
|
||||
public get isEmpty() {
|
||||
return this.added.size === 0 && this.removed.size === 0 && this.updated.size === 0;
|
||||
}
|
||||
|
||||
constructor(private readonly collection: MirroredTestCollection, private readonly emitter: Emitter<vscode.TestChangeEvent>) {
|
||||
super();
|
||||
}
|
||||
|
||||
/**
|
||||
* @override
|
||||
*/
|
||||
public add(node: MirroredCollectionTestItem): void {
|
||||
this.added.add(node);
|
||||
}
|
||||
|
||||
/**
|
||||
* @override
|
||||
*/
|
||||
public update(node: MirroredCollectionTestItem): void {
|
||||
Object.assign(node.revived, TestItem.to(node.item));
|
||||
if (!this.added.has(node)) {
|
||||
this.updated.add(node);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* @override
|
||||
*/
|
||||
public remove(node: MirroredCollectionTestItem): void {
|
||||
if (this.added.has(node)) {
|
||||
this.added.delete(node);
|
||||
return;
|
||||
}
|
||||
|
||||
this.updated.delete(node);
|
||||
|
||||
if (node.parent && this.alreadyRemoved.has(node.parent)) {
|
||||
this.alreadyRemoved.add(node.id);
|
||||
return;
|
||||
}
|
||||
|
||||
this.removed.add(node);
|
||||
}
|
||||
|
||||
/**
|
||||
* @override
|
||||
*/
|
||||
public getChangeEvent(): vscode.TestChangeEvent {
|
||||
const { collection, added, updated, removed } = this;
|
||||
return {
|
||||
get added() { return [...added].map(collection.getPublicTestItem, collection); },
|
||||
get updated() { return [...updated].map(collection.getPublicTestItem, collection); },
|
||||
get removed() { return [...removed].map(collection.getPublicTestItem, collection); },
|
||||
get commonChangeAncestor() {
|
||||
let ancestorPath: MirroredCollectionTestItem[] | undefined;
|
||||
const buildAncestorPath = (node: MirroredCollectionTestItem | undefined) => {
|
||||
if (!node) {
|
||||
return undefined;
|
||||
}
|
||||
|
||||
// add the node and all its parents to the list of ancestors. If
|
||||
// the node is detached, do not return a path (its parent will
|
||||
// also have been passed to remove() and be present)
|
||||
const path: MirroredCollectionTestItem[] = new Array(node.depth + 1);
|
||||
for (let i = node.depth; i >= 0; i--) {
|
||||
if (!node) {
|
||||
return undefined; // detached child
|
||||
}
|
||||
|
||||
path[node.depth] = node;
|
||||
node = node.parent ? collection.getMirroredTestDataById(node.parent) : undefined;
|
||||
}
|
||||
|
||||
return path;
|
||||
};
|
||||
|
||||
const addAncestorPath = (node: MirroredCollectionTestItem) => {
|
||||
// fast path: if the common ancestor is already the root, no more work to do
|
||||
if (ancestorPath && ancestorPath.length === 0) {
|
||||
return;
|
||||
}
|
||||
|
||||
const thisPath = buildAncestorPath(node);
|
||||
if (!thisPath) {
|
||||
return;
|
||||
}
|
||||
|
||||
if (!ancestorPath) {
|
||||
ancestorPath = thisPath;
|
||||
return;
|
||||
}
|
||||
|
||||
// removes node from the path to the ancestor that don't match
|
||||
// the corresponding node in *this* path.
|
||||
for (let i = ancestorPath.length - 1; i >= 0; i--) {
|
||||
if (ancestorPath[i] !== thisPath[i]) {
|
||||
ancestorPath.pop();
|
||||
}
|
||||
}
|
||||
};
|
||||
|
||||
const addParentAncestor = (node: MirroredCollectionTestItem) => {
|
||||
if (ancestorPath && ancestorPath.length === 0) {
|
||||
// no-op
|
||||
} else if (node.parent === null) {
|
||||
ancestorPath = [];
|
||||
} else {
|
||||
const parent = collection.getMirroredTestDataById(node.parent);
|
||||
if (parent) {
|
||||
addAncestorPath(parent);
|
||||
}
|
||||
}
|
||||
};
|
||||
|
||||
for (const node of added) { addParentAncestor(node); }
|
||||
for (const node of updated) { addAncestorPath(node); }
|
||||
for (const node of removed) { addParentAncestor(node); }
|
||||
|
||||
const ancestor = ancestorPath && ancestorPath[ancestorPath.length - 1];
|
||||
return ancestor ? collection.getPublicTestItem(ancestor) : null;
|
||||
},
|
||||
};
|
||||
}
|
||||
|
||||
public complete() {
|
||||
if (!this.isEmpty) {
|
||||
this.emitter.fire(this.getChangeEvent());
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Maintains tests in this extension host sent from the main thread.
|
||||
* @private
|
||||
*/
|
||||
export class MirroredTestCollection extends AbstractIncrementalTestCollection<MirroredCollectionTestItem> {
|
||||
private changeEmitter = new Emitter<vscode.TestItem | null>();
|
||||
private changeEmitter = new Emitter<vscode.TestChangeEvent>();
|
||||
|
||||
/**
|
||||
* Change emitter that fires with the same sematics as `TestObserver.onDidChangeTests`.
|
||||
*/
|
||||
public readonly onDidChangeTests = this.changeEmitter.event;
|
||||
|
||||
/**
|
||||
* Mapping of mirrored test items to their underlying ID. Given here to avoid
|
||||
* exposing them to extensions.
|
||||
*/
|
||||
protected readonly mirroredTestIds = new WeakMap<vscode.TestItem, string>();
|
||||
|
||||
/**
|
||||
* Gets a list of root test items.
|
||||
*/
|
||||
@@ -412,49 +545,67 @@ export class MirroredTestCollection extends AbstractIncrementalTestCollection<Mi
|
||||
public getAllAsTestItem(itemIds: ReadonlyArray<string>): vscode.TestItem[] {
|
||||
return itemIds.map(itemId => {
|
||||
const item = this.items.get(itemId);
|
||||
return item && this.createCollectionItemWrapper(item);
|
||||
return item && this.getPublicTestItem(item);
|
||||
}).filter(isDefined);
|
||||
}
|
||||
|
||||
/**
|
||||
*
|
||||
* If the test ID exists, returns its underlying ID.
|
||||
*/
|
||||
public getMirroredTestDataById(itemId: string) {
|
||||
return this.items.get(itemId);
|
||||
}
|
||||
|
||||
/**
|
||||
* If the test item is a mirrored test item, returns its underlying ID.
|
||||
*/
|
||||
public getMirroredTestDataByReference(item: vscode.TestItem) {
|
||||
const itemId = this.mirroredTestIds.get(item);
|
||||
return itemId ? this.items.get(itemId) : undefined;
|
||||
const id = getMirroredItemId(item);
|
||||
return id ? this.items.get(id) : undefined;
|
||||
}
|
||||
|
||||
/**
|
||||
* @override
|
||||
*/
|
||||
protected createItem(item: InternalTestItem): MirroredCollectionTestItem {
|
||||
return { ...item, revived: TestItem.to(item.item), children: new Set() };
|
||||
protected createItem(item: InternalTestItem, parent?: MirroredCollectionTestItem): MirroredCollectionTestItem {
|
||||
return { ...item, revived: TestItem.to(item.item), depth: parent ? parent.depth + 1 : 0, children: new Set() };
|
||||
}
|
||||
|
||||
/**
|
||||
* @override
|
||||
*/
|
||||
protected onChange(item: MirroredCollectionTestItem | null) {
|
||||
if (item) {
|
||||
Object.assign(item.revived, TestItem.to(item.item));
|
||||
}
|
||||
|
||||
this.changeEmitter.fire(item ? this.createCollectionItemWrapper(item) : null);
|
||||
protected createChangeCollector() {
|
||||
return new MirroredChangeCollector(this, this.changeEmitter);
|
||||
}
|
||||
|
||||
private createCollectionItemWrapper(item: MirroredCollectionTestItem): vscode.TestItem {
|
||||
/**
|
||||
* Gets the public test item instance for the given mirrored record.
|
||||
*/
|
||||
public getPublicTestItem(item: MirroredCollectionTestItem): vscode.TestItem {
|
||||
if (!item.wrapped) {
|
||||
item.wrapped = createMirroredTestItem(item, this);
|
||||
this.mirroredTestIds.set(item.wrapped, item.id);
|
||||
}
|
||||
|
||||
return item.wrapped;
|
||||
}
|
||||
}
|
||||
|
||||
const getMirroredItemId = (item: vscode.TestItem) => {
|
||||
return (item as any)[MirroredItemId] as string | undefined;
|
||||
};
|
||||
|
||||
const MirroredItemId = Symbol('MirroredItemId');
|
||||
|
||||
const createMirroredTestItem = (internal: MirroredCollectionTestItem, collection: MirroredTestCollection): vscode.TestItem => {
|
||||
const obj = {};
|
||||
|
||||
Object.defineProperty(obj, MirroredItemId, {
|
||||
enumerable: false,
|
||||
configurable: false,
|
||||
value: internal.id,
|
||||
});
|
||||
|
||||
Object.defineProperty(obj, 'children', {
|
||||
enumerable: true,
|
||||
configurable: false,
|
||||
|
||||
Reference in New Issue
Block a user