using System; using System.Collections.Generic; using System.IO; using System.Linq; using System.Reflection; using System.Reflection.Emit; using UnitTestSharp; namespace Vellum.UnitTests { public class TypeNamesTests : TestFixture { public const string Ns = "Vellum.UnitTests."; public static TypeNames Names() { return new TypeNames( new Dictionary { { Ns + "Dog", typeof(Dog) }, { Ns + "Point", typeof(Point) }, { Ns + "Outer+Inner", typeof(Outer.Inner) }, }, new[] { typeof(Box<>), typeof(Pair<,>), typeof(Limited<>), typeof(Holder<>.Slot<>), typeof(List<>), typeof(Dictionary<,>), }); } public static TypeNames FamilyNames() { return new TypeNames(new Dictionary(), new[] { typeof(Box<>) }, type => type == typeof(Wolf)); } public static string NameOf(Type type) { string name; Names().TryNameOf(type, out name); return name; } public static Type DeepBox(int count) { Type returnMe = typeof(int); for (int i = 0; i < count; ++i) { returnMe = typeof(Box<>).MakeGenericType(returnMe); } return returnMe; } /// /// Returns two enums named Twins.Pair, each in an assembly of its own. /// public static Type[] TwinEnums() { if (twins == null) { twins = new Type[2]; for (int i = 0; i < twins.Length; ++i) { AssemblyBuilder assembly = AssemblyBuilder.DefineDynamicAssembly( new AssemblyName("Twin" + i), AssemblyBuilderAccess.Run); EnumBuilder builder = assembly.DefineDynamicModule("Module") .DefineEnum("Twins.Pair", TypeAttributes.Public, typeof(int)); builder.DefineLiteral("Value", 0); twins[i] = builder.CreateType(); } } return twins; } private static Type[] twins; public class CtorTests : TestFixture { public void NoNamesAndNoGenerics_StillKnowsTheBuiltInTypes() { var names = new TypeNames(new Dictionary(), new Type[0]); CheckEqual(typeof(int), names.Resolve("int")); } public void EveryBuiltInType_ResolvesFromItsOwnName() { foreach (Type type in SavedValue.BuiltInTypes) { CheckEqual(type, Names().Resolve(NameOf(type))); } } } public class TryNameOfTests : TestFixture { public void KeywordTypes_AreSpelledAsTheKeyword() { foreach ((Type type, string keyword) in new[] { (typeof(bool), "bool"), (typeof(byte), "byte"), (typeof(sbyte), "sbyte"), (typeof(char), "char"), (typeof(decimal), "decimal"), (typeof(double), "double"), (typeof(float), "float"), (typeof(int), "int"), (typeof(uint), "uint"), (typeof(long), "long"), (typeof(ulong), "ulong"), (typeof(short), "short"), (typeof(ushort), "ushort"), (typeof(string), "string"), }) { CheckEqual(keyword, NameOf(type)); } } public void OtherBuiltInTypes_AreSpelledByFullName() { CheckEqual("System.DateTime", NameOf(typeof(DateTime))); CheckEqual("System.TimeSpan", NameOf(typeof(TimeSpan))); CheckEqual("System.DateTimeOffset", NameOf(typeof(DateTimeOffset))); CheckEqual("System.Guid", NameOf(typeof(Guid))); } public void RegisteredType_IsSpelledAsItsRegisteredName() { CheckEqual(Ns + "Dog", NameOf(typeof(Dog))); CheckEqual(Ns + "Outer+Inner", NameOf(typeof(Outer.Inner))); } public void GenericType_ListsItsArgumentsInTheirOwnNames() { CheckEqual(Ns + "Box", NameOf(typeof(Box))); CheckEqual(Ns + "Pair", NameOf(typeof(Pair))); CheckEqual(Ns + "Box<" + Ns + "Box<" + Ns + "Dog>>", NameOf(typeof(Box>))); CheckEqual("System.Collections.Generic.Dictionary", NameOf(typeof(Dictionary))); } public void NullableType_IsSpelledWithAQuestionMark() { CheckEqual(Ns + "Point?", NameOf(typeof(Point?))); CheckEqual(Ns + "Box", NameOf(typeof(Box))); } public void ArrayType_IsSpelledWithItsRank() { CheckEqual("int[]", NameOf(typeof(int[]))); CheckEqual("int[,]", NameOf(typeof(int[,]))); CheckEqual("int[,,]", NameOf(typeof(int[,,]))); CheckEqual("int[][]", NameOf(typeof(int[][]))); CheckEqual("int[,][]", NameOf(typeof(int[,][]))); CheckEqual(Ns + "Box[]", NameOf(typeof(Box[]))); CheckEqual(Ns + "Box", NameOf(typeof(Box))); CheckEqual("int?[]", NameOf(typeof(int?[]))); } public void TypeNestedInAGenericType_ListsEachTypesArgumentsAfterIt() { CheckEqual(Ns + "Holder+Slot", NameOf(typeof(Holder.Slot))); } public void SameTypeTwice_GetsTheSameName() { TypeNames names = Names(); string first; string second; CheckTrue(names.TryNameOf(typeof(Box), out first)); CheckTrue(names.TryNameOf(typeof(Box), out second)); CheckEqual(first, second); } public void TypeNothingRegistered_HasNoName() { string name; CheckFalse(Names().TryNameOf(typeof(FileInfo), out name)); CheckNull(name); } public void GenericTypeWithAnUnnamedPart_HasNoName() { string name; CheckFalse(Names().TryNameOf(typeof(Box), out name)); CheckFalse(Names().TryNameOf(typeof(Pair), out name)); } public void Enum_IsSpelledByItsFullName() { CheckEqual("System.DayOfWeek", NameOf(typeof(DayOfWeek))); CheckEqual(Ns + "Mood", NameOf(typeof(Mood))); CheckEqual(Ns + "Palette+Shade", NameOf(typeof(Palette.Shade))); CheckEqual(Ns + "Box", NameOf(typeof(Box))); CheckEqual(Ns + "Box<" + Ns + "Mood?[]>", NameOf(typeof(Box))); } public void EnumNamedTheSameAsAnotherLoadedEnum_HasNoName() { string name; CheckFalse(Names().TryNameOf(TwinEnums()[0], out name)); } public void TypeAFamilySaves_IsSpelledByItsFullName() { string name; CheckTrue(FamilyNames().TryNameOf(typeof(Wolf), out name)); CheckEqual(Ns + "Wolf", name); CheckTrue(FamilyNames().TryNameOf(typeof(Box), out name)); CheckEqual(Ns + "Box<" + Ns + "Wolf[]>", name); } public void TypeNoFamilySaves_HasNoName() { string name; CheckFalse(Names().TryNameOf(typeof(Wolf), out name)); CheckFalse(FamilyNames().TryNameOf(typeof(Creature), out name)); } public void GenericTypeNobodyRegistered_HasNoName() { string name; CheckFalse(Names().TryNameOf(typeof(Cell), out name)); } public void OpenGenericType_HasNoName() { string name; CheckFalse(Names().TryNameOf(typeof(Box<>), out name)); } public void ArrayNotStartingAtZero_HasNoName() { string name; Type offset = Array.CreateInstance(typeof(int), new[] { 2 }, new[] { 1 }).GetType(); CheckFalse(Names().TryNameOf(offset, out name)); } public void NestingAsDeepAsAllowed_HasAName() { string name; CheckTrue(Names().TryNameOf(DeepBox(SaveNode.MaxDepth - 1), out name)); } public void NestingPastTheLimit_HasNoName() { string name; CheckFalse(Names().TryNameOf(DeepBox(SaveNode.MaxDepth + 1), out name)); } public void ArraysNestedPastTheLimit_HaveNoName() { Type jagged = typeof(int); for (int i = 0; i < SaveNode.MaxDepth + 2; ++i) { jagged = jagged.MakeArrayType(); } string name; CheckFalse(Names().TryNameOf(jagged, out name)); } public void NameThatWouldNotReadBackToTheSameType_IsNoName() { var names = new TypeNames(new Dictionary { { "Odd Name!", typeof(Dog) } }, new[] { typeof(Box<>) }); string name; CheckTrue(names.TryNameOf(typeof(Dog), out name)); CheckFalse(names.TryNameOf(typeof(Box), out name)); } } public class ResolveTests : TestFixture { public static Type ThroughItsName(Type type) { return Names().Resolve(NameOf(type)); } public void RegisteredNames_ResolveDirectly() { CheckEqual(typeof(Dog), Names().Resolve(Ns + "Dog")); CheckEqual(typeof(Outer.Inner), Names().Resolve(Ns + "Outer+Inner")); CheckEqual(typeof(string), Names().Resolve("string")); CheckEqual(typeof(Guid), Names().Resolve("System.Guid")); } public void EveryNameableType_RoundTrips() { foreach (Type type in new[] { typeof(Box), typeof(Pair), typeof(Box>), typeof(Point?), typeof(Box), typeof(int[]), typeof(int[,]), typeof(int[,,]), typeof(int[][]), typeof(int[,][]), typeof(Box[]), typeof(Box), typeof(int?[]), typeof(Dictionary>), typeof(Holder.Slot), typeof(Holder>.Slot>), typeof(Pair, Box>), }) { CheckEqual(type, ThroughItsName(type)); } } public void Enums_ResolveByTheirFullNameWhetherOrNotAnythingRegisteredThem() { CheckEqual(typeof(DayOfWeek), Names().Resolve("System.DayOfWeek")); CheckEqual(typeof(Mood), Names().Resolve(Ns + "Mood")); CheckEqual(typeof(Palette.Shade), Names().Resolve(Ns + "Palette+Shade")); CheckEqual(typeof(Box), Names().Resolve(Ns + "Box<" + Ns + "Mood?[]>")); } public void TypesAFamilySaves_ResolveByTheirFullName() { CheckEqual(typeof(Wolf), FamilyNames().Resolve(Ns + "Wolf")); CheckEqual(typeof(Box), FamilyNames().Resolve(Ns + "Box<" + Ns + "Wolf[]>")); } public void TypeAFamilySavesResolvedTwice_IsTheSameType() { TypeNames names = FamilyNames(); CheckEqual(names.Resolve(Ns + "Wolf"), names.Resolve(Ns + "Wolf")); } public void LoadedTypeNoFamilySaves_Throws() { CheckThrow(typeof(InvalidDataException)); FamilyNames().Resolve(Ns + "Creature"); } public void TypeAFamilySavesWithoutTheFamily_Throws() { CheckThrow(typeof(InvalidDataException)); Names().Resolve(Ns + "Wolf"); } public void NameOfALoadedTypeThatIsntAnEnum_Throws() { CheckThrow(typeof(InvalidDataException)); Names().Resolve("System.IO.Path"); } public void EnumResolvedTwice_IsTheSameType() { TypeNames names = Names(); CheckEqual(names.Resolve("System.DayOfWeek"), names.Resolve("System.DayOfWeek")); } public void NameOfATypeStillBeingBuilt_Throws() { AssemblyBuilder assembly = AssemblyBuilder.DefineDynamicAssembly( new AssemblyName("Pending"), AssemblyBuilderAccess.Run); assembly.DefineDynamicModule("Module").DefineType("Pending.Thing", TypeAttributes.Public); CheckThrow(typeof(InvalidDataException)); Names().Resolve("Pending.Thing"); } public void EnumNameMoreThanOneLoadedEnumHas_Throws() { TwinEnums(); CheckThrow(typeof(InvalidDataException)); Names().Resolve("Twins.Pair"); } public void SpacesAfterCommas_AreOptional() { CheckEqual(typeof(Pair), Names().Resolve(Ns + "Pair")); CheckEqual(typeof(Pair), Names().Resolve(Ns + "Pair")); } public void NameNothingRegistered_Throws() { CheckThrow(typeof(InvalidDataException)); Names().Resolve("System.IO.FileInfo"); } public void ArgumentNothingRegistered_Throws() { CheckThrow(typeof(InvalidDataException)); Names().Resolve(Ns + "Box"); } public void GenericTypeNothingRegistered_Throws() { CheckThrow(typeof(InvalidDataException)); Names().Resolve(Ns + "Cell"); } public void GenericTypeWithTheWrongNumberOfArguments_Throws() { CheckThrow(typeof(InvalidDataException)); Names().Resolve(Ns + "Box"); } public void ArgumentsBreakingAConstraint_Throws() { CheckThrow(typeof(InvalidDataException)); Names().Resolve(Ns + "Limited"); } public void ConstraintMet_Resolves() { CheckEqual(typeof(Limited), Names().Resolve(Ns + "Limited")); } public void MalformedNames_Throw() { foreach (string malformed in new[] { "", "<", "int[", "int[,", "int[x]", "int]", "string?", "int??", "int? ", " int", "int,int", Ns + "Box<", Ns + "Box", Ns + "Box>", Ns + "Box x", Ns + "Box", Ns + "Box<,int>", Ns + "Box?", Ns + "Holder+", Ns + "Box+Slot", }) { bool threw = false; try { Names().Resolve(malformed); } catch (InvalidDataException) { threw = true; } CheckTrue(threw); } } public void NestingAsDeepAsAllowed_Resolves() { string name = NameOf(DeepBox(SaveNode.MaxDepth - 1)); CheckEqual(DeepBox(SaveNode.MaxDepth - 1), Names().Resolve(name)); } public void NestingPastTheLimit_Throws() { string name = ""; for (int i = 0; i < SaveNode.MaxDepth + 1; ++i) { name = Ns + "Box<" + name; } CheckThrow(typeof(InvalidDataException)); Names().Resolve(name + "int" + new string('>', SaveNode.MaxDepth + 1)); } public void NestingInLaterArgumentsPastTheLimit_Throws() { string name = ""; for (int i = 0; i < SaveNode.MaxDepth + 1; ++i) { name += Ns + "Pair', SaveNode.MaxDepth + 1)); } public void ArraysPastTheLimit_Throw() { CheckThrow(typeof(InvalidDataException)); Names().Resolve("int" + string.Concat(Enumerable.Repeat("[]", SaveNode.MaxDepth + 1))); } } } }