Skip to content

Commit

Permalink
"Run All Tests" and "Debug All Tests" (dotnet#1961)
Browse files Browse the repository at this point in the history
* Run All Tests Running But Building repeatedly

* Disposable variable for run all test

* Added code for debug all tests

* Code Cleaning

* Run all Tests running - Better logs required

* Run Tests running, all output shown at the end of all the tests

* Renamed variable to methodsInClass

* Changes for Debug All Tests

* Changes for debug tests request

* Debug All Tests running

* Added common helpers for single test and all test functions

* Improved logs for Run All Tests

* Changes to get only 1 response for a set of methods

* Extracted a common helper to get the test feature

* Resolved review comments

* Changes to not show this change for legacy projects

* Renamed incorrect variable

* Removing foreach for proper order of execution

* Remove unnecessary import

* Do not show the festure for legacy projects
  • Loading branch information
akshita31 committed Feb 23, 2018
1 parent dc08d2e commit c6cf2d7
Show file tree
Hide file tree
Showing 4 changed files with 231 additions and 64 deletions.
89 changes: 68 additions & 21 deletions src/features/codeLensProvider.ts
Original file line number Diff line number Diff line change
Expand Up @@ -31,8 +31,7 @@ export default class OmniSharpCodeLensProvider extends AbstractProvider implemen

private _options: Options;

constructor(server: OmniSharpServer, reporter: TelemetryReporter, testManager: TestManager)
{
constructor(server: OmniSharpServer, reporter: TelemetryReporter, testManager: TestManager) {
super(server, reporter);

this._resetCachedOptions();
Expand All @@ -52,20 +51,23 @@ export default class OmniSharpCodeLensProvider extends AbstractProvider implemen
'ToString': true
};

provideCodeLenses(document: vscode.TextDocument, token: vscode.CancellationToken): vscode.CodeLens[] | Thenable<vscode.CodeLens[]> {
if (!this._options.showReferencesCodeLens && !this._options.showTestsCodeLens)
{
async provideCodeLenses(document: vscode.TextDocument, token: vscode.CancellationToken) {
if (!this._options.showReferencesCodeLens && !this._options.showTestsCodeLens) {
return [];
}

return serverUtils.currentFileMembersAsTree(this._server, { FileName: document.fileName }, token).then(tree => {
let ret: vscode.CodeLens[] = [];
tree.TopLevelTypeDefinitions.forEach(node => this._convertQuickFix(ret, document.fileName, node));
return ret;
});
let tree = await serverUtils.currentFileMembersAsTree(this._server, { FileName: document.fileName }, token);
let ret: vscode.CodeLens[] = [];

for (let node of tree.TopLevelTypeDefinitions) {
await this._convertQuickFix(ret, document.fileName, node);
}

return ret;
}

private _convertQuickFix(bucket: vscode.CodeLens[], fileName: string, node: protocol.Node): void {

private async _convertQuickFix(bucket: vscode.CodeLens[], fileName: string, node: protocol.Node): Promise<void> {

if (node.Kind === 'MethodDeclaration' && OmniSharpCodeLensProvider.filteredSymbolNames[node.Location.Text]) {
return;
Expand All @@ -81,7 +83,7 @@ export default class OmniSharpCodeLensProvider extends AbstractProvider implemen
}

if (this._options.showTestsCodeLens) {
this._updateCodeLensForTest(bucket, fileName, node);
await this._updateCodeLensForTest(bucket, fileName, node);
}
}

Expand Down Expand Up @@ -113,15 +115,64 @@ export default class OmniSharpCodeLensProvider extends AbstractProvider implemen
}
}

private _updateCodeLensForTest(bucket: vscode.CodeLens[], fileName: string, node: protocol.Node) {
private async _updateCodeLensForTest(bucket: vscode.CodeLens[], fileName: string, node: protocol.Node): Promise<void> {
// backward compatible check: Features property doesn't present on older version OmniSharp
if (node.Features === undefined) {
return;
}

if (node.Kind === "ClassDeclaration" && node.ChildNodes.length > 0) {
let projectInfo = await serverUtils.requestProjectInformation(this._server, { FileName: fileName });
if (!projectInfo.DotNetProject && projectInfo.MsBuildProject) {
this._updateCodeLensForTestClass(bucket, fileName, node);
}
}

let [testFeature, testFrameworkName] = this._getTestFeatureAndFramework(node);
if (testFeature) {
bucket.push(new vscode.CodeLens(
toRange(node.Location),
{ title: "run test", command: 'dotnet.test.run', arguments: [testFeature.Data, fileName, testFrameworkName] }));

bucket.push(new vscode.CodeLens(
toRange(node.Location),
{ title: "debug test", command: 'dotnet.test.debug', arguments: [testFeature.Data, fileName, testFrameworkName] }));
}
}

private _updateCodeLensForTestClass(bucket: vscode.CodeLens[], fileName: string, node: protocol.Node) {
// if the class doesnot contain any method then return
if (!node.ChildNodes.find(value => (value.Kind === "MethodDeclaration"))) {
return;
}

let testMethods = new Array<string>();
let testFrameworkName: string = null;
for (let child of node.ChildNodes) {
let [testFeature, frameworkName] = this._getTestFeatureAndFramework(child);
if (testFeature) {
// this test method has a test feature
if (!testFrameworkName) {
testFrameworkName = frameworkName;
}

testMethods.push(testFeature.Data);
}
}

if (testMethods.length > 0) {
bucket.push(new vscode.CodeLens(
toRange(node.Location),
{ title: "run all tests", command: 'dotnet.classTests.run', arguments: [testMethods, fileName, testFrameworkName] }));
bucket.push(new vscode.CodeLens(
toRange(node.Location),
{ title: "debug all tests", command: 'dotnet.classTests.debug', arguments: [testMethods, fileName, testFrameworkName] }));
}
}

private _getTestFeatureAndFramework(node: protocol.Node): [protocol.SyntaxFeature, string] {
let testFeature = node.Features.find(value => (value.Name == 'XunitTestMethod' || value.Name == 'NUnitTestMethod' || value.Name == 'MSTestMethod'));
if (testFeature) {
// this test method has a test feature
let testFrameworkName = 'xunit';
if (testFeature.Name == 'NUnitTestMethod') {
testFrameworkName = 'nunit';
Expand All @@ -130,13 +181,9 @@ export default class OmniSharpCodeLensProvider extends AbstractProvider implemen
testFrameworkName = 'mstest';
}

bucket.push(new vscode.CodeLens(
toRange(node.Location),
{ title: "run test", command: 'dotnet.test.run', arguments: [testFeature.Data, fileName, testFrameworkName] }));

bucket.push(new vscode.CodeLens(
toRange(node.Location),
{ title: "debug test", command: 'dotnet.test.debug', arguments: [testFeature.Data, fileName, testFrameworkName] }));
return [testFeature, testFrameworkName];
}

return [null, null];
}
}
184 changes: 141 additions & 43 deletions src/features/dotnetTest.ts
Original file line number Diff line number Diff line change
Expand Up @@ -36,6 +36,14 @@ export default class TestManager extends AbstractProvider {
'dotnet.test.debug',
(testMethod, fileName, testFrameworkName) => this._debugDotnetTest(testMethod, fileName, testFrameworkName));

let d4 = vscode.commands.registerCommand(
'dotnet.classTests.run',
(methodsInClass, fileName, testFrameworkName) => this._runDotnetTestsInClass(methodsInClass, fileName, testFrameworkName));

let d5 = vscode.commands.registerCommand(
'dotnet.classTests.debug',
(methodsInClass, fileName, testFrameworkName) => this._debugDotnetTestsInClass(methodsInClass, fileName, testFrameworkName));

this._telemetryIntervalId = setInterval(() =>
this._reportTelemetry(), TelemetryReportingDelay);

Expand All @@ -48,7 +56,7 @@ export default class TestManager extends AbstractProvider {
}
});

this.addDisposables(d1, d2, d3);
this.addDisposables(d1, d2, d3, d4, d5);
}

private _getOutputChannel(): vscode.OutputChannel {
Expand Down Expand Up @@ -126,9 +134,11 @@ export default class TestManager extends AbstractProvider {

private _reportResults(results: protocol.V2.DotNetTestResult[]): Promise<void> {
const totalTests = results.length;
const output = this._getOutputChannel();

let totalPassed = 0, totalFailed = 0, totalSkipped = 0;
for (let result of results) {
output.appendLine(`${result.MethodName}: ${result.Outcome}`);
switch (result.Outcome) {
case protocol.V2.TestOutcomes.Failed:
totalFailed += 1;
Expand All @@ -142,15 +152,35 @@ export default class TestManager extends AbstractProvider {
}
}

const output = this._getOutputChannel();
output.appendLine('');
output.appendLine(`Total tests: ${totalTests}. Passed: ${totalPassed}. Failed: ${totalFailed}. Skipped: ${totalSkipped}`);
output.appendLine('');

return Promise.resolve();
}

private _runDotnetTest(testMethod: string, fileName: string, testFrameworkName: string) {
private async _recordRunAndGetFrameworkVersion(fileName: string, testFrameworkName: string) {

await this._saveDirtyFiles();
this._recordRunRequest(testFrameworkName);
let projectInfo = await serverUtils.requestProjectInformation(this._server, { FileName: fileName });

let targetFrameworkVersion: string;

if (projectInfo.DotNetProject) {
targetFrameworkVersion = undefined;
}
else if (projectInfo.MsBuildProject) {
targetFrameworkVersion = projectInfo.MsBuildProject.TargetFramework;
}
else {
throw new Error('Expected project.json or .csproj project.');
}

return targetFrameworkVersion;
}

private async _runDotnetTest(testMethod: string, fileName: string, testFrameworkName: string) {
const output = this._getOutputChannel();

output.show();
Expand All @@ -161,25 +191,9 @@ export default class TestManager extends AbstractProvider {
output.appendLine(e.Message);
});

this._saveDirtyFiles()
.then(_ => this._recordRunRequest(testFrameworkName))
.then(_ => serverUtils.requestProjectInformation(this._server, { FileName: fileName }))
.then(projectInfo =>
{
let targetFrameworkVersion: string;

if (projectInfo.DotNetProject) {
targetFrameworkVersion = undefined;
}
else if (projectInfo.MsBuildProject) {
targetFrameworkVersion = projectInfo.MsBuildProject.TargetFramework;
}
else {
throw new Error('Expected project.json or .csproj project.');
}
let targetFrameworkVersion = await this._recordRunAndGetFrameworkVersion(fileName, testFrameworkName);

return this._runTest(fileName, testMethod, testFrameworkName, targetFrameworkVersion);
})
return this._runTest(fileName, testMethod, testFrameworkName, targetFrameworkVersion)
.then(results => this._reportResults(results))
.then(() => listener.dispose())
.catch(reason => {
Expand All @@ -188,6 +202,37 @@ export default class TestManager extends AbstractProvider {
});
}

private async _runDotnetTestsInClass(methodsInClass: string[], fileName: string, testFrameworkName: string) {
const output = this._getOutputChannel();

output.show();
const listener = this._server.onTestMessage(e => {
output.appendLine(e.Message);
});

let targetFrameworkVersion = await this._recordRunAndGetFrameworkVersion(fileName, testFrameworkName);

return this._runTestsInClass(fileName, testFrameworkName, targetFrameworkVersion, methodsInClass)
.then(results => this._reportResults(results))
.then(() => listener.dispose())
.catch(reason => {
listener.dispose();
vscode.window.showErrorMessage(`Failed to run tests because ${reason}.`);
});
}

private _runTestsInClass(fileName: string, testFrameworkName: string, targetFrameworkVersion: string, methodsToRun: string[]): Promise<protocol.V2.DotNetTestResult[]> {
const request: protocol.V2.RunTestsInClassRequest = {
FileName: fileName,
TestFrameworkName: testFrameworkName,
TargetFrameworkVersion: targetFrameworkVersion,
MethodNames: methodsToRun
};

return serverUtils.runTestsInClass(this._server, request)
.then(response => response.Results);
}

private _createLaunchConfiguration(program: string, args: string, cwd: string, debuggerEventsPipeName: string) {
let debugOptions = vscode.workspace.getConfiguration('csharp').get('unitTestDebuggingOptions');

Expand Down Expand Up @@ -271,39 +316,64 @@ export default class TestManager extends AbstractProvider {
}
}

private _debugDotnetTest(testMethod: string, fileName: string, testFrameworkName: string) {
// We support to styles of 'dotnet test' for debugging: The legacy 'project.json' testing, and the newer csproj support
// using VS Test. These require a different level of communication.
private async _recordDebugAndGetDebugValues(fileName: string, testFrameworkName: string, output: vscode.OutputChannel) {
await this._saveDirtyFiles();
this._recordDebugRequest(testFrameworkName);
let projectInfo = await serverUtils.requestProjectInformation(this._server, { FileName: fileName });

let debugType: string;
let debugEventListener: DebugEventListener = null;
let targetFrameworkVersion: string;

if (projectInfo.DotNetProject) {
debugType = 'legacy';
targetFrameworkVersion = '';
}
else if (projectInfo.MsBuildProject) {
debugType = 'vstest';
targetFrameworkVersion = projectInfo.MsBuildProject.TargetFramework;
debugEventListener = new DebugEventListener(fileName, this._server, output);
debugEventListener.start();
}
else {
throw new Error('Expected project.json or .csproj project.');
}

return { debugType, debugEventListener, targetFrameworkVersion };
}

private async _debugDotnetTest(testMethod: string, fileName: string, testFrameworkName: string) {
// We support to styles of 'dotnet test' for debugging: The legacy 'project.json' testing, and the newer csproj support
// using VS Test. These require a different level of communication.

const output = this._getOutputChannel();

output.show();
output.appendLine(`Debugging method '${testMethod}'...`);
output.appendLine('');

return this._saveDirtyFiles()
.then(_ => this._recordDebugRequest(testFrameworkName))
.then(_ => serverUtils.requestProjectInformation(this._server, { FileName: fileName }))
.then(projectInfo => {
if (projectInfo.DotNetProject) {
debugType = 'legacy';
targetFrameworkVersion = '';
return Promise.resolve();
}
else if (projectInfo.MsBuildProject) {
debugType = 'vstest';
targetFrameworkVersion = projectInfo.MsBuildProject.TargetFramework;
debugEventListener = new DebugEventListener(fileName, this._server, output);
return debugEventListener.start();
}
else {
throw new Error('Expected project.json or .csproj project.');
}
let { debugType, debugEventListener, targetFrameworkVersion } = await this._recordDebugAndGetDebugValues(fileName, testFrameworkName, output);

return this._getLaunchConfiguration(debugType, fileName, testMethod, testFrameworkName, targetFrameworkVersion, debugEventListener)
.then(config => {
const workspaceFolder = vscode.workspace.getWorkspaceFolder(vscode.Uri.file(fileName));
return vscode.debug.startDebugging(workspaceFolder, config);
})
.then(() => this._getLaunchConfiguration(debugType, fileName, testMethod, testFrameworkName, targetFrameworkVersion, debugEventListener))
.catch(reason => {
vscode.window.showErrorMessage(`Failed to start debugger: ${reason}`);
if (debugEventListener != null) {
debugEventListener.close();
}
});
}

private async _debugDotnetTestsInClass(methodsToRun: string[], fileName: string, testFrameworkName: string) {

const output = this._getOutputChannel();

let { debugType, debugEventListener, targetFrameworkVersion } = await this._recordDebugAndGetDebugValues(fileName, testFrameworkName, output);

return await this._getLaunchConfigurationForClass(debugType, fileName, methodsToRun, testFrameworkName, targetFrameworkVersion, debugEventListener)
.then(config => {
const workspaceFolder = vscode.workspace.getWorkspaceFolder(vscode.Uri.file(fileName));
return vscode.debug.startDebugging(workspaceFolder, config);
Expand All @@ -315,6 +385,34 @@ export default class TestManager extends AbstractProvider {
}
});
}

private _getLaunchConfigurationForClass(debugType: string, fileName: string, methodsToRun: string[], testFrameworkName: string, targetFrameworkVersion: string, debugEventListener: DebugEventListener): Promise<any> {
if (debugType == 'vstest') {
return this._getLaunchConfigurationForVSTestClass(fileName, methodsToRun, testFrameworkName, targetFrameworkVersion, debugEventListener);
}
throw new Error(`Unexpected debug type: ${debugType}`);
}

private _getLaunchConfigurationForVSTestClass(fileName: string, methodsToRun: string[], testFrameworkName: string, targetFrameworkVersion: string, debugEventListener: DebugEventListener): Promise<any> {
const output = this._getOutputChannel();

const listener = this._server.onTestMessage(e => {
output.appendLine(e.Message);
});

const request: protocol.V2.DebugTestClassGetStartInfoRequest = {
FileName: fileName,
MethodNames: methodsToRun,
TestFrameworkName: testFrameworkName,
TargetFrameworkVersion: targetFrameworkVersion
};

return serverUtils.debugTestClassGetStartInfo(this._server, request)
.then(response => {
listener.dispose();
return this._createLaunchConfiguration(response.FileName, response.Arguments, response.WorkingDirectory, debugEventListener.pipePath());
});
}
}

class DebugEventListener {
Expand Down
Loading

0 comments on commit c6cf2d7

Please sign in to comment.