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.
1212// See the License for the specific language governing permissions and
1313// limitations under the License.
1414
15- #if FEATURE_ASSEMBLYBUILDER_SAVE
16-
1715namespace 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+ }
0 commit comments