#5733 handling all arguments for Fact and Theory#5734
#5733 handling all arguments for Fact and Theory#5734thomhurst merged 4 commits intothomhurst:mainfrom
Conversation
Up to standards ✅🟢 Issues
|
There was a problem hiding this comment.
Code Review: PR #5734 — "handling all arguments for Fact and Theory"
The PR addresses a real bug (#5733) where the xUnit-to-TUnit migrator was silently dropping named arguments on [Fact] and [Theory] other than Skip (e.g. [Fact(DisplayName = "...")] was migrated to [Test] without a [DisplayName("...")]). The general direction — looping over all named arguments rather than handling only Skip — is correct. However, the implementation has several correctness and architectural concerns.
Critical Bug: Blind Attribute Name Forwarding
The new code treats every named argument as if its name directly maps to a TUnit attribute name:
additionalAttributes.Add(new AdditionalAttribute(
argument.NameEquals!.Name.Identifier.ValueText, ...));This is not safe as a general strategy. xUnit and TUnit do not share the same attribute argument namespace. For example:
[Fact(Timeout = 5000)]→ emits[Timeout(5000)]— but xUnit'sTimeoutis in milliseconds and TUnit's[Timeout]may have a different signature or not exist at all.- Any unrecognized xUnit argument would emit a broken/invalid attribute silently.
Suggested fix: Use an explicit allowlist/mapping table (similar to how NUnitTwoPhaseAnalyzer maps TestName → DisplayName):
private static readonly Dictionary<string, string> FactArgToTUnitAttr = new()
{
{ "Skip", "Skip" },
{ "DisplayName", "DisplayName" },
// Timeout has no direct TUnit equivalent — needs TODO comment
};For unknown arguments, emit a // TODO: TUnit migration — unrecognized argument '{name}' could not be converted comment rather than silently dropping or blindly forwarding.
Silent Data Loss: Positional Arguments Discarded
if (argument.NameEquals is null) { continue; }Positional arguments are silently dropped with no warning. While xUnit's Fact/Theory don't normally take positional args, any edge case (subclass attributes, unusual invocations) will be silently broken. A // TODO diagnostic comment would be safer.
Stale Comments (Misleading for Future Maintainers)
Two comments now contradict the code:
- The final return block still reads
// Remove the Skip argumentbut now applies to all arguments, not justSkip. - The early-return null-ArgumentList path has
// Remove any argumentsbut there are no arguments to remove —// No arguments — plain [Test]would be accurate.
These will confuse future maintainers.
Missing Tests
No test cases were added (confirmed by the PR checklist). The following are needed at minimum:
[Fact(DisplayName = "My Test")]→[Test]+[DisplayName("My Test")][Theory(DisplayName = "My Theory")]→[Test]+[DisplayName("My Theory")]- Regression: existing
Skipbehavior still works
Without tests, the next refactor could silently reintroduce the original bug.
Minor: Eager List Allocation
new List<AdditionalAttribute>(3) is allocated for every [Fact]/[Theory] attribute, even when all arguments are positional (and thus skipped). Lazy allocation (only create the list when a known-good argument is found) would avoid unnecessary allocations in the common case. Low priority, but worth noting given TUnit's performance focus.
Summary
| Bug fix direction | Correct |
| Implementation safety | Needs explicit argument mapping — blind forwarding risks emitting invalid TUnit attributes |
| Silent failures | Positional args and unknown named args are dropped without any diagnostic |
| Test coverage | None added |
| Code clarity | Stale comments mislead future maintainers |
Recommended before merging:
- Add an explicit
string → stringargument-to-attribute mapping with an unknown-arg fallback (TODO comment) - Handle positional args with a TODO comment rather than silently skipping
- Fix the two stale comments
- Add test cases for
DisplayNameon bothFactandTheory
There was a problem hiding this comment.
Code Review: PR #5734 (Updated) — "handling all arguments for Fact and Theory"
This is a follow-up review comparing against my previous comments.
What Was Fixed ✅
All critical concerns from the previous review have been addressed:
-
Blind attribute forwarding eliminated — The new explicit mapping dictionary
_xUnitFactAndTheoryToTUnitArgumentswithTryGetValueis the right approach. Unknown xUnit arguments are now silently dropped rather than blindly emitted as invalid TUnit attributes. -
Tests added —
DisplayNamed_Test_Attributes_Can_Be_FixedandTimeout_Test_Attributes_Can_Be_Fixedcover bothFactandTheoryvariants. The regression test forSkipbehaviour was already present. -
Eager allocation reduced — Using
[]collection expression instead ofnew List<AdditionalAttribute>(3)removes the unnecessary pre-allocation. -
Stale "Remove the Skip argument" comment — Gone now that the block was rewritten.
Timeout Semantics Verified ✅
I checked TimeoutAttribute.cs — TUnit's [Timeout(int timeoutInMilliseconds)] uses milliseconds, matching xUnit's Timeout property. The migration is semantically correct and the test's use of 3600 is valid.
Minor Remaining Items
1. Positional arguments silently dropped (low priority)
xUnit's Fact/Theory don't take positional args in practice, but a TODO comment would protect future maintainers and custom subclasses:
2. Empty list vs null inconsistency (cosmetic)
When ArgumentList is non-null but no arguments match, the returned result has AdditionalAttributes = [] (empty list). The rest of the codebase (NUnit analyzer, the other xUnit path at line 958) normalizes to null when empty:
This is not a bug — MigrationTransformer.cs:607 correctly guards with != null && Count > 0. But for consistency with the established pattern, you could apply the same normalization. Not a blocker.
Summary
The previous critical concerns are resolved. The implementation is now correct and safe. The remaining items are minor — a TODO comment for dropped positional args would be a good defensive addition, but is not required for correctness.
Good work addressing the feedback.
There was a problem hiding this comment.
Code Review: PR #5734 (Updated) — "handling all arguments for Fact and Theory"
This is a follow-up review comparing against my previous comments.
What Was Fixed
All critical concerns from the previous review have been addressed:
-
Blind attribute forwarding eliminated -- The new explicit mapping dictionary
_xUnitFactAndTheoryToTUnitArgumentswithTryGetValueis the right approach. Unknown xUnit arguments are now silently dropped rather than blindly emitted as invalid TUnit attributes. -
Tests added --
DisplayNamed_Test_Attributes_Can_Be_FixedandTimeout_Test_Attributes_Can_Be_Fixedcover bothFactandTheoryvariants. The regression test forSkipbehaviour was already present. -
Eager allocation reduced -- Using
[]collection expression instead ofnew List<AdditionalAttribute>(3)removes the unnecessary pre-allocation. -
Stale "Remove the Skip argument" comment -- Gone now that the block was rewritten.
Timeout Semantics Verified
I checked TimeoutAttribute.cs -- TUnit's [Timeout(int timeoutInMilliseconds)] uses milliseconds, matching xUnit's Timeout property. The migration is semantically correct and the test's use of 3600 is valid.
Minor Remaining Items
1. Positional arguments silently dropped (low priority)
xUnit's Fact/Theory don't take positional args in practice, but a TODO comment would protect future maintainers handling custom subclasses:
if (argument.NameEquals is null)
{
// TODO: positional arguments on Fact/Theory are not supported -- skipping
continue;
}2. Empty list vs null inconsistency (cosmetic)
When ArgumentList is non-null but no arguments match, the returned result has AdditionalAttributes = [] (empty list). The rest of the codebase (NUnit analyzer, the other xUnit path at line 958) normalizes to null when empty:
AdditionalAttributes = additionalAttributes.Count > 0 ? additionalAttributes : nullThis is not a bug -- MigrationTransformer.cs:607 correctly guards with != null && Count > 0. But for consistency with the established pattern, the same normalization could be applied. Not a blocker.
Summary
The previous critical concerns are resolved. The implementation is now correct and safe. The remaining items are minor -- a TODO comment for dropped positional args would be a good defensive addition, but is not required for correctness.
Good work addressing the feedback.
|
Thanks @inyutin-maxim ! |
Description
handling all arguments for Fact and Theory
Related Issue
Fixes #5733
Fixes #
Type of Change
Checklist
Required
TUnit-Specific Requirements
TUnit.Core.SourceGenerator)TUnit.Engine)TUnit.Core.SourceGenerator.Testsand/orTUnit.PublicAPItests.received.txtfiles and accepted them as.verified.txt.verified.txtfiles[DynamicallyAccessedMembers]annotationsdotnet publish -p:PublishAot=trueTesting
dotnet test)Additional Notes