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 JavaScriptRuntimeTests
{
    [Fact]
    public void ModernJavaScriptUsesARealInterpreter()
    {
        var runtime = new JavaScriptRuntime();
        var result = runtime.Evaluate("class Item { constructor(value) { this.value = value; } } const items = [1,2,3].map(x => new Item(x * 2)); items.reduce((sum, x) => sum + x.value, 0)");
        Assert.True(result.Success, result.Error);
        Assert.Equal("12", result.Value);
    }

    [Fact]
    public void ClosuresJsonUnicodeAndTemplatesWork()
    {
        var result = new JavaScriptRuntime().Evaluate("const make = x => y => x + y; const value = JSON.parse('{\"Count\": 40}'); `中文 ${make(value.Count)(2)}`");
        Assert.True(result.Success, result.Error);
        Assert.Equal("中文 42", result.Value);
    }

    [Fact]
    public void ConsoleCallbacksCaptureValuesAndFilterTerminalControlCharacters()
    {
        var result = new JavaScriptRuntime().Evaluate("console.log('Answer', 42); console.warn('Hello\\u001b[31m'); 'done'");
        Assert.True(result.Success, result.Error);
        Assert.Equal("Answer 42\nHello[31m", result.ConsoleOutput);
        Assert.Equal("done", result.Value);
        Assert.DoesNotContain('\u001b', result.Text);
    }

    [Fact]
    public void ScriptStatePersistsOnlyWithinOneRuntime()
    {
        var first = new JavaScriptRuntime();
        Assert.True(first.Evaluate("var count = 10").Success);
        Assert.Equal("11", first.Evaluate("++count").Value);
        Assert.Equal("undefined", new JavaScriptRuntime().Evaluate("typeof count").Value);
    }

    [Theory]
    [InlineData("typeof System", "undefined")]
    [InlineData("typeof importNamespace", "undefined")]
    [InlineData("typeof require", "undefined")]
    [InlineData("typeof fetch", "undefined")]
    [InlineData("typeof process", "undefined")]
    [InlineData("typeof console.GetType", "undefined")]
    public void ScriptsCannotReachClrFilesSocketsOrNativeProcess(string source, string expected)
    {
        var result = new JavaScriptRuntime().Evaluate(source);
        Assert.True(result.Success, result.Error);
        Assert.Equal(expected, result.Value);
    }

    [Theory]
    [InlineData("eval('40 + 2')")]
    [InlineData("new Function('return 42')()")]
    public void DynamicCodeGenerationIsExplicitlyDisabled(string source)
    {
        var result = new JavaScriptRuntime().Evaluate(source);
        Assert.False(result.Success);
        Assert.NotEmpty(result.Error);
    }

    [Fact]
    public void InfiniteLoopsStopAtTheStatementLimitAndTheEngineCanRunAgain()
    {
        var runtime = new JavaScriptRuntime(new BrowserLimits { MaximumStatements = 100 });
        var result = runtime.Evaluate("while (true) { }");
        Assert.False(result.Success);
        Assert.Contains("statement", result.Error, StringComparison.OrdinalIgnoreCase);
        Assert.Equal("42", runtime.Evaluate("6 * 7").Value);
    }

    [Fact]
    public void ExcessiveAllocationFailsAtTheMemoryLimit()
    {
        var runtime = new JavaScriptRuntime(new BrowserLimits
        {
            MaximumScriptMemoryBytes = 1_000_000,
            MaximumStatements = 1_000_000,
            ScriptTimeout = TimeSpan.FromSeconds(2)
        });
        var result = runtime.Evaluate("const storage = []; while (true) storage.push('X'.repeat(1024));");
        Assert.False(result.Success);
        Assert.Contains("allocated", result.Error, StringComparison.OrdinalIgnoreCase);
    }

    [Fact]
    public void RecursiveCallsAndOutputAreBounded()
    {
        var recursion = new JavaScriptRuntime(new BrowserLimits { MaximumRecursionDepth = 16 })
            .Evaluate("function f() { return f(); } f()");
        Assert.False(recursion.Success);
        var output = new JavaScriptRuntime(new BrowserLimits { MaximumOutputCharacters = 40 })
            .Evaluate("console.log('X'.repeat(41))");
        Assert.False(output.Success);
        Assert.True(output.ConsoleOutput.Length <= 40);
    }

    [Fact]
    public void ScriptsResultsAndInvalidLimitsAreRejected()
    {
        var runtime = new JavaScriptRuntime(new BrowserLimits { MaximumScriptCharacters = 10, MaximumOutputCharacters = 8 });
        Assert.False(runtime.Evaluate("12345678901").Success);
        Assert.False(runtime.Evaluate("'123456789'").Success);
        Assert.Throws<ArgumentOutOfRangeException>(() => new JavaScriptRuntime(new BrowserLimits { MaximumStatements = 0 }));
    }

    [Fact]
    public void ResultConversionDoesNotInvokeAnUnboundedUserToStringCallback()
    {
        var result = new JavaScriptRuntime().Evaluate("({toString() { while(true) {} }})");
        Assert.True(result.Success, result.Error);
        Assert.Equal("[object Object]", result.Value);
    }

    [Fact]
    public void AtomicsCannotBlockTheHostThreadOutsideStatementTimeoutChecks()
    {
        var result = new JavaScriptRuntime().Evaluate("const shared = new Int32Array(new SharedArrayBuffer(4)); Atomics.wait(shared, 0, 0)");
        Assert.False(result.Success);
        Assert.Contains("Atomics.wait cannot be used", result.Error, StringComparison.Ordinal);
    }

    [Fact]
    public void BudgetClassificationUsesExceptionKindsRatherThanUserErrorWords()
    {
        var runtime = new JavaScriptRuntime(new() { MaximumStatements = 100 });
        Assert.True(runtime.Evaluate("while(true){}").BudgetExceeded);
        Assert.False(runtime.Evaluate("throw Error('statement memory timeout recursion')").BudgetExceeded);
        Assert.False(runtime.Evaluate("nonexistentFunction()").BudgetExceeded);
        Assert.True(new JavaScriptRuntime(new() { MaximumScriptCharacters = 10 }).Evaluate("12345678901").BudgetExceeded);
    }

    [Fact]
    public void PatchedProofChecksTheAssemblyActuallyLoadedByTheTestHost()
    {
        var expected = Environment.GetEnvironmentVariable("XFE_EXPECT_JINT_SHA");
        if (string.IsNullOrEmpty(expected)) return;
        var path = typeof(Jint.Engine).Assembly.Location;
        var actual = Convert.ToHexStringLower(System.Security.Cryptography.SHA256.HashData(File.ReadAllBytes(path)));
        Assert.Equal(expected, actual);
        Assert.NotNull(typeof(Jint.Engine).Assembly.GetType("XFESystem.NativeJintCleanupPatchV2"));
    }
}