mirror of
https://github.com/MaSzyna-EU07/StarterNG.git
synced 2026-11-12 17:11:04 +01:00
fix(tapes): a black box goes with the launch it started after and of its own scenery, not every one written after the last launch
This commit is contained in:
@@ -9,30 +9,35 @@ public class BlackBoxListingTests
|
|||||||
|
|
||||||
private static string Log(string name) => TestInstallation.At("physicslog", name);
|
private static string Log(string name) => TestInstallation.At("physicslog", name);
|
||||||
|
|
||||||
|
/// <summary>A black box's name: the simulator stamps it with the local time it started at.</summary>
|
||||||
|
private static string Box(string scenery, DateTime startedUtc) =>
|
||||||
|
$"{scenery}_{startedUtc.ToLocalTime():yyyy-MM-dd_HH-mm-ss}.blackbox";
|
||||||
|
|
||||||
[Fact]
|
[Fact]
|
||||||
public void Each_run_keeps_its_own_black_box_next_to_its_tapes()
|
public void Each_run_keeps_its_own_black_box_next_to_its_tapes()
|
||||||
{
|
{
|
||||||
|
string first = Box("td", Launched.AddSeconds(5)), second = Box("td", Launched.AddHours(1).AddSeconds(5));
|
||||||
var files = new InMemoryFileSystem()
|
var files = new InMemoryFileSystem()
|
||||||
.WithFile(Log("ra.dat"), "t")
|
.WithFile(Log("ra.dat"), "t")
|
||||||
.WithFile(Log("td_2025-12-31_23-00-05.blackbox"), "b")
|
.WithFile(Log(first), "b")
|
||||||
.WithFile(Log("td_2026-01-01_00-00-05.blackbox"), "b");
|
.WithFile(Log(second), "b");
|
||||||
var installation = new TestInstallation(files);
|
var installation = new TestInstallation(files);
|
||||||
var tapes = installation.Tapes;
|
var tapes = installation.Tapes;
|
||||||
installation.Clock.Now = Launched;
|
installation.Clock.Now = Launched;
|
||||||
tapes.RecordLaunch("td.scn", TimeSpan.Zero, Array.Empty<TapeVehicle>(), "ra", new[] { "ra" }, "A");
|
tapes.RecordLaunch("td.scn", TimeSpan.Zero, Array.Empty<TapeVehicle>(), "ra", new[] { "ra" }, "A");
|
||||||
installation.Clock.Now = Launched.AddHours(1);
|
installation.Clock.Now = Launched.AddHours(1);
|
||||||
tapes.RecordLaunch("td.scn", TimeSpan.Zero, Array.Empty<TapeVehicle>(), "ra", new[] { "ra" }, "B");
|
tapes.RecordLaunch("td.scn", TimeSpan.Zero, Array.Empty<TapeVehicle>(), "ra", new[] { "ra" }, "B");
|
||||||
files.WrittenAt(Log("td_2025-12-31_23-00-05.blackbox"), Launched.AddMinutes(30))
|
files.WrittenAt(Log(first), Launched.AddMinutes(30))
|
||||||
.WrittenAt(Log("td_2026-01-01_00-00-05.blackbox"), Launched.AddMinutes(90))
|
.WrittenAt(Log(second), Launched.AddMinutes(90))
|
||||||
.WrittenAt(Log("ra.dat"), Launched.AddMinutes(90));
|
.WrittenAt(Log("ra.dat"), Launched.AddMinutes(90));
|
||||||
|
|
||||||
var sessions = tapes.Sessions();
|
var sessions = tapes.Sessions();
|
||||||
|
|
||||||
Assert.Equal(new[] { "B", "A" }, sessions.Select(session => session.Launch!.SceneryName));
|
Assert.Equal(new[] { "B", "A" }, sessions.Select(session => session.Launch!.SceneryName));
|
||||||
Assert.EndsWith("00-00-05.blackbox", Assert.Single(sessions[0].BlackBoxes).Path);
|
Assert.EndsWith(second, Assert.Single(sessions[0].BlackBoxes).Path);
|
||||||
Assert.Single(sessions[0].Tapes);
|
Assert.Single(sessions[0].Tapes);
|
||||||
// The tape of the first run was written over by the second; its black box was not.
|
// The tape of the first run was written over by the second; its black box was not.
|
||||||
Assert.EndsWith("23-00-05.blackbox", Assert.Single(sessions[1].BlackBoxes).Path);
|
Assert.EndsWith(first, Assert.Single(sessions[1].BlackBoxes).Path);
|
||||||
Assert.Empty(sessions[1].Tapes);
|
Assert.Empty(sessions[1].Tapes);
|
||||||
Assert.Equal(Launched.AddMinutes(30), sessions[1].LastWrittenUtc);
|
Assert.Equal(Launched.AddMinutes(30), sessions[1].LastWrittenUtc);
|
||||||
}
|
}
|
||||||
@@ -83,8 +88,9 @@ public class BlackBoxListingTests
|
|||||||
installation.Clock.Now = Launched;
|
installation.Clock.Now = Launched;
|
||||||
var tapes = installation.Tapes;
|
var tapes = installation.Tapes;
|
||||||
tapes.RecordLaunch("td.scn", TimeSpan.Zero, Array.Empty<TapeVehicle>(), "ra");
|
tapes.RecordLaunch("td.scn", TimeSpan.Zero, Array.Empty<TapeVehicle>(), "ra");
|
||||||
files.WithFile(Log("td_2025-12-31_23-00-20.blackbox"), "b")
|
string box = Box("td", Launched.AddSeconds(20));
|
||||||
.WrittenAt(Log("td_2025-12-31_23-00-20.blackbox"), Launched.AddMinutes(5));
|
files.WithFile(Log(box), "b")
|
||||||
|
.WrittenAt(Log(box), Launched.AddMinutes(5));
|
||||||
|
|
||||||
installation.SimulatorRunning = true;
|
installation.SimulatorRunning = true;
|
||||||
var running = Assert.Single(tapes.Sessions());
|
var running = Assert.Single(tapes.Sessions());
|
||||||
@@ -93,4 +99,49 @@ public class BlackBoxListingTests
|
|||||||
Assert.Empty(running.Tapes);
|
Assert.Empty(running.Tapes);
|
||||||
Assert.Single(running.BlackBoxes);
|
Assert.Single(running.BlackBoxes);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
[Fact]
|
||||||
|
public void A_black_box_of_another_scenery_written_after_a_launch_is_not_taken_for_its_run()
|
||||||
|
{
|
||||||
|
// Copied in, or a run started without the starter: written after the last launch, but of another scenery.
|
||||||
|
var files = new InMemoryFileSystem();
|
||||||
|
var installation = new TestInstallation(files);
|
||||||
|
installation.Clock.Now = Launched;
|
||||||
|
installation.Tapes.RecordLaunch("$baltyk.scn", TimeSpan.Zero, Array.Empty<TapeVehicle>(), "ra", new[] { "ra" }, "Bałtyk");
|
||||||
|
string own = Box("baltyk", Launched.AddSeconds(10)), other = Box("l053_poludnie", Launched.AddHours(3));
|
||||||
|
files.WithFile(Log(own), "b").WrittenAt(Log(own), Launched.AddMinutes(40))
|
||||||
|
.WithFile(Log(other), "b").WrittenAt(Log(other), Launched.AddHours(4));
|
||||||
|
|
||||||
|
var sessions = installation.Tapes.Sessions();
|
||||||
|
|
||||||
|
Assert.Equal(2, sessions.Count);
|
||||||
|
var loose = Assert.Single(sessions, session => session.Launch is null);
|
||||||
|
Assert.EndsWith(other, Assert.Single(loose.BlackBoxes).Path);
|
||||||
|
var launched = Assert.Single(sessions, session => session.Launch is not null);
|
||||||
|
Assert.EndsWith(own, Assert.Single(launched.BlackBoxes).Path);
|
||||||
|
}
|
||||||
|
|
||||||
|
[Fact]
|
||||||
|
public void A_black_box_started_before_a_launch_is_not_taken_for_it()
|
||||||
|
{
|
||||||
|
// Started earlier, and only finished writing after the starter launched the same scenery again.
|
||||||
|
var files = new InMemoryFileSystem();
|
||||||
|
var installation = new TestInstallation(files);
|
||||||
|
installation.Clock.Now = Launched;
|
||||||
|
installation.Tapes.RecordLaunch("td.scn", TimeSpan.Zero, Array.Empty<TapeVehicle>(), "ra");
|
||||||
|
string early = Box("td", Launched.AddHours(-2));
|
||||||
|
files.WithFile(Log(early), "b").WrittenAt(Log(early), Launched.AddMinutes(1));
|
||||||
|
|
||||||
|
var session = Assert.Single(installation.Tapes.Sessions());
|
||||||
|
|
||||||
|
Assert.Null(session.Launch);
|
||||||
|
Assert.Single(session.BlackBoxes);
|
||||||
|
}
|
||||||
|
|
||||||
|
[Theory]
|
||||||
|
[InlineData("$td.scn", "td")]
|
||||||
|
[InlineData("l053_poludnie.scn", "l053_poludnie")]
|
||||||
|
[InlineData("Bałtyk zima.scn", "Ba_tyk_zima")]
|
||||||
|
public void The_scenery_is_named_as_the_simulator_names_the_black_box(string scenery, string stem) =>
|
||||||
|
Assert.Equal(stem, StarterNG.Application.HaslerTapes.SceneryStem(scenery));
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -260,7 +260,7 @@ public sealed class HaslerTapes
|
|||||||
var boxes = _files.DirectoryExists(Folder)
|
var boxes = _files.DirectoryExists(Folder)
|
||||||
? _files.GetFiles(Folder, "*.blackbox").Select(ReadBlackBox).ToList()
|
? _files.GetFiles(Folder, "*.blackbox").Select(ReadBlackBox).ToList()
|
||||||
: new List<BlackBoxRecording>();
|
: new List<BlackBoxRecording>();
|
||||||
var boxesBehind = boxes.GroupBy(box => launches.LastOrDefault(l => l.Covers(box.LastWriteUtc)))
|
var boxesBehind = boxes.GroupBy(box => LaunchOf(box, launches))
|
||||||
.Where(group => group.Key is not null)
|
.Where(group => group.Key is not null)
|
||||||
.ToDictionary(group => group.Key!, group => group.ToList());
|
.ToDictionary(group => group.Key!, group => group.ToList());
|
||||||
|
|
||||||
@@ -281,7 +281,7 @@ public sealed class HaslerTapes
|
|||||||
// What no launch covers goes together by when it was written: a run writes it all at its end.
|
// What no launch covers goes together by when it was written: a run writes it all at its end.
|
||||||
var loose = tapes.Where(t => !launches.Any(l => l.Covers(t.LastWriteUtc)))
|
var loose = tapes.Where(t => !launches.Any(l => l.Covers(t.LastWriteUtc)))
|
||||||
.Select(t => (Written: t.LastWriteUtc, Tape: (TapeFile?)t, Box: (BlackBoxRecording?)null))
|
.Select(t => (Written: t.LastWriteUtc, Tape: (TapeFile?)t, Box: (BlackBoxRecording?)null))
|
||||||
.Concat(boxes.Where(b => !launches.Any(l => l.Covers(b.LastWriteUtc)))
|
.Concat(boxes.Where(b => LaunchOf(b, launches) is null)
|
||||||
.Select(b => (Written: b.LastWriteUtc, Tape: (TapeFile?)null, Box: (BlackBoxRecording?)b)))
|
.Select(b => (Written: b.LastWriteUtc, Tape: (TapeFile?)null, Box: (BlackBoxRecording?)b)))
|
||||||
.OrderBy(file => file.Written);
|
.OrderBy(file => file.Written);
|
||||||
var run = new List<TapeFile>();
|
var run = new List<TapeFile>();
|
||||||
@@ -318,6 +318,50 @@ public sealed class HaslerTapes
|
|||||||
private TapeFile ReadTape(string path) =>
|
private TapeFile ReadTape(string path) =>
|
||||||
new(path, Path.GetFileNameWithoutExtension(path), _files.GetLastWriteTimeUtc(path), _files.GetFileSize(path));
|
new(path, Path.GetFileNameWithoutExtension(path), _files.GetLastWriteTimeUtc(path), _files.GetFileSize(path));
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// The launch a black box came from. Unlike a tape, it says which: its name has the scenery and the moment
|
||||||
|
/// the recording started. So it is the last launch before that moment, and only if that launch was of the
|
||||||
|
/// same scenery; one the starter didn't launch, copied in or started on its own, goes with no launch.
|
||||||
|
/// </summary>
|
||||||
|
public static TapeLaunch? LaunchOf(BlackBoxRecording box, IReadOnlyList<TapeLaunch> launches)
|
||||||
|
{
|
||||||
|
if (NamedBy(box.Path) is not { } named)
|
||||||
|
return launches.LastOrDefault(l => l.Covers(box.LastWriteUtc));
|
||||||
|
var launch = launches.LastOrDefault(l => l.Covers(named.StartedUtc));
|
||||||
|
return launch is not null && string.Equals(SceneryStem(launch.Scenery), named.Scenery, StringComparison.OrdinalIgnoreCase)
|
||||||
|
? launch
|
||||||
|
: null;
|
||||||
|
}
|
||||||
|
|
||||||
|
private const string BlackBoxStamp = "yyyy-MM-dd_HH-mm-ss";
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// The scenery and the start a black box is named by, <c>scenery_YYYY-MM-DD_HH-MM-SS</c> in the local time
|
||||||
|
/// of the computer that ran it; null for a name not made so.
|
||||||
|
/// </summary>
|
||||||
|
public static (string Scenery, DateTime StartedUtc)? NamedBy(string path)
|
||||||
|
{
|
||||||
|
string name = Path.GetFileNameWithoutExtension(path);
|
||||||
|
int stamp = BlackBoxStamp.Length;
|
||||||
|
if (name.Length < stamp + 2 || name[^(stamp + 1)] != '_'
|
||||||
|
|| !DateTime.TryParseExact(name[^stamp..], BlackBoxStamp, CultureInfo.InvariantCulture,
|
||||||
|
DateTimeStyles.AssumeLocal, out var local))
|
||||||
|
return null;
|
||||||
|
return (name[..^(stamp + 1)], local.ToUniversalTime());
|
||||||
|
}
|
||||||
|
|
||||||
|
/// <summary>A scenery's file as the simulator names its black box: no '$' in front, but for ASCII letters, digits, '-' and '_' all '_'.</summary>
|
||||||
|
public static string SceneryStem(string scenery)
|
||||||
|
{
|
||||||
|
string stem = Path.GetFileNameWithoutExtension(scenery);
|
||||||
|
if (stem.StartsWith('$'))
|
||||||
|
stem = stem[1..];
|
||||||
|
var text = new StringBuilder(stem.Length);
|
||||||
|
foreach (char c in stem)
|
||||||
|
text.Append(char.IsAsciiLetterOrDigit(c) || c is '-' or '_' ? c : '_');
|
||||||
|
return text.Length == 0 ? "scenery" : text.ToString();
|
||||||
|
}
|
||||||
|
|
||||||
private BlackBoxRecording ReadBlackBox(string path) =>
|
private BlackBoxRecording ReadBlackBox(string path) =>
|
||||||
new(path, _files.GetLastWriteTimeUtc(path), _files.GetFileSize(path));
|
new(path, _files.GetLastWriteTimeUtc(path), _files.GetFileSize(path));
|
||||||
|
|
||||||
|
|||||||
Reference in New Issue
Block a user