using XFEEDToolBox.Core.Galaxy;
using XFEEDToolBox.Core.Models;
using XFEExtension.NetCore.XUnit.Attributes;
namespace XFEEDToolBox.Test;
[TestFixture]
public class GalaxyCellStreamingTests
{
private static GalaxyCellQueryPlan Plan(double z = 5, Coordinates? origin = null)
{
var centre = new Coordinates(5, 5, z);
return new(GalaxyCamera.Create(centre, 1600, 900, 0, 0, 4),
GalaxyOriginalCatalogueLod.Create(centre, 1, 500, .001, 1, 0), origin ?? new(0, 0, 0));
}
private static GalacticSystem Star(ulong cell, Coordinates? position = null) =>
new("Fixture " + cell, position ?? new(9, 9, 9), (long)(cell | (1UL << (44 - 3 * (int)(cell & 7)))));
private static GalaxyRegionResult Receipt(IReadOnlyList<ulong> cells, IReadOnlyList<GalacticSystem>? stars = null) =>
new(stars ?? cells.Select(c => Star(c)).ToArray(), (int)cells.Min(c => c & 7), cells.Count, cells.Count, false, true)
{ Scope = "requested-cells", RequestedCells = cells.Count, Origin = new(0, 0, 0) };
private static async Task<List<GalaxyCellStreamUpdate>> Collect(GalaxyCellStreamingSession session, GalaxyCellQueryPlan plan,
Func<Coordinates, IReadOnlyList<double>, IReadOnlyList<ulong>, CancellationToken, Task<GalaxyRegionResult>> query)
{
var updates = new List<GalaxyCellStreamUpdate>();
await foreach (var update in session.StreamAsync(plan, query)) updates.Add(update);
return updates;
}
[Test] public void ReceiptProvesOnlyTheExactRequestedMassCells()
{
var actual = Receipt(new ulong[] { 7 });
Check.That(GalaxyCellStreamingSession.CoversRequestedCells(actual, 1));
Check.That(!actual.IsFullyCovered, "A requested mass-seven cell is not evidence of a whole all-mass region.");
Check.That(!GalaxyCellStreamingSession.CoversRequestedCells(actual, 2));
Check.That(!GalaxyCellStreamingSession.CoversRequestedCells(actual with { Scope = "region" }, 1));
Check.That(!GalaxyCellStreamingSession.CoversRequestedCells(actual with { RequestedCells = -1 }, 1));
Check.That(!GalaxyCellStreamingSession.CoversRequestedCells(actual with { FailedCells = 1 }, 1));
}
[Test] public async Task CappedBatchesSplitCellListsWithoutClippingOrDuplicatingEdgeStars()
{
using var session = new GalaxyCellStreamingSession(); var plan = Plan();
Task<GalaxyRegionResult> Query(Coordinates centre, IReadOnlyList<double> radii, IReadOnlyList<ulong> cells, CancellationToken token)
{
var result = Receipt(cells);
return Task.FromResult(cells.Count > 1 ? result with { Systems = [Star(cells[0])], Truncated = true, Complete = false } : result);
}
var updates = await Collect(session, plan, Query);
var rows = updates.SelectMany(u => u.Systems).ToArray();
Check.That(updates[^1].CoverageComplete && updates[^1].RefinedBatches > 0);
Check.Equal(plan.Cells.Count, rows.Length);
Check.Equal(rows.Length, rows.Select(s => s.SystemAddress).Distinct().Count());
Check.That(rows.Any(s => s.Position.DistanceTo(plan.Descriptor.Centre) > plan.Descriptor.InnerRadius));
var repeated = await Collect(session, plan, (_, _, _, _) => throw new Exception("Complete cell should be cached"));
Check.That(repeated[^1].CoverageComplete && repeated.Any(u => u.FromCache));
}
[Test] public async Task UnknownAndFailedSingleCellsRemainRetryableInsteadOfBecomingEmptyCoverage()
{
using var session = new GalaxyCellStreamingSession(new() { BatchSize = 1 }); var plan = Plan();
var first = await Collect(session, plan, (_, _, cells, _) => Task.FromResult(new GalaxyRegionResult([], (int)(cells[0] & 7), 1, 1){Origin=new(0,0,0)}));
Check.That(!first[^1].CoverageComplete && first[^1].UnverifiedCells == plan.Cells.Count);
var retried = 0;
var second = await Collect(session, plan, (_, _, cells, _) => { retried++; return Task.FromResult(Receipt(cells, [])); });
Check.That(second[^1].CoverageComplete && retried == plan.Cells.Count);
}
[Test] public async Task OffRequestAndConflictingRowsCannotPoisonTheCellCache()
{
using var session = new GalaxyCellStreamingSession(); var plan = Plan();
var rejected = false;
try { await Collect(session, plan, (_, _, cells, _) => Task.FromResult(Receipt(cells, [Star(GalaxyCellQueryPlan.Encode(0, 10, 10, 10))]))); }
catch (InvalidDataException) { rejected = true; }
Check.That(rejected && session.CachedCells == 0);
rejected = false;
try { await Collect(session, plan, (_, _, cells, _) => Task.FromResult(Receipt(cells) with { Origin = new(1, 2, 3) })); }
catch (InvalidDataException) { rejected = true; }
Check.That(rejected && session.CachedCells == 0);
rejected = false;
try { await Collect(session, plan, (_, _, cells, _) => Task.FromResult(Receipt(cells, [Star(cells[0]), Star(cells[0], new(2, 3, 4))]))); }
catch (InvalidDataException) { rejected = true; }
Check.That(rejected && session.CachedCells == 0);
}
[Test] public async Task LatestDisjointViewCancelsOldWorkAndNeverPublishesItsRows()
{
using var session = new GalaxyCellStreamingSession(); var started = new TaskCompletionSource(TaskCreationOptions.RunContinuationsAsynchronously);
async Task<GalaxyRegionResult> Old(Coordinates centre, IReadOnlyList<double> radii, IReadOnlyList<ulong> cells, CancellationToken token)
{ started.SetResult(); await Task.Delay(Timeout.Infinite, token); return Receipt(cells); }
await using var old = session.StreamAsync(Plan(), Old).GetAsyncEnumerator();
var oldMove = old.MoveNextAsync().AsTask(); await started.Task.WaitAsync(TimeSpan.FromSeconds(3));
var latest = await Collect(session, Plan(10005), (_, _, cells, _) => Task.FromResult(Receipt(cells, [])));
var cancelled = false; try { await oldMove; } catch (OperationCanceledException) { cancelled = true; }
Check.That(cancelled && latest[^1].CoverageComplete && latest.All(u => u.Systems.Count == 0));
}
[Test] public async Task UsefulInflightCellsCanFinishForTheNewViewWithoutStalePublication()
{
using var session = new GalaxyCellStreamingSession(); var plan = Plan();
var started = new TaskCompletionSource(TaskCreationOptions.RunContinuationsAsynchronously);
var release = new TaskCompletionSource(TaskCreationOptions.RunContinuationsAsynchronously);
async Task<GalaxyRegionResult> Query(Coordinates centre, IReadOnlyList<double> radii, IReadOnlyList<ulong> cells, CancellationToken token)
{ started.SetResult(); await release.Task.WaitAsync(token); return Receipt(cells); }
await using var old = session.StreamAsync(plan, Query).GetAsyncEnumerator();
var oldMove = old.MoveNextAsync().AsTask(); await started.Task.WaitAsync(TimeSpan.FromSeconds(3));
var latestTask = Collect(session, plan, (_, _, _, _) => throw new Exception("Useful accepted cells should be reused"));
release.SetResult(); var latest = await latestTask;
var cancelled = false; try { await oldMove; } catch (OperationCanceledException) { cancelled = true; }
Check.That(cancelled && latest[^1].CoverageComplete && latest.Any(u => u.FromCache && u.Systems.Count > 0));
}
[Test] public async Task CoordinateOriginIsPartOfTheCacheIdentityAndEvictionRemainsBounded()
{
using var session = new GalaxyCellStreamingSession(new() { MaximumCachedCells = 2, MaximumCachedSystems = 2 });
await Collect(session, Plan(), (_, _, cells, _) => Task.FromResult(Receipt(cells)));
Check.That(session.CachedCells <= 2);
var queried = 0;
await Collect(session, Plan(origin: new(100, 100, 100)), (_, _, cells, _) => { queried++; return Task.FromResult(Receipt(cells) with {Origin=new(100,100,100)}); });
Check.That(queried > 0 && session.CachedCells <= 2);
}
[Test] public async Task PartlyOverlappingPanKeepsUsefulAcceptedCellsAndQueriesOnlyTheNewOnes()
{
using var session = new GalaxyCellStreamingSession(); var first = Plan(); var next = Plan(15);
var oldIds = first.Cells.Select(c => c.Address).ToHashSet();
var newIds = next.Cells.Select(c => c.Address).ToHashSet();
Check.That(oldIds.Overlaps(newIds) && !oldIds.SetEquals(newIds));
var started = new TaskCompletionSource(TaskCreationOptions.RunContinuationsAsynchronously);
var release = new TaskCompletionSource(TaskCreationOptions.RunContinuationsAsynchronously);
async Task<GalaxyRegionResult> Original(Coordinates centre, IReadOnlyList<double> radii, IReadOnlyList<ulong> cells, CancellationToken token)
{ started.SetResult(); await release.Task.WaitAsync(token); return Receipt(cells); }
await using var old = session.StreamAsync(first, Original).GetAsyncEnumerator();
var oldMove = old.MoveNextAsync().AsTask(); await started.Task.WaitAsync(TimeSpan.FromSeconds(3));
var queried = new List<ulong>();
var latestTask = Collect(session, next, (_, _, cells, _) => { queried.AddRange(cells); return Task.FromResult(Receipt(cells)); });
release.SetResult(); var latest = await latestTask;
var cancelled = false; try { await oldMove; } catch (OperationCanceledException) { cancelled = true; }
Check.That(cancelled && latest[^1].CoverageComplete && latest.Any(u => u.FromCache));
Check.That(queried.ToHashSet().SetEquals(newIds.Except(oldIds)));
}
}
using XFEEDToolBox.Core.Galaxy;
using XFEEDToolBox.Core.Models;
using XFEExtension.NetCore.XUnit.Attributes;
namespace XFEEDToolBox.Test;
[TestFixture]
public class GalaxyCellStreamingTests
{
private static GalaxyCellQueryPlan Plan(double z = 5, Coordinates? origin = null)
{
var centre = new Coordinates(5, 5, z);
return new(GalaxyCamera.Create(centre, 1600, 900, 0, 0, 4),
GalaxyOriginalCatalogueLod.Create(centre, 1, 500, .001, 1, 0), origin ?? new(0, 0, 0));
}
private static GalacticSystem Star(ulong cell, Coordinates? position = null) =>
new("Fixture " + cell, position ?? new(9, 9, 9), (long)(cell | (1UL << (44 - 3 * (int)(cell & 7)))));
private static GalaxyRegionResult Receipt(IReadOnlyList<ulong> cells, IReadOnlyList<GalacticSystem>? stars = null) =>
new(stars ?? cells.Select(c => Star(c)).ToArray(), (int)cells.Min(c => c & 7), cells.Count, cells.Count, false, true)
{ Scope = "requested-cells", RequestedCells = cells.Count, Origin = new(0, 0, 0) };
private static async Task<List<GalaxyCellStreamUpdate>> Collect(GalaxyCellStreamingSession session, GalaxyCellQueryPlan plan,
Func<Coordinates, IReadOnlyList<double>, IReadOnlyList<ulong>, CancellationToken, Task<GalaxyRegionResult>> query)
{
var updates = new List<GalaxyCellStreamUpdate>();
await foreach (var update in session.StreamAsync(plan, query)) updates.Add(update);
return updates;
}
[Test] public void ReceiptProvesOnlyTheExactRequestedMassCells()
{
var actual = Receipt(new ulong[] { 7 });
Check.That(GalaxyCellStreamingSession.CoversRequestedCells(actual, 1));
Check.That(!actual.IsFullyCovered, "A requested mass-seven cell is not evidence of a whole all-mass region.");
Check.That(!GalaxyCellStreamingSession.CoversRequestedCells(actual, 2));
Check.That(!GalaxyCellStreamingSession.CoversRequestedCells(actual with { Scope = "region" }, 1));
Check.That(!GalaxyCellStreamingSession.CoversRequestedCells(actual with { RequestedCells = -1 }, 1));
Check.That(!GalaxyCellStreamingSession.CoversRequestedCells(actual with { FailedCells = 1 }, 1));
}
[Test] public async Task CappedBatchesSplitCellListsWithoutClippingOrDuplicatingEdgeStars()
{
using var session = new GalaxyCellStreamingSession(); var plan = Plan();
Task<GalaxyRegionResult> Query(Coordinates centre, IReadOnlyList<double> radii, IReadOnlyList<ulong> cells, CancellationToken token)
{
var result = Receipt(cells);
return Task.FromResult(cells.Count > 1 ? result with { Systems = [Star(cells[0])], Truncated = true, Complete = false } : result);
}
var updates = await Collect(session, plan, Query);
var rows = updates.SelectMany(u => u.Systems).ToArray();
Check.That(updates[^1].CoverageComplete && updates[^1].RefinedBatches > 0);
Check.Equal(plan.Cells.Count, rows.Length);
Check.Equal(rows.Length, rows.Select(s => s.SystemAddress).Distinct().Count());
Check.That(rows.Any(s => s.Position.DistanceTo(plan.Descriptor.Centre) > plan.Descriptor.InnerRadius));
var repeated = await Collect(session, plan, (_, _, _, _) => throw new Exception("Complete cell should be cached"));
Check.That(repeated[^1].CoverageComplete && repeated.Any(u => u.FromCache));
}
[Test] public async Task UnknownAndFailedSingleCellsRemainRetryableInsteadOfBecomingEmptyCoverage()
{
using var session = new GalaxyCellStreamingSession(new() { BatchSize = 1 }); var plan = Plan();
var first = await Collect(session, plan, (_, _, cells, _) => Task.FromResult(new GalaxyRegionResult([], (int)(cells[0] & 7), 1, 1){Origin=new(0,0,0)}));
Check.That(!first[^1].CoverageComplete && first[^1].UnverifiedCells == plan.Cells.Count);
var retried = 0;
var second = await Collect(session, plan, (_, _, cells, _) => { retried++; return Task.FromResult(Receipt(cells, [])); });
Check.That(second[^1].CoverageComplete && retried == plan.Cells.Count);
}
[Test] public async Task OffRequestAndConflictingRowsCannotPoisonTheCellCache()
{
using var session = new GalaxyCellStreamingSession(); var plan = Plan();
var rejected = false;
try { await Collect(session, plan, (_, _, cells, _) => Task.FromResult(Receipt(cells, [Star(GalaxyCellQueryPlan.Encode(0, 10, 10, 10))]))); }
catch (InvalidDataException) { rejected = true; }
Check.That(rejected && session.CachedCells == 0);
rejected = false;
try { await Collect(session, plan, (_, _, cells, _) => Task.FromResult(Receipt(cells) with { Origin = new(1, 2, 3) })); }
catch (InvalidDataException) { rejected = true; }
Check.That(rejected && session.CachedCells == 0);
rejected = false;
try { await Collect(session, plan, (_, _, cells, _) => Task.FromResult(Receipt(cells, [Star(cells[0]), Star(cells[0], new(2, 3, 4))]))); }
catch (InvalidDataException) { rejected = true; }
Check.That(rejected && session.CachedCells == 0);
}
[Test] public async Task LatestDisjointViewCancelsOldWorkAndNeverPublishesItsRows()
{
using var session = new GalaxyCellStreamingSession(); var started = new TaskCompletionSource(TaskCreationOptions.RunContinuationsAsynchronously);
async Task<GalaxyRegionResult> Old(Coordinates centre, IReadOnlyList<double> radii, IReadOnlyList<ulong> cells, CancellationToken token)
{ started.SetResult(); await Task.Delay(Timeout.Infinite, token); return Receipt(cells); }
await using var old = session.StreamAsync(Plan(), Old).GetAsyncEnumerator();
var oldMove = old.MoveNextAsync().AsTask(); await started.Task.WaitAsync(TimeSpan.FromSeconds(3));
var latest = await Collect(session, Plan(10005), (_, _, cells, _) => Task.FromResult(Receipt(cells, [])));
var cancelled = false; try { await oldMove; } catch (OperationCanceledException) { cancelled = true; }
Check.That(cancelled && latest[^1].CoverageComplete && latest.All(u => u.Systems.Count == 0));
}
[Test] public async Task UsefulInflightCellsCanFinishForTheNewViewWithoutStalePublication()
{
using var session = new GalaxyCellStreamingSession(); var plan = Plan();
var started = new TaskCompletionSource(TaskCreationOptions.RunContinuationsAsynchronously);
var release = new TaskCompletionSource(TaskCreationOptions.RunContinuationsAsynchronously);
async Task<GalaxyRegionResult> Query(Coordinates centre, IReadOnlyList<double> radii, IReadOnlyList<ulong> cells, CancellationToken token)
{ started.SetResult(); await release.Task.WaitAsync(token); return Receipt(cells); }
await using var old = session.StreamAsync(plan, Query).GetAsyncEnumerator();
var oldMove = old.MoveNextAsync().AsTask(); await started.Task.WaitAsync(TimeSpan.FromSeconds(3));
var latestTask = Collect(session, plan, (_, _, _, _) => throw new Exception("Useful accepted cells should be reused"));
release.SetResult(); var latest = await latestTask;
var cancelled = false; try { await oldMove; } catch (OperationCanceledException) { cancelled = true; }
Check.That(cancelled && latest[^1].CoverageComplete && latest.Any(u => u.FromCache && u.Systems.Count > 0));
}
[Test] public async Task CoordinateOriginIsPartOfTheCacheIdentityAndEvictionRemainsBounded()
{
using var session = new GalaxyCellStreamingSession(new() { MaximumCachedCells = 2, MaximumCachedSystems = 2 });
await Collect(session, Plan(), (_, _, cells, _) => Task.FromResult(Receipt(cells)));
Check.That(session.CachedCells <= 2);
var queried = 0;
await Collect(session, Plan(origin: new(100, 100, 100)), (_, _, cells, _) => { queried++; return Task.FromResult(Receipt(cells) with {Origin=new(100,100,100)}); });
Check.That(queried > 0 && session.CachedCells <= 2);
}
[Test] public async Task PartlyOverlappingPanKeepsUsefulAcceptedCellsAndQueriesOnlyTheNewOnes()
{
using var session = new GalaxyCellStreamingSession(); var first = Plan(); var next = Plan(15);
var oldIds = first.Cells.Select(c => c.Address).ToHashSet();
var newIds = next.Cells.Select(c => c.Address).ToHashSet();
Check.That(oldIds.Overlaps(newIds) && !oldIds.SetEquals(newIds));
var started = new TaskCompletionSource(TaskCreationOptions.RunContinuationsAsynchronously);
var release = new TaskCompletionSource(TaskCreationOptions.RunContinuationsAsynchronously);
async Task<GalaxyRegionResult> Original(Coordinates centre, IReadOnlyList<double> radii, IReadOnlyList<ulong> cells, CancellationToken token)
{ started.SetResult(); await release.Task.WaitAsync(token); return Receipt(cells); }
await using var old = session.StreamAsync(first, Original).GetAsyncEnumerator();
var oldMove = old.MoveNextAsync().AsTask(); await started.Task.WaitAsync(TimeSpan.FromSeconds(3));
var queried = new List<ulong>();
var latestTask = Collect(session, next, (_, _, cells, _) => { queried.AddRange(cells); return Task.FromResult(Receipt(cells)); });
release.SetResult(); var latest = await latestTask;
var cancelled = false; try { await oldMove; } catch (OperationCanceledException) { cancelled = true; }
Check.That(cancelled && latest[^1].CoverageComplete && latest.Any(u => u.FromCache));
Check.That(queried.ToHashSet().SetEquals(newIds.Except(oldIds)));
}
}