Skip to content

Commit

Permalink
Refactored unit tests for ReflectionSource and thread-safe mode (#291)
Browse files Browse the repository at this point in the history
* Removed usage of System.Reflection
* All changes to SmartSettings.IsThreadSafeMode are performed by SmartFormat.Tests.TestUtils.ThreadSafeMode methods
  • Loading branch information
axunonb committed May 29, 2022
1 parent 39e327d commit 3924ec2
Show file tree
Hide file tree
Showing 11 changed files with 84 additions and 117 deletions.
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

0 comments on commit 3924ec2

Please sign in to comment.