Skip to content

Commit 9e3b74f

Browse files
committed
Add tests for PersistentProxyBuilder
1 parent e14b6bd commit 9e3b74f

3 files changed

Lines changed: 136 additions & 5 deletions

File tree

src/Castle.Core.Tests/DynamicProxy.Tests/PersistentProxyBuilderTestCase.cs

Lines changed: 115 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,4 @@
1-
// Copyright 2004-2021 Castle Project - http://www.castleproject.org/
1+
// Copyright 2004-2025 Castle Project - http://www.castleproject.org/
22
//
33
// Licensed under the Apache License, Version 2.0 (the "License");
44
// you may not use this file except in compliance with the License.
@@ -12,14 +12,23 @@
1212
// See the License for the specific language governing permissions and
1313
// limitations under the License.
1414

15-
#if FEATURE_ASSEMBLYBUILDER_SAVE
16-
1715
namespace Castle.DynamicProxy.Tests
1816
{
1917
using System;
18+
using System.Collections.Generic;
2019
using System.IO;
20+
using System.Linq;
21+
using System.Reflection;
22+
#if NET9_0_OR_GREATER
23+
using System.Runtime.Loader;
24+
#endif
25+
26+
using Castle.DynamicProxy.Tests.Interfaces;
27+
2128
using NUnit.Framework;
2229

30+
#if NET462_OR_GREATER
31+
2332
[TestFixture]
2433
public class PersistentProxyBuilderTestCase
2534
{
@@ -44,6 +53,107 @@ public void PersistentProxyBuilder_SavesSignedFile()
4453
Assert.IsTrue(path.EndsWith(ModuleScope.DEFAULT_FILE_NAME));
4554
}
4655
}
47-
}
4856

49-
#endif
57+
#elif NET9_0_OR_GREATER
58+
59+
[TestFixture]
60+
[FixtureLifeCycle(LifeCycle.InstancePerTestCase)]
61+
public class PersistentProxyBuilderTestCase
62+
{
63+
// The above fixture life cycle gives each test its own fresh fixture, such that each test.
64+
// starts with an empty `assemblyPaths` list. This is important for parallel test execution
65+
// to work correctly.
66+
private List<string> assemblyPaths = new();
67+
68+
[SetUp]
69+
public void EnsureNoAssembliesSaved()
70+
{
71+
Assume.That(assemblyPaths, Is.Empty, "Test did not start with an empty assembly paths list.");
72+
}
73+
74+
[TearDown]
75+
public void DeleteSavedAssemblies()
76+
{
77+
foreach (var assemblyPath in assemblyPaths)
78+
{
79+
File.Delete(assemblyPath);
80+
}
81+
}
82+
83+
[Test]
84+
public void SavesOneAssemblyPerProxiedType()
85+
{
86+
87+
var builder = new PersistentProxyBuilder();
88+
builder.AssemblySaved += assemblyPaths.Add;
89+
90+
var oneProxyType = builder.CreateInterfaceProxyTypeWithoutTarget(typeof(IOne), Type.EmptyTypes, ProxyGenerationOptions.Default);
91+
92+
Assert.AreEqual(1, assemblyPaths.Count);
93+
94+
var twoProxyType = builder.CreateInterfaceProxyTypeWithoutTarget(typeof(ITwo), Type.EmptyTypes, ProxyGenerationOptions.Default);
95+
96+
Assert.AreEqual(2, assemblyPaths.Count);
97+
98+
InspectAssemblies(assemblyPaths, assemblies =>
99+
{
100+
var oneAssembly = assemblies[0];
101+
Assert.NotNull(oneAssembly.GetType(oneProxyType.FullName));
102+
Assert.Null(oneAssembly.GetType(twoProxyType.FullName));
103+
104+
var twoAssembly = assemblies[1];
105+
Assert.Null(twoAssembly.GetType(oneProxyType.FullName));
106+
Assert.NotNull(twoAssembly.GetType(twoProxyType.FullName));
107+
});
108+
}
109+
110+
[Test]
111+
public void TypeCacheWorks()
112+
{
113+
Assume.That(assemblyPaths, Is.Empty, "Test did not start with an empty assembly paths list.");
114+
115+
var builder = new PersistentProxyBuilder();
116+
builder.AssemblySaved += assemblyPaths.Add;
117+
118+
var proxyType1 = builder.CreateClassProxyType(typeof(object), Type.EmptyTypes, ProxyGenerationOptions.Default);
119+
var proxyType2 = builder.CreateClassProxyType(typeof(object), Type.EmptyTypes, ProxyGenerationOptions.Default);
120+
121+
Assert.AreEqual(1, assemblyPaths.Count);
122+
Assert.AreSame(proxyType1, proxyType2);
123+
Assert.AreSame(proxyType1.Assembly, proxyType2.Assembly);
124+
}
125+
126+
private void InspectAssemblies(IEnumerable<string> assemblyPaths, Action<List<Assembly>> inspect)
127+
{
128+
var alcs = new List<AssemblyLoadContext>();
129+
try
130+
{
131+
var assemblies = new List<Assembly>();
132+
foreach (var assemblyPath in assemblyPaths)
133+
{
134+
// All assemblies have the same name, so they require their own load contexts.
135+
var alc = new AssemblyLoadContext(null, isCollectible: true);
136+
alcs.Add(alc);
137+
138+
// Not using `alc.LoadFromAssemblyPath` because that could lock the files,
139+
// preventing their deletion during test tear-down.
140+
using var assemblyByteStream = new MemoryStream(File.ReadAllBytes(assemblyPath));
141+
var assembly = alc.LoadFromStream(assemblyByteStream);
142+
assemblies.Add(assembly);
143+
}
144+
145+
inspect(assemblies);
146+
}
147+
finally
148+
{
149+
foreach (var alc in alcs)
150+
{
151+
alc.Unload();
152+
}
153+
}
154+
}
155+
}
156+
157+
#endif
158+
159+
}

src/Castle.Core/DynamicProxy/PersistentIsolatedTypesModuleScope.cs

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -54,6 +54,9 @@ public PersistentIsolatedTypesModuleScope(bool savePhysicalAssemblies = true,
5454
usingStrongNamedModule = false;
5555
}
5656

57+
// for testing purposes
58+
internal event Action<string>? AssemblySaved;
59+
5760
internal override AssemblyBuilder CreateAssembly(bool signStrongName)
5861
{
5962
var assemblyName = GetAssemblyName(signStrongName);
@@ -116,6 +119,8 @@ internal override Type BuildType(TypeBuilder typeBuilder)
116119
stream.Seek(0, SeekOrigin.Begin);
117120
stream.CopyTo(file);
118121
file.Close();
122+
123+
AssemblySaved?.Invoke(assemblyPath);
119124
}
120125

121126
stream.Seek(0, SeekOrigin.Begin);

src/Castle.Core/DynamicProxy/PersistentProxyBuilder.cs

Lines changed: 16 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -14,6 +14,7 @@
1414

1515
#nullable enable
1616

17+
using System;
1718
using System.Diagnostics.CodeAnalysis;
1819

1920
namespace Castle.DynamicProxy
@@ -81,6 +82,21 @@ public PersistentProxyBuilder(string? outputDirectory = null)
8182
: base(new PersistentIsolatedTypesModuleScope(savePhysicalAssemblies: true, outputDirectory))
8283
{
8384
}
85+
86+
// for testing purposes
87+
internal event Action<string>? AssemblySaved
88+
{
89+
add
90+
{
91+
var moduleScope = (PersistentIsolatedTypesModuleScope)ModuleScope;
92+
moduleScope.AssemblySaved += value;
93+
}
94+
remove
95+
{
96+
var moduleScope = (PersistentIsolatedTypesModuleScope)ModuleScope;
97+
moduleScope.AssemblySaved -= value;
98+
}
99+
}
84100
}
85101

86102
#endif

0 commit comments

Comments
 (0)