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());
}
}
}
}