using System.Runtime.CompilerServices; using XFESystem.Collections; namespace XFESystem.Browser.Tests; [Collection("Browser lifetime ownership")] public sealed class EngineWeakCacheTests { [Fact] public void ShapeFactoryMatchesOriginalConditionalWeakTableReentrancyNullAndFailureSemantics() { var original=new ConditionalWeakTable();var scope=new WeakCollectionScope(); var owned=new OwnedWeakTable(scope);var key=new object();var existing=new object();var discarded=new object(); Assert.Same(existing,original.GetValue(key,k=>{original.Add(k,existing);return discarded;})); Assert.Same(existing,owned.GetValue(key,k=>{owned.Add(k,existing);return discarded;})); Assert.Throws(()=>original.GetValue(key,null!)); Assert.Throws(()=>owned.GetValue(key,null!)); Assert.Equal(Assert.Throws(()=>original.GetValue(null!,null!)).ParamName, Assert.Throws(()=>owned.GetValue(null!,null!)).ParamName); Assert.Throws(()=>original.GetValue(null!,_=>new object())); Assert.Throws(()=>owned.GetValue(null!,_=>new object())); var nullKey=new object();Assert.Null(original.GetValue(nullKey,_=>null!));Assert.Null(owned.GetValue(nullKey,_=>null!)); Assert.True(original.TryGetValue(nullKey,out var nullOriginal));Assert.Null(nullOriginal); Assert.True(owned.TryGetValue(nullKey,out var nullOwned));Assert.Null(nullOwned); int calls=0;Assert.Null(owned.GetValue(nullKey,_=>{calls++;return new();}));Assert.Equal(0,calls); var failedKey=new object();var known=new InvalidOperationException("factory failed"); Assert.Same(known,Assert.Throws(()=>owned.GetValue(failedKey,_=>throw known))); Assert.False(owned.TryGetValue(failedKey,out _)); Assert.Same(existing,owned.GetOrCreateValue(failedKey,()=>{owned.Add(failedKey,existing);return discarded;})); scope.Release();var released=scope.Lifetime; Assert.Throws(()=>owned.GetValue(key,_=>new())); Assert.Throws(()=>owned.GetOrCreateValue(key,()=>new())); Assert.Equal(released,scope.Lifetime);Assert.Equal(released.Created,released.Disposed); } [Fact] public void ActualShapeAndPrivateClassIntrinsicsReleaseEveryOwnedCacheWithoutGarbageCollection() { for(int iteration=0;iteration<4;iteration++) { var result=BrowserWeakCollectionsRegression.Run(); Assert.True(result.Passed,System.Text.Json.JsonSerializer.Serialize(result));Assert.True(result.EngineCachesExercised); Assert.True(result.ConstructorExceptionPreserved);Assert.True(result.ConstructorFailureReleased); Assert.Equal(result.ConstructorAfterRelease.Created,result.ConstructorAfterRelease.Disposed); Assert.Equal(result.ConstructorAfterRelease.TableHandlesCreated,result.ConstructorAfterRelease.TableHandlesDisposed); Assert.InRange(result.BeforeRelease.Created,130,long.MaxValue); } } [Fact] public void OriginalMonacoShapeCacheIsIncludedInExplicitPageRelease() { var browser=new BrowserService();browser.ResizeViewport(796,404); browser.NavigateEditor("using System;\nConsole.WriteLine(\"XFESystem\");\n","Hello.cs"); for(int step=0;step<4;step++)browser.AdvanceTime(step*100); Assert.Contains("XFESystem",browser.ReadEditorText(),StringComparison.Ordinal); var runtime=(JavaScriptRuntime)typeof(BrowserService).GetField("runtime",System.Reflection.BindingFlags.NonPublic|System.Reflection.BindingFlags.Instance)!.GetValue(browser)!; var scope=((IWeakCollectionOwner)(object)runtime.Engine).WeakCollections; var table=typeof(Jint.Engine).GetField("_emptyShapes",System.Reflection.BindingFlags.NonPublic|System.Reflection.BindingFlags.Instance)!.GetValue(runtime.Engine); Assert.NotNull(table);Assert.Equal(typeof(OwnedWeakTable<,>),table.GetType().GetGenericTypeDefinition()); var before=scope.Lifetime;Assert.InRange(before.Created,190,long.MaxValue); browser.Release();var after=scope.Lifetime; Assert.Equal(0,after.Tables);Assert.Equal(0,after.Entries);Assert.Equal(after.Created,after.Disposed); Assert.Equal(after.TableHandlesCreated,after.TableHandlesDisposed); GC.KeepAlive(runtime);GC.KeepAlive(table); } }