Skip to content

Extract duplicate STA thread dispatch pattern into StaThreadHelper#9305

Open
Copilot wants to merge 3 commits into
mainfrom
copilot/duplicate-code-sta-thread-dispatch-pattern
Open

Extract duplicate STA thread dispatch pattern into StaThreadHelper#9305
Copilot wants to merge 3 commits into
mainfrom
copilot/duplicate-code-sta-thread-dispatch-pattern

Conversation

Copilot AI commented Jun 21, 2026

Copy link
Copy Markdown
Contributor

The same ~40-line STA thread dispatch block (check → create named thread → set STA → start → join → warn on non-Windows) was copy-pasted in TestClassInfo.Initializer.cs and TestClassInfo.Cleanup.cs, making any bug fix or behavioral change require parallel edits.

Changes

  • New StaThreadHelper.RunOnStaThreadIfNeededAsync<TResult> in MSTestAdapter.PlatformServices/Helpers/ — generic helper encapsulating the full STA dispatch pattern:

    • Checks needsSta && Windows && currentThread != STA
    • Creates a named STA thread, starts it, joins it
    • Catches only ThreadStateException | ThreadInterruptedException from Join() (the only types documented to be thrown), logs with thread name context, returns defaultResult
    • Logs the non-Windows STA warning when applicable
    • Falls back to direct await otherwise
  • TestClassInfo.Initializer.cs and TestClassInfo.Cleanup.cs — ~80 lines of duplicated dispatch replaced with 4–5 line call sites each:

// Before: ~40 lines of inline STA thread setup
bool isSTATestClass = ClassAttribute is STATestClassAttribute;
bool isWindowsOS = RuntimeInformation.IsOSPlatform(OSPlatform.Windows);
if (isSTATestClass && isWindowsOS && Thread.CurrentThread.GetApartmentState() != ApartmentState.STA)
{
    TestResult? result = null;
    var entryPointThread = new Thread(() => result = DoRunAsync().GetAwaiter().GetResult())
    { Name = "MSTest STATestClass ClassCleanup" };
    entryPointThread.SetApartmentState(ApartmentState.STA);
    entryPointThread.Start();
    try { entryPointThread.Join(); }
    catch (Exception ex) { /* log */ }
    return result;
}
else
{
    if (!isWindowsOS && isSTATestClass) { /* warn */ }
    return await DoRunAsync().ConfigureAwait(false);
}

// After: 4 lines
return await StaThreadHelper.RunOnStaThreadIfNeededAsync(
    needsSta: ClassAttribute is STATestClassAttribute,
    action: DoRunAsync,
    threadName: "MSTest STATestClass ClassCleanup",
    defaultResult: null).ConfigureAwait(false);

TestExecutionManager.DefaultFactoryAsync and MSTestExecutor.RunTestsFromRightContextAsync are out of scope — both use structurally different mechanisms (TCS-based exception bridging and Task.Run(Join, cancellationToken) respectively) that don't fit this abstraction.

Copilot AI requested review from Copilot and removed request for Copilot June 21, 2026 05:59
Reduces ~80 lines of duplicate code across TestClassInfo.Initializer.cs
and TestClassInfo.Cleanup.cs into a single shared helper method
StaThreadHelper.RunOnStaThreadIfNeededAsync<TResult> in the Helpers folder.

The helper handles:
- Checking if STA thread is needed (needsSta && Windows && !already STA)
- Creating and starting a named STA thread
- Joining the thread with error logging on failure
- Logging a warning when STA is requested on non-Windows
- Falling back to direct await when STA is not needed

Co-authored-by: Evangelink <11340282+Evangelink@users.noreply.github.com>
Copilot AI requested review from Copilot and removed request for Copilot June 21, 2026 06:15
…dHelper

- Narrow catch to ThreadStateException|ThreadInterruptedException as
  those are the only exception types Thread.Join() can throw
- Include thread name in error log message for better diagnostics
- Extract default TestResult to a local variable in Initializer.cs
  to reduce line length

Co-authored-by: Evangelink <11340282+Evangelink@users.noreply.github.com>
Copilot AI requested review from Copilot and removed request for Copilot June 21, 2026 06:17
Copilot AI changed the title [WIP] Refactor duplicate STA thread dispatch code in MSTest adapter Extract duplicate STA thread dispatch pattern into StaThreadHelper Jun 21, 2026
Copilot AI requested a review from Evangelink June 21, 2026 06:17
@Evangelink Evangelink marked this pull request as ready for review June 21, 2026 07:20
Copilot AI review requested due to automatic review settings June 21, 2026 07:20

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 reduces duplication in the MSTest adapter’s STA dispatch logic by centralizing the “run on a dedicated STA thread when required” pattern into a single helper, and updating ClassInitialize/ClassCleanup to use it.

Changes:

  • Added StaThreadHelper.RunOnStaThreadIfNeededAsync<TResult> to encapsulate STA thread creation, start, join, and non-Windows warning behavior.
  • Replaced duplicated STA dispatch blocks in TestClassInfo.Initializer.cs and TestClassInfo.Cleanup.cs with a single helper call each.
  • Standardized Join() exception handling/logging in the extracted helper.
Show a summary per file
File Description
src/Adapter/MSTestAdapter.PlatformServices/Helpers/StaThreadHelper.cs Introduces a reusable STA-thread dispatch helper used by adapter execution paths.
src/Adapter/MSTestAdapter.PlatformServices/Execution/TestClassInfo.Initializer.cs Uses the helper for ClassInitialize STA dispatch, removing duplicated thread/join logic.
src/Adapter/MSTestAdapter.PlatformServices/Execution/TestClassInfo.Cleanup.cs Uses the helper for ClassCleanup STA dispatch, removing duplicated thread/join logic.

Copilot's findings

  • Files reviewed: 3/3 changed files
  • Comments generated: 2

Comment on lines +46 to +50
PlatformServiceProvider.Instance.AdapterTraceLogger.Error(
$"Failed to join STA thread '{threadName}': {ex}");
}

return defaultResult;
Comment on lines +175 to +179
return await StaThreadHelper.RunOnStaThreadIfNeededAsync(
needsSta: ClassAttribute is STATestClassAttribute,
action: DoRunAsync,
threadName: "MSTest STATestClass ClassInitialize",
defaultResult: initializeErrorResult).ConfigureAwait(false);
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

3 participants