void AssertInstanceMethodErrors(MethodClosureExtensionsFixture fixture, object instance, MethodInfo method,
                                        Type validDelegateType, Type invalidDelegateType,
                                        object[] validSampleArgs, object[] invalidSampleArgs,
                                        bool nullTargetDelegateIsException)
        {
            fixture.WithDebugOnlyFilter(() =>
            {
                // Verify that validDelegateType and validSampleArgs are, well, valid.
                method.Invoke(instance, validSampleArgs);
                method.CreateDelegate(validDelegateType, instance).DynamicInvoke(validSampleArgs);
            });
            // Instance method cannot be invoked without a target.
            Assert.Throws(typeof(TargetException), () => method.Invoke(null, validSampleArgs));
            // Instance method cannot be invoked with an invalid target.
            Assert.Throws(typeof(TargetException), () => method.Invoke(this, validSampleArgs));
            if (validSampleArgs.Length > 0)
            {
                // Method cannot be invoked with too few parameters.
                Assert.Throws(typeof(TargetParameterCountException), () => method.Invoke(instance, new object[0]));
            }
            // Method cannot be invoked with too many parameters.
            Assert.Throws(typeof(TargetParameterCountException), () => method.Invoke(instance, validSampleArgs.Append("extra")));
            if (!(invalidSampleArgs is null))
            {
                // Method cannot be invoked with invalid parameter type.
                Assert.Throws(typeof(ArgumentException), () => method.Invoke(instance, invalidSampleArgs));
            }
            // Instance method CreateDelegate cannot be invoked without a target.
            Assert.Throws(typeof(ArgumentException), () => method.CreateDelegate(validDelegateType));
            // Instance method CreateDelegate cannot be invoked with an invalid target.
            Assert.Throws(typeof(ArgumentException), () => method.CreateDelegate(validDelegateType, this));
            // CreateDelegate cannot be invoked with an invalid delegate type.
            // Note: On Mono runtime, if Action/Func and has wrong # of type parameters, TargetParameterCountException is thrown instead
            // so just ensure either the Action is passed for Func (or vice versa), or the same # of type parameters are passed,
            // just with wrong type parameter(s).
            Assert.Throws(typeof(ArgumentException), () => method.CreateDelegate(invalidDelegateType, instance));
            // CreateDelegate cannot be invoked with null delegate type.
            Assert.Throws(typeof(ArgumentNullException), () => method.CreateDelegate(null));
            Assert.Throws(typeof(ArgumentNullException), () => method.CreateDelegate(null, null));
            Assert.Throws(typeof(ArgumentNullException), () => method.CreateDelegate(null, instance));
            // CreateDelegate(delegateType, null) doesn't throw error, but invocation of the resulting delegate results is invalid.
            // Note: On Mono runtime, TargetException will be thrown by MethodInfo.CreateDelegate(validDelegateType, null).DynamicInvoke(...).
            // In all other cases (MethodInfo.CreateDelegate on MS .NET runtime, ClosureMethod.CreateDelegate)(validDelegateType, null).DynamicInvoke(...),
            // TargetInvocationException will be thrown.
            var nullBoundDelegate = method.CreateDelegate(validDelegateType, null);

            if (nullTargetDelegateIsException)
            {
                AssertThrowsOneOfTwoExceptions <TargetInvocationException, TargetException>(() => nullBoundDelegate.DynamicInvoke(validSampleArgs));
            }
            else
            {
                nullBoundDelegate.DynamicInvoke(validSampleArgs);
            }
        }
Ejemplo n.º 2
0
            // This is the entry point for using this fixture, and a workaround for the issue where local variables in a method
            // are not finalizable after last usage in DEBUG builds (ostensibly so that the debugger still has access to them).
            // The workaround is to ensure the main body of the test method is in its own "local functions" via lambda.
            public static void Do(Action <MethodClosureExtensionsFixture> action)
            {
                var testName      = TestContext.CurrentContext.Test.FullName;
                var fixture       = new MethodClosureExtensionsFixture();
                var alreadyFailed = false;

                try
                {
                    var loggingWithDisposable = Logging.With(log =>
                    {
                        // Synchronization is necessary since there are multithreaded tests,
                        // and finalizers can log and run in a different thread.
                        lock (fixture.ActualLogs)
                            fixture.ActualLogs.Add(log);
                    });
                    using (loggingWithDisposable)
                    {
                        try
                        {
                            action(fixture);
                        }
                        catch (Exception)
                        {
                            alreadyFailed = true;
                            throw;
                        }
                        try
                        {
                            // As this is called at the end of the test method, any variable (in)directly holding a finalizable object (such as closure owners)
                            // should now have those objects be detected as finalizable. Thus, assert an empty closure registry.
                            if (!alreadyFailed)
                            {
                                fixture.AssertClosureRegistryCountAfterFullGCFinalization(0, "after test & final GC: " + testName);
                            }
                        }
                        catch (Exception)
                        {
                            alreadyFailed = true;
                            throw;
                        }
                    }
                }
                finally
                {
                    lock (fixture.ActualLogs)
                    {
                        Logging.Log(fixture.ActualLogs.Join("\n\t"), "ActualLogs");
                        if (!alreadyFailed && !(fixture.ExpectedLogs is null))
                        {
                            CollectionAssert.AreEqual(fixture.ExpectedLogs, fixture.ActualLogs.Where(log => !log.StartsWith("DEBUG")));
                        }
                    }
                    //Logging.Log("Finished MethodClosureExtensionsFixture.Do for test: " + testName);
                }
            }
 public void Control_StaticMethod_Error()
 {
     MethodClosureExtensionsFixture.Do(fixture =>
     {
         var method = typeof(MethodClosureExtensionsTestsSimple).GetMethod(nameof(MethodClosureExtensionsTestsSimple.SimpleStaticNonVoidMethod));
         AssertStaticMethodErrors(fixture, method,
                                  validDelegateType: typeof(Func <string, int, long, int, string>),
                                  invalidDelegateType: typeof(Func <string, int, string, int, string>),
                                  validSampleArgs: new object[] { "hello world", 1, 2L, 3 },
                                  invalidSampleArgs: new object[] { "hello world", 1, 2L, "string" },
                                  invokeIsAlwaysTargetException: false,
                                  delegateDynamicInvokeIsAlwaysException: false);
     });
 }
 public void PartialApply_StructInstanceMethod_Error(string methodName,
                                                     Type validDelegateType, Type invalidDelegateType, bool nullTargetDelegateIsException)
 {
     MethodClosureExtensionsFixture.Do(fixture =>
     {
         var v      = new TestStruct(15);
         var method = typeof(TestStruct).GetMethod(methodName);
         var partialAppliedMethod = method.PartialApply(5);
         AssertInstanceMethodErrors(fixture, v, partialAppliedMethod,
                                    validDelegateType,
                                    invalidDelegateType,
                                    validSampleArgs: new object[] { new[] { "hello", "world" } },
                                    invalidSampleArgs: new object[] { "string" },
                                    nullTargetDelegateIsException);
     });
 }
        public void Control_SimpleStaticNonVoidMethod([Values] bool emptyPartialApply)
        {
            MethodClosureExtensionsFixture.Do(fixture =>
            {
                var returnValue = SimpleStaticNonVoidMethod("mystring", 2, 4L, 100);
                Assert.AreEqual("asdf", returnValue);

                var method = typeof(MethodClosureExtensionsTestsSimple).GetMethod(nameof(SimpleStaticNonVoidMethod));
                if (emptyPartialApply)
                {
                    var partialAppliedMethod = method.PartialApply();
                    Assert.IsNull(partialAppliedMethod.FixedThisArgument);
                    CollectionAssert.AreEqual(new object[0], partialAppliedMethod.FixedArguments);
                    method = partialAppliedMethod;
                }

                returnValue = method.Invoke(null, new object[] { "mystring", 2, 4L, 100 }) as string;
                Assert.AreEqual("asdf", returnValue);

                // Static method can be invoked with a non-null target - target is just ignored in this case.
                returnValue = method.Invoke(this, new object[] { "mystring", 2, 4L, 100 }) as string;
                Assert.AreEqual("asdf", returnValue);

                returnValue = method.CreateDelegate <Func <string, int, long, int, string> >()("mystring", 2, 4L, 100);
                Assert.AreEqual("asdf", returnValue);

                fixture.ExpectedLogs = new[]
                {
                    "s: mystring",
                    "y: 2",
                    "l: 4",
                    "x: 100",
                    "s: mystring",
                    "y: 2",
                    "l: 4",
                    "x: 100",
                    "s: mystring",
                    "y: 2",
                    "l: 4",
                    "x: 100",
                    "s: mystring",
                    "y: 2",
                    "l: 4",
                    "x: 100",
                };
            });
        }
 public void Multiple_PartialApply_ClassInstanceMethod_Error(string methodName,
                                                             Type validDelegateType, Type invalidDelegateType, bool nullTargetDelegateIsException)
 {
     MethodClosureExtensionsFixture.Do(fixture =>
     {
         var c      = new TestClassSimple(15);
         var method = typeof(TestClassSimple).GetMethod(methodName);
         var partialAppliedMethod = method.PartialApply(5);
         partialAppliedMethod     = partialAppliedMethod.PartialApply(new object[] { new[] { "hello", "world" } });
         AssertInstanceMethodErrors(fixture, c, partialAppliedMethod,
                                    validDelegateType,
                                    invalidDelegateType,
                                    validSampleArgs: new object[0],
                                    invalidSampleArgs: null,
                                    nullTargetDelegateIsException);
     });
 }
 public void Multiple_PartialApply_StaticMethod_Error()
 {
     MethodClosureExtensionsFixture.Do(fixture =>
     {
         var method = typeof(MethodClosureExtensionsTestsSimple).GetMethod(nameof(MethodClosureExtensionsTestsSimple.SimpleStaticNonVoidMethod));
         var partialAppliedMethod = method.PartialApply("hello world");
         partialAppliedMethod     = partialAppliedMethod.PartialApply(1);
         partialAppliedMethod     = partialAppliedMethod.PartialApply(2L);
         partialAppliedMethod     = partialAppliedMethod.PartialApply(3);
         AssertStaticMethodErrors(fixture, partialAppliedMethod,
                                  validDelegateType: typeof(Func <string>),
                                  invalidDelegateType: typeof(Action),
                                  validSampleArgs: new object[0],
                                  invalidSampleArgs: null,
                                  invokeIsAlwaysTargetException: false,
                                  delegateDynamicInvokeIsAlwaysException: false);
     });
 }
 public void Bind_ClassInstanceMethod_Error(string methodName, bool bindNull,
                                            Type validDelegateType, Type invalidDelegateType, bool invokeIsAlwaysTargetException, bool delegateDynamicInvokeIsAlwaysException)
 {
     MethodClosureExtensionsFixture.Do(fixture =>
     {
         var c      = new TestClassSimple(15);
         var method = typeof(TestClassSimple).GetMethod(methodName);
         // Instance method cannot be bound to invalid target.
         Assert.Throws(typeof(ArgumentException), () => method.Bind(this));
         var boundMethod = bindNull ? method.Bind(null) : method.Bind(c);
         AssertStaticMethodErrors(fixture, boundMethod,
                                  validDelegateType,
                                  invalidDelegateType,
                                  validSampleArgs: new object[] { 5, new[] { "hello", "world" } },
                                  invalidSampleArgs: new object[] { 5, "string" },
                                  invokeIsAlwaysTargetException,
                                  delegateDynamicInvokeIsAlwaysException);
     });
 }
        public void ClosureMethod_Registry_GC([Values] bool gcAtIntervals)
        {
            int delegateCount = 20;
            int gcInterval    = 5;

            MethodClosureExtensionsFixture.Do(fixture =>
            {
                fixture.AssertClosureRegistryCountAfterFullGCFinalization(0, "after initial GC");
                var method = typeof(MethodClosureExtensionsTestsFancy).GetMethod(nameof(MethodClosureExtensionsTestsFancy.FancyStaticNonVoidMethod));
                MethodClosureExtensionsTestsFancy.FancyStaticNonVoidMethod_PartialApply_Delegate partialAppliedDelegate1 = null, partialAppliedDelegate2 = null;
                for (var i = 1; i <= delegateCount; i++)
                {
                    var partialAppliedMethod = method.PartialApply("hello", "world", new TestStruct(10), new TestStruct(15), 20, 25, new TestClass(30), new TestClass(35));
                    partialAppliedDelegate1  = partialAppliedMethod.CreateDelegate <MethodClosureExtensionsTestsFancy.FancyStaticNonVoidMethod_PartialApply_Delegate>();
                    partialAppliedDelegate2  = partialAppliedMethod.CreateDelegate <MethodClosureExtensionsTestsFancy.FancyStaticNonVoidMethod_PartialApply_Delegate>();
                    if (i % 5 == 0 && gcAtIntervals)
                    {
                        //Logging.Log($"DEBUG before GC {i / gcInterval}:\n{ClosureMethod.Registry}");
                        // Note: Since GCs can be triggered at any moment before this point, we can't deterministically determine # active closures are in the registry.
                        // We can only deterministically determine the # active closures after a "full" GC and before any further ClosureMethod.CreateDelegate calls.
                        fixture.AssertClosureRegistryCountAfterFullGCFinalization(2, $"after GC {i / gcInterval}");
                    }
                }
                if (!gcAtIntervals)
                {
                    fixture.AssertClosureRegistryCountAfterFullGCFinalization(2, $"after GC {delegateCount / gcInterval}");
                }
                // Test that the latest partially applied method delegate still works.
                var x = 20;
                partialAppliedDelegate1(null, new List <string>()
                {
                    "qwerty"
                }, 40L, ref x);
                Assert.AreEqual(20 * 20, x);
                partialAppliedDelegate2(null, new List <string>()
                {
                    "qwerty"
                }, 40L, ref x);
                Assert.AreEqual(20 * 20 * 20 * 20, x);
                //Logging.Log($"DEBUG before final GC:\n{ClosureMethod.Registry}");
                // MethodClosureExtensionsFixture will call AssertEmptyClosureRegistryAfterTryFullGCFinalization a final time, so don't need to call it here.
            });
        }
 public void PartialApply_Bind_StructInstanceMethod_Error(string methodName, bool bindNull,
                                                          Type validDelegateType, Type invalidDelegateType, bool invokeIsAlwaysTargetException, bool delegateDynamicInvokeIsAlwaysException)
 {
     MethodClosureExtensionsFixture.Do(fixture =>
     {
         var v                    = new TestStruct(15);
         var method               = typeof(TestStruct).GetMethod(methodName);
         var boundMethod          = bindNull ? method.Bind(null) : method.Bind(v);
         var partialAppliedMethod = boundMethod.PartialApply(5);
         AssertStaticMethodErrors(fixture, partialAppliedMethod,
                                  validDelegateType,
                                  invalidDelegateType,
                                  validSampleArgs: new object[] { new[] { "hello", "world" } },
                                  invalidSampleArgs: new object[] { "string" },
                                  invokeIsAlwaysTargetException,
                                  delegateDynamicInvokeIsAlwaysException);
         // Partially applied bound method cannot be rebound.
         Assert.Throws(typeof(ArgumentException), () => partialAppliedMethod.Bind(v));
     });
 }
 public void Control_StructInstanceMethod_Error(string methodName,
                                                Type validDelegateType, Type invalidDelegateType, bool nullTargetDelegateIsException)
 {
     // On Mono runtime, MethodInfo.CreateDelegate(validDelegateType, null).DynamicInvoke(...) always throws TargetException,
     // regardless of whether the this instance is never used.
     if (DebugExtensions.IsRunningOnMono)
     {
         nullTargetDelegateIsException = true;
     }
     MethodClosureExtensionsFixture.Do(fixture =>
     {
         var v      = new TestStruct(15);
         var method = typeof(TestStruct).GetMethod(methodName);
         AssertInstanceMethodErrors(fixture, v, method,
                                    validDelegateType,
                                    invalidDelegateType,
                                    validSampleArgs: new object[] { 5, new[] { "hello", "world" } },
                                    invalidSampleArgs: new object[] { 5, "string" },
                                    nullTargetDelegateIsException);
     });
 }
        public void Control_SimpleStaticVoidMethod([Values] bool emptyPartialApply)
        {
            MethodClosureExtensionsFixture.Do(fixture =>
            {
                SimpleStaticVoidMethod("mystring", 2, 4L, 100);

                var method = typeof(MethodClosureExtensionsTestsSimple).GetMethod(nameof(SimpleStaticVoidMethod));
                if (emptyPartialApply)
                {
                    var partialAppliedMethod = method.PartialApply();
                    Assert.IsNull(partialAppliedMethod.FixedThisArgument);
                    CollectionAssert.AreEqual(new object[0], partialAppliedMethod.FixedArguments);
                    method = partialAppliedMethod;
                }

                var returnValue = method.Invoke(null, new object[] { "mystring", 2, 4L, 100 });
                Assert.IsNull(returnValue);

                method.CreateDelegate <Action <string, int, long, int> >()("mystring", 2, 4L, 100);

                fixture.ExpectedLogs = new[]
                {
                    "s: mystring",
                    "y: 2",
                    "l: 4",
                    "x: 100",
                    "s: mystring",
                    "y: 2",
                    "l: 4",
                    "x: 100",
                    "s: mystring",
                    "y: 2",
                    "l: 4",
                    "x: 100",
                };
            });
        }
        public void Control_SimpleInstanceNonVoidMethod([Values] bool emptyPartialApply)
        {
            MethodClosureExtensionsFixture.Do(fixture =>
            {
                var c           = new TestClassSimple(-15);
                var returnValue = c.SimpleInstanceNonVoidMethod(-5, "home", "alone");
                Assert.AreEqual("ghkj", returnValue);

                var method = typeof(TestClassSimple).GetMethod(nameof(TestClassSimple.SimpleInstanceNonVoidMethod));
                if (emptyPartialApply)
                {
                    var partialAppliedMethod = method.PartialApply();
                    Assert.IsNull(partialAppliedMethod.FixedThisArgument);
                    CollectionAssert.AreEqual(new object[0], partialAppliedMethod.FixedArguments);
                    method = partialAppliedMethod;
                }

                returnValue = method.Invoke(c, new object[] { -5, new[] { "home", "alone" } }) as string;
                Assert.AreEqual("ghkj", returnValue);

                returnValue = method.CreateDelegate <Func <int, string[], string> >(c)(-5, new[] { "home", "alone" });
                Assert.AreEqual("ghkj", returnValue);

                fixture.ExpectedLogs = new[]
                {
                    "x: -15",
                    "y: -5",
                    "ss: string[] { home, alone }",
                    "x: -15",
                    "y: -5",
                    "ss: string[] { home, alone }",
                    "x: -15",
                    "y: -5",
                    "ss: string[] { home, alone }",
                };
            });
        }
        public void PartialApply_SimpleStaticNonVoidMethod()
        {
            MethodClosureExtensionsFixture.Do(fixture =>
            {
                var method               = typeof(MethodClosureExtensionsTestsSimple).GetMethod(nameof(SimpleStaticNonVoidMethod));
                var fixedArguments       = new object[] { "hello world", 1, 2L };
                var partialAppliedMethod = method.PartialApply(fixedArguments);
                Assert.IsNull(partialAppliedMethod.FixedThisArgument);
                CollectionAssert.AreEqual(fixedArguments, partialAppliedMethod.FixedArguments);
                var returnValue = partialAppliedMethod.Invoke(null, new object[] { 3 });
                Assert.AreEqual("asdf", returnValue);

                // Static method can be invoked with a non-null target - target is just ignored in this case.
                returnValue = partialAppliedMethod.Invoke(this, new object[] { 5 });
                Assert.AreEqual("asdf", returnValue);

                returnValue = partialAppliedMethod.CreateDelegate <Func <int, string> >()(7);
                Assert.AreEqual("asdf", returnValue);

                fixture.ExpectedLogs = new[]
                {
                    "s: hello world",
                    "y: 1",
                    "l: 2",
                    "x: 3",
                    "s: hello world",
                    "y: 1",
                    "l: 2",
                    "x: 5",
                    "s: hello world",
                    "y: 1",
                    "l: 2",
                    "x: 7",
                };
            });
        }
        // TODO: IsStatic tests.

        void AssertStaticMethodErrors(MethodClosureExtensionsFixture fixture, MethodInfo method,
                                      Type validDelegateType, Type invalidDelegateType,
                                      object[] validSampleArgs, object[] invalidSampleArgs,
                                      bool invokeIsAlwaysTargetException, bool delegateDynamicInvokeIsAlwaysException)
        {
            if (invokeIsAlwaysTargetException)
            {
                Assert.Throws(typeof(TargetException), () => method.Invoke(null, validSampleArgs));
                Assert.Throws(typeof(TargetException), () => method.Invoke(this, validSampleArgs));
            }
            else
            {
                fixture.WithDebugOnlyFilter(() =>
                {
                    // Verify that validSampleArgs is, well, valid.
                    method.Invoke(null, validSampleArgs);
                    // Invocation target is ignored for static methods.
                    method.Invoke(this, validSampleArgs);
                });
            }
            if (delegateDynamicInvokeIsAlwaysException)
            {
                var @delegate = method.CreateDelegate(validDelegateType);
                Assert.Throws(typeof(TargetInvocationException), () => @delegate.DynamicInvoke(validSampleArgs));
                @delegate = method.CreateDelegate(validDelegateType, null);
                Assert.Throws(typeof(TargetInvocationException), () => @delegate.DynamicInvoke(validSampleArgs));
            }
            else
            {
                fixture.WithDebugOnlyFilter(() =>
                {
                    // Verify that validDelegateType is indeed valid.
                    method.CreateDelegate(validDelegateType).DynamicInvoke(validSampleArgs);
                    // null is valid target for static method CreateDelegate.
                    method.CreateDelegate(validDelegateType, null).DynamicInvoke(validSampleArgs);
                });
            }
            if (validSampleArgs.Length > 0)
            {
                // Method cannot be invoked with too few parameters.
                Assert.Throws(invokeIsAlwaysTargetException ? typeof(TargetException) : typeof(TargetParameterCountException),
                              () => method.Invoke(null, new object[0]));
            }
            // Method cannot be invoked with too many parameters.
            Assert.Throws(invokeIsAlwaysTargetException ? typeof(TargetException) : typeof(TargetParameterCountException),
                          () => method.Invoke(null, validSampleArgs.Append("extra")));
            if (!(invalidSampleArgs is null))
            {
                // Method cannot be invoked with invalid parameter type.
                Assert.Throws(invokeIsAlwaysTargetException ? typeof(TargetException) : typeof(ArgumentException),
                              () => method.Invoke(null, invalidSampleArgs));
            }
            // Static method CreateDelegate cannot be invoked with a non-null target.
            // Note: On Mono runtime, TargetParameterCountException will be thrown by MethodInfo.CreateDelegate.
            // In all other cases (MethodInfo.CreateDelegate on MS .NET runtime, ClosureMethod.CreateDelegate), ArgumentException will be thrown.
            AssertThrowsOneOfTwoExceptions <ArgumentException, TargetParameterCountException>(() => method.CreateDelegate(validDelegateType, this));
            // CreateDelegate cannot be invoked with null delegate type.
            Assert.Throws(typeof(ArgumentNullException), () => method.CreateDelegate(null));
            // CreateDelegate cannot be invoked with an invalid delegate type.
            // Note: On Mono runtime, if Action/Func and has wrong # of type parameters, TargetParameterCountException is thrown instead
            // so just ensure either the Action is passed for Func (or vice versa), or the same # of type parameters are passed,
            // just with wrong type parameter(s).
            Assert.Throws(typeof(ArgumentException), () => method.CreateDelegate(invalidDelegateType));
        }