-
Notifications
You must be signed in to change notification settings - Fork 749
Recognized private members in base class for Source attributes #4033
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
Recognized private members in base class for Source attributes #4033
Conversation
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
This of course changes the behaviour a little, but I don't think that this change will break anyone, as I think very few will have a method/field/property in the base class with the same name as the source.
@nunit/framework-team do you agree?
src/NUnitFramework/framework/Attributes/TypeGetMemberExtension.cs
Outdated
Show resolved
Hide resolved
src/NUnitFramework/framework/Attributes/TypeGetMemberExtension.cs
Outdated
Show resolved
Hide resolved
I'm a little hesitant about this myself. While it seems like a simple fix to look for private members, I'm concerned it may also introduce a lot of unexpected edge cases. For example, what if a base class and a child class each define identically named methods with identical arguments? Non-private members would have this caught by the C# compiler as an error but the guard rails are off if we try to go around that. I'm a little rusty on how/if private members can be used in other inheritance situations (like a virtual test method which gets overridden by a child class) but I see there's a bit of a discussion in the originating issue that this works with protected or public members and that perhaps an analyzer could help guide around this. I'm wondering if a in-framework fix could instead provide a more helpful error message. |
Co-authored-by: Mikkel Nylander Bundgaard <mikkelbu@users.noreply.github.com>
In the previous case, the
This is actually used in various NUnit Framework test where the base class ( In the changes for this PR it would test nothing :-( as To return previous behaviour, I will update the |
Hm, you are quite right, I hadn't thought about it this way. I suppose this is mostly to facilitate using It still "feels" more right to me to expect a member which is declared in a base class and potentially defined or overridden in a child class to require "protected" or "public" visibility much the way the C# compiler enforces member visibility, but I can also see it being a bit awkward when the resolution (via I still think we may want to improve error messages to guide a user to use protected visibility over private when using Thanks @manfred-brands (and thanks for the latest changes to help ensure this is non-breaking). Things are a bit busy for me, but I'll try and do a proper review over the next few days |
There are two ways of 'overriding' a
The initial ticket has both the attribute and the member applied to the base class only. The fact is hidden from the child class which doesn't need to know. Therefore Thinking more about this, I now tend to agree with you. If the member would be
I agree that we would need error messages, both analyzer and runtime, if the child class has a member with the same name as a private member in a base class if such a member is used in any of the Source attributes. If nunit itself would use nunit.analyzer (which has been discussed), then they would have a warning in What is the underlying reason for the |
Ah, yes, you're quite right. Somehow I had missed the "static" part of this scenario. You're absolutely right. Sorry about that @manfred-brands |
@stevenaw I was not shouting, I thought I was numbering, but they became big bold headings. :-( |
Not to worry, no offense taken at all @manfred-brands . I hadn't realized when making my earlier comments that we had also been considering static overriding, but of course your suggestions make sense in that context, and I am glad the conversation was steered back in that direction 🙂 |
@@ -44,9 +44,12 @@ public class TestFixtureSourceTests | |||
[TestCase(typeof(SourceReturnsFixtureParameters))] | |||
[TestCase(typeof(ExtraTestFixtureAttributeIsIgnored))] | |||
[TestCase(typeof(TestFixtureMayUseMultipleSourceAttributes))] | |||
[TestCase(typeof(DerivedClassUsingBaseClassDataSource))] | |||
[TestCase(typeof(BaseClassUsingDerivedClassDataSource))] |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
👍 Thanks for the thorough test cases!
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Test Driven Development. First create a test case that shows the problem, then fix it.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Thanks @manfred-brands ! Everything looks great. I left a few suggestions for a few existing classes or extensions which could help what you're trying to do here.
|
||
namespace NUnit.Framework | ||
{ | ||
internal static class TypeGetMemberExtension |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Thanks for making these extensions internal visibility by default.
Suggestion: I think there may be an existing + recently created TypeExtensions
class which lives in Internal/Extensions
. Would it make sense for this new method to live there too?
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I had looked for extension classes, but missed this one. Method moved and Tests added.
{ | ||
members.AddRange(type.GetMember(name, flags)); | ||
} | ||
while (members.Count == 0 && (type = type.BaseType) != null); |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Suggestion: NUnit.Framework.Internal.Reflect
also defines a few extension methods over the Type
type, including this "walking the class hierarchy" behaviour. Would it make sense to use TypeAndBaseTypes
here instead?
I know this part changed recently, but if I'm reading the current version of the code right, we also only walk the hierarchy until the first time that GetMember
returns something. Is the List<>
still required? Coupled with the above suggestion, would it be simpler to change this to a simple foreach + early return?
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
That class is an even better location, moved it and the tests again. Then there also is the class Extensions
.
You are absolutely right that the List<>
is no longer required. Factored out.
@@ -198,8 +198,8 @@ public IEnumerable<ITestFixtureData> GetParametersFor(Type sourceType) | |||
if (SourceName == null) | |||
return Reflect.Construct(sourceType) as IEnumerable; | |||
|
|||
MemberInfo[] members = sourceType.GetMember(SourceName, | |||
BindingFlags.Public | BindingFlags.NonPublic | BindingFlags.Static | BindingFlags.Instance | BindingFlags.FlattenHierarchy); |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Thanks for catching this. It's a little awkward for our purposes that FlattenHierarchy
doesn't handle private members
Factored out the now unnecessary List<>.
LGTM! Thanks for your contribution @manfred-brands 👍 |
Fixes #4032