Refactor tests - #7702
Conversation
|
Kudos, SonarCloud Quality Gate passed! |
|
🔒 Automated review in progress — Rich Chiodo (@rchiodo) is auto-reviewing this PR. |
There was a problem hiding this comment.
Pull request overview
This PR refactors parts of the UI test utilities to make Solution Explorer interactions and project loading more robust, primarily by adjusting solution-node identification, adding UI-thread helpers for IVs* interactions, and hardening TreeView node-name comparisons.
Changes:
- Simplifies how the solution root node text is computed for Solution Explorer interactions.
- Updates
VisualStudioApp.OpenProjectto route more IVs* API calls through new UI-thread helper methods. - Sanitizes
AutomationElement.NamePropertyvalues during Solution Explorer tree traversal to handle non-printable characters.
Reviewed changes
Copilot reviewed 5 out of 5 changed files in this pull request and generated 7 comments.
Show a summary per file
| File | Description |
|---|---|
| Common/Tests/Utilities.UI/UI/VisualStudioInstance.cs | Changes solution root node label construction used for Solution Explorer lookups. |
| Common/Tests/Utilities.UI/UI/VisualStudioApp.cs | Refactors project/solution open flow and adds UI-thread helper methods for IVs* calls. |
| Common/Tests/Utilities.UI/UI/TreeView.cs | Sanitizes node names (regex) before comparing path segments. |
| Common/Tests/Utilities.UI/TestUtilities.UI.csproj | Adds a project reference to VSCommon. |
| .vscode/settings.json | Adds a VS Code workspace setting for a specific extension. |
| var solutionName = Path.GetFileNameWithoutExtension(_solution.Filename); | ||
| return $"Solution '{solutionName}'"; |
| var name = (node.GetCurrentPropertyValue(AutomationElement.NameProperty) as string); | ||
|
|
||
| // Sometimes AutomationElement.NameProperty contains non-printable characters that mess up the | ||
| // string compare, so get rid of those. | ||
| // See https://stackoverflow.com/questions/40564692/c-sharp-regex-to-remove-non-printable-characters-and-control-characters-in-a | ||
| name = Regex.Replace(name, @"\p{C}+", string.Empty).Trim(); |
| using Microsoft.VisualStudio.TestTools.UnitTesting; | ||
| using Microsoft.VisualStudio.Threading; | ||
| using Microsoft.VisualStudioTools; | ||
| using IOleServiceProvider = Microsoft.VisualStudio.OLE.Interop.IServiceProvider; |
| t = CreateTaskOnUIThread(async () => | ||
| { | ||
| ErrorHandler.ThrowOnFailure(solution.OpenSolutionFile((uint)0, fullPath)); | ||
| }); |
| t = CreateTaskOnUIThread(async () => | ||
| { | ||
| Guid guidNull = Guid.Empty; | ||
| Guid iidUnknown = Guid.Empty; | ||
| IntPtr projPtr; |
| object o; | ||
| ErrorHandler.ThrowOnFailure(vsProject.GetProperty(VSConstants.VSITEMID_ROOT, (int)__VSHPROPID.VSHPROPID_ExtObject, out o)); | ||
| var project = (Project)o; |
| { | ||
| "marquee.widgets.npm-stats.packageNames": [ | ||
| "ptvs" | ||
| ] | ||
| } No newline at end of file |
|
Copilot resolve the merge conflicts in this pull request |
| while (!t.Wait(1000, cts.Token)) { | ||
| while (!t.Wait(1000, cts.Token)) | ||
| { | ||
| ErrorHandler.ThrowOnFailure(uiShell.GetDialogOwnerHwnd(out hwnd)); |
There was a problem hiding this comment.
The threading fix is incomplete: the new comment states "everything from the IVs* APIs need to run on the UI thread," but IVsUIShell.GetDialogOwnerHwnd here — and vsProject.GetProperty at ~1057 — still run on the calling background thread while their neighbors (GetSolutionInfo, EnumerateLoadedProjects, GetGuidOfProject, ReloadProject) were wrapped in RunOnUIThread. Off-thread IVs* calls can throw RPC_E_WRONG_THREAD or silently COM-marshal. Either wrap these in RunOnUIThread too, or narrow the "everything" wording to match reality.
| { | ||
| try | ||
| { | ||
| if (!t.Wait(1000, cts.Token)) |
There was a problem hiding this comment.
CreateTaskOnUIThread returns the raw .Task from a JoinableTask, and the caller blocks with t.Wait(1000, cts.Token). Extracting and blocking on JoinableTask.Task bypasses JTF's joinable-collection deadlock mitigation. It's safe today only because OpenProject runs off the UI thread and the lambda bodies are synchronous; if OpenProject is ever invoked on the UI thread, the scheduled main-thread continuation can't run while Wait() blocks that thread, deadlocking until the 30s CTS fires. Prefer returning the JoinableTask and driving the dialog-poll loop via Join()/JoinAsync.
| "marquee.widgets.npm-stats.packageNames": [ | ||
| "ptvs" | ||
| ] | ||
| } No newline at end of file |
There was a problem hiding this comment.
This file appears to be unrelated scope creep in a "Refactor tests" PR — it only sets a personal Marquee npm-stats widget config and is unrelated to test reliability. Consider dropping it from the change. (It is also missing a trailing newline.)
| else | ||
| { | ||
| t = CreateTaskOnUIThread(async () => | ||
| { |
There was a problem hiding this comment.
These async () => { ... } lambdas contain no await, producing CS1998 ("runs synchronously"). Harmless at runtime since the body executes on the UI thread after SwitchToMainThreadAsync() inside the helper, but if the harness project sets TreatWarningsAsErrors the build breaks. Drop the async keyword (return Task.CompletedTask) to silence the warning.
|
Solid test-reliability refactor. A few non-blocking notes: the UI-thread marshaling is applied inconsistently (a couple of IVs* calls remain off-thread), the JTF |
Rich Chiodo (rchiodo)
left a comment
There was a problem hiding this comment.
Approved via Review Center.
Heejae Chang (heejaechang)
left a comment
There was a problem hiding this comment.
Approved via Review Center.
|
|
||
| var solutionName = Path.GetFileNameWithoutExtension(_solution.Filename); | ||
| return $"Solution '{solutionName}'"; | ||
| } |
There was a problem hiding this comment.
Collapsing SolutionNodeText to $"Solution '{solutionName}'" drops the (N of N projects) suffix and no longer consults _solution.Projects. This may be correct if a newer VS version removed the count from the solution root node, but it's an unexplained behavioral change. A one-line comment citing the VS-version behavior that motivated it would help; any consumer/FindNode match still expecting the old count text would silently mismatch.
|
Overall this looks fine to merge. A couple of non-blocking notes: the UI-thread wrapping is applied inconsistently across the IVs* calls, and there's an unrelated .vscode/settings.json change that should probably be dropped from a test-refactor PR. |
Rich Chiodo (rchiodo)
left a comment
There was a problem hiding this comment.
Approved via Review Center.
|
🔒 Automated review in progress — Heejae Chang (@heejaechang) is auto-reviewing this PR. |
| }), | ||
| "Wrong number of loaded projects" | ||
| ); | ||
| } |
There was a problem hiding this comment.
Warning · Non-blocking recommendation
vsProject.GetProperty(...) is an IVsHierarchy call but remains outside RunOnUIThread, unlike the surrounding solution and hierarchy operations. This can still hit a wrong-thread COM failure; retrieve ExtObject within the UI-thread block.
| { | ||
| Guid guidNull = Guid.Empty; | ||
| Guid iidUnknown = Guid.Empty; | ||
| IntPtr projPtr; |
There was a problem hiding this comment.
Warning · Non-blocking recommendation
These async lambdas contain no await, producing CS1998 and suggesting asynchronous work that does not occur. Return Task.CompletedTask from a non-async lambda, or provide an Action overload for synchronous UI-thread work.
| "marquee.widgets.npm-stats.packageNames": [ | ||
| "ptvs" | ||
| ] | ||
| } No newline at end of file |
There was a problem hiding this comment.
Warning · Non-blocking recommendation
This Marquee widget setting is unrelated to the UI test-utility refactor and adds editor-specific configuration to the change. Please remove it from this PR.
Rich Chiodo (rchiodo)
left a comment
There was a problem hiding this comment.
Approved via Review Center.
|
hassan20990 please read the following Contributor License Agreement(CLA). If you agree with the CLA, please reply with the following information.
Contributor License AgreementContribution License AgreementThis Contribution License Agreement (“Agreement”) is agreed to by the party signing below (“You”),
|
Stella Huang (StellaHuang95)
left a comment
There was a problem hiding this comment.
Approved via Review Center.
|
🔒 Automated review in progress — Stella Huang (@StellaHuang95) is auto-reviewing this PR. |
| if (!string.IsNullOrEmpty(slnFile)) { | ||
| Console.WriteLine("Closing {0}", slnFile); | ||
| solution.CloseSolutionElement(0, null, 0); | ||
| } |
There was a problem hiding this comment.
Warning · Non-blocking recommendation
Only closing when slnFile is nonempty can leave an unsaved solution open and leak state into the next test. Preserve the previous close behavior after a successful GetSolutionInfo call.
| while (!t.Wait(1000, cts.Token)) { | ||
| while (!t.Wait(1000, cts.Token)) | ||
| { | ||
| ErrorHandler.ThrowOnFailure(uiShell.GetDialogOwnerHwnd(out hwnd)); |
There was a problem hiding this comment.
Warning · Non-blocking recommendation
The UI-thread boundary is incomplete: IVsUIShell.GetDialogOwnerHwnd here and IVsHierarchy.GetProperty below still execute outside RunOnUIThread. Move these calls behind the same boundary to apply the thread-affinity fix consistently.
| ); | ||
|
|
||
| var solutionName = Path.GetFileNameWithoutExtension(_solution.Filename); | ||
| return $"Solution '{solutionName}'"; |
There was a problem hiding this comment.
Warning · Non-blocking recommendation
TreeView.FindNode uses exact matching, so this change assumes all supported Visual Studio versions expose only Solution 'Name'. Match the stable solution-name prefix or support both known label formats with regression coverage.
| // See https://stackoverflow.com/questions/40564692/c-sharp-regex-to-remove-non-printable-characters-and-control-characters-in-a | ||
| name = Regex.Replace(name, @"\p{C}+", string.Empty).Trim(); | ||
|
|
||
| if (name.Equals(splitPath[depth], StringComparison.CurrentCulture)) { |
There was a problem hiding this comment.
Warning · Non-blocking recommendation
Removing all \p{C} characters and trimming only the automation label changes matching semantics and can reject legitimate names containing format characters or whitespace. Apply the narrow normalization required by the observed failure consistently to both values, with coverage for the hidden-character case.
| "marquee.widgets.npm-stats.packageNames": [ | ||
| "ptvs" | ||
| ] | ||
| } No newline at end of file |
There was a problem hiding this comment.
Info · Optional note
This Marquee extension configuration is unrelated to the test refactor and adds contributor-specific workspace state. Remove it unless it is an intentional repository-wide setting with separate justification.
Stella Huang (StellaHuang95)
left a comment
There was a problem hiding this comment.
Approved via Review Center.
Rich Chiodo (rchiodo)
left a comment
There was a problem hiding this comment.
Approved via Review Center.
Bill Schnurr (bschnurr)
left a comment
There was a problem hiding this comment.
Approved via Review Center.








No description provided.