コード例 #1
0
        public void ServiceActionParameterInvalidCasesTest()
        {
            ResourceType complexType           = new ResourceType(typeof(object), ResourceTypeKind.ComplexType, null, "foo", "Address", false);
            ResourceType entityType            = new ResourceType(typeof(object), ResourceTypeKind.EntityType, null, "foo", "Order", false);
            ResourceSet  entitySet             = new ResourceSet("Set", entityType);
            ResourceType collectionOfPrimitive = ResourceType.GetCollectionResourceType(ResourceType.GetPrimitiveResourceType(typeof(int)));
            ResourceType collectionOfComplex   = ResourceType.GetCollectionResourceType(complexType);
            ResourceType collectionType        = ResourceType.GetEntityCollectionResourceType(entityType);

            var parameterTypes = ResourceTypeUtils.GetPrimitiveResourceTypes()
                                 .Concat(new ResourceType[] {
                complexType,
                entityType,
                collectionOfPrimitive,
                collectionOfComplex,
                collectionType
            });

            AstoriaTestNS.TestUtil.RunCombinations(parameterTypes, (parameterType) =>
            {
                Exception e = AstoriaTestNS.TestUtil.RunCatching(() => new ServiceActionParameter("p", parameterType));
                if (parameterType != ResourceType.GetPrimitiveResourceType(typeof(System.IO.Stream)))
                {
                    Assert.IsNull(e, "Service op parameter should have succeeded.");
                }
                else
                {
                    ExceptionUtils.IsExpectedException <ArgumentException>(e, "The service operation parameter 'p' of type 'Edm.Stream' is not supported.\r\nParameter name: parameterType");
                }
            });
        }
コード例 #2
0
        public void ServiceOperationTypes()
        {
            ResourceType complexType           = new ResourceType(typeof(object), ResourceTypeKind.ComplexType, null, "foo", "Address", false);
            ResourceType entityType            = new ResourceType(typeof(object), ResourceTypeKind.EntityType, null, "foo", "Order", false);
            ResourceSet  entitySet             = new ResourceSet("Set", entityType);
            ResourceType collectionOfPrimitive = ResourceType.GetCollectionResourceType(ResourceType.GetPrimitiveResourceType(typeof(int)));
            ResourceType collectionOfComplex   = ResourceType.GetCollectionResourceType(complexType);

            var returnTypeDirectCases = ResourceTypeUtils.GetPrimitiveResourceTypes().Select(rt => new { Type = rt, Set = (ResourceSet)null, Invalid = rt.InstanceType == typeof(System.IO.Stream) ? true : false })
                                        .Concat(new[] {
                new { Type = complexType, Set = (ResourceSet)null, Invalid = false },
                new { Type = entityType, Set = entitySet, Invalid = false }
            })
                                        .Concat(new ResourceType[] { collectionOfPrimitive, collectionOfComplex }.Select(rt => new { Type = rt, Set = (ResourceSet)null, Invalid = true }));

            AstoriaTestNS.TestUtil.RunCombinations(returnTypeDirectCases, (returnTypeDirectCase) =>
            {
                Exception e = AstoriaTestNS.TestUtil.RunCatching(() =>
                                                                 new ServiceOperation("so", ServiceOperationResultKind.DirectValue, returnTypeDirectCase.Type, returnTypeDirectCase.Set, "GET", null));
                if (returnTypeDirectCase.Invalid)
                {
                    Assert.IsNotNull(e, "Service op should have failed to be constructed.");
                    Assert.IsTrue(e is ArgumentException, "Invalid exception type.");
                }
                else
                {
                    Assert.IsNull(e, "Service op should have succeeded.");
                }
            });
        }
コード例 #3
0
        public void NonReadOnlyValidation()
        {
            Type[] types = new Type[] {
                // cannot add service operation, since the parameters collection is passed in the constructor and is always cached.
                typeof(ResourceSet),
                typeof(ResourceType),
                typeof(ResourceProperty),
                typeof(ServiceOperationParameter)
            };

            AstoriaTestNS.TestUtil.RunCombinations(
                types,
                (t) =>
            {
                object instance = ResourceTypeUtils.GetTestInstance(t);

                // Make sure exception is throw if any of the public setters are called
                AstoriaTestNS.TestUtil.RunCombinations(
                    t.GetProperties(BindingFlags.Instance | BindingFlags.Public),
                    (propertyInfo) =>
                {
                    //Ignoring custom state, since its not governed by part of readonly
                    if (propertyInfo.Name == "CustomState")
                    {
                        return;
                    }

                    // call the getter twice and make sure they return the same instance for reference types i.e. caching takes place.
                    MethodInfo getter = propertyInfo.GetGetMethod();
                    if (getter != null && typeof(IList).IsAssignableFrom(propertyInfo.PropertyType))
                    {
                        object value1 = getter.Invoke(instance, null);
                        object value2 = getter.Invoke(instance, null);
                        Assert.IsTrue(!Object.ReferenceEquals(value1, value2), String.Format("For non-readonly types, the collection properties should not return the same instance. Property: {0}", propertyInfo.Name));
                    }
                });
            });
        }
コード例 #4
0
        public void CollectionTypeValidation()
        {
            ResourceType entityType   = new ResourceType(typeof(object), ResourceTypeKind.EntityType, null, "foo", "Order", false);
            ResourceType complexType  = new ResourceType(typeof(object), ResourceTypeKind.ComplexType, null, "foo", "bar", false);
            ResourceType complexType2 = new ResourceType(typeof(object), ResourceTypeKind.ComplexType, null, "foo", "bar2", false);

            complexType2.AddProperty(new ResourceProperty("CollectionProperty", ResourcePropertyKind.Collection, ResourceType.GetCollectionResourceType(complexType)));

            var itemTypes = new ResourceType[] { complexType, complexType2 }.Concat(ResourceTypeUtils.GetPrimitiveResourceTypes());
            var collectionTypes = itemTypes.Except(new[] { ResourceType.GetPrimitiveResourceType(typeof(System.IO.Stream)) }).Select(it => new { ItemType = it, CollectionType = ResourceType.GetCollectionResourceType(it) });

            AstoriaTestNS.TestUtil.RunCombinations(
                collectionTypes,
                c =>
            {
                Assert.AreEqual(c.ItemType, c.CollectionType.ItemType, "The item type of the collection doesn't match the one specified upon creation.");
                Assert.AreEqual("Collection(" + c.ItemType.FullName + ")", c.CollectionType.FullName, "The full name of a collection type is wrong.");
                Assert.AreEqual("Collection(" + c.ItemType.FullName + ")", c.CollectionType.Name, "The name of a collection type is wrong.");
                Assert.AreEqual("", c.CollectionType.Namespace, "The namespace of a collection type should be empty.");
                Assert.IsTrue(c.CollectionType.IsReadOnly, "The collection type is always read-only.");
                Assert.IsFalse(c.CollectionType.IsAbstract, "Collection type is never abstract.");
                Assert.IsFalse(c.CollectionType.IsOpenType, "Collection type is never open.");
                Assert.IsFalse(c.CollectionType.IsMediaLinkEntry, "Collection type is never an MLE.");
                Assert.AreEqual(typeof(IEnumerable <>).MakeGenericType(c.ItemType.InstanceType), c.CollectionType.InstanceType, "The instance type of the collection type is wrong.");
                Assert.IsTrue(c.CollectionType.CanReflectOnInstanceType, "Collection type has CanReflectOnInstanceType always true.");
                Assert.IsNull(c.CollectionType.BaseType, "Collection type has never a base type.");
                Assert.AreEqual(ResourceTypeKind.Collection, c.CollectionType.ResourceTypeKind, "The kind of a collection type is always Collection.");
                Assert.AreEqual(0, c.CollectionType.PropertiesDeclaredOnThisType.Count(), "Collection type has no properties.");
                Assert.AreEqual(0, c.CollectionType.Properties.Count(), "Collection type has no properties.");
                Assert.AreEqual(0, c.CollectionType.KeyProperties.Count(), "Collection type has no properties.");
                Assert.AreEqual(0, c.CollectionType.ETagProperties.Count(), "Collection type has no properties.");
                Assert.IsNull(c.CollectionType.CustomState, "Custom state should be null by default.");

                ExceptionUtils.ThrowsException <InvalidOperationException>(
                    () => c.CollectionType.IsMediaLinkEntry = true,
                    "Setting MLE on collection type should fail.");
                ExceptionUtils.ThrowsException <InvalidOperationException>(
                    () => c.CollectionType.IsOpenType = true,
                    "Setting IsOpenType on collection type should fail.");
                ExceptionUtils.ThrowsException <InvalidOperationException>(
                    () => c.CollectionType.CanReflectOnInstanceType = false,
                    "Setting CanReflectOnInstanceType on collection type should fail.");
                // Setting a custom state should still work
                c.CollectionType.CustomState = "some value";
                Assert.AreEqual("some value", c.CollectionType.CustomState, "Custom state doesn't persist its value.");

                ExceptionUtils.ThrowsException <InvalidOperationException>(
                    () => c.CollectionType.AddProperty(new ResourceProperty("ID", ResourcePropertyKind.ComplexType, complexType)),
                    "Adding a property on collection type should fail.");

                c.CollectionType.SetReadOnly();
                Assert.IsTrue(c.CollectionType.IsReadOnly, "The collection type is always read-only.");
            });

            // Verify that only primitive and complex types are allowed as items in a collection
            ResourceType collectionType = ResourceType.GetCollectionResourceType(complexType);

            foreach (var t in new ResourceType[] { entityType, collectionType })
            {
                Exception exception = AstoriaTestNS.TestUtil.RunCatching(() => ResourceType.GetCollectionResourceType(t));
                Assert.IsTrue(exception is ArgumentException, "Exception of a wrong type");
                Assert.AreEqual(
                    "Only collection properties that contain primitive types or complex types are supported.",
                    exception.Message, "Wrong exception message.");
            }
        }
コード例 #5
0
        public void InvalidCasesTest()
        {
            ResourceType rt = (ResourceType)ResourceTypeUtils.GetTestInstance(typeof(ResourceType));

            ResourceType complexType = new ResourceType(typeof(object), ResourceTypeKind.ComplexType, null, "Namespace", "Address", false);
            ResourceType entityType  = new ResourceType(typeof(object), ResourceTypeKind.EntityType, null, "Namespace", "Order", false);
            ResourceType primitiveOrComplexCollectionType = ResourceType.GetCollectionResourceType(complexType);
            ResourceType entityCollectionType             = ResourceType.GetEntityCollectionResourceType(entityType);
            ResourceType namedStreamType = ResourceType.GetPrimitiveResourceType(typeof(System.IO.Stream));

            AstoriaTestNS.TestUtil.RunCombinations(
                GetPropertyKindValues(),
                new ResourceType[] { entityType, complexType, primitiveOrComplexCollectionType, entityCollectionType }.Concat(ResourceTypeUtils.GetPrimitiveResourceTypes()),
                (kind, type) =>
            {
                bool invalidCase = true;

                if (IsValidValue(kind))
                {
                    if ((kind.HasFlag(ResourcePropertyKind.Primitive) && type.ResourceTypeKind == ResourceTypeKind.Primitive && type != namedStreamType) ||
                        (kind.HasFlag(ResourcePropertyKind.ComplexType) && type.ResourceTypeKind == ResourceTypeKind.ComplexType) ||
                        (kind.HasFlag(ResourcePropertyKind.ResourceReference) && type.ResourceTypeKind == ResourceTypeKind.EntityType) ||
                        (kind.HasFlag(ResourcePropertyKind.ResourceSetReference) && type.ResourceTypeKind == ResourceTypeKind.EntityType) ||
                        (kind.HasFlag(ResourcePropertyKind.Collection) && type.ResourceTypeKind == ResourceTypeKind.Collection) ||
                        (kind.HasFlag(ResourcePropertyKind.Stream) && type == namedStreamType))
                    {
                        invalidCase = false;
                    }

                    if ((kind & ResourcePropertyKind.Key) == ResourcePropertyKind.Key &&
                        type.InstanceType.IsGenericType && type.InstanceType.GetGenericTypeDefinition() == typeof(Nullable <>))
                    {
                        invalidCase = true;
                    }
                }

                if (invalidCase)
                {
                    ExceptionUtils.ThrowsException <ArgumentException>(
                        () => new ResourceProperty("TestProperty", kind, type),
                        string.Format("resource property constructor test case. Kind: '{0}', Type: '{1}: {2}'", kind, type.ResourceTypeKind, type.InstanceType));
                }
                else
                {
                    ResourceProperty p = new ResourceProperty("TestProperty", kind, type);
                    Assert.AreEqual("TestProperty", p.Name, "Name was not stored properly");
                    Assert.AreEqual(kind, p.Kind, "Kind was not stored properly");
                    Assert.AreEqual(type, p.ResourceType, "Type was not stored properly");
                    Assert.AreEqual(p.Kind.HasFlag(ResourcePropertyKind.Stream) ? false : true, p.CanReflectOnInstanceTypeProperty, "CanReflectOnInstanceTypeProperty should be true by default on non-NamedStreams, and false on NamedStreams.");
                    Assert.IsNull(p.MimeType, "MimeType should be null by default.");

                    if (p.Kind.HasFlag(ResourcePropertyKind.Stream))
                    {
                        ExceptionUtils.ThrowsException <InvalidOperationException>(
                            () => { p.CanReflectOnInstanceTypeProperty = false; },
                            "CanReflectOnInstanceTypeProperty should be settable on non-namedstreams, and not settable on namedstreams");
                    }
                    else
                    {
                        p.CanReflectOnInstanceTypeProperty = false;
                    }

                    Assert.AreEqual(false, p.CanReflectOnInstanceTypeProperty, "CanReflectOnInstanceTypeProperty should be false.");

                    bool shouldFail = true;
                    if ((kind & ResourcePropertyKind.Primitive) == ResourcePropertyKind.Primitive)
                    {
                        shouldFail = false;
                    }

                    if (shouldFail)
                    {
                        ExceptionUtils.ThrowsException <InvalidOperationException>(
                            () => p.MimeType = "plain/text",
                            string.Format("Setting MimeType on non-primitive property should fail. Kind: '{0}', Type: '{1}: {2}'", kind, type.ResourceTypeKind, type.InstanceType));
                    }
                    else
                    {
                        p.MimeType = "plain/text";
                    }
                }
            });
        }
コード例 #6
0
        public void ServiceActionConstructorTests()
        {
            ResourceProperty id           = new ResourceProperty("ID", ResourcePropertyKind.Primitive | ResourcePropertyKind.Key, ResourceType.GetPrimitiveResourceType(typeof(int)));
            ResourceType     customerType = new ResourceType(typeof(object), ResourceTypeKind.EntityType, null, "Foo", "Customer", false);

            customerType.AddProperty(id);
            customerType.SetReadOnly();
            ResourceSet  customerSet = new ResourceSet("Customers", customerType);
            ResourceType orderType   = new ResourceType(typeof(object), ResourceTypeKind.EntityType, null, "Foo", "Order", false);

            orderType.AddProperty(id);
            orderType.SetReadOnly();

            ResourceType complexType           = new ResourceType(typeof(object), ResourceTypeKind.ComplexType, null, "foo", "Address", false);
            ResourceType collectionOfPrimitive = ResourceType.GetCollectionResourceType(ResourceType.GetPrimitiveResourceType(typeof(int)));
            ResourceType collectionOfComplex   = ResourceType.GetCollectionResourceType(complexType);
            ResourceType collectionOfCustomer  = ResourceType.GetEntityCollectionResourceType(customerType);
            ResourceType collectionOfOrder     = ResourceType.GetEntityCollectionResourceType(orderType);

            var types = ResourceTypeUtils.GetPrimitiveResourceTypes().Concat(new ResourceType[] { null, customerType, orderType, complexType, collectionOfPrimitive, collectionOfComplex, collectionOfCustomer, collectionOfOrder });
            var resultSetsOrPathExpressions = new object[] { null, customerSet, new ResourceSetPathExpression("p1/Foo") };
            var parameters = types.Except(new[] { ResourceType.GetPrimitiveResourceType(typeof(System.IO.Stream)) }).Select(t => t != null ? new ServiceActionParameter[] { new ServiceActionParameter("p1", t) } : new ServiceActionParameter[0]);

            parameters = parameters.Concat(types.Except(new[] { ResourceType.GetPrimitiveResourceType(typeof(System.IO.Stream)), null }).ToList().Combinations(2).Select(tt => new ServiceActionParameter[] { new ServiceActionParameter("p1", tt[0]), new ServiceActionParameter("p2", tt[1]) }));
            var operationParameterBindings = new OperationParameterBindingKind[] { OperationParameterBindingKind.Never, OperationParameterBindingKind.Sometimes, OperationParameterBindingKind.Always };

            AstoriaTestNS.TestUtil.RunCombinations(
                types, resultSetsOrPathExpressions, parameters, operationParameterBindings,
                (returnType, resultSetOrPathExpression, paramList, operationParameterBindingKind) =>
            {
                ServiceAction action    = null;
                ResourceSet resourceSet = resultSetOrPathExpression as ResourceSet;
                ResourceSetPathExpression pathExpression = resultSetOrPathExpression as ResourceSetPathExpression;
                bool bindable = (operationParameterBindingKind == OperationParameterBindingKind.Always || operationParameterBindingKind == OperationParameterBindingKind.Sometimes);
                Exception e   = null;

                try
                {
                    if (pathExpression != null)
                    {
                        bindable = true;
                        action   = new ServiceAction("foo", returnType, OperationParameterBindingKind.Sometimes, paramList, pathExpression);
                    }
                    else
                    {
                        action = new ServiceAction("foo", returnType, resourceSet, operationParameterBindingKind, paramList);
                    }
                }
                catch (Exception ex)
                {
                    e = ex;
                }
                if (resourceSet != null && operationParameterBindingKind != OperationParameterBindingKind.Never)
                {
                    ExceptionUtils.IsExpectedException <ArgumentException>(e, "When 'returnType' is an entity type or an entity collection type, 'resultSetPathExpression' and 'resultSet' cannot be both null and the resource type of the result set must be assignable from 'returnType'.");
                }
                else if (returnType != null && (returnType.ResourceTypeKind == ResourceTypeKind.EntityType || returnType.ResourceTypeKind == ResourceTypeKind.EntityCollection) &&
                         (resourceSet == null && pathExpression == null ||
                          resourceSet != null && returnType.ResourceTypeKind == ResourceTypeKind.EntityType && resourceSet.ResourceType != returnType ||
                          resourceSet != null && returnType.ResourceTypeKind == ResourceTypeKind.EntityCollection && resourceSet.ResourceType != ((EntityCollectionResourceType)returnType).ItemType))
                {
                    ExceptionUtils.IsExpectedException <ArgumentException>(e, "When 'returnType' is an entity type or an entity collection type, 'resultSetPathExpression' and 'resultSet' cannot be both null and the resource type of the result set must be assignable from 'returnType'.");
                }
                else if ((returnType == null || returnType.ResourceTypeKind != ResourceTypeKind.EntityCollection && returnType.ResourceTypeKind != ResourceTypeKind.EntityType) && resourceSet != null)
                {
                    ExceptionUtils.IsExpectedException <ArgumentException>(e, "'resultSet' must be null when 'returnType' is null, not an entity type or not an entity collection type.");
                }
                else if ((returnType == null || returnType.ResourceTypeKind != ResourceTypeKind.EntityCollection && returnType.ResourceTypeKind != ResourceTypeKind.EntityType) && pathExpression != null)
                {
                    ExceptionUtils.IsExpectedException <ArgumentException>(e, "'resultSetPathExpression' must be null when 'returnType' is null, not an entity type or not an entity collection type.");
                }
                else if (returnType == ResourceType.GetPrimitiveResourceType(typeof(System.IO.Stream)))
                {
                    ExceptionUtils.IsExpectedException <ArgumentException>(e, "The resource type 'Edm.Stream' is not a type that can be returned by a function or action. A function or action can only return values of an entity type, an entity collection type, a complex type, a collection type or any primitive type, other than the stream type.\r\nParameter name: returnType");
                }
                else if (paramList.Length > 0 && paramList.Skip(bindable ? 1 : 0).Any(p => p.ParameterType.ResourceTypeKind == ResourceTypeKind.EntityType || p.ParameterType.ResourceTypeKind == ResourceTypeKind.EntityCollection))
                {
                    var param             = paramList.Skip(bindable ? 1 : 0).First(p => p.ParameterType.ResourceTypeKind == ResourceTypeKind.EntityType || p.ParameterType.ResourceTypeKind == ResourceTypeKind.EntityCollection);
                    var parameterTypeKind = param.ParameterType.ResourceTypeKind;
                    ExceptionUtils.IsExpectedException <ArgumentException>(e, string.Format("The '{0}' parameter is of resource type kind '{1}' and it is not the binding parameter. Parameter of type kind '{1}' is only supported for the binding parameter.", param.Name, parameterTypeKind));
                }
                else if (pathExpression != null && !bindable)
                {
                    ExceptionUtils.IsExpectedException <ArgumentException>(e, "The binding parameter type must be an entity type or an entity collection type when 'resultSetPathExpression' is not null.");
                }
                else if (bindable && paramList.Length == 0)
                {
                    ExceptionUtils.IsExpectedException <ArgumentException>(e, "Bindable actions or functions must have at least one parameter, where the first parameter is the binding parameter.\r\nParameter name: operationParameterBindingKind");
                }
                else if (pathExpression != null && bindable && paramList.First().ParameterType.ResourceTypeKind != ResourceTypeKind.EntityType && paramList.First().ParameterType.ResourceTypeKind != ResourceTypeKind.EntityCollection)
                {
                    ExceptionUtils.IsExpectedException <ArgumentException>(e, "The binding parameter type must be an entity type or an entity collection type when 'resultSetPathExpression' is not null.");
                }
                else if (paramList.Length > 0 && bindable && paramList[0].ParameterType.ResourceTypeKind != ResourceTypeKind.EntityType && paramList[0].ParameterType.ResourceTypeKind != ResourceTypeKind.EntityCollection)
                {
                    ExceptionUtils.IsExpectedException <ArgumentException>(e, "An action's binding parameter must be of type Entity or EntityCollection.\r\nParameter name: parameters");
                }
                else
                {
                    Assert.IsNull(e, "Received exception but expected none. Exception message: {0}", e == null ? string.Empty : e.Message);
                    Assert.IsNotNull(action, "Action should be constructed.");

                    Assert.AreEqual("foo", action.Name, "unexpected name");
                    Assert.AreEqual(returnType, action.ReturnType, "unexpected return type");
                    Assert.AreEqual(resourceSet, action.ResourceSet, "unexpected result set");
                    Assert.AreEqual(pathExpression, action.ResultSetPathExpression, "unexpected path expression");
                    Assert.IsTrue(!bindable || action.BindingParameter == action.Parameters.First(), "unexpected binding parameter");
                    Assert.IsTrue(action.Method == "POST", "HttpMethod must be POST for ServiceActions.");
                    Assert.IsTrue(action.ResourceSet == null || action.ResultSetPathExpression == null, "'resultSet' and 'resultSetPathExpression' cannot be both set by the constructor.");
                }
            });
        }
コード例 #7
0
        public void ReadOnlyValidationTest()
        {
            Type[] types = new Type[] {
                typeof(ResourceSet),
                typeof(ResourceType),
                typeof(ResourceProperty),
                typeof(ServiceOperation),
                typeof(ServiceOperationParameter)
            };

            Dictionary <Type, object> values = new Dictionary <Type, object>();

            values.Add(typeof(string), "foo");
            values.Add(typeof(bool), true);

            AstoriaTestNS.TestUtil.RunCombinations(
                types,
                (t) =>
            {
                object instance = ResourceTypeUtils.GetTestInstance(t);

                bool isReadOnly = (bool)ReflectionUtils.GetProperty(instance, "IsReadOnly");
                Assert.IsTrue(!isReadOnly, "If the type is not set to readonly yet, the IsReadOnly property should return false");

                // Make it readonly
                ReflectionUtils.InvokeMethod(instance, "SetReadOnly");

                isReadOnly = (bool)ReflectionUtils.GetProperty(instance, "IsReadOnly");
                Assert.IsTrue(isReadOnly, "Once the type is set to readonly, the readonly method should return true");

                // Make sure exception is throw if any of the public setters are called
                AstoriaTestNS.TestUtil.RunCombinations(
                    t.GetProperties(BindingFlags.Instance | BindingFlags.Public),
                    (propertyInfo) =>
                {
                    //Ignoring custom state, since its not governed by part of readonly
                    if (propertyInfo.Name == "CustomState")
                    {
                        return;
                    }

                    MethodInfo setter = propertyInfo.GetSetMethod();
                    if (setter != null)
                    {
                        try
                        {
                            setter.Invoke(instance, new object[] { values[propertyInfo.PropertyType] });
                            Assert.Fail(String.Format("Public properties cannot be modified after the type is set to ReadOnly. Property '{0}' did not throw", propertyInfo.Name));
                        }
                        catch (TargetInvocationException e)
                        {
                            Assert.IsTrue(e.InnerException is InvalidOperationException, "Expecting invalid operation exception");
                            Assert.IsTrue(e.InnerException.Message.Contains("cannot be modified since it is already set to read-only."), String.Format("Expecting a message with read-only in it. Actual: '{0}'", e.InnerException.Message));
                        }
                    }

                    // call the getter twice and make sure they return the same instance for reference types i.e. caching takes place.
                    MethodInfo getter = propertyInfo.GetGetMethod();
                    if (getter != null && !propertyInfo.PropertyType.IsValueType)
                    {
                        object value1 = getter.Invoke(instance, null);
                        object value2 = getter.Invoke(instance, null);
                        Assert.IsTrue(Object.ReferenceEquals(value1, value2), String.Format("For Readonly types, the reference type properties should return the same instance. Property: {0}", propertyInfo.Name));
                    }
                });

                // make sure the custom state property can be set even after the type is set to readonly
                string state = "foo";
                ReflectionUtils.SetProperty(instance, "CustomState", state);
                Assert.AreSame(state, ReflectionUtils.GetProperty(instance, "CustomState"), "custom state should be modified");

                // make sure calling SetReadOnly again is a no-op
                t.GetMethod("SetReadOnly", BindingFlags.Public | BindingFlags.Instance).Invoke(instance, null);
            });
        }