diff --git a/src/Agent/NewRelic/Agent/Core/DependencyInjection/AgentServices.cs b/src/Agent/NewRelic/Agent/Core/DependencyInjection/AgentServices.cs index 46d1b67d72..76b1130d09 100644 --- a/src/Agent/NewRelic/Agent/Core/DependencyInjection/AgentServices.cs +++ b/src/Agent/NewRelic/Agent/Core/DependencyInjection/AgentServices.cs @@ -100,6 +100,7 @@ public static void RegisterServices(IContainer container, bool serverlessModeEna } else { + container.Register(); container.Register(); container.Register(); } diff --git a/src/Agent/NewRelic/Agent/Core/Samplers/GCSamplerModern.cs b/src/Agent/NewRelic/Agent/Core/Samplers/GCSamplerModern.cs index 35957e15d4..3ee437d92e 100644 --- a/src/Agent/NewRelic/Agent/Core/Samplers/GCSamplerModern.cs +++ b/src/Agent/NewRelic/Agent/Core/Samplers/GCSamplerModern.cs @@ -2,12 +2,9 @@ // SPDX-License-Identifier: Apache-2.0 using System; -using System.Linq.Expressions; -using System.Reflection; using NewRelic.Agent.Core.Time; using NewRelic.Agent.Core.Transformers; using NewRelic.Agent.Extensions.Logging; -using NewRelic.Reflection; namespace NewRelic.Agent.Core.Samplers { @@ -17,56 +14,34 @@ public class GCSamplerModern : AbstractSampler private DateTime _lastSampleTime; private bool _hasGCOccurred; - private static Func _getGenerationInfo; - private static bool _reflectionFailed; - - private static Func GCGetMemoryInfo_Invoker; - private static Func GCGetTotalAllocatedBytes_Invoker; + private IGCSamplerModernReflectionHelper _gCSamplerModernReflectionHelper; private const int GCSamplerModernIntervalSeconds = 60; - static GCSamplerModern() - { - if (!VisibilityBypasser.Instance.TryGenerateOneParameterStaticMethodCaller("System.Runtime", "System.GC", "GetGCMemoryInfo", "System.GCKind", "System.GCMemoryInfo", out GCGetMemoryInfo_Invoker)) - { - _reflectionFailed = true; - } - - if (!_reflectionFailed) - { - if (!VisibilityBypasser.Instance.TryGenerateOneParameterStaticMethodCaller("System.Runtime", "System.GC", "GetTotalAllocatedBytes", "System.Boolean", "System.Int64", out GCGetTotalAllocatedBytes_Invoker)) - { - _reflectionFailed = true; - } - } - - if (!_reflectionFailed) - _getGenerationInfo = GCMemoryInfoHelper.GenerateGetMemoryInfoMethod(); - } - - public GCSamplerModern(IScheduler scheduler, IGCSampleTransformerModern transformer) + public GCSamplerModern(IScheduler scheduler, IGCSampleTransformerModern transformer, IGCSamplerModernReflectionHelper gCSamplerModernReflectionHelper) : base(scheduler, TimeSpan.FromSeconds(GCSamplerModernIntervalSeconds)) { _transformer = transformer; + _gCSamplerModernReflectionHelper = gCSamplerModernReflectionHelper; _lastSampleTime = DateTime.UtcNow; _hasGCOccurred = false; } public override void Sample() { - if (_reflectionFailed) + if (_gCSamplerModernReflectionHelper.ReflectionFailed) { Stop(); Log.Error($"Unable to get GC sample due to reflection error. No GC metrics will be reported."); } - _hasGCOccurred |= GC.CollectionCount(0) > 0; + _hasGCOccurred |= _gCSamplerModernReflectionHelper.HasGCOccurred; if (_hasGCOccurred) // don't do anything until at least one GC has completed { - dynamic gcMemoryInfo = GCGetMemoryInfo_Invoker(0); // GCKind.Any - dynamic generationInfo = _getGenerationInfo(gcMemoryInfo); + dynamic gcMemoryInfo = _gCSamplerModernReflectionHelper.GCGetMemoryInfo_Invoker(0); // GCKind.Any + dynamic generationInfo = _gCSamplerModernReflectionHelper.GetGenerationInfo(gcMemoryInfo); var genInfoLength = generationInfo.Length; var heapSizesBytes = new long[genInfoLength]; @@ -84,7 +59,7 @@ public override void Sample() } var totalMemoryBytes = GC.GetTotalMemory(false); - var totalAllocatedBytes = (long)GCGetTotalAllocatedBytes_Invoker(false); + var totalAllocatedBytes = (long)_gCSamplerModernReflectionHelper.GCGetTotalAllocatedBytes_Invoker(false); var totalCommittedBytes = gcMemoryInfo.TotalCommittedBytes; var currentSampleTime = DateTime.UtcNow; @@ -95,95 +70,4 @@ public override void Sample() } } } - - public class ImmutableGCSample - { - public readonly DateTime LastSampleTime; - public readonly DateTime CurrentSampleTime; - - public readonly long TotalMemoryBytes; // In-use memory on the GC heap as of current GC - public readonly long TotalAllocatedBytes; // total memory allocated on GC heap since process start - public readonly long TotalCommittedBytes;// committed virtual memory as of current GC - - public readonly long[] GCHeapSizesBytes; // heap sizes as of current GC - public readonly int[] GCCollectionCounts; // number of collections since last sample - public readonly long[] GCFragmentationSizesBytes; // heap fragmentation as of current GC - - public ImmutableGCSample() - { - LastSampleTime = CurrentSampleTime = DateTime.MinValue; - GCHeapSizesBytes = new long[5]; - GCCollectionCounts = new int[5]; - GCFragmentationSizesBytes = new long[5]; - } - - public ImmutableGCSample(DateTime lastSampleTime, DateTime currentSampleTime, long totalMemoryBytes, long totalAllocatedBytes, long totalCommittedBytes, long[] heapSizesBytes, int[] rawCollectionCounts, long[] fragmentationSizesBytes) - { - LastSampleTime = lastSampleTime; - CurrentSampleTime = currentSampleTime; - - TotalMemoryBytes = totalMemoryBytes; - - TotalAllocatedBytes = totalAllocatedBytes; - TotalCommittedBytes = totalCommittedBytes; - - GCHeapSizesBytes = heapSizesBytes; - GCFragmentationSizesBytes = fragmentationSizesBytes; - - // TODO: verify length is 5 as expected - GCCollectionCounts = new int[rawCollectionCounts.Length]; - // Gen 1 - GCCollectionCounts[0] = rawCollectionCounts[0] - rawCollectionCounts[1]; - // Gen 2 - GCCollectionCounts[1] = rawCollectionCounts[1] - rawCollectionCounts[2]; - // Gen 3 - GCCollectionCounts[2] = rawCollectionCounts[2]; - - // LOH - GCCollectionCounts[3] = rawCollectionCounts[3]; // or does this need to be [3] - [4]?? - // POH - GCCollectionCounts[4] = rawCollectionCounts[4]; //?? - } - } - - internal static class GCMemoryInfoHelper - { - /// - /// Generate a function that takes a GCMemoryInfo instance as an input parameter and - /// returns an array of GCGenerationInfo instances. - /// - public static Func GenerateGetMemoryInfoMethod() - { - var assembly = Assembly.Load("System.Runtime"); - var gcMemoryInfoType = assembly.GetType("System.GCMemoryInfo"); - - // Define a parameter expression for the input object - var inputParameter = Expression.Parameter(typeof(object), "input"); - - // Cast the input parameter to GCMemoryInfo - var gcMemoryInfoParameter = Expression.Convert(inputParameter, gcMemoryInfoType); - - // Get the GenerationInfo property - var generationInfoProperty = gcMemoryInfoType.GetProperty("GenerationInfo"); - - // Access the GenerationInfo property - var accessGenerationInfo = Expression.Property(gcMemoryInfoParameter, generationInfoProperty); - - // Get the ReadOnlySpan type using the full type name - var readOnlySpanType = assembly.GetType("System.ReadOnlySpan`1[[System.GCGenerationInfo, System.Private.CoreLib]]"); - - // Get the ToArray method of ReadOnlySpan - var toArrayMethod = readOnlySpanType.GetMethod("ToArray", BindingFlags.Public | BindingFlags.Instance); - - // Call ToArray() on GenerationInfo - var callToArray = Expression.Call(accessGenerationInfo, toArrayMethod); - - // Create a lambda expression - var lambda = Expression.Lambda>(Expression.Convert(callToArray, typeof(object)), inputParameter); - - // Compile the lambda expression into a delegate - return lambda.Compile(); - } - - } } diff --git a/src/Agent/NewRelic/Agent/Core/Samplers/GCSamplerModernReflectionHelper.cs b/src/Agent/NewRelic/Agent/Core/Samplers/GCSamplerModernReflectionHelper.cs new file mode 100644 index 0000000000..c995aaf32f --- /dev/null +++ b/src/Agent/NewRelic/Agent/Core/Samplers/GCSamplerModernReflectionHelper.cs @@ -0,0 +1,101 @@ +// Copyright 2020 New Relic, Inc. All rights reserved. +// SPDX-License-Identifier: Apache-2.0 + +using System; +using System.Linq.Expressions; +using System.Reflection; +using NewRelic.Reflection; + +namespace NewRelic.Agent.Core.Samplers +{ + // to allow for unit testing + public interface IGCSamplerModernReflectionHelper + { + Func GetGenerationInfo { get; } + bool ReflectionFailed { get; } + Func GCGetMemoryInfo_Invoker { get; } + Func GCGetTotalAllocatedBytes_Invoker { get; } + + bool HasGCOccurred {get;} + } + + public class GCSamplerModernReflectionHelper : IGCSamplerModernReflectionHelper + { + public Func GetGenerationInfo { get; private set; } + public bool ReflectionFailed { get; private set; } + public Func GCGetMemoryInfo_Invoker { get; private set; } + public Func GCGetTotalAllocatedBytes_Invoker { get; private set; } + + public GCSamplerModernReflectionHelper() + { + var assembly = Assembly.Load("System.Runtime"); + var gcType = assembly.GetType("System.GC"); + var paramType = assembly.GetType("System.GCKind"); + var returnType = assembly.GetType("System.GCMemoryInfo"); + + if (!VisibilityBypasser.Instance.TryGenerateOneParameterStaticMethodCaller(gcType, "GetGCMemoryInfo", paramType, returnType, out var accessor)) + { + ReflectionFailed = true; + } + else + GCGetMemoryInfo_Invoker = accessor; + + if (!ReflectionFailed) + { + paramType = assembly.GetType("System.Boolean"); + returnType = assembly.GetType("System.Int64"); + if (!VisibilityBypasser.Instance.TryGenerateOneParameterStaticMethodCaller(gcType, "GetTotalAllocatedBytes", paramType, returnType, out var accessor1)) + { + ReflectionFailed = true; + } + else + GCGetTotalAllocatedBytes_Invoker = accessor1; + } + + if (!ReflectionFailed) + GetGenerationInfo = GCMemoryInfoHelper.GenerateGetMemoryInfoMethod(); + } + + public bool HasGCOccurred => GC.CollectionCount(0) > 0; + } + + internal static class GCMemoryInfoHelper + { + /// + /// Generate a function that takes a GCMemoryInfo instance as an input parameter and + /// returns an array of GCGenerationInfo instances. + /// + public static Func GenerateGetMemoryInfoMethod() + { + var assembly = Assembly.Load("System.Runtime"); + var gcMemoryInfoType = assembly.GetType("System.GCMemoryInfo"); + + // Define a parameter expression for the input object + var inputParameter = Expression.Parameter(typeof(object), "input"); + + // Cast the input parameter to GCMemoryInfo + var gcMemoryInfoParameter = Expression.Convert(inputParameter, gcMemoryInfoType); + + // Get the GenerationInfo property + var generationInfoProperty = gcMemoryInfoType.GetProperty("GenerationInfo"); + + // Access the GenerationInfo property + var accessGenerationInfo = Expression.Property(gcMemoryInfoParameter, generationInfoProperty); + + // Get the ReadOnlySpan type using the full type name + var readOnlySpanType = assembly.GetType("System.ReadOnlySpan`1[[System.GCGenerationInfo, System.Private.CoreLib]]"); + + // Get the ToArray method of ReadOnlySpan + var toArrayMethod = readOnlySpanType.GetMethod("ToArray", BindingFlags.Public | BindingFlags.Instance); + + // Call ToArray() on GenerationInfo + var callToArray = Expression.Call(accessGenerationInfo, toArrayMethod); + + // Create a lambda expression + var lambda = Expression.Lambda>(Expression.Convert(callToArray, typeof(object)), inputParameter); + + // Compile the lambda expression into a delegate + return lambda.Compile(); + } + } +} diff --git a/src/Agent/NewRelic/Agent/Core/Samplers/ImmutableGCSample.cs b/src/Agent/NewRelic/Agent/Core/Samplers/ImmutableGCSample.cs new file mode 100644 index 0000000000..42cb55aadf --- /dev/null +++ b/src/Agent/NewRelic/Agent/Core/Samplers/ImmutableGCSample.cs @@ -0,0 +1,57 @@ +// Copyright 2020 New Relic, Inc. All rights reserved. +// SPDX-License-Identifier: Apache-2.0 + +using System; + +namespace NewRelic.Agent.Core.Samplers +{ + public class ImmutableGCSample + { + public readonly DateTime LastSampleTime; + public readonly DateTime CurrentSampleTime; + + public readonly long TotalMemoryBytes; // In-use memory on the GC heap as of current GC + public readonly long TotalAllocatedBytes; // total memory allocated on GC heap since process start + public readonly long TotalCommittedBytes;// committed virtual memory as of current GC + + public readonly long[] GCHeapSizesBytes; // heap sizes as of current GC + public readonly int[] GCCollectionCounts; // number of collections since last sample + public readonly long[] GCFragmentationSizesBytes; // heap fragmentation as of current GC + + public ImmutableGCSample() + { + LastSampleTime = CurrentSampleTime = DateTime.MinValue; + GCHeapSizesBytes = new long[5]; + GCCollectionCounts = new int[5]; + GCFragmentationSizesBytes = new long[5]; + } + + public ImmutableGCSample(DateTime lastSampleTime, DateTime currentSampleTime, long totalMemoryBytes, long totalAllocatedBytes, long totalCommittedBytes, long[] heapSizesBytes, int[] rawCollectionCounts, long[] fragmentationSizesBytes) + { + LastSampleTime = lastSampleTime; + CurrentSampleTime = currentSampleTime; + + TotalMemoryBytes = totalMemoryBytes; + + TotalAllocatedBytes = totalAllocatedBytes; + TotalCommittedBytes = totalCommittedBytes; + + GCHeapSizesBytes = heapSizesBytes; + GCFragmentationSizesBytes = fragmentationSizesBytes; + + // TODO: verify length is 5 as expected + GCCollectionCounts = new int[rawCollectionCounts.Length]; + // Gen 1 + GCCollectionCounts[0] = rawCollectionCounts[0] - rawCollectionCounts[1]; + // Gen 2 + GCCollectionCounts[1] = rawCollectionCounts[1] - rawCollectionCounts[2]; + // Gen 3 + GCCollectionCounts[2] = rawCollectionCounts[2]; + + // LOH + GCCollectionCounts[3] = rawCollectionCounts[3]; // or does this need to be [3] - [4]?? + // POH + GCCollectionCounts[4] = rawCollectionCounts[4]; //?? + } + } +} diff --git a/src/Agent/NewRelic/Agent/Extensions/NewRelic.Agent.Extensions/Reflection/VisibilityBypasser.cs b/src/Agent/NewRelic/Agent/Extensions/NewRelic.Agent.Extensions/Reflection/VisibilityBypasser.cs index 21d786ac7e..064c7b3fc5 100644 --- a/src/Agent/NewRelic/Agent/Extensions/NewRelic.Agent.Extensions/Reflection/VisibilityBypasser.cs +++ b/src/Agent/NewRelic/Agent/Extensions/NewRelic.Agent.Extensions/Reflection/VisibilityBypasser.cs @@ -297,14 +297,10 @@ private static Func GenerateMethodCallerInternal(Type ownerType, return GenerateMethodCallerInternal(resultType, methodInfo); } - public bool TryGenerateOneParameterStaticMethodCaller(string assemblyName, string typeName, string methodName, string parameterTypeName, string returnTypeName, out Func accessor) + public bool TryGenerateOneParameterStaticMethodCaller(Type ownerType, string methodName, Type paramType, Type returnType, out Func accessor) { try { - var ownerType = GetType(assemblyName, typeName); - var paramType = GetType(assemblyName, parameterTypeName); - var returnType = GetType(assemblyName, returnTypeName); - var methodInfo = ownerType.GetMethod(methodName, BindingFlags.Public | BindingFlags.NonPublic | BindingFlags.Instance | BindingFlags.Static, null, new Type[] { paramType }, null); if (methodInfo == null) { @@ -682,10 +678,8 @@ public Func GenerateParameterlessStaticMethodCaller(string ass return (Func)methodInfo.CreateDelegate(typeof(Func)); } - public bool TryGenerateParameterlessStaticMethodCaller(string assemblyName, string typeName, string methodName, out Func accessor) + public bool TryGenerateParameterlessStaticMethodCaller(Type ownerType, string methodName, out Func accessor) { - var ownerType = GetType(assemblyName, typeName); - var methodInfo = ownerType.GetMethod(methodName, BindingFlags.Public | BindingFlags.NonPublic | BindingFlags.Instance | BindingFlags.Static); if (methodInfo == null) { diff --git a/tests/Agent/UnitTests/Core.UnitTest/Samplers/GCSamplerModernTests.cs b/tests/Agent/UnitTests/Core.UnitTest/Samplers/GCSamplerModernTests.cs new file mode 100644 index 0000000000..c8e445cc56 --- /dev/null +++ b/tests/Agent/UnitTests/Core.UnitTest/Samplers/GCSamplerModernTests.cs @@ -0,0 +1,137 @@ +// Copyright 2020 New Relic, Inc. All rights reserved. +// SPDX-License-Identifier: Apache-2.0 + +using System; +using NewRelic.Agent.Core.Time; +using NewRelic.Agent.Core.Transformers; +using NUnit.Framework; +using Telerik.JustMock; + +namespace NewRelic.Agent.Core.Samplers +{ + [TestFixture] + public class GCSamplerModernTests + { + private IScheduler _scheduler; + private IGCSampleTransformerModern _transformer; + private IGCSamplerModernReflectionHelper _reflectionHelper; + private GCSamplerModern _gcSamplerModern; + + [SetUp] + public void SetUp() + { + _scheduler = Mock.Create(); + _transformer = Mock.Create(); + _reflectionHelper = Mock.Create(); + + _gcSamplerModern = new GCSamplerModern(_scheduler, _transformer, _reflectionHelper); + } + + [TearDown] + public void TearDown() + { + _gcSamplerModern.Dispose(); + } + + [Test] + public void Sample_ShouldStop_WhenReflectionFails() + { + // Arrange + Mock.Arrange(() => _reflectionHelper.ReflectionFailed).Returns(true); + + // Act + _gcSamplerModern.Sample(); + + // Assert + Mock.Assert(() => _scheduler.StopExecuting(Arg.IsAny(), Arg.IsAny()), Occurs.Once()); + } + + [Test] + public void Sample_ShouldNotTransform_WhenNoGCOccurred() + { + // Arrange + Mock.Arrange(() => _reflectionHelper.ReflectionFailed).Returns(false); + Mock.Arrange(() => _reflectionHelper.HasGCOccurred).Returns(false); + + // Act + _gcSamplerModern.Sample(); + + // Assert + Mock.Assert(() => _transformer.Transform(Arg.IsAny()), Occurs.Never()); + } + + [Test] + public void Sample_ShouldTransform_WhenGCOccurred() + { + // Arrange + Mock.Arrange(() => _reflectionHelper.ReflectionFailed).Returns(false); + Mock.Arrange(() => _reflectionHelper.HasGCOccurred).Returns(true); + + var gcMemoryInfo = new GCMemoryInfo { TotalCommittedBytes = 4096L }; + var generationInfo = new[] + { + new GenerationInfo { SizeAfterBytes = 100L, FragmentationAfterBytes = 10L }, + new GenerationInfo { SizeAfterBytes = 200L, FragmentationAfterBytes = 20L }, + new GenerationInfo { SizeAfterBytes = 300L, FragmentationAfterBytes = 30L }, + new GenerationInfo { SizeAfterBytes = 400L, FragmentationAfterBytes = 40L }, + new GenerationInfo { SizeAfterBytes = 500L, FragmentationAfterBytes = 50L } + }; + + Mock.Arrange(() => _reflectionHelper.GCGetMemoryInfo_Invoker(Arg.IsAny())).Returns(gcMemoryInfo); + Mock.Arrange(() => _reflectionHelper.GetGenerationInfo(Arg.IsAny())).Returns(generationInfo); + Mock.Arrange(() => _reflectionHelper.GCGetTotalAllocatedBytes_Invoker(Arg.IsAny())).Returns(2048L); + + // Act + _gcSamplerModern.Sample(); + + // Assert + Mock.Assert(() => _transformer.Transform(Arg.IsAny()), Occurs.Once()); + } + + [Test] + public void Sample_ShouldUpdateLastSampleTime_WhenGCOccurred() + { + // Arrange + Mock.Arrange(() => _reflectionHelper.ReflectionFailed).Returns(false); + Mock.Arrange(() => _reflectionHelper.HasGCOccurred).Returns(true); + var gcMemoryInfo = new GCMemoryInfo { TotalCommittedBytes = 4096L }; + var generationInfo = new[] + { + new GenerationInfo { SizeAfterBytes = 100L, FragmentationAfterBytes = 10L }, + new GenerationInfo { SizeAfterBytes = 200L, FragmentationAfterBytes = 20L }, + new GenerationInfo { SizeAfterBytes = 300L, FragmentationAfterBytes = 30L }, + new GenerationInfo { SizeAfterBytes = 400L, FragmentationAfterBytes = 40L }, + new GenerationInfo { SizeAfterBytes = 500L, FragmentationAfterBytes = 50L } + }; + + Mock.Arrange(() => _reflectionHelper.GCGetMemoryInfo_Invoker(Arg.IsAny())).Returns(gcMemoryInfo); + Mock.Arrange(() => _reflectionHelper.GetGenerationInfo(Arg.IsAny())).Returns(generationInfo); + Mock.Arrange(() => _reflectionHelper.GCGetTotalAllocatedBytes_Invoker(Arg.IsAny())).Returns(2048L); + + var initialSampleTime = (DateTime)typeof(GCSamplerModern) + .GetField("_lastSampleTime", System.Reflection.BindingFlags.NonPublic | System.Reflection.BindingFlags.Instance) + .GetValue(_gcSamplerModern); + + // Act + _gcSamplerModern.Sample(); + var newSampleTime = (DateTime)typeof(GCSamplerModern) + .GetField("_lastSampleTime", System.Reflection.BindingFlags.NonPublic | System.Reflection.BindingFlags.Instance) + .GetValue(_gcSamplerModern); + + // Assert + Assert.That(newSampleTime, Is.GreaterThan(initialSampleTime)); + } + + // Mock classes to replace anonymous types + public class GCMemoryInfo + { + public long TotalCommittedBytes { get; set; } + } + + public class GenerationInfo + { + public long SizeAfterBytes { get; set; } + public long FragmentationAfterBytes { get; set; } + } + } +} diff --git a/tests/Agent/UnitTests/NewRelic.Agent.Extensions.Tests/Reflection/VisibilityBypasserTests.cs b/tests/Agent/UnitTests/NewRelic.Agent.Extensions.Tests/Reflection/VisibilityBypasserTests.cs index f25521058f..f9cc2811d4 100644 --- a/tests/Agent/UnitTests/NewRelic.Agent.Extensions.Tests/Reflection/VisibilityBypasserTests.cs +++ b/tests/Agent/UnitTests/NewRelic.Agent.Extensions.Tests/Reflection/VisibilityBypasserTests.cs @@ -54,6 +54,8 @@ private class PrivateInner public string GetWritableStringField { get { return _writableStringField; } } private int _writeableIntField = 7; public int GetWriteableIntField { get { return _writeableIntField; } } + + public static int StaticMethodWithOneParameter(int param) { return param;} } public static class PublicStatic @@ -628,7 +630,7 @@ public void test_input_validation() public class StaticMethodTests { [Test] - public void test_static_generator() + public void generate_parameterless_static_method_caller() { var assemblyName = Assembly.GetExecutingAssembly().FullName; var typeName = "NewRelic.Reflection.UnitTests.PublicStatic"; @@ -640,5 +642,66 @@ public void test_static_generator() Assert.Throws(() => VisibilityBypasser.Instance.GenerateParameterlessStaticMethodCaller(assemblyName, typeName, "NoSuchMethod")); } - } + [Test] + public void try_generate_one_parameter_static_method_caller() + { + var methodName = "StaticMethodWithOneParameter"; + var expectedValue = 5; + + var success = VisibilityBypasser.Instance.TryGenerateOneParameterStaticMethodCaller(typeof(PublicOuter), methodName, typeof(int), typeof(int), out var accessor ); + Assert.That(success, Is.True); + + var actualValue = accessor(5); + + Assert.That(actualValue, Is.EqualTo(expectedValue)); + } + + [Test] + public void try_generate_one_parameter_static_method_caller_failure() + { + // Arrange + var ownerType = typeof(PublicStatic); + var methodName = "NonExistentMethod"; + var paramType = typeof(int); + var returnType = typeof(int); + Func accessor; + + // Act + var result = VisibilityBypasser.Instance.TryGenerateOneParameterStaticMethodCaller(ownerType, methodName, paramType, returnType, out accessor); + + // Assert + Assert.That(result, Is.False); + Assert.That(accessor, Is.Null); + } + + + [Test] + public void try_generate_parameterless_static_method_caller() + { + var methodName = "GetANumber"; + var expectedValue = 3; + + var success = VisibilityBypasser.Instance.TryGenerateParameterlessStaticMethodCaller(typeof(PublicStatic), methodName, out var accessor); + Assert.That(success, Is.True); + + var actualValue = accessor(); + + Assert.That(actualValue, Is.EqualTo(expectedValue)); + } + + [Test] + public void try_generate_parameterless_static_method_caller_failure() + { + // Arrange + var ownerType = typeof(PublicStatic); + var methodName = "NonExistentMethod"; + Func accessor; + + // Act + var success = VisibilityBypasser.Instance.TryGenerateParameterlessStaticMethodCaller(ownerType, methodName, out accessor); + + // Assert + Assert.That(success, Is.False); + Assert.That(accessor, Is.Null); + } } }