From 3ac792c0fad5496594432497672c1fa6a2009c17 Mon Sep 17 00:00:00 2001 From: maj00r Date: Sun, 4 Oct 2026 14:38:05 +0200 Subject: [PATCH] Move the log and crash dump lookup out of the start use case --- StarterNG.Tests/SimulatorAftermathTests.cs | 39 ++++++++++ StarterNG.Tests/StartSimulationTests.cs | 27 ------- StarterNG/Application/AppServices.cs | 3 + StarterNG/Application/SimulatorAftermath.cs | 81 +++++++++++++++++++++ StarterNG/Application/StartSimulation.cs | 58 --------------- StarterNG/MainWindow.axaml.cs | 4 +- 6 files changed, 125 insertions(+), 87 deletions(-) create mode 100644 StarterNG.Tests/SimulatorAftermathTests.cs create mode 100644 StarterNG/Application/SimulatorAftermath.cs diff --git a/StarterNG.Tests/SimulatorAftermathTests.cs b/StarterNG.Tests/SimulatorAftermathTests.cs new file mode 100644 index 0000000..ecbdf93 --- /dev/null +++ b/StarterNG.Tests/SimulatorAftermathTests.cs @@ -0,0 +1,39 @@ +using StarterNG.Application; +using StarterNG.Tests.Fakes; + +namespace StarterNG.Tests; + +public class SimulatorAftermathTests +{ + private static readonly string Build = + Path.Combine(Path.GetTempPath(), "maszyna", "cmake-build-diag", "bin", "eu07_2026-10-04_fc195880"); + + [Fact] + public void Minidumps_of_this_run_are_found_in_the_installation_and_under_crashpad() + { + var installation = new TestInstallation(new InMemoryFileSystem() + .WithExecutable(Build) + .WithFile(TestInstallation.At("crash_2026-10-04_13-03-12.dmp"), "MDMP") + .WithFile(TestInstallation.At("crashdumps", "reports", "1a2b.dmp"), "MDMP")); + installation.Files.LastWriteTimeUtc = new DateTime(2026, 10, 4, 11, 3, 12, DateTimeKind.Utc); + var aftermath = new SimulatorAftermath(installation.Files, installation.Paths, installation.Log); + + var thisRun = aftermath.CrashDumpsSince(new DateTime(2026, 10, 4, 11, 3, 0, DateTimeKind.Utc), Build); + var later = aftermath.CrashDumpsSince(new DateTime(2026, 10, 4, 12, 0, 0, DateTimeKind.Utc), Build); + + Assert.Equal(2, thisRun.Count); + Assert.Contains(thisRun, dump => dump.EndsWith("crash_2026-10-04_13-03-12.dmp")); + Assert.Empty(later); + } + + [Fact] + public void The_end_of_the_simulator_log_is_read_for_a_silent_crash() + { + var installation = new TestInstallation(new InMemoryFileSystem() + .WithFile(TestInstallation.At("log.txt"), "first\r\nsecond\r\n\r\nthird\r\n")); + + var tail = new SimulatorAftermath(installation.Files, installation.Paths, installation.Log).SimulatorLogTail(2); + + Assert.Equal(new[] { "second", "third" }, tail); + } +} diff --git a/StarterNG.Tests/StartSimulationTests.cs b/StarterNG.Tests/StartSimulationTests.cs index 9c9d563..2940fa6 100644 --- a/StarterNG.Tests/StartSimulationTests.cs +++ b/StarterNG.Tests/StartSimulationTests.cs @@ -68,33 +68,6 @@ public class StartSimulationTests Assert.False(launcher.CapturedErrors); } - [Fact] - public void Minidumps_of_this_run_are_found_in_the_installation_and_under_crashpad() - { - var (start, _, installation) = Rig(files => files - .WithExecutable(Build) - .WithFile(TestInstallation.At("crash_2026-10-04_13-03-12.dmp"), "MDMP") - .WithFile(TestInstallation.At("crashdumps", "reports", "1a2b.dmp"), "MDMP")); - installation.Files.LastWriteTimeUtc = new DateTime(2026, 10, 4, 11, 3, 12, DateTimeKind.Utc); - - var thisRun = start.CrashDumpsSince(new DateTime(2026, 10, 4, 11, 3, 0, DateTimeKind.Utc), Build); - var later = start.CrashDumpsSince(new DateTime(2026, 10, 4, 12, 0, 0, DateTimeKind.Utc), Build); - - Assert.Equal(2, thisRun.Count); - Assert.Contains(thisRun, dump => dump.EndsWith("crash_2026-10-04_13-03-12.dmp")); - Assert.Empty(later); - } - - [Fact] - public void The_end_of_the_simulator_log_is_read_for_a_silent_crash() - { - var (start, _, _) = Rig(files => files - .WithExecutable(Build) - .WithFile(TestInstallation.At("log.txt"), "first\r\nsecond\r\n\r\nthird\r\n")); - - Assert.Equal(new[] { "second", "third" }, start.SimulatorLogTail(2)); - } - [Fact] public void The_editor_opens_the_scenery_itself_not_an_export() { diff --git a/StarterNG/Application/AppServices.cs b/StarterNG/Application/AppServices.cs index 77fd3f4..a2cb1cc 100644 --- a/StarterNG/Application/AppServices.cs +++ b/StarterNG/Application/AppServices.cs @@ -62,6 +62,7 @@ public sealed class AppServices files, clock, log, settingsPaths.UpdateCheckPath()); StartSimulation = new StartSimulation(State, SettingsStore, Library.Vehicles, Executables, files, paths, Processes, Random, log); + Aftermath = new SimulatorAftermath(files, paths, log); } public static AppServices Current => @@ -139,5 +140,7 @@ public sealed class AppServices public StartSimulation StartSimulation { get; } + public SimulatorAftermath Aftermath { get; } + public LocalizationService Localization { get; } } diff --git a/StarterNG/Application/SimulatorAftermath.cs b/StarterNG/Application/SimulatorAftermath.cs new file mode 100644 index 0000000..90f63c7 --- /dev/null +++ b/StarterNG/Application/SimulatorAftermath.cs @@ -0,0 +1,81 @@ +using System; +using System.Collections.Generic; +using System.IO; +using System.Linq; +using StarterNG.Application.Abstractions; +using StarterNG.Infrastructure.Adapters; + +namespace StarterNG.Application; + +/// What a simulator that went down left behind: its log and its crash dumps. +public sealed class SimulatorAftermath +{ + private readonly IFileSystem _files; + private readonly IGamePaths _paths; + private readonly IDiagnosticsLog _log; + + public SimulatorAftermath(IFileSystem files, IGamePaths paths, IDiagnosticsLog log) + { + _files = files; + _paths = paths; + _log = log; + } + + /// + /// Minidumps written since the simulator was started, newest first. The + /// simulator names them crash_*.dmp relative to its working directory - the + /// installation - and crashpad, where it is set up, keeps its own under + /// crashdumps; the folder of the binary is looked at too, for a simulator that + /// was once run from there. + /// + public IReadOnlyList CrashDumpsSince(DateTime startedUtc, string executable) + { + var dumps = new List(); + try + { + foreach (string directory in new[] { _paths.Root, Path.GetDirectoryName(executable) ?? _paths.Root } + .Distinct(StringComparer.OrdinalIgnoreCase) + .Where(_files.DirectoryExists)) + dumps.AddRange(_files.GetFiles(directory, "*.dmp")); + + string crashpad = _paths.FromRoot("crashdumps"); + if (_files.DirectoryExists(crashpad)) + dumps.AddRange(_files.GetFilesRecursive(crashpad, "*.dmp")); + + // A little slack: the start is taken just after the launch, not before it. + var since = startedUtc - TimeSpan.FromSeconds(5); + return dumps.Distinct(StringComparer.OrdinalIgnoreCase) + .Where(dump => _files.GetLastWriteTimeUtc(dump) >= since) + .OrderByDescending(_files.GetLastWriteTimeUtc) + .ToList(); + } + catch (Exception ex) + { + _log.Log("looking for crash dumps", ex); + return Array.Empty(); + } + } + + /// The end of the simulator's own log, for when it died without a word on its error stream. + public IReadOnlyList SimulatorLogTail(int count) + { + string path = _paths.FromRoot("log.txt"); + try + { + if (!_files.FileExists(path)) + return Array.Empty(); + + var lines = LegacyText.Decode(_files.ReadAllBytes(path)) + .Split('\n') + .Select(line => line.TrimEnd('\r')) + .Where(line => line.Trim().Length > 0) + .ToList(); + return lines.Skip(Math.Max(0, lines.Count - count)).ToList(); + } + catch (Exception ex) + { + _log.Log($"reading {path}", ex); + return Array.Empty(); + } + } +} diff --git a/StarterNG/Application/StartSimulation.cs b/StarterNG/Application/StartSimulation.cs index d24d2ac..d8da998 100644 --- a/StarterNG/Application/StartSimulation.cs +++ b/StarterNG/Application/StartSimulation.cs @@ -63,64 +63,6 @@ public sealed class StartSimulation _log = log; } - /// - /// Minidumps written since the simulator was started, newest first. The - /// simulator names them crash_*.dmp relative to its working directory - the - /// installation - and crashpad, where it is set up, keeps its own under - /// crashdumps; the folder of the binary is looked at too, for a simulator that - /// was once run from there. - /// - public IReadOnlyList CrashDumpsSince(DateTime startedUtc, string executable) - { - var dumps = new List(); - try - { - foreach (string directory in new[] { _paths.Root, Path.GetDirectoryName(executable) ?? _paths.Root } - .Distinct(StringComparer.OrdinalIgnoreCase) - .Where(_files.DirectoryExists)) - dumps.AddRange(_files.GetFiles(directory, "*.dmp")); - - string crashpad = _paths.FromRoot("crashdumps"); - if (_files.DirectoryExists(crashpad)) - dumps.AddRange(_files.GetFilesRecursive(crashpad, "*.dmp")); - - // A little slack: the start is taken just after the launch, not before it. - var since = startedUtc - TimeSpan.FromSeconds(5); - return dumps.Distinct(StringComparer.OrdinalIgnoreCase) - .Where(dump => _files.GetLastWriteTimeUtc(dump) >= since) - .OrderByDescending(_files.GetLastWriteTimeUtc) - .ToList(); - } - catch (Exception ex) - { - _log.Log("looking for crash dumps", ex); - return Array.Empty(); - } - } - - /// The end of the simulator's own log, for when it died without a word on its error stream. - public IReadOnlyList SimulatorLogTail(int count) - { - string path = _paths.FromRoot("log.txt"); - try - { - if (!_files.FileExists(path)) - return Array.Empty(); - - var lines = LegacyText.Decode(_files.ReadAllBytes(path)) - .Split('\n') - .Select(line => line.TrimEnd('\r')) - .Where(line => line.Trim().Length > 0) - .ToList(); - return lines.Skip(Math.Max(0, lines.Count - count)).ToList(); - } - catch (Exception ex) - { - _log.Log($"reading {path}", ex); - return Array.Empty(); - } - } - public static string? StartableVehicle(Trainset? trainset, string? preferred) { if (trainset is null || trainset.Vehicles.Count == 0) diff --git a/StarterNG/MainWindow.axaml.cs b/StarterNG/MainWindow.axaml.cs index 6bb4895..2cc5225 100644 --- a/StarterNG/MainWindow.axaml.cs +++ b/StarterNG/MainWindow.axaml.cs @@ -597,7 +597,7 @@ public partial class MainWindow : Window var words = exit.CleanErrors.Count > 0 ? exit.CleanErrors - : AppServices.Current.StartSimulation.SimulatorLogTail(12); + : AppServices.Current.Aftermath.SimulatorLogTail(12); var message = new StringBuilder(); message.AppendLine(string.Format(App.Loc["SimExitCode"], exit.DescribeCode())); @@ -611,7 +611,7 @@ public partial class MainWindow : Window } message.AppendLine().Append(App.Loc["SimExitSeeLog"]); - string? dump = AppServices.Current.StartSimulation.CrashDumpsSince(startedUtc, executable).FirstOrDefault(); + string? dump = AppServices.Current.Aftermath.CrashDumpsSince(startedUtc, executable).FirstOrDefault(); if (dump is not null) message.AppendLine().AppendLine().AppendLine(string.Format(App.Loc["SimExitDump"], dump)) .Append(App.Loc["SimExitOpenDump"]);