93 lines
3.7 KiB
C#
93 lines
3.7 KiB
C#
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);
|
|
}
|
|
}
|