-
Notifications
You must be signed in to change notification settings - Fork 4.9k
Add System.ComponentModel.Annotations tests #20934
Add System.ComponentModel.Annotations tests #20934
Conversation
<data name="MinLengthAttribute_InvalidMinLength" xml:space="preserve"> | ||
<value>MinLengthAttribute must have a Length value that is zero or greater.</value> | ||
</data> | ||
<data name="MinLengthAttribute_ValidationError" xml:space="preserve"> | ||
<value>The field {0} must be a string or array type with a minimum length of '{1}'.</value> | ||
</data> | ||
<data name="MinLengthAttribute_InvalidValueType" xml:space="preserve"> | ||
<value>The field of type {0} must be a string, array or ICollection type.</value> | ||
</data> |
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 is the same string as MaxLengthAttribute_InvalidValueType... why do we need both?
@@ -56,35 +56,23 @@ protected override ValidationResult IsValid(object value, ValidationContext vali | |||
{ | |||
if (OtherPropertyDisplayName == null) | |||
{ | |||
OtherPropertyDisplayName = GetDisplayNameForProperty(validationContext.ObjectType, OtherProperty); | |||
OtherPropertyDisplayName = GetDisplayNameForProperty(validationContext.ObjectType, otherPropertyInfo); |
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.
Why did this change?
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.
If you look above, we have the following code:
var otherPropertyInfo = validationContext.ObjectType.GetRuntimeProperty(OtherProperty);
In the GetDisplayNameForProperty
method, we used to have the buggy code:
var property = containerType.GetRuntimeProperties()
.SingleOrDefault(
prop =>
IsPublic(prop) &&
string.Equals(propertyName, prop.Name, StringComparison.OrdinalIgnoreCase));
However, we already have the property otherPropertyInfo
above. The name comparison is broken, and GetRuntimeProperty
only returns public methods so this whole thing is redundant and broken.
So we can just use the actual PropertyInfo we found before
var attributes = CustomAttributeExtensions.GetCustomAttributes(property, true); | ||
var display = attributes.OfType<DisplayAttribute>().FirstOrDefault(); | ||
if (display != null) | ||
{ | ||
return display.GetName(); | ||
} | ||
|
||
return propertyName; | ||
return OtherProperty; |
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.
Why did this change?
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.
The old value passed to the parameter propertyName
was OtherProperty
. Now that that parameter is gone, we use OtherProperty
directly
} | ||
|
||
throw; | ||
throw tie.InnerException; |
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.
Do you know for sure that InnerException will always be non-null in all circumstances?
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 is the try
block:
try
{
// 1-parameter form is ValidationResult Method(object value)
// 2-parameter form is ValidationResult Method(object value, ValidationContext context),
var methodParams = _isSingleArgumentMethod
? new object[] { convertedValue }
: new[] { convertedValue, validationContext };
var result = (ValidationResult)methodInfo.Invoke(null, methodParams);
// We capture the message they provide us only in the event of failure,
// otherwise we use the normal message supplied via the ctor
_lastMessage = null;
if (result != null)
{
_lastMessage = result.ErrorMessage;
}
return result;
}
catch (TargetInvocationException tie)
{
throw tie.InnerException;
}
The only place where the TIE can be thrown is from reflection invoke, and there will always be a non-null inner exception in this case.
// A cast exception previously occurred if a non-{string|array} property was passed | ||
// in so preserve this behavior if the value does not implement ICollection | ||
length = ((Array)value).Length; | ||
throw new InvalidCastException(SR.Format(SR.MaxLengthAttribute_InvalidValueType, value.GetType())); |
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.
Why did you change this?
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.
What if it is an array? You're removing functionality here which was added to solve a previous issue.
In reply to: 121382258 [](ancestors = 121382258)
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 think this was addressing https://github.com/dotnet/corefx/issues/18361
In reply to: 121792346 [](ancestors = 121792346,121382258)
@@ -212,12 +213,9 @@ internal bool TryGetPropertyStoreItem(string propertyName, out PropertyStoreItem | |||
|
|||
if (_propertyStoreItems == null) | |||
{ | |||
lock (_syncRoot) | |||
lock (_lock) |
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.
As far as I can tell, this is a buggy change. Why did you change this? It's now using a process-wide lock to protect instance state, and it removed the double-check that would ensure that multiple threads always see the same value even in the face of race conditions.
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.
Okay. I wasn't sure about the double check. I reverted this change
@@ -236,7 +237,7 @@ public static void ValidateProperty(object value, ValidationContext validationCo | |||
var err = GetValidationErrors(value, validationContext, attributes, false).FirstOrDefault(); | |||
if (err != null) | |||
{ | |||
err.ThrowValidationException(); | |||
throw err.GetValidationException(); |
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.
Why did you change these? If it's purely about getting to 100% code coverage by allowing the compiler to see that the subsequent brace is unreachable, that's not worth the change.
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 was why, yes. I reverted this change
} | ||
|
||
[Serializable] | ||
public class SubException : ValidationException |
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.
Why is a SubException type necessary for this test? Shouldn't a PNSE be produced as well when working with ValidationException directly?
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.
ValidationException is not [Serializable]
so can't be serialized. The test is deserialization, so obviously if we can't serialize, then we can't test deserialization
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've deleted this test
// A cast exception previously occurred if a non-{string|array} property was passed | ||
// in so preserve this behavior if the value does not implement ICollection | ||
length = ((Array)value).Length; | ||
throw new InvalidCastException(SR.Format(SR.LengthAttribute_InvalidValueType, value.GetType())); |
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.
throw new InvalidCastException(SR.Format(SR.LengthAttribute_InvalidValueType, value.GetType())); [](start = 20, length = 96)
As for MaxLengthAttribute: what if it is an array? You're removing functionality here which was added to solve a previous issue.
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.
So that's what I first thought when I saw the code and why I fixed this. All Arrays are ICollections, so this code path will never be hit as they will take the precious if block. We have tests that cover this in the project.
What this is actually doing is simulating an InvalidCastException for comparability with before we added ICollection support.
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 makes sense - thanks for clarifying.
@hughbe General comment - could we have a separate PR for each issue please? Would like to address the fix for #20932 separately from the addition of other tests. |
Gladly! Will do so soon |
I've extracted the netfx compat fix to #21031, this just adds some tests and cleans up some code |
Test Portable Linux x64 Debug Build |
I rebased this PR to fix merge conflicts |
@@ -69,6 +69,26 @@ public static void Constructor(Type validatorType, string method) | |||
Assert.Equal(method, attribute.Method); | |||
} | |||
|
|||
[Fact] | |||
public void FormatErrorMessage_NoError_ContainsName() |
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.
Nit: Just checking - did you mean this to say "NoError"? The test is about what happens to the error message? The test below checks that validation fails - so did you mean "validation passes" here? If so maybe add that assert or rename the tests.
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.
It's not quite that validation passes. It's actually that no validation has ever been performed on that instance - i've updated the comments accordingly
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.
OK. Now it all makes sense. Thanks for the updates.
@ajcvickers This looks goods to me. Do you want to review before @hughbe merges this in? |
I don't actually have permission to merge it in as I'm not an MSFT employee so that's up to you guys :) thanks for reviewing |
My bad. I meant "before we merge this in for @hughbe" 😄 |
Friendly ping :) |
* Moving the Utf8Formatter and Utf8Parser into S.P.Corelib * Doing some minimal cleanup to lineup types and get the Utf8Parser/Utf8Formatter building * Updating the Utf8 Float Parser to have different buffers for Single vs Double * Fixing the Utf8Parser to track trailing zero digits and to properly mark the end of the buffer * Fixing a couple of issues in Utf8Parser.Number Signed-off-by: dotnet-bot <[email protected]>
* Moving the Utf8Formatter and Utf8Parser into S.P.Corelib * Doing some minimal cleanup to lineup types and get the Utf8Parser/Utf8Formatter building * Updating the Utf8 Float Parser to have different buffers for Single vs Double * Fixing the Utf8Parser to track trailing zero digits and to properly mark the end of the buffer * Fixing a couple of issues in Utf8Parser.Number Signed-off-by: dotnet-bot <[email protected]>
* Moving the Utf8Formatter and Utf8Parser into S.P.Corelib * Doing some minimal cleanup to lineup types and get the Utf8Parser/Utf8Formatter building * Updating the Utf8 Float Parser to have different buffers for Single vs Double * Fixing the Utf8Parser to track trailing zero digits and to properly mark the end of the buffer * Fixing a couple of issues in Utf8Parser.Number Signed-off-by: dotnet-bot <[email protected]>
…otations-tests Add System.ComponentModel.Annotations tests Commit migrated from dotnet/corefx@1a76f61
No description provided.