Skip to content

Commit

Permalink
Merge pull request nunit#1222 from nunit/issue-1196
Browse files Browse the repository at this point in the history
Issue 1196
  • Loading branch information
CharliePoole committed Feb 2, 2016
2 parents 11a7a7c + f85fc96 commit 70e4716
Show file tree
Hide file tree
Showing 7 changed files with 189 additions and 48 deletions.
93 changes: 65 additions & 28 deletions src/NUnitFramework/framework/Constraints/MsgUtils.cs
Original file line number Diff line number Diff line change
Expand Up @@ -25,9 +25,25 @@
using System.Text;
using System.Collections;
using System.Globalization;
using NUnit.Framework.Internal;

namespace NUnit.Framework.Constraints
{
/// <summary>
/// Custom value formatter function
/// </summary>
/// <param name="val">The value</param>
/// <returns></returns>
public delegate string ValueFormatter(object val);

/// <summary>
/// Custom value formatter factory function
/// </summary>
/// <param name="next">The next formatter function</param>
/// <returns>ValueFormatter</returns>
/// <remarks>If the given formatter is unable to handle a certain format, it must call the next formatter in the chain</remarks>
public delegate ValueFormatter ValueFormatterFactory(ValueFormatter next);

/// <summary>
/// Static methods used in creating messages
/// </summary>
Expand All @@ -52,48 +68,69 @@ internal static class MsgUtils
private static readonly string Fmt_Default = "<{0}>";

/// <summary>
/// Formats text to represent a generalized value.
/// Current head of chain of value formatters. Public for testing.
/// </summary>
/// <param name="val">The value</param>
/// <returns>The formatted text</returns>
public static string FormatValue(object val)
public static ValueFormatter DefaultValueFormatter { get; set; }

static MsgUtils()
{
if (val == null)
return Fmt_Null;
// Initialize formatter to default for values of indeterminate type.
DefaultValueFormatter = val => string.Format(Fmt_Default, val);

if (val.GetType().IsArray)
return FormatArray((Array)val);
AddFormatter(next => val => val is ValueType ? string.Format(Fmt_ValueType, val) : next(val));

if (val is string)
return FormatString((string)val);
AddFormatter(next => val => val is DateTime ? FormatDateTime((DateTime)val) : next(val));

if (val is IEnumerable)
return FormatCollection((IEnumerable)val, 0, 10);
AddFormatter(next => val => val is decimal ? FormatDecimal((decimal)val) : next(val));

if (val is char)
return string.Format(Fmt_Char, val);
AddFormatter(next => val => val is float ? FormatFloat((float)val) : next(val));

if (val is double)
return FormatDouble((double)val);
AddFormatter(next => val => val is double ? FormatDouble((double)val) : next(val));

if (val is float)
return FormatFloat((float)val);
AddFormatter(next => val => val is char ? string.Format(Fmt_Char, val) : next(val));

if (val is decimal)
return FormatDecimal((decimal)val);
AddFormatter(next => val => val is IEnumerable ? FormatCollection((IEnumerable)val, 0, 10) : next(val));

if (val is DateTime)
return FormatDateTime((DateTime)val);
AddFormatter(next => val => val is string ? FormatString((string)val) : next(val));

if (val is ValueType)
return string.Format(Fmt_ValueType, val);
AddFormatter(next => val => val.GetType().IsArray ? FormatArray((Array)val) : next(val));

#if NETCF
var vi = val as System.Reflection.MethodInfo;
if (vi != null && vi.IsGenericMethodDefinition)
return string.Format(Fmt_Default, vi.Name + "<>");
AddFormatter(next => val =>
{
var vi = val as System.Reflection.MethodInfo;
return (vi != null && vi.IsGenericMethodDefinition)
? string.Format(Fmt_Default, vi.Name + "<>")
: next(val);
});
#endif
return string.Format(Fmt_Default, val);
}

/// <summary>
/// Add a formatter to the chain of responsibility.
/// </summary>
/// <param name="formatterFactory"></param>
public static void AddFormatter(ValueFormatterFactory formatterFactory)
{
DefaultValueFormatter = formatterFactory(DefaultValueFormatter);
}

/// <summary>
/// Formats text to represent a generalized value.
/// </summary>
/// <param name="val">The value</param>
/// <returns>The formatted text</returns>
public static string FormatValue(object val)
{
if (val == null)
return Fmt_Null;

var context = TestExecutionContext.CurrentContext;

if (context != null)
return context.CurrentValueFormatter(val);
else
return DefaultValueFormatter(val);
}

/// <summary>
Expand Down
13 changes: 8 additions & 5 deletions src/NUnitFramework/framework/GlobalSettings.cs
Original file line number Diff line number Diff line change
@@ -1,5 +1,5 @@
// ***********************************************************************
// Copyright (c) 2008 Charlie Poole
// Copyright (c) 2016 Charlie Poole
//
// Permission is hereby granted, free of charge, to any person obtaining
// a copy of this software and associated documentation files (the
Expand All @@ -22,18 +22,21 @@
// ***********************************************************************

using System;
using NUnit.Framework.Constraints;

namespace NUnit.Framework
{
/// <summary>
/// GlobalSettings is a place for setting default _values used
/// by the framework in performing asserts.
/// GlobalSettings is a place for setting default values used
/// by the framework in performing asserts. Anything set through
/// this class applies to the entire test run. It should not normally
/// be used from within a test, since it is not thread-safe.
/// </summary>
public class GlobalSettings
public static class GlobalSettings
{
/// <summary>
/// Default tolerance for floating point equality
/// </summary>
public static double DefaultFloatingPointTolerance = 0.0d;
}
}
}
2 changes: 1 addition & 1 deletion src/NUnitFramework/framework/Internal/ExceptionHelper.cs
Original file line number Diff line number Diff line change
Expand Up @@ -54,7 +54,7 @@ static ExceptionHelper()
}
#endif

#if NET_4_0 || NET_4_5 || PORTABLE
#if !NETCF && !SILVERLIGHT
/// <summary>
/// Rethrows an exception, preserving its stack trace
/// </summary>
Expand Down
41 changes: 30 additions & 11 deletions src/NUnitFramework/framework/Internal/TestExecutionContext.cs
Original file line number Diff line number Diff line change
Expand Up @@ -27,6 +27,7 @@
using System.Diagnostics;
using System.IO;
using System.Threading;
using NUnit.Framework.Constraints;
using NUnit.Framework.Interfaces;
using NUnit.Framework.Internal.Execution;

Expand Down Expand Up @@ -117,15 +118,17 @@ public class TestExecutionContext
public TestExecutionContext()
{
_priorContext = null;
this.TestCaseTimeout = 0;
this.UpstreamActions = new List<ITestAction>();
TestCaseTimeout = 0;
UpstreamActions = new List<ITestAction>();

_currentCulture = CultureInfo.CurrentCulture;
_currentUICulture = CultureInfo.CurrentUICulture;

#if !NETCF && !SILVERLIGHT && !PORTABLE
_currentPrincipal = Thread.CurrentPrincipal;
#endif

CurrentValueFormatter = (val) => MsgUtils.DefaultValueFormatter(val);
}

/// <summary>
Expand All @@ -136,14 +139,14 @@ public TestExecutionContext(TestExecutionContext other)
{
_priorContext = other;

this.CurrentTest = other.CurrentTest;
this.CurrentResult = other.CurrentResult;
this.TestObject = other.TestObject;
this.WorkDirectory = other.WorkDirectory;
CurrentTest = other.CurrentTest;
CurrentResult = other.CurrentResult;
TestObject = other.TestObject;
WorkDirectory = other.WorkDirectory;
_listener = other._listener;
this.StopOnError = other.StopOnError;
this.TestCaseTimeout = other.TestCaseTimeout;
this.UpstreamActions = new List<ITestAction>(other.UpstreamActions);
StopOnError = other.StopOnError;
TestCaseTimeout = other.TestCaseTimeout;
UpstreamActions = new List<ITestAction>(other.UpstreamActions);

_currentCulture = other.CurrentCulture;
_currentUICulture = other.CurrentUICulture;
Expand All @@ -152,8 +155,10 @@ public TestExecutionContext(TestExecutionContext other)
_currentPrincipal = other.CurrentPrincipal;
#endif

this.Dispatcher = other.Dispatcher;
this.ParallelScope = other.ParallelScope;
CurrentValueFormatter = other.CurrentValueFormatter;

Dispatcher = other.Dispatcher;
ParallelScope = other.ParallelScope;
}

#endregion
Expand Down Expand Up @@ -435,6 +440,11 @@ public IPrincipal CurrentPrincipal
}
#endif

/// <summary>
/// The current head of the ValueFormatter chain, copied from MsgUtils.ValueFormatter
/// </summary>
public ValueFormatter CurrentValueFormatter { get; private set; }

#endregion

#region Instance Methods
Expand Down Expand Up @@ -491,6 +501,15 @@ public void IncrementAssertCount(int count)
Interlocked.Increment(ref _assertCount);
}

/// <summary>
/// Adds a new ValueFormatterFactory to the chain of formatters
/// </summary>
/// <param name="formatterFactory">The new factory</param>
public void AddFormatter(ValueFormatterFactory formatterFactory)
{
CurrentValueFormatter = formatterFactory(CurrentValueFormatter);
}

#endregion

#region InitializeLifetimeService
Expand Down
26 changes: 26 additions & 0 deletions src/NUnitFramework/framework/TestContext.cs
Original file line number Diff line number Diff line change
Expand Up @@ -23,6 +23,7 @@

using System;
using System.IO;
using NUnit.Framework.Constraints;
using NUnit.Framework.Interfaces;
using NUnit.Framework.Internal;

Expand Down Expand Up @@ -226,6 +227,31 @@ public Randomizer Random
/// <summary>Write a formatted string to the current result followed by a line terminator</summary>
public static void WriteLine(string format, params object[] args) { Out.WriteLine(format, args); }

/// <summary>
/// This method adds the a new ValueFormatterFactory to the
/// chain of responsibility used for fomatting values in messages.
/// The scope of the change is the current TestContext.
/// </summary>
/// <param name="formatterFactory">The factory delegate</param>
public static void AddFormatter(ValueFormatterFactory formatterFactory)
{
TestExecutionContext.CurrentContext.AddFormatter(formatterFactory);
}

/// <summary>
/// This method provides a simplified way to add a ValueFormatter
/// delegate to the chain of responsibility, creating the factory
/// delegate internally. It is useful when the Type of the object
/// is the only criterion for selection of the formatter, since
/// it can be used without getting involved with a compould function.
/// </summary>
/// <typeparam name="TSUPPORTED">The type supported by this formatter</typeparam>
/// <param name="formatter">The ValueFormatter delegate</param>
public static void AddFormatter<TSUPPORTED>(ValueFormatter formatter)
{
AddFormatter(next => val => (val is TSUPPORTED) ? formatter(val) : next(val));
}

#endregion

#region Nested TestAdapter Class
Expand Down
26 changes: 26 additions & 0 deletions src/NUnitFramework/tests/Constraints/MsgUtilTests.cs
Original file line number Diff line number Diff line change
Expand Up @@ -32,6 +32,32 @@ namespace NUnit.Framework.Constraints
public static class MsgUtilTests
{
#region FormatValue
class CustomFormattableType { }

[Test]
public static void FormatValue_ContextualCustomFormatterInvoked_FactoryArg()
{
TestContext.AddFormatter(next => val => (val is CustomFormattableType) ? "custom_formatted" : next(val));

Assert.That(MsgUtils.FormatValue(new CustomFormattableType()), Is.EqualTo("custom_formatted"));
}

[Test]
public static void FormatValue_ContextualCustomFormatterNotInvokedForNull()
{
// If this factory is actually called with null, it will throw
TestContext.AddFormatter(next => val => (val.GetType() == typeof(CustomFormattableType)) ? val.ToString() : next(val));

Assert.That(MsgUtils.FormatValue(null), Is.EqualTo("null"));
}

[Test]
public static void FormatValue_ContextualCustomFormatterInvoked_FormatterArg()
{
TestContext.AddFormatter<CustomFormattableType>(val => "custom_formatted_using_type");

Assert.That(MsgUtils.FormatValue(new CustomFormattableType()), Is.EqualTo("custom_formatted_using_type"));
}

[Test]
public static void FormatValue_IntegerIsWrittenAsIs()
Expand Down
36 changes: 33 additions & 3 deletions src/NUnitFramework/tests/Internal/TestExecutionContextTests.cs
Original file line number Diff line number Diff line change
Expand Up @@ -27,11 +27,13 @@
using System.Globalization;
using NUnit.Common;
using NUnit.Framework;
using NUnit.Framework.Constraints;
using NUnit.TestData.TestContextData;
using NUnit.TestUtilities;

#if !NETCF
using System.Security.Principal;
#endif
using NUnit.TestData.TestContextData;
using NUnit.TestUtilities;

namespace NUnit.Framework.Internal
{
Expand Down Expand Up @@ -195,7 +197,7 @@ public void TestCanAccessItsOwnProperties()
CultureInfo originalUICulture;

[Test]
public void FixtureSetUpontextReflectsCurrentCulture()
public void FixtureSetUpContextReflectsCurrentCulture()
{
Assert.That(fixtureContext.CurrentCulture, Is.EqualTo(CultureInfo.CurrentCulture));
}
Expand Down Expand Up @@ -326,6 +328,34 @@ public void SetAndRestoreCurrentPrincipal()

#endregion

#region ValueFormatter

[Test]
public void SetAndRestoreValueFormatter()
{
var context = new TestExecutionContext(setupContext);
var originalFormatter = context.CurrentValueFormatter;

try
{
ValueFormatter f = val => "dummy";
context.AddFormatter(next => f);
Assert.That(context.CurrentValueFormatter, Is.EqualTo(f));

context.EstablishExecutionEnvironment();
Assert.That(MsgUtils.FormatValue(123), Is.EqualTo("dummy"));
}
finally
{
setupContext.EstablishExecutionEnvironment();
}

Assert.That(TestExecutionContext.CurrentContext.CurrentValueFormatter, Is.EqualTo(originalFormatter));
Assert.That(MsgUtils.FormatValue(123), Is.EqualTo("123"));
}

#endregion

#region ExecutionStatus

[Test]
Expand Down

0 comments on commit 70e4716

Please sign in to comment.