Skip to content
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

Feature/assertionscope #1091

Merged
merged 17 commits into from Jul 8, 2019
Merged
Show file tree
Hide file tree
Changes from 11 commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Jump to
Jump to file
Failed to load files.
Diff view
Diff view
61 changes: 49 additions & 12 deletions Src/FluentAssertions/Execution/AssertionScope.cs
@@ -1,7 +1,14 @@
using System;
using System;
using System.Linq;
#if NOTNET45
conklinb marked this conversation as resolved.
Show resolved Hide resolved
using Microsoft.VisualStudio.Threading;
#else
using System.Runtime.Remoting.Messaging;
#endif
using FluentAssertions.Common;



namespace FluentAssertions.Execution
{
/// <summary>
Expand All @@ -11,6 +18,9 @@ namespace FluentAssertions.Execution
/// This class is supposed to have a very short life time and is not safe to be used in assertion that cross thread-boundaries such as when
/// using <c>async</c> or <c>await</c>.
/// </remarks>
#if NET45
[Serializable]
#endif
public class AssertionScope : IAssertionScope
{
#region Private Definitions
Expand All @@ -21,15 +31,15 @@ public class AssertionScope : IAssertionScope
private Func<string> reason;
private bool useLineBreaks;

[ThreadStatic]
private static AssertionScope current;

#if NOTNET45
private static AsyncLocal<AssertionScope> current = new AsyncLocal<AssertionScope>();
#endif
private AssertionScope parent;
private Func<string> expectation;
private string fallbackIdentifier = "object";
private bool? succeeded;

#endregion
#endregion

private AssertionScope(IAssertionStrategy _assertionStrategy)
{
Expand All @@ -44,9 +54,15 @@ private AssertionScope(IAssertionStrategy _assertionStrategy)
public AssertionScope()
: this(new CollectingAssertionStrategy())
{
parent = current;
current = this;

#if NOTNET45
conklinb marked this conversation as resolved.
Show resolved Hide resolved
parent = current.Value;
current.Value = this;
#else

parent = (AssertionScope) CallContext.LogicalGetData("this");
CallContext.LogicalSetData("this", this);
#endif
if (parent != null)
{
contextData.Add(parent.contextData);
Expand All @@ -69,15 +85,33 @@ public AssertionScope(string context)
/// </summary>
public string Context { get; set; }

#if NOTNET45
/// <summary>
/// Gets the current thread-specific assertion scope.
/// </summary>
public static AssertionScope Current
{
get => current ?? new AssertionScope(new DefaultAssertionStrategy());
private set => current = value;

get => current.Value ?? new AssertionScope(new DefaultAssertionStrategy());
private set => current.Value = value;

}
#else
/// <summary>
/// Gets the current thread-specific assertion scope.
/// </summary>
public static AssertionScope Current => LogicalCurrent();

public static AssertionScope LogicalCurrent()
{
if (CallContext.LogicalGetData("this") == null)
{
CallContext.LogicalSetData("this", new AssertionScope(new DefaultAssertionStrategy()));
conklinb marked this conversation as resolved.
Show resolved Hide resolved
}

return CallContext.LogicalGetData("this").As<AssertionScope>();
}
#endif
public AssertionScope UsingLineBreaks
{
get
Expand Down Expand Up @@ -258,8 +292,11 @@ public T Get<T>(string key)
/// </summary>
public void Dispose()
{
#if NOTNET45
Current = parent;

#else
CallContext.LogicalSetData("this", parent);
#endif
if (parent != null)
{
foreach (string failureMessage in assertionStrategy.FailureMessages)
Expand All @@ -281,7 +318,7 @@ public AssertionScope WithDefaultIdentifier(string identifier)
return this;
}

#region Explicit Implementation to support the interface
#region Explicit Implementation to support the interface

IAssertionScope IAssertionScope.ForCondition(bool condition) => ForCondition(condition);

Expand All @@ -293,6 +330,6 @@ public AssertionScope WithDefaultIdentifier(string identifier)

IAssertionScope IAssertionScope.UsingLineBreaks => UsingLineBreaks;

#endregion
#endregion
}
}
@@ -1,4 +1,4 @@
using System;
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
Expand All @@ -7,6 +7,9 @@

namespace FluentAssertions.Execution
{
#if NET45
[Serializable]
#endif
internal class CollectingAssertionStrategy : IAssertionStrategy
{
private readonly List<string> failureMessages = new List<string>();
Expand Down
5 changes: 5 additions & 0 deletions Src/FluentAssertions/Execution/Continuation.cs
@@ -1,8 +1,13 @@
using System;

namespace FluentAssertions.Execution
{
/// <summary>
/// Enables chaining multiple assertions on an <see cref="AssertionScope"/>.
/// </summary>
#if NET45
[Serializable]
#endif
public class Continuation
{
private readonly AssertionScope sourceScope;
Expand Down
5 changes: 4 additions & 1 deletion Src/FluentAssertions/Execution/ContinuedAssertionScope.cs
@@ -1,4 +1,4 @@
using System;
using System;

namespace FluentAssertions.Execution
{
Expand All @@ -9,6 +9,9 @@ namespace FluentAssertions.Execution
/// If the parent scope has captured a failed assertion, this class ensures that successive assertions
/// are no longer evaluated.
/// </remarks>
#if NET45
[Serializable]
#endif
public class ContinuedAssertionScope : IAssertionScope
{
private readonly AssertionScope predecessor;
Expand Down
53 changes: 36 additions & 17 deletions Src/FluentAssertions/Execution/DefaultAssertionStrategy.cs
@@ -1,43 +1,62 @@
using System.Collections.Generic;
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;

using FluentAssertions.Common;

namespace FluentAssertions.Execution
{
internal class DefaultAssertionStrategy : IAssertionStrategy
#if NET45
[Serializable]
#endif
internal class CollectingAssertionStrategy : IAssertionStrategy
conklinb marked this conversation as resolved.
Show resolved Hide resolved
{
private readonly List<string> failureMessages = new List<string>();

/// <summary>
/// Returns the messages for the assertion failures that happened until now.
/// </summary>
public IEnumerable<string> FailureMessages
{
get
{
return new string[0];
}
}
public IEnumerable<string> FailureMessages => failureMessages;

/// <summary>
/// Instructs the strategy to handle a assertion failure.
/// Discards and returns the failure messages that happened up to now.
/// </summary>
public void HandleFailure(string message)
public IEnumerable<string> DiscardFailures()
{
Services.ThrowException(message);
var discardedFailures = failureMessages.ToArray();
failureMessages.Clear();
return discardedFailures;
}

/// <summary>
/// Discards and returns the failure messages that happened up to now.
/// Will throw a combined exception for any failures have been collected since <see cref="StartCollecting"/> was called.
/// </summary>
public IEnumerable<string> DiscardFailures()
public void ThrowIfAny(IDictionary<string, object> context)
{
return new string[0];
if (failureMessages.Any())
{
var builder = new StringBuilder();
builder.AppendLine(string.Join(Environment.NewLine, failureMessages));

if (context.Any())
{
foreach (KeyValuePair<string, object> pair in context)
{
builder.AppendFormat("\nWith {0}:\n{1}", pair.Key, pair.Value);
}
}

Services.ThrowException(builder.ToString());
}
}

/// <summary>
/// Will throw a combined exception for any failures have been collected since <see cref="StartCollecting"/> was called.
/// Instructs the strategy to handle a assertion failure.
/// </summary>
public void ThrowIfAny(IDictionary<string, object> context)
public void HandleFailure(string message)
{
failureMessages.Add(message);
}
}
}
3 changes: 3 additions & 0 deletions Src/FluentAssertions/Execution/GivenSelector.cs
Expand Up @@ -7,6 +7,9 @@ namespace FluentAssertions.Execution
/// Represents a chaining object returned from <see cref="AssertionScope.Given{T}"/> to continue the assertion using
/// an object returned by a selector.
/// </summary>
#if NET45
[Serializable]
#endif
public class GivenSelector<T>
{
#region Private Definitions
Expand Down
8 changes: 7 additions & 1 deletion Src/FluentAssertions/FluentAssertions.csproj
@@ -1,4 +1,4 @@
<Project Sdk="Microsoft.NET.Sdk">
<Project Sdk="Microsoft.NET.Sdk">
<PropertyGroup>
<TargetFrameworks>net45;net47;netstandard1.3;netstandard1.6;netstandard2.0;netcoreapp2.0</TargetFrameworks>
<SignAssembly>True</SignAssembly>
Expand Down Expand Up @@ -27,6 +27,9 @@
<Copyright>Copyright Dennis Doomen 2010-2019</Copyright>
<LangVersion>7.3</LangVersion>
</PropertyGroup>
<PropertyGroup Condition="'$(TargetFramework)' != 'net45'">
<DefineConstants>NOTNET45</DefineConstants>
</PropertyGroup>
<PropertyGroup Condition="'$(TargetFramework)' == 'net45'">
<DefineConstants>NET45</DefineConstants>
</PropertyGroup>
Expand Down Expand Up @@ -60,6 +63,9 @@
<PackageReference Include="System.ValueTuple" Version="4.4.0" />
<Reference Include="System.Configuration" />
</ItemGroup>
<ItemGroup Condition="'$(TargetFramework)' != 'net45'">
conklinb marked this conversation as resolved.
Show resolved Hide resolved
<PackageReference Include="Microsoft.VisualStudio.Threading" Version="16.0.102" />
</ItemGroup>
<ItemGroup Condition="'$(TargetFramework)' == 'net47'">
<Reference Include="System.Configuration" />
</ItemGroup>
Expand Down
5 changes: 3 additions & 2 deletions Tests/NetStandard13.Specs/NetStandard13.Specs.csproj
@@ -1,4 +1,4 @@
<Project Sdk="Microsoft.NET.Sdk">
<Project Sdk="Microsoft.NET.Sdk">
<PropertyGroup>
<TargetFramework>netcoreapp1.1</TargetFramework>
<AssemblyName>FluentAssertions.NetStandard13.Specs</AssemblyName>
Expand All @@ -10,6 +10,7 @@
<ItemGroup>
<PackageReference Include="Chill" Version="4.0.0" />
<PackageReference Include="Microsoft.NET.Test.Sdk" Version="16.0.1" />
<PackageReference Include="Microsoft.VisualStudio.Threading" Version="16.0.102" />
<PackageReference Include="Newtonsoft.Json" Version="11.0.2" />
<PackageReference Include="System.ValueTuple" Version="4.4.0" />
<PackageReference Include="xunit" Version="2.4.1" />
Expand All @@ -27,4 +28,4 @@
</Reference>
</ItemGroup>
<Import Project="..\Shared.Specs\Shared.Specs.projitems" Label="Shared" />
</Project>
</Project>