// Copyright (c) Microsoft Corporation. All rights reserved. See License.txt in the project root for license information. using System; using System.Linq.Expressions; using System.Reflection; using Assert = Microsoft.TestCommon.AssertEx; namespace Microsoft.TestCommon { public class ReflectionAssert { private static PropertyInfo GetPropertyInfo<T, TProp>(Expression<Func<T, TProp>> property) { if (property.Body is MemberExpression) { return (PropertyInfo)((MemberExpression)property.Body).Member; } else if (property.Body is UnaryExpression && property.Body.NodeType == ExpressionType.Convert) { return (PropertyInfo)((MemberExpression)((UnaryExpression)property.Body).Operand).Member; } else { throw new InvalidOperationException("Could not determine property from lambda expression."); } } private static void TestPropertyValue<TInstance, TValue>(TInstance instance, Func<TInstance, TValue> getFunc, Action<TInstance, TValue> setFunc, TValue valueToSet, TValue valueToCheck) { setFunc(instance, valueToSet); TValue newValue = getFunc(instance); Assert.Equal(valueToCheck, newValue); } private static void TestPropertyValue<TInstance, TValue>(TInstance instance, Func<TInstance, TValue> getFunc, Action<TInstance, TValue> setFunc, TValue value) { TestPropertyValue(instance, getFunc, setFunc, value, value); } public void Property<T, TResult>(T instance, Expression<Func<T, TResult>> propertyGetter, TResult expectedDefaultValue, bool allowNull = false, TResult roundTripTestValue = null) where TResult : class { PropertyInfo property = GetPropertyInfo(propertyGetter); Func<T, TResult> getFunc = (obj) => (TResult)property.GetValue(obj, index: null); Action<T, TResult> setFunc = (obj, value) => property.SetValue(obj, value, index: null); Assert.Equal(expectedDefaultValue, getFunc(instance)); if (allowNull) { TestPropertyValue(instance, getFunc, setFunc, null); } else { Assert.ThrowsArgumentNull(() => { setFunc(instance, null); }, "value"); } if (roundTripTestValue != null) { TestPropertyValue(instance, getFunc, setFunc, roundTripTestValue); } } public void IntegerProperty<T, TResult>(T instance, Expression<Func<T, TResult>> propertyGetter, TResult expectedDefaultValue, TResult? minLegalValue, TResult? illegalLowerValue, TResult? maxLegalValue, TResult? illegalUpperValue, TResult roundTripTestValue) where TResult : struct { PropertyInfo property = GetPropertyInfo(propertyGetter); Func<T, TResult> getFunc = (obj) => (TResult)property.GetValue(obj, index: null); Action<T, TResult> setFunc = (obj, value) => property.SetValue(obj, value, index: null); Assert.Equal(expectedDefaultValue, getFunc(instance)); if (minLegalValue.HasValue) { TestPropertyValue(instance, getFunc, setFunc, minLegalValue.Value); } if (maxLegalValue.HasValue) { TestPropertyValue(instance, getFunc, setFunc, maxLegalValue.Value); } if (illegalLowerValue.HasValue) { Assert.ThrowsArgumentGreaterThanOrEqualTo(() => { setFunc(instance, illegalLowerValue.Value); }, "value", minLegalValue.Value.ToString(), illegalLowerValue.Value); } if (illegalUpperValue.HasValue) { Assert.ThrowsArgumentLessThanOrEqualTo(() => { setFunc(instance, illegalLowerValue.Value); }, "value", maxLegalValue.Value.ToString(), illegalUpperValue.Value); } TestPropertyValue(instance, getFunc, setFunc, roundTripTestValue); } public void BooleanProperty<T>(T instance, Expression<Func<T, bool>> propertyGetter, bool expectedDefaultValue) { PropertyInfo property = GetPropertyInfo(propertyGetter); Func<T, bool> getFunc = (obj) => (bool)property.GetValue(obj, index: null); Action<T, bool> setFunc = (obj, value) => property.SetValue(obj, value, index: null); Assert.Equal(expectedDefaultValue, getFunc(instance)); TestPropertyValue(instance, getFunc, setFunc, !expectedDefaultValue); } public void EnumProperty<T, TResult>(T instance, Expression<Func<T, TResult>> propertyGetter, TResult expectedDefaultValue, TResult illegalValue, TResult roundTripTestValue) where TResult : struct { PropertyInfo property = GetPropertyInfo(propertyGetter); Func<T, TResult> getFunc = (obj) => (TResult)property.GetValue(obj, index: null); Action<T, TResult> setFunc = (obj, value) => property.SetValue(obj, value, index: null); Assert.Equal(expectedDefaultValue, getFunc(instance)); Assert.ThrowsInvalidEnumArgument(() => { setFunc(instance, illegalValue); }, "value", Convert.ToInt32(illegalValue), typeof(TResult)); TestPropertyValue(instance, getFunc, setFunc, roundTripTestValue); } public void StringProperty<T>(T instance, Expression<Func<T, string>> propertyGetter, string expectedDefaultValue, bool allowNullAndEmpty = true, string nullAndEmptyReturnValue = "") { PropertyInfo property = GetPropertyInfo(propertyGetter); Func<T, string> getFunc = (obj) => (string)property.GetValue(obj, index: null); Action<T, string> setFunc = (obj, value) => property.SetValue(obj, value, index: null); Assert.Equal(expectedDefaultValue, getFunc(instance)); if (allowNullAndEmpty) { // Assert get/set works for null TestPropertyValue(instance, getFunc, setFunc, null, nullAndEmptyReturnValue); // Assert get/set works for String.Empty TestPropertyValue(instance, getFunc, setFunc, String.Empty, nullAndEmptyReturnValue); } else { Assert.ThrowsArgumentNullOrEmpty( delegate() { try { TestPropertyValue(instance, getFunc, setFunc, null); } catch (TargetInvocationException e) { throw e.InnerException; } }, "value"); Assert.ThrowsArgumentNullOrEmpty( delegate() { try { TestPropertyValue(instance, getFunc, setFunc, String.Empty); } catch (TargetInvocationException e) { throw e.InnerException; } }, "value"); } // Assert get/set works for arbitrary value TestPropertyValue(instance, getFunc, setFunc, "TestValue"); } } }