using System; using System.Collections.Generic; using System.Linq; using System.Reflection; using UnitTestSharp; namespace Glass.Blacklight.UnitTests { /// /// Glass duplicates Blacklight.Core.Keyboard's KeyCode/KeyModifiers/KeyState/KeyInput/Chord wholesale rather /// than referencing them, to keep Glass free of a Blacklight dependency. These tests are the tripwire for /// the two copies drifting apart. /// public class KeyboardMirrorTests : TestFixture { public void KeyCode_MembersMatchByNameAndOrder() { CheckEqual(Enum.GetNames(typeof(global::Blacklight.Core.Keyboard.KeyCode)), Enum.GetNames(typeof(Glass.KeyCode))); } public void KeyModifiers_MembersMatchByNameAndValue() { CheckEqual(NamesAndValues(typeof(global::Blacklight.Core.Keyboard.KeyModifiers)), NamesAndValues(typeof(Glass.KeyModifiers))); } public void KeyState_PublicSurfaceMatches() { CheckEqual(Describe(typeof(global::Blacklight.Core.Keyboard.KeyState)), Describe(typeof(Glass.KeyState))); } public void KeyInput_PublicSurfaceMatches() { CheckEqual(Describe(typeof(global::Blacklight.Core.Keyboard.KeyInput)), Describe(typeof(Glass.KeyInput))); } public void Chord_PublicSurfaceMatches() { CheckEqual(Describe(typeof(global::Blacklight.Core.Keyboard.Chord)), Describe(typeof(Glass.Chord))); } [IgnoreTest] private static List NamesAndValues(Type enumType) { return Enum.GetNames(enumType) .Select(name => $"{name}={Convert.ToInt64(Enum.Parse(enumType, name))}") .OrderBy(entry => entry, StringComparer.Ordinal) .ToList(); } // Describes a type's public surface by member shape (kind/name/simple type names) rather than by exact // Type identity, since the two mirrors live in different assemblies and so never share an actual Type. [IgnoreTest] private static List Describe(Type type) { var returnMe = new List(); foreach (ConstructorInfo ctor in type.GetConstructors(BindingFlags.Public | BindingFlags.Instance)) { returnMe.Add($"ctor({ParameterShape(ctor.GetParameters())})"); } const BindingFlags declaredMembers = BindingFlags.Public | BindingFlags.Static | BindingFlags.Instance | BindingFlags.DeclaredOnly; foreach (FieldInfo field in type.GetFields(declaredMembers)) { returnMe.Add($"field {field.FieldType.Name} {field.Name}"); } foreach (PropertyInfo property in type.GetProperties(declaredMembers)) { returnMe.Add($"property {property.PropertyType.Name} {property.Name}"); } foreach (MethodInfo method in type.GetMethods(declaredMembers)) { // Property get_/set_ accessors are already covered by the property above; everything else -- // ordinary methods, operators, and a record's compiler-generated Equals/GetHashCode/ToString -- // is signature-relevant. if (method.IsSpecialName && (method.Name.StartsWith("get_") || method.Name.StartsWith("set_"))) { continue; } string parameters = ParameterShape(method.GetParameters()); returnMe.Add($"method {method.ReturnType.Name} {method.Name}({parameters})"); } returnMe.Sort(StringComparer.Ordinal); return returnMe; } [IgnoreTest] private static string ParameterShape(ParameterInfo[] parameters) { return string.Join(",", parameters.Select(parameter => parameter.ParameterType.Name)); } } }