Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
38 changes: 25 additions & 13 deletions src/client/unittests/common/managers/baseTestManager.ts
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
import { CancellationToken, CancellationTokenSource, Diagnostic, DiagnosticCollection, DiagnosticRelatedInformation, Disposable, languages, OutputChannel, Uri } from 'vscode';
import { CancellationToken, CancellationTokenSource, Diagnostic, DiagnosticCollection, DiagnosticRelatedInformation, Disposable, Event, EventEmitter, languages, OutputChannel, Uri } from 'vscode';
import { IWorkspaceService } from '../../../common/application/types';
import { isNotInstalledError } from '../../../common/helpers';
import { IFileSystem } from '../../../common/platform/types';
Expand Down Expand Up @@ -38,6 +38,7 @@ export abstract class BaseTestManager implements ITestManager {
private testRunnerCancellationTokenSource?: CancellationTokenSource;
private _installer!: IInstaller;
private discoverTestsPromise?: Promise<Tests>;
private _onDidStatusChange = new EventEmitter<TestStatus>();
private get installer(): IInstaller {
if (!this._installer) {
this._installer = this.serviceContainer.get<IInstaller>(IInstaller);
Expand All @@ -46,7 +47,7 @@ export abstract class BaseTestManager implements ITestManager {
}
constructor(public readonly testProvider: TestProvider, private product: Product, public readonly workspaceFolder: Uri, protected rootDirectory: string,
protected serviceContainer: IServiceContainer) {
this._status = TestStatus.Unknown;
this.updateStatus(TestStatus.Unknown);
Comment thread
DonJayamanne marked this conversation as resolved.
Resolved
const configService = serviceContainer.get<IConfigurationService>(IConfigurationService);
this.settings = configService.getSettings(this.rootDirectory ? Uri.file(this.rootDirectory) : undefined);
const disposables = serviceContainer.get<Disposable[]>(IDisposableRegistry);
Expand All @@ -70,6 +71,9 @@ export abstract class BaseTestManager implements ITestManager {
public get status(): TestStatus {
return this._status;
}
public get onDidStatusChange(): Event<TestStatus> {
return this._onDidStatusChange.event;
}
public get workingDirectory(): string {
return this.settings.unitTest.cwd && this.settings.unitTest.cwd.length > 0 ? this.settings.unitTest.cwd : this.rootDirectory;
}
Expand All @@ -82,8 +86,8 @@ export abstract class BaseTestManager implements ITestManager {
}
}
public reset() {
this._status = TestStatus.Unknown;
this.tests = undefined;
this.updateStatus(TestStatus.Unknown);
}
public resetTestResults() {
if (!this.tests) {
Expand All @@ -98,11 +102,10 @@ export abstract class BaseTestManager implements ITestManager {
}

if (!ignoreCache && this.tests! && this.tests!.testFunctions.length > 0) {
this._status = TestStatus.Idle;
this.updateStatus(TestStatus.Idle);
return Promise.resolve(this.tests!);
}
this._status = TestStatus.Discovering;

this.updateStatus(TestStatus.Discovering);
// If ignoreCache is true, its an indication of the fact that its a user invoked operation.
// Hence we can stop the debugger.
if (userInitiated) {
Expand All @@ -121,8 +124,8 @@ export abstract class BaseTestManager implements ITestManager {
return discoveryService.discoverTests(discoveryOptions)
.then(tests => {
this.tests = tests;
this._status = TestStatus.Idle;
this.resetTestResults();
this.updateStatus(TestStatus.Idle);
this.discoverTestsPromise = undefined;

// have errors in Discovering
Expand Down Expand Up @@ -155,11 +158,11 @@ export abstract class BaseTestManager implements ITestManager {
this.discoverTestsPromise = undefined;
if (this.testDiscoveryCancellationToken && this.testDiscoveryCancellationToken.isCancellationRequested) {
reason = CANCELLATION_REASON;
this._status = TestStatus.Idle;
this.updateStatus(TestStatus.Idle);
} else {
telementryProperties.failed = true;
sendTelemetryEvent(EventName.UNITTEST_DISCOVER, undefined, telementryProperties);
this._status = TestStatus.Error;
this.updateStatus(TestStatus.Error);
this.outputChannel.appendLine('Test Discovery failed: ');
// tslint:disable-next-line:prefer-template
this.outputChannel.appendLine(reason.toString());
Expand Down Expand Up @@ -211,7 +214,7 @@ export abstract class BaseTestManager implements ITestManager {
this.resetTestResults();
}

this._status = TestStatus.Running;
this.updateStatus(TestStatus.Running);
if (this.testRunnerCancellationTokenSource) {
this.testRunnerCancellationTokenSource.cancel();
}
Expand All @@ -235,16 +238,16 @@ export abstract class BaseTestManager implements ITestManager {
this.createCancellationToken(CancellationTokenType.testRunner);
return this.runTestImpl(tests, testsToRun, runFailedTests, debug);
}).then(() => {
this._status = TestStatus.Idle;
this.updateStatus(TestStatus.Idle);
this.disposeCancellationToken(CancellationTokenType.testRunner);
sendTelemetryEvent(EventName.UNITTEST_RUN, undefined, telementryProperties);
return this.tests!;
}).catch(reason => {
if (this.testRunnerCancellationToken && this.testRunnerCancellationToken.isCancellationRequested) {
reason = CANCELLATION_REASON;
this._status = TestStatus.Idle;
this.updateStatus(TestStatus.Idle);
} else {
this._status = TestStatus.Error;
this.updateStatus(TestStatus.Error);
telementryProperties.failed = true;
sendTelemetryEvent(EventName.UNITTEST_RUN, undefined, telementryProperties);
}
Expand Down Expand Up @@ -291,6 +294,15 @@ export abstract class BaseTestManager implements ITestManager {
// tslint:disable-next-line:no-any
protected abstract runTestImpl(tests: Tests, testsToRun?: TestsToRun, runFailedTests?: boolean, debug?: boolean): Promise<any>;
protected abstract getDiscoveryOptions(ignoreCache: boolean): TestDiscoveryOptions;
private updateStatus(status: TestStatus): void {
if (status === this._status) {
return;
}
this._status = status;
// Fire after 1ms, let existing code run to completion,
// We need to allow for code to get into a consistent state.
setTimeout(() => this._onDidStatusChange.fire(status), 1);
Comment thread
DonJayamanne marked this conversation as resolved.
Resolved
}
private createCancellationToken(tokenType: CancellationTokenType) {
this.disposeCancellationToken(tokenType);
if (tokenType === CancellationTokenType.testDiscovery) {
Expand Down
10 changes: 1 addition & 9 deletions src/client/unittests/common/services/storageService.ts
Original file line number Diff line number Diff line change
@@ -1,21 +1,15 @@
import { inject, injectable } from 'inversify';
import {
Disposable, Event, EventEmitter,
Uri, workspace
} from 'vscode';
import { Disposable, Uri, workspace } from 'vscode';
import { IDisposableRegistry } from '../../../common/types';
import { FlattenedTestFunction, FlattenedTestSuite, ITestCollectionStorageService, TestFunction, Tests, TestSuite } from './../types';

@injectable()
export class TestCollectionStorageService implements ITestCollectionStorageService {
public readonly onUpdated: Event<Uri>;

private testsIndexedByWorkspaceUri = new Map<string, Tests | undefined>();
private _onTestStoreUpdated: EventEmitter<Uri> = new EventEmitter<Uri>();

constructor(@inject(IDisposableRegistry) disposables: Disposable[]) {
disposables.push(this);
this.onUpdated = this._onTestStoreUpdated.event;
}
public getTests(wkspace: Uri): Tests | undefined {
const workspaceFolder = this.getWorkspaceFolderPath(wkspace) || '';
Expand All @@ -24,7 +18,6 @@ export class TestCollectionStorageService implements ITestCollectionStorageServi
public storeTests(wkspace: Uri, tests: Tests | undefined): void {
const workspaceFolder = this.getWorkspaceFolderPath(wkspace) || '';
this.testsIndexedByWorkspaceUri.set(workspaceFolder, tests);
this._onTestStoreUpdated.fire(wkspace);
}
public findFlattendTestFunction(resource: Uri, func: TestFunction): FlattenedTestFunction | undefined {
const tests = this.getTests(resource);
Expand All @@ -42,7 +35,6 @@ export class TestCollectionStorageService implements ITestCollectionStorageServi
}
public dispose() {
this.testsIndexedByWorkspaceUri.clear();
this._onTestStoreUpdated.dispose();
}
private getWorkspaceFolderPath(resource: Uri): string | undefined {
const folder = workspace.getWorkspaceFolder(resource);
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -7,7 +7,7 @@ import { ITestManager, ITestManagerService, ITestManagerServiceFactory, IWorkspa
@injectable()
export class WorkspaceTestManagerService implements IWorkspaceTestManagerService, Disposable {
private workspaceTestManagers = new Map<string, ITestManagerService>();
constructor( @inject(IOutputChannel) @named(TEST_OUTPUT_CHANNEL) private outChannel: OutputChannel,
constructor(@inject(IOutputChannel) @named(TEST_OUTPUT_CHANNEL) private outChannel: OutputChannel,
@inject(ITestManagerServiceFactory) private testManagerServiceFactory: ITestManagerServiceFactory,
@inject(IDisposableRegistry) disposables: Disposable[]) {
disposables.push(this);
Expand Down
20 changes: 19 additions & 1 deletion src/client/unittests/common/testUtils.ts
Original file line number Diff line number Diff line change
Expand Up @@ -7,7 +7,7 @@ import { IUnitTestSettings, Product } from '../../common/types';
import { IServiceContainer } from '../../ioc/types';
import { CommandSource } from './constants';
import { TestFlatteningVisitor } from './testVisitors/flatteningVisitor';
import { ITestsHelper, ITestVisitor, TestFile, TestFolder, TestProvider, Tests, TestSettingsPropertyNames, TestsToRun, UnitTestProduct } from './types';
import { ITestsHelper, ITestVisitor, TestFile, TestFolder, TestFunction, TestProvider, Tests, TestSettingsPropertyNames, TestsToRun, TestSuite, UnitTestProduct } from './types';

export async function selectTestWorkspace(): Promise<Uri | undefined> {
if (!Array.isArray(workspace.workspaceFolders) || workspace.workspaceFolders.length === 0) {
Expand Down Expand Up @@ -206,4 +206,22 @@ export class TestsHelper implements ITestsHelper {

return true;
}
public getTestFile(test: TestFile | TestFolder | TestSuite | TestFunction): TestFile | undefined {
// Only TestFile has a `fullPath` property.
return Array.isArray((test as TestFile).fullPath) ? test as TestFile : undefined;
}
public getTestSuite(test: TestFile | TestFolder | TestSuite | TestFunction): TestSuite | undefined {
// Only TestSuite has a `suites` property.
return Array.isArray((test as TestSuite).suites) ? test as TestSuite : undefined;
}
public getTestFolder(test: TestFile | TestFolder | TestSuite | TestFunction): TestFolder | undefined {
// Only TestFolder has a `folders` property.
return Array.isArray((test as TestFolder).folders) ? test as TestFolder : undefined;
}
public getTestFunction(test: TestFile | TestFolder | TestSuite | TestFunction): TestFunction | undefined {
if (this.getTestFile(test) || this.getTestSuite(test) || this.getTestSuite(test)) {
return;
}
return test as TestFunction;
}
}
14 changes: 8 additions & 6 deletions src/client/unittests/common/types.ts
Original file line number Diff line number Diff line change
@@ -1,7 +1,4 @@
import {
CancellationToken, DiagnosticCollection, Disposable,
Event, OutputChannel, Uri
} from 'vscode';
import { CancellationToken, DiagnosticCollection, Disposable, Event, OutputChannel, Uri } from 'vscode';
import { IUnitTestSettings, Product } from '../../common/types';
import { IPythonUnitTestMessage } from '../types';
import { CommandSource } from './constants';
Expand Down Expand Up @@ -37,7 +34,12 @@ export type TestFolder = TestResult & {
status?: TestStatus;
folders: TestFolder[];
};

export enum TestType {
testFile = 'testFile',
testFolder = 'testFolder',
testSuite = 'testSuite',
testFunction = 'testFunction'
}
export type TestFile = TestResult & {
name: string;
fullPath: string;
Expand Down Expand Up @@ -184,7 +186,6 @@ export interface ITestVisitor {
export const ITestCollectionStorageService = Symbol('ITestCollectionStorageService');

export interface ITestCollectionStorageService extends Disposable {
onUpdated: Event<Uri>;
getTests(wkspace: Uri): Tests | undefined;
storeTests(wkspace: Uri, tests: Tests | null | undefined): void;
findFlattendTestFunction(resource: Uri, func: TestFunction): FlattenedTestFunction | undefined;
Expand Down Expand Up @@ -232,6 +233,7 @@ export interface ITestManager extends Disposable {
readonly workingDirectory: string;
readonly workspaceFolder: Uri;
diagnosticCollection: DiagnosticCollection;
readonly onDidStatusChange: Event<TestStatus>;
stop(): void;
resetTestResults(): void;
discoverTests(cmdSource: CommandSource, ignoreCache?: boolean, quietMode?: boolean, userInitiated?: boolean): Promise<Tests>;
Expand Down
49 changes: 27 additions & 22 deletions src/client/unittests/main.ts
Original file line number Diff line number Diff line change
Expand Up @@ -5,8 +5,8 @@
import { inject, injectable } from 'inversify';
import {
ConfigurationChangeEvent, Disposable,
DocumentSymbolProvider, EventEmitter,
OutputChannel, TextDocument, Uri, window
DocumentSymbolProvider, Event,
EventEmitter, OutputChannel, TextDocument, Uri, window
} from 'vscode';
import {
ICommandManager, IDocumentManager, IWorkspaceService
Expand All @@ -26,15 +26,8 @@ import {
CANCELLATION_REASON, CommandSource, TEST_OUTPUT_CHANNEL
} from './common/constants';
import { selectTestWorkspace } from './common/testUtils';
import {
ITestCollectionStorageService, ITestManager,
IWorkspaceTestManagerService, TestFile,
TestFunction, TestStatus, TestsToRun
} from './common/types';
import {
ITestDisplay, ITestResultDisplay,
IUnitTestConfigurationService, IUnitTestManagementService
} from './types';
import { ITestCollectionStorageService, ITestManager, IWorkspaceTestManagerService, TestFile, TestFunction, TestStatus, TestsToRun } from './common/types';
import { ITestDisplay, ITestResultDisplay, IUnitTestConfigurationService, IUnitTestManagementService, WorkspaceTestStatus } from './types';

@injectable()
export class UnitTestManagementService implements IUnitTestManagementService, Disposable {
Expand All @@ -46,7 +39,8 @@ export class UnitTestManagementService implements IUnitTestManagementService, Di
private testResultDisplay?: ITestResultDisplay;
private autoDiscoverTimer?: NodeJS.Timer;
private configChangedTimer?: NodeJS.Timer;
private readonly onDidChange: EventEmitter<void> = new EventEmitter<void>();
private testManagers = new Set<ITestManager>();
private readonly _onDidStatusChange: EventEmitter<WorkspaceTestStatus> = new EventEmitter<WorkspaceTestStatus>();

constructor(@inject(IServiceContainer) private serviceContainer: IServiceContainer) {
this.disposableRegistry = serviceContainer.get<Disposable[]>(IDisposableRegistry);
Expand All @@ -61,6 +55,9 @@ export class UnitTestManagementService implements IUnitTestManagementService, Di
this.workspaceTestManagerService.dispose();
}
}
public get onDidStatusChange(): Event<WorkspaceTestStatus> {
return this._onDidStatusChange.event;
}
public async activate(symbolProvider: DocumentSymbolProvider): Promise<void> {
this.workspaceTestManagerService = this.serviceContainer.get<IWorkspaceTestManagerService>(IWorkspaceTestManagerService);
const disposablesRegistry = this.serviceContainer.get<Disposable[]>(IDisposableRegistry);
Expand All @@ -78,11 +75,6 @@ export class UnitTestManagementService implements IUnitTestManagementService, Di
await this.registerSymbolProvider(symbolProvider);
}

public async activateCodeLenses(symbolProvider: DocumentSymbolProvider): Promise<void> {
const testCollectionStorage = this.serviceContainer.get<ITestCollectionStorageService>(ITestCollectionStorageService);
this.disposableRegistry.push(activateCodeLenses(this.onDidChange, symbolProvider, testCollectionStorage));
}

public async getTestManager(displayTestNotConfiguredMessage: boolean, resource?: Uri): Promise<ITestManager | undefined | void> {
let wkspace: Uri | undefined;
if (resource) {
Expand All @@ -96,6 +88,13 @@ export class UnitTestManagementService implements IUnitTestManagementService, Di
}
const testManager = this.workspaceTestManagerService!.getTestManager(wkspace);
if (testManager) {
if (!this.testManagers.has(testManager)) {
this.testManagers.add(testManager);
const handler = testManager.onDidStatusChange(e => {
this._onDidStatusChange.fire({ workspace: testManager.workspaceFolder, status: e });
});
this.disposableRegistry.push(handler);
}
return testManager;
}
if (displayTestNotConfiguredMessage) {
Expand Down Expand Up @@ -177,7 +176,6 @@ export class UnitTestManagementService implements IUnitTestManagementService, Di

if (!this.testResultDisplay) {
this.testResultDisplay = this.serviceContainer.get<ITestResultDisplay>(ITestResultDisplay);
this.testResultDisplay.onDidChange(() => this.onDidChange.fire());
}
const discoveryPromise = testManager.discoverTests(cmdSource, ignoreCache, quietMode, userInitiated);
this.testResultDisplay.displayDiscoverStatus(discoveryPromise, quietMode)
Expand Down Expand Up @@ -295,7 +293,6 @@ export class UnitTestManagementService implements IUnitTestManagementService, Di

if (!this.testResultDisplay) {
this.testResultDisplay = this.serviceContainer.get<ITestResultDisplay>(ITestResultDisplay);
this.testResultDisplay.onDidChange(() => this.onDidChange.fire());
}

const promise = testManager.runTest(cmdSource, testsToRun, runFailedTests, debug)
Expand All @@ -312,7 +309,15 @@ export class UnitTestManagementService implements IUnitTestManagementService, Di

private async registerSymbolProvider(symbolProvider: DocumentSymbolProvider): Promise<void> {
const testCollectionStorage = this.serviceContainer.get<ITestCollectionStorageService>(ITestCollectionStorageService);
this.disposableRegistry.push(activateCodeLenses(this.onDidChange, symbolProvider, testCollectionStorage));
const event = new EventEmitter<void>();
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));
}

@captureTelemetry(EventName.UNITTEST_CONFIGURE, undefined, false)
Expand All @@ -339,13 +344,13 @@ export class UnitTestManagementService implements IUnitTestManagementService, Di
// Ignore the exceptions returned.
// This command will be invoked from other places of the extension.
this.discoverTests(cmdSource, resource, true, true)
.ignoreErrors();
.ignoreErrors();
}),
commandManager.registerCommand(constants.Commands.Tests_Configure, (_, cmdSource: CommandSource = CommandSource.commandPalette, resource?: Uri) => {
// Ignore the exceptions returned.
// This command will be invoked from other places of the extension.
this.configureTests(resource)
.ignoreErrors();
.ignoreErrors();
}),
commandManager.registerCommand(constants.Commands.Tests_Run_Failed, (_, cmdSource: CommandSource = CommandSource.commandPalette, resource: Uri) => this.runTestsImpl(cmdSource, resource, undefined, true)),
commandManager.registerCommand(constants.Commands.Tests_Run, (_, cmdSource: CommandSource = CommandSource.commandPalette, file: Uri, testToRun?: TestsToRun) => this.runTestsImpl(cmdSource, file, testToRun)),
Expand Down
Loading