Skip to content

Try harder to format error messages #1026

New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Merged
merged 1 commit into from
Apr 28, 2015
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
1 change: 1 addition & 0 deletions LibGit2Sharp.Tests/LibGit2Sharp.Tests.csproj
Original file line number Diff line number Diff line change
Expand Up @@ -63,6 +63,7 @@
<Compile Include="RefSpecFixture.cs" />
<Compile Include="EqualityFixture.cs" />
<Compile Include="RevertFixture.cs" />
<Compile Include="SetErrorFixture.cs" />
<Compile Include="SignatureFixture.cs" />
<Compile Include="FilterBranchFixture.cs" />
<Compile Include="RemoveFixture.cs" />
Expand Down
182 changes: 182 additions & 0 deletions LibGit2Sharp.Tests/SetErrorFixture.cs
Original file line number Diff line number Diff line change
@@ -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<Blob>(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

}
}
2 changes: 1 addition & 1 deletion LibGit2Sharp/Core/NativeMethods.cs
Original file line number Diff line number Diff line change
Expand Up @@ -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();
Expand Down
61 changes: 60 additions & 1 deletion LibGit2Sharp/Core/Proxy.cs
Original file line number Diff line number Diff line change
Expand Up @@ -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;
Expand All @@ -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));
}
}

Expand All @@ -32,6 +33,64 @@ public static void giterr_set_str(GitErrorCategory error_class, String errorStri
NativeMethods.giterr_set_str(error_class, errorString);
}

/// <summary>
/// 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.
/// </summary>
/// <param name="ex"></param>
/// <returns></returns>
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_
Expand Down