using System.Collections.Generic; using System.Collections.Immutable; using System.Linq; using Microsoft.CodeAnalysis; using UnitTestSharp; namespace Vellum.Generator.UnitTests { public class SaveableShapeTests : TestFixture { public static SaveableShape ShapeOf(string metadataName, string source) { return SaveableClass.Describe(SaveableClassTests.TypeOf(metadataName, source)).Shape; } /// /// Returns a shape that differs from the default one only in the arguments given. /// public static SaveableShape Variant(string fileName = "Ns.A.Saveable.g.cs", string namespaceName = "Ns", string[] declarations = null, string name = "A", string typeName = "global::Ns.A", string converterFullName = "global::Ns.A.__Converter", MemberShape[] members = null, bool writesOwnLoader = false, bool chainsToParameterless = false, bool keepsImplicitConstructor = false, bool isDerivable = false, bool hasSaveableBase = false, bool isAbstract = false, bool hidesBaseConverter = false, bool isClass = false, string baseTypeName = "", MemberShape[] childMembers = null) { return new SaveableShape(fileName, namespaceName, new EquatableArray((declarations ?? new[] { "partial struct A" }).ToImmutableArray()), name, typeName, converterFullName, new EquatableArray((members ?? new[] { new MemberShape("X", "int") }).ToImmutableArray()), writesOwnLoader, chainsToParameterless, keepsImplicitConstructor, isDerivable, hasSaveableBase, isAbstract, hidesBaseConverter, isClass, baseTypeName, new EquatableArray((childMembers ?? new MemberShape[0]).ToImmutableArray())); } public class CtorTests : TestFixture { public void KeepsEveryArgument() { SaveableShape shape = Variant(writesOwnLoader: true, chainsToParameterless: true, keepsImplicitConstructor: true, isDerivable: true, hasSaveableBase: true, isAbstract: true, hidesBaseConverter: true, isClass: true, baseTypeName: "global::Ns.B", childMembers: new[] { new MemberShape("Y", "global::Ns.C") }); CheckEqual("Ns.A.Saveable.g.cs", shape.FileName); CheckEqual("Ns", shape.NamespaceName); CheckEqual(new[] { "partial struct A" }, shape.Declarations.Items.ToArray()); CheckEqual("A", shape.Name); CheckEqual("global::Ns.A", shape.TypeName); CheckEqual("global::Ns.A.__Converter", shape.ConverterFullName); CheckEqual(new MemberShape("X", "int"), shape.Members.Items.Single()); CheckTrue(shape.WritesOwnLoader); CheckTrue(shape.ChainsToParameterless); CheckTrue(shape.KeepsImplicitConstructor); CheckTrue(shape.IsDerivable); CheckTrue(shape.HasSaveableBase); CheckTrue(shape.IsAbstract); CheckTrue(shape.HidesBaseConverter); CheckTrue(shape.IsClass); CheckEqual("global::Ns.B", shape.BaseTypeName); CheckEqual(new MemberShape("Y", "global::Ns.C"), shape.ChildMembers.Items.Single()); } } public class OfTests : TestFixture { public const string Hierarchy = @" [Saveable] public abstract partial class Animal { public string Name; } [Saveable] public abstract partial class Pet : Animal { } [Saveable] public partial class Dog : Pet { public bool Barks; } [Saveable] public sealed partial class Puppy : Dog { } [Saveable] public sealed partial class Cat : Animal { }"; public void Type_IsDescribedByItsNames() { SaveableShape shape = ShapeOf("Game.Point", "namespace Game { [Saveable] public partial struct Point { } }"); CheckEqual("Game.Point.Saveable.g.cs", shape.FileName); CheckEqual("Game", shape.NamespaceName); CheckEqual(new[] { "partial struct Point" }, shape.Declarations.Items.ToArray()); CheckEqual("Point", shape.Name); CheckEqual("global::Game.Point", shape.TypeName); CheckEqual("global::Game.Point.__Converter", shape.ConverterFullName); } public void TypeInTheGlobalNamespace_HasNoNamespace() { CheckEqual("", ShapeOf("Point", "[Saveable] public partial struct Point { }").NamespaceName); } public void ClassAndStruct_AreRedeclaredAsEach() { CheckEqual(new[] { "partial class Crate" }, ShapeOf("Crate", "[Saveable] public sealed partial class Crate { }").Declarations.Items.ToArray()); CheckEqual(new[] { "partial struct Point" }, ShapeOf("Point", "[Saveable] public partial struct Point { }").Declarations.Items.ToArray()); } public void EnclosingTypes_AreRedeclaredOutermostFirstKeepingStaticAndReadonly() { SaveableShape shape = ShapeOf("Game.Holds+Bay+Slot", @" namespace Game { public static partial class Holds { public partial class Bay { [Saveable] public readonly partial struct Slot { } } } }"); CheckEqual(new[] { "static partial class Holds", "partial class Bay", "readonly partial struct Slot" }, shape.Declarations.Items.ToArray()); CheckEqual("global::Game.Holds.Bay.Slot.__Converter", shape.ConverterFullName); } public void GenericType_IsRedeclaredWithItsTypeParametersAndNoConstraints() { SaveableShape shape = ShapeOf("Game.Pair`2", @" namespace Game { [Saveable] public partial struct Pair where A : class, new() { public A First; public B Second; } }"); CheckEqual(new[] { "partial struct Pair" }, shape.Declarations.Items.ToArray()); CheckEqual("Pair", shape.Name); CheckEqual("global::Game.Pair", shape.TypeName); CheckEqual(new[] { new MemberShape("First", "A"), new MemberShape("Second", "B") }, shape.Members.Items.ToArray()); } public void GenericType_IsNamedForItsArityInTheFileNameAndOpenInTheConverterName() { SaveableShape shape = ShapeOf("Game.Pair`2", "namespace Game { [Saveable] public partial struct Pair { } }"); CheckEqual("Game.Pair`2.Saveable.g.cs", shape.FileName); CheckEqual("global::Game.Pair<,>.__Converter", shape.ConverterFullName); } public void TypeNestedInGenericTypes_RedeclaresEachWithItsOwnTypeParameters() { SaveableShape shape = ShapeOf("Game.Outer`1+Middle+Slot`1", @" namespace Game { public static partial class Outer { public partial class Middle { [Saveable] public partial struct Slot { public A First; public B Second; } } } }"); CheckEqual(new[] { "static partial class Outer", "partial class Middle", "partial struct Slot" }, shape.Declarations.Items.ToArray()); CheckEqual("Slot", shape.Name); CheckEqual("global::Game.Outer.Middle.Slot", shape.TypeName); CheckEqual("Game.Outer`1.Middle.Slot`1.Saveable.g.cs", shape.FileName); CheckEqual("global::Game.Outer<>.Middle.Slot<>.__Converter", shape.ConverterFullName); } public void NonGenericTypeNestedInAGenericType_IsRegisteredOpen() { SaveableShape shape = ShapeOf("Outer`1+Inner", "public partial class Outer { [Saveable] public partial struct Inner { public T Value; } }"); CheckEqual("global::Outer<>.Inner.__Converter", shape.ConverterFullName); CheckEqual("Outer`1.Inner.Saveable.g.cs", shape.FileName); CheckEqual(new[] { new MemberShape("Value", "T") }, shape.Members.Items.ToArray()); } public void Members_AreKeyedAndTypedForGeneratedCode() { SaveableShape shape = ShapeOf("Crate", @" [Saveable] public sealed partial class Crate { public int Count; public string Name { get; set; } public List Sizes; public int @event; }"); CheckEqual(new[] { new MemberShape("Count", "int"), new MemberShape("Name", "string"), new MemberShape("Sizes", "global::System.Collections.Generic.List"), new MemberShape("event", "int"), }, shape.Members.Items.ToArray()); } public void ChildMembers_AreTheOnesNotOfABuiltInType() { SaveableShape shape = ShapeOf("Crate", @" [Saveable] public sealed partial class Crate { public int Count; public string Name; public System.DayOfWeek Day; public int? Maybe; public System.DateTime When; public Crate Inside; public List Sizes; public int[] Counts; public object Anything; }"); CheckEqual(new[] { new MemberShape("Inside", "global::Crate"), new MemberShape("Sizes", "global::System.Collections.Generic.List"), new MemberShape("Counts", "int[]"), new MemberShape("Anything", "object"), }, shape.ChildMembers.Items.ToArray()); } public void ChildMembers_OfAGenericTypeIncludeItsTypeParameters() { SaveableShape shape = ShapeOf("Pair`2", @" [Saveable] public partial struct Pair { public A First; public B Second; public int Count; }"); CheckEqual(new[] { new MemberShape("First", "A"), new MemberShape("Second", "B") }, shape.ChildMembers.Items.ToArray()); } public void StructWithOnlyBuiltInMembers_HasNoChildMembers() { CheckEqual(0, ShapeOf("Point", "[Saveable] public partial struct Point { public double X; }") .ChildMembers.Items.Length); } public void BaseTypeName_IsTheSaveableBaseOrEmpty() { CheckEqual("global::Dog", ShapeOf("Puppy", Hierarchy).BaseTypeName); CheckEqual("global::Pet", ShapeOf("Dog", Hierarchy).BaseTypeName); CheckEqual("", ShapeOf("Animal", Hierarchy).BaseTypeName); CheckEqual("", ShapeOf("Point", "[Saveable] public partial struct Point { }").BaseTypeName); } public void BaseTypeName_OfADerivedGenericClass_NamesItsTypeArguments() { SaveableShape shape = ShapeOf("Derived`1", @" [Saveable] public partial class Base { public T Value; } [Saveable] public sealed partial class Derived : Base { }"); CheckEqual("global::Base", shape.BaseTypeName); } public void Flags_AreCarriedFromTheClass() { SaveableShape own = ShapeOf("Own", "[Saveable] public partial struct Own { private Own(SaveReader reader) { } }"); SaveableShape implicitClass = ShapeOf("Crate", "[Saveable] public sealed partial class Crate { }"); SaveableShape other = ShapeOf("Sized", "[Saveable] public sealed partial class Sized { public Sized(int size) { } }"); CheckTrue(own.WritesOwnLoader); CheckTrue(own.ChainsToParameterless); CheckFalse(own.KeepsImplicitConstructor); CheckFalse(implicitClass.WritesOwnLoader); CheckTrue(implicitClass.ChainsToParameterless); CheckTrue(implicitClass.KeepsImplicitConstructor); CheckFalse(other.ChainsToParameterless); CheckFalse(other.KeepsImplicitConstructor); } public void ClassIsAClassAndStructIsNot() { CheckTrue(ShapeOf("Dog", Hierarchy).IsClass); CheckTrue(ShapeOf("Puppy", Hierarchy).IsClass); CheckFalse(ShapeOf("Point", "[Saveable] public partial struct Point { }").IsClass); } public void ClassOthersCanDeriveFrom_IsDerivable() { CheckTrue(ShapeOf("Dog", Hierarchy).IsDerivable); CheckTrue(ShapeOf("Animal", Hierarchy).IsDerivable); } public void SealedClassAndStruct_AreNotDerivable() { CheckFalse(ShapeOf("Puppy", Hierarchy).IsDerivable); CheckFalse(ShapeOf("Point", "[Saveable] public partial struct Point { }").IsDerivable); } public void ClassDerivingFromASaveableClass_HasASaveableBase() { CheckTrue(ShapeOf("Dog", Hierarchy).HasSaveableBase); CheckTrue(ShapeOf("Puppy", Hierarchy).HasSaveableBase); } public void ClassDerivingFromObject_HasNoSaveableBase() { CheckFalse(ShapeOf("Animal", Hierarchy).HasSaveableBase); CheckFalse(ShapeOf("Point", "[Saveable] public partial struct Point { }").HasSaveableBase); } public void AbstractClass_IsAbstract() { CheckTrue(ShapeOf("Pet", Hierarchy).IsAbstract); CheckFalse(ShapeOf("Dog", Hierarchy).IsAbstract); } public void ClassBelowAConcreteSaveableClass_HidesItsConverter() { CheckTrue(ShapeOf("Puppy", Hierarchy).HidesBaseConverter); } public void ClassWhoseSaveableBasesAreAllAbstract_HidesNothing() { CheckFalse(ShapeOf("Pet", Hierarchy).HidesBaseConverter); CheckFalse(ShapeOf("Dog", Hierarchy).HidesBaseConverter); CheckFalse(ShapeOf("Cat", Hierarchy).HidesBaseConverter); CheckFalse(ShapeOf("Animal", Hierarchy).HidesBaseConverter); } } public class EqualsTests : TestFixture { public void SameValues_AreEqual() { CheckTrue(Variant().Equals(Variant())); CheckTrue(Variant().Equals((object)Variant())); } public void Null_IsNotEqual() { CheckFalse(Variant().Equals((SaveableShape)null)); CheckFalse(Variant().Equals((object)null)); } public void OtherType_IsNotEqual() { CheckFalse(Variant().Equals("A")); } public void DifferentFileName_IsNotEqual() { CheckFalse(Variant().Equals(Variant(fileName: "Other.Saveable.g.cs"))); } public void DifferentNamespaceName_IsNotEqual() { CheckFalse(Variant().Equals(Variant(namespaceName: "Other"))); } public void DifferentDeclarations_AreNotEqual() { CheckFalse(Variant().Equals(Variant(declarations: new[] { "partial class A" }))); } public void DifferentName_IsNotEqual() { CheckFalse(Variant().Equals(Variant(name: "B"))); } public void DifferentTypeName_IsNotEqual() { CheckFalse(Variant().Equals(Variant(typeName: "global::Ns.B"))); } public void DifferentConverterFullName_IsNotEqual() { CheckFalse(Variant().Equals(Variant(converterFullName: "global::Ns.B.__Converter"))); } public void DifferentMembers_AreNotEqual() { CheckFalse(Variant().Equals(Variant(members: new[] { new MemberShape("Y", "int") }))); } public void DifferentBaseTypeName_IsNotEqual() { CheckFalse(Variant().Equals(Variant(baseTypeName: "global::Ns.B"))); } public void DifferentChildMembers_AreNotEqual() { CheckFalse(Variant().Equals(Variant(childMembers: new[] { new MemberShape("Y", "global::Ns.C") }))); } public void DifferentWritesOwnLoader_IsNotEqual() { CheckFalse(Variant().Equals(Variant(writesOwnLoader: true))); } public void DifferentChainsToParameterless_IsNotEqual() { CheckFalse(Variant().Equals(Variant(chainsToParameterless: true))); } public void DifferentKeepsImplicitConstructor_IsNotEqual() { CheckFalse(Variant().Equals(Variant(keepsImplicitConstructor: true))); } public void DifferentIsDerivable_IsNotEqual() { CheckFalse(Variant().Equals(Variant(isDerivable: true))); } public void DifferentHasSaveableBase_IsNotEqual() { CheckFalse(Variant().Equals(Variant(hasSaveableBase: true))); } public void DifferentIsAbstract_IsNotEqual() { CheckFalse(Variant().Equals(Variant(isAbstract: true))); } public void DifferentHidesBaseConverter_IsNotEqual() { CheckFalse(Variant().Equals(Variant(hidesBaseConverter: true))); } public void DifferentIsClass_IsNotEqual() { CheckFalse(Variant().Equals(Variant(isClass: true))); } } public class GetHashCodeTests : TestFixture { public void EqualShapes_HaveEqualHashCodes() { CheckEqual(Variant().GetHashCode(), Variant().GetHashCode()); } } } }