fix(scriptanalysis): memoise ScriptTrustValidator verdicts by code hash for the default reference set (review 2026-10-21 04/P3)
This commit is contained in:
@@ -0,0 +1,66 @@
|
||||
using ZB.MOM.WW.ScadaBridge.ScriptAnalysis;
|
||||
|
||||
namespace ZB.MOM.WW.ScadaBridge.ScriptAnalysis.Tests;
|
||||
|
||||
/// <summary>
|
||||
/// Review 2026-10-21 04/P3: <see cref="ScriptTrustValidator.FindViolations(string, IEnumerable{Microsoft.CodeAnalysis.MetadataReference})"/>
|
||||
/// is memoised by code hash for the default (policy) reference set, so the bundle
|
||||
/// importer's preview and apply passes — which call it with no change at their call
|
||||
/// sites — do not redo a full-framework semantic analysis per script. A cache hit
|
||||
/// returns the very same verdict instance, which is what these tests observe.
|
||||
/// </summary>
|
||||
public class ScriptTrustValidatorCacheTests
|
||||
{
|
||||
/// <summary>Verifies that the same violating code analysed twice is served from the cache.</summary>
|
||||
[Fact]
|
||||
public void FindViolations_SameCodeTwice_SecondCallIsCacheHit()
|
||||
{
|
||||
var code = $"var x = System.IO.File.Exists(\"{Guid.NewGuid()}\");";
|
||||
var first = ScriptTrustValidator.FindViolations(code);
|
||||
var second = ScriptTrustValidator.FindViolations(code);
|
||||
|
||||
Assert.NotEmpty(second);
|
||||
Assert.Same(first, second); // pre-fix: a fresh list per call
|
||||
}
|
||||
|
||||
/// <summary>Verifies that a clean verdict is cached too.</summary>
|
||||
[Fact]
|
||||
public void FindViolations_CleanCodeTwice_SecondCallIsCacheHit()
|
||||
{
|
||||
var code = $"var x = \"{Guid.NewGuid()}\".Length;";
|
||||
var first = ScriptTrustValidator.FindViolations(code);
|
||||
var second = ScriptTrustValidator.FindViolations(code);
|
||||
|
||||
Assert.Empty(second);
|
||||
Assert.Same(first, second);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Verifies that a call with extra references is never served from (or stored in)
|
||||
/// the cache — extra references can change symbol resolution, so the verdict is a
|
||||
/// pure function of the code only for the default reference set.
|
||||
/// </summary>
|
||||
[Fact]
|
||||
public void FindViolations_WithExtraReferences_BypassesCache()
|
||||
{
|
||||
var code = $"var x = System.IO.File.Exists(\"{Guid.NewGuid()}\");";
|
||||
var extra = new[] { Microsoft.CodeAnalysis.MetadataReference.CreateFromFile(typeof(ScriptCompileSurface).Assembly.Location) };
|
||||
var a = ScriptTrustValidator.FindViolations(code, extra);
|
||||
var b = ScriptTrustValidator.FindViolations(code, extra);
|
||||
var cached = ScriptTrustValidator.FindViolations(code);
|
||||
|
||||
Assert.NotSame(a, b);
|
||||
Assert.NotSame(a, cached);
|
||||
Assert.NotSame(b, cached);
|
||||
}
|
||||
|
||||
/// <summary>Verifies that the shared verdict cannot be mutated by a caller to poison the cache.</summary>
|
||||
[Fact]
|
||||
public void FindViolations_ReturnedList_IsReadOnly()
|
||||
{
|
||||
var code = $"var x = System.IO.File.Exists(\"{Guid.NewGuid()}\");";
|
||||
var verdict = ScriptTrustValidator.FindViolations(code);
|
||||
Assert.False(verdict is List<string>);
|
||||
Assert.True(verdict is System.Collections.Generic.ICollection<string> { IsReadOnly: true });
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user