mirror of
https://github.com/MaSzyna-EU07/StarterNG.git
synced 2026-10-16 23:49:19 +02:00
Move the log and crash dump lookup out of the start use case
This commit is contained in:
39
StarterNG.Tests/SimulatorAftermathTests.cs
Normal file
39
StarterNG.Tests/SimulatorAftermathTests.cs
Normal file
@@ -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);
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -68,33 +68,6 @@ public class StartSimulationTests
|
|||||||
Assert.False(launcher.CapturedErrors);
|
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]
|
[Fact]
|
||||||
public void The_editor_opens_the_scenery_itself_not_an_export()
|
public void The_editor_opens_the_scenery_itself_not_an_export()
|
||||||
{
|
{
|
||||||
|
|||||||
@@ -62,6 +62,7 @@ public sealed class AppServices
|
|||||||
files, clock, log, settingsPaths.UpdateCheckPath());
|
files, clock, log, settingsPaths.UpdateCheckPath());
|
||||||
StartSimulation = new StartSimulation(State, SettingsStore, Library.Vehicles, Executables, files, paths,
|
StartSimulation = new StartSimulation(State, SettingsStore, Library.Vehicles, Executables, files, paths,
|
||||||
Processes, Random, log);
|
Processes, Random, log);
|
||||||
|
Aftermath = new SimulatorAftermath(files, paths, log);
|
||||||
}
|
}
|
||||||
|
|
||||||
public static AppServices Current =>
|
public static AppServices Current =>
|
||||||
@@ -139,5 +140,7 @@ public sealed class AppServices
|
|||||||
|
|
||||||
public StartSimulation StartSimulation { get; }
|
public StartSimulation StartSimulation { get; }
|
||||||
|
|
||||||
|
public SimulatorAftermath Aftermath { get; }
|
||||||
|
|
||||||
public LocalizationService Localization { get; }
|
public LocalizationService Localization { get; }
|
||||||
}
|
}
|
||||||
|
|||||||
81
StarterNG/Application/SimulatorAftermath.cs
Normal file
81
StarterNG/Application/SimulatorAftermath.cs
Normal file
@@ -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;
|
||||||
|
|
||||||
|
/// <summary>What a simulator that went down left behind: its log and its crash dumps.</summary>
|
||||||
|
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;
|
||||||
|
}
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// 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.
|
||||||
|
/// </summary>
|
||||||
|
public IReadOnlyList<string> CrashDumpsSince(DateTime startedUtc, string executable)
|
||||||
|
{
|
||||||
|
var dumps = new List<string>();
|
||||||
|
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<string>();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/// <summary>The end of the simulator's own log, for when it died without a word on its error stream.</summary>
|
||||||
|
public IReadOnlyList<string> SimulatorLogTail(int count)
|
||||||
|
{
|
||||||
|
string path = _paths.FromRoot("log.txt");
|
||||||
|
try
|
||||||
|
{
|
||||||
|
if (!_files.FileExists(path))
|
||||||
|
return Array.Empty<string>();
|
||||||
|
|
||||||
|
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<string>();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -63,64 +63,6 @@ public sealed class StartSimulation
|
|||||||
_log = log;
|
_log = log;
|
||||||
}
|
}
|
||||||
|
|
||||||
/// <summary>
|
|
||||||
/// 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.
|
|
||||||
/// </summary>
|
|
||||||
public IReadOnlyList<string> CrashDumpsSince(DateTime startedUtc, string executable)
|
|
||||||
{
|
|
||||||
var dumps = new List<string>();
|
|
||||||
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<string>();
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
/// <summary>The end of the simulator's own log, for when it died without a word on its error stream.</summary>
|
|
||||||
public IReadOnlyList<string> SimulatorLogTail(int count)
|
|
||||||
{
|
|
||||||
string path = _paths.FromRoot("log.txt");
|
|
||||||
try
|
|
||||||
{
|
|
||||||
if (!_files.FileExists(path))
|
|
||||||
return Array.Empty<string>();
|
|
||||||
|
|
||||||
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<string>();
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
public static string? StartableVehicle(Trainset? trainset, string? preferred)
|
public static string? StartableVehicle(Trainset? trainset, string? preferred)
|
||||||
{
|
{
|
||||||
if (trainset is null || trainset.Vehicles.Count == 0)
|
if (trainset is null || trainset.Vehicles.Count == 0)
|
||||||
|
|||||||
@@ -597,7 +597,7 @@ public partial class MainWindow : Window
|
|||||||
|
|
||||||
var words = exit.CleanErrors.Count > 0
|
var words = exit.CleanErrors.Count > 0
|
||||||
? exit.CleanErrors
|
? exit.CleanErrors
|
||||||
: AppServices.Current.StartSimulation.SimulatorLogTail(12);
|
: AppServices.Current.Aftermath.SimulatorLogTail(12);
|
||||||
|
|
||||||
var message = new StringBuilder();
|
var message = new StringBuilder();
|
||||||
message.AppendLine(string.Format(App.Loc["SimExitCode"], exit.DescribeCode()));
|
message.AppendLine(string.Format(App.Loc["SimExitCode"], exit.DescribeCode()));
|
||||||
@@ -611,7 +611,7 @@ public partial class MainWindow : Window
|
|||||||
}
|
}
|
||||||
message.AppendLine().Append(App.Loc["SimExitSeeLog"]);
|
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)
|
if (dump is not null)
|
||||||
message.AppendLine().AppendLine().AppendLine(string.Format(App.Loc["SimExitDump"], dump))
|
message.AppendLine().AppendLine().AppendLine(string.Format(App.Loc["SimExitDump"], dump))
|
||||||
.Append(App.Loc["SimExitOpenDump"]);
|
.Append(App.Loc["SimExitOpenDump"]);
|
||||||
|
|||||||
Reference in New Issue
Block a user