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

Refactored unit tests for ReflectionSource and thread-safe mode #291

Merged
merged 11 commits into from
May 29, 2022
13 changes: 2 additions & 11 deletions src/SmartFormat.Tests/Core/FormatterTests.cs
Original file line number Diff line number Diff line change
Expand Up @@ -287,16 +287,7 @@ public void Not_Existing_Formatter_Name_Should_Throw()
public void Parallel_Smart_Format()
{
// Switch to thread safety - otherwise the test would throw an InvalidOperationException
const bool currentThreadSafeMode = true;
var savedIsThreadSafeMode = SmartSettings.IsThreadSafeMode;
SmartSettings.IsThreadSafeMode = currentThreadSafeMode;

// Thread pools might not be created in thread-safe mode,
// so we have to reset them
foreach (dynamic p in PoolRegistry.Items.Values)
{
if (!p.IsThreadSafeMode) p.Reset(SmartSettings.IsThreadSafeMode);
}
var savedMode = ThreadSafeMode.SwitchOn();

var results = new ConcurrentDictionary<long, string>();
var threadIds = new ConcurrentDictionary<int, int>();
Expand Down Expand Up @@ -329,7 +320,7 @@ public void Parallel_Smart_Format()
Assert.That(results.Count, Is.EqualTo(resultCounter));

// Restore to saved value
SmartSettings.IsThreadSafeMode = savedIsThreadSafeMode;
ThreadSafeMode.SwitchTo(savedMode);
}
}
}
7 changes: 2 additions & 5 deletions src/SmartFormat.Tests/Core/SourceExtensionsTests.cs
Original file line number Diff line number Diff line change
@@ -1,13 +1,10 @@
using System;
using System.Collections.Generic;
using System.Collections.Generic;
using System.Globalization;
using System.Linq;
using System.Reflection;
using NUnit.Framework;
using SmartFormat.Core.Extensions;
using SmartFormat.Core.Settings;
using SmartFormat.Extensions;
using SmartFormat.Tests.TestUtils;

namespace SmartFormat.Tests.Core
{
Expand Down Expand Up @@ -150,4 +147,4 @@ public bool TryEvaluateFormat(IFormattingInfo formattingInfo)

}

}
}
7 changes: 3 additions & 4 deletions src/SmartFormat.Tests/Extensions/GlobalVariableSourceTests.cs
Original file line number Diff line number Diff line change
Expand Up @@ -3,6 +3,7 @@
using SmartFormat.Core.Settings;
using SmartFormat.Extensions;
using SmartFormat.Extensions.PersistentVariables;
using SmartFormat.Tests.TestUtils;

namespace SmartFormat.Tests.Extensions
{
Expand Down Expand Up @@ -53,9 +54,7 @@ public void Reset_Should_Create_A_New_Instance()
public void Parallel_Load_By_Adding_Variables_To_Instance()
{
// Switch to thread safety - otherwise the test would throw an InvalidOperationException
const bool currentThreadSafeMode = true;
var savedIsThreadSafeMode = SmartSettings.IsThreadSafeMode;
SmartSettings.IsThreadSafeMode = currentThreadSafeMode;
var savedMode = ThreadSafeMode.SwitchOn();

GlobalVariablesSource.Instance.Add("global", new VariablesGroup());

Expand All @@ -69,7 +68,7 @@ public void Parallel_Load_By_Adding_Variables_To_Instance()
Assert.That(GlobalVariablesSource.Instance["global"].Count, Is.EqualTo(1000));

// Restore to saved value
SmartSettings.IsThreadSafeMode = savedIsThreadSafeMode;
ThreadSafeMode.SwitchTo(savedMode);
}
}
}
18 changes: 2 additions & 16 deletions src/SmartFormat.Tests/Extensions/ListFormatterTests.cs
Original file line number Diff line number Diff line change
Expand Up @@ -173,20 +173,7 @@ public void List_Of_Lists_With_Element_Format()
[Test]
public void WithThreadPool_ShouldNotMixUpCollectionIndex()
{
void ResetAllPools(bool goThreadSafe)
{
// get specialized pools (includes smart pools)
foreach (dynamic p in PoolRegistry.Items.Values)
{
p.Reset(goThreadSafe);
}
}

// Switch to thread safety
const bool currentThreadSafeMode = true;
var savedIsThreadSafeMode = SmartSettings.IsThreadSafeMode;
SmartSettings.IsThreadSafeMode = ListFormatter.IsThreadSafeMode = currentThreadSafeMode;
ResetAllPools(currentThreadSafeMode);
var savedMode = ThreadSafeMode.SwitchOn();

const string format = "wheres-Index={Index} - List: {0:{}| and }";
const string expected = "wheres-Index=-1 - List: test1 and test2";
Expand Down Expand Up @@ -220,8 +207,7 @@ void ResetAllPools(bool goThreadSafe)
}

// Restore thread safety
SmartSettings.IsThreadSafeMode = ListFormatter.IsThreadSafeMode = savedIsThreadSafeMode;
ResetAllPools(savedIsThreadSafeMode);
ThreadSafeMode.SwitchTo(savedMode);
}

[TestCase("{0:list:{} = {Index}|, }", "A = 0, B = 1, C = 2, D = 3, E = 4")] // Index holds the current index of the iteration
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -8,6 +8,7 @@
using SmartFormat.Core.Settings;
using SmartFormat.Extensions;
using SmartFormat.Extensions.PersistentVariables;
using SmartFormat.Tests.TestUtils;

namespace SmartFormat.Tests.Extensions
{
Expand Down Expand Up @@ -284,9 +285,7 @@ public void Format_Args_Should_Override_Persistent_Vars()
public void Parallel_Load_By_Adding_Variables_To_Source()
{
// Switch to thread safety - otherwise the test would throw an InvalidOperationException
const bool currentThreadSafeMode = true;
var savedIsThreadSafeMode = SmartSettings.IsThreadSafeMode;
SmartSettings.IsThreadSafeMode = currentThreadSafeMode;
var savedMode = ThreadSafeMode.SwitchOn();

var pvs = new PersistentVariablesSource { { "global", new VariablesGroup() } };
var options = new ParallelOptions { MaxDegreeOfParallelism = 10 };
Expand All @@ -299,7 +298,7 @@ public void Parallel_Load_By_Adding_Variables_To_Source()
Assert.That(pvs["global"].Count, Is.EqualTo(1000));

// Restore to saved value
SmartSettings.IsThreadSafeMode = savedIsThreadSafeMode;
ThreadSafeMode.SwitchTo(savedMode);
}
}
}
58 changes: 3 additions & 55 deletions src/SmartFormat.Tests/Extensions/ReflectionSourceTests.cs
Original file line number Diff line number Diff line change
@@ -1,6 +1,4 @@
using System;
using System.Collections.Generic;
using System.Reflection;
using NUnit.Framework;
using SmartFormat.Core.Formatting;
using SmartFormat.Core.Settings;
Expand Down Expand Up @@ -174,20 +172,8 @@ public void Test_With_TypeCaching_And_Method_Call()

var formatter = Smart.CreateDefaultSmartFormat();
var reflectionSource = formatter.GetSourceExtension<ReflectionSource>()!;

IDictionary<(Type, string?), (FieldInfo? field, MethodInfo?
method)>? typeCache;

if(SmartSettings.IsThreadSafeMode)
typeCache =
GetInstanceField(typeof(ReflectionSource), reflectionSource, TestReflectionSource.TypeCacheFieldName) as
System.Collections.Concurrent.ConcurrentDictionary<(Type, string?), (FieldInfo? field, MethodInfo?
method)>;
else
typeCache =
GetInstanceField(typeof(ReflectionSource), reflectionSource, TestReflectionSource.TypeCacheFieldName) as
System.Collections.Generic.Dictionary<(Type, string?), (FieldInfo? field, MethodInfo? method)>;

var typeCache = reflectionSource.TypeCache;
var obj = new {Obj = new MiscObject { MethodReturnValue = "The Method Value"}};

// Invoke formatter 1st time
Expand All @@ -207,19 +193,7 @@ public void Test_With_TypeCaching_And_Property_Value()
{
var formatter = Smart.CreateDefaultSmartFormat();
var reflectionSource = formatter.GetSourceExtension<ReflectionSource>()!;
IDictionary<(Type, string?), (FieldInfo? field, MethodInfo?
method)>? typeCache;

if(SmartSettings.IsThreadSafeMode)
typeCache =
GetInstanceField(typeof(ReflectionSource), reflectionSource, TestReflectionSource.TypeCacheFieldName) as
System.Collections.Concurrent.ConcurrentDictionary<(Type, string?), (FieldInfo? field, MethodInfo?
method)>;
else
typeCache =
GetInstanceField(typeof(ReflectionSource), reflectionSource, TestReflectionSource.TypeCacheFieldName) as
System.Collections.Generic.Dictionary<(Type, string?), (FieldInfo? field, MethodInfo? method)>;
var obj = new {Obj = new MiscObject { Field = "The Field Value"}};
var typeCache = reflectionSource.TypeCache; var obj = new {Obj = new MiscObject { Field = "The Field Value"}};

// Invoke formatter 1st time
Assert.That(formatter.Format("{Obj.Field}", obj), Is.EqualTo(obj.Obj.Field));
Expand All @@ -239,18 +213,7 @@ public void Test_With_TypeCaching_Disabled()
var formatter = Smart.CreateDefaultSmartFormat();
var reflectionSource = formatter.GetSourceExtension<ReflectionSource>()!;
reflectionSource.IsTypeCacheEnabled = false;
IDictionary<(Type, string?), (FieldInfo? field, MethodInfo?
method)>? typeCache;

if(SmartSettings.IsThreadSafeMode)
typeCache =
GetInstanceField(typeof(ReflectionSource), reflectionSource, TestReflectionSource.TypeCacheFieldName) as
System.Collections.Concurrent.ConcurrentDictionary<(Type, string?), (FieldInfo? field, MethodInfo?
method)>;
else
typeCache =
GetInstanceField(typeof(ReflectionSource), reflectionSource, TestReflectionSource.TypeCacheFieldName) as
System.Collections.Generic.Dictionary<(Type, string?), (FieldInfo? field, MethodInfo? method)>;
var typeCache = reflectionSource.TypeCache;
var obj = new {Obj = new MiscObject { Field = "The Field Value", MethodReturnValue = "The Method Value"}};

// Invoke formatter, expecting results, but empty cache
Expand Down Expand Up @@ -300,21 +263,6 @@ public class DerivedMiscObject : MiscObject
{
}

/// <summary>
/// Uses reflection to get the field value from an object.
/// </summary>
/// <param name="type">The instance type.</param>
/// <param name="instance">The instance object.</param>
/// <param name="fieldName">The field's name which is to be fetched.</param>
/// <returns>The field value from the object.</returns>
internal static object? GetInstanceField(Type type, object instance, string fieldName)
{
const BindingFlags bindingFlags = BindingFlags.Instance | BindingFlags.Public | BindingFlags.NonPublic
| BindingFlags.Static;
var field = type.GetField(fieldName, bindingFlags);
return field?.GetValue(instance);
}

internal class Address
{
public readonly string Country = string.Empty;
Expand Down
21 changes: 3 additions & 18 deletions src/SmartFormat.Tests/Pooling/ConcurrentPoolingTests.cs
Original file line number Diff line number Diff line change
Expand Up @@ -2,29 +2,20 @@
using System.Collections.Concurrent;
using System.Collections.Generic;
using System.Linq;
using System.Threading;
using System.Threading.Tasks;
using NUnit.Framework;
using SmartFormat.Core.Parsing;
using SmartFormat.Core.Settings;
using SmartFormat.Extensions;
using SmartFormat.Pooling;
using SmartFormat.Pooling.ObjectPools;
using SmartFormat.Tests.TestUtils;

namespace SmartFormat.Tests.Pooling
{
[TestFixture]
public class ConcurrentPoolingTests
{
private static void ResetAllPools(bool goThreadSafe)
{
// get specialized pools (includes smart pools)
foreach (dynamic p in PoolRegistry.Items.Values)
{
p.Reset(goThreadSafe);
}
}

private static List<(Type? Type, IPoolCounters? Counters)> GetAllPoolCounters()
{
var l = new List<(Type? Type, IPoolCounters? Counters)>();
Expand Down Expand Up @@ -68,10 +59,7 @@ public void Parallel_Load_On_Pool()
public void Parallel_Load_On_Specialized_Pools()
{
// Switch to thread safety
const bool currentThreadSafeMode = true;
var savedIsThreadSafeMode = SmartSettings.IsThreadSafeMode;
SmartSettings.IsThreadSafeMode = currentThreadSafeMode;
ResetAllPools(currentThreadSafeMode);
var savedMode = ThreadSafeMode.SwitchOn();

const int maxLoops = 100;
var options = new ParallelOptions { MaxDegreeOfParallelism = 10 };
Expand Down Expand Up @@ -108,10 +96,7 @@ public void Parallel_Load_On_Specialized_Pools()
}

// Restore thread safety
SmartSettings.IsThreadSafeMode = savedIsThreadSafeMode;
ResetAllPools(savedIsThreadSafeMode);

Assert.That(PoolRegistry.Items.Count, Is.EqualTo(0), "PoolRegistry.Items");
ThreadSafeMode.SwitchTo(savedMode);
}
}
}
2 changes: 1 addition & 1 deletion src/SmartFormat.Tests/Pooling/StringBuilderPoolTests.cs
Original file line number Diff line number Diff line change
Expand Up @@ -68,7 +68,7 @@ public void Reset_Pool()
var savedObjectPoolType = sbp.Pool.GetType();

// Change the current setting
sbp.Reset(!savedIsThreadSafeMode);
sbp.Reset(!sbp.Pool.IsThreadSafeMode);

var newThreadSafety = !savedIsThreadSafeMode;
var newObjectPoolType = sbp.Pool.GetType();
Expand Down
5 changes: 3 additions & 2 deletions src/SmartFormat.Tests/TestSetup.cs
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,7 @@
using System.IO;
using NUnit.Framework;
using SmartFormat.Core.Settings;
using SmartFormat.Tests.TestUtils;

namespace SmartFormat.Tests
{
Expand All @@ -14,7 +15,7 @@ public void RunBeforeAnyTests()
// Disable console output from test methods
Console.SetOut(TextWriter.Null);

SmartSettings.IsThreadSafeMode = false;
SmartSettings.IsThreadSafeMode = ThreadSafeMode.SwitchOff();
PoolSettings.IsPoolingEnabled = true;
PoolSettings.CheckReturnedObjectsExistInPool = true;

Expand All @@ -28,4 +29,4 @@ public void RunAfterAnyTests()
// Nothing defined here
}
}
}
}
61 changes: 61 additions & 0 deletions src/SmartFormat.Tests/TestUtils/ThreadSafeMode.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,61 @@
//
// Copyright SmartFormat Project maintainers and contributors.
// Licensed under the MIT license.
//

using SmartFormat.Core.Settings;
using SmartFormat.Pooling;

namespace SmartFormat.Tests.TestUtils
{
/// <summary>
/// Change <see cref="SmartSettings.IsThreadSafeMode"/> during unit tests.
/// </summary>
public static class ThreadSafeMode
{
/// <summary>
/// Sets <see cref="SmartSettings.IsThreadSafeMode"/> to <see langword="true"/>
/// and resets <see cref="PoolRegistry.Items"/> using the new mode.
/// </summary>
/// <returns>The current value of <see cref="SmartSettings.IsThreadSafeMode"/> before switching the mode.</returns>
public static bool SwitchOn()
{
return SwitchTo(true);
}

/// <summary>
/// Sets <see cref="SmartSettings.IsThreadSafeMode"/> to <see langword="true"/>
/// and resets <see cref="PoolRegistry.Items"/> using the new mode.
/// </summary>
/// <returns>The current value of <see cref="SmartSettings.IsThreadSafeMode"/> before switching the mode.</returns>
public static bool SwitchOff()
{
return SwitchTo(false);
}

/// <summary>
/// Sets <see cref="SmartSettings.IsThreadSafeMode"/> to the <paramref name="newSetting"/>.
/// and resets <see cref="PoolRegistry.Items"/> using the new mode.
/// </summary>
/// <returns>The value of <see cref="SmartSettings.IsThreadSafeMode"/> before switching the mode.</returns>
public static bool SwitchTo(bool newSetting)
{
var currentMode = SmartSettings.IsThreadSafeMode;
SmartSettings.IsThreadSafeMode = newSetting;

ResetThreadPools(newSetting);

return currentMode;
}

private static void ResetThreadPools(bool mode)
{
// Thread pools might not be created in thread-safe mode,
// so we have to reset them
foreach (dynamic p in PoolRegistry.Items.Values)
{
p.Reset(mode);
}
}
}
}
2 changes: 1 addition & 1 deletion src/SmartFormat/Extensions/ReflectionSource.cs
Original file line number Diff line number Diff line change
Expand Up @@ -30,7 +30,7 @@ public class ReflectionSource : Source
/// Note: For reading, <see cref="Dictionary{TKey, TValue}"/> and <see cref="ConcurrentDictionary{TKey, TValue}"/> perform equally.
/// For writing, <see cref="ConcurrentDictionary{TKey, TValue}"/> is slower with more garbage (tested under net5.0).
/// </remarks>
protected readonly IDictionary<(Type, string?), (FieldInfo? field, MethodInfo? method)> TypeCache =
protected internal readonly IDictionary<(Type, string?), (FieldInfo? field, MethodInfo? method)> TypeCache =
SmartSettings.IsThreadSafeMode
? new ConcurrentDictionary<(Type, string?), (FieldInfo? field, MethodInfo? method)>()
: new Dictionary<(Type, string?), (FieldInfo? field, MethodInfo? method)>();
Expand Down