Add Result methods for debugging (#90)

- Allow setting a callback function for when Result.Log is called.
- Allow setting a function that returns a name for a Result value.
- Print Result name in error messages
This commit is contained in:
Alex Barney 2019-10-25 22:13:09 -05:00 committed by GitHub
parent 172817a7d5
commit cccb811293
No known key found for this signature in database
GPG key ID: 4AEE18F83AFDEB23
6 changed files with 145 additions and 4 deletions

View file

@ -82,10 +82,10 @@ namespace LibHac
{
if (!string.IsNullOrWhiteSpace(InnerMessage))
{
return $"{ResultValue.ErrorCode}: {InnerMessage}";
return $"{ResultValue.ToStringWithName()}: {InnerMessage}";
}
return ResultValue.ErrorCode;
return ResultValue.ToStringWithName();
}
}
}

View file

@ -4,7 +4,7 @@ using System.Diagnostics;
namespace LibHac
{
[Serializable]
[DebuggerDisplay("{ToString()}")]
[DebuggerDisplay("{ToStringWithName(),nq}")]
public struct Result : IEquatable<Result>
{
public readonly int Value;
@ -42,6 +42,9 @@ namespace LibHac
/// <returns>The called <see cref="Result"/> value.</returns>
public Result Log()
{
#if DEBUG
LogCallback?.Invoke(this);
#endif
return this;
}
@ -52,9 +55,43 @@ namespace LibHac
/// <returns>The called <see cref="Result"/> value.</returns>
public Result LogConverted(Result originalResult)
{
#if DEBUG
ConvertedLogCallback?.Invoke(this, originalResult);
#endif
return this;
}
public delegate void ResultLogger(Result result);
public delegate void ConvertedResultLogger(Result result, Result originalResult);
public delegate bool ResultNameGetter(Result result, out string name);
public static ResultLogger LogCallback { get; set; }
public static ConvertedResultLogger ConvertedLogCallback { get; set; }
public static ResultNameGetter GetResultNameHandler { get; set; }
public bool TryGetResultName(out string name)
{
ResultNameGetter func = GetResultNameHandler;
if (func == null)
{
name = default;
return false;
}
return func(this, out name);
}
public string ToStringWithName()
{
if (TryGetResultName(out string name))
{
return $"{name} ({ErrorCode})";
}
return ErrorCode;
}
public override string ToString()
{
return IsSuccess() ? "Success" : ErrorCode;

View file

@ -19,6 +19,7 @@ namespace hactoolnet
new CliOption("titlekeys", 1, (o, a) => o.TitleKeyFile = a[0]),
new CliOption("consolekeys", 1, (o, a) => o.ConsoleKeyFile = a[0]),
new CliOption("accesslog", 1, (o, a) => o.AccessLog = a[0]),
new CliOption("resultlog", 1, (o, a) => o.ResultLog = a[0]),
new CliOption("section0", 1, (o, a) => o.SectionOut[0] = a[0]),
new CliOption("section1", 1, (o, a) => o.SectionOut[1] = a[0]),
new CliOption("section2", 1, (o, a) => o.SectionOut[2] = a[0]),

View file

@ -16,6 +16,7 @@ namespace hactoolnet
public string TitleKeyFile;
public string ConsoleKeyFile;
public string AccessLog;
public string ResultLog;
public string[] SectionOut = new string[4];
public string[] SectionOutDir = new string[4];
public string HeaderOut;

View file

@ -25,7 +25,7 @@ namespace hactoolnet
if (ex.ResultValue != ex.InternalResultValue)
{
Console.Error.WriteLine($"Internal Code: {ex.InternalResultValue.ErrorCode}");
Console.Error.WriteLine($"Internal Code: {ex.InternalResultValue.ToStringWithName()}");
}
Console.Error.WriteLine();
@ -52,9 +52,12 @@ namespace hactoolnet
if (ctx.Options == null) return false;
StreamWriter logWriter = null;
StreamWriter resultWriter = null;
try
{
Result.GetResultNameHandler = ResultLogFunctions.TryGetResultName;
using (var logger = new ProgressBar())
{
ctx.Logger = logger;
@ -71,6 +74,15 @@ namespace hactoolnet
ctx.Horizon.Fs.SetAccessLogObject(accessLog);
}
if (ctx.Options.ResultLog != null)
{
resultWriter = new StreamWriter(ctx.Options.ResultLog);
ResultLogFunctions.LogWriter = resultWriter;
Result.LogCallback = ResultLogFunctions.LogResult;
Result.ConvertedLogCallback = ResultLogFunctions.LogConvertedResult;
}
OpenKeyset(ctx);
if (ctx.Options.RunCustom)
@ -85,6 +97,9 @@ namespace hactoolnet
finally
{
logWriter?.Dispose();
resultWriter?.Dispose();
ResultLogFunctions.LogWriter = null;
}
return true;

View file

@ -0,0 +1,87 @@
using System;
using System.Collections.Generic;
using System.Diagnostics;
using System.IO;
using System.Linq;
using System.Reflection;
using LibHac;
using LibHac.Fs;
namespace hactoolnet
{
public static class ResultLogFunctions
{
private static Dictionary<Result, string> ResultNames { get; } = GetResultNames();
public static TextWriter LogWriter { get; set; }
public static void LogResult(Result result)
{
if (LogWriter == null) return;
var st = new StackTrace(2, true);
if (st.FrameCount > 1)
{
MethodBase method = st.GetFrame(0).GetMethod();
// This result from these functions is usually noise because they
// are frequently used to detect if a file exists
if (result == ResultFs.PathNotFound &&
typeof(IFileSystem).IsAssignableFrom(method.DeclaringType) &&
method.Name.StartsWith(nameof(IFileSystem.GetEntryType)) ||
method.Name.StartsWith(nameof(IAttributeFileSystem.GetFileAttributes)))
{
// return;
}
string methodName = $"{method.DeclaringType?.FullName}.{method.Name}";
LogWriter.WriteLine($"{result.ToStringWithName()} returned by {methodName}");
LogWriter.WriteLine(st);
}
}
public static void LogConvertedResult(Result result, Result originalResult)
{
if (LogWriter == null) return;
var st = new StackTrace(2, false);
if (st.FrameCount > 1)
{
MethodBase method = st.GetFrame(0).GetMethod();
string methodName = $"{method.DeclaringType?.FullName}.{method.Name}";
LogWriter.WriteLine($"{originalResult.ToStringWithName()} was converted to {result.ToStringWithName()} by {methodName}");
}
}
public static Dictionary<Result, string> GetResultNames()
{
var dict = new Dictionary<Result, string>();
Assembly assembly = typeof(Result).Assembly;
foreach (Type type in assembly.GetTypes().Where(x => x.Name.Contains("Result")))
{
foreach (PropertyInfo property in type.GetProperties()
.Where(x => x.PropertyType == typeof(Result) && x.GetMethod.IsStatic && x.SetMethod == null))
{
var value = (Result)property.GetValue(null, null);
string name = $"{type.Name}{property.Name}";
dict.Add(value, name);
}
}
return dict;
}
public static bool TryGetResultName(Result result, out string name)
{
return ResultNames.TryGetValue(result, out name);
}
}
}