using XFESystem.Core;
using XFESystem.Desktop;
using Xunit;
namespace XFESystem.Desktop.Tests;
public sealed class EditorRetirementTests
{
[Fact]
public void ClosedWorkerPageIsRetriedUntilItsOwnerActuallyReleasesIt()
{
var page = new WorkerPage();
var preparation = new Preparation(page, publish: true);
var desktop = Create(preparation);
var editor = desktop.Open(DesktopApplication.Editor, Path);
desktop.AdvanceFeedback(1);
Assert.Same(page, editor.BrowserSession);
desktop.Close(editor.Id);
Assert.Empty(desktop.Windows);
Assert.True(page.Cancelled);
Assert.False(page.IsRetired);
desktop.AdvanceFeedback(2);
desktop.AdvanceFeedback(3);
Assert.True(page.Advances >= 2);
page.AllowRetirement = true;
desktop.AdvanceFeedback(4);
Assert.True(page.IsRetired);
int completed = page.Advances;
desktop.AdvanceFeedback(5);
Assert.Equal(completed, page.Advances);
}
[Fact]
public void ClosingWhilePreparingKeepsRetryingEvenThoughTheWindowIsGone()
{
var preparation = new Preparation(new WorkerPage(), publish: false);
var desktop = Create(preparation);
var editor = desktop.Open(DesktopApplication.Editor, Path);
desktop.Close(editor.Id);
Assert.Empty(desktop.Windows);
Assert.True(preparation.Operation!.Cancelled);
desktop.AdvanceFeedback(1);
desktop.AdvanceFeedback(2);
Assert.Equal(2, preparation.Operation.RetirementAdvances);
preparation.Operation.AllowRetirement = true;
desktop.AdvanceFeedback(3);
Assert.True(preparation.Operation.IsRetired);
desktop.AdvanceFeedback(4);
Assert.Equal(3, preparation.Operation.RetirementAdvances);
}
[Fact]
public void UiOwnedBrowserReleasesOnCloseOnlyOnce()
{
var page = new Page();
var desktop = new DesktopSession(new Canvas(), new MemoryWorkspace(), new Factory(page));
var window = desktop.Open(DesktopApplication.Browser);
desktop.Close(window.Id);
desktop.Close(window.Id);
Assert.Equal(1, page.Releases);
Assert.Equal(Environment.CurrentManagedThreadId, page.ReleaseThread);
}
[Fact]
public void FailedSynchronousEditorInitializationReleasesItsPartlyCreatedPage()
{
var page = new Page { FailEditor = true };
var storage = new MemoryWorkspace();
storage.WriteText(Path, Source);
var desktop = new DesktopSession(new Canvas(), storage, new Factory(page));
var editor = desktop.Open(DesktopApplication.Editor, Path);
Assert.Null(editor.BrowserSession);
Assert.Equal(Source, editor.Content);
Assert.Equal(1, page.Releases);
desktop.Close(editor.Id);
Assert.Equal(1, page.Releases);
}
private const string Source = "using System;";
private static readonly string Path = SystemPaths.Combine(SystemPaths.Documents, "Retire.cs");
private static DesktopSession Create(Preparation preparation)
{
var storage = new MemoryWorkspace();
storage.WriteText(Path, Source);
return new(new Canvas(), storage, new Factory(new Page()), editorPreparation: preparation);
}
private sealed class Preparation(WorkerPage page, bool publish) : IEditorPreparationService
{
public Operation? Operation;
public IEditorPreparationOperation Begin(EditorPreparationRequest request) => Operation = new(request, page, publish);
}
private sealed class Operation(EditorPreparationRequest request, WorkerPage page, bool publish) : IEditorPreparationOperation
{
private bool taken;
public EditorPreparationRequest Request => request;
public bool Cancelled, AllowRetirement;
public int RetirementAdvances;
public bool IsRetired { get; private set; }
public void Cancel() => Cancelled = true;
public void AdvanceRetirement(long now) { RetirementAdvances++; IsRetired = AllowRetirement; }
public bool TryTakeResult(out EditorPreparationResult result)
{
result = new(page, null, 42);
if (!publish || taken || Cancelled) return false;
taken = true;
return true;
}
}
private class Page : IInteractiveBrowserSession
{
public int Releases, ReleaseThread;
public bool FailEditor;
public BrowserPage CurrentPage => new("about:Test", "Test", Source);
public long Revision => 0;
public double DocumentHeight => 10;
public BrowserPage Navigate(string address) => CurrentPage;
public BrowserPage NavigateEditor(string source, string path) => FailEditor ? throw new IOException("partly initialized") : CurrentPage;
public void Paint(IDesktopCanvas canvas, DesktopBounds viewport, double scrollY) { }
public bool Pointer(BrowserPointerInput input) => false;
public bool Key(BrowserKeyboardInput input) => false;
public virtual bool AdvanceTime(long milliseconds) => false;
public void Release() { Releases++; ReleaseThread = Environment.CurrentManagedThreadId; }
}
private sealed class WorkerPage : Page, IBackgroundEditorSession
{
public bool Cancelled, AllowRetirement;
public int Advances;
public bool IsRetired { get; private set; }
public BackgroundEditorDiagnostics Diagnostics => new("{}", "", 0, 0, 0, 0, 42);
public long RasterPaintCount => 0;
public long CachedPaintCount => 0;
public bool RequestEditorReadback(Action<string?, string?> completed) => false;
public void Cancel() => Cancelled = true;
public override bool AdvanceTime(long milliseconds)
{
if (Cancelled) { Advances++; IsRetired = AllowRetirement; }
return false;
}
}
private sealed class Factory(Page page) : IBrowserService, IInteractiveBrowserFactory
{
public IInteractiveBrowserSession CreatePageSession() => page;
public BrowserPage Navigate(string address) => page.CurrentPage;
public string ExecuteJavaScript(string source) => "";
public string PreviewMarkdown(string source) => "";
}
private sealed class Canvas : IDesktopCanvas
{
public int Width => 1920;
public int Height => 1080;
public void Fill(uint rgb, int x, int y, int width, int height) { }
public void Line(uint rgb, int x1, int y1, int x2, int y2) { }
public void Text(string text, uint rgb, int x, int y) { }
public void Present() { }
}
}
using XFESystem.Core;
using XFESystem.Desktop;
using Xunit;
namespace XFESystem.Desktop.Tests;
public sealed class EditorRetirementTests
{
[Fact]
public void ClosedWorkerPageIsRetriedUntilItsOwnerActuallyReleasesIt()
{
var page = new WorkerPage();
var preparation = new Preparation(page, publish: true);
var desktop = Create(preparation);
var editor = desktop.Open(DesktopApplication.Editor, Path);
desktop.AdvanceFeedback(1);
Assert.Same(page, editor.BrowserSession);
desktop.Close(editor.Id);
Assert.Empty(desktop.Windows);
Assert.True(page.Cancelled);
Assert.False(page.IsRetired);
desktop.AdvanceFeedback(2);
desktop.AdvanceFeedback(3);
Assert.True(page.Advances >= 2);
page.AllowRetirement = true;
desktop.AdvanceFeedback(4);
Assert.True(page.IsRetired);
int completed = page.Advances;
desktop.AdvanceFeedback(5);
Assert.Equal(completed, page.Advances);
}
[Fact]
public void ClosingWhilePreparingKeepsRetryingEvenThoughTheWindowIsGone()
{
var preparation = new Preparation(new WorkerPage(), publish: false);
var desktop = Create(preparation);
var editor = desktop.Open(DesktopApplication.Editor, Path);
desktop.Close(editor.Id);
Assert.Empty(desktop.Windows);
Assert.True(preparation.Operation!.Cancelled);
desktop.AdvanceFeedback(1);
desktop.AdvanceFeedback(2);
Assert.Equal(2, preparation.Operation.RetirementAdvances);
preparation.Operation.AllowRetirement = true;
desktop.AdvanceFeedback(3);
Assert.True(preparation.Operation.IsRetired);
desktop.AdvanceFeedback(4);
Assert.Equal(3, preparation.Operation.RetirementAdvances);
}
[Fact]
public void UiOwnedBrowserReleasesOnCloseOnlyOnce()
{
var page = new Page();
var desktop = new DesktopSession(new Canvas(), new MemoryWorkspace(), new Factory(page));
var window = desktop.Open(DesktopApplication.Browser);
desktop.Close(window.Id);
desktop.Close(window.Id);
Assert.Equal(1, page.Releases);
Assert.Equal(Environment.CurrentManagedThreadId, page.ReleaseThread);
}
[Fact]
public void FailedSynchronousEditorInitializationReleasesItsPartlyCreatedPage()
{
var page = new Page { FailEditor = true };
var storage = new MemoryWorkspace();
storage.WriteText(Path, Source);
var desktop = new DesktopSession(new Canvas(), storage, new Factory(page));
var editor = desktop.Open(DesktopApplication.Editor, Path);
Assert.Null(editor.BrowserSession);
Assert.Equal(Source, editor.Content);
Assert.Equal(1, page.Releases);
desktop.Close(editor.Id);
Assert.Equal(1, page.Releases);
}
private const string Source = "using System;";
private static readonly string Path = SystemPaths.Combine(SystemPaths.Documents, "Retire.cs");
private static DesktopSession Create(Preparation preparation)
{
var storage = new MemoryWorkspace();
storage.WriteText(Path, Source);
return new(new Canvas(), storage, new Factory(new Page()), editorPreparation: preparation);
}
private sealed class Preparation(WorkerPage page, bool publish) : IEditorPreparationService
{
public Operation? Operation;
public IEditorPreparationOperation Begin(EditorPreparationRequest request) => Operation = new(request, page, publish);
}
private sealed class Operation(EditorPreparationRequest request, WorkerPage page, bool publish) : IEditorPreparationOperation
{
private bool taken;
public EditorPreparationRequest Request => request;
public bool Cancelled, AllowRetirement;
public int RetirementAdvances;
public bool IsRetired { get; private set; }
public void Cancel() => Cancelled = true;
public void AdvanceRetirement(long now) { RetirementAdvances++; IsRetired = AllowRetirement; }
public bool TryTakeResult(out EditorPreparationResult result)
{
result = new(page, null, 42);
if (!publish || taken || Cancelled) return false;
taken = true;
return true;
}
}
private class Page : IInteractiveBrowserSession
{
public int Releases, ReleaseThread;
public bool FailEditor;
public BrowserPage CurrentPage => new("about:Test", "Test", Source);
public long Revision => 0;
public double DocumentHeight => 10;
public BrowserPage Navigate(string address) => CurrentPage;
public BrowserPage NavigateEditor(string source, string path) => FailEditor ? throw new IOException("partly initialized") : CurrentPage;
public void Paint(IDesktopCanvas canvas, DesktopBounds viewport, double scrollY) { }
public bool Pointer(BrowserPointerInput input) => false;
public bool Key(BrowserKeyboardInput input) => false;
public virtual bool AdvanceTime(long milliseconds) => false;
public void Release() { Releases++; ReleaseThread = Environment.CurrentManagedThreadId; }
}
private sealed class WorkerPage : Page, IBackgroundEditorSession
{
public bool Cancelled, AllowRetirement;
public int Advances;
public bool IsRetired { get; private set; }
public BackgroundEditorDiagnostics Diagnostics => new("{}", "", 0, 0, 0, 0, 42);
public long RasterPaintCount => 0;
public long CachedPaintCount => 0;
public bool RequestEditorReadback(Action<string?, string?> completed) => false;
public void Cancel() => Cancelled = true;
public override bool AdvanceTime(long milliseconds)
{
if (Cancelled) { Advances++; IsRetired = AllowRetirement; }
return false;
}
}
private sealed class Factory(Page page) : IBrowserService, IInteractiveBrowserFactory
{
public IInteractiveBrowserSession CreatePageSession() => page;
public BrowserPage Navigate(string address) => page.CurrentPage;
public string ExecuteJavaScript(string source) => "";
public string PreviewMarkdown(string source) => "";
}
private sealed class Canvas : IDesktopCanvas
{
public int Width => 1920;
public int Height => 1080;
public void Fill(uint rgb, int x, int y, int width, int height) { }
public void Line(uint rgb, int x1, int y1, int x2, int y2) { }
public void Text(string text, uint rgb, int x, int y) { }
public void Present() { }
}
}