diff --git a/.gitignore b/.gitignore index b631c59..7b40a66 100644 --- a/.gitignore +++ b/.gitignore @@ -6,4 +6,5 @@ npm-debug.log yarn.* [Bb]in/ [Oo]bj/ -lcov.info \ No newline at end of file +lcov.info +.vscode-test/** \ No newline at end of file diff --git a/.vscode/launch.json b/.vscode/launch.json index d105ede..acd53a0 100644 --- a/.vscode/launch.json +++ b/.vscode/launch.json @@ -2,6 +2,7 @@ { "version": "0.1.0", "configurations": [ + { "name": "Launch Extension (xunit)", "type": "extensionHost", @@ -118,6 +119,17 @@ "sourceMaps": true, "outFiles": [ "${workspaceRoot}/out/test/**/*.js" ], "preLaunchTask": "npm" + }, + { + "name": "Run Extension", + "type": "extensionHost", + "request": "launch", + "runtimeExecutable": "${execPath}", + "args": [ + "--extensionDevelopmentPath=${workspaceRoot}", + ], + "outFiles": [ "${workspaceRoot}/out/test/**/*.js" ], + "preLaunchTask": "npm" } ] } diff --git a/package.json b/package.json index 9a19e81..edc0c4d 100644 --- a/package.json +++ b/package.json @@ -109,6 +109,14 @@ { "command": "dotnet-test-explorer.openPanel", "title": "Open Tests Panel" + }, + { + "command": "dotnet-test-explorer.showTestOutput", + "title": "Show Test Output", + "icon": { + "light": "resources/light/log.svg", + "dark": "resources/dark/log.svg" + } } ], "menus": { @@ -143,7 +151,7 @@ { "command": "dotnet-test-explorer.runTest", "when": "view == dotnetTestExplorer", - "group": "inline" + "group": "inline@0" }, { "command": "dotnet-test-explorer.gotoTest", @@ -154,6 +162,11 @@ "command": "dotnet-test-explorer.debugTest", "when": "viewItem == test", "group": "dotnetTestExplorer@2" + }, + { + "command": "dotnet-test-explorer.showTestOutput", + "when": "viewItem == test", + "group": "inline@1" } ], "editor/context": [ @@ -294,7 +307,8 @@ "compile": "tsc -p ./", "watch": "tsc -watch -p ./", "test": "node ./out/test/runTest.js", - "tslint": "tslint -t verbose src/**/*.ts" + "tslint": "tslint -t verbose src/**/*.ts", + "precompile": "node ./test/restorePackages.js" }, "devDependencies": { "@types/glob": "^7.1.1", diff --git a/src/extension.ts b/src/extension.ts index 008d1a0..8b952a7 100644 --- a/src/extension.ts +++ b/src/extension.ts @@ -17,6 +17,7 @@ import { TestNode } from "./testNode"; import { TestStatusCodeLensProvider } from "./testStatusCodeLensProvider"; import { Utility } from "./utility"; import { Watch } from "./watch"; +import { TestResultDocumentContentProvider } from './testResultDocumentContentProvider'; export function activate(context: vscode.ExtensionContext) { const testDirectories = new TestDirectories(); @@ -115,6 +116,12 @@ export function activate(context: vscode.ExtensionContext) { context.subscriptions.push(vscode.window.onDidCloseTerminal((closedTerminal: vscode.Terminal) => { Executor.onDidCloseTerminal(closedTerminal); })); + + context.subscriptions.push(vscode.workspace.registerTextDocumentContentProvider("dotnet-test-explorer.testResult", new TestResultDocumentContentProvider())); + + context.subscriptions.push(vscode.commands.registerCommand("dotnet-test-explorer.showTestOutput", (test: TestNode) => { + testCommands.showTestOutput(test); + })); } export function deactivate() { diff --git a/src/testCommands.ts b/src/testCommands.ts index 042dc43..44e8107 100644 --- a/src/testCommands.ts +++ b/src/testCommands.ts @@ -25,6 +25,7 @@ export class TestCommands implements Disposable { private onNewTestResultsEmitter = new EventEmitter(); private onBuildFailedEmitter = new EventEmitter(); private lastRunTestContext: ITestRunContext = null; + private lastRunTestResults: TestResult[] = []; private testResultsFolder: string; private testResultsFolderWatcher: any; @@ -203,6 +204,7 @@ export class TestCommands implements Disposable { allTestResults.push(...testResults); } this.sendNewTestResults({ clearPreviousTestResults: testName === "", testResults: allTestResults }); + this.lastRunTestResults = allTestResults; } catch (err) { Logger.Log(`Error while executing test command: ${err}`); if (err.message === "Build command failed") { @@ -296,4 +298,18 @@ export class TestCommands implements Disposable { }); }); } + + public async showTestOutput(test: TestNode) { + const testResult = this.lastRunTestResults.find(x => x.name == test.name); + if (!testResult) vscode.window.showInformationMessage(`Result not found for test '${test.name}'. Make sure the test is included in a test run.`); + + const uri = vscode.Uri.parse(`dotnet-test-explorer.testResult:${testResult?.name}`) + .with({ + query: testResult.stdout, + fragment: testResult.outcome + }); + + const doc = await vscode.workspace.openTextDocument(uri); + await vscode.window.showTextDocument(doc, { preview: true }); + } } diff --git a/src/testResult.ts b/src/testResult.ts index 15e7826..d78aa7b 100644 --- a/src/testResult.ts +++ b/src/testResult.ts @@ -7,7 +7,7 @@ export class TestResult { private className: string; private method: string; - public constructor(private _testId: string, private _outcome: string, private _message: string, private _stackTrace: string) { + public constructor(private _testId: string, private _outcome: string, private _message: string, private _stackTrace: string, private _stdout: string) { } public get fullName(): string { @@ -30,6 +30,15 @@ export class TestResult { return this._stackTrace; } + public get stdout(): string { + return this._stdout + || `${this._message}\r\n${this._stackTrace}` ; + } + + public get name(): string { + return this.method; + } + public matches(className: string, method: string): boolean { return this.fullName.indexOf(className + "." + method) > -1; } diff --git a/src/testResultDocumentContentProvider.ts b/src/testResultDocumentContentProvider.ts new file mode 100644 index 0000000..5d603c6 --- /dev/null +++ b/src/testResultDocumentContentProvider.ts @@ -0,0 +1,16 @@ +"use strict"; +import { TextDocumentContentProvider, Uri, EventEmitter } from 'vscode'; + +export class TestResultDocumentContentProvider implements TextDocumentContentProvider { + onDidChangeEmitter = new EventEmitter(); + onDidChange = this.onDidChangeEmitter.event; + + provideTextDocumentContent(uri: Uri): string { + return ` +Test Name: ${uri.path} +Test Outcome: ${uri.fragment} +Standard Output: +${uri.query} +`; + } +} \ No newline at end of file diff --git a/src/testResultsFile.ts b/src/testResultsFile.ts index ae4ed22..e63c73c 100644 --- a/src/testResultsFile.ts +++ b/src/testResultsFile.ts @@ -39,6 +39,7 @@ function parseUnitTestResults(xml: Element): TestResult[] { getAttributeValue(nodes[i], "outcome"), getTextContentForTag(nodes[i], "Message"), getTextContentForTag(nodes[i], "StackTrace"), + getTextContentForTag(nodes[i], "StdOut"), )); } diff --git a/test/problems.test.ts b/test/problems.test.ts index 54354a7..2ce57cc 100644 --- a/test/problems.test.ts +++ b/test/problems.test.ts @@ -64,5 +64,5 @@ suite("Problems tests", () => { }); function GetTestResult(id: string, outcome: string, message: string, stackTrace: string) { - return new TestResult(id, outcome, message, stackTrace); + return new TestResult(id, outcome, message, stackTrace, ""); } diff --git a/test/restorePackages.js b/test/restorePackages.js new file mode 100644 index 0000000..a116ff0 --- /dev/null +++ b/test/restorePackages.js @@ -0,0 +1,8 @@ +let { exec } = require('child_process'); +let log = (err, stdout, stderr) => console.log(stdout) + +exec("dotnet restore ./test/fsxunittests/FSharpTests.fsproj", log); +exec("dotnet restore ./test/mstest/MSTestTests.csproj", log); +exec("dotnet restore ./test/nunit/NunitTests.csproj", log); +exec("dotnet restore ./test/nunitNet5/NunitTests.csproj", log); +exec("dotnet restore ./test/xunittests/XunitTests.csproj", log); \ No newline at end of file diff --git a/test/testNode.test.ts b/test/testNode.test.ts index cffe160..c87e0d0 100644 --- a/test/testNode.test.ts +++ b/test/testNode.test.ts @@ -92,7 +92,7 @@ suite("Icon tests", () => { }); function GetTestResult(id: string, outcome: string, className: string, method: string) { - const testResult = new TestResult(id, outcome, "", ""); + const testResult = new TestResult(id, outcome, "", "", ""); testResult.updateName(className, method); return testResult; }