-
Notifications
You must be signed in to change notification settings - Fork 97
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
Display test results in CodeLens #24
Conversation
+ Output the test results to the TRX logger with a temporary filename + Listen to changes to the temporary directory to see when the TRX file has been written to.
+ Add dependency on xmldom + Parse the XML into strongly types objects
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Thanks for your PR! Please help resolve the TSLint error in CI first. 😄
Sorry about that, still learning the ropes 😄 |
src/testResultsFile.ts
Outdated
const nodes = xdoc.documentElement.getElementsByTagName("UnitTestResult"); | ||
|
||
// TSLint wants to use for-of here, but nodes doesn't support it | ||
for (let i = 0; i < nodes.length; i++) { // tslint:disable-line |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
You could use for (const node of nodes)
: https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Statements/for...of
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I did try that, however, at runtime it throws an exception TypeError: undefined is not a function
as I don't believe HTMLCollection (returned by getElementsByTagName
) has the iterator
property so it can't be used in for-of. May be there's a better XML library I could use to parse the TRX file?
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
In that case, let's just keep this for loop.
src/testResultsFile.ts
Outdated
let timeout: NodeJS.Timer; | ||
this.watcher = fs.watch(tempFolder, (eventType, fileName) => { | ||
if ((fileName === TestResultsFile.ResultsFileName) && (eventType === "change")) { | ||
if (!timeout) { |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I think you could use clearTimeout
here
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I'm not sure that does the same thing, as it a timeout previously established by calling setTimeout
, however, we're calling it in the callback so there's nothing to cancel. I'm setting it to null so that I can see if I've already triggered a wait or not. I can introduce another flag variable if that would make the code easier to follow?
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I think you could refer to https://github.com/formulahendry/vscode-docker-explorer/blob/master/src/dockerTreeBase.ts#L21:31 The logic should be similar.
In current implementation, if the change event lasts for 30 seconds continuously (say every 100ms), parseResults
will be called 30 times.
With the clearTimeout
, it would be called only once (if the change event is 100ms, or less then 1000ms).
When the test is running, I guess the trx file will be changed continuously. I am not sure whether the TRX will always be a valid XML and could be read normally during the test is running. If it is, I think current implementation is good, since we could have a quick update when test is running.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Like it, done! 😄
src/testResultsFile.ts
Outdated
try { | ||
this.watcher.close(); | ||
fs.unlinkSync(this.resultsFile); | ||
} catch { |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
miss (error)
--> catch (error)
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Added
+ Use clearTimeout when filtering multiple file change events + Fix empty catch block
src/dotnetTestExplorer.ts
Outdated
* Gets the dotnet test argument to speicfy the output for the test results. | ||
*/ | ||
private outputTestResults(): string { | ||
return " --logger 'trx;LogFileName=" + this.resultsFile.fileName + "'"; |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I test it on Windows, it is failed, After I change single quote to double quote, it is working. How about you?
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
It did work for me, but I've replaced with double quotes and it still works for me so hopefully that should fix it for you
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I guess you are using PowerShell. Both are working on PowerShell, while single quote does not work on CMD.
src/testStatusCodeLensProvider.ts
Outdated
for (const symbol of symbols.filter((x) => x.kind === SymbolKind.Method)) { | ||
const fullName = symbol.containerName + "." + symbol.name; | ||
for (const result of results.values()) { | ||
if (result.testName.endsWith(fullName)) { |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
The symbol provider doesn't return anything for the namespace and the container of the class is empty, so would need to look at perhaps using omni-sharp directly? There's actually an issue with the test discovery with NUnit that only lists the method names, so if two tests exist with the same method name in different classes, NUnit only returns one of them.
Not sure how you want to approach this one? Accept it as a corner case until the extension uses omni-sharp to parse the files to discover the unit test methods?
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I think this corner case could be accepted.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
If you have interest to look at omni-sharp or other approach, it is warmly welcome! You could send PR in the future.
@@ -198,7 +215,7 @@ export class DotnetTestExplorer implements TreeDataProvider<TestNode> { | |||
return new Promise((c, e) => { | |||
try { | |||
const results = Executor | |||
.execSync(`dotnet test -t -v=q${this.checkBuildOption()}${this.checkRestoreOption()}`, this.testDirectoryPath) | |||
.execSync(`dotnet test -t -v=q${this.getDotNetTestOptions()}`, this.testDirectoryPath) |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
You did not add ${this.outputTestResults()}
, so the test result will not be shown when VS Code starts. Is it by design?
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Oh, please ignore. It only lists the tests.
src/testResultsFile.ts
Outdated
} | ||
|
||
export class TestResultsFile implements Disposable { | ||
private static readonly ResultsFileName = "TestExplorerResults.trx"; |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
What if we have multiple test project opened in different VS Code?
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I'm now using a temporary random folder to store the results, so now works across multiple instances
return this.onDidChangeCodeLensesEmitter.event; | ||
} | ||
|
||
public provideCodeLenses(document: TextDocument, token: CancellationToken): CodeLens[] | Thenable<CodeLens[]> { |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Try with xUnit, NUnit and MSTest. Seems only xUnit is showing the test result icon. It would be great if you could also support NUnit and MSTest. 😃
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
This should now be fixed in the latest version, I've tried xUnit, MSTest and NUnit (the later two have the same format)
src/testStatusCodeLensProvider.ts
Outdated
} | ||
|
||
private checkEnabledOption(): void { | ||
this.enabled = workspace.getConfiguration("csharp").get<boolean>("testsCodeLens.enabled", true); |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Not sure it is better to add an extra boolean setting, in case user want to only show the run test | debug test
. When user click run test
, our test result icon would not updated. This may confuse users.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Added an extra setting dotnet-test-explorer.showCodeLens
+ Map the test definition to the test result in the TRX file
+ Use double quotes instead of single quotes
+ Use a specific setting for this extension to determine whether to show/hide the CodeLens test status
+ Use a random temporary folder to store the test results
src/testResultsFile.ts
Outdated
// delay before we read anything to avoid doing too much work | ||
const me = this; | ||
let changeDelay: NodeJS.Timer; | ||
this.watcher = fs.watch(tempFolder, (eventType, fileName) => { |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Only do this when 'showCodeLens' is true
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Good catch, done
src/dotnetTestExplorer.ts
Outdated
* Gets the dotnet test argument to speicfy the output for the test results. | ||
*/ | ||
private outputTestResults(): string { | ||
return " --logger \"trx;LogFileName=" + this.resultsFile.fileName + "\""; |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
return empty string when 'showCodeLens' is false
src/testResultsFile.ts
Outdated
private watcher: fs.FSWatcher; | ||
|
||
public constructor() { | ||
const tempFolder = fs.mkdtempSync(path.join(os.tmpdir(), "test-explorer-")); |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Same here. Only do this when 'showCodeLens' is true.
Just want to make sure no unnecessary code is running
src/testResultsFile.ts
Outdated
|
||
public dispose(): void { | ||
try { | ||
this.watcher.close(); |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Also handle when when 'showCodeLens' is false
+ Only create the temproary folder if the CodeLens setting is enabled + Don't output the test results if CodeLens is disabled
src/testResultsFile.ts
Outdated
const me = this; | ||
let changeDelay: NodeJS.Timer; | ||
this.watcher = fs.watch(folder, (eventType, fileName) => { | ||
if ((fileName === TestResultsFile.ResultsFileName) && (eventType === "change")) { |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Test it on macOS, when first run the test, it will only have eventType === "rename". So we could just remove (eventType === "change")
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Removed
src/testStatusCodeLens.ts
Outdated
export class TestStatusCodeLens extends CodeLens { | ||
public static parseOutcome(outcome: string): string { | ||
if (outcome === "Passed") { | ||
return "✔️"; |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Test on macOS, it is showing a black check mark (https://emojipedia.org/heavy-check-mark/) which is hard to see.
Two improvements:
- Using another better check mark: https://emojipedia.org/white-heavy-check-mark/. Or you may have a better one? 😄
- Using the unicode, it would be good to maintain
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I've added settings now so the user can override what we display.
I've kept the heavy check mark for platforms apart from OS X, in which case it uses the white heavy check mark (I'm not a big fan of it, but I can't find any alternatives).
Since the user can specify the text, they could even switch to "passing", "FAILING" etc if they want so hopefully this is OK?
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Looks great to me!
src/testStatusCodeLens.ts
Outdated
if (outcome === "Passed") { | ||
return "✔️"; | ||
} else if (outcome === "Failed") { | ||
return "❌"; |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Same here, using unicode: https://emojipedia.org/cross-mark/
src/testStatusCodeLens.ts
Outdated
} else if (outcome === "Failed") { | ||
return "❌"; | ||
} else if (outcome === "NotExecuted") { | ||
return "️️⚠️"; |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Same here, using unicode
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I left some comments which should just have some small code change. I think we are closing to ship it!
+ Moved fields after methods
src/utility.ts
Outdated
const osx = platform() === "darwin"; | ||
|
||
Utility.showCodeLens = configuration.get<boolean>("showCodeLens", true); | ||
Utility.failed = Utility.getLensText(configuration, "codeLensPassed", "\u274c"); // Cross Mark |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
codeLensPassed?
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Good catch, I've actually tested them all this time before submitting (and had my ☕️!)
src/utility.ts
Outdated
Utility.showCodeLens = configuration.get<boolean>("showCodeLens", true); | ||
Utility.failed = Utility.getLensText(configuration, "codeLensPassed", "\u274c"); // Cross Mark | ||
Utility.passed = Utility.getLensText(configuration, "codeLensPassed", osx ? "\u2705" : "\u2714"); // White Heavy Check Mark / Heavy Check Mark | ||
Utility.skipped = Utility.getLensText(configuration, "codeLensPassed", "\u26a0"); // Warning |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
codeLensPassed?
Don't commit code before coffee...
@samcragg , it is all good for C# in xUnit, NUnit and MSTest, also Windows and macOS. I just try it on F# but not show the CodeLens. I think F# extension also implements the |
I just realize that you registerCodeLensProvider for csharp only. I think it is good enough to support C# first. 😃 Thank you for your contribution! This feature is awesome! If you have interest, welcome PR to support F# or any other features. |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Thanks! I'll take a look at F# and submit another PR. I also want to make it so you can click on the lens and it shows you the test output inline (similar to how clicking on the references CodeLens shows them inline) so you can see why a test failed without having to dig through the output yourself (the output is in the XML, just need somewhere to display it). |
Thanks @samcragg ! That would be great! |
Hope you don't mind me suggesting an enhancement. Also, it's my first foray into TypeScript so happy to change things to match your preferences. I wanted to display the test result status next to the method, like VS does so I looked at getting the results from the most recent test run.:
The results are outputted to a TRX file and then that file is parsed to get the test results. Finally, the
vscode.executeDocumentSymbolProvider
command is used to get all the symbols in the current document to match the method to the test name.Things to note. I've tested it with xUnit - it probably needs checking with NUnit as well. Also, I'm using emoji's to display the results, as you can only have text in the CodeLens. This looks fine on Windows 10 but might need a switch to use normal text instead of emoji's in case it looks wrong on other OS's.
Finally, there are things I'd like to enhance but not sure how to implement. I'd like it so you can click on the status and a popup show you the test output (useful for failing tests).