mirror of
https://github.com/MaSzyna-EU07/StarterNG.git
synced 2026-11-01 20:34:57 +01:00
Add Hasler tape viewer for the simulator's physics logs
Reads physicslog/*.dat and draws each trip as a Hasler tape or, for vehicles with an electronic recorder, as an evaluation screen. Tapes are grouped by launch, and a launch still running can be followed live. Includes whole-tape PNG export and a summary of times and averages. With the log off, the page offers to turn it on.
This commit is contained in:
@@ -37,6 +37,7 @@ public sealed class TestInstallation
|
||||
|
||||
Executables = new ExecutableLocator(Files, Paths, Environment, Log);
|
||||
SettingsStore = new SettingsStore(Files, Clock, Log, SettingsPaths, new SettingsSerializer(), Executables);
|
||||
Tapes = new HaslerTapes(Files, Paths, Log, Clock, new TimetableLocator(Files), () => SimulatorRunning);
|
||||
}
|
||||
|
||||
public InMemoryFileSystem Files { get; }
|
||||
@@ -57,6 +58,8 @@ public sealed class TestInstallation
|
||||
public SettingsPaths SettingsPaths { get; }
|
||||
public ExecutableLocator Executables { get; }
|
||||
public SettingsStore SettingsStore { get; }
|
||||
public HaslerTapes Tapes { get; }
|
||||
public bool SimulatorRunning { get; set; }
|
||||
|
||||
/// <summary>Path of a file inside the fake installation.</summary>
|
||||
public static string At(params string[] segments) => Path.Combine(new[] { Root }.Concat(segments).ToArray());
|
||||
|
||||
168
StarterNG.Tests/HaslerTapeTests.cs
Normal file
168
StarterNG.Tests/HaslerTapeTests.cs
Normal file
@@ -0,0 +1,168 @@
|
||||
using System.Globalization;
|
||||
using StarterNG.Domain.Tapes;
|
||||
using StarterNG.Tests.Fakes;
|
||||
|
||||
namespace StarterNG.Tests;
|
||||
|
||||
public class HaslerTapeTests
|
||||
{
|
||||
private const string Header =
|
||||
"Time[s] Velocity[m/s] Acceleration[m/ss] Coupler.Dist[m] Coupler.Force[N] TractionForce[kN] FrictionForce[kN] " +
|
||||
"BrakeForce[kN] BrakePress[MPa] PipePress[MPa] MotorCurrent[A] MCP SCP BCP LBP Direction Command CVal1 CVal2 " +
|
||||
"Security Wheelslip EngineTemp[Deg] OilTemp[Deg] WaterTemp[Deg] WaterAuxTemp[Deg]\r\n";
|
||||
|
||||
private static string Line(double t, double v, double brake, double current, string command = "none",
|
||||
int security = 0) =>
|
||||
string.Create(CultureInfo.InvariantCulture,
|
||||
$"{t:0.0000} {v:0.0000} 0.0000 0.0000 0.0000 0.0000 0.0000 0.0000 {brake:0.0000} 0.5000 {current:0.0000} ") +
|
||||
$"3 0 0.0000 0.0000 1 {command} 0.0000 0.0000 {security} 0 0.0000 0.0000 0.0000 0.0000\r\n";
|
||||
|
||||
[Fact]
|
||||
public void A_physics_log_reads_as_speed_distance_and_the_two_hasler_marks()
|
||||
{
|
||||
var tape = HaslerTape.Parse(Header
|
||||
+ Line(10, 0, 0.45, 0)
|
||||
+ Line(20, 36, 0, 400)
|
||||
+ Line(30, 72, 0, 0, security: 1));
|
||||
|
||||
Assert.Equal(3, tape.Samples.Count);
|
||||
Assert.Equal(72, tape.MaxSpeedKmh, 6);
|
||||
Assert.Equal(20, tape.Duration, 6);
|
||||
Assert.Equal(50 + 150, tape.DistanceM, 6); // trapezoids: 0->36 km/h and 36->72 km/h over 10 s each
|
||||
Assert.True(tape.Samples[0].Braking);
|
||||
Assert.True(tape.Samples[1].Current);
|
||||
Assert.True(tape.Samples[2].SecurityAlarm);
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public void Standing_and_running_time_split_the_recording_like_the_old_analyser()
|
||||
{
|
||||
var tape = HaslerTape.Parse(Header + Line(0, 0, 0.45, 0) + Line(60, 0, 0.45, 0) + Line(120, 36, 0, 400)
|
||||
+ Line(180, 36, 0, 0));
|
||||
|
||||
Assert.Equal(60, tape.StandingTime, 6);
|
||||
Assert.Equal(120, tape.RunningTime, 6);
|
||||
Assert.Equal(900 / 180.0 * 3.6, tape.AverageWithStopsKmh, 6); // 300 m ramping up to 36 km/h, 600 m at it
|
||||
Assert.Equal(900 / 120.0 * 3.6, tape.AverageSpeedKmh, 6);
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public void A_line_from_the_simulator_reads_in_kmh_and_bar_whatever_its_header_says()
|
||||
{
|
||||
var tape = HaslerTape.Parse(Header +
|
||||
"107.5512 38.6099 -0.0201 -0.0002 917.2355 -0.0000 436.4420 0.0000 0.0021 4.9981 0.0000 0 0 0.0000 " +
|
||||
"0.0000 1 none 0.0000 0.0000 0 0 50.0000 45.0000 50.0000 40.0000\r\n");
|
||||
|
||||
var s = Assert.Single(tape.Samples);
|
||||
Assert.Equal(38.6099, s.SpeedKmh, 6);
|
||||
Assert.Equal(4.9981, s.PipePressBar, 6);
|
||||
Assert.False(s.Braking);
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public void A_line_still_being_written_or_a_command_with_spaces_does_not_break_the_tape()
|
||||
{
|
||||
var tape = HaslerTape.Parse(Header + Line(0, 18, 0, 0, command: "Set Velocity") + Line(1, 18, 0, 0) + "2.0500 5.0");
|
||||
|
||||
Assert.Equal(2, tape.Samples.Count);
|
||||
Assert.Equal(5, tape.DistanceM, 6);
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public void The_sample_under_a_point_of_the_tape_is_found_by_time_and_by_distance()
|
||||
{
|
||||
var tape = HaslerTape.Parse(Header + Line(0, 36, 0, 0) + Line(1, 36, 0, 0) + Line(2, 36, 0, 0));
|
||||
|
||||
Assert.Equal(1, tape.IndexAt(1.5, byDistance: false));
|
||||
Assert.Equal(2, tape.IndexAt(25, byDistance: true));
|
||||
Assert.Equal(0, tape.IndexAt(-3, byDistance: false));
|
||||
}
|
||||
|
||||
[Theory]
|
||||
[InlineData(0, 90)]
|
||||
[InlineData(90, 90)]
|
||||
[InlineData(90.5, 100)]
|
||||
[InlineData(120, 120)]
|
||||
[InlineData(151, 180)]
|
||||
[InlineData(180, 180)]
|
||||
[InlineData(181, 200)]
|
||||
[InlineData(205, 220)]
|
||||
public void The_scale_tops_out_at_the_speedometers_range_then_at_the_next_twenty(double max, double top) =>
|
||||
Assert.Equal(top, TapeFigures.TopSpeed(max));
|
||||
|
||||
[Theory]
|
||||
[InlineData(1, 10)]
|
||||
[InlineData(0.8, 10)]
|
||||
[InlineData(0.79, 20)]
|
||||
[InlineData(0.4, 20)]
|
||||
[InlineData(0.16, 50)]
|
||||
[InlineData(0.08, 100)]
|
||||
[InlineData(0.04, 200)]
|
||||
[InlineData(0.039, 500)]
|
||||
public void Speed_lines_take_the_first_step_at_least_8_pixels_apart(double pxPerKmh, int step) =>
|
||||
Assert.Equal(step, TapeFigures.SpeedStep(pxPerKmh));
|
||||
|
||||
[Theory]
|
||||
[InlineData(60, 1)]
|
||||
[InlineData(59, 2)]
|
||||
[InlineData(30, 2)]
|
||||
[InlineData(12, 5)]
|
||||
[InlineData(6, 10)]
|
||||
[InlineData(4, 15)]
|
||||
[InlineData(2, 30)]
|
||||
[InlineData(1, 60)]
|
||||
[InlineData(0.99, 120)]
|
||||
public void Clock_lines_take_the_first_step_at_least_60_pixels_apart(double pxPerMinute, double step) =>
|
||||
Assert.Equal(step, TapeFigures.MinuteStep(pxPerMinute));
|
||||
|
||||
[Theory]
|
||||
[InlineData(600, 0.1)]
|
||||
[InlineData(500, 0.1)]
|
||||
[InlineData(499, 0.2)]
|
||||
[InlineData(100, 0.5)]
|
||||
[InlineData(50, 1)]
|
||||
[InlineData(25, 2)]
|
||||
[InlineData(10, 5)]
|
||||
[InlineData(5, 10)]
|
||||
[InlineData(2.5, 20)]
|
||||
[InlineData(2, 50)]
|
||||
public void Distance_marks_take_the_first_step_at_least_50_pixels_apart(double pxPerKm, double step) =>
|
||||
Assert.Equal(step, TapeFigures.KmStep(pxPerKm));
|
||||
|
||||
[Theory]
|
||||
[InlineData(-5, "0:00")]
|
||||
[InlineData(0, "0:00")]
|
||||
[InlineData(65, "1:05")]
|
||||
[InlineData(3599, "59:59")]
|
||||
[InlineData(3600, "1:00:00")]
|
||||
[InlineData(3723, "1:02:03")]
|
||||
[InlineData(90000, "25:00:00")]
|
||||
public void A_length_of_time_prints_hours_only_when_it_has_them(double seconds, string text) =>
|
||||
Assert.Equal(text, TapeFigures.Clock(seconds));
|
||||
|
||||
[Theory]
|
||||
[InlineData("7:52", 7, 52, 0)]
|
||||
[InlineData("07:52", 7, 52, 0)]
|
||||
[InlineData(" 23:05 ", 23, 5, 0)]
|
||||
[InlineData("7:52:30", 7, 52, 30)]
|
||||
[InlineData("24:00", 0, 0, 0)]
|
||||
[InlineData("noon", 0, 0, 0)]
|
||||
[InlineData("", 0, 0, 0)]
|
||||
[InlineData(null, 0, 0, 0)]
|
||||
public void A_clock_typed_in_is_a_time_of_the_day_or_nothing(string? text, int hours, int minutes, int seconds) =>
|
||||
Assert.Equal(new TimeSpan(hours, minutes, seconds), TapeFigures.ParseClock(text));
|
||||
|
||||
[Fact]
|
||||
public void Tapes_are_listed_from_physicslog_newest_first()
|
||||
{
|
||||
var installation = new TestInstallation(new InMemoryFileSystem()
|
||||
.WithFile(TestInstallation.At("physicslog", "EU07-424.dat"), Header + Line(0, 1, 0, 0)));
|
||||
var tapes = installation.Tapes;
|
||||
|
||||
var list = tapes.List();
|
||||
|
||||
Assert.Single(list);
|
||||
Assert.Equal("EU07-424", list[0].Vehicle);
|
||||
Assert.Single(tapes.Load(list[0])!.Samples);
|
||||
}
|
||||
}
|
||||
485
StarterNG.Tests/TapeLaunchTests.cs
Normal file
485
StarterNG.Tests/TapeLaunchTests.cs
Normal file
@@ -0,0 +1,485 @@
|
||||
using StarterNG.Application;
|
||||
using StarterNG.Domain.Tapes;
|
||||
using StarterNG.Tests.Fakes;
|
||||
|
||||
namespace StarterNG.Tests;
|
||||
|
||||
public class TapeLaunchTests
|
||||
{
|
||||
private static readonly DateTime Launched = new(2025, 12, 31, 23, 0, 0, DateTimeKind.Utc);
|
||||
|
||||
[Fact]
|
||||
public void A_launch_is_written_and_read_back_with_names_that_hold_spaces()
|
||||
{
|
||||
var launch = new TapeLaunch(Launched, "td.scn", new TimeSpan(7, 52, 0), new[]
|
||||
{
|
||||
new TapeVehicle("EP07-424-HIST", "pkp/ep07_v1", "ep07-424"),
|
||||
new TapeVehicle("wagon 1", "pkp/111a_v2", "111a")
|
||||
});
|
||||
|
||||
var back = TapeLaunch.Parse(launch.Format())!;
|
||||
|
||||
Assert.Equal(Launched, back.LaunchedUtc);
|
||||
Assert.Equal("td.scn", back.Scenery);
|
||||
Assert.Equal(new TimeSpan(7, 52, 0), back.StartClock);
|
||||
Assert.Equal("pkp/ep07_v1", back.Find("ep07-424-hist")!.DataFolder);
|
||||
Assert.Equal("111a", back.Find("wagon 1")!.TypeName);
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public void The_driven_vehicle_and_the_consist_are_written_and_read_back()
|
||||
{
|
||||
var launch = new TapeLaunch(Launched, "td.scn", TimeSpan.Zero, Array.Empty<TapeVehicle>(),
|
||||
"EP07 424", new[] { "EP07 424", "wagon 1" });
|
||||
|
||||
var back = TapeLaunch.Parse(launch.Format())!;
|
||||
|
||||
Assert.Equal(TapeRole.Driven, back.RoleOf("ep07 424"));
|
||||
Assert.Equal(TapeRole.OwnConsist, back.RoleOf("WAGON 1"));
|
||||
Assert.Equal(TapeRole.Other, back.RoleOf("et22-100"));
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public void A_note_without_the_driven_vehicle_tells_nothing_of_it()
|
||||
{
|
||||
var back = TapeLaunch.Parse("launched\t2025-12-31T23:00:00.0000000Z\nscenery\ttd.scn\nclock\t10:30\n")!;
|
||||
|
||||
Assert.Null(back.Driven);
|
||||
Assert.Equal(TapeRole.Other, back.RoleOf("ep07-424"));
|
||||
Assert.Null(back.SceneryName);
|
||||
Assert.Null(back.Train);
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public void The_scenario_and_the_train_are_written_and_read_back()
|
||||
{
|
||||
var launch = new TapeLaunch(Launched, "td.scn", TimeSpan.Zero, Array.Empty<TapeVehicle>(),
|
||||
SceneryName: "Tor doświadczalny", Train: "EIE 1531");
|
||||
|
||||
var back = TapeLaunch.Parse(launch.Format())!;
|
||||
|
||||
Assert.Equal("Tor doświadczalny", back.SceneryName);
|
||||
Assert.Equal("EIE 1531", back.Train);
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public void A_tape_of_the_last_launch_gets_its_scenario_and_train()
|
||||
{
|
||||
var installation = new TestInstallation(new InMemoryFileSystem()
|
||||
.WithFile(TestInstallation.At("physicslog", "EP07-424.dat"), "t"));
|
||||
installation.Clock.Now = Launched;
|
||||
var tapes = installation.Tapes;
|
||||
|
||||
tapes.RecordLaunch("td.scn", TimeSpan.Zero, Array.Empty<TapeVehicle>(), "EP07-424", new[] { "EP07-424" },
|
||||
"Tor doświadczalny", "EIE1531");
|
||||
var context = tapes.ContextOf(tapes.List().Single());
|
||||
|
||||
Assert.Equal("Tor doświadczalny", context.Scenery);
|
||||
Assert.Equal("EIE1531", context.Train);
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public void A_tape_of_someone_elses_vehicle_does_not_get_the_train_of_the_player()
|
||||
{
|
||||
var installation = new TestInstallation(new InMemoryFileSystem()
|
||||
.WithFile(TestInstallation.At("physicslog", "ET22-100.dat"), "t"));
|
||||
installation.Clock.Now = Launched;
|
||||
var tapes = installation.Tapes;
|
||||
|
||||
tapes.RecordLaunch("td.scn", TimeSpan.Zero, Array.Empty<TapeVehicle>(), "EP07-424", new[] { "EP07-424" },
|
||||
"Tor doświadczalny", "EIE1531");
|
||||
var context = tapes.ContextOf(tapes.List().Single());
|
||||
|
||||
Assert.Equal("Tor doświadczalny", context.Scenery);
|
||||
Assert.Null(context.Train);
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public void A_scenario_without_a_name_goes_by_its_file()
|
||||
{
|
||||
var installation = new TestInstallation(new InMemoryFileSystem()
|
||||
.WithFile(TestInstallation.At("physicslog", "EP07-424.dat"), "t"));
|
||||
installation.Clock.Now = Launched;
|
||||
var tapes = installation.Tapes;
|
||||
|
||||
tapes.RecordLaunch("td.scn", TimeSpan.Zero, Array.Empty<TapeVehicle>());
|
||||
|
||||
Assert.Equal("td.scn", tapes.ContextOf(tapes.List().Single()).Scenery);
|
||||
}
|
||||
|
||||
[Theory]
|
||||
[InlineData("[ Rodzaj i numer pociagu | EIE1531 \t ] \r\n[ Kategoria | IC ]\r\n", "EIE1531")]
|
||||
[InlineData(" ____\r\n[ Rodzaj i numer pociągu | LPE 782 ]\r\n", "LPE 782")]
|
||||
[InlineData("[ Kategoria | IC ]\r\n", null)]
|
||||
[InlineData("", null)]
|
||||
public void The_train_number_comes_from_the_heading_of_its_timetable(string timetable, string? number)
|
||||
{
|
||||
var files = new InMemoryFileSystem().WithFile(TestInstallation.At("scenery", "baltyk", "eie1531.txt"), timetable);
|
||||
|
||||
Assert.Equal(number, new StarterNG.Infrastructure.Sceneries.TimetableLocator(files)
|
||||
.TrainNumber(new StarterNG.Domain.Sceneries.Scenery(TestInstallation.At("scenery", "td.scn"), ""),
|
||||
"baltyk/eie1531"));
|
||||
}
|
||||
|
||||
[Theory]
|
||||
[InlineData("none")]
|
||||
[InlineData("NONE")]
|
||||
[InlineData("")]
|
||||
[InlineData(null)]
|
||||
public void A_trainset_without_a_timetable_has_no_train_number(string? name) =>
|
||||
Assert.Null(new StarterNG.Infrastructure.Sceneries.TimetableLocator(new InMemoryFileSystem())
|
||||
.TrainNumber(new StarterNG.Domain.Sceneries.Scenery(TestInstallation.At("scenery", "td.scn"), ""), name));
|
||||
|
||||
[Fact]
|
||||
public void A_timetable_that_is_not_there_gives_no_train_number() =>
|
||||
Assert.Null(new StarterNG.Infrastructure.Sceneries.TimetableLocator(new InMemoryFileSystem())
|
||||
.TrainNumber(new StarterNG.Domain.Sceneries.Scenery(TestInstallation.At("scenery", "td.scn"), ""),
|
||||
@"baltyk\mod\eie1531"));
|
||||
|
||||
[Fact]
|
||||
public void The_driven_tape_and_the_consist_are_listed_ahead_of_the_rest()
|
||||
{
|
||||
var installation = new TestInstallation(new InMemoryFileSystem()
|
||||
.WithFile(TestInstallation.At("physicslog", "a-other.dat"), "t")
|
||||
.WithFile(TestInstallation.At("physicslog", "b-wagon.dat"), "t")
|
||||
.WithFile(TestInstallation.At("physicslog", "c-lok.dat"), "t"));
|
||||
installation.Clock.Now = Launched;
|
||||
var tapes = installation.Tapes;
|
||||
|
||||
tapes.RecordLaunch("td.scn", TimeSpan.Zero, Array.Empty<TapeVehicle>(), "c-lok", new[] { "c-lok", "b-wagon" });
|
||||
|
||||
Assert.Equal("c-lok", tapes.DrivenVehicle);
|
||||
Assert.Equal(new[] { "c-lok", "b-wagon", "a-other" }, tapes.List().Select(tape => tape.Vehicle));
|
||||
Assert.Equal(new[] { TapeRole.StartedIn, TapeRole.OwnConsist, TapeRole.Other }, tapes.List().Select(tape => tape.Role));
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public void With_no_log_the_vehicle_of_the_launch_is_only_the_one_it_started_in()
|
||||
{
|
||||
var launch = new TapeLaunch(Launched, "td.scn", TimeSpan.Zero, Array.Empty<TapeVehicle>(), "ra", new[] { "ra", "rb" });
|
||||
|
||||
Assert.Equal(TapeRole.StartedIn, launch.RoleOf("ra", PlayerVehicles.Unknown));
|
||||
Assert.Equal(TapeRole.OwnConsist, launch.RoleOf("rb", PlayerVehicles.Unknown));
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public void Where_the_player_has_been_is_written_and_read_back()
|
||||
{
|
||||
var launch = new TapeLaunch(Launched, "td.scn", TimeSpan.Zero, Array.Empty<TapeVehicle>(),
|
||||
Player: PlayerVehicles.None.After("a received command: [x]\nb received command: [x]\n"));
|
||||
|
||||
var back = TapeLaunch.Parse(launch.Format())!;
|
||||
|
||||
Assert.Equal("b", back.Player!.Current);
|
||||
Assert.True(back.Player.Used("a"));
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public void A_note_of_a_single_launch_still_reads()
|
||||
{
|
||||
var all = TapeLaunch.ParseAll("launched\t2025-12-31T23:00:00.0000000Z\nscenery\ttd.scn\nclock\t10:30\n");
|
||||
|
||||
Assert.Equal("td.scn", all.Single().Scenery);
|
||||
Assert.Null(all.Single().Player);
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public void Tapes_part_between_the_launches_they_were_written_after()
|
||||
{
|
||||
var files = new InMemoryFileSystem()
|
||||
.WithFile(TestInstallation.At("physicslog", "ra.dat"), "t")
|
||||
.WithFile(TestInstallation.At("physicslog", "rb.dat"), "t");
|
||||
var installation = new TestInstallation(files);
|
||||
var tapes = installation.Tapes;
|
||||
installation.Clock.Now = Launched;
|
||||
tapes.RecordLaunch("a.scn", new TimeSpan(7, 0, 0), Array.Empty<TapeVehicle>(), "ra", new[] { "ra" }, "A", "T1");
|
||||
installation.Clock.Now = Launched.AddHours(1);
|
||||
tapes.RecordLaunch("b.scn", new TimeSpan(9, 0, 0), Array.Empty<TapeVehicle>(), "rb", new[] { "rb" }, "B", "T2");
|
||||
files.WrittenAt(TestInstallation.At("physicslog", "ra.dat"), Launched.AddMinutes(30))
|
||||
.WrittenAt(TestInstallation.At("physicslog", "rb.dat"), Launched.AddMinutes(90));
|
||||
|
||||
var sessions = tapes.Sessions();
|
||||
|
||||
Assert.Equal(new[] { "B", "A" }, sessions.Select(session => session.Launch!.SceneryName));
|
||||
Assert.Equal("rb", sessions[0].Tapes.Single().Vehicle);
|
||||
Assert.Equal("ra", sessions[1].Tapes.Single().Vehicle);
|
||||
Assert.Equal(new TimeSpan(7, 0, 0), tapes.ContextOf(sessions[1].Tapes[0]).StartClock);
|
||||
Assert.Equal("T1", tapes.ContextOf(sessions[1].Tapes[0]).Train);
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public void Tapes_with_no_launch_noted_go_together_by_when_they_were_written()
|
||||
{
|
||||
var early = new DateTime(2026, 3, 1, 12, 0, 0, DateTimeKind.Utc);
|
||||
var installation = new TestInstallation(new InMemoryFileSystem()
|
||||
.WithFile(TestInstallation.At("physicslog", "a.dat"), "t")
|
||||
.WithFile(TestInstallation.At("physicslog", "b.dat"), "t")
|
||||
.WithFile(TestInstallation.At("physicslog", "c.dat"), "t")
|
||||
.WrittenAt(TestInstallation.At("physicslog", "a.dat"), early)
|
||||
.WrittenAt(TestInstallation.At("physicslog", "b.dat"), early.AddSeconds(30))
|
||||
.WrittenAt(TestInstallation.At("physicslog", "c.dat"), early.AddHours(1)));
|
||||
|
||||
var sessions = installation.Tapes.Sessions();
|
||||
|
||||
Assert.Equal(new[] { 1, 2 }, sessions.Select(session => session.Tapes.Count));
|
||||
Assert.All(sessions, session => Assert.Null(session.Launch));
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public void A_running_session_is_listed_with_no_tapes_yet_and_goes_with_the_simulator()
|
||||
{
|
||||
var installation = new TestInstallation(new InMemoryFileSystem());
|
||||
installation.Clock.Now = Launched;
|
||||
var tapes = installation.Tapes;
|
||||
tapes.RecordLaunch("td.scn", TimeSpan.Zero, Array.Empty<TapeVehicle>(), "ra");
|
||||
|
||||
installation.SimulatorRunning = true;
|
||||
var running = tapes.Sessions().Single();
|
||||
Assert.True(running.Running);
|
||||
Assert.Empty(running.Tapes);
|
||||
|
||||
installation.SimulatorRunning = false;
|
||||
Assert.Empty(tapes.Sessions());
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public void A_log_of_its_own_tells_of_the_vehicles_when_the_common_one_is_stale()
|
||||
{
|
||||
string kept = TestInstallation.At("logs", "log (td.scn) 2026.txt");
|
||||
var installation = new TestInstallation(new InMemoryFileSystem()
|
||||
.WithFile(TestInstallation.At("log.txt"), "ra received command: [x]\n")
|
||||
.WithFile(kept, "rb received command: [x]\n")
|
||||
.WrittenAt(TestInstallation.At("log.txt"), Launched.AddHours(-2))
|
||||
.WrittenAt(kept, Launched.AddMinutes(10)));
|
||||
installation.Clock.Now = Launched;
|
||||
var tapes = installation.Tapes;
|
||||
|
||||
tapes.RecordLaunch("td.scn", TimeSpan.Zero, Array.Empty<TapeVehicle>(), "ra", new[] { "ra", "rb" });
|
||||
|
||||
Assert.Equal("rb", tapes.DrivenVehicle);
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public void The_vehicles_of_a_run_stay_with_it_when_the_next_begins()
|
||||
{
|
||||
var (tapes, installation) = TapesOfTrain("ra received command: [x]\nrb received command: [x]\n");
|
||||
var written = Launched.AddMinutes(30);
|
||||
installation.Files.WrittenAt(TestInstallation.At("log.txt"), written)
|
||||
.WrittenAt(TestInstallation.At("physicslog", "ra.dat"), written)
|
||||
.WrittenAt(TestInstallation.At("physicslog", "rb.dat"), written)
|
||||
.WrittenAt(TestInstallation.At("physicslog", "obcy.dat"), written);
|
||||
|
||||
installation.Clock.Now = Launched.AddHours(1);
|
||||
tapes.RecordLaunch("td.scn", TimeSpan.Zero, Array.Empty<TapeVehicle>(), "inny", new[] { "inny" });
|
||||
|
||||
var run = tapes.Sessions().Single();
|
||||
Assert.Equal("Test", run.Launch!.SceneryName);
|
||||
Assert.Equal(TapeRole.Driven, run.Tapes.Single(tape => tape.Vehicle == "rb").Role);
|
||||
Assert.Equal(TapeRole.DrivenEarlier, run.Tapes.Single(tape => tape.Vehicle == "ra").Role);
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public void Only_the_last_launches_are_remembered()
|
||||
{
|
||||
var installation = new TestInstallation(new InMemoryFileSystem());
|
||||
var tapes = installation.Tapes;
|
||||
for (int i = 0; i < 35; i++)
|
||||
{
|
||||
installation.Clock.Now = Launched.AddMinutes(i);
|
||||
tapes.RecordLaunch($"s{i}.scn", TimeSpan.Zero, Array.Empty<TapeVehicle>());
|
||||
}
|
||||
|
||||
var noted = TapeLaunch.ParseAll(installation.Files.ReadAllText(
|
||||
TestInstallation.At("physicslog", "starter-launch.txt"), System.Text.Encoding.UTF8));
|
||||
|
||||
Assert.Equal(30, noted.Count);
|
||||
Assert.Equal("s5.scn", noted[0].Scenery);
|
||||
Assert.Equal("s34.scn", noted[^1].Scenery);
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public void The_vehicle_the_commands_go_to_is_the_one_the_player_is_in()
|
||||
{
|
||||
var player = PlayerVehicles.None.After(
|
||||
"[ 21.764 ]\t\tTrying to enter player train, \"lok-a\"\r\n" +
|
||||
"[ 25.609 ]\t\tlok-a received command: [aidriverdisable]\r\n" +
|
||||
"[ 255.539 ]\t\tKey pressed: [F5]\r\n" +
|
||||
"[ 255.569 ]\t\tlok b received command: [aidriverdisable]\r\n" +
|
||||
"lok-c received command: [cabchangebackward]\n");
|
||||
|
||||
Assert.Equal("lok-c", player.Current);
|
||||
Assert.True(player.Used("LOK-A"));
|
||||
Assert.True(player.Used("lok b"));
|
||||
Assert.False(player.Used("lok-d"));
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public void Going_back_to_a_vehicle_makes_it_the_current_one_again()
|
||||
{
|
||||
var player = PlayerVehicles.None
|
||||
.After("a received command: [x]\nb received command: [x]\n")
|
||||
.After("A received command: [x]\n");
|
||||
|
||||
Assert.Equal("A", player.Current);
|
||||
Assert.True(player.Used("b"));
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public void After_a_change_of_vehicle_the_new_one_is_driven_and_the_first_was_driven_earlier()
|
||||
{
|
||||
var launch = new TapeLaunch(Launched, "td.scn", TimeSpan.Zero, Array.Empty<TapeVehicle>(),
|
||||
"ra", new[] { "ra", "s", "rb" });
|
||||
var player = PlayerVehicles.None.After("ra received command: [x]\nrb received command: [x]\n");
|
||||
|
||||
Assert.Equal(TapeRole.Driven, launch.RoleOf("RB", player));
|
||||
Assert.Equal(TapeRole.DrivenEarlier, launch.RoleOf("ra", player));
|
||||
Assert.Equal(TapeRole.OwnConsist, launch.RoleOf("s", player));
|
||||
Assert.Equal(TapeRole.Other, launch.RoleOf("obcy", player));
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public void Before_the_first_command_the_vehicle_of_the_launch_is_driven()
|
||||
{
|
||||
var launch = new TapeLaunch(Launched, "td.scn", TimeSpan.Zero, Array.Empty<TapeVehicle>(), "ra", new[] { "ra", "rb" });
|
||||
|
||||
Assert.Equal(TapeRole.Driven, launch.RoleOf("ra", PlayerVehicles.None));
|
||||
Assert.Equal(TapeRole.OwnConsist, launch.RoleOf("rb", PlayerVehicles.None));
|
||||
}
|
||||
|
||||
private static (HaslerTapes Tapes, TestInstallation Installation) TapesOfTrain(string log)
|
||||
{
|
||||
var installation = new TestInstallation(new InMemoryFileSystem()
|
||||
.WithFile(TestInstallation.At("physicslog", "ra.dat"), "t")
|
||||
.WithFile(TestInstallation.At("physicslog", "rb.dat"), "t")
|
||||
.WithFile(TestInstallation.At("physicslog", "obcy.dat"), "t")
|
||||
.WithFile(TestInstallation.At("log.txt"), log));
|
||||
installation.Clock.Now = Launched;
|
||||
var tapes = installation.Tapes;
|
||||
tapes.RecordLaunch("td.scn", TimeSpan.Zero, Array.Empty<TapeVehicle>(), "ra", new[] { "ra", "rb" }, "Test", "MPJ1");
|
||||
return (tapes, installation);
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public void The_tape_of_the_vehicle_changed_to_comes_first_and_keeps_the_train()
|
||||
{
|
||||
var (tapes, _) = TapesOfTrain("ra received command: [x]\nrb received command: [x]\n");
|
||||
|
||||
Assert.Equal(new[] { "rb", "ra", "obcy" }, tapes.List().Select(tape => tape.Vehicle));
|
||||
Assert.Equal(new[] { TapeRole.Driven, TapeRole.DrivenEarlier, TapeRole.Other }, tapes.List().Select(tape => tape.Role));
|
||||
Assert.Equal("rb", tapes.DrivenVehicle);
|
||||
Assert.Equal("MPJ1", tapes.ContextOf(tapes.List().First()).Train);
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public void A_vehicle_of_another_train_changed_to_is_driven_without_the_number_of_the_first()
|
||||
{
|
||||
var (tapes, _) = TapesOfTrain("obcy received command: [x]\n");
|
||||
|
||||
var driven = tapes.List().First();
|
||||
|
||||
Assert.Equal("obcy", driven.Vehicle);
|
||||
Assert.Null(tapes.ContextOf(driven).Train);
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public void A_change_of_vehicle_during_the_run_is_seen_at_the_next_look()
|
||||
{
|
||||
const string start = "ra received command: [x]\n";
|
||||
var (tapes, installation) = TapesOfTrain(start);
|
||||
Assert.Equal("ra", tapes.DrivenVehicle);
|
||||
|
||||
installation.Files.WithFile(TestInstallation.At("log.txt"), start + "rb received com");
|
||||
Assert.Equal("ra", tapes.DrivenVehicle);
|
||||
|
||||
installation.Files.WithFile(TestInstallation.At("log.txt"), start + "rb received command: [x]\n");
|
||||
Assert.Equal("rb", tapes.DrivenVehicle);
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public void A_log_begun_anew_forgets_the_vehicles_of_the_old_one()
|
||||
{
|
||||
var (tapes, installation) = TapesOfTrain("ra received command: [x]\nrb received command: [x]\n");
|
||||
Assert.Equal("rb", tapes.DrivenVehicle);
|
||||
|
||||
installation.Files.WithFile(TestInstallation.At("log.txt"), "obcy received command: [x]\n");
|
||||
|
||||
Assert.Equal("obcy", tapes.DrivenVehicle);
|
||||
Assert.Equal(TapeRole.OwnConsist, tapes.List().Single(tape => tape.Vehicle == "rb").Role);
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public void A_log_older_than_the_launch_says_nothing_of_it()
|
||||
{
|
||||
var (tapes, installation) = TapesOfTrain("rb received command: [x]\n");
|
||||
installation.Clock.Now = installation.Files.LastWriteTimeUtc.AddMinutes(10);
|
||||
tapes.RecordLaunch("td.scn", TimeSpan.Zero, Array.Empty<TapeVehicle>(), "ra", new[] { "ra", "rb" });
|
||||
|
||||
Assert.Equal("ra", tapes.DrivenVehicle);
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public void A_tape_older_than_the_last_launch_has_no_part_in_it()
|
||||
{
|
||||
var installation = new TestInstallation(new InMemoryFileSystem()
|
||||
.WithFile(TestInstallation.At("physicslog", "c-lok.dat"), "t"));
|
||||
installation.Clock.Now = installation.Files.LastWriteTimeUtc.AddDays(1);
|
||||
var tapes = installation.Tapes;
|
||||
|
||||
tapes.RecordLaunch("td.scn", TimeSpan.Zero, Array.Empty<TapeVehicle>(), "c-lok");
|
||||
|
||||
Assert.Equal(TapeRole.Other, tapes.List().Single().Role);
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public void A_log_written_before_the_launch_is_not_taken_for_one_of_it()
|
||||
{
|
||||
var launch = new TapeLaunch(Launched, "td.scn", TimeSpan.Zero, Array.Empty<TapeVehicle>());
|
||||
|
||||
Assert.True(launch.Covers(Launched.AddMinutes(3)));
|
||||
Assert.False(launch.Covers(Launched.AddHours(-1)));
|
||||
}
|
||||
|
||||
[Theory]
|
||||
[InlineData("pkp/ep07_v1", "ep07-424", RecorderKind.Hasler)]
|
||||
[InlineData("pkp/en57al_v1", "en57al-1234", RecorderKind.Electronic)]
|
||||
[InlineData("pkp/x4e_v1", "x4e-659", RecorderKind.Electronic)]
|
||||
[InlineData("pkp/et22_v3", "et22-1100", RecorderKind.Hasler)]
|
||||
public void The_recorder_follows_the_vehicle_behind_the_name(string folder, string type, RecorderKind kind) =>
|
||||
Assert.Equal(kind, Recorders.Guess(new TapeVehicle("poc1_lok", folder, type), "poc1_lok"));
|
||||
|
||||
[Fact]
|
||||
public void Without_a_launch_the_tape_name_is_all_there_is_to_go_by()
|
||||
{
|
||||
Assert.Equal(RecorderKind.Electronic, Recorders.Guess(null, "ED250-001"));
|
||||
Assert.Equal(RecorderKind.Hasler, Recorders.Guess(null, "eu07-193"));
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public void A_tape_of_the_last_launch_gets_its_clock_and_recorder()
|
||||
{
|
||||
var installation = new TestInstallation(new InMemoryFileSystem()
|
||||
.WithFile(TestInstallation.At("physicslog", "ED250-001.dat"), "t"));
|
||||
installation.Clock.Now = Launched;
|
||||
var tapes = installation.Tapes;
|
||||
|
||||
tapes.RecordLaunch("lk4.scn", new TimeSpan(14, 5, 0),
|
||||
new[] { new TapeVehicle("ED250-001", "pkp/ed250_v1", "ed250-001") });
|
||||
var context = tapes.ContextOf(tapes.List().Single());
|
||||
|
||||
Assert.Equal(new TimeSpan(14, 5, 0), context.StartClock);
|
||||
Assert.Equal(RecorderKind.Electronic, context.Recorder);
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public void A_tape_older_than_the_last_launch_keeps_its_hour_unknown()
|
||||
{
|
||||
var installation = new TestInstallation(new InMemoryFileSystem()
|
||||
.WithFile(TestInstallation.At("physicslog", "EU07-424.dat"), "t"));
|
||||
installation.Clock.Now = installation.Files.LastWriteTimeUtc.AddDays(1);
|
||||
var tapes = installation.Tapes;
|
||||
|
||||
tapes.RecordLaunch("td.scn", new TimeSpan(8, 0, 0), Array.Empty<TapeVehicle>());
|
||||
|
||||
Assert.Null(tapes.ContextOf(tapes.List().Single()).StartClock);
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user