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