Skip to content

Commit ac07e45

Browse files
committed
Add allocation tracker test helpers
1 parent 5d46d68 commit ac07e45

9 files changed

Lines changed: 168 additions & 63 deletions

File tree

tests/ImageSharp.Drawing.Tests/Drawing/Text/DrawTextOnImageTests.cs

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -16,6 +16,7 @@
1616
namespace SixLabors.ImageSharp.Drawing.Tests.Drawing.Text
1717
{
1818
[GroupOutput("Drawing/Text")]
19+
[ValidateDisposedMemoryAllocations]
1920
public class DrawTextOnImageTests
2021
{
2122
private const string AB = "AB\nAB";
Lines changed: 64 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,64 @@
1+
// Copyright (c) Six Labors.
2+
// Licensed under the Apache License, Version 2.0.
3+
4+
using System;
5+
using System.Collections.Generic;
6+
using System.Diagnostics;
7+
using System.Threading;
8+
using SixLabors.ImageSharp.Diagnostics;
9+
using Xunit;
10+
11+
namespace SixLabors.ImageSharp.Drawing.Tests
12+
{
13+
public static class MemoryAllocatorValidator
14+
{
15+
private static List<string> traces = new List<string>();
16+
17+
public static void MonitorStackTraces() => MemoryDiagnostics.UndisposedAllocation += MemoryDiagnostics_UndisposedAllocation;
18+
19+
public static List<string> GetStackTraces()
20+
{
21+
GC.Collect();
22+
GC.WaitForPendingFinalizers();
23+
return traces;
24+
}
25+
26+
private static void MemoryDiagnostics_UndisposedAllocation(string allocationStackTrace)
27+
{
28+
lock (traces)
29+
{
30+
traces.Add(allocationStackTrace);
31+
}
32+
}
33+
34+
public static TestMemoryAllocatorDisposable MonitorAllocations()
35+
{
36+
MemoryDiagnostics.Current = new();
37+
return new TestMemoryAllocatorDisposable();
38+
}
39+
40+
public static void StopMonitoringAllocations() => MemoryDiagnostics.Current = null;
41+
42+
public static void ValidateAllocation(int max = 0)
43+
{
44+
var count = MemoryDiagnostics.TotalUndisposedAllocationCount;
45+
46+
var pass = count <= max;
47+
Assert.True(pass, $"Expected a max of {max} undisposed buffers but found {count}");
48+
49+
if (count > 0)
50+
{
51+
Debug.WriteLine("We should have Zero undisposed memory allocations.");
52+
}
53+
}
54+
55+
public struct TestMemoryAllocatorDisposable : IDisposable
56+
{
57+
public void Dispose()
58+
=> StopMonitoringAllocations();
59+
60+
public void Validate(int maxAllocations = 0)
61+
=> ValidateAllocation(maxAllocations);
62+
}
63+
}
64+
}

tests/ImageSharp.Drawing.Tests/Shapes/Scan/ScanEdgeCollectionTests.cs

Lines changed: 50 additions & 50 deletions
Original file line numberDiff line numberDiff line change
@@ -10,21 +10,20 @@ namespace SixLabors.ImageSharp.Drawing.Tests.Shapes.Scan
1010
{
1111
public class ScanEdgeCollectionTests
1212
{
13-
private ScanEdgeCollection edges;
14-
1513
private static MemoryAllocator MemoryAllocator => Configuration.Default.MemoryAllocator;
1614

1715
private static readonly DebugDraw DebugDraw = new DebugDraw(nameof(ScanEdgeCollectionTests));
1816

1917
private void VerifyEdge(
18+
ScanEdgeCollection edges,
2019
float y0,
2120
float y1,
2221
(FuzzyFloat X, FuzzyFloat Y) arbitraryPoint,
2322
int emit0,
2423
int emit1,
2524
bool edgeUp)
2625
{
27-
foreach (ScanEdge e in this.edges.Edges)
26+
foreach (ScanEdge e in edges.Edges)
2827
{
2928
if (y0 == e.Y0 && y1 == e.Y1)
3029
{
@@ -45,6 +44,7 @@ private void VerifyEdge(
4544
}
4645

4746
[Fact]
47+
[ValidateDisposedMemoryAllocations]
4848
public void SimplePolygon_AllEmitCases()
4949
{
5050
// see: SimplePolygon_AllEmitCases.png
@@ -78,29 +78,29 @@ public void SimplePolygon_AllEmitCases()
7878

7979
DebugDraw.Polygon(polygon, 1, 100);
8080

81-
this.edges = ScanEdgeCollection.Create(polygon, MemoryAllocator, 16);
82-
83-
Assert.Equal(19, this.edges.Edges.Length);
84-
85-
this.VerifyEdge(1f, 2f, (2.5f, 1.5f), 1, 2, true);
86-
this.VerifyEdge(1f, 3f, (3.5f, 2f), 1, 1, false);
87-
this.VerifyEdge(1f, 3f, (5f, 2f), 1, 1, true);
88-
this.VerifyEdge(1f, 2f, (6.5f, 1.5f), 1, 2, false);
89-
this.VerifyEdge(2f, 3f, (8.5f, 2.5f), 1, 0, false);
90-
this.VerifyEdge(3f, 4f, (9f, 3.5f), 1, 0, false);
91-
this.VerifyEdge(4f, 5f, (9.5f, 4.5f), 1, 0, false);
92-
this.VerifyEdge(5f, 6f, (9.5f, 5.5f), 1, 1, false);
93-
this.VerifyEdge(6f, 7f, (8f, 6.5f), 2, 2, false);
94-
this.VerifyEdge(7f, 8f, (9f, 7.5f), 1, 1, false);
95-
this.VerifyEdge(7f, 8f, (6.5f, 7.5f), 1, 1, true);
96-
this.VerifyEdge(7f, 8f, (5.5f, 7.5f), 1, 1, false);
97-
this.VerifyEdge(7f, 8f, (4.5f, 7.5f), 1, 1, true);
98-
this.VerifyEdge(7f, 8f, (3.5f, 7.5f), 1, 1, false);
99-
this.VerifyEdge(6f, 8f, (2f, 7f), 0, 1, true);
100-
this.VerifyEdge(5f, 6f, (2.5f, 5.5f), 2, 1, true);
101-
this.VerifyEdge(4f, 5f, (2f, 4.5f), 0, 1, true);
102-
this.VerifyEdge(3f, 4f, (1.5f, 3.5f), 0, 1, true);
103-
this.VerifyEdge(2f, 3f, (1f, 1.5f), 1, 1, true);
81+
using var edges = ScanEdgeCollection.Create(polygon, MemoryAllocator, 16);
82+
83+
Assert.Equal(19, edges.Edges.Length);
84+
85+
this.VerifyEdge(edges, 1f, 2f, (2.5f, 1.5f), 1, 2, true);
86+
this.VerifyEdge(edges, 1f, 3f, (3.5f, 2f), 1, 1, false);
87+
this.VerifyEdge(edges, 1f, 3f, (5f, 2f), 1, 1, true);
88+
this.VerifyEdge(edges, 1f, 2f, (6.5f, 1.5f), 1, 2, false);
89+
this.VerifyEdge(edges, 2f, 3f, (8.5f, 2.5f), 1, 0, false);
90+
this.VerifyEdge(edges, 3f, 4f, (9f, 3.5f), 1, 0, false);
91+
this.VerifyEdge(edges, 4f, 5f, (9.5f, 4.5f), 1, 0, false);
92+
this.VerifyEdge(edges, 5f, 6f, (9.5f, 5.5f), 1, 1, false);
93+
this.VerifyEdge(edges, 6f, 7f, (8f, 6.5f), 2, 2, false);
94+
this.VerifyEdge(edges, 7f, 8f, (9f, 7.5f), 1, 1, false);
95+
this.VerifyEdge(edges, 7f, 8f, (6.5f, 7.5f), 1, 1, true);
96+
this.VerifyEdge(edges, 7f, 8f, (5.5f, 7.5f), 1, 1, false);
97+
this.VerifyEdge(edges, 7f, 8f, (4.5f, 7.5f), 1, 1, true);
98+
this.VerifyEdge(edges, 7f, 8f, (3.5f, 7.5f), 1, 1, false);
99+
this.VerifyEdge(edges, 6f, 8f, (2f, 7f), 0, 1, true);
100+
this.VerifyEdge(edges, 5f, 6f, (2.5f, 5.5f), 2, 1, true);
101+
this.VerifyEdge(edges, 4f, 5f, (2f, 4.5f), 0, 1, true);
102+
this.VerifyEdge(edges, 3f, 4f, (1.5f, 3.5f), 0, 1, true);
103+
this.VerifyEdge(edges, 2f, 3f, (1f, 1.5f), 1, 1, true);
104104
}
105105

106106
[Fact]
@@ -114,54 +114,54 @@ public void ComplexPolygon()
114114
IPath polygon = contour.Clip(hole);
115115
DebugDraw.Polygon(polygon, 1, 100);
116116

117-
this.edges = ScanEdgeCollection.Create(polygon, MemoryAllocator, 16);
117+
using var edges = ScanEdgeCollection.Create(polygon, MemoryAllocator, 16);
118118

119-
Assert.Equal(8, this.edges.Count);
119+
Assert.Equal(8, edges.Count);
120120

121-
this.VerifyEdge(1, 4, (1, 2), 1, 1, true);
122-
this.VerifyEdge(1, 2, (4, 1.5f), 1, 2, false);
123-
this.VerifyEdge(4, 5, (2, 4.5f), 2, 1, true);
124-
this.VerifyEdge(2, 5, (5, 3f), 1, 1, false);
121+
this.VerifyEdge(edges, 1, 4, (1, 2), 1, 1, true);
122+
this.VerifyEdge(edges, 1, 2, (4, 1.5f), 1, 2, false);
123+
this.VerifyEdge(edges, 4, 5, (2, 4.5f), 2, 1, true);
124+
this.VerifyEdge(edges, 2, 5, (5, 3f), 1, 1, false);
125125

126-
this.VerifyEdge(2, 3, (2, 2.5f), 2, 2, false);
127-
this.VerifyEdge(2, 3, (3.5f, 2.5f), 2, 1, true);
128-
this.VerifyEdge(3, 4, (3, 3.5f), 1, 2, false);
129-
this.VerifyEdge(3, 4, (4, 3.5f), 0, 2, true);
126+
this.VerifyEdge(edges, 2, 3, (2, 2.5f), 2, 2, false);
127+
this.VerifyEdge(edges, 2, 3, (3.5f, 2.5f), 2, 1, true);
128+
this.VerifyEdge(edges, 3, 4, (3, 3.5f), 1, 2, false);
129+
this.VerifyEdge(edges, 3, 4, (4, 3.5f), 0, 2, true);
130130
}
131131

132132
[Fact]
133133
public void NumericCornerCase_C()
134134
{
135-
this.edges = ScanEdgeCollection.Create(NumericCornerCasePolygons.C, MemoryAllocator, 4);
136-
Assert.Equal(2, this.edges.Count);
137-
this.VerifyEdge(3.5f, 4f, (2f, 3.75f), 1, 1, true);
138-
this.VerifyEdge(3.5f, 4f, (8f, 3.75f), 1, 1, false);
135+
using var edges = ScanEdgeCollection.Create(NumericCornerCasePolygons.C, MemoryAllocator, 4);
136+
Assert.Equal(2, edges.Count);
137+
this.VerifyEdge(edges, 3.5f, 4f, (2f, 3.75f), 1, 1, true);
138+
this.VerifyEdge(edges, 3.5f, 4f, (8f, 3.75f), 1, 1, false);
139139
}
140140

141141
[Fact]
142142
public void NumericCornerCase_D()
143143
{
144-
this.edges = ScanEdgeCollection.Create(NumericCornerCasePolygons.D, MemoryAllocator, 4);
145-
Assert.Equal(5, this.edges.Count);
144+
using var edges = ScanEdgeCollection.Create(NumericCornerCasePolygons.D, MemoryAllocator, 4);
145+
Assert.Equal(5, edges.Count);
146146

147-
this.VerifyEdge(3.25f, 4f, (12f, 3.75f), 1, 1, true);
148-
this.VerifyEdge(3.25f, 3.5f, (15f, 3.375f), 1, 0, false);
149-
this.VerifyEdge(3.5f, 4f, (18f, 3.75f), 1, 1, false);
147+
this.VerifyEdge(edges, 3.25f, 4f, (12f, 3.75f), 1, 1, true);
148+
this.VerifyEdge(edges, 3.25f, 3.5f, (15f, 3.375f), 1, 0, false);
149+
this.VerifyEdge(edges, 3.5f, 4f, (18f, 3.75f), 1, 1, false);
150150

151151
// TODO: verify 2 more edges
152152
}
153153

154154
[Fact]
155155
public void NumericCornerCase_H_ShouldCollapseNearZeroEdge()
156156
{
157-
this.edges = ScanEdgeCollection.Create(NumericCornerCasePolygons.H, MemoryAllocator, 4);
157+
using var edges = ScanEdgeCollection.Create(NumericCornerCasePolygons.H, MemoryAllocator, 4);
158158

159-
Assert.Equal(3, this.edges.Count);
160-
this.VerifyEdge(1.75f, 2f, (15f, 1.875f), 1, 1, true);
161-
this.VerifyEdge(1.75f, 2.25f, (16f, 2f), 1, 1, false);
159+
Assert.Equal(3, edges.Count);
160+
this.VerifyEdge(edges, 1.75f, 2f, (15f, 1.875f), 1, 1, true);
161+
this.VerifyEdge(edges, 1.75f, 2.25f, (16f, 2f), 1, 1, false);
162162

163163
// this places two dummy points:
164-
this.VerifyEdge(2f, 2.25f, (15f, 2.125f), 2, 1, true);
164+
this.VerifyEdge(edges, 2f, 2.25f, (15f, 2.125f), 2, 1, true);
165165
}
166166

167167
private static FuzzyFloat F(float value, float eps) => new FuzzyFloat(value, eps);

tests/ImageSharp.Drawing.Tests/TestFormat.cs

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -199,7 +199,7 @@ public class TestDecoder : IImageDecoder
199199

200200
public int HeaderSize => this.testFormat.HeaderSize;
201201

202-
public Image<TPixel> Decode<TPixel>(Configuration config, Stream stream)
202+
public Image<TPixel> Decode<TPixel>(Configuration config, Stream stream, CancellationToken cancellationToken)
203203
where TPixel : unmanaged, IPixel<TPixel>
204204
{
205205
var ms = new MemoryStream();
@@ -218,7 +218,7 @@ public Image<TPixel> Decode<TPixel>(Configuration config, Stream stream)
218218

219219
public bool IsSupportedFileFormat(Span<byte> header) => this.testFormat.IsSupportedFileFormat(header);
220220

221-
public Image Decode(Configuration configuration, Stream stream) => this.Decode<TestPixelForAgnosticDecode>(configuration, stream);
221+
public Image Decode(Configuration configuration, Stream stream, CancellationToken cancellationToken) => this.Decode<TestPixelForAgnosticDecode>(configuration, stream, cancellationToken);
222222

223223
public Task<Image<TPixel>> DecodeAsync<TPixel>(Configuration configuration, Stream stream, CancellationToken cancellationToken)
224224
where TPixel : unmanaged, IPixel<TPixel>

tests/ImageSharp.Drawing.Tests/TestUtilities/ImageProviders/FileProvider.cs

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -5,6 +5,7 @@
55
using System.Collections.Concurrent;
66
using System.Collections.Generic;
77
using System.Reflection;
8+
using SixLabors.ImageSharp.Diagnostics;
89
using SixLabors.ImageSharp.Formats;
910
using SixLabors.ImageSharp.PixelFormats;
1011

tests/ImageSharp.Drawing.Tests/TestUtilities/ReferenceCodecs/MagickReferenceDecoder.cs

Lines changed: 3 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -61,9 +61,9 @@ private static void FromRgba64Bytes<TPixel>(Configuration configuration, Span<by
6161

6262
public Task<Image<TPixel>> DecodeAsync<TPixel>(Configuration configuration, Stream stream, CancellationToken cancellationToken)
6363
where TPixel : unmanaged, PixelFormats.IPixel<TPixel>
64-
=> Task.FromResult(this.Decode<TPixel>(configuration, stream));
64+
=> Task.FromResult(this.Decode<TPixel>(configuration, stream, cancellationToken));
6565

66-
public Image<TPixel> Decode<TPixel>(Configuration configuration, Stream stream)
66+
public Image<TPixel> Decode<TPixel>(Configuration configuration, Stream stream, CancellationToken cancellationToken)
6767
where TPixel : unmanaged, PixelFormats.IPixel<TPixel>
6868
{
6969
var bmpReadDefines = new BmpReadDefines
@@ -105,7 +105,7 @@ public Image<TPixel> Decode<TPixel>(Configuration configuration, Stream stream)
105105
return new Image<TPixel>(configuration, new ImageMetadata(), framesList);
106106
}
107107

108-
public Image Decode(Configuration configuration, Stream stream) => this.Decode<Rgba32>(configuration, stream);
108+
public Image Decode(Configuration configuration, Stream stream, CancellationToken cancellationToken) => this.Decode<Rgba32>(configuration, stream, cancellationToken);
109109

110110
public async Task<Image> DecodeAsync(Configuration configuration, Stream stream, CancellationToken cancellationToken)
111111
=> await this.DecodeAsync<Rgba32>(configuration, stream, cancellationToken);

tests/ImageSharp.Drawing.Tests/TestUtilities/ReferenceCodecs/SystemDrawingReferenceDecoder.cs

Lines changed: 4 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -14,7 +14,7 @@ public class SystemDrawingReferenceDecoder : IImageDecoder, IImageInfoDetector
1414
{
1515
public static SystemDrawingReferenceDecoder Instance { get; } = new SystemDrawingReferenceDecoder();
1616

17-
public Image<TPixel> Decode<TPixel>(Configuration configuration, Stream stream)
17+
public Image<TPixel> Decode<TPixel>(Configuration configuration, Stream stream, CancellationToken cancellationToken)
1818
where TPixel : unmanaged, IPixel<TPixel>
1919
{
2020
using (var sourceBitmap = new System.Drawing.Bitmap(stream))
@@ -44,7 +44,7 @@ public Image<TPixel> Decode<TPixel>(Configuration configuration, Stream stream)
4444
}
4545
}
4646

47-
public IImageInfo Identify(Configuration configuration, Stream stream)
47+
public IImageInfo Identify(Configuration configuration, Stream stream, CancellationToken cancellationToken)
4848
{
4949
using (var sourceBitmap = new System.Drawing.Bitmap(stream))
5050
{
@@ -56,8 +56,8 @@ public IImageInfo Identify(Configuration configuration, Stream stream)
5656
public Task<IImageInfo> IdentifyAsync(Configuration configuration, Stream stream, CancellationToken cancellationToken)
5757
=> throw new System.NotImplementedException();
5858

59-
public Image Decode(Configuration configuration, Stream stream)
60-
=> this.Decode<Rgba32>(configuration, stream);
59+
public Image Decode(Configuration configuration, Stream stream, CancellationToken cancellationToken)
60+
=> this.Decode<Rgba32>(configuration, stream, cancellationToken);
6161

6262
public Task<Image<TPixel>> DecodeAsync<TPixel>(Configuration configuration, Stream stream, CancellationToken cancellationToken)
6363
where TPixel : unmanaged, IPixel<TPixel>

tests/ImageSharp.Drawing.Tests/TestUtilities/Tests/TestImageProviderTests.cs

Lines changed: 4 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -350,7 +350,7 @@ public static void DoTestThreadSafe(Action action)
350350
}
351351
}
352352

353-
public Image<TPixel> Decode<TPixel>(Configuration configuration, Stream stream)
353+
public Image<TPixel> Decode<TPixel>(Configuration configuration, Stream stream, CancellationToken cancellationToken)
354354
where TPixel : unmanaged, IPixel<TPixel>
355355
{
356356
InvocationCounts[this.callerName]++;
@@ -365,7 +365,7 @@ internal void InitCaller(string name)
365365
InvocationCounts[name] = 0;
366366
}
367367

368-
public Image Decode(Configuration configuration, Stream stream) => this.Decode<Rgba32>(configuration, stream);
368+
public Image Decode(Configuration configuration, Stream stream, CancellationToken cancellationToken) => this.Decode<Rgba32>(configuration, stream, cancellationToken);
369369

370370
public Task<Image<TPixel>> DecodeAsync<TPixel>(Configuration configuration, Stream stream, CancellationToken cancellationToken)
371371
where TPixel : unmanaged, IPixel<TPixel>
@@ -396,7 +396,7 @@ public static void DoTestThreadSafe(Action action)
396396
}
397397
}
398398

399-
public Image<TPixel> Decode<TPixel>(Configuration configuration, Stream stream)
399+
public Image<TPixel> Decode<TPixel>(Configuration configuration, Stream stream, CancellationToken cancellationToken)
400400
where TPixel : unmanaged, IPixel<TPixel>
401401
{
402402
InvocationCounts[this.callerName]++;
@@ -411,7 +411,7 @@ internal void InitCaller(string name)
411411
InvocationCounts[name] = 0;
412412
}
413413

414-
public Image Decode(Configuration configuration, Stream stream) => this.Decode<Rgba32>(configuration, stream);
414+
public Image Decode(Configuration configuration, Stream stream, CancellationToken cancellationToken) => this.Decode<Rgba32>(configuration, stream, cancellationToken);
415415

416416
public Task<Image<TPixel>> DecodeAsync<TPixel>(Configuration configuration, Stream stream, CancellationToken cancellationToken)
417417
where TPixel : unmanaged, IPixel<TPixel>
Lines changed: 39 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,39 @@
1+
// Copyright (c) Six Labors.
2+
// Licensed under the Apache License, Version 2.0.
3+
4+
using System;
5+
using System.Diagnostics;
6+
using System.Reflection;
7+
using Xunit.Sdk;
8+
9+
namespace SixLabors.ImageSharp.Drawing.Tests
10+
{
11+
[AttributeUsage(AttributeTargets.Class | AttributeTargets.Method, AllowMultiple = false, Inherited = true)]
12+
public class ValidateDisposedMemoryAllocationsAttribute : BeforeAfterTestAttribute
13+
{
14+
private readonly int max = 0;
15+
16+
public ValidateDisposedMemoryAllocationsAttribute()
17+
: this(0)
18+
{
19+
}
20+
21+
public ValidateDisposedMemoryAllocationsAttribute(int max)
22+
{
23+
this.max = max;
24+
if (max > 0)
25+
{
26+
Debug.WriteLine("Needs fixing, we shoudl have Zero undisposed memory allocations.");
27+
}
28+
}
29+
30+
public override void Before(MethodInfo methodUnderTest)
31+
=> MemoryAllocatorValidator.MonitorAllocations();
32+
33+
public override void After(MethodInfo methodUnderTest)
34+
{
35+
MemoryAllocatorValidator.ValidateAllocation(this.max);
36+
MemoryAllocatorValidator.StopMonitoringAllocations();
37+
}
38+
}
39+
}

0 commit comments

Comments
 (0)