chr
2026-04-08 53e656200368a983e563550e2cc1acbc6d86b729
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
using System;
using System.IO;
using System.Linq;
using System.Threading.Tasks;
using Mono.Cecil;
using Mono.Cecil.Cil;
using NUnit.Framework;
using OpenTap;
using OpenTap.Package;
 
namespace Package.UnitTests;
 
[TestFixture]
public class TestConcurrentTypeDataLoad
{
    static System.Reflection.Assembly CreateNewAssemblyWithTestStep(string stepName)
    {
        var asmName = new AssemblyNameDefinition(Guid.NewGuid().ToString(), new Version(1, 0));
        string moduleName = Guid.NewGuid().ToString();
 
        using var asm = AssemblyDefinition.CreateAssembly(asmName, moduleName, ModuleKind.Dll);
 
        var teststep = asm.MainModule.ImportReference(typeof(TestStep));
        var runMethod = asm.MainModule.ImportReference(typeof(TestStep).GetMethod("Run", System.Reflection.BindingFlags.Public | System.Reflection.BindingFlags.Instance)).Resolve();
        var ctorMethod = asm.MainModule.ImportReference(typeof(TestStep).GetConstructor([])).Resolve();
 
        // create dummy plugin
        {
            // Create new test step
            var t = new TypeDefinition(Guid.NewGuid().ToString(), stepName, TypeAttributes.AnsiClass | TypeAttributes.BeforeFieldInit | TypeAttributes.Public, teststep);
            asm.MainModule.Types.Add(t);
 
            // Create default constructor
            {
                var ctor = new MethodDefinition(".ctor", ctorMethod.Attributes, asm.MainModule.TypeSystem.Void);
                var il = ctor.Body.GetILProcessor();
                il.Emit(OpCodes.Ldarg_0);
                il.Emit(OpCodes.Call, t.Module.ImportReference(ctorMethod));
                il.Emit(OpCodes.Ret);
                t.Methods.Add(ctor);
            }
 
            // build Run() method
            {
                var newRunMethod = new MethodDefinition("Run", MethodAttributes.Public, runMethod.ReturnType) { IsHideBySig = true, IsVirtual = true };
                var il = newRunMethod.Body.GetILProcessor();
                il.Emit(OpCodes.Ret);
                t.Methods.Add(newRunMethod);
            }
        }
 
        var path = Path.Combine(Installation.Current.Directory, "TestTypeDataLoadAssemblies", asm.Name.Name + ".dll");
        asm.Write(path);
        return System.Reflection.Assembly.LoadFrom(path);
    }
 
    [TestCase(1)]
    [TestCase(10)]
    [TestCase(100)]
    [TestCase(500)]
    public void TestTypeDataLoad(int count)
    {
        static void VerifyAssemblyData(int index, System.Reflection.Assembly asm)
        {
            var step = asm.GetExportedTypes().First();
            var td = TypeData.FromType(step);
            TestStep instance = (TestStep)td.CreateInstance();
            Assert.That(instance, Is.Not.Null);
            Assert.That(td.GetDisplayAttribute().Name, Is.EqualTo($"Step_{index}"));
        }
 
        var ctx = UninstallContext.Create(Installation.Current);
        var directory = Path.Combine(Installation.Current.Directory, "TestTypeDataLoadAssemblies");
        Directory.CreateDirectory(directory);
 
        // Ensure searching is done before we start creating assemblies
        PluginManager.GetSearcher();
 
        // Create a ton of distinct assemblies and load them
        var assemblies = Enumerable.Range(0, count).Select(i => CreateNewAssemblyWithTestStep($"Step_{i}")).ToArray();
        // Simultaneously attempt to load typedata from each plugin
        Task[] tasks = [.. Enumerable.Range(0, count).Select(i => StartAwaitable(() => VerifyAssemblyData(i, assemblies[i])))];
        Task.WaitAll(tasks);
        // Delete all assemblies and try again -- it should still work
        foreach (var asm in assemblies)
        {
            var rel = Path.GetRelativePath(Installation.Current.Directory, asm.Location);
            ctx.Delete(rel);
        }
        PluginManager.Search();
        tasks = [.. Enumerable.Range(0, count).Select(i => StartAwaitable(() => VerifyAssemblyData(i, assemblies[i])))];
        Task.WaitAll(tasks);
        // This should succeed since all the dlls were uninstalled
        Directory.Delete(directory, false);
    }
 
    internal static Task StartAwaitable(Action action)
    {
        var result = new TaskCompletionSource<bool>();
        TapThread.Start(() =>
        {
            try
            {
                action();
                result.SetResult(true);
            }
            catch (Exception inner)
            {
                result.SetException(inner);
            }
        });
        return result.Task;
    }
}