Skip to content

Fix MVC Core tests for CoreCLR GetCustomAttributes consistency #62872

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

Merged
merged 4 commits into from
Jul 23, 2025

Conversation

medhatiwari
Copy link
Contributor

Fix MVC Core tests for CoreCLR GetCustomAttributes consistency

here

  • You've read the Contributor Guide and Code of Conduct.
  • You've included unit or integration tests for your change, where applicable.
  • You've included inline docs for your change, where applicable.
  • There's an open issue for the PR that you are making. If you'd like to propose a new feature or change, please open an issue to discuss the change or find an existing issue.

Fix MVC Core tests for CoreCLR GetCustomAttributes consistency

Description

This PR updates two test methods in the MVC Core test suite that were expecting CoreCLR's old inconsistent behavior for typeof(object).GetCustomAttributes().

Background:
CoreCLR had a bug where typeof(object).GetCustomAttributes(inherit: true) and typeof(object).GetCustomAttributes(inherit: false) returned different numbers of attributes (1 vs 5). This inconsistency caused ASP.NET Core tests to fail when running on Mono, which correctly returned 5 attributes for both cases.

Changes:
Updates two test methods that were written expecting the old buggy behavior:

  • ModelAttributesTest.GetAttributesForParameter_NoAttributes
  • DefaultModelMetadataProviderTest.GetMetadataForParameter_SuppliesEmptyAttributes_WhenParameterHasNoAttributes

Both tests now correctly expect 5 attributes (SerializableAttribute, NullableContextAttribute, ClassInterfaceAttribute, ComVisibleAttribute, TypeForwardedFromAttribute) instead of just 1.

Impact:

  • Ensures ASP.NET Core tests pass with the fixed CoreCLR runtime

Related to dotnet/runtime#117864

@Copilot Copilot AI review requested due to automatic review settings July 23, 2025 05:10
@medhatiwari medhatiwari requested a review from a team as a code owner July 23, 2025 05:10
@github-actions github-actions bot added the area-mvc Includes: MVC, Actions and Controllers, Localization, CORS, most templates label Jul 23, 2025
@dotnet-policy-service dotnet-policy-service bot added the community-contribution Indicates that the PR has been added by a community member label Jul 23, 2025
Copy link
Contributor

@Copilot 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 fixes two test methods in the MVC Core test suite to align with CoreCLR's corrected behavior for typeof(object).GetCustomAttributes(). Previously, CoreCLR had inconsistent behavior where GetCustomAttributes(inherit: true) and GetCustomAttributes(inherit: false) returned different numbers of attributes for the object type. The fix ensures both calls consistently return all 5 pseudo-attributes.

  • Updates test assertions to expect 5 attributes instead of 1
  • Adds explicit checks for all pseudo-attributes on the object type
  • Ensures ASP.NET Core tests pass with the fixed CoreCLR runtime

Reviewed Changes

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

File Description
ModelAttributesTest.cs Updates GetAttributesForParameter_NoAttributes test to expect all 5 pseudo-attributes
DefaultModelMetadataProviderTest.cs Updates GetMetadataForParameter_SuppliesEmptyAttributes_WhenParameterHasNoAttributes test to expect all 5 pseudo-attributes

Comment on lines 192 to 195
Assert.Contains(attributes.Attributes, attr => attr.GetType().Name == "NullableContextAttribute");
Assert.Contains(attributes.Attributes, attr => attr.GetType().Name == "ClassInterfaceAttribute");
Assert.Contains(attributes.Attributes, attr => attr.GetType().Name == "ComVisibleAttribute");
Assert.Contains(attributes.Attributes, attr => attr.GetType().Name == "TypeForwardedFromAttribute");
Copy link
Preview

Copilot AI Jul 23, 2025

Choose a reason for hiding this comment

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

Using string comparison with GetType().Name for attribute type checking is fragile and could break if the attribute type is renamed or moved to a different namespace. Consider using typeof() comparison or checking if the attribute is assignable from the expected type.

Suggested change
Assert.Contains(attributes.Attributes, attr => attr.GetType().Name == "NullableContextAttribute");
Assert.Contains(attributes.Attributes, attr => attr.GetType().Name == "ClassInterfaceAttribute");
Assert.Contains(attributes.Attributes, attr => attr.GetType().Name == "ComVisibleAttribute");
Assert.Contains(attributes.Attributes, attr => attr.GetType().Name == "TypeForwardedFromAttribute");
Assert.Contains(attributes.Attributes, attr => attr.GetType() == Type.GetType("System.Runtime.CompilerServices.NullableContextAttribute"));
Assert.Contains(attributes.Attributes, attr => attr.GetType() == typeof(System.Runtime.InteropServices.ClassInterfaceAttribute));
Assert.Contains(attributes.Attributes, attr => attr.GetType() == typeof(System.Runtime.InteropServices.ComVisibleAttribute));
Assert.Contains(attributes.Attributes, attr => attr.GetType() == typeof(System.Runtime.Serialization.TypeForwardedFromAttribute));

Copilot uses AI. Check for mistakes.

Comment on lines 192 to 195
Assert.Contains(attributes.Attributes, attr => attr.GetType().Name == "NullableContextAttribute");
Assert.Contains(attributes.Attributes, attr => attr.GetType().Name == "ClassInterfaceAttribute");
Assert.Contains(attributes.Attributes, attr => attr.GetType().Name == "ComVisibleAttribute");
Assert.Contains(attributes.Attributes, attr => attr.GetType().Name == "TypeForwardedFromAttribute");
Copy link
Preview

Copilot AI Jul 23, 2025

Choose a reason for hiding this comment

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

Using string comparison with GetType().Name for attribute type checking is fragile and could break if the attribute type is renamed or moved to a different namespace. Consider using typeof() comparison or checking if the attribute is assignable from the expected type.

Suggested change
Assert.Contains(attributes.Attributes, attr => attr.GetType().Name == "NullableContextAttribute");
Assert.Contains(attributes.Attributes, attr => attr.GetType().Name == "ClassInterfaceAttribute");
Assert.Contains(attributes.Attributes, attr => attr.GetType().Name == "ComVisibleAttribute");
Assert.Contains(attributes.Attributes, attr => attr.GetType().Name == "TypeForwardedFromAttribute");
Assert.Contains(attributes.Attributes, attr => attr.GetType() == typeof(NullableContextAttribute));
Assert.Contains(attributes.Attributes, attr => attr.GetType() == typeof(ClassInterfaceAttribute));
Assert.Contains(attributes.Attributes, attr => attr.GetType() == typeof(ComVisibleAttribute));
Assert.Contains(attributes.Attributes, attr => attr.GetType() == typeof(TypeForwardedFromAttribute));

Copilot uses AI. Check for mistakes.

Comment on lines 192 to 195
Assert.Contains(attributes.Attributes, attr => attr.GetType().Name == "NullableContextAttribute");
Assert.Contains(attributes.Attributes, attr => attr.GetType().Name == "ClassInterfaceAttribute");
Assert.Contains(attributes.Attributes, attr => attr.GetType().Name == "ComVisibleAttribute");
Assert.Contains(attributes.Attributes, attr => attr.GetType().Name == "TypeForwardedFromAttribute");
Copy link
Preview

Copilot AI Jul 23, 2025

Choose a reason for hiding this comment

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

Using string comparison with GetType().Name for attribute type checking is fragile and could break if the attribute type is renamed or moved to a different namespace. Consider using typeof() comparison or checking if the attribute is assignable from the expected type.

Suggested change
Assert.Contains(attributes.Attributes, attr => attr.GetType().Name == "NullableContextAttribute");
Assert.Contains(attributes.Attributes, attr => attr.GetType().Name == "ClassInterfaceAttribute");
Assert.Contains(attributes.Attributes, attr => attr.GetType().Name == "ComVisibleAttribute");
Assert.Contains(attributes.Attributes, attr => attr.GetType().Name == "TypeForwardedFromAttribute");
Assert.Contains(attributes.Attributes, attr => attr.GetType() == typeof(NullableContextAttribute));
Assert.Contains(attributes.Attributes, attr => attr.GetType() == typeof(ClassInterfaceAttribute));
Assert.Contains(attributes.Attributes, attr => attr.GetType() == typeof(ComVisibleAttribute));
Assert.Contains(attributes.Attributes, attr => attr.GetType() == typeof(TypeForwardedFromAttribute));

Copilot uses AI. Check for mistakes.

Comment on lines 192 to 195
Assert.Contains(attributes.Attributes, attr => attr.GetType().Name == "NullableContextAttribute");
Assert.Contains(attributes.Attributes, attr => attr.GetType().Name == "ClassInterfaceAttribute");
Assert.Contains(attributes.Attributes, attr => attr.GetType().Name == "ComVisibleAttribute");
Assert.Contains(attributes.Attributes, attr => attr.GetType().Name == "TypeForwardedFromAttribute");
Copy link
Preview

Copilot AI Jul 23, 2025

Choose a reason for hiding this comment

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

Using string comparison with GetType().Name for attribute type checking is fragile and could break if the attribute type is renamed or moved to a different namespace. Consider using typeof() comparison or checking if the attribute is assignable from the expected type.

Suggested change
Assert.Contains(attributes.Attributes, attr => attr.GetType().Name == "NullableContextAttribute");
Assert.Contains(attributes.Attributes, attr => attr.GetType().Name == "ClassInterfaceAttribute");
Assert.Contains(attributes.Attributes, attr => attr.GetType().Name == "ComVisibleAttribute");
Assert.Contains(attributes.Attributes, attr => attr.GetType().Name == "TypeForwardedFromAttribute");
Assert.Contains(attributes.Attributes, attr => attr.GetType() == Type.GetType("System.Runtime.CompilerServices.NullableContextAttribute"));
Assert.Contains(attributes.Attributes, attr => attr.GetType() == typeof(System.Runtime.InteropServices.ClassInterfaceAttribute));
Assert.Contains(attributes.Attributes, attr => attr.GetType() == typeof(System.Runtime.InteropServices.ComVisibleAttribute));
Assert.Contains(attributes.Attributes, attr => attr.GetType() == typeof(System.Runtime.CompilerServices.TypeForwardedFromAttribute));

Copilot uses AI. Check for mistakes.

Comment on lines 218 to 221
Assert.Contains(defaultMetadata.Attributes.Attributes, attr => attr.GetType().Name == "NullableContextAttribute");
Assert.Contains(defaultMetadata.Attributes.Attributes, attr => attr.GetType().Name == "ClassInterfaceAttribute");
Assert.Contains(defaultMetadata.Attributes.Attributes, attr => attr.GetType().Name == "ComVisibleAttribute");
Assert.Contains(defaultMetadata.Attributes.Attributes, attr => attr.GetType().Name == "TypeForwardedFromAttribute");
Copy link
Preview

Copilot AI Jul 23, 2025

Choose a reason for hiding this comment

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

Using string comparison with GetType().Name for attribute type checking is fragile and could break if the attribute type is renamed or moved to a different namespace. Consider using typeof() comparison or checking if the attribute is assignable from the expected type.

Suggested change
Assert.Contains(defaultMetadata.Attributes.Attributes, attr => attr.GetType().Name == "NullableContextAttribute");
Assert.Contains(defaultMetadata.Attributes.Attributes, attr => attr.GetType().Name == "ClassInterfaceAttribute");
Assert.Contains(defaultMetadata.Attributes.Attributes, attr => attr.GetType().Name == "ComVisibleAttribute");
Assert.Contains(defaultMetadata.Attributes.Attributes, attr => attr.GetType().Name == "TypeForwardedFromAttribute");
Assert.Contains(defaultMetadata.Attributes.Attributes, attr => attr is NullableContextAttribute);
Assert.Contains(defaultMetadata.Attributes.Attributes, attr => attr is ClassInterfaceAttribute);
Assert.Contains(defaultMetadata.Attributes.Attributes, attr => attr is ComVisibleAttribute);
Assert.Contains(defaultMetadata.Attributes.Attributes, attr => attr is TypeForwardedFromAttribute);

Copilot uses AI. Check for mistakes.

Comment on lines 218 to 221
Assert.Contains(defaultMetadata.Attributes.Attributes, attr => attr.GetType().Name == "NullableContextAttribute");
Assert.Contains(defaultMetadata.Attributes.Attributes, attr => attr.GetType().Name == "ClassInterfaceAttribute");
Assert.Contains(defaultMetadata.Attributes.Attributes, attr => attr.GetType().Name == "ComVisibleAttribute");
Assert.Contains(defaultMetadata.Attributes.Attributes, attr => attr.GetType().Name == "TypeForwardedFromAttribute");
Copy link
Preview

Copilot AI Jul 23, 2025

Choose a reason for hiding this comment

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

Using string comparison with GetType().Name for attribute type checking is fragile and could break if the attribute type is renamed or moved to a different namespace. Consider using typeof() comparison or checking if the attribute is assignable from the expected type.

Suggested change
Assert.Contains(defaultMetadata.Attributes.Attributes, attr => attr.GetType().Name == "NullableContextAttribute");
Assert.Contains(defaultMetadata.Attributes.Attributes, attr => attr.GetType().Name == "ClassInterfaceAttribute");
Assert.Contains(defaultMetadata.Attributes.Attributes, attr => attr.GetType().Name == "ComVisibleAttribute");
Assert.Contains(defaultMetadata.Attributes.Attributes, attr => attr.GetType().Name == "TypeForwardedFromAttribute");
Assert.Contains(defaultMetadata.Attributes.Attributes, attr => attr.GetType() == typeof(NullableContextAttribute));
Assert.Contains(defaultMetadata.Attributes.Attributes, attr => attr.GetType() == typeof(ClassInterfaceAttribute));
Assert.Contains(defaultMetadata.Attributes.Attributes, attr => attr.GetType() == typeof(ComVisibleAttribute));
Assert.Contains(defaultMetadata.Attributes.Attributes, attr => attr.GetType() == typeof(TypeForwardedFromAttribute));

Copilot uses AI. Check for mistakes.

Comment on lines 218 to 221
Assert.Contains(defaultMetadata.Attributes.Attributes, attr => attr.GetType().Name == "NullableContextAttribute");
Assert.Contains(defaultMetadata.Attributes.Attributes, attr => attr.GetType().Name == "ClassInterfaceAttribute");
Assert.Contains(defaultMetadata.Attributes.Attributes, attr => attr.GetType().Name == "ComVisibleAttribute");
Assert.Contains(defaultMetadata.Attributes.Attributes, attr => attr.GetType().Name == "TypeForwardedFromAttribute");
Copy link
Preview

Copilot AI Jul 23, 2025

Choose a reason for hiding this comment

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

Using string comparison with GetType().Name for attribute type checking is fragile and could break if the attribute type is renamed or moved to a different namespace. Consider using typeof() comparison or checking if the attribute is assignable from the expected type.

Suggested change
Assert.Contains(defaultMetadata.Attributes.Attributes, attr => attr.GetType().Name == "NullableContextAttribute");
Assert.Contains(defaultMetadata.Attributes.Attributes, attr => attr.GetType().Name == "ClassInterfaceAttribute");
Assert.Contains(defaultMetadata.Attributes.Attributes, attr => attr.GetType().Name == "ComVisibleAttribute");
Assert.Contains(defaultMetadata.Attributes.Attributes, attr => attr.GetType().Name == "TypeForwardedFromAttribute");
Assert.Contains(defaultMetadata.Attributes.Attributes, attr => attr is NullableContextAttribute);
Assert.Contains(defaultMetadata.Attributes.Attributes, attr => attr is ClassInterfaceAttribute);
Assert.Contains(defaultMetadata.Attributes.Attributes, attr => attr is ComVisibleAttribute);
Assert.Contains(defaultMetadata.Attributes.Attributes, attr => attr is TypeForwardedFromAttribute);

Copilot uses AI. Check for mistakes.

Comment on lines 218 to 221
Assert.Contains(defaultMetadata.Attributes.Attributes, attr => attr.GetType().Name == "NullableContextAttribute");
Assert.Contains(defaultMetadata.Attributes.Attributes, attr => attr.GetType().Name == "ClassInterfaceAttribute");
Assert.Contains(defaultMetadata.Attributes.Attributes, attr => attr.GetType().Name == "ComVisibleAttribute");
Assert.Contains(defaultMetadata.Attributes.Attributes, attr => attr.GetType().Name == "TypeForwardedFromAttribute");
Copy link
Preview

Copilot AI Jul 23, 2025

Choose a reason for hiding this comment

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

Using string comparison with GetType().Name for attribute type checking is fragile and could break if the attribute type is renamed or moved to a different namespace. Consider using typeof() comparison or checking if the attribute is assignable from the expected type.

Suggested change
Assert.Contains(defaultMetadata.Attributes.Attributes, attr => attr.GetType().Name == "NullableContextAttribute");
Assert.Contains(defaultMetadata.Attributes.Attributes, attr => attr.GetType().Name == "ClassInterfaceAttribute");
Assert.Contains(defaultMetadata.Attributes.Attributes, attr => attr.GetType().Name == "ComVisibleAttribute");
Assert.Contains(defaultMetadata.Attributes.Attributes, attr => attr.GetType().Name == "TypeForwardedFromAttribute");
Assert.Contains(defaultMetadata.Attributes.Attributes, attr => attr is System.Runtime.CompilerServices.NullableContextAttribute);
Assert.Contains(defaultMetadata.Attributes.Attributes, attr => attr is System.Runtime.InteropServices.ClassInterfaceAttribute);
Assert.Contains(defaultMetadata.Attributes.Attributes, attr => attr is System.Runtime.InteropServices.ComVisibleAttribute);
Assert.Contains(defaultMetadata.Attributes.Attributes, attr => attr is System.Runtime.CompilerServices.TypeForwardedFromAttribute);

Copilot uses AI. Check for mistakes.

// Not exactly "no attributes" due to SerializableAttribute on object.
Assert.IsType<SerializableAttribute>(Assert.Single(defaultMetadata.Attributes.Attributes));
// Not exactly "no attributes" due to pseudo-attributes on object.
// After CoreCLR consistency fix, object.GetCustomAttributes() returns all pseudo-attributes.
Copy link
Member

Choose a reason for hiding this comment

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

Should this test be deleted instead? It does not make sense for ASP.NET to test internal implementation details of CoreCLR.

Copy link
Contributor Author

Choose a reason for hiding this comment

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

but looking at the name of the testcase
GetMetadataForParameter_SuppliesEmptyAttributes_WhenParameterHasNoAttributes and GetAttributesForParameter_NoAttributes the intent seems to be testing that a parameter with "no attributes" behaves correctly, so in this situation we either could just remove this testscase or Change the test to just verify that ParameterAttributes is empty (thats what I feel is the actual ASP.NET Core concern)

Copy link
Member

Choose a reason for hiding this comment

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

Either way sounds good to me.

…assertions

Signed-off-by: Medha Tiwari <medhavns1@gmail.com>
Copy link
Member

@jkotas jkotas left a comment

Choose a reason for hiding this comment

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

LGTM

@Minimal-APIs PTLA

Copy link
Member

@captainsafia captainsafia left a comment

Choose a reason for hiding this comment

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

LGTM -- thanks!

@captainsafia captainsafia merged commit 90236e1 into dotnet:main Jul 23, 2025
30 checks passed
@dotnet-policy-service dotnet-policy-service bot added this to the 10.0-preview7 milestone Jul 23, 2025
@wtgodbe wtgodbe modified the milestones: 10.0-preview7, 10.0-rc1 Jul 23, 2025
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
area-mvc Includes: MVC, Actions and Controllers, Localization, CORS, most templates community-contribution Indicates that the PR has been added by a community member
Projects
None yet
Development

Successfully merging this pull request may close these issues.

4 participants