XFE Git
XFE Studio Git
Git 首页 全局搜索
XFE 主站 文档 NuGet

XFESystem

【Rust】XFE系统,基于Rust和汇编

公开
关注 0 Fork 0 Star 0
UTF-8
namespace XFESystem.Browser.Tests;

public sealed class PreparedScriptCacheTests
{
    private static string Padded(string source) => source + "\n/*" + new string(' ', 1_024) + "*/";

    [Fact]
    public void PackagedSyntaxReusesAcrossFreshEnginesWithoutSharingGlobalsOrResults()
    {
        var cache = new PreparedScriptCache();
        string source = Padded("globalThis.counter=(globalThis.counter||0)+1;globalThis.created={value:counter};counter;");
        var first = new JavaScriptRuntime(null, cache);
        Assert.Equal("1", first.EvaluatePackaged(source, "packaged/shared.js").Value);
        Assert.Equal("2", first.EvaluatePackaged(source, "packaged/shared.js").Value);
        Assert.Equal(1, first.PackagedScriptPreparations);
        Assert.Equal(1, first.PackagedScriptCacheHits);
        var second = new JavaScriptRuntime(null, cache);
        Assert.Equal("1", second.EvaluatePackaged(source, "packaged/shared.js").Value);
        Assert.Equal(1, second.PackagedScriptCacheHits);
        Assert.Equal("99", first.Evaluate("created.value=99").Value);
        Assert.Equal("1", second.Evaluate("created.value").Value);
    }

    [Fact]
    public void ChangedSourceAtSameAddressIsReparsedAndOldSyntaxIsReplaced()
    {
        var cache = new PreparedScriptCache();
        var runtime = new JavaScriptRuntime(null, cache);
        Assert.Equal("41", runtime.EvaluatePackaged(Padded("41"), "packaged/same.js").Value);
        Assert.Equal("42", runtime.EvaluatePackaged(Padded("42"), "packaged/same.js").Value);
        Assert.Equal(0, runtime.PackagedScriptCacheHits);
        Assert.Equal(2, runtime.PackagedScriptPreparations);
        Assert.Equal(1, cache.Snapshot.Entries);
    }

    [Fact]
    public void OrdinaryEvaluationDoesNotPopulateOrConsumePackagedCache()
    {
        var cache = new PreparedScriptCache();
        var runtime = new JavaScriptRuntime(null, cache);
        string source = Padded("6*7");
        Assert.Equal("42", runtime.Evaluate(source).Value);
        Assert.Equal("42", runtime.Evaluate(source).Value);
        Assert.Equal((0, 0, 0), cache.Snapshot);
        Assert.Equal(0, runtime.PackagedScriptCacheHits);
        Assert.Equal(0, runtime.PackagedScriptPreparations);
    }

    [Fact]
    public void WarmSyntaxStillChecksSourceSizeAndStatementBudgetOfReceivingEngine()
    {
        var cache = new PreparedScriptCache();
        string source = Padded("for(let i=0;i<1000;i++){};42");
        Assert.Equal("42", new JavaScriptRuntime(null, cache).EvaluatePackaged(source, "packaged/loop.js").Value);
        var limited = new JavaScriptRuntime(new() { MaximumStatements = 100 }, cache);
        var failed = limited.EvaluatePackaged(source, "packaged/loop.js");
        Assert.False(failed.Success); Assert.True(failed.BudgetExceeded); Assert.Equal(1, limited.PackagedScriptCacheHits);
        var small = new JavaScriptRuntime(new() { MaximumScriptCharacters = 100 }, cache);
        failed = small.EvaluatePackaged(source, "packaged/loop.js");
        Assert.False(failed.Success); Assert.True(failed.BudgetExceeded); Assert.Equal(0, small.PackagedScriptCacheHits);
        Assert.Equal("42", limited.Evaluate("6*7").Value);
    }

    [Fact]
    public void WarmSyntaxStillChecksTimeoutAndDisabledStringCompilation()
    {
        var cache = new PreparedScriptCache();
        string source = Padded("for(let i=0;i<500000;i++){};42");
        var generous = new JavaScriptRuntime(new() { MaximumStatements = 100_000_000, ScriptTimeout = TimeSpan.FromSeconds(15) }, cache);
        Assert.Equal("42", generous.EvaluatePackaged(source, "packaged/timeout.js").Value);
        var limited = new JavaScriptRuntime(new() { MaximumStatements = 100_000_000, ScriptTimeout = TimeSpan.FromMilliseconds(1) }, cache);
        var failed = limited.EvaluatePackaged(source, "packaged/timeout.js");
        Assert.False(failed.Success); Assert.True(failed.BudgetExceeded); Assert.Equal(1, limited.PackagedScriptCacheHits);
        source = Padded("eval('42')");
        Assert.False(generous.EvaluatePackaged(source, "packaged/eval.js").Success);
        Assert.False(generous.EvaluatePackaged(source, "packaged/eval.js").Success);
    }

    [Fact]
    public void SyntaxFailureIsNotCachedAndReceivingEngineRecovers()
    {
        var cache = new PreparedScriptCache();
        var runtime = new JavaScriptRuntime(null, cache);
        Assert.False(runtime.EvaluatePackaged(Padded("function {"), "packaged/bad.js").Success);
        Assert.Equal((0, 0, 0), cache.Snapshot);
        Assert.Equal("42", runtime.EvaluatePackaged(Padded("42"), "packaged/bad.js").Value);
        Assert.Equal(1, cache.Snapshot.Entries);
    }

    [Fact]
    public void LeastRecentSyntaxIsEvictedWithinEntryCharacterAndNodeBudgets()
    {
        var cache = new PreparedScriptCache(maximumEntries: 2, maximumCharacters: 3_000, maximumNodes: 100);
        string source = Padded("42");
        cache.GetOrPrepare(source, "a", out bool reused); Assert.False(reused);
        cache.GetOrPrepare(source, "b", out reused); Assert.False(reused);
        cache.GetOrPrepare(source, "a", out reused); Assert.True(reused);
        cache.GetOrPrepare(source, "c", out reused); Assert.False(reused);
        cache.GetOrPrepare(source, "a", out reused); Assert.True(reused);
        cache.GetOrPrepare(source, "b", out reused); Assert.False(reused);
        Assert.InRange(cache.Snapshot.Entries, 0, 2);
        Assert.InRange(cache.Snapshot.Characters, 0, 3_000);
        Assert.InRange(cache.Snapshot.Nodes, 0, 100);
    }

    [Fact]
    public void OversizedSyntaxExecutesWithoutBeingRetainedByCache()
    {
        var cache = new PreparedScriptCache(maximumCharacters: 10, maximumNodes: 1);
        var runtime = new JavaScriptRuntime(null, cache);
        Assert.Equal("42", runtime.EvaluatePackaged(Padded("42"), "large").Value);
        Assert.Equal("42", runtime.EvaluatePackaged(Padded("42"), "large").Value);
        Assert.Equal((0, 0, 0), cache.Snapshot);
        Assert.Equal(0, runtime.PackagedScriptCacheHits);
    }

    [Fact]
    public async Task PreparedClosuresRegularExpressionsAndBoundCallsStayLocalAcrossConcurrentEngines()
    {
        var cache = new PreparedScriptCache();
        string source = Padded("var expression=/x/g;expression.test('xx');var local={count:0};var increment=function(){return ++this.count}.bind(local);var values=[increment(),increment(),expression.lastIndex];values.join(',');");
        var results = await Task.WhenAll(Enumerable.Range(0, 12).Select(_ => Task.Run(() =>
        {
            var runtime = new JavaScriptRuntime(null, cache);
            return runtime.EvaluatePackaged(source, "packaged/concurrent.js");
        })));
        Assert.All(results, result => { Assert.True(result.Success, result.Error); Assert.Equal("1,2,1", result.Value); });
        Assert.Equal(1, cache.Snapshot.Entries);
    }

    [Fact]
    public void WarmSyntaxHonorsMemoryAndRecursionLimitsOfEachReceivingEngine()
    {
        var cache = new PreparedScriptCache();
        var generous = new JavaScriptRuntime(new() { MaximumStatements = 1_000_000, MaximumScriptMemoryBytes = 64_000_000 }, cache);
        string source = Padded("var items=[];for(let i=0;i<20000;i++)items.push({value:i});items.length");
        Assert.Equal("20000", generous.EvaluatePackaged(source, "packaged/memory.js").Value);
        var memory = new JavaScriptRuntime(new() { MaximumStatements = 1_000_000, MaximumScriptMemoryBytes = 100_000 }, cache);
        var failed = memory.EvaluatePackaged(source, "packaged/memory.js");
        Assert.False(failed.Success); Assert.True(failed.BudgetExceeded); Assert.Equal(1, memory.PackagedScriptCacheHits);
        source = Padded("function nested(n){return n?nested(n-1):42};nested(20)");
        Assert.Equal("42", generous.EvaluatePackaged(source, "packaged/recursion.js").Value);
        var recursion = new JavaScriptRuntime(new() { MaximumRecursionDepth = 4 }, cache);
        failed = recursion.EvaluatePackaged(source, "packaged/recursion.js");
        Assert.False(failed.Success); Assert.True(failed.BudgetExceeded); Assert.Equal(1, recursion.PackagedScriptCacheHits);
    }

    [Fact]
    public void NodeBudgetIndependentlyRefusesRetainingAnOtherwiseSmallSource()
    {
        var cache = new PreparedScriptCache(maximumCharacters: 8_192, maximumNodes: 2);
        var runtime = new JavaScriptRuntime(null, cache);
        Assert.Equal("42", runtime.EvaluatePackaged(Padded("6*7"), "node-bound").Value);
        Assert.Equal("42", runtime.EvaluatePackaged(Padded("6*7"), "node-bound").Value);
        Assert.Equal((0, 0, 0), cache.Snapshot);
        Assert.Equal(0, runtime.PackagedScriptCacheHits);
    }
}