Skip to content

Add support for cancellationtoken, params, and optional arguments to TestCaseSource#5057

Merged
stevenaw merged 24 commits intomainfrom
testcase-params
Nov 19, 2025
Merged

Add support for cancellationtoken, params, and optional arguments to TestCaseSource#5057
stevenaw merged 24 commits intomainfrom
testcase-params

Conversation

@stevenaw
Copy link
Copy Markdown
Member

@stevenaw stevenaw commented Nov 17, 2025

Relates to #2268

Fixes #1883
Fixes #1281
Fixes #4900

This PR primarily moves existing functionality in TestCaseAttribute into a common place where it can also be used by TestCaseSourceAttribute.

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

This PR adds support for cancellation tokens, params arrays, and optional arguments to TestCaseSource attributes, bringing it to feature parity with TestCaseAttribute. The implementation extracts common argument adjustment logic into a new AdjustArgumentsForMethod method in TestCaseParameters, which is used by both TestCaseAttribute and TestCaseSourceAttribute.

  • Refactored argument handling logic from TestCaseAttribute into a reusable AdjustArgumentsForMethod method
  • Added support for params arrays, optional parameters, and implicit CancellationToken injection in TestCaseSource
  • Removed .NET 6.0+ preprocessor directives to make generic TestCaseData classes available on all target frameworks
  • Added comprehensive test coverage for the new functionality

Reviewed Changes

Copilot reviewed 7 out of 7 changed files in this pull request and generated 8 comments.

Show a summary per file
File Description
src/NUnitFramework/tests/Attributes/TestCaseSourceTests.cs Added comprehensive tests for params arrays, optional arguments, and cancellation token support in TestCaseSource; removed .NET 6.0+ conditional compilation for generic TestCaseData tests
src/NUnitFramework/tests/Attributes/TestCaseAttributeTests.cs Added test case using named parameter syntax to verify params array handling
src/NUnitFramework/framework/TestCaseData.cs Added new constructors for generic TestCaseData to support 2 and 3 arguments; removed .NET 6.0+ preprocessor directives to enable usage on all frameworks
src/NUnitFramework/framework/Internal/TestCaseParameters.cs Introduced AdjustArgumentsForMethod method that centralizes logic for handling params arrays, optional parameters, CancellationToken injection, and type conversions
src/NUnitFramework/framework/Internal/Extensions/ArgumentsExtensions.cs Added LastParameterIsParamsArray extension method to detect params array parameters
src/NUnitFramework/framework/Attributes/TestCaseSourceAttribute.cs Updated to use AdjustArgumentsForMethod and modified array unpacking logic to support params arrays; added error handling
src/NUnitFramework/framework/Attributes/TestCaseAttribute.cs Refactored to use the new AdjustArgumentsForMethod method, removing duplicated argument handling logic
Comments suppressed due to low confidence (1)

src/NUnitFramework/framework/Attributes/TestCaseSourceAttribute.cs:118

  • This assignment to methodName is useless, since its value is never read.
            var methodName = method.Name;

💡 Add Copilot custom instructions for smarter, more guided reviews. Learn how to get started.

Comment thread src/NUnitFramework/framework/Internal/TestCaseParameters.cs Outdated
Comment thread src/NUnitFramework/framework/Internal/TestCaseParameters.cs Outdated
Comment thread src/NUnitFramework/tests/Attributes/TestCaseSourceTests.cs Outdated
Comment thread src/NUnitFramework/framework/TestCaseData.cs
Comment thread src/NUnitFramework/framework/Attributes/TestCaseSourceAttribute.cs Outdated
Comment thread src/NUnitFramework/framework/Internal/TestCaseParameters.cs Outdated
Comment thread src/NUnitFramework/framework/Internal/TestCaseParameters.cs
Comment on lines +206 to 209
catch (Exception ex)
{
parms = new TestCaseParameters(ex);
}
Copy link

Copilot AI Nov 17, 2025

Choose a reason for hiding this comment

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

Generic catch clause.

Copilot uses AI. Check for mistakes.
Copy link
Copy Markdown
Member Author

@stevenaw stevenaw Nov 18, 2025

Choose a reason for hiding this comment

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

Correct. We want to catch all exceptions here so that we can report them and associate them with the testcase instead of losing a record of it

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.

This method is also rather horrible. And no tests are verifying that any exception ever is raised
image

(Again, outside scope of this issue, see comment at the bottom, but we should refactor this one too later)

Copy link
Copy Markdown
Member Author

@stevenaw stevenaw Nov 19, 2025

Choose a reason for hiding this comment

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

This error checking was added by me as the method previously had none. That meant that an error with processing a single testcase would cause the entire collection of them to not be discovered or run.

I primarily added it to make it easier to debug my own other bugs while trying to complete this. Agreed this would be good to add coverage over later.

@OsirisTerje
Copy link
Copy Markdown
Member

Nice @stevenaw

@stevenaw stevenaw marked this pull request as ready for review November 18, 2025 17:19
@stevenaw
Copy link
Copy Markdown
Member Author

Phew. CI is happy now. This is ready for review

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

Copilot reviewed 7 out of 7 changed files in this pull request and generated 3 comments.


💡 Add Copilot custom instructions for smarter, more guided reviews. Learn how to get started.

Comment thread src/NUnitFramework/framework/Internal/TestCaseParameters.cs Outdated
Comment thread src/NUnitFramework/framework/Internal/TestCaseParameters.cs
Comment thread src/NUnitFramework/framework/Attributes/TestCaseSourceAttribute.cs
@OsirisTerje
Copy link
Copy Markdown
Member

@stevenaw

Line 167 is never touched by any of the tests
image

And I agree that the the condition on lin 166 is always false.

The Roslyn and in my case Resharper should have flagged this as dead code. It doesn't and the reason is probably that the whole method is simply too complex. The dead code warning is not very advanced.

The whole method is rather complex with a bunch of if then else and for loops. (and has probably been like this for a very long time), 107 lines long . That's outside the scope of this issue, but we should do a refactoring of this piece of code.

Copy link
Copy Markdown
Member

@OsirisTerje OsirisTerje left a comment

Choose a reason for hiding this comment

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

@stevenaw I am actually VERY impressed you have managed to work this out with this code base. A lot of this code is 16 years old (at least), and with way too high cyclomatic complexity, and large methods. To me it is too hard to figure out what the code is actually doing, I am drowning in the details, and then I loose the context.

I mark this just as comments, and not sure if doing anything more here within the scope of the issues you're fixing should be done. I think it is better to mark this code to be refactored and at that time fix the lacking tests too.

Awesome work!

}
parms.Arguments = newArgList;
}
parms.AdjustArgumentsForMethod(method);
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.

Love you're moving all that code off.

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.

Which I just now understood was moved......

Copy link
Copy Markdown
Member Author

Choose a reason for hiding this comment

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

That's a sign that I found the right abstraction 😀 It took 3-4 tries until I found one that seemed right.

You're right, that entire method is just moved. There are improvements to be had, but I tried to minimize changes to it in order to minimize risk of breaking anything.

{
if (parameters.Length == 0)
{
return false;
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.

No tests checks this

/// <summary>
/// Construct a TestCaseData with a list of arguments.
/// </summary>
public TestCaseData(T argument1, T argument2, T argument3)
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.

This method is not tested

Copy link
Copy Markdown
Member Author

Choose a reason for hiding this comment

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

I can quickly add a test over this. This was added for API parity with the pre-existing TestCaseData<T1, T2, T3> since we have a TestCaseData<T> which accepts 1 arg and I was found myself adding support for a TestCaseData<T> which accepts 2 args of the same type.

Copy link
Copy Markdown
Member

@manfred-brands manfred-brands left a comment

Choose a reason for hiding this comment

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

Nice work @stevenaw

I added a few small clean-up commits.
We had an inconsistency between uses of collection initializers for arrays.
Your new code used them, the old code not.
I have aligned them for consistency and readability.

{
IParameterInfo lastParameter = parameters[argsNeeded - 1];

if (parameters.LastParameterIsParamsArray())
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.

This re-determines lastParameter.
I have split the method and call it here with lastParameter.

}

/// <summary>
/// Construct a TestCaseData with a list of arguments.
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.

This is also not a list. I update the comments.

return n / d;
}

[Test, TestCaseSource(nameof(SingleDimensionArray))]
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.

We don't need a Test attribute if we have another TestXXX attribute.

@stevenaw
Copy link
Copy Markdown
Member Author

Thanks @manfred-brands !
Appreciate your catching and fixing some of those things

@stevenaw stevenaw merged commit e11ec35 into main Nov 19, 2025
5 checks passed
@stevenaw stevenaw deleted the testcase-params branch November 19, 2025 17:00
@manfred-brands
Copy link
Copy Markdown
Member

@stevenaw This PR would have been a good candidate for squashing to not pollute the repository with commit messages like: "tmp commit".

@OsirisTerje Do we have a rule/guideline for merging vs squash vs rebase?

@stevenaw
Copy link
Copy Markdown
Member Author

Good point. I had thought I had kept much of the early experimentation out of the history but I see there was some still there. Agreed this should've been squashed.

@OsirisTerje I'd also be curious to hear thoughts on if you'd prefer we always or just prefer a given strategy

@OsirisTerje
Copy link
Copy Markdown
Member

OsirisTerje commented Nov 23, 2025

@manfred-brands @stevenaw No guidelines for branching/merging (afaik).

The @nunit/core-team and the @nunit/framework-team are the only one who can merge a PR, so this only applies to us.

Merging has the benefit of being visible afterwards, since Git will know of the task branch. I find that useful.

However, if there are many commits (like >3) and they are very much small commits with no new real info in their messages (like your experimentation), I would prefer a squash. I myself also often do a lot of small commits, I find that kind of safer.

But, a squash is not directly visible afterwards, so I would say, use it when you should, and not when you should not.

There are however no way for a github branch ruleset to have such a rule, so I would then prefer to have no rules - and that "we" do as well as we can, and try to ensure that we don't merge in a multitude of small commits, but squash instead.

I see in our docs about Team Practices, we have no branch guidelines. We could add some, but not sure if anybody really would read them.

I am not a fan of policing, and strict rules. So common sense, and do as well as one can, is fine with me.

PS:

One thought; We could add a CoPilot instructions.md expressing such a concern, and then say we should try to always use CoPilot as a reviewer, then we could be warned to merge or squash.

PS2:

I didn't mention Rebasing. I rarely do that myself, but anyone who like to do that, feel free.

@CharliePoole
Copy link
Copy Markdown
Member

I use rebasing a lot for long-running feature branches but I would not suggest a "rule" about it. If you were documenting guidelines, I'd suggest that as a "Consider" item.

I've occasionally merged a series of commits that included a wip or temp item. That's to be avoided but I don't think it's worth worrying about if it doesn't happen often.

IMO, talking about this kind of thing on a case by case basis, rather than establishing "rules" is good for a team.

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

Labels

None yet

Projects

None yet

5 participants