1+ /*
2+ * Licensed to the Apache Software Foundation (ASF) under one or more
3+ * contributor license agreements. See the NOTICE file distributed with
4+ * this work for additional information regarding copyright ownership.
5+ * The ASF licenses this file to You under the Apache License, Version 2.0
6+ * (the "License"); you may not use this file except in compliance with
7+ * the License. You may obtain a copy of the License at
8+ *
9+ * http://www.apache.org/licenses/LICENSE-2.0
10+ *
11+ * Unless required by applicable law or agreed to in writing, software
12+ * distributed under the License is distributed on an "AS IS" BASIS,
13+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
14+ * See the License for the specific language governing permissions and
15+ * limitations under the License.
16+ *
17+ */
18+
19+ package net .bytebuddy .agent .builder ;
20+
21+ import org .junit .Test ;
22+ import org .junit .Assert ;
23+
24+ import java .lang .reflect .Field ;
25+ import java .lang .reflect .Method ;
26+ import java .net .URLClassLoader ;
27+ import java .net .URL ;
28+ import java .util .Map ;
29+
30+ /**
31+ * Tests the behavior of the WeakHashMap caching mechanism in SWDescriptionStrategy.
32+ */
33+ public class SWDescriptionStrategyCacheTest {
34+
35+ @ Test
36+ public void testWeakHashMapCacheCleanup () throws Exception {
37+ // Get static cache field
38+ Field cacheField = SWDescriptionStrategy .SWTypeDescriptionWrapper .class
39+ .getDeclaredField ("CLASS_LOADER_TYPE_CACHE" );
40+ cacheField .setAccessible (true );
41+ @ SuppressWarnings ("unchecked" )
42+ Map <ClassLoader , Map <String , SWDescriptionStrategy .TypeCache >> cache =
43+ (Map <ClassLoader , Map <String , SWDescriptionStrategy .TypeCache >>) cacheField .get (null );
44+
45+ // Record initial cache size
46+ int initialCacheSize = cache .size ();
47+
48+ // Create test ClassLoader
49+ URLClassLoader testClassLoader = new URLClassLoader (new URL [0 ], null );
50+ String testTypeName = "com.test.DynamicClass" ;
51+
52+ // Create SWTypeDescriptionWrapper instance
53+ SWDescriptionStrategy .SWTypeDescriptionWrapper wrapper =
54+ new SWDescriptionStrategy .SWTypeDescriptionWrapper (
55+ null , "test" , testClassLoader , testTypeName );
56+
57+ // Call getTypeCache method via reflection to trigger caching
58+ Method getTypeCacheMethod = wrapper .getClass ()
59+ .getDeclaredMethod ("getTypeCache" );
60+ getTypeCacheMethod .setAccessible (true );
61+ SWDescriptionStrategy .TypeCache typeCache =
62+ (SWDescriptionStrategy .TypeCache ) getTypeCacheMethod .invoke (wrapper );
63+
64+ // Verify that the ClassLoader exists in cache
65+ Assert .assertTrue ("Cache should contain the test ClassLoader" ,
66+ cache .containsKey (testClassLoader ));
67+ Assert .assertNotNull ("TypeCache should be created" , typeCache );
68+ Assert .assertEquals ("Cache size should increase by 1" ,
69+ initialCacheSize + 1 , cache .size ());
70+
71+ // Clear ClassLoader references, prepare for GC test
72+ testClassLoader = null ;
73+ wrapper = null ;
74+ typeCache = null ;
75+
76+ // Force garbage collection
77+ System .gc ();
78+ Thread .sleep (100 );
79+ System .gc ();
80+ Thread .sleep (100 );
81+
82+ // WeakHashMap should automatically clean up garbage collected ClassLoader entries
83+ int attempts = 0 ;
84+ int currentCacheSize = cache .size ();
85+ while (currentCacheSize > initialCacheSize && attempts < 20 ) {
86+ System .gc ();
87+ Thread .sleep (50 );
88+ currentCacheSize = cache .size ();
89+ attempts ++;
90+ }
91+
92+ System .out .println ("Cache size after GC: " + currentCacheSize +
93+ " (initial: " + initialCacheSize + ", attempts: " + attempts + ")" );
94+
95+ // Verify that WeakHashMap cleanup mechanism works properly
96+ Assert .assertTrue ("WeakHashMap should clean up entries or attempts should be reasonable" ,
97+ currentCacheSize <= initialCacheSize + 1 && attempts < 20 );
98+ }
99+
100+ @ Test
101+ public void testBootstrapClassLoaderHandling () throws Exception {
102+ // Get Bootstrap ClassLoader cache field
103+ Field bootstrapCacheField = SWDescriptionStrategy .SWTypeDescriptionWrapper .class
104+ .getDeclaredField ("BOOTSTRAP_TYPE_CACHE" );
105+ bootstrapCacheField .setAccessible (true );
106+ @ SuppressWarnings ("unchecked" )
107+ Map <String , SWDescriptionStrategy .TypeCache > bootstrapCache =
108+ (Map <String , SWDescriptionStrategy .TypeCache >) bootstrapCacheField .get (null );
109+
110+ int initialBootstrapCacheSize = bootstrapCache .size ();
111+
112+ // Test Bootstrap ClassLoader (null) handling
113+ String testTypeName = "test.BootstrapClass" ;
114+ SWDescriptionStrategy .SWTypeDescriptionWrapper wrapper =
115+ new SWDescriptionStrategy .SWTypeDescriptionWrapper (
116+ null , "test" , null , testTypeName );
117+
118+ // Call getTypeCache method via reflection
119+ Method getTypeCacheMethod = wrapper .getClass ()
120+ .getDeclaredMethod ("getTypeCache" );
121+ getTypeCacheMethod .setAccessible (true );
122+ SWDescriptionStrategy .TypeCache typeCache =
123+ (SWDescriptionStrategy .TypeCache ) getTypeCacheMethod .invoke (wrapper );
124+
125+ // Verify Bootstrap ClassLoader cache handling
126+ Assert .assertNotNull ("TypeCache should be created for bootstrap classloader" , typeCache );
127+ Assert .assertTrue ("Bootstrap cache should contain the type" ,
128+ bootstrapCache .containsKey (testTypeName ));
129+ Assert .assertEquals ("Bootstrap cache size should increase by 1" ,
130+ initialBootstrapCacheSize + 1 , bootstrapCache .size ());
131+ }
132+
133+ @ Test
134+ public void testMultipleClassLoadersIndependentCache () throws Exception {
135+ Field cacheField = SWDescriptionStrategy .SWTypeDescriptionWrapper .class
136+ .getDeclaredField ("CLASS_LOADER_TYPE_CACHE" );
137+ cacheField .setAccessible (true );
138+ @ SuppressWarnings ("unchecked" )
139+ Map <ClassLoader , Map <String , SWDescriptionStrategy .TypeCache >> cache =
140+ (Map <ClassLoader , Map <String , SWDescriptionStrategy .TypeCache >>) cacheField .get (null );
141+
142+ int initialCacheSize = cache .size ();
143+
144+ // Create two different ClassLoaders
145+ URLClassLoader classLoader1 = new URLClassLoader (new URL [0 ], null );
146+ URLClassLoader classLoader2 = new URLClassLoader (new URL [0 ], null );
147+ String testTypeName = "com.test.SameClassName" ;
148+
149+ // Create TypeCache with same class name for both ClassLoaders
150+ SWDescriptionStrategy .SWTypeDescriptionWrapper wrapper1 =
151+ new SWDescriptionStrategy .SWTypeDescriptionWrapper (
152+ null , "test" , classLoader1 , testTypeName );
153+ SWDescriptionStrategy .SWTypeDescriptionWrapper wrapper2 =
154+ new SWDescriptionStrategy .SWTypeDescriptionWrapper (
155+ null , "test" , classLoader2 , testTypeName );
156+
157+ // Call getTypeCache method via reflection
158+ Method getTypeCacheMethod =
159+ SWDescriptionStrategy .SWTypeDescriptionWrapper .class .getDeclaredMethod ("getTypeCache" );
160+ getTypeCacheMethod .setAccessible (true );
161+
162+ SWDescriptionStrategy .TypeCache typeCache1 =
163+ (SWDescriptionStrategy .TypeCache ) getTypeCacheMethod .invoke (wrapper1 );
164+ SWDescriptionStrategy .TypeCache typeCache2 =
165+ (SWDescriptionStrategy .TypeCache ) getTypeCacheMethod .invoke (wrapper2 );
166+
167+ // Verify that the two ClassLoaders have independent cache entries
168+ Assert .assertNotNull ("TypeCache1 should be created" , typeCache1 );
169+ Assert .assertNotNull ("TypeCache2 should be created" , typeCache2 );
170+ Assert .assertNotSame ("TypeCaches should be different instances" , typeCache1 , typeCache2 );
171+
172+ // Verify cache structure
173+ Assert .assertEquals ("Cache should contain both classloaders" ,
174+ initialCacheSize + 2 , cache .size ());
175+ Assert .assertTrue ("Cache should contain classloader1" , cache .containsKey (classLoader1 ));
176+ Assert .assertTrue ("Cache should contain classloader2" , cache .containsKey (classLoader2 ));
177+
178+ // Verify each ClassLoader has independent type cache
179+ Map <String , SWDescriptionStrategy .TypeCache > typeCacheMap1 = cache .get (classLoader1 );
180+ Map <String , SWDescriptionStrategy .TypeCache > typeCacheMap2 = cache .get (classLoader2 );
181+
182+ Assert .assertNotNull ("ClassLoader1 should have type cache map" , typeCacheMap1 );
183+ Assert .assertNotNull ("ClassLoader2 should have type cache map" , typeCacheMap2 );
184+ Assert .assertNotSame ("Type cache maps should be different" , typeCacheMap1 , typeCacheMap2 );
185+
186+ Assert .assertTrue ("ClassLoader1 cache should contain the type" ,
187+ typeCacheMap1 .containsKey (testTypeName ));
188+ Assert .assertTrue ("ClassLoader2 cache should contain the type" ,
189+ typeCacheMap2 .containsKey (testTypeName ));
190+ }
191+
192+ @ Test
193+ public void testConcurrentAccess () throws Exception {
194+ // Test concurrent access scenario
195+ final String testTypeName = "com.test.ConcurrentClass" ;
196+ final int threadCount = 10 ;
197+ final Thread [] threads = new Thread [threadCount ];
198+ final URLClassLoader [] classLoaders = new URLClassLoader [threadCount ];
199+ final SWDescriptionStrategy .TypeCache [] results = new SWDescriptionStrategy .TypeCache [threadCount ];
200+
201+ // Create multiple threads to access cache simultaneously
202+ for (int i = 0 ; i < threadCount ; i ++) {
203+ final int index = i ;
204+ classLoaders [i ] = new URLClassLoader (new URL [0 ], null );
205+ threads [i ] = new Thread (() -> {
206+ try {
207+ SWDescriptionStrategy .SWTypeDescriptionWrapper wrapper =
208+ new SWDescriptionStrategy .SWTypeDescriptionWrapper (
209+ null , "test" , classLoaders [index ], testTypeName );
210+
211+ Method getTypeCacheMethod = wrapper .getClass ()
212+ .getDeclaredMethod ("getTypeCache" );
213+ getTypeCacheMethod .setAccessible (true );
214+ results [index ] = (SWDescriptionStrategy .TypeCache )
215+ getTypeCacheMethod .invoke (wrapper );
216+ } catch (Exception e ) {
217+ Assert .fail ("Concurrent access should not throw exception: " + e .getMessage ());
218+ }
219+ });
220+ }
221+
222+ // Start all threads
223+ for (Thread thread : threads ) {
224+ thread .start ();
225+ }
226+
227+ // Wait for all threads to complete
228+ for (Thread thread : threads ) {
229+ thread .join (1000 ); // Wait at most 1 second
230+ }
231+
232+ // Verify all results
233+ for (int i = 0 ; i < threadCount ; i ++) {
234+ Assert .assertNotNull ("Result " + i + " should not be null" , results [i ]);
235+ }
236+
237+ System .out .println ("Concurrent access test completed successfully with " + threadCount + " threads" );
238+ }
239+ }
0 commit comments