Skip to content

Commit

Permalink
Unittests around memory allocation (#29)
Browse files Browse the repository at this point in the history
  • Loading branch information
martinothamar authored Apr 23, 2022
1 parent 4a70af7 commit dbad06b
Show file tree
Hide file tree
Showing 12 changed files with 262 additions and 2 deletions.
32 changes: 31 additions & 1 deletion Mediator.sln
Original file line number Diff line number Diff line change
Expand Up @@ -72,7 +72,11 @@ Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "Mediator.SourceGenerator.Im
EndProject
Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "Mediator.SourceGenerator.Roslyn40.Tests", "test\Mediator.SourceGenerator.Roslyn40.Tests\Mediator.SourceGenerator.Roslyn40.Tests.csproj", "{E4EE80C5-179C-4483-9E91-3107B3E1CD5A}"
EndProject
Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "Mediator.SmokeTestConsole", "test\Mediator.SmokeTestConsole\Mediator.SmokeTestConsole.csproj", "{65D8C0F8-E4CF-452D-86FB-482FE63C7C89}"
Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "Mediator.SmokeTestConsole", "test\Mediator.SmokeTestConsole\Mediator.SmokeTestConsole.csproj", "{65D8C0F8-E4CF-452D-86FB-482FE63C7C89}"
EndProject
Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "Mediator.MemAllocationTests", "test\Mediator.MemAllocationTests\Mediator.MemAllocationTests.csproj", "{EECFACC8-E841-4EF5-A1D7-A64C84B7DBCD}"
EndProject
Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "Mediator.Tests.Common", "test\Mediator.Tests.Common\Mediator.Tests.Common.csproj", "{32DC3B96-7E6E-40B0-AA12-40656B2D0381}"
EndProject
Global
GlobalSection(SolutionConfigurationPlatforms) = preSolution
Expand Down Expand Up @@ -336,6 +340,30 @@ Global
{65D8C0F8-E4CF-452D-86FB-482FE63C7C89}.Release|x64.Build.0 = Release|Any CPU
{65D8C0F8-E4CF-452D-86FB-482FE63C7C89}.Release|x86.ActiveCfg = Release|Any CPU
{65D8C0F8-E4CF-452D-86FB-482FE63C7C89}.Release|x86.Build.0 = Release|Any CPU
{EECFACC8-E841-4EF5-A1D7-A64C84B7DBCD}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
{EECFACC8-E841-4EF5-A1D7-A64C84B7DBCD}.Debug|Any CPU.Build.0 = Debug|Any CPU
{EECFACC8-E841-4EF5-A1D7-A64C84B7DBCD}.Debug|x64.ActiveCfg = Debug|Any CPU
{EECFACC8-E841-4EF5-A1D7-A64C84B7DBCD}.Debug|x64.Build.0 = Debug|Any CPU
{EECFACC8-E841-4EF5-A1D7-A64C84B7DBCD}.Debug|x86.ActiveCfg = Debug|Any CPU
{EECFACC8-E841-4EF5-A1D7-A64C84B7DBCD}.Debug|x86.Build.0 = Debug|Any CPU
{EECFACC8-E841-4EF5-A1D7-A64C84B7DBCD}.Release|Any CPU.ActiveCfg = Release|Any CPU
{EECFACC8-E841-4EF5-A1D7-A64C84B7DBCD}.Release|Any CPU.Build.0 = Release|Any CPU
{EECFACC8-E841-4EF5-A1D7-A64C84B7DBCD}.Release|x64.ActiveCfg = Release|Any CPU
{EECFACC8-E841-4EF5-A1D7-A64C84B7DBCD}.Release|x64.Build.0 = Release|Any CPU
{EECFACC8-E841-4EF5-A1D7-A64C84B7DBCD}.Release|x86.ActiveCfg = Release|Any CPU
{EECFACC8-E841-4EF5-A1D7-A64C84B7DBCD}.Release|x86.Build.0 = Release|Any CPU
{32DC3B96-7E6E-40B0-AA12-40656B2D0381}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
{32DC3B96-7E6E-40B0-AA12-40656B2D0381}.Debug|Any CPU.Build.0 = Debug|Any CPU
{32DC3B96-7E6E-40B0-AA12-40656B2D0381}.Debug|x64.ActiveCfg = Debug|Any CPU
{32DC3B96-7E6E-40B0-AA12-40656B2D0381}.Debug|x64.Build.0 = Debug|Any CPU
{32DC3B96-7E6E-40B0-AA12-40656B2D0381}.Debug|x86.ActiveCfg = Debug|Any CPU
{32DC3B96-7E6E-40B0-AA12-40656B2D0381}.Debug|x86.Build.0 = Debug|Any CPU
{32DC3B96-7E6E-40B0-AA12-40656B2D0381}.Release|Any CPU.ActiveCfg = Release|Any CPU
{32DC3B96-7E6E-40B0-AA12-40656B2D0381}.Release|Any CPU.Build.0 = Release|Any CPU
{32DC3B96-7E6E-40B0-AA12-40656B2D0381}.Release|x64.ActiveCfg = Release|Any CPU
{32DC3B96-7E6E-40B0-AA12-40656B2D0381}.Release|x64.Build.0 = Release|Any CPU
{32DC3B96-7E6E-40B0-AA12-40656B2D0381}.Release|x86.ActiveCfg = Release|Any CPU
{32DC3B96-7E6E-40B0-AA12-40656B2D0381}.Release|x86.Build.0 = Release|Any CPU
EndGlobalSection
GlobalSection(SolutionProperties) = preSolution
HideSolutionNode = FALSE
Expand Down Expand Up @@ -363,6 +391,8 @@ Global
{22984D49-8DDF-4263-8375-D018643E44B2} = {438C928E-FBB5-45B2-9CD2-7ED3341AC75E}
{E4EE80C5-179C-4483-9E91-3107B3E1CD5A} = {ED1809FC-733B-4D9E-8FEE-70D3BB0BBD84}
{65D8C0F8-E4CF-452D-86FB-482FE63C7C89} = {ED1809FC-733B-4D9E-8FEE-70D3BB0BBD84}
{EECFACC8-E841-4EF5-A1D7-A64C84B7DBCD} = {ED1809FC-733B-4D9E-8FEE-70D3BB0BBD84}
{32DC3B96-7E6E-40B0-AA12-40656B2D0381} = {ED1809FC-733B-4D9E-8FEE-70D3BB0BBD84}
EndGlobalSection
GlobalSection(ExtensibilityGlobals) = postSolution
SolutionGuid = {D45B5457-4190-49B6-BF89-7FA5F4C8ABE2}
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,39 @@
<Project Sdk="Microsoft.NET.Sdk">

<PropertyGroup>
<TargetFramework>net6.0</TargetFramework>

<IsPackable>false</IsPackable>
<AllowUnsafeBlocks>true</AllowUnsafeBlocks>
<EmitCompilerGeneratedFiles>true</EmitCompilerGeneratedFiles>
<!--<ReportAnalyzer>true</ReportAnalyzer>-->
</PropertyGroup>

<ItemGroup>
<Using Include="Xunit" />
<Using Include="System.Runtime.CompilerServices" />
</ItemGroup>

<ItemGroup>
<PackageReference Include="Microsoft.Extensions.DependencyInjection" Version="$(DotNetVersion)" />
<PackageReference Include="Microsoft.NET.Test.Sdk" Version="17.1.0" />
<PackageReference Include="xunit" Version="2.4.1" />
<PackageReference Include="xunit.runner.visualstudio" Version="2.4.3">
<IncludeAssets>runtime; build; native; contentfiles; analyzers; buildtransitive</IncludeAssets>
<PrivateAssets>all</PrivateAssets>
</PackageReference>
<PackageReference Include="coverlet.collector" Version="3.1.2">
<IncludeAssets>runtime; build; native; contentfiles; analyzers; buildtransitive</IncludeAssets>
<PrivateAssets>all</PrivateAssets>
</PackageReference>
<PackageReference Include="JetBrains.DotMemoryUnit" Version="3.1.20200127.214830" />
</ItemGroup>

<ItemGroup>
<ProjectReference Include="..\..\src\Mediator.SourceGenerator.Implementation\Mediator.SourceGenerator.Implementation.csproj" OutputItemType="Analyzer" />
<ProjectReference Include="..\..\src\Mediator.SourceGenerator.Roslyn40\Mediator.SourceGenerator.Roslyn40.csproj" OutputItemType="Analyzer" />
<ProjectReference Include="..\..\src\Mediator\Mediator.csproj" />
<ProjectReference Include="..\Mediator.Tests.Common\Mediator.Tests.Common.csproj" />
</ItemGroup>

</Project>
Original file line number Diff line number Diff line change
@@ -0,0 +1,6 @@
namespace Mediator.Tests;

[CollectionDefinition("Non-Parallel", DisableParallelization = true)]
public class NonParallelCollectionDefinitionClass
{
}
32 changes: 32 additions & 0 deletions test/Mediator.MemAllocationTests/NotificationTests.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,32 @@
using Mediator.Tests.Common;
using Microsoft.Extensions.DependencyInjection;

namespace Mediator.MemAllocationTests;

[Collection("Non-Parallel")]
public class NotificationTests
{
[Fact]
public void Test_Notification_Handler()
{
var services = new ServiceCollection();

services.AddMediator();

using var sp = services.BuildServiceProvider(validateScopes: true);
var mediator = sp.GetRequiredService<IMediator>();

var id = Guid.NewGuid();
var notification = new SomeNotificationMemAllocTracking(id);

// Ensure everything is cached
mediator.Publish(notification); // Everything returns sync

var beforeBytes = Allocations.GetCurrentThreadAllocatedBytes();
mediator.Publish(notification); // Everything returns sync
var afterBytes = Allocations.GetCurrentThreadAllocatedBytes();

// TODO: why 40? benchmarkdotnet reports 0 alloc for this case
Assert.Equal(40, afterBytes - beforeBytes);
}
}
94 changes: 94 additions & 0 deletions test/Mediator.MemAllocationTests/RequestTests.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,94 @@
using JetBrains.dotMemoryUnit;
using JetBrains.dotMemoryUnit.Kernel;
using Mediator.Tests.Common;
using Microsoft.Extensions.DependencyInjection;
using Xunit.Abstractions;

namespace Mediator.MemAllocationTests;

[Collection("Non-Parallel")]
public class RequestTests
{
private readonly ITestOutputHelper _output;

public RequestTests(ITestOutputHelper output)
{
_output = output;
DotMemoryUnitTestOutput.SetOutputMethod(output.WriteLine);
}

/// <summary>
/// To run these tests
/// dotMemoryUnit.exe "$((Get-Command dotnet.exe).Path)" -- test "./test/Mediator.MemAllocationTests/"
/// TODO: find a way to filter out dotMemoryUnit's own mem allocations from report...
/// </summary>
/// <returns></returns>
[DotMemoryUnit(CollectAllocations = true)]
[Fact]
public async Task Test_Request_Handler_dotMemory()
{
if (!dotMemoryApi.IsEnabled)
return;

var services = new ServiceCollection();

services.AddMediator();

await using var sp = services.BuildServiceProvider(validateScopes: true);
var mediator = sp.GetRequiredService<Mediator>();

var id = Guid.NewGuid();
var request = new SomeRequestMemAllocTracking(id);

// Ensure everything is cached
await mediator.Send(request);
GC.Collect();
GC.WaitForPendingFinalizers();
GC.WaitForFullGCComplete();
GC.Collect();

var checkpoint = dotMemory.Check();
var beforeBytes = GC.GetAllocatedBytesForCurrentThread();
await mediator.Send(request);
var afterBytes = GC.GetAllocatedBytesForCurrentThread();

Assert.Equal(0, afterBytes - beforeBytes);
dotMemory.Check(memory =>
{
var traffic = memory.GetDifference(checkpoint);
var newObjects = traffic.GetNewObjects();
foreach (var obj in newObjects.GroupByType())
_output.WriteLine($"Allocations for {obj.TypeFullyQualifiedName}: {obj.SizeInBytes} bytes");

//_output.WriteLine($"Allocated: {traffic.AllocatedMemory.SizeInBytes}");
//foreach (var obj in traffic.GroupByType())
// _output.WriteLine($"Allocations for {obj.TypeFullyQualifiedName}: {obj.AllocatedMemoryInfo.SizeInBytes}");

Assert.Equal(0, traffic.GetNewObjects().ObjectsCount);
});
}


[Fact]
public void Test_Request_Handler()
{
var services = new ServiceCollection();

services.AddMediator();

using var sp = services.BuildServiceProvider(validateScopes: true);
var mediator = sp.GetRequiredService<IMediator>();

var id = Guid.NewGuid();
var request = new SomeRequestMemAllocTracking(id);

// Ensure everything is cached
mediator.Send(request); // Everything returns sync

var beforeBytes = Allocations.GetCurrentThreadAllocatedBytes();
mediator.Send(request); // Everything returns sync
var afterBytes = Allocations.GetCurrentThreadAllocatedBytes();

Assert.Equal(0, afterBytes - beforeBytes);
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,8 @@
namespace Mediator.MemAllocationTests;

public sealed record SomeNotificationMemAllocTracking(Guid Id) : INotification;

public sealed class SomeNotificationMemAllocTrackingHandler : INotificationHandler<SomeNotificationMemAllocTracking>
{
public ValueTask Handle(SomeNotificationMemAllocTracking notification, CancellationToken cancellationToken) => default;
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,8 @@
namespace Mediator.MemAllocationTests;

public sealed record SomeRequestMemAllocTracking(Guid Id) : IRequest;

public sealed class SomeRequestMemAllocTrackingHandler : IRequestHandler<SomeRequestMemAllocTracking>
{
public ValueTask<Unit> Handle(SomeRequestMemAllocTracking request, CancellationToken cancellationToken) => default;
}
22 changes: 22 additions & 0 deletions test/Mediator.Tests.Common/Fixture.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,22 @@
namespace Mediator.Tests.Common;

public static class Allocations
{
public static long GetCurrentThreadAllocatedBytes()
{
// Run before and after message sending to test allocations.
// Stolen from https://github.com/dotnet/BenchmarkDotNet/blob/4bd433d85fff4fb6ba8c4f8df3e685ad669e2519/src/BenchmarkDotNet/Engines/GcStats.cs#L132
// There is a reason GC.Collect is run first
GC.Collect();
return GC.GetAllocatedBytesForCurrentThread();
}

public static long GetCurrentAllocatedBytes()
{
// Run before and after message sending to test allocations.
// Stolen from https://github.com/dotnet/BenchmarkDotNet/blob/4bd433d85fff4fb6ba8c4f8df3e685ad669e2519/src/BenchmarkDotNet/Engines/GcStats.cs#L132
// There is a reason GC.Collect is run first
GC.Collect();
return GC.GetTotalAllocatedBytes(true);
}
}
15 changes: 15 additions & 0 deletions test/Mediator.Tests.Common/Mediator.Tests.Common.csproj
Original file line number Diff line number Diff line change
@@ -0,0 +1,15 @@
<Project Sdk="Microsoft.NET.Sdk">

<PropertyGroup>
<TargetFramework>net6.0</TargetFramework>
</PropertyGroup>

<ItemGroup>
<PackageReference Include="Microsoft.Extensions.DependencyInjection" Version="$(DotNetVersion)" />
</ItemGroup>

<ItemGroup>
<ProjectReference Include="..\..\src\Mediator\Mediator.csproj" />
</ItemGroup>

</Project>
1 change: 0 additions & 1 deletion test/Mediator.Tests/Fixture.cs
Original file line number Diff line number Diff line change
Expand Up @@ -49,7 +49,6 @@ public static (IServiceProvider sp, IMediator mediator) GetMediatorCustomContain
return (sp, mediator!);
}


private sealed class CustomServiceProvider : IServiceProvider
{
private readonly IServiceCollection _services;
Expand Down
1 change: 1 addition & 0 deletions test/Mediator.Tests/Mediator.Tests.csproj
Original file line number Diff line number Diff line change
Expand Up @@ -32,6 +32,7 @@
<ProjectReference Include="..\..\src\Mediator.SourceGenerator.Implementation\Mediator.SourceGenerator.Implementation.csproj" OutputItemType="Analyzer" />
<ProjectReference Include="..\..\src\Mediator.SourceGenerator.Roslyn38\Mediator.SourceGenerator.Roslyn38.csproj" OutputItemType="Analyzer" />
<ProjectReference Include="..\..\src\Mediator\Mediator.csproj" />
<ProjectReference Include="..\Mediator.Tests.Common\Mediator.Tests.Common.csproj" />
</ItemGroup>

</Project>
6 changes: 6 additions & 0 deletions test/Mediator.Tests/NonParallelCollectionDefinitionClass.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,6 @@
namespace Mediator.Tests;

[CollectionDefinition("Non-Parallel", DisableParallelization = true)]
public class NonParallelCollectionDefinitionClass
{
}

0 comments on commit dbad06b

Please # to comment.