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); } }
// 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)); }