diff --git a/src/client/testing/codeLenses/main.ts b/src/client/testing/codeLenses/main.ts deleted file mode 100644 index 9c2a7e688ee9..000000000000 --- a/src/client/testing/codeLenses/main.ts +++ /dev/null @@ -1,27 +0,0 @@ -import * as vscode from 'vscode'; -import { IServiceContainer } from '../../../client/ioc/types'; -import { PYTHON } from '../../common/constants'; -import { ITestCollectionStorageService } from '../common/types'; -import { TestFileCodeLensProvider } from './testFiles'; - -export function activateCodeLenses( - onDidChange: vscode.EventEmitter, - symbolProvider: vscode.DocumentSymbolProvider, - testCollectionStorage: ITestCollectionStorageService, - serviceContainer: IServiceContainer, -): vscode.Disposable { - const disposables: vscode.Disposable[] = []; - const codeLensProvider = new TestFileCodeLensProvider( - onDidChange, - symbolProvider, - testCollectionStorage, - serviceContainer, - ); - disposables.push(vscode.languages.registerCodeLensProvider(PYTHON, codeLensProvider)); - - return { - dispose: () => { - disposables.forEach((d) => d.dispose()); - }, - }; -} diff --git a/src/client/testing/codeLenses/testFiles.ts b/src/client/testing/codeLenses/testFiles.ts deleted file mode 100644 index 16cb2f5ef8de..000000000000 --- a/src/client/testing/codeLenses/testFiles.ts +++ /dev/null @@ -1,321 +0,0 @@ -'use strict'; - -import { - CancellationToken, - CancellationTokenSource, - CodeLens, - CodeLensProvider, - DocumentSymbolProvider, - Event, - EventEmitter, - Position, - Range, - SymbolInformation, - SymbolKind, - TextDocument, - Uri, -} from 'vscode'; -import { IWorkspaceService } from '../../../client/common/application/types'; -import { IFileSystem } from '../../../client/common/platform/types'; -import { IServiceContainer } from '../../../client/ioc/types'; -import * as constants from '../../common/constants'; -import { - ITestCollectionStorageService, - TestFile, - TestFunction, - TestStatus, - TestsToRun, - TestSuite, -} from '../common/types'; - -type FunctionsAndSuites = { - functions: TestFunction[]; - suites: TestSuite[]; -}; - -export class TestFileCodeLensProvider implements CodeLensProvider { - private workspaceService: IWorkspaceService; - private fileSystem: IFileSystem; - - constructor( - private _onDidChange: EventEmitter, - private symbolProvider: DocumentSymbolProvider, - private testCollectionStorage: ITestCollectionStorageService, - serviceContainer: IServiceContainer, - ) { - this.workspaceService = serviceContainer.get(IWorkspaceService); - this.fileSystem = serviceContainer.get(IFileSystem); - } - - get onDidChangeCodeLenses(): Event { - return this._onDidChange.event; - } - - public async provideCodeLenses(document: TextDocument, token: CancellationToken) { - const wkspace = this.workspaceService.getWorkspaceFolder(document.uri); - if (!wkspace) { - return []; - } - const testItems = this.testCollectionStorage.getTests(wkspace.uri); - if (!testItems || testItems.testFiles.length === 0 || testItems.testFunctions.length === 0) { - return []; - } - - const cancelTokenSrc = new CancellationTokenSource(); - token.onCancellationRequested(() => { - cancelTokenSrc.cancel(); - }); - - // Strop trying to build the code lenses if unable to get a list of - // symbols in this file afrer x time. - setTimeout(() => { - if (!cancelTokenSrc.token.isCancellationRequested) { - cancelTokenSrc.cancel(); - } - }, constants.Delays.MaxUnitTestCodeLensDelay); - - return this.getCodeLenses(document, cancelTokenSrc.token, this.symbolProvider); - } - - public resolveCodeLens(codeLens: CodeLens, _token: CancellationToken): CodeLens | Thenable { - codeLens.command = { command: 'python.runtests', title: 'Test' }; - return Promise.resolve(codeLens); - } - - public getTestFileWhichNeedsCodeLens(document: TextDocument): TestFile | undefined { - const wkspace = this.workspaceService.getWorkspaceFolder(document.uri); - if (!wkspace) { - return; - } - const tests = this.testCollectionStorage.getTests(wkspace.uri); - if (!tests) { - return; - } - return tests.testFiles.find((item) => this.fileSystem.arePathsSame(item.fullPath, document.uri.fsPath)); - } - - private async getCodeLenses( - document: TextDocument, - token: CancellationToken, - symbolProvider: DocumentSymbolProvider, - ) { - const file = this.getTestFileWhichNeedsCodeLens(document); - if (!file) { - return []; - } - const allFuncsAndSuites = getAllTestSuitesAndFunctionsPerFile(file); - - try { - const symbols = (await symbolProvider.provideDocumentSymbols(document, token)) as SymbolInformation[]; - if (!symbols) { - return []; - } - return symbols - .filter( - (symbol) => - symbol.kind === SymbolKind.Function || - symbol.kind === SymbolKind.Method || - symbol.kind === SymbolKind.Class, - ) - .map((symbol) => { - // This is crucial, if the start and end columns are the same then vscode bugs out - // whenever you edit a line (start scrolling magically). - const range = new Range( - symbol.location.range.start, - new Position(symbol.location.range.end.line, symbol.location.range.end.character + 1), - ); - - return this.getCodeLens( - document.uri, - allFuncsAndSuites, - range, - symbol.name, - symbol.kind, - symbol.containerName, - ); - }) - .reduce((previous, current) => previous.concat(current), []) - .filter((codeLens) => codeLens !== null); - } catch (reason) { - if (token.isCancellationRequested) { - return []; - } - return Promise.reject(reason); - } - } - - private getCodeLens( - file: Uri, - allFuncsAndSuites: FunctionsAndSuites, - range: Range, - symbolName: string, - symbolKind: SymbolKind, - symbolContainer: string, - ): CodeLens[] { - switch (symbolKind) { - case SymbolKind.Function: - case SymbolKind.Method: { - return getFunctionCodeLens(file, allFuncsAndSuites, symbolName, range, symbolContainer); - } - case SymbolKind.Class: { - const cls = allFuncsAndSuites.suites.find((item) => item.name === symbolName); - if (!cls) { - return []; - } - return [ - new CodeLens(range, { - title: getTestStatusIcon(cls.status) + constants.Text.CodeLensRunUnitTest, - command: constants.Commands.Tests_Run, - arguments: [ - undefined, - constants.CommandSource.codelens, - file, - { testSuite: [cls] }, - ], - }), - new CodeLens(range, { - title: getTestStatusIcon(cls.status) + constants.Text.CodeLensDebugUnitTest, - command: constants.Commands.Tests_Debug, - arguments: [ - undefined, - constants.CommandSource.codelens, - file, - { testSuite: [cls] }, - ], - }), - ]; - } - default: { - return []; - } - } - } -} - -function getTestStatusIcon(status?: TestStatus): string { - switch (status) { - case TestStatus.Pass: { - return `${constants.Octicons.Test_Pass} `; - } - case TestStatus.Error: { - return `${constants.Octicons.Test_Error} `; - } - case TestStatus.Fail: { - return `${constants.Octicons.Test_Fail} `; - } - case TestStatus.Skipped: { - return `${constants.Octicons.Test_Skip} `; - } - default: { - return ''; - } - } -} - -function getTestStatusIcons(fns: TestFunction[]): string { - const statuses: string[] = []; - let count = fns.filter((fn) => fn.status === TestStatus.Pass).length; - if (count > 0) { - statuses.push(`${constants.Octicons.Test_Pass} ${count}`); - } - count = fns.filter((fn) => fn.status === TestStatus.Skipped).length; - if (count > 0) { - statuses.push(`${constants.Octicons.Test_Skip} ${count}`); - } - count = fns.filter((fn) => fn.status === TestStatus.Fail).length; - if (count > 0) { - statuses.push(`${constants.Octicons.Test_Fail} ${count}`); - } - count = fns.filter((fn) => fn.status === TestStatus.Error).length; - if (count > 0) { - statuses.push(`${constants.Octicons.Test_Error} ${count}`); - } - - return statuses.join(' '); -} -function getFunctionCodeLens( - file: Uri, - functionsAndSuites: FunctionsAndSuites, - symbolName: string, - range: Range, - symbolContainer: string, -): CodeLens[] { - let fn: TestFunction | undefined; - if (symbolContainer.length === 0) { - fn = functionsAndSuites.functions.find((func) => func.name === symbolName); - } else { - // Assume single levels for now. - functionsAndSuites.suites - .filter((s) => s.name === symbolContainer) - .forEach((s) => { - const f = s.functions.find((item) => item.name === symbolName); - if (f) { - fn = f; - } - }); - } - - if (fn) { - return [ - new CodeLens(range, { - title: getTestStatusIcon(fn.status) + constants.Text.CodeLensRunUnitTest, - command: constants.Commands.Tests_Run, - arguments: [undefined, constants.CommandSource.codelens, file, { testFunction: [fn] }], - }), - new CodeLens(range, { - title: getTestStatusIcon(fn.status) + constants.Text.CodeLensDebugUnitTest, - command: constants.Commands.Tests_Debug, - arguments: [undefined, constants.CommandSource.codelens, file, { testFunction: [fn] }], - }), - ]; - } - - // Ok, possible we're dealing with parameterized unit tests. - // If we have [ in the name, then this is a parameterized function. - const functions = functionsAndSuites.functions.filter( - (func) => func.name.startsWith(`${symbolName}[`) && func.name.endsWith(']'), - ); - if (functions.length === 0) { - return []; - } - - // Find all flattened functions. - return [ - new CodeLens(range, { - title: `${getTestStatusIcons(functions)} ${constants.Text.CodeLensRunUnitTest} (Multiple)`, - command: constants.Commands.Tests_Picker_UI, - arguments: [undefined, constants.CommandSource.codelens, file, functions], - }), - new CodeLens(range, { - title: `${getTestStatusIcons(functions)} ${constants.Text.CodeLensDebugUnitTest} (Multiple)`, - command: constants.Commands.Tests_Picker_UI_Debug, - arguments: [undefined, constants.CommandSource.codelens, file, functions], - }), - ]; -} - -function getAllTestSuitesAndFunctionsPerFile(testFile: TestFile): FunctionsAndSuites { - const all = { functions: [...testFile.functions], suites: [] as TestSuite[] }; - testFile.suites.forEach((suite) => { - all.suites.push(suite); - - const allChildItems = getAllTestSuitesAndFunctions(suite); - all.functions.push(...allChildItems.functions); - all.suites.push(...allChildItems.suites); - }); - return all; -} -function getAllTestSuitesAndFunctions(testSuite: TestSuite): FunctionsAndSuites { - const all: { functions: TestFunction[]; suites: TestSuite[] } = { functions: [], suites: [] }; - testSuite.functions.forEach((fn) => { - all.functions.push(fn); - }); - testSuite.suites.forEach((suite) => { - all.suites.push(suite); - - const allChildItems = getAllTestSuitesAndFunctions(suite); - all.functions.push(...allChildItems.functions); - all.suites.push(...allChildItems.suites); - }); - return all; -} diff --git a/src/client/testing/main.ts b/src/client/testing/main.ts index 965a81c5d713..4eb2bafc6c67 100644 --- a/src/client/testing/main.ts +++ b/src/client/testing/main.ts @@ -21,7 +21,6 @@ import { IInterpreterService } from '../interpreter/contracts'; import { IServiceContainer } from '../ioc/types'; import { EventName } from '../telemetry/constants'; import { captureTelemetry, sendTelemetryEvent } from '../telemetry/index'; -import { activateCodeLenses } from './codeLenses/main'; import { CANCELLATION_REASON } from './common/constants'; import { selectTestWorkspace } from './common/testUtils'; import { TestSettingsPropertyNames } from './configuration/types'; @@ -103,7 +102,7 @@ export class UnitTestManagementService implements ITestManagementService, Dispos public get onDidStatusChange(): Event { return this._onDidStatusChange.event; } - public async activate(symbolProvider: DocumentSymbolProvider): Promise { + public async activate(): Promise { if (this.activatedOnce) { return; } @@ -117,7 +116,6 @@ export class UnitTestManagementService implements ITestManagementService, Dispos this.autoDiscoverTests(undefined).catch((ex) => traceError('Failed to auto discover tests upon activation', ex), ); - await this.registerSymbolProvider(symbolProvider); } public async getTestManager( displayTestNotConfiguredMessage: boolean, @@ -432,23 +430,6 @@ export class UnitTestManagementService implements ITestManagementService, Dispos await promise; } - public async registerSymbolProvider(symbolProvider: DocumentSymbolProvider): Promise { - const testCollectionStorage = this.serviceContainer.get( - ITestCollectionStorageService, - ); - const event = new EventEmitter(); - this.disposableRegistry.push(event); - const handler = this._onDidStatusChange.event((e) => { - if (e.status !== TestStatus.Discovering && e.status !== TestStatus.Running) { - event.fire(); - } - }); - this.disposableRegistry.push(handler); - this.disposableRegistry.push( - activateCodeLenses(event, symbolProvider, testCollectionStorage, this.serviceContainer), - ); - } - @captureTelemetry(EventName.UNITTEST_CONFIGURE, undefined, false) public async configureTests(resource?: Uri) { let wkspace: Uri | undefined; diff --git a/src/test/testing/codeLenses/testFiles.unit.test.ts b/src/test/testing/codeLenses/testFiles.unit.test.ts deleted file mode 100644 index 5a42bac1b5aa..000000000000 --- a/src/test/testing/codeLenses/testFiles.unit.test.ts +++ /dev/null @@ -1,154 +0,0 @@ -// Copyright (c) Microsoft Corporation. All rights reserved. -// Licensed under the MIT License. - -'use strict'; - -import { assert, expect } from 'chai'; -import { mock } from 'ts-mockito'; -import * as typemoq from 'typemoq'; -import { DocumentSymbolProvider, EventEmitter, Uri } from 'vscode'; -import { IWorkspaceService } from '../../../client/common/application/types'; -import { IFileSystem } from '../../../client/common/platform/types'; -import { IServiceContainer } from '../../../client/ioc/types'; -import { LanguageServerSymbolProvider } from '../../../client/providers/symbolProvider'; -import { TestFileCodeLensProvider } from '../../../client/testing/codeLenses/testFiles'; -import { ITestCollectionStorageService } from '../../../client/testing/common/types'; - -suite('Code lenses - Test files', () => { - let testCollectionStorage: typemoq.IMock; - let workspaceService: typemoq.IMock; - let fileSystem: typemoq.IMock; - let serviceContainer: typemoq.IMock; - let symbolProvider: DocumentSymbolProvider; - let onDidChange: EventEmitter; - let codeLensProvider: TestFileCodeLensProvider; - setup(() => { - workspaceService = typemoq.Mock.ofType(); - fileSystem = typemoq.Mock.ofType(); - testCollectionStorage = typemoq.Mock.ofType(); - serviceContainer = typemoq.Mock.ofType(); - symbolProvider = mock(LanguageServerSymbolProvider); - onDidChange = new EventEmitter(); - serviceContainer - .setup((c) => c.get(typemoq.It.isValue(IWorkspaceService))) - .returns(() => workspaceService.object); - serviceContainer.setup((c) => c.get(typemoq.It.isValue(IFileSystem))).returns(() => fileSystem.object); - codeLensProvider = new TestFileCodeLensProvider( - onDidChange, - symbolProvider, - testCollectionStorage.object, - serviceContainer.object, - ); - }); - - teardown(() => { - onDidChange.dispose(); - }); - - test('Function getTestFileWhichNeedsCodeLens() returns `undefined` if there are no workspace corresponding to document', async () => { - const document = { - uri: Uri.file('path/to/document'), - }; - workspaceService - .setup((w) => w.getWorkspaceFolder(document.uri)) - .returns(() => undefined) - .verifiable(typemoq.Times.once()); - testCollectionStorage - .setup((w) => w.getTests(typemoq.It.isAny())) - .returns(() => undefined) - .verifiable(typemoq.Times.never()); - const files = codeLensProvider.getTestFileWhichNeedsCodeLens(document as any); - expect(files).to.equal(undefined, 'No files should be returned'); - workspaceService.verifyAll(); - testCollectionStorage.verifyAll(); - }); - - test('Function getTestFileWhichNeedsCodeLens() returns `undefined` if test storage is empty', async () => { - const document = { - uri: Uri.file('path/to/document'), - }; - const workspaceUri = Uri.file('path/to/workspace'); - const workspace = { uri: workspaceUri }; - workspaceService - .setup((w) => w.getWorkspaceFolder(document.uri)) - .returns(() => workspace as any) - .verifiable(typemoq.Times.once()); - testCollectionStorage - .setup((w) => w.getTests(workspaceUri)) - .returns(() => undefined) - .verifiable(typemoq.Times.once()); - const files = codeLensProvider.getTestFileWhichNeedsCodeLens(document as any); - expect(files).to.equal(undefined, 'No files should be returned'); - workspaceService.verifyAll(); - testCollectionStorage.verifyAll(); - }); - - test('Function getTestFileWhichNeedsCodeLens() returns `undefined` if tests returned from storage does not contain document', async () => { - const document = { - uri: Uri.file('path/to/document5'), - }; - const workspaceUri = Uri.file('path/to/workspace'); - const workspace = { uri: workspaceUri }; - const tests = { - testFiles: [ - { - fullPath: 'path/to/document1', - }, - { - fullPath: 'path/to/document2', - }, - ], - }; - workspaceService - .setup((w) => w.getWorkspaceFolder(document.uri)) - .returns(() => workspace as any) - .verifiable(typemoq.Times.once()); - testCollectionStorage - .setup((w) => w.getTests(workspaceUri)) - .returns(() => tests as any) - .verifiable(typemoq.Times.once()); - fileSystem.setup((f) => f.arePathsSame('path/to/document1', 'path/to/document5')).returns(() => false); - fileSystem.setup((f) => f.arePathsSame('path/to/document2', 'path/to/document5')).returns(() => false); - const files = codeLensProvider.getTestFileWhichNeedsCodeLens(document as any); - expect(files).to.equal(undefined, 'No files should be returned'); - workspaceService.verifyAll(); - testCollectionStorage.verifyAll(); - }); - - test('Function getTestFileWhichNeedsCodeLens() returns test file if tests returned from storage contains document', async () => { - const document = { - uri: Uri.file('path/to/document2'), - }; - const workspaceUri = Uri.file('path/to/workspace'); - const workspace = { uri: workspaceUri }; - const testFile2 = { - fullPath: Uri.file('path/to/document2').fsPath, - }; - const tests = { - testFiles: [ - { - fullPath: Uri.file('path/to/document1').fsPath, - }, - testFile2, - ], - }; - workspaceService - .setup((w) => w.getWorkspaceFolder(typemoq.It.isValue(document.uri))) - .returns(() => workspace as any) - .verifiable(typemoq.Times.once()); - testCollectionStorage - .setup((w) => w.getTests(typemoq.It.isValue(workspaceUri))) - .returns(() => tests as any) - .verifiable(typemoq.Times.once()); - fileSystem - .setup((f) => f.arePathsSame(Uri.file('/path/to/document1').fsPath, Uri.file('/path/to/document2').fsPath)) - .returns(() => false); - fileSystem - .setup((f) => f.arePathsSame(Uri.file('/path/to/document2').fsPath, Uri.file('/path/to/document2').fsPath)) - .returns(() => true); - const files = codeLensProvider.getTestFileWhichNeedsCodeLens(document as any); - assert.deepEqual(files, testFile2 as any); - workspaceService.verifyAll(); - testCollectionStorage.verifyAll(); - }); -}); diff --git a/src/test/testing/main.unit.test.ts b/src/test/testing/main.unit.test.ts index bef1c18e3358..821eb1a408b3 100644 --- a/src/test/testing/main.unit.test.ts +++ b/src/test/testing/main.unit.test.ts @@ -11,7 +11,6 @@ import { ICommandManager } from '../../client/common/application/types'; import { IDisposableRegistry } from '../../client/common/types'; import { ServiceContainer } from '../../client/ioc/container'; import { IServiceContainer } from '../../client/ioc/types'; -import { JediSymbolProvider } from '../../client/providers/symbolProvider'; import { UnitTestManagementService } from '../../client/testing/main'; suite('Unit Tests - ManagementService', () => { @@ -24,7 +23,6 @@ suite('Unit Tests - ManagementService', () => { serviceContainer = mock(ServiceContainer); sandbox = sinon.createSandbox(); - sandbox.stub(UnitTestManagementService.prototype, 'registerSymbolProvider'); sandbox.stub(UnitTestManagementService.prototype, 'registerCommands'); sandbox.stub(UnitTestManagementService.prototype, 'registerHandlers'); sandbox.stub(UnitTestManagementService.prototype, 'autoDiscoverTests').callsFake(() => Promise.resolve()); @@ -42,7 +40,7 @@ suite('Unit Tests - ManagementService', () => { }); test('Do not execute command', async () => { - await testManagementService.activate(instance(mock(JediSymbolProvider))); + await testManagementService.activate(); verify(commandManager.executeCommand('setContext', 'testsDiscovered', anything())).never(); });