Skip to content

Fix XUnitWrapperGenerator: testArguments out of scope in ConditionalTest else branch for MemberData theories#126505

Merged
jkoritzinsky merged 3 commits intomainfrom
copilot/update-xunitwrappergenerator-code
Apr 12, 2026
Merged

Fix XUnitWrapperGenerator: testArguments out of scope in ConditionalTest else branch for MemberData theories#126505
jkoritzinsky merged 3 commits intomainfrom
copilot/update-xunitwrappergenerator-code

Conversation

Copy link
Copy Markdown
Contributor

Copilot AI commented Apr 3, 2026

When a [Theory] with [MemberData] is also annotated with a conditional skip attribute ([SkipOnPlatform], [SkipOnCoreClr], etc.), the generator wraps the MemberDataTest inside a ConditionalTest. The else branch of that conditional reports the skipped test using MemberDataTest.TestNameExpression — which was previously copied verbatim from the inner BasicTestMethod.TestNameExpression. That expression references the testArguments foreach loop variable, which is only in scope inside the if branch, producing:

error CS0103: The name 'testArguments' does not exist in the current context

Fix (ITestInfo.csMemberDataTest constructor):

  • Reorder assignments so Method/ContainingType are set first
  • Replace TestNameExpression = innerTest.TestNameExpression with a static literal: $"\"{externAlias}::{ContainingType}.{Method}(...)\""
  • The inner BasicTestMethod.TestNameExpression (used inside the foreach loop for per-iteration filter/reporting) is unchanged

…ta with conditional attributes

When a Theory test uses MemberDataAttribute and is also decorated with conditional
skip attributes (SkipOnPlatform, SkipOnCoreClr, etc.), the generator wraps the
MemberDataTest inside a ConditionalTest. The ConditionalTest.GenerateTestExecution()
puts the foreach loop (from MemberDataTest) inside the if-condition block, but the
else block uses MemberDataTest.TestNameExpression which was previously copied from
the inner BasicTestMethod's TestNameExpression. That expression contains a reference
to the testArguments loop variable (e.g., testArguments[0], string.Join(",", testArguments)),
causing CS0103 since testArguments is only in scope inside the foreach loop.

Fix: Change MemberDataTest.TestNameExpression to use a static literal string
"alias::Type.Method(...)" that doesn't reference any loop variables. The inner
BasicTestMethod.TestNameExpression still references testArguments for per-iteration
reporting inside the foreach loop.

Fixes: error CS0103: The name 'testArguments' does not exist in the current context

Agent-Logs-Url: https://github.com/dotnet/runtime/sessions/ff0bc935-d962-4daf-891b-5592aab629f4

Co-authored-by: jkoritzinsky <1571408+jkoritzinsky@users.noreply.github.com>
Copilot AI changed the title [WIP] Fix generated code for XUnitWrapperGenerator handling Theory dynamic member data Fix XUnitWrapperGenerator: testArguments out of scope in ConditionalTest else branch for MemberData theories Apr 3, 2026
Copilot AI requested a review from jkoritzinsky April 3, 2026 15:11
@dotnet-policy-service
Copy link
Copy Markdown
Contributor

Tagging subscribers to this area: @agocke
See info in area-owners.md if you want to be subscribed.

@jkoritzinsky jkoritzinsky marked this pull request as ready for review April 4, 2026 02:19
Copilot AI review requested due to automatic review settings April 4, 2026 02:19
Copy link
Copy Markdown
Contributor

Copilot AI left a comment

Choose a reason for hiding this comment

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

Pull request overview

Fixes a C# compile error in the XUnitWrapperGenerator output when [Theory] + [MemberData] tests are wrapped in a ConditionalTest (e.g., via [SkipOnPlatform]), by ensuring the else-branch skip-reporting name does not reference the foreach loop variable (testArguments) that’s only in scope in the if branch.

Changes:

  • Updates MemberDataTest to use a static TestNameExpression literal (no loop variable reference).
  • Reorders MemberDataTest constructor assignments so Method/ContainingType are set before building the test name.
  • Adds an explanatory comment about the scope issue (ConditionalTest else branch vs foreach scope).

@danmoseley
Copy link
Copy Markdown
Member

build analysis stuck

@github-actions
Copy link
Copy Markdown
Contributor

🤖 Copilot Code Review — PR #126505

Note

This review was generated by Copilot using multi-model analysis (Claude Opus 4.6 primary, Claude Sonnet 4.6 + GPT-5.3-codex secondary).

Holistic Assessment

Motivation: The bug is real and clear. When ConditionalTest wraps a MemberDataTest, the generated else branch references testArguments — a loop variable only in scope inside the foreach in the if branch — causing CS0103. This means any [Theory] with [MemberData] combined with conditional skip attributes ([SkipOnPlatform], [SkipOnCoreClr], etc.) produces uncompilable test wrappers.

Approach: The fix is minimal and correct. Changing MemberDataTest.TestNameExpression to a static literal avoids the scope issue while preserving per-argument reporting inside the foreach loop via the inner BasicTestMethod.TestNameExpression. Alternative approaches (moving the foreach to encompass both branches, special-casing in ConditionalTest, adding a separate OutOfScopeTestNameExpression) would all be more complex for no added benefit.

Summary: ✅ LGTM. The fix correctly resolves a compile-time error in generated code with a focused 3-line change. All code paths were traced and verified correct. No new issues introduced. All three models (Claude Opus 4.6, Claude Sonnet 4.6, GPT-5.3-codex) agree on the verdict.


Detailed Findings

✅ Correctness — Fix resolves the CS0103 scope error

Traced the full code-generation flow:

Generated code site TestNameExpression source Inside foreach? Valid?
BasicTestMethodWrapTestExecutionWithReporting(..., this) BasicTestMethod (dynamic, refs testArguments) ✅ Yes
ConditionalTest else → GenerateSkippedTestReporting(_innerTest) MemberDataTest.TestNameExpression ❌ No ✅ Fixed by this PR
WrapperLibraryTestSummaryReporting filter/report calls BasicTestMethod.TestNameExpression (passed as this inside loop) ✅ Yes

MemberDataTest.TestNameExpression is never referenced inside the foreach loop body — MemberDataTest.GenerateTestExecution() delegates entirely to _innerTest.GenerateTestExecution(wrapper), where _innerTest is the BasicTestMethod carrying the per-argument interpolated expression. The MemberDataTest.TestNameExpression is only exposed to outer wrappers (ConditionalTest), where the static literal is both correct and necessary.

✅ Property assignment reordering is correct

Moving Method and ContainingType assignments before TestNameExpression is necessary because the new expression uses these values. The original order only worked because it forwarded innerTest.TestNameExpression verbatim.

✅ Equality/hashing behavior preserved (flagged by all 3 models)

MemberDataTest.Equals checks TestNameExpression, Method, ContainingType, and _innerTest.Equals(...). Previously TestNameExpression mirrored _innerTest.TestNameExpression exactly, making it redundant in Equals. Now the two diverge, but _innerTest.Equals still provides full distinctiveness via BasicTestMethod's per-argument expressions. No regression.

ConditionalTest copies TestNameExpression from its inner test (line 143). After this fix, it gets the static string — correct since ConditionalTest should never reference loop variables either.

💡 Minor test-name fidelity change in skip reporting — acceptable (flagged by all 3 models)

When a ConditionalTest(MemberDataTest) skips (condition is false), the skip report now shows "alias::Type.Method(...)" instead of per-argument details. This is a non-issue because: (1) the old code never compiled, so this path was always broken; (2) when the condition is false, the entire member-data loop is skipped — there are no specific arguments to report.

💡 No regression test — acceptable for this context

The XUnitWrapperGenerator has no dedicated unit test suite. The "test" is whether projects using [Theory] + [MemberData] + conditional skip attributes compile successfully, which CI will verify. A dedicated generator test would be ideal but is out of scope for this fix.

💡 Pre-existing: _memberInvocation not included in Equals/GetHashCode (not introduced by this PR)

Two MemberDataTest instances wrapping the same BasicTestMethod but backed by different member data sources would compare as equal. This is a pre-existing gap — noting for awareness, not a PR blocker.

Generated by Code Review for issue #126505 ·

@jkoritzinsky jkoritzinsky merged commit c62c41f into main Apr 12, 2026
89 checks passed
@jkoritzinsky jkoritzinsky deleted the copilot/update-xunitwrappergenerator-code branch April 12, 2026 02:20
@github-project-automation github-project-automation Bot moved this to Done in AppModel Apr 12, 2026
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Projects

Status: Done

Development

Successfully merging this pull request may close these issues.

Looks like the generated code for XUnitWrapperGenerator for handling Theory dynamic member data is broken.

4 participants