EnumExtensionMethodsIncrementalGeneratorTests.cs 12 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327
  1. using System.Collections.Concurrent;
  2. using System.Collections.ObjectModel;
  3. using System.Reflection;
  4. using NUnit.Framework.Interfaces;
  5. using NUnit.Framework.Internal;
  6. using Terminal.Gui.Analyzers.Internal.Attributes;
  7. using Terminal.Gui.Analyzers.Internal.Generators.EnumExtensions;
  8. namespace Terminal.Gui.Analyzers.Internal.Tests.Generators.EnumExtensions;
  9. [TestFixture]
  10. [Category ("Source Generators")]
  11. [TestOf (typeof (EnumExtensionMethodsIncrementalGenerator))]
  12. [Parallelizable (ParallelScope.Children)]
  13. public class EnumExtensionMethodsIncrementalGeneratorTests
  14. {
  15. private static bool _isInitialized;
  16. /// <summary>All enum types declared in the test assembly.</summary>
  17. private static readonly ObservableCollection<Type> _allEnumTypes = [];
  18. /// <summary>
  19. /// All enum types without a <see cref="GenerateEnumExtensionMethodsAttribute"/>, <see cref="_allEnumTypes"/>
  20. /// </summary>
  21. private static readonly HashSet<Type> _boringEnumTypes = [];
  22. /// <summary>All extension classes generated for enums with our attribute.</summary>
  23. private static readonly ObservableCollection<Type> _enumExtensionClasses = [];
  24. private static readonly ConcurrentDictionary<Type, EnumData> _extendedEnumTypeMappings = [];
  25. private static IEnumerable<Type> ExtendedEnumTypes => _extendedEnumTypeMappings.Keys;
  26. private static readonly ReaderWriterLockSlim _initializationLock = new ();
  27. private static IEnumerable<AssemblyExtendedEnumTypeAttribute> GetAssemblyExtendedEnumTypeAttributes () =>
  28. Assembly.GetExecutingAssembly ()
  29. .GetCustomAttributes<AssemblyExtendedEnumTypeAttribute> ();
  30. private static IEnumerable<TestCaseData> Get_AssemblyExtendedEnumTypeAttribute_EnumHasGeneratorAttribute_Cases ()
  31. {
  32. return GetAssemblyExtendedEnumTypeAttributes ()
  33. .Select (
  34. static attr => new TestCaseData (attr)
  35. {
  36. TestName = $"{nameof (AssemblyExtendedEnumTypeAttribute_EnumHasGeneratorAttribute)}({attr.EnumType.Name},{attr.ExtensionClass.Name})",
  37. HasExpectedResult = true,
  38. ExpectedResult = true
  39. });
  40. }
  41. [Test]
  42. [Category ("Attributes")]
  43. [TestCaseSource (nameof (Get_AssemblyExtendedEnumTypeAttribute_EnumHasGeneratorAttribute_Cases))]
  44. public bool AssemblyExtendedEnumTypeAttribute_EnumHasGeneratorAttribute (AssemblyExtendedEnumTypeAttribute attr)
  45. {
  46. Assume.That (attr, Is.Not.Null);
  47. Assume.That (attr.EnumType, Is.Not.Null);
  48. Assume.That (attr.EnumType!.IsEnum);
  49. return attr.EnumType.IsDefined (typeof (GenerateEnumExtensionMethodsAttribute));
  50. }
  51. private const string AssemblyExtendedEnumTypeAttributeEnumPropertyName =
  52. $"{nameof (AssemblyExtendedEnumTypeAttribute)}.{nameof (AssemblyExtendedEnumTypeAttribute.EnumType)}";
  53. [Test]
  54. [Category("Attributes")]
  55. public void AssemblyExtendedEnumTypeAttribute_ExtensionClassHasExpectedReverseMappingAttribute ([ValueSource(nameof(GetAssemblyExtendedEnumTypeAttributes))]AssemblyExtendedEnumTypeAttribute attr)
  56. {
  57. Assume.That (attr, Is.Not.Null);
  58. Assume.That (attr.ExtensionClass, Is.Not.Null);
  59. Assume.That (attr.ExtensionClass!.IsClass);
  60. Assume.That (attr.ExtensionClass!.IsSealed);
  61. Assert.That (attr.ExtensionClass.IsDefined (typeof (ExtensionsForEnumTypeAttribute<>)));
  62. }
  63. [Test]
  64. [Category("Attributes")]
  65. public void ExtendedEnum_AssemblyHasMatchingAttribute ([ValueSource(nameof(GetExtendedEnum_EnumData))]EnumData enumData)
  66. {
  67. Assume.That (enumData, Is.Not.Null);
  68. Assume.That (enumData.EnumType, Is.Not.Null);
  69. Assume.That (enumData.EnumType!.IsEnum);
  70. Assert.That (enumData.EnumType, Has.Attribute<GenerateEnumExtensionMethodsAttribute> ());
  71. }
  72. [Test]
  73. public void BoringEnum_DoesNotHaveExtensions ([ValueSource (nameof (_boringEnumTypes))] Type enumType)
  74. {
  75. Assume.That (enumType.IsEnum);
  76. Assert.That (enumType, Has.No.Attribute<GenerateEnumExtensionMethodsAttribute> ());
  77. }
  78. [Test]
  79. public void ExtendedEnum_FastIsDefinedFalse_DoesNotHaveFastIsDefined ([ValueSource (nameof (GetExtendedEnumTypes_FastIsDefinedFalse))] EnumData enumData)
  80. {
  81. Assume.That (enumData.EnumType.IsEnum);
  82. Assume.That (enumData.EnumType, Has.Attribute<GenerateEnumExtensionMethodsAttribute> ());
  83. Assume.That (enumData.GeneratorAttribute, Is.Not.Null);
  84. Assume.That (enumData.GeneratorAttribute, Is.EqualTo (enumData.EnumType.GetCustomAttribute<GenerateEnumExtensionMethodsAttribute> ()));
  85. Assume.That (enumData.GeneratorAttribute, Has.Property ("FastIsDefined").False);
  86. Assume.That (enumData.ExtensionClass, Is.Not.Null);
  87. Assert.That (enumData.ExtensionClass!.GetMethod ("FastIsDefined"), Is.Null);
  88. }
  89. [Test]
  90. public void ExtendedEnum_StaticExtensionClassExists ([ValueSource (nameof (ExtendedEnumTypes))] Type enumType)
  91. {
  92. Assume.That (enumType.IsEnum);
  93. Assume.That (enumType, Has.Attribute<GenerateEnumExtensionMethodsAttribute> ());
  94. ITypeInfo enumTypeInfo = new TypeWrapper (enumType);
  95. Assume.That (enumType, Has.Attribute<GenerateEnumExtensionMethodsAttribute> ());
  96. }
  97. [Test]
  98. public void ExtendedEnum_FastIsDefinedTrue_HasFastIsDefined ([ValueSource (nameof (GetExtendedEnumTypes_FastIsDefinedTrue))] EnumData enumData)
  99. {
  100. Assume.That (enumData.EnumType, Is.Not.Null);
  101. Assume.That (enumData.EnumType.IsEnum);
  102. Assume.That (enumData.EnumType, Has.Attribute<GenerateEnumExtensionMethodsAttribute> ());
  103. Assume.That (enumData.ExtensionClass, Is.Not.Null);
  104. ITypeInfo extensionClassTypeInfo = new TypeWrapper (enumData.ExtensionClass!);
  105. Assume.That (extensionClassTypeInfo.IsStaticClass);
  106. Assume.That (enumData.GeneratorAttribute, Is.Not.Null);
  107. Assume.That (enumData.GeneratorAttribute, Is.EqualTo (enumData.EnumType.GetCustomAttribute<GenerateEnumExtensionMethodsAttribute> ()));
  108. Assume.That (enumData.GeneratorAttribute, Has.Property ("FastIsDefined").True);
  109. MethodInfo? fastIsDefinedMethod = enumData.ExtensionClass!.GetMethod ("FastIsDefined");
  110. Assert.That (fastIsDefinedMethod, Is.Not.Null);
  111. Assert.That (fastIsDefinedMethod, Has.Attribute<ExtensionAttribute> ());
  112. IMethodInfo[] extensionMethods = extensionClassTypeInfo.GetMethodsWithAttribute<ExtensionAttribute> (false);
  113. }
  114. private static IEnumerable<EnumData> GetExtendedEnum_EnumData ()
  115. {
  116. _initializationLock.EnterUpgradeableReadLock ();
  117. try
  118. {
  119. if (!_isInitialized)
  120. {
  121. Initialize ();
  122. }
  123. return _extendedEnumTypeMappings.Values;
  124. }
  125. finally
  126. {
  127. _initializationLock.ExitUpgradeableReadLock ();
  128. }
  129. }
  130. private static IEnumerable<Type> GetBoringEnumTypes ()
  131. {
  132. _initializationLock.EnterUpgradeableReadLock ();
  133. try
  134. {
  135. if (!_isInitialized)
  136. {
  137. Initialize ();
  138. }
  139. return _boringEnumTypes;
  140. }
  141. finally
  142. {
  143. _initializationLock.ExitUpgradeableReadLock ();
  144. }
  145. }
  146. private static IEnumerable<EnumData> GetExtendedEnumTypes_FastIsDefinedFalse ()
  147. {
  148. _initializationLock.EnterUpgradeableReadLock ();
  149. try
  150. {
  151. if (!_isInitialized)
  152. {
  153. Initialize ();
  154. }
  155. return _extendedEnumTypeMappings.Values.Where (static t => t.GeneratorAttribute?.FastIsDefined is false);
  156. }
  157. finally
  158. {
  159. _initializationLock.ExitUpgradeableReadLock ();
  160. }
  161. }
  162. private static IEnumerable<EnumData> GetExtendedEnumTypes_FastIsDefinedTrue ()
  163. {
  164. _initializationLock.EnterUpgradeableReadLock ();
  165. try
  166. {
  167. if (!_isInitialized)
  168. {
  169. Initialize ();
  170. }
  171. return _extendedEnumTypeMappings.Values.Where (static t => t.GeneratorAttribute?.FastIsDefined is true);
  172. }
  173. finally
  174. {
  175. _initializationLock.ExitUpgradeableReadLock ();
  176. }
  177. }
  178. private static void Initialize ()
  179. {
  180. if (!_initializationLock.IsUpgradeableReadLockHeld || !_initializationLock.TryEnterWriteLock (5000))
  181. {
  182. return;
  183. }
  184. try
  185. {
  186. if (_isInitialized)
  187. {
  188. return;
  189. }
  190. _allEnumTypes.CollectionChanged += AllEnumTypes_CollectionChanged;
  191. _enumExtensionClasses.CollectionChanged += EnumExtensionClasses_OnCollectionChanged;
  192. Type [] allAssemblyTypes = Assembly
  193. .GetExecutingAssembly ()
  194. .GetTypes ();
  195. IEnumerable<Type> allEnumTypes = allAssemblyTypes.Where (IsDefinedEnum);
  196. foreach (Type type in allEnumTypes)
  197. {
  198. _allEnumTypes.Add (type);
  199. }
  200. foreach (Type type in allAssemblyTypes.Where (static t => t.IsClass && t.IsDefined (typeof (ExtensionsForEnumTypeAttribute<>))))
  201. {
  202. _enumExtensionClasses.Add (type);
  203. }
  204. _isInitialized = true;
  205. }
  206. finally
  207. {
  208. _initializationLock.ExitWriteLock ();
  209. }
  210. return;
  211. static bool IsDefinedEnum (Type t) { return t is { IsEnum: true, IsGenericType: false, IsConstructedGenericType: false, IsTypeDefinition: true }; }
  212. static void AllEnumTypes_CollectionChanged (object? sender, NotifyCollectionChangedEventArgs e)
  213. {
  214. if (e.Action is not NotifyCollectionChangedAction.Add and not NotifyCollectionChangedAction.Replace || e.NewItems is null)
  215. {
  216. return;
  217. }
  218. foreach (Type enumType in e.NewItems.OfType<Type> ())
  219. {
  220. if (enumType.GetCustomAttribute<GenerateEnumExtensionMethodsAttribute> () is not { } generatorAttribute)
  221. {
  222. _boringEnumTypes.Add (enumType);
  223. continue;
  224. }
  225. _extendedEnumTypeMappings.AddOrUpdate (
  226. enumType,
  227. CreateNewEnumData,
  228. UpdateGeneratorAttributeProperty,
  229. generatorAttribute);
  230. }
  231. }
  232. static EnumData CreateNewEnumData (Type tEnum, GenerateEnumExtensionMethodsAttribute attr) { return new (tEnum, attr); }
  233. static EnumData UpdateGeneratorAttributeProperty (Type tEnum, EnumData data, GenerateEnumExtensionMethodsAttribute attr)
  234. {
  235. data.GeneratorAttribute ??= attr;
  236. return data;
  237. }
  238. static void EnumExtensionClasses_OnCollectionChanged (object? sender, NotifyCollectionChangedEventArgs e)
  239. {
  240. if (e.Action != NotifyCollectionChangedAction.Add)
  241. {
  242. return;
  243. }
  244. foreach (Type extensionClassType in e.NewItems!.OfType<Type> ())
  245. {
  246. if (extensionClassType.GetCustomAttribute (typeof (ExtensionsForEnumTypeAttribute<>), false) is not IExtensionsForEnumTypeAttributes
  247. {
  248. EnumType.IsEnum: true
  249. } extensionForAttribute)
  250. {
  251. continue;
  252. }
  253. _extendedEnumTypeMappings [extensionForAttribute.EnumType].ExtensionClass ??= extensionClassType;
  254. }
  255. }
  256. }
  257. public sealed record EnumData (
  258. Type EnumType,
  259. GenerateEnumExtensionMethodsAttribute? GeneratorAttribute = null,
  260. Type? ExtensionClass = null,
  261. IExtensionsForEnumTypeAttributes? ExtensionForEnumTypeAttribute = null)
  262. {
  263. public Type? ExtensionClass { get; set; } = ExtensionClass;
  264. public IExtensionsForEnumTypeAttributes? ExtensionForEnumTypeAttribute { get; set; } = ExtensionForEnumTypeAttribute;
  265. public GenerateEnumExtensionMethodsAttribute? GeneratorAttribute { get; set; } = GeneratorAttribute;
  266. }
  267. }