From 983ce4928390d89b77aba1d9ccb043699652d282 Mon Sep 17 00:00:00 2001 From: Jameson Miller Date: Fri, 17 Apr 2015 17:28:20 -0400 Subject: [PATCH] Try harder to format error messages Also marshals text as UTF8, as expected by libgit2. --- LibGit2Sharp.Tests/LibGit2Sharp.Tests.csproj | 1 + LibGit2Sharp.Tests/SetErrorFixture.cs | 182 +++++++++++++++++++ LibGit2Sharp/Core/NativeMethods.cs | 2 +- LibGit2Sharp/Core/Proxy.cs | 61 ++++++- 4 files changed, 244 insertions(+), 2 deletions(-) create mode 100644 LibGit2Sharp.Tests/SetErrorFixture.cs diff --git a/LibGit2Sharp.Tests/LibGit2Sharp.Tests.csproj b/LibGit2Sharp.Tests/LibGit2Sharp.Tests.csproj index c477d4f84..904c427d7 100644 --- a/LibGit2Sharp.Tests/LibGit2Sharp.Tests.csproj +++ b/LibGit2Sharp.Tests/LibGit2Sharp.Tests.csproj @@ -63,6 +63,7 @@ + diff --git a/LibGit2Sharp.Tests/SetErrorFixture.cs b/LibGit2Sharp.Tests/SetErrorFixture.cs new file mode 100644 index 000000000..62bdeab86 --- /dev/null +++ b/LibGit2Sharp.Tests/SetErrorFixture.cs @@ -0,0 +1,182 @@ +using System; +using System.IO; +using System.Text; +using LibGit2Sharp.Tests.TestHelpers; +using Xunit; + +namespace LibGit2Sharp.Tests +{ + public class SetErrorFixture : BaseFixture + { + + private const string simpleExceptionMessage = "This is a simple exception message."; + private const string aggregateExceptionMessage = "This is aggregate exception."; + private const string outerExceptionMessage = "This is an outer exception."; + private const string innerExceptionMessage = "This is an inner exception."; + private const string innerExceptionMessage2 = "This is inner exception #2."; + + private const string expectedInnerExceptionHeaderText = "Inner Exception:"; + private const string expectedAggregateExceptionHeaderText = "Contained Exception:"; + private const string expectedAggregateExceptionsHeaderText = "Contained Exceptions:"; + + [Fact] + public void FormatSimpleException() + { + Exception exceptionToThrow = new Exception(simpleExceptionMessage); + string expectedMessage = simpleExceptionMessage; + + AssertExpectedExceptionMessage(expectedMessage, exceptionToThrow); + } + + [Fact] + public void FormatExceptionWithInnerException() + { + Exception exceptionToThrow = new Exception(outerExceptionMessage, new Exception(innerExceptionMessage)); + + StringBuilder sb = new StringBuilder(); + sb.AppendLine(outerExceptionMessage); + sb.AppendLine(); + AppendIndentedLine(sb, expectedInnerExceptionHeaderText, 0); + AppendIndentedText(sb, innerExceptionMessage, 1); + string expectedMessage = sb.ToString(); + + AssertExpectedExceptionMessage(expectedMessage, exceptionToThrow); + } + + [Fact] + public void FormatAggregateException() + { + Exception exceptionToThrow = new AggregateException(aggregateExceptionMessage, new Exception(innerExceptionMessage), new Exception(innerExceptionMessage2)); + + StringBuilder sb = new StringBuilder(); + sb.AppendLine(aggregateExceptionMessage); + sb.AppendLine(); + + AppendIndentedLine(sb, expectedAggregateExceptionsHeaderText, 0); + + AppendIndentedLine(sb, innerExceptionMessage, 1); + sb.AppendLine(); + + AppendIndentedText(sb, innerExceptionMessage2, 1); + + string expectedMessage = sb.ToString(); + + AssertExpectedExceptionMessage(expectedMessage, exceptionToThrow); + } + + private void AssertExpectedExceptionMessage(string expectedMessage, Exception exceptionToThrow) + { + Exception thrownException = null; + + ObjectId id = new ObjectId("deadbeefdeadbeefdeadbeefdeadbeefdeadbeef"); + + string repoPath = InitNewRepository(); + using (var repo = new Repository(repoPath)) + { + repo.ObjectDatabase.AddBackend(new ThrowingOdbBackend(exceptionToThrow), priority: 1); + + try + { + repo.Lookup(id); + } + catch (Exception ex) + { + thrownException = ex; + } + } + + Assert.NotNull(thrownException); + Assert.Equal(expectedMessage, thrownException.Message); + } + + private void AppendIndentedText(StringBuilder sb, string text, int indentLevel) + { + sb.AppendFormat("{0}{1}", IndentString(indentLevel), text); + } + + private void AppendIndentedLine(StringBuilder sb, string text, int indentLevel) + { + sb.AppendFormat("{0}{1}{2}", IndentString(indentLevel), text, Environment.NewLine); + } + + private string IndentString(int level) + { + return new string(' ', level * 4); + } + + #region ThrowingOdbBackend + + private class ThrowingOdbBackend : OdbBackend + { + private Exception exceptionToThrow; + + public ThrowingOdbBackend(Exception exceptionToThrow) + { + this.exceptionToThrow = exceptionToThrow; + } + + protected override OdbBackendOperations SupportedOperations + { + get + { + return OdbBackendOperations.Read | + OdbBackendOperations.ReadPrefix | + OdbBackendOperations.Write | + OdbBackendOperations.WriteStream | + OdbBackendOperations.Exists | + OdbBackendOperations.ExistsPrefix | + OdbBackendOperations.ForEach | + OdbBackendOperations.ReadHeader; + } + } + + public override int Read(ObjectId oid, out UnmanagedMemoryStream data, out ObjectType objectType) + { + throw this.exceptionToThrow; + } + + public override int ReadPrefix(string shortSha, out ObjectId id, out UnmanagedMemoryStream data, out ObjectType objectType) + { + throw this.exceptionToThrow; + } + + public override int Write(ObjectId oid, Stream dataStream, long length, ObjectType objectType) + { + throw this.exceptionToThrow; + } + + public override int WriteStream(long length, ObjectType objectType, out OdbBackendStream stream) + { + throw this.exceptionToThrow; + } + + public override bool Exists(ObjectId oid) + { + throw this.exceptionToThrow; + } + + public override int ExistsPrefix(string shortSha, out ObjectId found) + { + throw this.exceptionToThrow; + } + + public override int ReadHeader(ObjectId oid, out int length, out ObjectType objectType) + { + throw this.exceptionToThrow; + } + + public override int ReadStream(ObjectId oid, out OdbBackendStream stream) + { + throw this.exceptionToThrow; + } + + public override int ForEach(ForEachCallback callback) + { + throw this.exceptionToThrow; + } + } + + #endregion + + } +} diff --git a/LibGit2Sharp/Core/NativeMethods.cs b/LibGit2Sharp/Core/NativeMethods.cs index e74e250b6..b71d47081 100644 --- a/LibGit2Sharp/Core/NativeMethods.cs +++ b/LibGit2Sharp/Core/NativeMethods.cs @@ -85,7 +85,7 @@ static NativeMethods() [DllImport(libgit2)] internal static extern void giterr_set_str( GitErrorCategory error_class, - string errorString); + [MarshalAs(UnmanagedType.CustomMarshaler, MarshalCookie = UniqueId.UniqueIdentifier, MarshalTypeRef = typeof(StrictUtf8Marshaler))] string errorString); [DllImport(libgit2)] internal static extern void giterr_set_oom(); diff --git a/LibGit2Sharp/Core/Proxy.cs b/LibGit2Sharp/Core/Proxy.cs index 2ba1be499..a7b4d6ef9 100644 --- a/LibGit2Sharp/Core/Proxy.cs +++ b/LibGit2Sharp/Core/Proxy.cs @@ -4,6 +4,7 @@ using System.IO; using System.Linq; using System.Runtime.InteropServices; +using System.Text; using System.Threading; using LibGit2Sharp.Core.Handles; using LibGit2Sharp.Handlers; @@ -23,7 +24,7 @@ public static void giterr_set_str(GitErrorCategory error_class, Exception except } else { - NativeMethods.giterr_set_str(error_class, exception.Message); + NativeMethods.giterr_set_str(error_class, ErrorMessageFromException(exception)); } } @@ -32,6 +33,64 @@ public static void giterr_set_str(GitErrorCategory error_class, String errorStri NativeMethods.giterr_set_str(error_class, errorString); } + /// + /// This method will take an exception and try to generate an error message + /// that captures the important messages of the error. + /// The formatting is a bit subjective. + /// + /// + /// + public static string ErrorMessageFromException(Exception ex) + { + StringBuilder sb = new StringBuilder(); + BuildErrorMessageFromException(sb, 0, ex); + return sb.ToString(); + } + + private static void BuildErrorMessageFromException(StringBuilder sb, int level, Exception ex) + { + string indent = new string(' ', level * 4); + sb.AppendFormat("{0}{1}", indent, ex.Message); + + if (ex is AggregateException) + { + AggregateException aggregateException = ((AggregateException)ex).Flatten(); + + if (aggregateException.InnerExceptions.Count == 1) + { + sb.AppendLine(); + sb.AppendLine(); + + sb.AppendFormat("{0}Contained Exception:{1}", indent, Environment.NewLine); + BuildErrorMessageFromException(sb, level + 1, aggregateException.InnerException); + } + else + { + sb.AppendLine(); + sb.AppendLine(); + + sb.AppendFormat("{0}Contained Exceptions:{1}", indent, Environment.NewLine); + for (int i = 0; i < aggregateException.InnerExceptions.Count; i++) + { + if (i != 0) + { + sb.AppendLine(); + sb.AppendLine(); + } + + BuildErrorMessageFromException(sb, level + 1, aggregateException.InnerExceptions[i]); + } + } + } + else if (ex.InnerException != null) + { + sb.AppendLine(); + sb.AppendLine(); + sb.AppendFormat("{0}Inner Exception:{1}", indent, Environment.NewLine); + BuildErrorMessageFromException(sb, level + 1, ex.InnerException); + } + } + #endregion #region git_blame_