feat(scripts): add process-wide caching metadata resolver for script compiles
This commit is contained in:
+92
@@ -0,0 +1,92 @@
|
||||
using System.Collections.Immutable;
|
||||
using Microsoft.CodeAnalysis;
|
||||
using Microsoft.CodeAnalysis.Scripting;
|
||||
|
||||
namespace ZB.MOM.WW.ScadaBridge.ScriptAnalysis.Tests;
|
||||
|
||||
/// <summary>
|
||||
/// Pins the per-compile native-metadata fix (2026-08-12 follow-up to 5a781c70):
|
||||
/// Roslyn's default ScriptMetadataResolver re-resolves the transitive assembly
|
||||
/// closure through MetadataReference.CreateFromFile on EVERY script compile —
|
||||
/// each call minting a fresh AssemblyMetadata → PEReader → NativeHeapMemoryBlock.
|
||||
/// The decorator memoizes so each distinct assembly is resolved once per process.
|
||||
/// </summary>
|
||||
public class CachingScriptMetadataResolverTests
|
||||
{
|
||||
private sealed class CountingResolver : MetadataReferenceResolver
|
||||
{
|
||||
public int MissingCalls;
|
||||
public int ReferenceCalls;
|
||||
public override bool ResolveMissingAssemblies => true;
|
||||
|
||||
public override PortableExecutableReference? ResolveMissingAssembly(
|
||||
MetadataReference definition, AssemblyIdentity referenceIdentity)
|
||||
{
|
||||
Interlocked.Increment(ref MissingCalls);
|
||||
return MetadataReference.CreateFromFile(typeof(object).Assembly.Location);
|
||||
}
|
||||
|
||||
public override ImmutableArray<PortableExecutableReference> ResolveReference(
|
||||
string reference, string? baseFilePath, MetadataReferenceProperties properties)
|
||||
{
|
||||
Interlocked.Increment(ref ReferenceCalls);
|
||||
return [MetadataReference.CreateFromFile(typeof(object).Assembly.Location)];
|
||||
}
|
||||
|
||||
public override bool Equals(object? other) => ReferenceEquals(this, other);
|
||||
public override int GetHashCode() => 0;
|
||||
}
|
||||
|
||||
private static readonly AssemblyIdentity SomeIdentity = new("System.Fake", new Version(1, 0, 0, 0));
|
||||
private static readonly MetadataReference SomeDefinition =
|
||||
MetadataReference.CreateFromFile(typeof(object).Assembly.Location);
|
||||
|
||||
[Fact]
|
||||
public void ResolveMissingAssembly_SameIdentityTwice_ResolvesOnceAndSharesInstance()
|
||||
{
|
||||
var inner = new CountingResolver();
|
||||
var sut = new CachingScriptMetadataResolver(inner);
|
||||
|
||||
var first = sut.ResolveMissingAssembly(SomeDefinition, SomeIdentity);
|
||||
var second = sut.ResolveMissingAssembly(SomeDefinition, SomeIdentity);
|
||||
|
||||
Assert.Equal(1, inner.MissingCalls);
|
||||
Assert.Same(first, second); // one AssemblyMetadata/PEReader/native block, not two
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public void ResolveMissingAssembly_DistinctIdentities_ResolveIndependently()
|
||||
{
|
||||
var inner = new CountingResolver();
|
||||
var sut = new CachingScriptMetadataResolver(inner);
|
||||
|
||||
sut.ResolveMissingAssembly(SomeDefinition, SomeIdentity);
|
||||
sut.ResolveMissingAssembly(SomeDefinition, new AssemblyIdentity("System.Other", new Version(1, 0, 0, 0)));
|
||||
|
||||
Assert.Equal(2, inner.MissingCalls);
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public void ResolveReference_SameArgsTwice_ResolvesOnceAndSharesInstances()
|
||||
{
|
||||
var inner = new CountingResolver();
|
||||
var sut = new CachingScriptMetadataResolver(inner);
|
||||
|
||||
var first = sut.ResolveReference("System.Xml", baseFilePath: null, MetadataReferenceProperties.Assembly);
|
||||
var second = sut.ResolveReference("System.Xml", baseFilePath: null, MetadataReferenceProperties.Assembly);
|
||||
|
||||
Assert.Equal(1, inner.ReferenceCalls);
|
||||
Assert.Same(first[0], second[0]);
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public void Instance_IsProcessWideSingleton()
|
||||
=> Assert.Same(CachingScriptMetadataResolver.Instance, CachingScriptMetadataResolver.Instance);
|
||||
|
||||
[Fact]
|
||||
public void ResolveMissingAssemblies_DelegatesToInner()
|
||||
{
|
||||
var sut = new CachingScriptMetadataResolver(new CountingResolver());
|
||||
Assert.True(sut.ResolveMissingAssemblies);
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user