Skip to content

Commit a6330b1

Browse files
author
Kartik Raj
authored
Fix code lens bug (#6755)
* Fix code lens bug * Added tests for code lenses test file * Corrected functionality * Added tests for test parser * Code reviews * Minor comments
1 parent fc9d290 commit a6330b1

8 files changed

Lines changed: 355 additions & 15 deletions

File tree

news/2 Fixes/6303.md

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1 @@
1+
Fix code lenses shown for pytest

src/client/testing/codeLenses/main.ts

Lines changed: 4 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1,15 +1,17 @@
11
import * as vscode from 'vscode';
2+
import { IServiceContainer } from '../../../client/ioc/types';
23
import { PYTHON } from '../../common/constants';
34
import { ITestCollectionStorageService } from '../common/types';
45
import { TestFileCodeLensProvider } from './testFiles';
56

67
export function activateCodeLenses(
78
onDidChange: vscode.EventEmitter<void>,
89
symbolProvider: vscode.DocumentSymbolProvider,
9-
testCollectionStorage: ITestCollectionStorageService
10+
testCollectionStorage: ITestCollectionStorageService,
11+
serviceContainer: IServiceContainer
1012
): vscode.Disposable {
1113
const disposables: vscode.Disposable[] = [];
12-
const codeLensProvider = new TestFileCodeLensProvider(onDidChange, symbolProvider, testCollectionStorage);
14+
const codeLensProvider = new TestFileCodeLensProvider(onDidChange, symbolProvider, testCollectionStorage, serviceContainer);
1315
disposables.push(vscode.languages.registerCodeLensProvider(PYTHON, codeLensProvider));
1416

1517
return {

src/client/testing/codeLenses/testFiles.ts

Lines changed: 20 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -2,7 +2,10 @@
22

33
// tslint:disable:no-object-literal-type-assertion
44

5-
import { CancellationToken, CancellationTokenSource, CodeLens, CodeLensProvider, DocumentSymbolProvider, Event, EventEmitter, Position, Range, SymbolInformation, SymbolKind, TextDocument, Uri, workspace } from 'vscode';
5+
import { CancellationToken, CancellationTokenSource, CodeLens, CodeLensProvider, DocumentSymbolProvider, Event, EventEmitter, Position, Range, SymbolInformation, SymbolKind, TextDocument, Uri } from 'vscode';
6+
import { IWorkspaceService } from '../../../client/common/application/types';
7+
import { IFileSystem } from '../../../client/common/platform/types';
8+
import { IServiceContainer } from '../../../client/ioc/types';
69
import * as constants from '../../common/constants';
710
import { CommandSource } from '../common/constants';
811
import { ITestCollectionStorageService, TestFile, TestFunction, TestStatus, TestsToRun, TestSuite } from '../common/types';
@@ -13,18 +16,23 @@ type FunctionsAndSuites = {
1316
};
1417

1518
export class TestFileCodeLensProvider implements CodeLensProvider {
19+
private workspaceService: IWorkspaceService;
20+
private fileSystem: IFileSystem;
1621
// tslint:disable-next-line:variable-name
1722
constructor(private _onDidChange: EventEmitter<void>,
1823
private symbolProvider: DocumentSymbolProvider,
19-
private testCollectionStorage: ITestCollectionStorageService) {
24+
private testCollectionStorage: ITestCollectionStorageService,
25+
serviceContainer: IServiceContainer) {
26+
this.workspaceService = serviceContainer.get<IWorkspaceService>(IWorkspaceService);
27+
this.fileSystem = serviceContainer.get<IFileSystem>(IFileSystem);
2028
}
2129

2230
get onDidChangeCodeLenses(): Event<void> {
2331
return this._onDidChange.event;
2432
}
2533

2634
public async provideCodeLenses(document: TextDocument, token: CancellationToken) {
27-
const wkspace = workspace.getWorkspaceFolder(document.uri);
35+
const wkspace = this.workspaceService.getWorkspaceFolder(document.uri);
2836
if (!wkspace) {
2937
return [];
3038
}
@@ -52,16 +60,20 @@ export class TestFileCodeLensProvider implements CodeLensProvider {
5260
return Promise.resolve(codeLens);
5361
}
5462

55-
private async getCodeLenses(document: TextDocument, token: CancellationToken, symbolProvider: DocumentSymbolProvider) {
56-
const wkspace = workspace.getWorkspaceFolder(document.uri);
63+
public getTestFileWhichNeedsCodeLens(document: TextDocument): TestFile | undefined {
64+
const wkspace = this.workspaceService.getWorkspaceFolder(document.uri);
5765
if (!wkspace) {
58-
return [];
66+
return;
5967
}
6068
const tests = this.testCollectionStorage.getTests(wkspace.uri);
6169
if (!tests) {
62-
return [];
70+
return;
6371
}
64-
const file = tests.testFiles.find(item => item.fullPath === document.uri.fsPath);
72+
return tests.testFiles.find(item => this.fileSystem.arePathsSame(item.fullPath, document.uri.fsPath));
73+
}
74+
75+
private async getCodeLenses(document: TextDocument, token: CancellationToken, symbolProvider: DocumentSymbolProvider) {
76+
const file = this.getTestFileWhichNeedsCodeLens(document);
6577
if (!file) {
6678
return [];
6779
}

src/client/testing/common/services/discoveredTestParser.ts

Lines changed: 24 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -8,14 +8,18 @@ import * as path from 'path';
88
import { Uri } from 'vscode';
99
import { IWorkspaceService } from '../../../common/application/types';
1010
import { traceError } from '../../../common/logger';
11+
import { IFileSystem } from '../../../common/platform/types';
1112
import { TestDataItem } from '../../types';
1213
import { getParentFile, getParentSuite, getTestType } from '../testUtils';
1314
import { FlattenedTestFunction, FlattenedTestSuite, SubtestParent, TestFile, TestFolder, TestFunction, Tests, TestSuite, TestType } from '../types';
1415
import { DiscoveredTests, ITestDiscoveredTestParser, TestContainer, TestItem } from './types';
1516

1617
@injectable()
1718
export class TestDiscoveredTestParser implements ITestDiscoveredTestParser {
18-
constructor(@inject(IWorkspaceService) private readonly workspaceService: IWorkspaceService) { }
19+
constructor(
20+
@inject(IWorkspaceService) private readonly workspaceService: IWorkspaceService,
21+
@inject(IFileSystem) private readonly fileSystem: IFileSystem
22+
) { }
1923
public parse(resource: Uri, discoveredTests: DiscoveredTests[]): Tests {
2024
const tests: Tests = {
2125
rootTestFolders: [],
@@ -34,8 +38,14 @@ export class TestDiscoveredTestParser implements ITestDiscoveredTestParser {
3438

3539
// If the root is the workspace folder, then ignore that.
3640
for (const data of discoveredTests) {
41+
// For now we only check the current workspace.
42+
const root = this.findRoot(data.root, [workspace.uri.fsPath]);
43+
if (!root) {
44+
// For now we only support tests from the workspace.
45+
continue;
46+
}
3747
const rootFolder = {
38-
name: data.root, folders: [], time: 0,
48+
name: root, folders: [], time: 0,
3949
testFiles: [], resource: resource, nameToRun: data.rootid
4050
};
4151
tests.rootTestFolders.push(rootFolder);
@@ -58,7 +68,7 @@ export class TestDiscoveredTestParser implements ITestDiscoveredTestParser {
5868
* @param {Tests} tests
5969
* @memberof TestsDiscovery
6070
*/
61-
protected buildChildren(rootFolder: TestFolder, parent: TestDataItem, discoveredTests: DiscoveredTests, tests: Tests) {
71+
public buildChildren(rootFolder: TestFolder, parent: TestDataItem, discoveredTests: DiscoveredTests, tests: Tests) {
6272
const parentType = getTestType(parent);
6373
switch (parentType) {
6474
case TestType.testFolder: {
@@ -193,6 +203,17 @@ export class TestDiscoveredTestParser implements ITestDiscoveredTestParser {
193203
parent.functions.push(...functions);
194204
tests.testFunctions.push(...functions.map(func => createFlattenedParameterizedFunction(tests, func, parent)));
195205
}
206+
207+
/**
208+
* Returns the vscode recognized file paths which matches the pytest data test root
209+
*/
210+
private findRoot(raw: string, roots: string[]): string | undefined {
211+
for (const root of roots) {
212+
if (this.fileSystem.arePathsSame(raw, root)) {
213+
return root;
214+
}
215+
}
216+
}
196217
}
197218

198219
function createTestFolder(root: TestFolder, item: TestContainer): TestFolder {

src/client/testing/main.ts

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -324,7 +324,7 @@ export class UnitTestManagementService implements ITestManagementService, Dispos
324324
}
325325
});
326326
this.disposableRegistry.push(handler);
327-
this.disposableRegistry.push(activateCodeLenses(event, symbolProvider, testCollectionStorage));
327+
this.disposableRegistry.push(activateCodeLenses(event, symbolProvider, testCollectionStorage, this.serviceContainer));
328328
}
329329

330330
@captureTelemetry(EventName.UNITTEST_CONFIGURE, undefined, false)
Lines changed: 158 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,158 @@
1+
// Copyright (c) Microsoft Corporation. All rights reserved.
2+
// Licensed under the MIT License.
3+
4+
'use strict';
5+
6+
// tslint:disable:no-any
7+
8+
import { assert, expect } from 'chai';
9+
import { mock } from 'ts-mockito';
10+
import * as typemoq from 'typemoq';
11+
import { DocumentSymbolProvider, EventEmitter, Uri } from 'vscode';
12+
import { IWorkspaceService } from '../../../client/common/application/types';
13+
import { IFileSystem } from '../../../client/common/platform/types';
14+
import { IServiceContainer } from '../../../client/ioc/types';
15+
import { LanguageServerSymbolProvider } from '../../../client/providers/symbolProvider';
16+
import { TestFileCodeLensProvider } from '../../../client/testing/codeLenses/testFiles';
17+
import { ITestCollectionStorageService } from '../../../client/testing/common/types';
18+
19+
// tslint:disable-next-line: max-func-body-length
20+
suite('Code lenses - Test files', () => {
21+
let testCollectionStorage: typemoq.IMock<ITestCollectionStorageService>;
22+
let workspaceService: typemoq.IMock<IWorkspaceService>;
23+
let fileSystem: typemoq.IMock<IFileSystem>;
24+
let serviceContainer: typemoq.IMock<IServiceContainer>;
25+
let symbolProvider: DocumentSymbolProvider;
26+
let onDidChange: EventEmitter<void>;
27+
let codeLensProvider: TestFileCodeLensProvider;
28+
setup(() => {
29+
workspaceService = typemoq.Mock.ofType<IWorkspaceService>();
30+
fileSystem = typemoq.Mock.ofType<IFileSystem>();
31+
testCollectionStorage = typemoq.Mock.ofType<ITestCollectionStorageService>();
32+
serviceContainer = typemoq.Mock.ofType<IServiceContainer>();
33+
symbolProvider = mock(LanguageServerSymbolProvider);
34+
onDidChange = new EventEmitter<void>();
35+
serviceContainer
36+
.setup(c => c.get(typemoq.It.isValue(IWorkspaceService)))
37+
.returns(() => workspaceService.object);
38+
serviceContainer
39+
.setup(c => c.get(typemoq.It.isValue(IFileSystem)))
40+
.returns(() => fileSystem.object);
41+
codeLensProvider = new TestFileCodeLensProvider(onDidChange, symbolProvider, testCollectionStorage.object, serviceContainer.object);
42+
});
43+
44+
teardown(() => {
45+
onDidChange.dispose();
46+
});
47+
48+
test('Function getTestFileWhichNeedsCodeLens() returns `undefined` if there are no workspace corresponding to document', async () => {
49+
const document = {
50+
uri: Uri.file('path/to/document')
51+
};
52+
workspaceService
53+
.setup(w => w.getWorkspaceFolder(document.uri))
54+
.returns(() => undefined)
55+
.verifiable(typemoq.Times.once());
56+
testCollectionStorage
57+
.setup(w => w.getTests(typemoq.It.isAny()))
58+
.returns(() => undefined)
59+
.verifiable(typemoq.Times.never());
60+
const files = codeLensProvider.getTestFileWhichNeedsCodeLens(document as any);
61+
expect(files).to.equal(undefined, 'No files should be returned');
62+
workspaceService.verifyAll();
63+
testCollectionStorage.verifyAll();
64+
});
65+
66+
test('Function getTestFileWhichNeedsCodeLens() returns `undefined` if test storage is empty', async () => {
67+
const document = {
68+
uri: Uri.file('path/to/document')
69+
};
70+
const workspaceUri = Uri.file('path/to/workspace');
71+
const workspace = { uri: workspaceUri };
72+
workspaceService
73+
.setup(w => w.getWorkspaceFolder(document.uri))
74+
.returns(() => workspace as any)
75+
.verifiable(typemoq.Times.once());
76+
testCollectionStorage
77+
.setup(w => w.getTests(workspaceUri))
78+
.returns(() => undefined)
79+
.verifiable(typemoq.Times.once());
80+
const files = codeLensProvider.getTestFileWhichNeedsCodeLens(document as any);
81+
expect(files).to.equal(undefined, 'No files should be returned');
82+
workspaceService.verifyAll();
83+
testCollectionStorage.verifyAll();
84+
});
85+
86+
test('Function getTestFileWhichNeedsCodeLens() returns `undefined` if tests returned from storage does not contain document', async () => {
87+
const document = {
88+
uri: Uri.file('path/to/document5')
89+
};
90+
const workspaceUri = Uri.file('path/to/workspace');
91+
const workspace = { uri: workspaceUri };
92+
const tests = {
93+
testFiles: [
94+
{
95+
fullPath: 'path/to/document1'
96+
},
97+
{
98+
fullPath: 'path/to/document2'
99+
}
100+
]
101+
};
102+
workspaceService
103+
.setup(w => w.getWorkspaceFolder(document.uri))
104+
.returns(() => workspace as any)
105+
.verifiable(typemoq.Times.once());
106+
testCollectionStorage
107+
.setup(w => w.getTests(workspaceUri))
108+
.returns(() => tests as any)
109+
.verifiable(typemoq.Times.once());
110+
fileSystem
111+
.setup(f => f.arePathsSame('path/to/document1', 'path/to/document5'))
112+
.returns(() => false);
113+
fileSystem
114+
.setup(f => f.arePathsSame('path/to/document2', 'path/to/document5'))
115+
.returns(() => false);
116+
const files = codeLensProvider.getTestFileWhichNeedsCodeLens(document as any);
117+
expect(files).to.equal(undefined, 'No files should be returned');
118+
workspaceService.verifyAll();
119+
testCollectionStorage.verifyAll();
120+
});
121+
122+
test('Function getTestFileWhichNeedsCodeLens() returns test file if tests returned from storage contains document', async () => {
123+
const document = {
124+
uri: Uri.file('path/to/document2')
125+
};
126+
const workspaceUri = Uri.file('path/to/workspace');
127+
const workspace = { uri: workspaceUri };
128+
const testFile2 = {
129+
fullPath: 'path/to/document2'
130+
};
131+
const tests = {
132+
testFiles: [
133+
{
134+
fullPath: 'path/to/document1'
135+
},
136+
testFile2
137+
]
138+
};
139+
workspaceService
140+
.setup(w => w.getWorkspaceFolder(document.uri))
141+
.returns(() => workspace as any)
142+
.verifiable(typemoq.Times.once());
143+
testCollectionStorage
144+
.setup(w => w.getTests(workspaceUri))
145+
.returns(() => tests as any)
146+
.verifiable(typemoq.Times.once());
147+
fileSystem
148+
.setup(f => f.arePathsSame('path/to/document1', 'path/to/document2'))
149+
.returns(() => false);
150+
fileSystem
151+
.setup(f => f.arePathsSame('path/to/document2', 'path/to/document2'))
152+
.returns(() => true);
153+
const files = codeLensProvider.getTestFileWhichNeedsCodeLens(document as any);
154+
assert.deepEqual(files, testFile2 as any);
155+
workspaceService.verifyAll();
156+
testCollectionStorage.verifyAll();
157+
});
158+
});

0 commit comments

Comments
 (0)