Add tests for nullable metadata and unconditionally set nullablePublicOnly (dotnet...
authorSantiago Fernandez Madero <safern@microsoft.com>
Mon, 15 Jul 2019 17:37:36 +0000 (10:37 -0700)
committerGitHub <noreply@github.com>
Mon, 15 Jul 2019 17:37:36 +0000 (10:37 -0700)
* Add tests for nullable compiler emitted attributes

* Set nullablePublicOnly compiler feature unconditionally

* PR Feedback

* PR Feedback -- add positive and more exhaustive tests

Commit migrated from https://github.com/dotnet/corefx/commit/08cd402602ab5d3ce26fda968f9e0e1dff2d8b6f

src/libraries/System.Runtime/tests/System.Runtime.Tests.csproj
src/libraries/System.Runtime/tests/System/Attributes.cs
src/libraries/System.Runtime/tests/System/NullableMetadataTests.cs [new file with mode: 0644]

index fb5d157..59ab721 100644 (file)
     <Compile Include="System\NotFiniteNumberExceptionTests.cs" />
     <Compile Include="System\NotImplementedExceptionTests.cs" />
     <Compile Include="System\NotSupportedExceptionTests.cs" />
+    <Compile Include="System\NullableMetadataTests.cs" />
     <Compile Include="System\NullableTests.cs" />
     <Compile Include="System\NullReferenceExceptionTests.cs" />
     <Compile Include="System\ObjectTests.cs" />
index 484d05d..d56a398 100644 (file)
@@ -207,6 +207,7 @@ namespace System.Tests
             Assert.Equal("System.Tests.MyCustomAttribute System.Tests.MyCustomAttribute", string.Join(" ", typeof(MultipleAttributes).GetCustomAttributes(inherit: true)));
         }
     }
+
     public static class GetCustomAttribute
     {
 
@@ -220,6 +221,7 @@ namespace System.Tests
             // [System.Diagnostics.DebuggableAttribute((Boolean)True, (Boolean)False)]
             Assert.Equal(4, customAttributes.Count);
         }
+
         [Fact]
         public static void PositiveTest1()
         {
diff --git a/src/libraries/System.Runtime/tests/System/NullableMetadataTests.cs b/src/libraries/System.Runtime/tests/System/NullableMetadataTests.cs
new file mode 100644 (file)
index 0000000..f7d2f39
--- /dev/null
@@ -0,0 +1,155 @@
+using System.Buffers;
+using System.Collections.Concurrent;
+using System.Collections.Generic;
+using System.Linq;
+using System.Reflection;
+using Xunit;
+
+namespace System.Runtime.Tests
+{
+    public static class NullableMetadataTests
+    {
+        private const string NullableAttributeFullName = "System.Runtime.CompilerServices.NullableAttribute";
+        private const string NullableContextAttributeFullName = "System.Runtime.CompilerServices.NullableContextAttribute";
+        private const string NullablePublicOnlyAttributeFullName = "System.Runtime.CompilerServices.NullablePublicOnlyAttribute";
+
+        private static IEnumerable<CustomAttributeData> GetNullableAttributes(this IEnumerable<CustomAttributeData> attributes) =>
+            attributes.Where(attribute => attribute.AttributeType.FullName.Equals(NullableAttributeFullName) ||
+                                          attribute.AttributeType.FullName.Equals(NullableContextAttributeFullName));
+
+        private static CustomAttributeData GetNullablePublicOnlyAttribute(this IEnumerable<CustomAttributeData> attributes) =>
+            attributes.Where(attribute => attribute.AttributeType.FullName.Equals(NullablePublicOnlyAttributeFullName)).FirstOrDefault();
+
+        private static bool IsProtected(this MemberInfo info)
+        {
+            if (info is MethodBase methodBase)
+            {
+                return methodBase.IsFamily || methodBase.IsFamilyOrAssembly;
+            }
+            else if (info is FieldInfo fieldInfo)
+            {
+                return fieldInfo.IsFamily || fieldInfo.IsFamilyOrAssembly;
+            }
+            else if (info is PropertyInfo propertyInfo)
+            {
+                return (propertyInfo.GetMethod != null && (propertyInfo.GetMethod.IsFamily || propertyInfo.GetMethod.IsFamilyOrAssembly)) ||
+                       (propertyInfo.SetMethod != null && (propertyInfo.SetMethod.IsFamily || propertyInfo.SetMethod.IsFamilyOrAssembly));
+            }
+            else if (info is TypeInfo typeInfo)
+            {
+                return typeInfo.IsNestedFamily || typeInfo.IsNestedFamORAssem;
+            }
+
+            return false;
+        }
+
+        private static IEnumerable<object[]> NullableMetadataTypesTestData()
+        {
+            yield return new object[] { typeof(string) };
+            yield return new object[] { typeof(Dictionary<,>) };
+            yield return new object[] { typeof(Uri) };
+            yield return new object[] { typeof(ConcurrentDictionary<,>) };
+            yield return new object[] { typeof(ArrayPool<>) };
+        }
+
+        [Theory]
+        [MemberData(nameof(NullableMetadataTypesTestData))]
+        public static void NullableAttributesOnPublicApiOnly(Type type)
+        {
+            MemberInfo[] internalMembers = type.GetMembers(BindingFlags.NonPublic | BindingFlags.Static | BindingFlags.Instance);
+
+            foreach (MemberInfo internalMember in internalMembers)
+            {
+                // When using BindingFlags.NonPublic protected members are included and those are expected
+                // to have Nullable attributes.
+                if (internalMember.IsProtected() || internalMember is PropertyInfo) // TODO-NULLABLE: validate properties (https://github.com/dotnet/roslyn/issues/37161)
+                    continue;
+
+                Assert.Empty(internalMember.CustomAttributes.GetNullableAttributes());
+
+                if (internalMember is MethodInfo methodInfo)
+                {
+                    Assert.Empty(methodInfo.ReturnParameter.CustomAttributes.GetNullableAttributes());
+
+                    foreach (ParameterInfo param in methodInfo.GetParameters())
+                    {
+                        Assert.Empty(param.CustomAttributes.GetNullableAttributes());
+                    }
+                }
+            }
+
+            Assert.True(type.CustomAttributes.GetNullableAttributes().Any());
+
+            bool foundAtLeastOneNullableAttribute = type.CustomAttributes.Where(a => a.AttributeType.Name.Equals(NullableContextAttributeFullName)).Any();
+            
+            // If there is a NullableContextAttribute there is no guarantee that its members will have
+            // nullable attributes, if a class declare all reference types with the same nullability
+            // none will contain an attribute and will take the type's NullableContextAttribute value.
+            if (!foundAtLeastOneNullableAttribute)
+            {
+                MemberInfo[] publicMembers = type.GetMembers(BindingFlags.Public | BindingFlags.Static | BindingFlags.Instance);
+                foreach (MemberInfo publicMember in publicMembers)
+                {
+                    if (publicMember.CustomAttributes.GetNullableAttributes().Any())
+                    {
+                        foundAtLeastOneNullableAttribute = true;
+                        break;
+                    }
+
+                    if (publicMember is MethodInfo methodInfo)
+                    {
+                        if (methodInfo.ReturnParameter.CustomAttributes.GetNullableAttributes().Any())
+                        {
+                            foundAtLeastOneNullableAttribute = true;
+                            break;
+                        }
+                    }
+
+                    if (publicMember is MethodBase methodBase)
+                    {
+                        foreach (ParameterInfo param in methodBase.GetParameters())
+                        {
+                            if (param.CustomAttributes.GetNullableAttributes().Any())
+                            {
+                                foundAtLeastOneNullableAttribute = true;
+                                break;
+                            }
+                        }
+
+                        if (foundAtLeastOneNullableAttribute)
+                            break;
+                    }
+                }
+            }
+
+            Assert.True(foundAtLeastOneNullableAttribute);
+        }
+
+        [Fact]
+        public static void ShimsHaveOnlyTypeForwards()
+        {
+            Assembly assembly = Assembly.Load("mscorlib");
+
+            Assert.Empty(assembly.GetTypes());
+            Assert.NotEmpty(assembly.GetForwardedTypes());
+        }
+
+        [Fact]
+        public static void ShimsDontHaveNullablePublicOnlyAttribute()
+        {
+            Assembly assembly = Assembly.Load("mscorlib");
+            Module module = assembly.Modules.First();
+            Assert.Null(module.CustomAttributes.GetNullablePublicOnlyAttribute());
+        }
+
+        [Theory]
+        [MemberData(nameof(NullableMetadataTypesTestData))]
+        public static void NullablePublicOnlyAttributePresent(Type type)
+        {
+            CustomAttributeData nullablePublicOnlyAttribute = type.Module.CustomAttributes.GetNullablePublicOnlyAttribute();
+            Assert.NotNull(nullablePublicOnlyAttribute);
+
+            Assert.False((bool)nullablePublicOnlyAttribute.ConstructorArguments.First().Value);
+        }
+    }
+}