1 // Licensed to the .NET Foundation under one or more agreements. 2 // The .NET Foundation licenses this file to you under the MIT license. 3 // See the LICENSE file in the project root for more information. 4 5 using Xunit; 6 using System; 7 using System.Collections.Generic; 8 using System.IO; 9 using System.Linq; 10 using System.Reflection; 11 using System.Reflection.Emit; 12 using System.Threading.Tasks; 13 14 namespace System.Runtime.Loader.Tests 15 { 16 public class RefEmitLoadContext : AssemblyLoadContext 17 { Load(AssemblyName assemblyName)18 protected override Assembly Load(AssemblyName assemblyName) 19 { 20 // We implement Load override since the assembly being loaded (this assembly) is already loaded in the DefaultContext 21 // and we want to load a different copy of it in the custom load context. 22 var loadPath = Path.Combine(RefEmitLoadContextTests.s_loadFromPath, assemblyName.Name+".dll"); 23 Assembly loadedAssembly = null; 24 if (File.Exists(loadPath)) 25 { 26 loadedAssembly = LoadFromAssemblyPath(loadPath); 27 } 28 29 return loadedAssembly; 30 } 31 } 32 33 [SkipOnTargetFramework(TargetFrameworkMonikers.UapAot, "AssemblyLoadContext not supported on .Net Native")] 34 public class RefEmitLoadContextTests 35 { 36 public static string s_loadFromPath = null; 37 Init()38 private static void Init() 39 { 40 var assemblyFilename = "System.Runtime.Loader.Noop.Assembly.dll"; 41 42 // Form the dynamic path that would not collide if another instance of this test is running. 43 s_loadFromPath = Path.Combine(Path.GetTempPath(), Guid.NewGuid().ToString()); 44 45 // Create the folder 46 Directory.CreateDirectory(s_loadFromPath); 47 48 var targetPath = Path.Combine(s_loadFromPath, assemblyFilename); 49 50 // Rename the file local to the test folder. 51 var sourcePath = Path.Combine(Directory.GetCurrentDirectory(),assemblyFilename); 52 53 // Finally, copy the file to the temp location from where we expect to load it 54 File.Copy(sourcePath, targetPath); 55 56 // Copy the current assembly to the target location as well since we will load it in the custom load context via the 57 // RefEmitted assembly. 58 var asmCurrentAssembly = typeof(RefEmitLoadContext).GetTypeInfo().Assembly.GetName(); 59 var pathCurrentAssembly = typeof(RefEmitLoadContext).GetTypeInfo().Assembly.Location; 60 61 targetPath = Path.Combine(s_loadFromPath, asmCurrentAssembly.Name+".dll"); 62 63 File.Copy(pathCurrentAssembly, targetPath); 64 } 65 DeleteDirectory()66 private static void DeleteDirectory() 67 { 68 try { Directory.Delete(s_loadFromPath, recursive: true); } 69 catch { } 70 } 71 72 [Fact] LoadRefEmitAssembly()73 public static void LoadRefEmitAssembly() 74 { 75 Init(); 76 77 // Scenario 1 - Generate a non-collectible dynamic assembly that triggers load of a static assembly 78 RefEmitLoadContext refEmitLCRun = new RefEmitLoadContext(); 79 LoadRefEmitAssemblyInLoadContext(refEmitLCRun, AssemblyBuilderAccess.Run); 80 81 // Scenario 2 - Generate a collectible dynamic assembly that triggers load of a static assembly 82 RefEmitLoadContext refEmitLCRunAndCollect = new RefEmitLoadContext(); 83 LoadRefEmitAssemblyInLoadContext(refEmitLCRunAndCollect, AssemblyBuilderAccess.RunAndCollect); 84 DeleteDirectory(); 85 } 86 LoadRefEmitAssemblyInLoadContext(AssemblyLoadContext loadContext, AssemblyBuilderAccess builderType)87 public static void LoadRefEmitAssemblyInLoadContext(AssemblyLoadContext loadContext, AssemblyBuilderAccess builderType) 88 { 89 // Load this assembly in custom LoadContext 90 var assemblyNameStr = "System.Runtime.Loader.Noop.Assembly.dll"; 91 92 // Load the assembly in the specified load context 93 var asmTargetAsm = loadContext.LoadFromAssemblyPath(Path.Combine(s_loadFromPath, assemblyNameStr)); 94 var creatorLoadContext = AssemblyLoadContext.GetLoadContext(asmTargetAsm); 95 Assert.Equal(loadContext, creatorLoadContext); 96 97 // Get reference to the helper method that will RefEmit an assembly and return reference to it. 98 Type type = asmTargetAsm.GetType("System.Runtime.Loader.Tests.TestClass"); 99 var method = System.Reflection.TypeExtensions.GetMethod(type, "GetRefEmitAssembly"); 100 101 // Use the helper to generate an assembly 102 var assemblyNameRefEmit = "RefEmitTestAssembly"; 103 var asmRefEmitLoaded = (Assembly)method.Invoke(null, new object[] {assemblyNameRefEmit, builderType}); 104 Assert.NotNull(asmRefEmitLoaded); 105 106 // Assert that Dynamically emitted assemblies load context is the same as that of the assembly 107 // that created them. 108 var loadContextRefEmitAssembly = AssemblyLoadContext.GetLoadContext(asmRefEmitLoaded); 109 Assert.Equal(creatorLoadContext, loadContextRefEmitAssembly); 110 111 // Invoke the method that will trigger a static load in the dynamically generated assembly. 112 Type typeRefEmit = asmRefEmitLoaded.GetType("RefEmitTestType"); 113 method = System.Reflection.TypeExtensions.GetMethod(typeRefEmit, "LoadStaticAssembly"); 114 Assert.NotNull(method); 115 116 // Invoke the method to load the current assembly from the temp location 117 var assemblyStaticToLoad = typeof(RefEmitLoadContext).GetTypeInfo().Assembly.GetName().Name; 118 var asmRefEmitLoadedStatic = method.Invoke(null, new object[] {assemblyStaticToLoad}); 119 Assert.NotNull(asmRefEmitLoadedStatic); 120 121 // Load context of the statically loaded assembly is the custom load context in which dynamic assembly was created 122 Assert.Equal(loadContextRefEmitAssembly, AssemblyLoadContext.GetLoadContext((Assembly)asmRefEmitLoadedStatic)); 123 124 // Enumerate the assemblies in the AppDomain and confirm that the Dynamically generated assembly is present. 125 var loadedAssemblies = AppDomain.CurrentDomain.GetAssemblies(); 126 bool fDynamicAssemblyFound = false; 127 foreach (Assembly asm in loadedAssemblies) 128 { 129 if (asmRefEmitLoaded == asm) 130 { 131 if (asm.FullName == asmRefEmitLoaded.FullName) 132 { 133 fDynamicAssemblyFound = true; 134 break; 135 } 136 } 137 } 138 139 Assert.Equal(true, fDynamicAssemblyFound); 140 } 141 } 142 } 143