Skip to content

Commit 61a0a39

Browse files
author
Kartik Raj
committed
Fix code lens
1 parent 6f5c3c9 commit 61a0a39

File tree

7 files changed

+287
-12
lines changed

7 files changed

+287
-12
lines changed

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: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -58,7 +58,7 @@ export class TestDiscoveredTestParser implements ITestDiscoveredTestParser {
5858
* @param {Tests} tests
5959
* @memberof TestsDiscovery
6060
*/
61-
protected buildChildren(rootFolder: TestFolder, parent: TestDataItem, discoveredTests: DiscoveredTests, tests: Tests) {
61+
public buildChildren(rootFolder: TestFolder, parent: TestDataItem, discoveredTests: DiscoveredTests, tests: Tests) {
6262
const parentType = getTestType(parent);
6363
switch (parentType) {
6464
case TestType.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+
});
Lines changed: 102 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,102 @@
1+
// Copyright (c) Microsoft Corporation. All rights reserved.
2+
// Licensed under the MIT License.
3+
4+
'use strict';
5+
6+
import * as assert from 'assert';
7+
import * as sinon from 'sinon';
8+
import * as typemoq from 'typemoq';
9+
import { Uri } from 'vscode';
10+
import { IWorkspaceService } from '../../../../client/common/application/types';
11+
import { TestDiscoveredTestParser } from '../../../../client/testing/common/services/discoveredTestParser';
12+
import { Tests } from '../../../../client/testing/common/types';
13+
14+
// tslint:disable:no-any max-func-body-length
15+
suite('Services - Discovered test parser', () => {
16+
let workspaceService: typemoq.IMock<IWorkspaceService>;
17+
let parser: TestDiscoveredTestParser;
18+
setup(() => {
19+
workspaceService = typemoq.Mock.ofType<IWorkspaceService>();
20+
});
21+
22+
teardown(() => {
23+
sinon.restore();
24+
});
25+
26+
test('Parse returns empty tests if resource does not belong to workspace', () => {
27+
// That is, getWorkspaceFolder() returns undefined.
28+
const expectedTests: Tests = {
29+
rootTestFolders: [],
30+
summary: { errors: 0, failures: 0, passed: 0, skipped: 0 },
31+
testFiles: [],
32+
testFolders: [],
33+
testFunctions: [],
34+
testSuites: []
35+
};
36+
const discoveredTests = [{
37+
root: 'path/to/testDataRoot'
38+
}];
39+
const buildChildren = sinon.stub(TestDiscoveredTestParser.prototype, 'buildChildren');
40+
buildChildren.callsFake(() => undefined);
41+
workspaceService
42+
.setup(w => w.getWorkspaceFolder(typemoq.It.isAny()))
43+
.returns(() => undefined)
44+
.verifiable(typemoq.Times.once());
45+
parser = new TestDiscoveredTestParser(workspaceService.object);
46+
const result = parser.parse(Uri.file('path/to/resource'), discoveredTests as any);
47+
assert.ok(buildChildren.notCalled);
48+
assert.deepEqual(expectedTests, result);
49+
workspaceService.verifyAll();
50+
});
51+
52+
test('Parse returns expected tests otherwise', () => {
53+
const discoveredTests = [
54+
{
55+
root: 'path/to/testDataRoot1',
56+
rootid: 'rootId1'
57+
},
58+
{
59+
root: 'path/to/testDataRoot2',
60+
rootid: 'rootId2'
61+
}
62+
];
63+
const workspaceUri = Uri.file('path/to/workspace');
64+
const workspace = { uri: workspaceUri };
65+
const expectedTests: Tests = {
66+
rootTestFolders: [
67+
{
68+
name: 'path/to/testDataRoot1', folders: [], time: 0,
69+
testFiles: [], resource: workspaceUri, nameToRun: 'rootId1'
70+
},
71+
{
72+
name: 'path/to/testDataRoot2', folders: [], time: 0,
73+
testFiles: [], resource: workspaceUri, nameToRun: 'rootId2'
74+
}
75+
],
76+
summary: { errors: 0, failures: 0, passed: 0, skipped: 0 },
77+
testFiles: [],
78+
testFolders: [
79+
{
80+
name: 'path/to/testDataRoot1', folders: [], time: 0,
81+
testFiles: [], resource: workspaceUri, nameToRun: 'rootId1'
82+
},
83+
{
84+
name: 'path/to/testDataRoot2', folders: [], time: 0,
85+
testFiles: [], resource: workspaceUri, nameToRun: 'rootId2'
86+
}
87+
],
88+
testFunctions: [],
89+
testSuites: []
90+
};
91+
const buildChildren = sinon.stub(TestDiscoveredTestParser.prototype, 'buildChildren');
92+
buildChildren.callsFake(() => undefined);
93+
workspaceService
94+
.setup(w => w.getWorkspaceFolder(typemoq.It.isAny()))
95+
.returns(() => workspace as any)
96+
.verifiable(typemoq.Times.once());
97+
parser = new TestDiscoveredTestParser(workspaceService.object);
98+
const result = parser.parse(workspaceUri, discoveredTests as any);
99+
assert.ok(buildChildren.calledTwice);
100+
assert.deepEqual(expectedTests, result);
101+
});
102+
});

0 commit comments

Comments
 (0)