ScopeTests.cs 9.1 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249
  1. #nullable enable
  2. using System.Reflection;
  3. namespace UnitTests_Parallelizable.ConfigurationTests;
  4. public class ScopeTests
  5. {
  6. [Fact]
  7. public void Constructor_Scope_Is_Empty ()
  8. {
  9. // Arrange
  10. // Act
  11. var scope = new Scope<object> ();
  12. // Assert
  13. Assert.NotNull (scope);
  14. Assert.Empty (scope);
  15. }
  16. // The property key will be "ScopeTests.BoolProperty"
  17. [ConfigurationProperty (Scope = typeof (ScopeTestsScope))]
  18. public static bool? BoolProperty { get; set; } = true;
  19. // The property key will be "ScopeTests.StringProperty"
  20. [ConfigurationProperty (Scope = typeof (ScopeTestsScope))]
  21. public static string? StringProperty { get; set; } // null
  22. // The property key will be "ScopeTests.KeyProperty"
  23. [ConfigurationProperty (Scope = typeof (ScopeTestsScope))]
  24. public static Key? KeyProperty { get; set; } = Key.A;
  25. // The property key will be "ScopeTests.DictionaryProperty"
  26. [ConfigurationProperty (Scope = typeof (ScopeTestsScope))]
  27. public static Dictionary<string, ConfigProperty>? DictionaryProperty { get; set; }
  28. public class ScopeTestsScope : Scope<ScopeTestsScope>
  29. {
  30. }
  31. // The property key will be "ScopeTests.DictionaryItemProperty1"
  32. [ConfigurationProperty (Scope = typeof (ScopeTestsDictionaryItemScope))]
  33. public static string? DictionaryItemProperty1 { get; set; } // null
  34. // The property key will be "ScopeTests.DictionaryItemProperty2"
  35. [ConfigurationProperty (Scope = typeof (ScopeTestsDictionaryItemScope))]
  36. public static string? DictionaryItemProperty2 { get; set; } // null
  37. public class ScopeTestsDictionaryItemScope : Scope<ScopeTestsDictionaryItemScope>
  38. {
  39. }
  40. [Fact]
  41. public void TestScope_Constructor_Creates_Properties ()
  42. {
  43. // Arrange
  44. // Act
  45. var scope = new ScopeTestsScope ();
  46. scope.LoadHardCodedDefaults ();
  47. var cache = CM.GetHardCodedConfigPropertyCache ();
  48. Assert.NotNull (cache);
  49. // Assert
  50. Assert.NotNull (scope);
  51. Assert.True (scope.ContainsKey ("ScopeTests.BoolProperty"));
  52. Assert.Equal (typeof (ScopeTests).GetProperty ("BoolProperty"), scope ["ScopeTests.BoolProperty"].PropertyInfo);
  53. Assert.True ((bool)scope ["ScopeTests.BoolProperty"].PropertyValue!);
  54. Assert.True (scope ["ScopeTests.BoolProperty"].HasValue);
  55. Assert.Equal (typeof (ScopeTests).GetProperty ("StringProperty"), scope ["ScopeTests.StringProperty"].PropertyInfo);
  56. Assert.Null (scope ["ScopeTests.StringProperty"].PropertyValue);
  57. Assert.True (scope ["ScopeTests.StringProperty"].HasValue);
  58. }
  59. [Fact]
  60. public void UpdateFrom_Unknown_Key_Adds ()
  61. {
  62. // Arrange
  63. var scope = new ScopeTestsScope ();
  64. scope.LoadHardCodedDefaults ();
  65. var scopeWithAddedProperty = new ScopeTestsScope ();
  66. scopeWithAddedProperty.TryAdd ("AddedProperty", new ConfigProperty ()
  67. {
  68. Immutable = false,
  69. PropertyInfo = scope ["ScopeTests.BoolProperty"].PropertyInfo, // cheat and reuse the same PropertyInfo
  70. PropertyValue = false
  71. });
  72. // Act
  73. scope.UpdateFrom (scopeWithAddedProperty);
  74. // Assert
  75. Assert.NotNull (scope);
  76. Assert.NotEmpty (scope);
  77. Assert.True (scope.ContainsKey ("AddedProperty"));
  78. Assert.Equal (false, scopeWithAddedProperty ["AddedProperty"].PropertyValue);
  79. }
  80. [Fact]
  81. public void UpdateFrom_HasValue_Updates ()
  82. {
  83. // Arrange
  84. ScopeTestsScope originalScope = new ScopeTestsScope ();
  85. originalScope.LoadHardCodedDefaults ();
  86. Assert.Equal (Key.A, originalScope ["ScopeTests.KeyProperty"].PropertyValue);
  87. ScopeTestsScope sourceScope = new ScopeTestsScope ();
  88. sourceScope.TryAdd ("ScopeTests.KeyProperty", new ConfigProperty
  89. {
  90. Immutable = false,
  91. PropertyInfo = originalScope ["ScopeTests.KeyProperty"].PropertyInfo,
  92. });
  93. sourceScope ["ScopeTests.KeyProperty"].PropertyValue = Key.B;
  94. // StringProperty is set to null
  95. Assert.DoesNotContain ("ScopeTests.StringProperty", sourceScope);
  96. Assert.True (sourceScope ["ScopeTests.KeyProperty"].HasValue);
  97. // Act
  98. originalScope.UpdateFrom (sourceScope);
  99. // Assert
  100. Assert.True (originalScope ["ScopeTests.StringProperty"].HasValue);
  101. Assert.True (originalScope ["ScopeTests.KeyProperty"].HasValue);
  102. Assert.Equal (Key.B, originalScope ["ScopeTests.KeyProperty"].PropertyValue);
  103. }
  104. [Fact]
  105. public void UpdateFrom_HasValue_Dictionary_Updates ()
  106. {
  107. // Arrange
  108. ScopeTestsScope originalScope = new ScopeTestsScope ();
  109. originalScope.LoadHardCodedDefaults ();
  110. Assert.Null (originalScope ["ScopeTests.DictionaryProperty"].PropertyValue);
  111. // QUESTION: Should this be done automatically?
  112. originalScope ["ScopeTests.DictionaryProperty"].PropertyValue = new Dictionary<string, ConfigProperty> ();
  113. ScopeTestsScope sourceScope = new ScopeTestsScope ();
  114. sourceScope.LoadHardCodedDefaults ();
  115. sourceScope ["ScopeTests.DictionaryProperty"].PropertyValue = new Dictionary<string, ConfigProperty> ()
  116. {
  117. { "item1", ConfigProperty.GetAllConfigProperties () ["ScopeTests.DictionaryItemProperty1"] },
  118. { "item2", ConfigProperty.GetAllConfigProperties () ["ScopeTests.DictionaryItemProperty2"] }
  119. };
  120. Assert.True (sourceScope ["ScopeTests.KeyProperty"].HasValue);
  121. Assert.True (sourceScope ["ScopeTests.DictionaryProperty"].HasValue);
  122. Dictionary<string, ConfigProperty>? sourceDict = sourceScope ["ScopeTests.DictionaryProperty"].PropertyValue as Dictionary<string, ConfigProperty>;
  123. sourceDict! ["item1"].Immutable = false;
  124. sourceDict ["item2"].Immutable = false;
  125. Assert.NotNull (sourceDict);
  126. Assert.Equal (2, sourceDict!.Count);
  127. Assert.True (sourceDict.ContainsKey ("item1"));
  128. Assert.True (sourceDict.ContainsKey ("item2"));
  129. Assert.False (sourceDict ["item1"].HasValue);
  130. Assert.False (sourceDict ["item1"].Immutable);
  131. // Update the original scope with the source scope, which has no values
  132. originalScope.UpdateFrom (sourceScope);
  133. // Confirm original is unchanged
  134. Assert.NotNull (originalScope ["ScopeTests.DictionaryProperty"].PropertyValue);
  135. Dictionary<string, ConfigProperty>? destDict = originalScope ["ScopeTests.DictionaryProperty"].PropertyValue as Dictionary<string, ConfigProperty>;
  136. Assert.NotNull (destDict);
  137. Assert.Empty (destDict);
  138. Assert.False (destDict.ContainsKey ("item1"));
  139. Assert.False (destDict.ContainsKey ("item2"));
  140. // Confirm source is unchanged
  141. sourceDict ["item1"].PropertyValue = "hello";
  142. Assert.True (sourceDict ["item1"].HasValue);
  143. Assert.Equal ("hello", sourceDict ["item1"].PropertyValue);
  144. // Now update the original scope with the source scope again
  145. originalScope.UpdateFrom (sourceScope);
  146. // Confirm the original has been updated with only the values in source that have been set
  147. Assert.NotNull (originalScope ["ScopeTests.DictionaryProperty"].PropertyValue);
  148. destDict = originalScope ["ScopeTests.DictionaryProperty"].PropertyValue as Dictionary<string, ConfigProperty>;
  149. // 1 item (item1) should now be in the original scope
  150. Assert.Single (destDict!);
  151. Assert.True (destDict! ["item1"].HasValue);
  152. Assert.Equal ("hello", destDict ["item1"].PropertyValue);
  153. originalScope.Apply ();
  154. // Verify apply worked
  155. Assert.Equal ("hello", DictionaryProperty? ["item1"].PropertyValue);
  156. // The item property should not have had its value set
  157. Assert.Null (DictionaryItemProperty1);
  158. DictionaryItemProperty1 = null;
  159. }
  160. #region Concurrency Testing
  161. [Fact]
  162. public void Scope_Concurrent_Additions ()
  163. {
  164. // Arrange
  165. var scope = new Scope<object> ();
  166. int threadCount = 10;
  167. int itemsPerThread = 100;
  168. var tasks = new List<Task> ();
  169. // Act
  170. for (int t = 0; t < threadCount; t++)
  171. {
  172. int threadId = t;
  173. tasks.Add (Task.Run (() =>
  174. {
  175. for (int i = 0; i < itemsPerThread; i++)
  176. {
  177. string key = $"Thread{threadId}_Item{i}";
  178. scope.TryAdd (key, new ConfigProperty { PropertyValue = i });
  179. }
  180. }));
  181. }
  182. #pragma warning disable xUnit1031
  183. Task.WaitAll (tasks.ToArray ());
  184. #pragma warning restore xUnit1031
  185. // Assert
  186. Assert.Equal (threadCount * itemsPerThread, scope.Count);
  187. for (int t = 0; t < threadCount; t++)
  188. {
  189. for (int i = 0; i < itemsPerThread; i++)
  190. {
  191. string key = $"Thread{t}_Item{i}";
  192. Assert.True (scope.ContainsKey (key));
  193. Assert.Equal (i, scope [key].PropertyValue);
  194. }
  195. }
  196. }
  197. #endregion
  198. }