Skip to content

Refactor tests - #7702

Open
hassan20990 wants to merge 2 commits into
microsoft:mainfrom
hassan20990:refactor_tests
Open

Refactor tests#7702
hassan20990 wants to merge 2 commits into
microsoft:mainfrom
hassan20990:refactor_tests

Conversation

@hassan20990

Copy link
Copy Markdown

No description provided.

@hassan20990
hassan20990 requested a review from a team as a code owner August 23, 2023 12:57
@sonarqubecloud

Copy link
Copy Markdown

Kudos, SonarCloud Quality Gate passed!    Quality Gate passed

Bug A 0 Bugs
Vulnerability A 0 Vulnerabilities
Security Hotspot A 0 Security Hotspots
Code Smell A 0 Code Smells

No Coverage information No Coverage information
No Duplication information No Duplication information

@rchiodo

Rich Chiodo (rchiodo) commented Jul 7, 2026

Copy link
Copy Markdown
Contributor

🔒 Automated review in progress — Rich Chiodo (@rchiodo) is auto-reviewing this PR.

Copilot AI left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

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.OpenProject to route more IVs* API calls through new UI-thread helper methods.
  • Sanitizes AutomationElement.NameProperty values 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.

Comment on lines +160 to +161
var solutionName = Path.GetFileNameWithoutExtension(_solution.Filename);
return $"Solution '{solutionName}'";
Comment on lines +96 to +101
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();
Comment on lines 35 to 38
using Microsoft.VisualStudio.TestTools.UnitTesting;
using Microsoft.VisualStudio.Threading;
using Microsoft.VisualStudioTools;
using IOleServiceProvider = Microsoft.VisualStudio.OLE.Interop.IServiceProvider;
Comment on lines +942 to 945
t = CreateTaskOnUIThread(async () =>
{
ErrorHandler.ThrowOnFailure(solution.OpenSolutionFile((uint)0, fullPath));
});
Comment on lines +949 to 953
t = CreateTaskOnUIThread(async () =>
{
Guid guidNull = Guid.Empty;
Guid iidUnknown = Guid.Empty;
IntPtr projPtr;
Comment on lines 1056 to 1058
object o;
ErrorHandler.ThrowOnFailure(vsProject.GetProperty(VSConstants.VSITEMID_ROOT, (int)__VSHPROPID.VSHPROPID_ExtObject, out o));
var project = (Project)o;
Comment thread .vscode/settings.json
Comment on lines +1 to +5
{
"marquee.widgets.npm-stats.packageNames": [
"ptvs"
]
} No newline at end of file
@bschnurr

Copy link
Copy Markdown
Member

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));

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

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))

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

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.

Comment thread .vscode/settings.json
"marquee.widgets.npm-stats.packageNames": [
"ptvs"
]
} No newline at end of file

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

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 () =>
{

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

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.

@rchiodo

Copy link
Copy Markdown
Contributor

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 .Task/Wait pattern is a latent deadlock footgun if OpenProject is ever called on the UI thread, and the .vscode/settings.json addition looks unrelated to this PR.

@rchiodo Rich Chiodo (rchiodo) left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Approved via Review Center.

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Approved via Review Center.


var solutionName = Path.GetFileNameWithoutExtension(_solution.Filename);
return $"Solution '{solutionName}'";
}

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

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.

@rchiodo

Copy link
Copy Markdown
Contributor

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.

@rchiodo Rich Chiodo (rchiodo) left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Approved via Review Center.

@heejaechang

Heejae Chang (heejaechang) commented Jul 20, 2026

Copy link
Copy Markdown

🔒 Automated review in progress — Heejae Chang (@heejaechang) is auto-reviewing this PR.

}),
"Wrong number of loaded projects"
);
}

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

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;

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

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.

Comment thread .vscode/settings.json
"marquee.widgets.npm-stats.packageNames": [
"ptvs"
]
} No newline at end of file

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

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.

@rchiodo Rich Chiodo (rchiodo) left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Approved via Review Center.

@rchiodo Rich Chiodo (rchiodo) added the review-auto:approved Automated review: no blocking findings (approval posted). label Aug 5, 2026
@microsoft-github-policy-service

Copy link
Copy Markdown
Contributor

hassan20990 please read the following Contributor License Agreement(CLA). If you agree with the CLA, please reply with the following information.

@microsoft-github-policy-service agree [company="{your company}"]

Options:

  • (default - no company specified) I have sole ownership of intellectual property rights to my Submissions and I am not making Submissions in the course of work for my employer.
@microsoft-github-policy-service agree
  • (when company given) I am making Submissions in the course of work for my employer (or my employer has intellectual property rights in my Submissions by contract or applicable law). I have permission from my employer to make Submissions and enter into this Agreement on behalf of my employer. By signing below, the defined term “You” includes me and my employer.
@microsoft-github-policy-service agree company="Microsoft"
Contributor License Agreement

Contribution License Agreement

This Contribution License Agreement (“Agreement”) is agreed to by the party signing below (“You”),
and conveys certain license rights to Microsoft Corporation and its affiliates (“Microsoft”) for Your
contributions to Microsoft open source projects. This Agreement is effective as of the latest signature
date below.

  1. Definitions.
    “Code” means the computer software code, whether in human-readable or machine-executable form,
    that is delivered by You to Microsoft under this Agreement.
    “Project” means any of the projects owned or managed by Microsoft and offered under a license
    approved by the Open Source Initiative (www.opensource.org).
    “Submit” is the act of uploading, submitting, transmitting, or distributing code or other content to any
    Project, including but not limited to communication on electronic mailing lists, source code control
    systems, and issue tracking systems that are managed by, or on behalf of, the Project for the purpose of
    discussing and improving that Project, but excluding communication that is conspicuously marked or
    otherwise designated in writing by You as “Not a Submission.”
    “Submission” means the Code and any other copyrightable material Submitted by You, including any
    associated comments and documentation.
  2. Your Submission. You must agree to the terms of this Agreement before making a Submission to any
    Project. This Agreement covers any and all Submissions that You, now or in the future (except as
    described in Section 4 below), Submit to any Project.
  3. Originality of Work. You represent that each of Your Submissions is entirely Your original work.
    Should You wish to Submit materials that are not Your original work, You may Submit them separately
    to the Project if You (a) retain all copyright and license information that was in the materials as You
    received them, (b) in the description accompanying Your Submission, include the phrase “Submission
    containing materials of a third party:” followed by the names of the third party and any licenses or other
    restrictions of which You are aware, and (c) follow any other instructions in the Project’s written
    guidelines concerning Submissions.
  4. Your Employer. References to “employer” in this Agreement include Your employer or anyone else
    for whom You are acting in making Your Submission, e.g. as a contractor, vendor, or agent. If Your
    Submission is made in the course of Your work for an employer or Your employer has intellectual
    property rights in Your Submission by contract or applicable law, You must secure permission from Your
    employer to make the Submission before signing this Agreement. In that case, the term “You” in this
    Agreement will refer to You and the employer collectively. If You change employers in the future and
    desire to Submit additional Submissions for the new employer, then You agree to sign a new Agreement
    and secure permission from the new employer before Submitting those Submissions.
  5. Licenses.
  • Copyright License. You grant Microsoft, and those who receive the Submission directly or
    indirectly from Microsoft, a perpetual, worldwide, non-exclusive, royalty-free, irrevocable license in the
    Submission to reproduce, prepare derivative works of, publicly display, publicly perform, and distribute
    the Submission and such derivative works, and to sublicense any or all of the foregoing rights to third
    parties.
  • Patent License. You grant Microsoft, and those who receive the Submission directly or
    indirectly from Microsoft, a perpetual, worldwide, non-exclusive, royalty-free, irrevocable license under
    Your patent claims that are necessarily infringed by the Submission or the combination of the
    Submission with the Project to which it was Submitted to make, have made, use, offer to sell, sell and
    import or otherwise dispose of the Submission alone or with the Project.
  • Other Rights Reserved. Each party reserves all rights not expressly granted in this Agreement.
    No additional licenses or rights whatsoever (including, without limitation, any implied licenses) are
    granted by implication, exhaustion, estoppel or otherwise.
  1. Representations and Warranties. You represent that You are legally entitled to grant the above
    licenses. You represent that each of Your Submissions is entirely Your original work (except as You may
    have disclosed under Section 3). You represent that You have secured permission from Your employer to
    make the Submission in cases where Your Submission is made in the course of Your work for Your
    employer or Your employer has intellectual property rights in Your Submission by contract or applicable
    law. If You are signing this Agreement on behalf of Your employer, You represent and warrant that You
    have the necessary authority to bind the listed employer to the obligations contained in this Agreement.
    You are not expected to provide support for Your Submission, unless You choose to do so. UNLESS
    REQUIRED BY APPLICABLE LAW OR AGREED TO IN WRITING, AND EXCEPT FOR THE WARRANTIES
    EXPRESSLY STATED IN SECTIONS 3, 4, AND 6, THE SUBMISSION PROVIDED UNDER THIS AGREEMENT IS
    PROVIDED WITHOUT WARRANTY OF ANY KIND, INCLUDING, BUT NOT LIMITED TO, ANY WARRANTY OF
    NONINFRINGEMENT, MERCHANTABILITY, OR FITNESS FOR A PARTICULAR PURPOSE.
  2. Notice to Microsoft. You agree to notify Microsoft in writing of any facts or circumstances of which
    You later become aware that would make Your representations in this Agreement inaccurate in any
    respect.
  3. Information about Submissions. You agree that contributions to Projects and information about
    contributions may be maintained indefinitely and disclosed publicly, including Your name and other
    information that You submit with Your Submission.
  4. Governing Law/Jurisdiction. This Agreement is governed by the laws of the State of Washington, and
    the parties consent to exclusive jurisdiction and venue in the federal courts sitting in King County,
    Washington, unless no federal subject matter jurisdiction exists, in which case the parties consent to
    exclusive jurisdiction and venue in the Superior Court of King County, Washington. The parties waive all
    defenses of lack of personal jurisdiction and forum non-conveniens.
  5. Entire Agreement/Assignment. This Agreement is the entire agreement between the parties, and
    supersedes any and all prior agreements, understandings or communications, written or oral, between
    the parties relating to the subject matter hereof. This Agreement may be assigned by Microsoft.

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Approved via Review Center.

@StellaHuang95

Stella Huang (StellaHuang95) commented Aug 10, 2026

Copy link
Copy Markdown
Contributor

🔒 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);
}

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

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));

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

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}'";

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

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)) {

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

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.

Comment thread .vscode/settings.json
"marquee.widgets.npm-stats.packageNames": [
"ptvs"
]
} No newline at end of file

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

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.

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Approved via Review Center.

@rchiodo Rich Chiodo (rchiodo) left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Approved via Review Center.

@bschnurr Bill Schnurr (bschnurr) left a comment

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Approved via Review Center.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

review-auto:approved Automated review: no blocking findings (approval posted).

Projects

None yet

Development

Successfully merging this pull request may close these issues.

7 participants