|
| 1 | +using System.Text; |
| 2 | +using Xunit; |
| 3 | +using System.Reflection; |
| 4 | +using System; |
| 5 | +using System.Linq; |
| 6 | +using System.Collections.Generic; |
| 7 | + |
| 8 | +namespace LibGit2Sharp.Tests |
| 9 | +{ |
| 10 | + public class MetaFixture |
| 11 | + { |
| 12 | + private static readonly Type[] excludedTypes = new[] { typeof(Repository) }; |
| 13 | + |
| 14 | + // Related to https://github.com/libgit2/libgit2sharp/pull/185 |
| 15 | + [Fact] |
| 16 | + public void TypesInLibGit2SharpMustBeExtensibleInATestingContext() |
| 17 | + { |
| 18 | + var nonTestableTypes = new Dictionary<Type, IEnumerable<string>>(); |
| 19 | + |
| 20 | + IEnumerable<Type> libGit2SharpTypes = Assembly.GetAssembly(typeof(Repository)).GetExportedTypes().Where(t => !excludedTypes.Contains(t) && t.Namespace == typeof(Repository).Namespace); |
| 21 | + |
| 22 | + foreach (Type type in libGit2SharpTypes) |
| 23 | + { |
| 24 | + if (type.IsInterface || type.IsEnum || IsStatic(type)) |
| 25 | + continue; |
| 26 | + |
| 27 | + ConstructorInfo[] publicConstructor = type.GetConstructors(BindingFlags.Public | BindingFlags.Instance); |
| 28 | + if (publicConstructor.Any()) |
| 29 | + { |
| 30 | + continue; |
| 31 | + } |
| 32 | + |
| 33 | + var nonVirtualMethodNamesForType = GetNonVirtualPublicMethodsNames(type).ToList(); |
| 34 | + if (nonVirtualMethodNamesForType.Any()) |
| 35 | + { |
| 36 | + nonTestableTypes.Add(type, nonVirtualMethodNamesForType); |
| 37 | + continue; |
| 38 | + } |
| 39 | + |
| 40 | + if (!HasEmptyProtectedConstructor(type)) |
| 41 | + { |
| 42 | + nonTestableTypes.Add(type, new List<string>()); |
| 43 | + } |
| 44 | + } |
| 45 | + |
| 46 | + if (nonTestableTypes.Any()) |
| 47 | + { |
| 48 | + Assert.True(false, Environment.NewLine + BuildNonTestableTypesMessage(nonTestableTypes)); |
| 49 | + } |
| 50 | + } |
| 51 | + |
| 52 | + private static string BuildNonTestableTypesMessage(Dictionary<Type, IEnumerable<string>> nonTestableTypes) |
| 53 | + { |
| 54 | + var sb = new StringBuilder(); |
| 55 | + |
| 56 | + foreach (var kvp in nonTestableTypes) |
| 57 | + { |
| 58 | + sb.AppendFormat("'{0}' cannot be easily abstracted in a testing context. Please make sure it either has a public constructor, or an empty protected constructor.{1}", |
| 59 | + kvp.Key, Environment.NewLine); |
| 60 | + |
| 61 | + foreach (string methodName in kvp.Value) |
| 62 | + { |
| 63 | + sb.AppendFormat(" - Method '{0}' must be virtual{1}", methodName, Environment.NewLine); |
| 64 | + } |
| 65 | + } |
| 66 | + |
| 67 | + return sb.ToString(); |
| 68 | + } |
| 69 | + |
| 70 | + private static IEnumerable<string> GetNonVirtualPublicMethodsNames(Type type) |
| 71 | + { |
| 72 | + var publicMethods = type.GetMethods(BindingFlags.Public | BindingFlags.Instance | BindingFlags.DeclaredOnly); |
| 73 | + |
| 74 | + return from mi in publicMethods where !mi.IsVirtual && !mi.IsStatic select mi.ToString(); |
| 75 | + } |
| 76 | + |
| 77 | + private static bool HasEmptyProtectedConstructor(Type type) |
| 78 | + { |
| 79 | + ConstructorInfo[] nonPublicConstructors = type.GetConstructors(BindingFlags.NonPublic | BindingFlags.Instance); |
| 80 | + |
| 81 | + return nonPublicConstructors.Any(ci => !ci.IsPrivate && !ci.IsAssembly && !ci.IsFinal && !ci.GetParameters().Any()); |
| 82 | + } |
| 83 | + |
| 84 | + private static bool IsStatic(Type type) |
| 85 | + { |
| 86 | + return type.IsAbstract && type.IsSealed; |
| 87 | + } |
| 88 | + } |
| 89 | +} |
0 commit comments