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

XFESystem

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

公开
关注 0 Fork 0 Star 0
UTF-8
using System.Runtime.CompilerServices;
using XFESystem.Collections;

namespace XFESystem.Browser.Tests;

[Collection("Browser lifetime ownership")]
public sealed class WeakCollectionTests
{
    [Fact]
    public void PublicNativeRegressionRunsRealIntrinsicsAndFreesAllOwnedSlots()
    {
        var result=BrowserWeakCollectionsRegression.Run();
        Assert.True(result.Passed,result.Actual);Assert.Equal(result.Expected,result.Actual);
        Assert.Equal(result.AfterRelease.Created,result.AfterRelease.Disposed);
    }
    private sealed record BackReference(object Owner);
    [MethodImpl(MethodImplOptions.NoInlining)]
    private static (WeakReference<object> Key,WeakReference<object> Value) AddDeadCycle(OwnedWeakTable<object,object> table)
    {var key=new object();var value=new BackReference(key);table.Add(key,value);return(new(key),new(value));}
    [MethodImpl(MethodImplOptions.NoInlining)]
    private static (WeakReference<object> Table,WeakReference<object> Value) AddDeadTable(WeakCollectionScope scope,object key)
    {var table=new OwnedWeakTable<object,object>(scope);var value=new object();table.Add(key,value);return(new(table),new(value));}
    [MethodImpl(MethodImplOptions.NoInlining)]
    private static WeakReference<object> AddOwnerCycle(WeakCollectionScope scope,object key)
    {var table=new OwnedWeakTable<object,object>(scope);table.Add(key,new BackReference(table));return new(table);}
    private static void Collect(){GC.Collect();GC.WaitForPendingFinalizers();GC.Collect();}
    [MethodImpl(MethodImplOptions.NoInlining)]
    private static bool HasTarget(WeakReference<object> weak)=>weak.TryGetTarget(out _);

    [Fact]
    public void WeakStoragePrunesDeadCyclesAndDeadTablesWithoutFinalizersOrStrongerRegistryRoots()
    {
        var scope=new WeakCollectionScope();var table=new OwnedWeakTable<object,object>(scope);
        var cycle=AddDeadCycle(table);Collect();
        Assert.False(cycle.Key.TryGetTarget(out _));Assert.False(cycle.Value.TryGetTarget(out _));
        var dead=scope.Prune();Assert.Equal(0,dead.Entries);Assert.Equal(dead.Created,dead.Disposed);
        var key=new object();var abandoned=AddDeadTable(scope,key);Collect();
        Assert.False(HasTarget(abandoned.Table));Assert.True(HasTarget(abandoned.Value));
        var pruned=scope.Prune();Assert.Equal(1,pruned.Tables);Assert.Equal(1,pruned.TableHandlesDisposed);
        Collect();Assert.False(HasTarget(abandoned.Value));
        // Actual .NET CWT likewise keeps owner alive when a rooted key's value
        // refers back to that owner; the registry must not add another such edge.
        var owner=AddOwnerCycle(scope,key);Collect();Assert.True(owner.TryGetTarget(out _));
        scope.Release();scope.Release();var final=scope.Lifetime;
        Assert.Equal(0,final.Tables);Assert.Equal(0,final.Entries);Assert.Equal(final.Created,final.Disposed);
        Assert.Equal(final.TableHandlesCreated,final.TableHandlesDisposed);
        Assert.Throws<ObjectDisposedException>(()=>table.Add(key,new object()));
        Assert.Throws<ObjectDisposedException>(()=>table.AddOrUpdate(key,new object()));
        Assert.Throws<ObjectDisposedException>(()=>table.TryGetValue(key,out _));
        Assert.Throws<ObjectDisposedException>(()=>table.Remove(key));
        Assert.Equal(final,scope.Lifetime);
        GC.KeepAlive(key);GC.KeepAlive(table);
    }

    [Fact]
    public void OriginalWeakIntrinsicsKeepValidationIdentityDescriptorsChainingAndComputedInsert()
    {
        var runtime=new JavaScriptRuntime();
        Assert.NotNull(typeof(Jint.Engine).Assembly.GetType("XFESystem.JintOwnedWeakCollectionsV1"));
        Assert.IsAssignableFrom<IWeakCollectionOwner>((object)runtime.Engine);
        var result=runtime.Evaluate("""
        const k={},v={},m=new WeakMap([[k,v]]),s=new WeakSet([k]);
        let bad=0;try{new WeakMap()}catch(e){bad+=100}try{WeakMap()}catch(e){bad+=e instanceof TypeError?1:100}
        try{m.set(1,2)}catch(e){bad+=e instanceof TypeError?1:100}try{s.add(1)}catch(e){bad+=e instanceof TypeError?1:100}
        const same=m.set(k,v)===m&&m.get(k)===v&&m.has(k)&&s.add(k)===s&&s.has(k);
        const primitive=m.get(4)===undefined&&!m.has(4)&&!m.delete(4)&&!s.has(4)&&!s.delete(4);
        const desc=!Object.getOwnPropertyDescriptor(WeakMap.prototype,'set').enumerable&&Object.getPrototypeOf(m)===WeakMap.prototype;
        m.delete(k);const inserted=m.getOrInsert(k,21)===21&&m.getOrInsert(k,99)===21;
        m.delete(k);const computed=m.getOrInsertComputed(k,()=>{m.set(k,77);return 42})===42&&m.get(k)===42;
        [same,primitive,desc,inserted,computed,bad,s.delete(k),!s.has(k)].join(',')
        """);
        Assert.True(result.Success,result.Error);Assert.Equal("true,true,true,true,true,3,true,true",result.Text);
        runtime.Release();var life=((IWeakCollectionOwner)(object)runtime.Engine).WeakCollections.Lifetime;
        Assert.Equal(life.Created,life.Disposed);Assert.Equal(life.TableHandlesCreated,life.TableHandlesDisposed);
    }

    [Fact]
    public void RepeatedEnginePagesReleaseEveryNativeSlotAndPendingTimer()
    {
        for(int page=0;page<12;page++)
        {
            var runtime=new JavaScriptRuntime();
            Assert.True(runtime.Evaluate("globalThis.map=new WeakMap();globalThis.set=new WeakSet();for(let i=0;i<100;i++){const k={};map.set(k,{key:k});set.add(k)}setInterval(()=>map.has({}),1);'ready'").Success);
            Collect();runtime.Pump(200);var owner=(IWeakCollectionOwner)(object)runtime.Engine;var pruned=owner.WeakCollections.Prune();
            Assert.InRange(pruned.Entries,0,4);Assert.True(pruned.Disposed>=190);
            runtime.Release();var after=owner.WeakCollections.Lifetime;
            Assert.Equal(0,runtime.PendingTaskCount);Assert.Equal(0,after.Tables);Assert.Equal(after.Created,after.Disposed);
            Assert.Equal(after.TableHandlesCreated,after.TableHandlesDisposed);
        }
    }
}