Skip to content

Commit

Permalink
Handle exceptions thrown in problem constructors (#142)
Browse files Browse the repository at this point in the history
This aims to tackle an issue uncovered in #140.
  • Loading branch information
eduherminio authored Dec 11, 2022
1 parent 3bdee29 commit a3db61d
Show file tree
Hide file tree
Showing 5 changed files with 122 additions and 7 deletions.
22 changes: 22 additions & 0 deletions src/AoCHelper.PoC/Day05.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,22 @@
namespace AoCHelper.PoC
{
public class Day05 : BaseProblem
{
public Day05()
{
throw new SolvingException("Exception in constructor");
}

public override ValueTask<string> Solve_1()
{
Thread.Sleep(66);
return new("Solution 5.1");
}

public override ValueTask<string> Solve_2()
{
Thread.Sleep(66);
return new("Solution 5.2");
}
}
}
16 changes: 16 additions & 0 deletions src/AoCHelper.PoC/Day06.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,16 @@
namespace AoCHelper.PoC
{
public class Day06 : BaseDay
{
public override ValueTask<string> Solve_1()
{
throw new SolvingException("Exception in part 1");
}

public override ValueTask<string> Solve_2()
{
Thread.Sleep(123);
return new("Solution 6.2");
}
}
}
15 changes: 15 additions & 0 deletions src/AoCHelper.PoC/Day07.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,15 @@
namespace AoCHelper.PoC
{
public class Day07 : BaseDay
{
public override ValueTask<string> Solve_1()
{
return new("Solution 7.1");
}

public override ValueTask<string> Solve_2()
{
throw new SolvingException("Exception in part 2");
}
}
}
68 changes: 62 additions & 6 deletions src/AoCHelper/Solver.cs
Original file line number Diff line number Diff line change
Expand Up @@ -37,14 +37,18 @@ await AnsiConsole.Live(table)
{
var sw = new Stopwatch();
sw.Start();
var potentialProblem = Activator.CreateInstance(lastProblem);
var potentialProblem = InstantiateProblem(lastProblem);
sw.Stop();
if (potentialProblem is BaseProblem problem)
{
await SolveProblem(problem, table, CalculateElapsedMilliseconds(sw), configuration);
ctx.Refresh();
}
else
{
RenderEmptyProblem(lastProblem, potentialProblem as string, table, CalculateElapsedMilliseconds(sw), configuration);
}
}
});
}
Expand Down Expand Up @@ -75,10 +79,17 @@ await AnsiConsole.Live(table)
{
var sw = new Stopwatch();
sw.Start();
TProblem problem = new();
sw.Stop();
try
{
TProblem problem = new();
sw.Stop();
await SolveProblem(problem, table, CalculateElapsedMilliseconds(sw), configuration);
await SolveProblem(problem, table, CalculateElapsedMilliseconds(sw), configuration);
}
catch (Exception e)
{
RenderEmptyProblem(typeof(TProblem), e.Message + Environment.NewLine + e.StackTrace, table, CalculateElapsedMilliseconds(sw), configuration);
}
ctx.Refresh();
});
}
Expand Down Expand Up @@ -131,14 +142,18 @@ await AnsiConsole.Live(table)
if (problems.Contains(problemType))
{
sw.Restart();
var potentialProblem = Activator.CreateInstance(problemType);
var potentialProblem = InstantiateProblem(problemType);
sw.Stop();
if (potentialProblem is BaseProblem problem)
{
totalElapsedTime.Add(await SolveProblem(problem, table, CalculateElapsedMilliseconds(sw), configuration));
ctx.Refresh();
}
else
{
totalElapsedTime.Add(RenderEmptyProblem(problemType, potentialProblem as string, table, CalculateElapsedMilliseconds(sw), configuration));
}
}
}
});
Expand All @@ -149,6 +164,7 @@ await AnsiConsole.Live(table)
/// <summary>
/// Solves those problems whose <see cref="BaseProblem.CalculateIndex"/> method matches one of the provided numbers.
/// 0 can be used for those problems whose <see cref="BaseProblem.CalculateIndex"/> returns the default value due to not being able to deduct the index.
/// This method might not work correctly if any of the problems in the assembly throws an exception in its constructor.
/// </summary>
/// <param name="problemNumbers"></param>
/// <param name="options"></param>
Expand All @@ -174,6 +190,9 @@ await AnsiConsole.Live(table)
foreach (Type problemType in LoadAllProblems(Assembly.GetEntryAssembly()!))
{
sw.Restart();
// Since we're trying to instantiate them all, we don't want to show unrelated errors or render unrelated problem rows
// However, without the index, calculated once the constructor success, we can't separate those unrelated errors from
// our desired problems' ones. So there's a limitation when using this method if other constructors are failing
var potentialProblem = Activator.CreateInstance(problemType);
sw.Stop();
Expand Down Expand Up @@ -215,14 +234,18 @@ await AnsiConsole.Live(table)
foreach (Type problemType in LoadAllProblems(Assembly.GetEntryAssembly()!))
{
sw.Restart();
var potentialProblem = Activator.CreateInstance(problemType);
var potentialProblem = InstantiateProblem(problemType);
sw.Stop();
if (potentialProblem is BaseProblem problem)
{
totalElapsedTime.Add(await SolveProblem(problem, table, CalculateElapsedMilliseconds(sw), configuration));
ctx.Refresh();
}
else
{
totalElapsedTime.Add(RenderEmptyProblem(problemType, potentialProblem as string, table, CalculateElapsedMilliseconds(sw), configuration));
}
}
});

Expand Down Expand Up @@ -507,6 +530,18 @@ private static SolverConfiguration PopulateConfiguration(Action<SolverConfigurat
return configuration;
}

private static object? InstantiateProblem(Type problemType)
{
try
{
return Activator.CreateInstance(problemType);
}
catch (Exception e)
{
return e.InnerException?.Message + Environment.NewLine + e.InnerException?.StackTrace;
}
}

private static async Task<ElapsedTime> SolveProblem(BaseProblem problem, Table table, double constructorElapsedTime, SolverConfiguration configuration)
{
var problemIndex = problem.CalculateIndex();
Expand Down Expand Up @@ -535,6 +570,27 @@ private static async Task<ElapsedTime> SolveProblem(BaseProblem problem, Table t
return new ElapsedTime(constructorElapsedTime, elapsedMillisecondsPart1, elapsedMillisecondsPart2);
}

private static ElapsedTime RenderEmptyProblem(Type problemType, string? exceptionString, Table table, double constructorElapsedTime, SolverConfiguration configuration)
{
var problemTitle = problemType.Name;

RenderRow(table, problemTitle, $"{problemTitle}()", exceptionString ?? "Unhandled exception during constructor", constructorElapsedTime, configuration);

const double elapsedMillisecondsPart1 = 0;
const double elapsedMillisecondsPart2 = 0;
RenderRow(table, problemTitle, "Part 1", "-----------", elapsedMillisecondsPart1, configuration);
RenderRow(table, problemTitle, "Part 2", "-----------", elapsedMillisecondsPart2, configuration);

if (configuration.ShowTotalElapsedTimePerDay)
{
RenderRow(table, problemTitle, "[bold]Total[/]", "-----------", constructorElapsedTime + elapsedMillisecondsPart1 + elapsedMillisecondsPart2, configuration);
}

table.AddEmptyRow();

return new ElapsedTime(constructorElapsedTime, elapsedMillisecondsPart1, elapsedMillisecondsPart2);
}

private static async Task<(string solution, double elapsedTime)> SolvePart(bool isPart1, BaseProblem problem)
{
Stopwatch stopwatch = new();
Expand Down
8 changes: 7 additions & 1 deletion tests/AoCHelper.Test/SolverTest.cs
Original file line number Diff line number Diff line change
Expand Up @@ -43,7 +43,13 @@ private ValueTask<string> Solve()

private class Problem66 : ProblemFixture { }

private class IllCreatedCustomProblem : ProblemFixture { }
private class IllCreatedCustomProblem : ProblemFixture
{
public IllCreatedCustomProblem()
{
throw new Exception();
}
}

[Fact]
public async Task Solve()
Expand Down

0 comments on commit a3db61d

Please sign in to comment.