Skip to content

Commit ee6ae8d

Browse files
author
Song Gao
committed
fix bug
1 parent 655ff24 commit ee6ae8d

File tree

5 files changed

+52
-116
lines changed

5 files changed

+52
-116
lines changed

editors/code/src/test_explorer/README.md

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -8,7 +8,7 @@ An obvious choice to to load all tests for all projects at the first time, then
88
Another choice is only to load test cases laziness. Only when we open a file or click expand button of the case in test explorer, we load itself and its parents(if they are not loaded yet).
99

1010
1. Where should user go when they click "open file" for test module, definition or the declaration?
11-
For now, I choose definition
11+
For now, I choose declaration
1212

1313
1. How to know whether a test case start? When run the whole test suite, how to know the test case in it is queued or started?
1414
1. How to support multi select test cases, especially when the target is differnt(part of them might be --lib, and part of them might be --test)?
Lines changed: 0 additions & 85 deletions
Original file line numberDiff line numberDiff line change
@@ -1,85 +1,10 @@
11
import * as vscode from "vscode";
22
import { testController } from ".";
3-
import { assert } from "../util";
4-
5-
interface TestItemWithParentCollection {
6-
testItem: vscode.TestItem;
7-
8-
collection: vscode.TestItemCollection;
9-
}
103

114
/**
125
* General helper functions for VSCode TestItemController
136
*/
147
export abstract class TestControllerHelper {
15-
/**
16-
* This is feasible for the id we are using is in order, like the parent of "A::B::C::D" is "A::B::C"
17-
*
18-
* Return `undefined`, if the package level item is not created.
19-
*/
20-
static findNeartestTestItemById(id: string): vscode.TestItem | undefined {
21-
const idPaths = id.split("::");
22-
const indexLength = idPaths.length;
23-
let currentId = idPaths[0];
24-
let candidate: vscode.TestItem | undefined;
25-
candidate = testController!.items.get(currentId);
26-
for (let index = 1; index < indexLength; index++) {
27-
currentId = currentId + `::${idPaths[index]}`;
28-
const next = candidate?.children.get(currentId);
29-
if (next === undefined) {
30-
return candidate;
31-
}
32-
candidate = next;
33-
}
34-
35-
return candidate;
36-
}
37-
38-
/**
39-
* This is feasible for the id of each item is unique and in regular.
40-
*
41-
* Unique -- could compare id directly
42-
* Regular -- not need to traversal the tree
43-
*/
44-
static findTestItemById(id: string): vscode.TestItem | undefined {
45-
const nearestItem = TestControllerHelper.findNeartestTestItemById(id);
46-
if (nearestItem?.id === id) {
47-
return nearestItem;
48-
}
49-
return undefined;
50-
}
51-
52-
static findTestItemPathsByUri(
53-
uri: vscode.Uri,
54-
testItemCollection: vscode.TestItemCollection = testController!.items
55-
): TestItemWithParentCollection[] {
56-
const results: TestItemWithParentCollection[] = [];
57-
const curTestItemPaths: vscode.TestItem[] = [];
58-
const curTestItemCollectionPaths: vscode.TestItemCollection[] = [];
59-
60-
TestControllerHelper.visitTestItemTreePreOrder((item, collection) => {
61-
curTestItemPaths.push(item);
62-
curTestItemCollectionPaths.push(collection);
63-
if (item.uri?.toString() === uri.toString()) {
64-
assert(curTestItemPaths.length === curTestItemCollectionPaths.length, "each item must be contained in a collection.");
65-
for (let index = 0; index < curTestItemPaths.length; index++) {
66-
const testItem = curTestItemPaths[index];
67-
const collection = curTestItemCollectionPaths[index];
68-
results.push({
69-
collection,
70-
testItem,
71-
});
72-
}
73-
return true;
74-
};
75-
return;
76-
}, testItemCollection, () => {
77-
curTestItemPaths.pop();
78-
curTestItemCollectionPaths.pop();
79-
});
80-
return results;
81-
}
82-
838
/**
849
*
8510
* @param cb Stop serach the current subtree if returning non-falsy value. But the rest subtree will continute to search.
@@ -100,14 +25,4 @@ export abstract class TestControllerHelper {
10025
exitCb?.(item, collection);
10126
});
10227
}
103-
104-
static visitTestItemTreePostOrder(
105-
cb: (item: vscode.TestItem, collection: vscode.TestItemCollection) => void,
106-
root: vscode.TestItemCollection = testController!.items
107-
) {
108-
root.forEach((item, collection) => {
109-
TestControllerHelper.visitTestItemTreePostOrder(cb, item.children);
110-
cb(item, collection);
111-
});
112-
}
11328
}

editors/code/src/test_explorer/TestMetadata.ts

Lines changed: 0 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -3,11 +3,6 @@ import * as ra from "../lsp_ext";
33
import { assert, assertNever } from "../util";
44
import { TargetKind, NodeKind, TestLikeNodeKind, TestLocation } from "./test_model_tree";
55

6-
export enum TestKind {
7-
TestModule,
8-
Test
9-
}
10-
116
export class RunnableFacde {
127
public readonly origin: ra.Runnable;
138

editors/code/src/test_explorer/discover_and_update.ts

Lines changed: 48 additions & 22 deletions
Original file line numberDiff line numberDiff line change
@@ -35,7 +35,7 @@ export function registerWatcherForWorkspaces() {
3535
}
3636

3737
console.log("onDidChangeTextDocument callback");
38-
debounceUpToDownUpdateTestItems(e.document.uri);
38+
debounceHandleFileChangeCore(e.document.uri);
3939

4040
function isRustFile(document: vscode.TextDocument): boolean {
4141
return document.fileName.toLowerCase().endsWith('.rs');
@@ -62,17 +62,21 @@ function watchWorkspace(workspaceFolder: vscode.WorkspaceFolder) {
6262
function debounce(fn: Function, ms: number) {
6363
let timeout: NodeJS.Timeout | undefined = undefined;
6464
return (...params: any[]) => {
65-
console.log(performance.now());
65+
console.log("debounce debug: " + performance.now());
6666
clearTimeout(timeout);
6767
timeout = setTimeout(() => {
6868
fn(...params);
6969
}, ms);
7070
};
7171
}
7272

73-
const deboundeRefresh = debounce(refreshAllThings, 500);
73+
// Why choose 2s:
74+
// when auto save is enabled, there seems to be 2 events for workspace.onDidChangeTextDocument
75+
// the first one is for the change of the file, the second one is for the save of the file
76+
// And usually it takes about 1s between on my machine between the two events
77+
const deboundeRefresh = debounce(refreshAllThings, 2000);
7478
// FIXME: if there is changes in two files, we will lost the first chagne
75-
const debounceUpToDownUpdateTestItems = debounce(updateModelByChangeOfFile, 500);
79+
const debounceHandleFileChangeCore = debounce(handleFileChangeCore, 2000);
7680

7781
export async function refreshAllThings() {
7882
if (!isInitilized) return;
@@ -133,10 +137,14 @@ async function handleFileCreate(uri: vscode.Uri) {
133137
updateTestItemsByModel();
134138
}
135139

140+
async function handleFileChangeCore(uri: vscode.Uri) {
141+
await updateModelByChangeOfFile(uri);
142+
updateTestItemsByModel();
143+
}
144+
136145
async function handleFileChange(uri: vscode.Uri) {
137146
console.log(`handleFileChange triggered for ${uri}`);
138-
await debounceUpToDownUpdateTestItems(uri);
139-
updateTestItemsByModel();
147+
await debounceHandleFileChangeCore(uri);
140148
}
141149

142150
async function handleFileDelete(uri: vscode.Uri) {
@@ -148,24 +156,43 @@ async function handleFileDelete(uri: vscode.Uri) {
148156
function updateTestItemsByModel() {
149157
testController!.items.replace([]);
150158
const rootTestItems = VscodeTestTreeBuilder.build();
151-
// const mockItem = testController!.createTestItem('123', '456');
152-
// testController!.items.add(mockItem);
153159
testController!.items.replace(rootTestItems);
154160
}
155161

162+
async function getNormalizedTestRunnablesInFile(uri: vscode.Uri) {
163+
const rawRunables = await RaApiHelper.getTestRunnablesInFile(uri);
164+
165+
assert(!!rawRunables);
166+
167+
const runnables = rawRunables.map(it => new RunnableFacde(it));
168+
169+
// User might copy and past test, and then there might be same name test or test module
170+
// Although it's wrong, we need to tollerate it.
171+
// We only choose the first one.
172+
return uniqueRunnables(runnables);
173+
174+
function uniqueRunnables(runnables: RunnableFacde[]) {
175+
const map = new Map<string, RunnableFacde>();
176+
runnables.forEach(runnable => {
177+
const key = `${runnable.workspaceRoot}|${runnable.packageName}|${runnable.targetKind}|${runnable.targetName}|${runnable.origin.label}`;
178+
if (!map.has(key)) {
179+
map.set(key,runnable);
180+
}
181+
});
182+
return Array.from(map.values());
183+
}
184+
}
185+
156186
async function updateModelByChangeOfFile(uri: vscode.Uri) {
157-
const testInfos = await RaApiHelper.getTestRunnablesInFile(uri);
187+
const runnables = await getNormalizedTestRunnablesInFile(uri);
158188

159189
// Maybe from some to none
160190
// need to recursively clean the parent, until there is at least one test cases.
161-
if (testInfos === null || testInfos.length === 0) {
191+
if (runnables.length === 0) {
162192
testModelTree.removeTestItemsRecusivelyByUri(uri);
163193
return;
164194
}
165195

166-
// Ensure parent is created in the test-model-tree
167-
const runnables = testInfos.map(it => new RunnableFacde(it));
168-
169196
const testModuelRunnables = runnables.filter(it =>
170197
it.testKind === NodeKind.TestModule)
171198
.sort(RunnableFacde.sortByLabel);
@@ -175,6 +202,7 @@ async function updateModelByChangeOfFile(uri: vscode.Uri) {
175202

176203
assert(testModuelRunnables.length + testItemRunnables.length === runnables.length);
177204

205+
// FIXME: should be file test modules
178206
const rootTestModuleRunnbale = testModuelRunnables[0];
179207

180208
// Now, we know the root test module
@@ -231,13 +259,13 @@ async function updateModelByChangeOfFile(uri: vscode.Uri) {
231259
// but this is overkill, we could reuse the test modules
232260
// if definition are same(and not in the same file)
233261
if (nearestNode.kind === NodeKind.TestModule
234-
&& nearestNode.declarationInfo.uri.toString() === rootTestModuleRunnbale.uri.toString()
262+
&& nearestNode.definitionUri.toString() === rootTestModuleRunnbale.uri.toString()
235263
&& nearestNode.name === rootTestModuleRunnbale.testOrSuiteName) {
236264
nearestNode.testChildren.clear();
237265
}
238266

239267
// collect nodes which children need to be fetched
240-
const nodeNeededFetched = FalsyLeavesCollector.collect();
268+
const nodeNeededFetched = FalsyLeavesCollector.collect(nearestNode);
241269

242270
// fetch the children for nodes
243271
await fetchChildrenForFalsyLeaves(nodeNeededFetched);
@@ -258,11 +286,8 @@ async function fetchChildrenForTestModuleNode(testModuleNode: TestModuleNode) {
258286
, "if the test module is not a declaration module, it must be the root module of some target node");
259287

260288
const definitionUri = testModuleNode.definitionUri;
261-
const rawRunnables = await RaApiHelper.getTestRunnablesInFile(definitionUri);
262-
263-
assert(!!rawRunnables);
264289

265-
const runnables = rawRunnables.map(it => new RunnableFacde(it));
290+
const runnables = await getNormalizedTestRunnablesInFile(definitionUri);
266291

267292
await updateModelByRunnables(testModuleNode, runnables);
268293
}
@@ -287,7 +312,8 @@ async function updateModelByRunnables(parentNode: TestModuleNode, runnables: Run
287312
// which means, when find the test item in test explorer, will refirect to declaration rather than the file
288313

289314
// Handle testRunnables and test modules which have items, which are in the same test file
290-
addTestModuleWithItemsRunnablesToTestModule(parentNode, [...withItemsModuleRunnables, ...testRunnables]);
315+
addTestModuleWithItemsRunnablesToTestModule(parentNode, withItemsModuleRunnables);
316+
addTestModuleWithItemsRunnablesToTestModule(parentNode, testRunnables);
291317

292318
// Handle declarationModules
293319
// TODO: maybe concurrent?
@@ -305,7 +331,7 @@ async function addAndFetchDeclarationModuleRunnableToTestModule(parentNode: Test
305331
parentNode,
306332
declarationModuleRunnable.testOrSuiteName,
307333
declarationModuleRunnable.toTestLocation(),
308-
vscode.Uri.file(definition.targetUri));
334+
vscode.Uri.parse(definition.targetUri));
309335
parentNode.testChildren.add(testModule);
310336

311337
// Fetch and update their definitions
@@ -320,7 +346,7 @@ function addTestModuleWithItemsRunnablesToTestModule(parentNode: TestModuleNode,
320346
const parentNode = testModelTree.findNearestNodeByRunnable(runnable);
321347
assert(parentNode.kind === NodeKind.TestModule, "Runable should be inserted into TestModule/Test, we create mock runnable for target/workspace node");
322348
if (!parentNode.isRootTestModule()) {
323-
assert(parentNode.name === runnable.testPaths[runnable.testPaths.length - 1]);
349+
assert(parentNode.name === runnable.testPaths[runnable.testPaths.length - 2]);
324350
}
325351

326352
switch (runnable.testKind) {

editors/code/src/test_explorer/test_model_tree.ts

Lines changed: 3 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -168,7 +168,7 @@ export class WorkspacesRoot {
168168

169169
for (let index = 0; index < testPaths.length; index += 1) {
170170
const testModuleNmae = testPaths[index];
171-
const targetKind = index !== testPaths.length - 1 ? testLevel : NodeKind.TestModule;
171+
const targetKind = index === testPaths.length - 1 ? testLevel : NodeKind.TestModule;
172172

173173
const candidate = Array.from(testModuleNode.testChildren).find((t) =>
174174
t.kind === targetKind &&
@@ -344,7 +344,7 @@ export class TargetNode implements Node {
344344

345345
static from(targetMetadata: CargoTargetMetadata, parent: CargoPackageNode): TargetNode | undefined {
346346
const targetKind = TargetKind.from(targetMetadata.kind);
347-
if (!targetKind) return undefined;
347+
if (targetKind === undefined) return undefined;
348348

349349
const res = new TargetNode(parent, targetKind, targetMetadata.name, targetMetadata.src_path);
350350
return res;
@@ -362,7 +362,7 @@ export class TargetNode implements Node {
362362
uri: this.srcPath,
363363
range: new vscode.Range(0, 0, 0, 0),
364364
},
365-
this.srcPath,);
365+
this.srcPath);
366366
}
367367
}
368368

0 commit comments

Comments
 (0)