diff options
author | Michal Strehovský <michals@microsoft.com> | 2017-12-28 23:07:40 +0300 |
---|---|---|
committer | Michal Strehovský <michals@microsoft.com> | 2017-12-29 14:41:32 +0300 |
commit | 2a0ded50fd791bc98b7e881a2c0af083715d9c0c (patch) | |
tree | 5f34df67d30b6097b21eb0800f917892ba7660c6 | |
parent | 3f55a51b0012116bd766c1dd4edf2f09de20f323 (diff) |
Add test coverage for the "reflection metadata from use" feature
-rw-r--r-- | tests/src/Simple/Reflection/Reflection.cs | 516 | ||||
-rw-r--r-- | tests/src/Simple/SimpleTest.targets | 4 |
2 files changed, 341 insertions, 179 deletions
diff --git a/tests/src/Simple/Reflection/Reflection.cs b/tests/src/Simple/Reflection/Reflection.cs index 79b137560..189090b3a 100644 --- a/tests/src/Simple/Reflection/Reflection.cs +++ b/tests/src/Simple/Reflection/Reflection.cs @@ -2,278 +2,436 @@ // The .NET Foundation licenses this file to you under the MIT license. // See the LICENSE file in the project root for more information. +#if MULTIMODULE_BUILD && !DEBUG +// Some tests won't work if we're using optimizing codegen, but scanner doesn't run. +// This currently happens in optimized multi-obj builds. +#define OPTIMIZED_MODE_WITHOUT_SCANNER +#endif + using System; -using System.Globalization; using System.Reflection; -using System.Runtime.CompilerServices; -public class ReflectionTest -{ - const int Pass = 100; - const int Fail = -1; +[assembly: TestAssembly] +[module: TestModule] - public static int Main() +internal class ReflectionTest +{ + private static int Main() { - CultureInfo.CurrentCulture = CultureInfo.InvariantCulture; - - if (TestNames() == Fail) - return Fail; + // Things I would like to test, but we don't fully support yet: + // * Interface method is reflectable if we statically called it through a constrained call + // * Delegate Invoke method is reflectable if we statically called it + + // + // Tests for dependency graph in the compiler + // +#if !OPTIMIZED_MODE_WITHOUT_SCANNER + TestContainment.Run(); + TestInterfaceMethod.Run(); +#endif + TestAttributeInheritance.Run(); + TestStringConstructor.Run(); + TestAssemblyAndModuleAttributes.Run(); + TestAttributeExpressions.Run(); + + // + // Mostly functionality tests + // + TestCreateDelegate.Run(); + TestInstanceFields.Run(); + TestReflectionInvoke.Run(); + + return 100; + } - if (TestUnification() == Fail) - return Fail; + class TestReflectionInvoke + { + internal class InvokeTests + { + private string _world = "world"; + + public InvokeTests() { } + + public InvokeTests(string message) { _world = message; } + +#if OPTIMIZED_MODE_WITHOUT_SCANNER + [System.Runtime.CompilerServices.MethodImpl(System.Runtime.CompilerServices.MethodImplOptions.NoInlining)] +#endif + public static string GetHello(string name) + { + return "Hello " + name; + } + +#if OPTIMIZED_MODE_WITHOUT_SCANNER + [System.Runtime.CompilerServices.MethodImpl(System.Runtime.CompilerServices.MethodImplOptions.NoInlining)] +#endif + public static void GetHelloByRef(string name, out string result) + { + result = "Hello " + name; + } + +#if OPTIMIZED_MODE_WITHOUT_SCANNER + [System.Runtime.CompilerServices.MethodImpl(System.Runtime.CompilerServices.MethodImplOptions.NoInlining)] +#endif + public static string GetHelloGeneric<T>(T obj) + { + return "Hello " + obj; + } + + +#if OPTIMIZED_MODE_WITHOUT_SCANNER + [System.Runtime.CompilerServices.MethodImpl(System.Runtime.CompilerServices.MethodImplOptions.NoInlining)] +#endif + public string GetHelloInstance() + { + return "Hello " + _world; + } + } - if (TestTypeOf() == Fail) - return Fail; + public static void Run() + { + Console.WriteLine(nameof(TestReflectionInvoke)); + + // Ensure things we reflect on are in the static callgraph + if (string.Empty.Length > 0) + { + new InvokeTests().ToString(); + InvokeTests.GetHello(null); + InvokeTests.GetHelloGeneric<int>(0); + InvokeTests.GetHelloGeneric<double>(0); + string unused; + InvokeTests.GetHelloByRef(null, out unused); + unused.ToString(); + } + + { + MethodInfo helloMethod = typeof(InvokeTests).GetTypeInfo().GetDeclaredMethod("GetHello"); + string result = (string)helloMethod.Invoke(null, new object[] { "world" }); + if (result != "Hello world") + throw new Exception(); + } + + { + MethodInfo helloGenericMethod = typeof(InvokeTests).GetTypeInfo().GetDeclaredMethod("GetHelloGeneric").MakeGenericMethod(typeof(int)); + string result = (string)helloGenericMethod.Invoke(null, new object[] { 12345 }); + if (result != "Hello 12345") + throw new Exception(); + } + + { + MethodInfo helloByRefMethod = typeof(InvokeTests).GetTypeInfo().GetDeclaredMethod("GetHelloByRef"); + object[] args = new object[] { "world", null }; + helloByRefMethod.Invoke(null, args); + if ((string)args[1] != "Hello world") + throw new Exception(); + } + } + } - if (TestGenericComposition() == Fail) - return Fail; + class TestInstanceFields + { + public class FieldInvokeSample + { + public String InstanceField; + } - if (TestReflectionInvoke() == Fail) - return Fail; + public static void Run() + { + Console.WriteLine(nameof(TestInstanceFields)); - if (TestReflectionFieldAccess() == Fail) - return Fail; + TypeInfo ti = typeof(FieldInvokeSample).GetTypeInfo(); - if (TestCreateDelegate() == Fail) - return Fail; + FieldInfo instanceField = ti.GetDeclaredField("InstanceField"); + FieldInvokeSample obj = new FieldInvokeSample(); - return Pass; - } + String value = (String)(instanceField.GetValue(obj)); + if (value != null) + throw new Exception(); - private static int TestNames() - { - string hello = "Hello"; + obj.InstanceField = "Hi!"; + value = (String)(instanceField.GetValue(obj)); + if (value != "Hi!") + throw new Exception(); - Type stringType = hello.GetType(); + instanceField.SetValue(obj, "Bye!"); + if (obj.InstanceField != "Bye!") + throw new Exception(); - if (stringType.FullName != "System.String") - { - Console.WriteLine("Bad name"); - return Fail; + value = (String)(instanceField.GetValue(obj)); + if (value != "Bye!") + throw new Exception(); } - - return Pass; } - private static int TestUnification() + class TestCreateDelegate { - Console.WriteLine("Testing unification"); + internal class Greeter + { + private string _who; - // ReflectionTest type doesn't have an EEType and is metadata only. - Type programType = Type.GetType("ReflectionTest"); - TypeInfo programTypeInfo = programType.GetTypeInfo(); + public Greeter(string who) { _who = who; } - Type programBaseType = programTypeInfo.BaseType; +#if OPTIMIZED_MODE_WITHOUT_SCANNER + [System.Runtime.CompilerServices.MethodImpl(System.Runtime.CompilerServices.MethodImplOptions.NoInlining)] +#endif + public string Greet() + { + return "Hello " + _who; + } + } - Type objectType = (new Object()).GetType(); + delegate string GetHelloInstanceDelegate(Greeter o); - if (!objectType.Equals(programBaseType)) + public static void Run() { - Console.WriteLine("Unification failed"); - return Fail; + Console.WriteLine(nameof(TestCreateDelegate)); + + // Ensure things we reflect on are in the static callgraph + if (string.Empty.Length > 0) + { + new Greeter(null).Greet(); + GetHelloInstanceDelegate d = null; + Func<Greeter, string> d2 = d.Invoke; + d = d2.Invoke; + } + + TypeInfo ti = typeof(Greeter).GetTypeInfo(); + MethodInfo mi = ti.GetDeclaredMethod(nameof(Greeter.Greet)); + { + var d = (GetHelloInstanceDelegate)mi.CreateDelegate(typeof(GetHelloInstanceDelegate)); + if (d(new Greeter("mom")) != "Hello mom") + throw new Exception(); + } + + { + var d = (Func<Greeter, string>)mi.CreateDelegate(typeof(Func<Greeter, string>)); + if (d(new Greeter("pop")) != "Hello pop") + throw new Exception(); + } } - - return Pass; } - private static int TestTypeOf() + class TestAttributeExpressions { - Console.WriteLine("Testing typeof()"); + struct FirstNeverUsedType { } - Type intType = typeof(int); + struct SecondNeverUsedType { } - if (intType.FullName != "System.Int32") - { - Console.WriteLine("Bad name"); - return Fail; - } + class Gen<T> { } - if (12.GetType() != typeof(int)) + class TypeAttribute : Attribute { - Console.WriteLine("Bad compare"); - return Fail; - } + public Type SomeType { get; set; } - // This type only has a limited EEType (without a vtable) because it's not constructed. - if (typeof(UnallocatedType).FullName != "UnallocatedType") - { - return Fail; + public TypeAttribute() { } + public TypeAttribute(Type someType) + { + SomeType = someType; + } } - if (typeof(int) != typeof(int)) + enum MyEnum { } + + class EnumArrayAttribute : Attribute { - Console.WriteLine("Bad compare"); - return Fail; + public MyEnum[] EnumArray; } - return Pass; - } - - private static int TestGenericComposition() - { - Console.WriteLine("Testing generic composition"); + [Type(typeof(FirstNeverUsedType*[,]))] + class Holder1 { } - Type nullableOfIntType = typeof(int?); + [Type(SomeType = typeof(Gen<SecondNeverUsedType>))] + class Holder2 { } - string fullName = nullableOfIntType.FullName; - if (fullName.Contains("System.Nullable`1") && fullName.Contains("System.Int32")) - return Pass; - - return Fail; - } + [EnumArray(EnumArray = new MyEnum[] { 0 })] + class Holder3 { } - internal class InvokeTests - { - private string _world = "world"; + public static void Run() + { + Console.WriteLine(nameof(TestAttributeExpressions)); - public InvokeTests() { } + TypeAttribute attr1 = typeof(Holder1).GetCustomAttribute<TypeAttribute>(); + if (attr1.SomeType.ToString() != "ReflectionTest+TestAttributeExpressions+FirstNeverUsedType*[,]") + throw new Exception(); - public InvokeTests(string message) { _world = message; } + TypeAttribute attr2 = typeof(Holder2).GetCustomAttribute<TypeAttribute>(); + if (attr2.SomeType.ToString() != "ReflectionTest+TestAttributeExpressions+Gen`1[ReflectionTest+TestAttributeExpressions+SecondNeverUsedType]") + throw new Exception(); - [MethodImpl(MethodImplOptions.NoInlining)] - public static string GetHello(string name) - { - return "Hello " + name; + EnumArrayAttribute attr3 = typeof(Holder3).GetCustomAttribute<EnumArrayAttribute>(); + if (attr3.EnumArray[0] != 0) + throw new Exception(); } + } - [MethodImpl(MethodImplOptions.NoInlining)] - public static void GetHelloByRef(string name, out string result) + class TestAssemblyAndModuleAttributes + { + public static void Run() { - result = "Hello " + name; - } + Console.WriteLine(nameof(TestAssemblyAndModuleAttributes)); - [MethodImpl(MethodImplOptions.NoInlining)] - public static string GetHelloGeneric<T>(T obj) - { - return "Hello " + obj; + // Also tests GetExecutingAssembly + var assAttr = Assembly.GetExecutingAssembly().GetCustomAttribute<TestAssemblyAttribute>(); + if (assAttr == null) + throw new Exception(); + + // Also tests GetEntryAssembly + var modAttr = Assembly.GetEntryAssembly().ManifestModule.GetCustomAttribute<TestModuleAttribute>(); + if (modAttr == null) + throw new Exception(); } + } - [MethodImpl(MethodImplOptions.NoInlining)] - public string GetHelloInstance() + class TestStringConstructor + { + public static void Run() { - return "Hello " + _world; + Console.WriteLine(nameof(TestStringConstructor)); + + // Ensure things we reflect on are in the static callgraph + if (string.Empty.Length > 0) + { + new string(new char[] { }, 0, 0); + } + + ConstructorInfo ctor = typeof(string).GetConstructor(new Type[] { typeof(char[]), typeof(int), typeof(int) }); + object str = ctor.Invoke(new object[] { new char[] { 'a' }, 0, 1 }); + if ((string)str != "a") + throw new Exception(); } } - private static int TestReflectionInvoke() + class TestAttributeInheritance { - Console.WriteLine("Testing reflection invoke"); - - // Dummy code to make sure the reflection targets are compiled. - if (String.Empty.Length > 0) + class BaseAttribute : Attribute { - new InvokeTests().ToString(); - InvokeTests.GetHello(null); - InvokeTests.GetHelloGeneric<int>(0); - InvokeTests.GetHelloGeneric<double>(0); - string unused; - InvokeTests.GetHelloByRef(null, out unused); - unused.ToString(); + public string Field; + public int Property { get; set; } } + class DerivedAttribute : BaseAttribute { } + + [Derived(Field = "Hello", Property = 100)] + class TestType { } + + public static void Run() { - MethodInfo helloMethod = typeof(InvokeTests).GetTypeInfo().GetDeclaredMethod("GetHello"); - string result = (string)helloMethod.Invoke(null, new object[] { "world" }); - if (result != "Hello world") - return Fail; + Console.WriteLine(nameof(TestAttributeInheritance)); + + DerivedAttribute attr = typeof(TestType).GetCustomAttribute<DerivedAttribute>(); + if (attr.Field != "Hello" || attr.Property != 100) + throw new Exception(); } + } + class TestInterfaceMethod + { + interface IFoo { - MethodInfo helloGenericMethod = typeof(InvokeTests).GetTypeInfo().GetDeclaredMethod("GetHelloGeneric").MakeGenericMethod(typeof(int)); - string result = (string)helloGenericMethod.Invoke(null, new object[] { 12345 }); - if (result != "Hello 12345") - return Fail; + string Frob(int x); } + class Foo : IFoo { - MethodInfo helloGenericMethod = typeof(InvokeTests).GetTypeInfo().GetDeclaredMethod("GetHelloGeneric").MakeGenericMethod(typeof(double)); - string result = (string)helloGenericMethod.Invoke(null, new object[] { 3.14 }); - if (result != "Hello 3.14") - return Fail; + public string Frob(int x) + { + return x.ToString(); + } } + public static void Run() { - MethodInfo helloByRefMethod = typeof(InvokeTests).GetTypeInfo().GetDeclaredMethod("GetHelloByRef"); - object[] args = new object[] { "world", null }; - helloByRefMethod.Invoke(null, args); - if ((string)args[1] != "Hello world") - return Fail; - } + Console.WriteLine(nameof(TestInterfaceMethod)); - return Pass; - } + // Ensure things we reflect on are in the static callgraph + if (string.Empty.Length > 0) + { + ((IFoo)new Foo()).Frob(1); + } - public class FieldInvokeSample - { - public String InstanceField; + object result = InvokeTestMethod(typeof(IFoo), "Frob", new Foo(), 42); + if ((string)result != "42") + throw new Exception(); + } } - private static int TestReflectionFieldAccess() + class TestContainment { - Console.WriteLine("Testing reflection field access"); - - if (string.Empty.Length > 0) + class NeverUsedContainerType { - new FieldInvokeSample().ToString(); + public class UsedNestedType + { + public static int CallMe() + { + return 42; + } + } } - TypeInfo ti = typeof(FieldInvokeSample).GetTypeInfo(); + + public static void Run() { - FieldInfo instanceField = ti.GetDeclaredField("InstanceField"); - FieldInvokeSample obj = new FieldInvokeSample(); + Console.WriteLine(nameof(TestContainment)); - String value = (String)(instanceField.GetValue(obj)); - if (value != null) - return Fail; + // Ensure things we reflect on are in the static callgraph + if (string.Empty.Length > 0) + { + NeverUsedContainerType.UsedNestedType.CallMe(); + } - obj.InstanceField = "Hi!"; - value = (String)(instanceField.GetValue(obj)); - if (value != "Hi!") - return Fail; + Type neverUsedContainerType = GetTestType(nameof(TestContainment), nameof(NeverUsedContainerType)); + Type usedNestedType = neverUsedContainerType.GetNestedType(nameof(NeverUsedContainerType.UsedNestedType)); - instanceField.SetValue(obj, "Bye!"); - if (obj.InstanceField != "Bye!") - return Fail; + // Since we called CallMe, it has reflection metadata and it is invokable + object o = InvokeTestMethod(usedNestedType, nameof(NeverUsedContainerType.UsedNestedType.CallMe)); + if ((int)o != 42) + throw new Exception(); - value = (String)(instanceField.GetValue(obj)); - if (value != "Bye!") - return Fail; + // We can get a type handle for the nested type (the invoke mapping table needs it) + if (!HasTypeHandle(usedNestedType)) + throw new Exception($"{nameof(NeverUsedContainerType.UsedNestedType)} should have an EEType"); - return Pass; + // But the containing type doesn't need an EEType + if (HasTypeHandle(neverUsedContainerType)) + throw new Exception($"{nameof(NeverUsedContainerType)} should not have an EEType"); } } - delegate string GetHelloInstanceDelegate(InvokeTests o); + #region Helpers - private static int TestCreateDelegate() + private static Type GetTestType(string testName, string typeName) { - Console.WriteLine("Testing MethodInfo.CreateDelegate"); + string fullTypeName = $"{nameof(ReflectionTest)}+{testName}+{typeName}"; + Type result = Type.GetType(fullTypeName); + if (result == null) + throw new Exception($"'{fullTypeName}' could not be located"); + return result; + } - // Dummy code to make sure the reflection targets are compiled. - if (String.Empty.Length > 0) - { - new InvokeTests().GetHelloInstance(); - GetHelloInstanceDelegate d = null; - Func<InvokeTests, string> d2 = d.Invoke; - d = d2.Invoke; - } + private static object InvokeTestMethod(Type type, string methodName, object thisObj = null, params object[] param) + { + MethodInfo method = type.GetMethod(methodName); + if (method == null) + throw new Exception($"Method '{methodName}' not found on type {type}"); + + return method.Invoke(thisObj, param); + } - TypeInfo ti = typeof(InvokeTests).GetTypeInfo(); - MethodInfo mi = ti.GetDeclaredMethod("GetHelloInstance"); + private static bool HasTypeHandle(Type type) + { + try { - var d = (GetHelloInstanceDelegate)mi.CreateDelegate(typeof(GetHelloInstanceDelegate)); - if (d(new InvokeTests("mom")) != "Hello mom") - return Fail; + RuntimeTypeHandle typeHandle = type.TypeHandle; } - + catch (Exception) { - var d = (Func<InvokeTests, string>)mi.CreateDelegate(typeof(Func<InvokeTests, string>)); - if (d(new InvokeTests("pop")) != "Hello pop") - return Fail; + return false; } - - return Pass; + return true; } + + #endregion } -class UnallocatedType { } +class TestAssemblyAttribute : Attribute { } +class TestModuleAttribute : Attribute { } diff --git a/tests/src/Simple/SimpleTest.targets b/tests/src/Simple/SimpleTest.targets index f222bfc4d..aae948971 100644 --- a/tests/src/Simple/SimpleTest.targets +++ b/tests/src/Simple/SimpleTest.targets @@ -50,6 +50,10 @@ <CustomLinkerArg Include="$(AdditionalLinkerFlags)" /> </ItemGroup> + <PropertyGroup> + <DefineConstants Condition="'$(IlcMultiModule)' == 'true'">MULTIMODULE_BUILD;$(DefineConstants)</DefineConstants> + </PropertyGroup> + <ItemGroup> <IlcArg Include="--targetarch=$(Platform)" /> <IlcArg Include="--stacktracedata" /> |