using System.Collections.Generic; using System.Linq; using Microsoft.CodeAnalysis; using Microsoft.CodeAnalysis.CSharp; using UnitTestSharp; namespace Vellum.Generator.UnitTests { public class SaveableTypesTests : TestFixture { public class NamesOfTests : TestFixture { public const string Source = ConverterScanTests.DescribeTests.Pets + ConverterScanTests.DescribeTests.Creature + "public class Wolf : Creature, IDog { }\npublic struct Puppy : IPet { }\n" + "public class Kennel : IBowl, IBowl { }\npublic class Plain { }\n"; public static readonly CSharpCompilation Types = VellumRun.Compile(("Types.cs", Source)); public static ITypeSymbol Named(string metadataName) { return Types.GetTypeByMetadataName(metadataName); } public void ClassNames_ListTheClassAndItsBasesNearestFirst() { CheckEqual(new[] { "Wolf", "Creature" }, SaveableTypes.ClassNamesOf(Named("Wolf")).Items.ToArray()); } public void ClassNames_AreEmptyForAStructAndAnInterface() { CheckEqual(0, SaveableTypes.ClassNamesOf(Named("Puppy")).Items.Length); CheckEqual(0, SaveableTypes.ClassNamesOf(Named("IPet")).Items.Length); } public void InterfaceNames_ListTheMoreDerivedInterfaceFirst() { CheckEqual(new[] { "IDog", "IPet" }, SaveableTypes.InterfaceNamesOf(Named("Wolf")).Items.ToArray()); } public void InterfaceNames_NameAGenericInterfaceOnceWhateverItsArguments() { CheckEqual(new[] { "IBowl" }, SaveableTypes.InterfaceNamesOf(Named("Kennel")).Items.ToArray()); } public void InterfaceNames_AreListedForAStruct() { CheckEqual(new[] { "IPet" }, SaveableTypes.InterfaceNamesOf(Named("Puppy")).Items.ToArray()); } public void InterfaceNames_OfAnInterfaceAreItselfAndTheInterfacesItExtends() { CheckEqual(new[] { "IDog", "IPet" }, SaveableTypes.InterfaceNamesOf(Named("IDog")).Items.ToArray()); } public void InterfaceNames_AreEmptyForAnEnum() { CheckEqual(0, SaveableTypes.InterfaceNamesOf(Named("System.DayOfWeek")).Items.Length); } public void RepeatedInterfaces_ListTheGenericInterfacesImplementedMoreThanOnce() { CheckEqual(new[] { "IBowl" }, SaveableTypes.RepeatedInterfacesOf(Named("Kennel")).Items.ToArray()); CheckEqual(0, SaveableTypes.RepeatedInterfacesOf(Named("Wolf")).Items.Length); CheckEqual(0, SaveableTypes.RepeatedInterfacesOf(Named("IDog")).Items.Length); } public void FamilyNames_ListTheClassesThenTheInterfaces() { CheckEqual(new[] { "Wolf", "Creature", "IDog", "IPet" }, SaveableTypes.FamilyNamesOf(Named("Wolf")).Items.ToArray()); CheckEqual(new[] { "Plain" }, SaveableTypes.FamilyNamesOf(Named("Plain")).Items.ToArray()); } } public class FullMetadataNameOfTests : TestFixture { public static readonly CSharpCompilation Types = VellumRun.Compile(("Types.cs", "public class Outer { public class Inner { } }\n" + "public class Box { public T Item; }")); public void TopLevelType_IsItsNamespaceAndMetadataName() { CheckEqual("System.Guid", SaveableTypes.FullMetadataNameOf(Types.GetTypeByMetadataName("System.Guid"))); } public void TypeInTheGlobalNamespace_IsJustItsMetadataName() { CheckEqual("Box`1", SaveableTypes.FullMetadataNameOf(Types.GetTypeByMetadataName("Box`1"))); } public void ClosedGeneric_IsItsDefinitionsName() { INamedTypeSymbol closed = Types.GetTypeByMetadataName("System.Collections.Generic.List`1") .Construct(Types.GetSpecialType(SpecialType.System_Int32)); CheckEqual("System.Collections.Generic.List`1", SaveableTypes.FullMetadataNameOf(closed)); } public void NestedType_IsNull() { CheckNull(SaveableTypes.FullMetadataNameOf(Types.GetTypeByMetadataName("Outer+Inner"))); } public void ArrayAndTypeParameter_AreNull() { INamedTypeSymbol box = Types.GetTypeByMetadataName("Box`1"); CheckNull(SaveableTypes.FullMetadataNameOf( Types.CreateArrayTypeSymbol(Types.GetSpecialType(SpecialType.System_Int32)))); CheckNull(SaveableTypes.FullMetadataNameOf(box.TypeParameters[0])); } } public class IsBuiltInTests : TestFixture { public static readonly CSharpCompilation Types = VellumRun.Compile(("Types.cs", @" public struct Formattable : System.IFormattable { public string ToString(string format, System.IFormatProvider provider) { return """"; } public static Formattable Parse(string text, System.IFormatProvider provider) { return new Formattable(); } }")); public static bool IsBuiltIn(string metadataName) { return SaveableTypes.IsBuiltIn(Types.GetTypeByMetadataName(metadataName)); } public static ITypeSymbol NullableOf(string metadataName) { return Types.GetSpecialType(SpecialType.System_Nullable_T) .Construct(Types.GetTypeByMetadataName(metadataName)); } public void EveryBuiltInType_Is() { foreach (string name in new[] { "System.String", "System.Boolean", "System.Char", "System.SByte", "System.Byte", "System.Int16", "System.UInt16", "System.Int32", "System.UInt32", "System.Int64", "System.UInt64", "System.Single", "System.Double", "System.Decimal", "System.TimeSpan", "System.DateTime", "System.DateTimeOffset", "System.Guid", }) { CheckTrue(IsBuiltIn(name)); } } public void Enum_Is() { CheckTrue(IsBuiltIn("System.DayOfWeek")); } public void TheNames_AreTheRuntimesEighteenTypes() { CheckEqual(18, SaveableTypes.BuiltInNames.Count); CheckTrue(SaveableTypes.BuiltInNames.Contains("System.Guid")); CheckTrue(SaveableTypes.BuiltInNames.Contains("System.Int32")); } public void ATypeInAnotherNamespaceWithABuiltInsName_IsNot() { CSharpCompilation own = VellumRun.Compile(("Own.cs", "namespace Mine { public struct Guid { } }")); CheckFalse(SaveableTypes.IsBuiltIn(own.GetTypeByMetadataName("Mine.Guid"))); } public void ATypeNestedInTheBuiltInsNamespace_IsNot() { CSharpCompilation own = VellumRun.Compile(("Own.cs", "namespace System { public class Outer { public struct Guid { } } }")); CheckFalse(SaveableTypes.IsBuiltIn(own.GetTypeByMetadataName("System.Outer+Guid"))); } public void NullableOfABuiltInType_Is() { CheckTrue(SaveableTypes.IsBuiltIn(NullableOf("System.Int32"))); CheckTrue(SaveableTypes.IsBuiltIn(NullableOf("System.DayOfWeek"))); } public void NullableOfAnotherType_IsNot() { CheckFalse(SaveableTypes.IsBuiltIn(NullableOf("System.IntPtr"))); } public void FormattableWithAParse_IsNot() { CheckFalse(IsBuiltIn("Formattable")); } public void OtherTypes_AreNot() { CheckFalse(IsBuiltIn("System.Collections.Generic.List`1")); CheckFalse(IsBuiltIn("System.Object")); CheckFalse(IsBuiltIn("System.Version")); } /// /// The core library's structs that can't be a type argument, so have no Nullable. /// public static readonly HashSet NoNullable = new HashSet { typeof(void), typeof(System.TypedReference), typeof(System.ArgIterator), typeof(System.RuntimeArgumentHandle), }; public void EveryCoreLibraryTypeAndItsNullable_AgreesWithTheRuntime() { var disagreements = new List(); int compared = 0; foreach (System.Type type in typeof(object).Assembly.GetExportedTypes()) { INamedTypeSymbol symbol = Types.GetTypeByMetadataName(type.FullName); if (type.IsGenericTypeDefinition || symbol == null) { continue; } var pairs = new List> { new KeyValuePair(type, symbol), }; if (type.IsValueType && System.Nullable.GetUnderlyingType(type) == null && !NoNullable.Contains(type)) { pairs.Add(new KeyValuePair(typeof(System.Nullable<>) .MakeGenericType(type), Types.GetSpecialType(SpecialType.System_Nullable_T) .Construct(symbol))); } foreach (KeyValuePair pair in pairs) { compared++; if (SavedValue.IsBuiltIn(pair.Key) != SaveableTypes.IsBuiltIn(pair.Value)) { disagreements.Add(pair.Key.FullName); } } } CheckEqual(new string[0], disagreements); CheckGreater(compared, 1000); } } public class IsBuiltInCollectionTests : TestFixture { public static readonly CSharpCompilation Types = VellumRun.Compile(("Types.cs", "")); public static INamedTypeSymbol Named(string metadataName) { return Types.GetTypeByMetadataName(metadataName); } public static ITypeSymbol Int() { return Types.GetSpecialType(SpecialType.System_Int32); } public void ListSetAndDictionary_Are() { CheckTrue(SaveableTypes.IsBuiltInCollection( Named("System.Collections.Generic.List`1").Construct(Int()))); CheckTrue(SaveableTypes.IsBuiltInCollection( Named("System.Collections.Generic.HashSet`1").Construct(Int()))); CheckTrue(SaveableTypes.IsBuiltInCollection( Named("System.Collections.Generic.Dictionary`2").Construct(Int(), Int()))); } public void TheirOpenDefinitions_Are() { CheckTrue(SaveableTypes.IsBuiltInCollection(Named("System.Collections.Generic.List`1"))); CheckTrue(SaveableTypes.IsBuiltInCollection(Named("System.Collections.Generic.HashSet`1"))); CheckTrue(SaveableTypes.IsBuiltInCollection(Named("System.Collections.Generic.Queue`1"))); CheckTrue(SaveableTypes.IsBuiltInCollection(Named("System.Collections.Generic.Stack`1"))); CheckTrue(SaveableTypes.IsBuiltInCollection(Named("System.Collections.Generic.Dictionary`2"))); } public void OneDimensionalArray_Is() { CheckTrue(SaveableTypes.IsBuiltInCollection(Types.CreateArrayTypeSymbol(Int()))); CheckTrue(SaveableTypes.IsBuiltInCollection( Types.CreateArrayTypeSymbol(Types.CreateArrayTypeSymbol(Int())))); } public void ArrayOfAnyNumberOfDimensions_Is() { CheckTrue(SaveableTypes.IsBuiltInCollection(Types.CreateArrayTypeSymbol(Int(), 2))); CheckTrue(SaveableTypes.IsBuiltInCollection(Types.CreateArrayTypeSymbol(Int(), 3))); CheckTrue(SaveableTypes.IsBuiltInCollection(Types.CreateArrayTypeSymbol(Int(), 9))); } public void LinkedListAndTheSortedCollections_Are() { CheckTrue(SaveableTypes.IsBuiltInCollection( Named("System.Collections.Generic.LinkedList`1").Construct(Int()))); CheckTrue(SaveableTypes.IsBuiltInCollection( Named("System.Collections.Generic.SortedSet`1").Construct(Int()))); CheckTrue(SaveableTypes.IsBuiltInCollection( Named("System.Collections.Generic.SortedDictionary`2").Construct(Int(), Int()))); CheckTrue(SaveableTypes.IsBuiltInCollection( Named("System.Collections.Generic.SortedList`2").Construct(Int(), Int()))); } public void QueueAndStack_Are() { CheckTrue(SaveableTypes.IsBuiltInCollection( Named("System.Collections.Generic.Queue`1").Construct(Int()))); CheckTrue(SaveableTypes.IsBuiltInCollection( Named("System.Collections.Generic.Stack`1").Construct(Int()))); } public void OtherGenericTypes_AreNot() { CheckFalse(SaveableTypes.IsBuiltInCollection( Named("System.Collections.ObjectModel.Collection`1").Construct(Int()))); CheckFalse(SaveableTypes.IsBuiltInCollection( Types.GetSpecialType(SpecialType.System_Nullable_T).Construct(Int()))); } public void TheNamesTheRuntimeSavesByName_AreHeldByFullMetadataName() { CheckEqual(9, SaveableTypes.BuiltInCollectionNames.Count); CheckTrue(SaveableTypes.BuiltInCollectionNames.Contains("System.Collections.Generic.List`1")); CheckTrue(SaveableTypes.BuiltInCollectionNames.Contains("System.Collections.Generic.SortedList`2")); } public void ATypeInAnotherNamespaceWithACollectionsName_IsNot() { CSharpCompilation own = VellumRun.Compile(("Own.cs", "namespace Mine { public class List { } }")); CheckFalse(SaveableTypes.IsBuiltInCollection(own.GetTypeByMetadataName("Mine.List`1"))); } public void ATypeNestedWhereACollectionWouldBe_IsNot() { CSharpCompilation own = VellumRun.Compile(("Own.cs", "namespace System.Collections.Generic { public class Outer { public class List { } } }")); CheckFalse(SaveableTypes.IsBuiltInCollection( own.GetTypeByMetadataName("System.Collections.Generic.Outer+List`1"))); } public void TypesThatArentGeneric_AreNot() { CheckFalse(SaveableTypes.IsBuiltInCollection(Int())); CheckFalse(SaveableTypes.IsBuiltInCollection(Named("System.Version"))); } public void EveryGenericTypeInTheCoreLibraries_AgreesWithTheRuntime() { var disagreements = new List(); int collections = 0; foreach (System.Reflection.Assembly assembly in new[] { typeof(object).Assembly, typeof(System.Uri).Assembly, typeof(Enumerable).Assembly, }) { foreach (System.Type type in assembly.GetExportedTypes()) { INamedTypeSymbol symbol = Types.GetTypeByMetadataName(type.FullName); if (!type.IsGenericTypeDefinition || symbol == null) { continue; } if (SaveConverters.IsBuiltInCollection(type)) { collections++; } if (SaveConverters.IsBuiltInCollection(type) != SaveableTypes.IsBuiltInCollection(symbol)) { disagreements.Add(type.FullName); } } } CheckEqual(new string[0], disagreements); CheckEqual(9, collections); } } public class NameOfTests : TestFixture { public static ITypeSymbol FieldType(string source, string field) { CSharpCompilation types = VellumRun.Compile(("Types.cs", "#nullable enable\n" + source)); return ((IFieldSymbol)types.GetTypeByMetadataName("Holder").GetMembers(field).Single()).Type; } public void NullableReferenceType_IsSpelledWithoutTheQuestionMark() { ITypeSymbol type = FieldType("public class Foo { }\npublic class Holder { public Foo? Item; }", "Item"); CheckEqual("Foo?", type.ToDisplayString()); CheckEqual("Foo", SaveableTypes.NameOf(type)); } public void NullableReferenceTypeArgument_IsSpelledWithoutTheQuestionMark() { ITypeSymbol type = FieldType("public class Foo { }\n" + "public class Holder { public List Items = new List(); }", "Items"); CheckEqual("System.Collections.Generic.List", SaveableTypes.NameOf(type)); } public void OtherTypes_AreSpelledAsC() { ITypeSymbol type = FieldType("public class Holder { public int[] Sizes = new int[0]; }", "Sizes"); CheckEqual("int[]", SaveableTypes.NameOf(type)); } } } }