using System.Runtime.CompilerServices;
using XFESystem.Desktop;
using Xunit;
namespace XFESystem.Desktop.Tests;
public sealed class EditorReadbackTrackerTests
{
[Theory]
[InlineData("Large.cs")]
[InlineData("Large.md")]
public void TenThousandUnchangedLargeReadbacksAllocateNothing(string path)
{
var tracker = new EditorReadbackTracker();
string content = new('x', 128 * 1024);
Assert.True(tracker.TryUpdate(7, path, content, out string prefix));
Assert.Equal(EditorReadbackTracker.MaximumPrefixCharacters, prefix.Length);
for (int index = 0; index < 100; index++) tracker.TryUpdate(7, path, content, out _);
bool unchanged = true;
long before = GC.GetAllocatedBytesForCurrentThread();
for (int index = 0; index < 10_000; index++)
unchanged &= !tracker.TryUpdate(7, path, content, out _);
long allocated = GC.GetAllocatedBytesForCurrentThread() - before;
Assert.True(unchanged);
Assert.Equal(0, allocated);
GC.KeepAlive(tracker);
}
[Fact]
public void ObservedPrefixLengthPathAndWindowChangesEachReportOnce()
{
var tracker = new EditorReadbackTracker();
string original = new('a', 128 * 1024);
AssertChangeOnce(tracker, 1, "Sample.cs", original);
string prefixChanged = "b" + original[1..];
AssertChangeOnce(tracker, 1, "Sample.cs", prefixChanged);
AssertChangeOnce(tracker, 1, "Sample.cs", prefixChanged + "x");
AssertChangeOnce(tracker, 1, "Moved.cs", prefixChanged + "x");
AssertChangeOnce(tracker, 2, "Moved.cs", prefixChanged + "x");
}
[Fact]
public void EqualLengthChangesBeyondThePrefixKeepTheExistingTruncationSemantics()
{
var tracker = new EditorReadbackTracker();
string content = new('a', 128 * 1024);
Assert.True(tracker.TryUpdate(1, "Large.md", content, out string originalPrefix));
string changedOutsidePrefix = content[..EditorReadbackTracker.MaximumPrefixCharacters] +
new string('z', content.Length - EditorReadbackTracker.MaximumPrefixCharacters);
Assert.False(tracker.TryUpdate(1, "Large.md", changedOutsidePrefix, out string prefix));
Assert.Same(originalPrefix, prefix);
Assert.Equal(content[..EditorReadbackTracker.MaximumPrefixCharacters], prefix);
}
[Theory]
[InlineData(0)]
[InlineData(8191)]
[InlineData(8192)]
[InlineData(8193)]
public void PrefixBoundaryAndResetPreserveExactSourceCharacters(int characters)
{
var tracker = new EditorReadbackTracker();
string content = new('\u4E2D', characters);
Assert.True(tracker.TryUpdate(0, "Boundary.md", content, out string prefix));
Assert.Equal(content[..Math.Min(characters, EditorReadbackTracker.MaximumPrefixCharacters)], prefix);
Assert.False(tracker.TryUpdate(0, "Boundary.md", content, out _));
tracker.Reset();
tracker.Reset();
Assert.True(tracker.TryUpdate(0, "Boundary.md", content, out string afterReset));
Assert.Equal(prefix, afterReset);
}
[Fact]
public void AStillLiveTrackerDoesNotKeepTheOriginalLargeSourceAlive()
{
var tracker = new EditorReadbackTracker();
var source = TrackLargeSource(tracker, out string prefix);
GC.Collect();
GC.WaitForPendingFinalizers();
GC.Collect();
Assert.False(source.TryGetTarget(out _));
Assert.Equal(EditorReadbackTracker.MaximumPrefixCharacters, prefix.Length);
Assert.Equal(new string('q', EditorReadbackTracker.MaximumPrefixCharacters), prefix);
GC.KeepAlive(prefix);
GC.KeepAlive(tracker);
}
[MethodImpl(MethodImplOptions.NoInlining)]
private static WeakReference<string> TrackLargeSource(EditorReadbackTracker tracker, out string prefix)
{
string content = new('q', 128 * 1024);
Assert.True(tracker.TryUpdate(1, "Closed.cs", content, out prefix));
Assert.NotSame(content, prefix);
return new(content);
}
private static void AssertChangeOnce(EditorReadbackTracker tracker, int id, string path, string content)
{
Assert.True(tracker.TryUpdate(id, path, content, out string prefix));
Assert.Equal(content[..EditorReadbackTracker.MaximumPrefixCharacters], prefix);
Assert.False(tracker.TryUpdate(id, path, content, out string unchanged));
Assert.Same(prefix, unchanged);
}
}
using System.Runtime.CompilerServices;
using XFESystem.Desktop;
using Xunit;
namespace XFESystem.Desktop.Tests;
public sealed class EditorReadbackTrackerTests
{
[Theory]
[InlineData("Large.cs")]
[InlineData("Large.md")]
public void TenThousandUnchangedLargeReadbacksAllocateNothing(string path)
{
var tracker = new EditorReadbackTracker();
string content = new('x', 128 * 1024);
Assert.True(tracker.TryUpdate(7, path, content, out string prefix));
Assert.Equal(EditorReadbackTracker.MaximumPrefixCharacters, prefix.Length);
for (int index = 0; index < 100; index++) tracker.TryUpdate(7, path, content, out _);
bool unchanged = true;
long before = GC.GetAllocatedBytesForCurrentThread();
for (int index = 0; index < 10_000; index++)
unchanged &= !tracker.TryUpdate(7, path, content, out _);
long allocated = GC.GetAllocatedBytesForCurrentThread() - before;
Assert.True(unchanged);
Assert.Equal(0, allocated);
GC.KeepAlive(tracker);
}
[Fact]
public void ObservedPrefixLengthPathAndWindowChangesEachReportOnce()
{
var tracker = new EditorReadbackTracker();
string original = new('a', 128 * 1024);
AssertChangeOnce(tracker, 1, "Sample.cs", original);
string prefixChanged = "b" + original[1..];
AssertChangeOnce(tracker, 1, "Sample.cs", prefixChanged);
AssertChangeOnce(tracker, 1, "Sample.cs", prefixChanged + "x");
AssertChangeOnce(tracker, 1, "Moved.cs", prefixChanged + "x");
AssertChangeOnce(tracker, 2, "Moved.cs", prefixChanged + "x");
}
[Fact]
public void EqualLengthChangesBeyondThePrefixKeepTheExistingTruncationSemantics()
{
var tracker = new EditorReadbackTracker();
string content = new('a', 128 * 1024);
Assert.True(tracker.TryUpdate(1, "Large.md", content, out string originalPrefix));
string changedOutsidePrefix = content[..EditorReadbackTracker.MaximumPrefixCharacters] +
new string('z', content.Length - EditorReadbackTracker.MaximumPrefixCharacters);
Assert.False(tracker.TryUpdate(1, "Large.md", changedOutsidePrefix, out string prefix));
Assert.Same(originalPrefix, prefix);
Assert.Equal(content[..EditorReadbackTracker.MaximumPrefixCharacters], prefix);
}
[Theory]
[InlineData(0)]
[InlineData(8191)]
[InlineData(8192)]
[InlineData(8193)]
public void PrefixBoundaryAndResetPreserveExactSourceCharacters(int characters)
{
var tracker = new EditorReadbackTracker();
string content = new('\u4E2D', characters);
Assert.True(tracker.TryUpdate(0, "Boundary.md", content, out string prefix));
Assert.Equal(content[..Math.Min(characters, EditorReadbackTracker.MaximumPrefixCharacters)], prefix);
Assert.False(tracker.TryUpdate(0, "Boundary.md", content, out _));
tracker.Reset();
tracker.Reset();
Assert.True(tracker.TryUpdate(0, "Boundary.md", content, out string afterReset));
Assert.Equal(prefix, afterReset);
}
[Fact]
public void AStillLiveTrackerDoesNotKeepTheOriginalLargeSourceAlive()
{
var tracker = new EditorReadbackTracker();
var source = TrackLargeSource(tracker, out string prefix);
GC.Collect();
GC.WaitForPendingFinalizers();
GC.Collect();
Assert.False(source.TryGetTarget(out _));
Assert.Equal(EditorReadbackTracker.MaximumPrefixCharacters, prefix.Length);
Assert.Equal(new string('q', EditorReadbackTracker.MaximumPrefixCharacters), prefix);
GC.KeepAlive(prefix);
GC.KeepAlive(tracker);
}
[MethodImpl(MethodImplOptions.NoInlining)]
private static WeakReference<string> TrackLargeSource(EditorReadbackTracker tracker, out string prefix)
{
string content = new('q', 128 * 1024);
Assert.True(tracker.TryUpdate(1, "Closed.cs", content, out prefix));
Assert.NotSame(content, prefix);
return new(content);
}
private static void AssertChangeOnce(EditorReadbackTracker tracker, int id, string path, string content)
{
Assert.True(tracker.TryUpdate(id, path, content, out string prefix));
Assert.Equal(content[..EditorReadbackTracker.MaximumPrefixCharacters], prefix);
Assert.False(tracker.TryUpdate(id, path, content, out string unchanged));
Assert.Same(prefix, unchanged);
}
}