diff --git a/TrayApp.Test/ConsoleRunnerTests.cs b/TrayApp.Test/ConsoleRunnerTests.cs index f0dd286..b00dcaa 100644 --- a/TrayApp.Test/ConsoleRunnerTests.cs +++ b/TrayApp.Test/ConsoleRunnerTests.cs @@ -88,7 +88,7 @@ public void Run_WhenTheToolRefusesToStart_ExitsOneWithoutStopping() } [TestMethod] - public void Run_WhenTheRunItselfThrows_StillStops() + public void Run_WhenTheStatusThrows_ReportsItAndStillRunsAndStops() { bool stopped = false; @@ -97,10 +97,26 @@ public void Run_WhenTheRunItselfThrows_StillStops() .OnStop(() => stopped = true) .Build(); - // Describing the run is the tool's code too. Whatever it does, the release on the way out is the - // library's promise, so it happens in a finally rather than after the last statement. - Assert.ThrowsExactly(() => ConsoleRunner.Run(app, ShortRun())); + // Describing the run is the tool's code too, and a getter that throws is most likely to do so in the + // environment that sends a run here, so it is the line's text, as in --status, not an unhandled + // exception that breaks the exit-code contract. + using System.IO.StringWriter output = new(); + System.IO.TextWriter original = Console.Out; + Console.SetOut(output); + int exitCode; + try + { + exitCode = ConsoleRunner.Run(app, ShortRun()); + } + finally + { + Console.SetOut(original); + } + + Assert.AreEqual(0, exitCode); Assert.IsTrue(stopped); + Assert.Contains("demo: error: the status broke, stopping after", output.ToString()); + Assert.Contains("demo: stopped.", output.ToString()); } [TestMethod] diff --git a/TrayApp/ConsoleRunner.cs b/TrayApp/ConsoleRunner.cs index 2b0da29..afcf132 100644 --- a/TrayApp/ConsoleRunner.cs +++ b/TrayApp/ConsoleRunner.cs @@ -94,9 +94,14 @@ private static string DescribeRun(TrayAppDefinition app, CommandLineOptions opti ? string.Create(CultureInfo.InvariantCulture, $", stopping after {window:g}") : string.Empty; + // The getter is the tool's code, and it is most likely to throw for the same reason the tool cannot + // work - a missing helper, say - which is also when the tray falls back to this mode. It is read the + // way --status reads it, so it becomes the line's text rather than an unhandled exception. + string state = app.Status is Func status ? StatusReport.Read(status) : app.Describe(); + return string.Create( CultureInfo.InvariantCulture, - $"{app.Name}: {app.Describe()}{until}. Press Ctrl+C to stop."); + $"{app.Name}: {state}{until}. Press Ctrl+C to stop."); } ///