using System;
using System.Collections.Generic;
using System.Collections.ObjectModel;
using System.IO;
using System.Linq;
using System.Reflection;
using System.Reflection.Emit;
using System.Threading;
using UnitTestSharp;
namespace Vellum.UnitTests
{
public class SaveConvertersTests : TestFixture
{
public static SaveConverters TestConverters()
{
return SaveConverters.For(typeof(SaveConvertersTests).Assembly);
}
///
/// Writes as Lua text, inside the table a save has to be, and reads it back.
///
public static SaveNode ThroughLuaText(SaveNode node)
{
SaveNode save = SaveNode.NewTable();
save.Entries["value"] = node;
return LuaSaveFormat.Parse(LuaSaveFormat.Format(save)).RequireEntry("value");
}
public static string[] IssuePaths(SaveSession session)
{
return session.Sandbox.Issues.Select(issue => issue.Path).ToArray();
}
///
/// Returns a chain of links, the last with no next.
///
public static Link Chain(int count)
{
Link returnMe = null;
for (int i = 0; i < count; ++i)
{
returnMe = new Link { Next = returnMe };
}
return returnMe;
}
///
/// Returns the node levels down from , following the entry
/// named at each level.
///
public static SaveNode Descend(SaveNode node, string key, int steps)
{
SaveNode returnMe = node.Resolved;
for (int i = 0; i < steps; ++i)
{
returnMe = returnMe.RequireEntry(key).Resolved;
}
return returnMe;
}
///
/// Returns how many tables lie inside one another from down, following the entry named
/// .
///
public static int DepthOf(SaveNode node, string key)
{
int returnMe = 0;
for (SaveNode next = node.Resolved; next.Kind == SaveNode.Kinds.Table; next = next.Entries[key].Resolved)
{
++returnMe;
}
return returnMe;
}
///
/// Returns converters for Point and for the generic types the test converters handle.
///
public static SaveConverters WithGenericConverters()
{
return new SaveConverters(new SaveConverter[] { new PointConverter() }, new[]
{
typeof(BoxConverter<>), typeof(PairConverter<,>), typeof(CellConverter<>), typeof(CountedConverter<>),
});
}
public static SaveSession Session()
{
return TestConverters().CreateSession();
}
public static SaveSession GenericSession()
{
return WithGenericConverters().CreateSession();
}
///
/// Returns a session for converters of nothing but the open converters given.
///
public static SaveSession SessionWith(params Type[] openConverters)
{
return new SaveConverters(new SaveConverter[0], openConverters).CreateSession();
}
///
/// Returns a chain of cells, the last with no next.
///
public static Cell CellChain(int count)
{
Cell returnMe = null;
for (int i = 0; i < count; ++i)
{
returnMe = new Cell { Next = returnMe };
}
return returnMe;
}
///
/// Returns a converter for a new public type named , in an assembly of its own.
///
public static SaveConverter ConverterForANewType(string typeName)
{
AssemblyBuilder assembly = AssemblyBuilder.DefineDynamicAssembly(
new AssemblyName("Holds" + Guid.NewGuid().ToString("N")), AssemblyBuilderAccess.Run);
Type type = assembly.DefineDynamicModule("Module").DefineType(typeName, TypeAttributes.Public).CreateType();
return (SaveConverter)Activator.CreateInstance(typeof(StubConverter<>).MakeGenericType(type));
}
public class CtorTests : TestFixture
{
public void TwoConvertersForDifferentTypesOfOneName_Throws()
{
CheckThrow(typeof(InvalidOperationException));
new SaveConverters(new[] { ConverterForANewType("Zoo.Twin"), ConverterForANewType("Zoo.Twin") });
}
public void TwoGenericConvertersForOneGenericType_Throws()
{
CheckThrow(typeof(InvalidOperationException));
new SaveConverters(new SaveConverter[0],
new[] { typeof(BoxConverter<>), typeof(StructBoxConverter<>) });
}
public void GenericConverterOfArrays_Throws()
{
CheckThrow(typeof(InvalidOperationException));
new SaveConverters(new SaveConverter[0], new[] { typeof(ArrayOfParameterConverter<>) });
}
public void GenericConverterOfLists_Throws()
{
CheckThrow(typeof(InvalidOperationException));
new SaveConverters(new SaveConverter[0], new[] { typeof(ListConverter<>) });
}
public void GenericConverterOfMultiDimensionalArrays_Throws()
{
CheckThrow(typeof(InvalidOperationException));
new SaveConverters(new SaveConverter[0], new[] { typeof(MultiDimensionalConverter<>) });
}
public void GenericConverterOfQueues_Throws()
{
CheckThrow(typeof(InvalidOperationException));
new SaveConverters(new SaveConverter[0], new[] { typeof(QueueConverter<>) });
}
public void GenericConverterOfStacks_Throws()
{
CheckThrow(typeof(InvalidOperationException));
new SaveConverters(new SaveConverter[0], new[] { typeof(StackConverter<>) });
}
public void GenericConverterOfSets_Throws()
{
CheckThrow(typeof(InvalidOperationException));
new SaveConverters(new SaveConverter[0], new[] { typeof(HashSetConverter<>) });
}
public void GenericConverterOfDictionaries_Throws()
{
CheckThrow(typeof(InvalidOperationException));
new SaveConverters(new SaveConverter[0], new[] { typeof(DictionaryConverter<,>) });
}
public void GenericConverterOfNullable_Throws()
{
CheckThrow(typeof(InvalidOperationException));
new SaveConverters(new SaveConverter[0], new[] { typeof(NullableOfAnyConverter<>) });
}
public void GenericConverterWhoseTypeCantBeMatched_Throws()
{
CheckThrow(typeof(InvalidOperationException));
new SaveConverters(new SaveConverter[0], new[] { typeof(AnyTypeConverter<>) });
}
public void TwoFamiliesOfOneBaseClass_Throws()
{
CheckThrow(typeof(InvalidOperationException));
new SaveConverters(new SaveConverter[0],
new[] { typeof(CreatureConverter<>), typeof(ThrowingCreatureConverter<>) });
}
public void TwoFamiliesOfOneGenericBaseClass_Throws()
{
CheckThrow(typeof(InvalidOperationException));
new SaveConverters(new SaveConverter[0],
new[] { typeof(HerdConverter<,>), typeof(StructHerdConverter<,>) });
}
public void FamiliesOfDifferentInterfaces_AreAccepted()
{
new SaveConverters(new SaveConverter[0],
new[] { typeof(CarnivoreConverter<>), typeof(PredatorConverter<>), typeof(AquaticConverter<>) });
}
public void FamiliesOfDifferentBaseClasses_AreAccepted()
{
new SaveConverters(new SaveConverter[0],
new[] { typeof(CreatureConverter<>), typeof(WolfConverter<>), typeof(HerdConverter<,>) });
}
public void TwoConvertersForOneType_Throws()
{
CheckThrow(typeof(InvalidOperationException));
new SaveConverters(new SaveConverter[] { new StubConverter(), new StubConverter() });
}
public void ConverterForAnInterface_Throws()
{
CheckThrow(typeof(InvalidOperationException));
new SaveConverters(new SaveConverter[] { new StubConverter() });
}
public void ConverterForABuiltInType_Throws()
{
CheckThrow(typeof(InvalidOperationException));
new SaveConverters(new SaveConverter[] { new StubConverter() });
}
public void ConverterForAnEnum_Throws()
{
CheckThrow(typeof(InvalidOperationException));
new SaveConverters(new SaveConverter[] { new StubConverter() });
}
public void ConverterForANullableEnum_Throws()
{
CheckThrow(typeof(InvalidOperationException));
new SaveConverters(new SaveConverter[] { new StubConverter() });
}
public void ConverterForANullableBuiltInType_Throws()
{
CheckThrow(typeof(InvalidOperationException));
new SaveConverters(new SaveConverter[] { new StubConverter() });
}
public void ConverterForAnArray_Throws()
{
CheckThrow(typeof(InvalidOperationException));
new SaveConverters(new SaveConverter[] { new StubConverter() });
}
public void ConverterForAList_Throws()
{
CheckThrow(typeof(InvalidOperationException));
new SaveConverters(new SaveConverter[] { new StubConverter>() });
}
public void ConverterForASet_Throws()
{
CheckThrow(typeof(InvalidOperationException));
new SaveConverters(new SaveConverter[] { new StubConverter>() });
}
public void ConverterForADictionary_Throws()
{
CheckThrow(typeof(InvalidOperationException));
new SaveConverters(new SaveConverter[] { new StubConverter>() });
}
public void ConverterForAMultiDimensionalArray_Throws()
{
CheckThrow(typeof(InvalidOperationException));
new SaveConverters(new SaveConverter[] { new StubConverter() });
}
public void ConverterForAQueue_Throws()
{
CheckThrow(typeof(InvalidOperationException));
new SaveConverters(new SaveConverter[] { new StubConverter>() });
}
public void ConverterForAStack_Throws()
{
CheckThrow(typeof(InvalidOperationException));
new SaveConverters(new SaveConverter[] { new StubConverter>() });
}
public void ConverterForAnotherGenericType_IsAllowed()
{
new SaveConverters(new SaveConverter[] { new StubConverter>() });
}
}
public class BuiltInCollectionsTests : TestFixture
{
public void HoldsTheNineGenericCollectionsVellumSaves()
{
var expected = new HashSet
{
typeof(List<>), typeof(LinkedList<>), typeof(HashSet<>), typeof(SortedSet<>), typeof(Queue<>),
typeof(Stack<>), typeof(Dictionary<,>), typeof(SortedDictionary<,>), typeof(SortedList<,>),
};
CheckEqual(9, SaveConverters.BuiltInCollections.Count);
CheckTrue(expected.SetEquals(SaveConverters.BuiltInCollections));
}
public void EachIsABuiltInCollection()
{
foreach (Type definition in SaveConverters.BuiltInCollections)
{
CheckTrue(SaveConverters.IsBuiltInCollection(definition));
}
}
}
public class IsBuiltInCollectionTests : TestFixture
{
public void OneDimensionalArray_Is()
{
CheckTrue(SaveConverters.IsBuiltInCollection(typeof(int[])));
CheckTrue(SaveConverters.IsBuiltInCollection(typeof(string[][])));
}
public void ArrayOfTwoOrThreeDimensions_Is()
{
CheckTrue(SaveConverters.IsBuiltInCollection(typeof(int[,])));
CheckTrue(SaveConverters.IsBuiltInCollection(typeof(string[,,])));
}
public void ArrayOfAnyNumberOfDimensions_Is()
{
CheckTrue(SaveConverters.IsBuiltInCollection(typeof(int[,,,])));
CheckTrue(SaveConverters.IsBuiltInCollection(typeof(int).MakeArrayType(32)));
}
public void ArrayThatDoesntStartAtTheOrdinaryZero_IsNot()
{
CheckFalse(SaveConverters.IsBuiltInCollection(typeof(int).MakeArrayType(1)));
}
public void ListSetQueueStackAndDictionary_Are()
{
CheckTrue(SaveConverters.IsBuiltInCollection(typeof(List)));
CheckTrue(SaveConverters.IsBuiltInCollection(typeof(HashSet)));
CheckTrue(SaveConverters.IsBuiltInCollection(typeof(Queue)));
CheckTrue(SaveConverters.IsBuiltInCollection(typeof(Stack)));
CheckTrue(SaveConverters.IsBuiltInCollection(typeof(LinkedList)));
CheckTrue(SaveConverters.IsBuiltInCollection(typeof(SortedSet)));
CheckTrue(SaveConverters.IsBuiltInCollection(typeof(SortedDictionary)));
CheckTrue(SaveConverters.IsBuiltInCollection(typeof(SortedList)));
CheckTrue(SaveConverters.IsBuiltInCollection(typeof(Dictionary>)));
}
public void TheirOpenDefinitions_Are()
{
CheckTrue(SaveConverters.IsBuiltInCollection(typeof(List<>)));
CheckTrue(SaveConverters.IsBuiltInCollection(typeof(HashSet<>)));
CheckTrue(SaveConverters.IsBuiltInCollection(typeof(Queue<>)));
CheckTrue(SaveConverters.IsBuiltInCollection(typeof(Stack<>)));
CheckTrue(SaveConverters.IsBuiltInCollection(typeof(LinkedList<>)));
CheckTrue(SaveConverters.IsBuiltInCollection(typeof(SortedSet<>)));
CheckTrue(SaveConverters.IsBuiltInCollection(typeof(Dictionary<,>)));
CheckTrue(SaveConverters.IsBuiltInCollection(typeof(SortedDictionary<,>)));
CheckTrue(SaveConverters.IsBuiltInCollection(typeof(SortedList<,>)));
}
public void OtherGenericTypes_AreNot()
{
CheckFalse(SaveConverters.IsBuiltInCollection(typeof(Collection)));
CheckFalse(SaveConverters.IsBuiltInCollection(typeof(Box)));
CheckFalse(SaveConverters.IsBuiltInCollection(typeof(int?)));
}
public void TypesThatArentGeneric_AreNot()
{
CheckFalse(SaveConverters.IsBuiltInCollection(typeof(int)));
CheckFalse(SaveConverters.IsBuiltInCollection(typeof(Point)));
}
}
public class FromTests : TestFixture
{
public void ConvertersAndGenericConverters_AreBothUsed()
{
SaveSession session = SaveConverters.From(
new[] { typeof(PointConverter), typeof(BoxConverter<>) }).CreateSession();
var value = new Box { Content = new Point(1.5, 0) };
SaveNode node = session.ToNode(value, typeof(Box));
CheckEqual("0x3FF8000000000000", node.RequireEntry("content").RequireEntry("x").Text);
}
public void NoConverters_SavesOnlyTheBuiltInTypes()
{
SaveSession session = SaveConverters.From(new Type[0]).CreateSession();
CheckEqual("5", session.ToNode(5, typeof(int)).Text);
}
}
public static int dynamicAssemblyCount;
///
/// Returns a dynamic assembly holding one converter type per entry of , each given
/// the attributes beside its name, and no [SavedWith] list.
///
public static AssemblyBuilder AssemblyWithConverters(params KeyValuePair[] kinds)
{
AssemblyBuilder assembly = AssemblyBuilder.DefineDynamicAssembly(
new AssemblyName("NoGenerator" + ++dynamicAssemblyCount), AssemblyBuilderAccess.Run);
ModuleBuilder module = assembly.DefineDynamicModule("NoGenerator");
foreach (KeyValuePair kind in kinds)
{
TypeBuilder type = module.DefineType(kind.Key, kind.Value, typeof(SaveConverter));
if ((kind.Value & TypeAttributes.Abstract) == 0)
{
StubOverride(type, "get_ValueType", typeof(Type), Type.EmptyTypes);
StubOverride(type, "ToNode", typeof(SaveNode), new[] { typeof(object), typeof(SaveSession) });
StubOverride(type, "FromNode", typeof(object), new[] { typeof(SaveNode), typeof(SaveSession) });
}
type.CreateType();
}
return assembly;
}
public static void StubOverride(TypeBuilder type, string name, Type returnType, Type[] parameters)
{
MethodBuilder method = type.DefineMethod(name, MethodAttributes.Public | MethodAttributes.Virtual
| MethodAttributes.HideBySig | MethodAttributes.SpecialName, returnType, parameters);
ILGenerator il = method.GetILGenerator();
il.Emit(OpCodes.Ldnull);
il.Emit(OpCodes.Ret);
}
public static KeyValuePair Kind(string name, TypeAttributes attributes)
{
return new KeyValuePair(name, attributes);
}
public class TryFindConverterTests : TestFixture
{
public void OneDimensionalArray_GetsAnArrayConverterOfItsElementType()
{
SaveConverters converters = TestConverters();
SaveConverter ints;
SaveConverter strings;
CheckTrue(converters.TryFindConverter(typeof(int[]), out ints));
CheckTrue(converters.TryFindConverter(typeof(string[]), out strings));
CheckTrue(ints.GetType() == typeof(ArrayConverter));
CheckTrue(strings.GetType() == typeof(ArrayConverter));
}
public void ArrayOfArrays_GetsAnArrayConverterOfTheInnerArray()
{
SaveConverter converter;
CheckTrue(TestConverters().TryFindConverter(typeof(int[][]), out converter));
CheckTrue(converter.GetType() == typeof(ArrayConverter));
}
public void TwoOrMoreDimensions_GetAnArrayConverterOfTheirElementType()
{
SaveConverters converters = TestConverters();
SaveConverter table;
SaveConverter cube;
CheckTrue(converters.TryFindConverter(typeof(int[,]), out table));
CheckTrue(converters.TryFindConverter(typeof(int[,,]), out cube));
CheckTrue(table.GetType() == typeof(ArrayConverter));
CheckTrue(cube.GetType() == typeof(ArrayConverter));
CheckTrue(table.ValueType == typeof(int[,]));
CheckTrue(cube.ValueType == typeof(int[,,]));
}
public void SameArrayType_GetsTheSameConverter()
{
SaveConverters converters = TestConverters();
SaveConverter first;
SaveConverter second;
converters.TryFindConverter(typeof(int[]), out first);
converters.TryFindConverter(typeof(int[]), out second);
CheckTrue(ReferenceEquals(first, second));
}
public void ArrayOfPointers_HasNoConverter()
{
SaveConverters converters = TestConverters();
Type pointers = typeof(int).MakePointerType().MakeArrayType();
SaveConverter converter;
CheckFalse(converters.TryFindConverter(pointers, out converter));
CheckFalse(converters.TryFindConverter(pointers, out converter));
CheckNull(converter);
}
}
public class FindPublicConverterTests : TestFixture
{
public void AssemblyWithAPublicConcreteConverter_ReturnsIt()
{
AssemblyBuilder assembly = AssemblyWithConverters(Kind("Lonely", TypeAttributes.Public));
CheckEqual("Lonely", SaveConverters.FindPublicConverter(assembly).FullName);
}
public void AssemblyWithNoTypes_ReturnsNull()
{
CheckNull(SaveConverters.FindPublicConverter(AssemblyWithConverters()));
}
public void AbstractConverter_IsSkipped()
{
AssemblyBuilder assembly = AssemblyWithConverters(
Kind("Abstract", TypeAttributes.Public | TypeAttributes.Abstract));
CheckNull(SaveConverters.FindPublicConverter(assembly));
}
public void NonPublicConverter_IsSkipped()
{
AssemblyBuilder assembly = AssemblyWithConverters(Kind("Hidden", TypeAttributes.NotPublic));
CheckNull(SaveConverters.FindPublicConverter(assembly));
}
public void VellumItself_IsSkipped()
{
CheckNull(SaveConverters.FindPublicConverter(typeof(SaveConverters).Assembly));
}
}
public class ForTests : TestFixture
{
public void AnAssemblyWithAPublicConverterAndNoSavedWith_Throws()
{
AssemblyBuilder assembly = AssemblyWithConverters(Kind("Lonely", TypeAttributes.Public));
CheckThrow(typeof(InvalidOperationException));
SaveConverters.For(assembly);
}
public void TheForgottenGeneratorError_NamesTheAssemblyAndTheConverter()
{
AssemblyBuilder assembly = AssemblyWithConverters(Kind("Lonely", TypeAttributes.Public));
string message = null;
try
{
SaveConverters.For(assembly);
}
catch (InvalidOperationException exception)
{
message = exception.Message;
}
CheckNotNull(message);
CheckTrue(message.Contains(assembly.GetName().Name));
CheckTrue(message.Contains("Lonely"));
CheckTrue(message.Contains("Vellum.Generator"));
}
public void AnAssemblyWithAPublicConverterThatItNames_DoesNotThrow()
{
AssemblyBuilder assembly = AssemblyWithConverters(Kind("Lonely", TypeAttributes.Public));
ConstructorInfo attribute = typeof(SavedWithAttribute).GetConstructor(new[] { typeof(Type) });
assembly.SetCustomAttribute(new CustomAttributeBuilder(
attribute, new object[] { typeof(PointConverter) }));
CheckNotNull(SaveConverters.For(assembly));
}
public void AnAssemblyWithNoConvertersAtAll_DoesNotThrow()
{
CheckNotNull(SaveConverters.For(AssemblyWithConverters()));
}
public void AnAssemblyNamingAGenericConverter_SavesWithItsClosedConverters()
{
AssemblyBuilder assembly = AssemblyBuilder.DefineDynamicAssembly(
new AssemblyName("NamesAGenericConverter"), AssemblyBuilderAccess.Run);
ConstructorInfo attribute = typeof(SavedWithAttribute).GetConstructor(new[] { typeof(Type) });
assembly.SetCustomAttribute(
new CustomAttributeBuilder(attribute, new object[] { typeof(BoxConverter<>) }));
SaveSession session = SaveConverters.For(assembly).CreateSession();
SaveNode node = session.ToNode(new Box { Content = 7 }, typeof(Box));
CheckEqual("7", node.RequireEntry("content").Text);
}
public void FindsTheConvertersTheAssemblyNames()
{
SaveSession session = Session();
CheckEqual(SaveNode.Kinds.Table, session.ToNode(new Point(1, 2), typeof(Point)).Kind);
CheckEqual(SaveNode.Kinds.Table, session.ToNode(new Waypoint(), typeof(Waypoint)).Kind);
CheckEqual(SaveNode.Kinds.Table, session.ToNode(new Link(), typeof(Link)).Kind);
}
public void AnAssemblyNamingNoConverters_CantSaveAConvertedType()
{
SaveSession session = SaveConverters.For(typeof(SaveNode).Assembly).CreateSession();
CheckThrow(typeof(NotSupportedException));
session.ToNode(new Point(1, 2), typeof(Point));
}
}
public class VerifyInterfaceFamiliesTests : TestFixture
{
public static SaveConverters WithFamilies(params Type[] families)
{
return new SaveConverters(new SaveConverter[0], families);
}
public static string ThrownMessage(SaveConverters converters, params Type[] types)
{
try
{
converters.VerifyInterfaceFamilies(types);
}
catch (InvalidOperationException exception)
{
return exception.Message;
}
return null;
}
public static Assembly AssemblyImplementing(params Type[] interfaces)
{
AssemblyBuilder assembly = AssemblyBuilder.DefineDynamicAssembly(
new AssemblyName("Implements" + Guid.NewGuid().ToString("N")), AssemblyBuilderAccess.Run);
assembly.DefineDynamicModule("Module")
.DefineType("Chimera", TypeAttributes.Public, typeof(object), interfaces)
.CreateType();
return assembly;
}
public void TypeInTwoUnrelatedFamilies_ThrowsNamingTheTypeAndBothConverters()
{
string message = ThrownMessage(WithFamilies(typeof(CarnivoreConverter<>), typeof(AquaticConverter<>)),
typeof(Shark));
CheckNotNull(message);
CheckTrue(message.Contains("Shark"));
CheckTrue(message.Contains("CarnivoreConverter"));
CheckTrue(message.Contains("AquaticConverter"));
}
public void EveryUnsaveableType_IsNamedInTheOneException()
{
string message = ThrownMessage(WithFamilies(typeof(CarnivoreConverter<>), typeof(AquaticConverter<>),
typeof(VesselConverter<,>)), typeof(Shark), typeof(Lynx), typeof(Twin));
CheckNotNull(message);
CheckTrue(message.Contains("Shark"));
CheckTrue(message.Contains("Twin"));
CheckFalse(message.Contains("Lynx"));
}
public void TypeImplementingAGenericInterfaceTwice_ThrowsWithJustThatFamily()
{
string message = ThrownMessage(WithFamilies(typeof(VesselConverter<,>)), typeof(Twin));
CheckNotNull(message);
CheckTrue(message.Contains("Twin"));
}
public void TypesEachFamilyCanChoose_DoNotThrow()
{
CheckNull(ThrownMessage(WithFamilies(typeof(CarnivoreConverter<>), typeof(PredatorConverter<>),
typeof(AquaticConverter<>), typeof(VesselConverter<,>)), typeof(Lynx), typeof(Orca),
typeof(Otter), typeof(Sprat), typeof(Cod), typeof(Barrel), typeof(Gryphon)));
}
public void TypesNoFamilyCouldSave_DoNotThrow()
{
CheckNull(ThrownMessage(WithFamilies(typeof(CarnivoreConverter<>), typeof(AquaticConverter<>)),
typeof(int), typeof(string), typeof(DayOfWeek), typeof(ICarnivore), typeof(AbstractAquatic),
typeof(List), typeof(int[])));
}
public void NoInterfaceFamilies_DoNotThrow()
{
CheckNull(ThrownMessage(WithFamilies(typeof(CreatureConverter<>)), typeof(Shark), typeof(Twin)));
}
public void Assembly_ChecksTheTypesItDefines()
{
SaveConverters converters = WithFamilies(typeof(CarnivoreConverter<>), typeof(AquaticConverter<>));
CheckThrow(typeof(InvalidOperationException));
converters.VerifyInterfaceFamilies(AssemblyImplementing(typeof(ICarnivore), typeof(IAquatic)));
}
public void Assembly_WithNothingAmbiguous_DoesNotThrow()
{
WithFamilies(typeof(CarnivoreConverter<>), typeof(AquaticConverter<>))
.VerifyInterfaceFamilies(AssemblyImplementing(typeof(ICarnivore)));
}
}
public class SubtypeContentsTests : TestFixture
{
public void TableWithEntries_HoldsThemAllButTheName()
{
SaveNode node = SaveNode.NewTable();
node.Entries[SaveConverters.TypeKey] = SaveNode.OfText("Vellum.UnitTests.Dog");
node.Entries["name"] = SaveNode.OfText("Rex");
node.Entries["age"] = SaveNode.OfText("3");
SaveNode contents = SaveConverters.SubtypeContents(node, "Vellum.UnitTests.Dog");
CheckEqual(2, contents.Entries.Count);
CheckTrue(ReferenceEquals(node.Entries["name"], contents.Entries["name"]));
CheckTrue(ReferenceEquals(node.Entries["age"], contents.Entries["age"]));
}
public void TableWithEntries_IsLeftAlone()
{
SaveNode node = SaveNode.NewTable();
node.Entries[SaveConverters.TypeKey] = SaveNode.OfText("Vellum.UnitTests.Dog");
SaveConverters.SubtypeContents(node, "Vellum.UnitTests.Dog");
CheckEqual(1, node.Entries.Count);
}
public void NameOnly_HoldsAnEmptyTable()
{
SaveNode node = SaveNode.NewTable();
node.Entries[SaveConverters.TypeKey] = SaveNode.OfText("Vellum.UnitTests.Dog");
SaveNode contents = SaveConverters.SubtypeContents(node, "Vellum.UnitTests.Dog");
CheckEqual(SaveNode.Kinds.Table, contents.Kind);
CheckEqual(0, contents.Entries.Count);
}
public void ValueEntry_IsWhatTheNodeHolds()
{
SaveNode value = SaveNode.OfText("5");
SaveNode node = SaveNode.NewTable();
node.Entries[SaveConverters.TypeKey] = SaveNode.OfText("int");
node.Entries[SaveConverters.ValueKey] = value;
CheckTrue(ReferenceEquals(value, SaveConverters.SubtypeContents(node, "int")));
}
public void ValueEntryBesideOthers_Throws()
{
SaveNode node = SaveNode.NewTable();
node.Entries[SaveConverters.TypeKey] = SaveNode.OfText("int");
node.Entries[SaveConverters.ValueKey] = SaveNode.OfText("5");
node.Entries["extra"] = SaveNode.OfText("6");
CheckThrow(typeof(InvalidDataException));
SaveConverters.SubtypeContents(node, "int");
}
}
public class CreateSessionTests : TestFixture
{
public void ObjectSavedInTwoCalls_IsTheSameNode()
{
SaveSession session = Session();
var waypoint = new Waypoint { Name = "home" };
SaveNode first = session.ToNode(waypoint, typeof(Waypoint));
CheckTrue(ReferenceEquals(first, session.ToNode(waypoint, typeof(Waypoint))));
}
public void ObjectSavedInAnotherSession_IsANewNode()
{
SaveConverters registry = TestConverters();
var waypoint = new Waypoint { Name = "home" };
SaveNode first = registry.CreateSession().ToNode(waypoint, typeof(Waypoint));
CheckFalse(ReferenceEquals(first, registry.CreateSession().ToNode(waypoint, typeof(Waypoint))));
}
public void ObjectChangedBetweenTwoSaves_IsStillTheFirstNode()
{
SaveSession session = Session();
var waypoint = new Waypoint { Name = "home" };
SaveNode first = session.ToNode(waypoint, typeof(Waypoint));
waypoint.Name = "away";
SaveNode second = session.ToNode(waypoint, typeof(Waypoint));
CheckTrue(ReferenceEquals(first, second));
CheckEqual("home", second.RequireEntry("name").Text);
}
public void NodeLoadedInTwoCalls_IsTheSameObject()
{
SaveConverters registry = TestConverters();
SaveNode node = registry.CreateSession().ToNode(new Waypoint { Name = "home" }, typeof(Waypoint));
SaveSession session = registry.CreateSession();
object first = session.FromNode(node, typeof(Waypoint));
CheckTrue(ReferenceEquals(first, session.FromNode(node, typeof(Waypoint))));
}
public void NodeLoadedInAnotherSession_IsAnotherObject()
{
SaveConverters registry = TestConverters();
SaveNode node = registry.CreateSession().ToNode(new Waypoint { Name = "home" }, typeof(Waypoint));
object first = registry.CreateSession().FromNode(node, typeof(Waypoint));
CheckFalse(ReferenceEquals(first, registry.CreateSession().FromNode(node, typeof(Waypoint))));
}
public void NodeLoadedAsTwoTypes_Throws()
{
SaveConverters registry = TestConverters();
SaveNode node = registry.CreateSession().ToNode(new Waypoint { Name = "home" }, typeof(Waypoint));
SaveSession session = registry.CreateSession();
session.FromNode(node, typeof(Waypoint));
CheckThrow(typeof(InvalidDataException));
session.FromNode(node, typeof(Link));
}
public void SessionsOnSeveralThreads_EachSaveAndLoadTheirOwnValues()
{
const int threadCount = 4;
const int roundsPerThread = 20;
SaveConverters registry = WithGenericConverters();
var start = new Barrier(threadCount);
var failures = new List();
var threads = new List();
for (int thread = 0; thread < threadCount; ++thread)
{
int seed = thread;
threads.Add(new Thread(() =>
{
try
{
start.SignalAndWait();
for (int round = 0; round < roundsPerThread; ++round)
{
SaveSession session = registry.CreateSession();
var boxed = new Box>
{
Content = new List { new Point(seed, round) },
};
var pair = new Pair { First = seed, Second = "round" + round };
var loadedBox = (Box>)session.FromNode(
session.ToNode(boxed, typeof(Box>)), typeof(Box>));
var loadedPair = (Pair)session.FromNode(
session.ToNode(pair, typeof(Pair)), typeof(Pair));
if (loadedBox.Content[0].X != seed || loadedBox.Content[0].Y != round
|| loadedPair.First != seed || loadedPair.Second != "round" + round)
{
throw new InvalidOperationException("A thread got another thread's values.");
}
}
}
catch (Exception exception)
{
lock (failures)
{
failures.Add(exception);
}
}
}));
}
foreach (Thread thread in threads)
{
thread.Start();
}
foreach (Thread thread in threads)
{
thread.Join();
}
CheckEqual(0, failures.Count);
}
}
public class PublishTests : TestFixture
{
public static SaveNode EmptyChair()
{
SaveNode returnMe = SaveNode.NewTable();
returnMe.Entries["twin"] = SaveNode.Null;
return returnMe;
}
public void ObjectPublishedForANode_IsWhatLoadingThatNodeReturns()
{
SaveSession session = Session();
SaveNode node = EmptyChair();
var half = new Chair();
session.Publish(node, half);
CheckTrue(ReferenceEquals(half, session.FromNode(node, typeof(Chair))));
}
public void ObjectPublishedInAnotherSession_IsNotHandedOut()
{
SaveConverters registry = TestConverters();
SaveNode node = EmptyChair();
var half = new Chair();
registry.CreateSession().Publish(node, half);
CheckFalse(ReferenceEquals(half, registry.CreateSession().FromNode(node, typeof(Chair))));
}
public void ObjectOfAnotherTypeThanIsAskedFor_Throws()
{
SaveSession session = Session();
SaveNode node = EmptyChair();
session.Publish(node, new Stamp());
CheckThrow(typeof(InvalidDataException));
session.FromNode(node, typeof(Chair));
}
public void Struct_IsIgnored()
{
SaveSession session = Session();
SaveNode node = EmptyChair();
session.Publish(node, new Point(1, 2));
CheckNotNull(session.FromNode(node, typeof(Chair)) as Chair);
}
public void Null_Throws()
{
CheckThrow(typeof(ArgumentNullException));
TestConverters().CreateSession().Publish(EmptyChair(), null);
}
}
public class MaxDepthTests : TestFixture
{
public void Default_AllowsAMillionLevels()
{
CheckEqual(1 << 20, TestConverters().MaxDepth);
}
public void Set_IsWhatGetReturns()
{
SaveConverters registry = TestConverters();
registry.MaxDepth = 5000;
CheckEqual(5000, registry.MaxDepth);
}
public void SetToOne_Works()
{
SaveConverters registry = TestConverters();
registry.MaxDepth = 1;
CheckEqual(1, registry.MaxDepth);
}
public void SetToZero_Throws()
{
CheckThrow(typeof(ArgumentOutOfRangeException));
TestConverters().MaxDepth = 0;
}
}
public class ToNodeTests : TestFixture
{
public void GenericConverter_IsClosedOverTheTypeArgument()
{
SaveNode node = GenericSession().ToNode(new Box { Content = 7 }, typeof(Box));
CheckEqual("7", node.RequireEntry("content").Text);
}
public void GenericConverter_IsClosedOverEachTypeArgumentSeparately()
{
SaveSession session = GenericSession();
SaveNode number = session.ToNode(new Box { Content = 7 }, typeof(Box));
SaveNode text = session.ToNode(new Box { Content = "seven" }, typeof(Box));
CheckEqual("7", number.RequireEntry("content").Text);
CheckEqual("seven", text.RequireEntry("content").Text);
}
public void ConverterWithItsParametersInAnotherOrder_SavesEachPartAsItsOwnType()
{
var value = new Pair { First = 1, Second = "one" };
SaveNode node = GenericSession().ToNode(value, typeof(Pair));
CheckEqual("1", node.RequireEntry("first").Text);
CheckEqual("one", node.RequireEntry("second").Text);
}
public void GenericTypeInsideAGenericType_Saves()
{
var value = new Box> { Content = new Box { Content = new Point(1.5, 0) } };
SaveNode node = GenericSession().ToNode(value, typeof(Box>));
CheckEqual("0x3FF8000000000000", node.RequireEntry("content").RequireEntry("content")
.RequireEntry("x").Text);
}
public void Array_SavesEachElementAsItsType()
{
SaveNode node = GenericSession().ToNode(new[] { 3, 1, 2 }, typeof(int[]));
CheckEqual(new[] { "3", "1", "2" }, node.Items.Select(item => item.Text).ToArray());
}
public void ArrayOfArrays_Saves()
{
var value = new[] { new[] { "a" }, new[] { "b", "c" } };
SaveNode node = GenericSession().ToNode(value, typeof(string[][]));
CheckEqual("c", node.Items[1].Items[1].Text);
}
public void ExactConverter_WinsOverAGenericOneThatAlsoMatches()
{
SaveSession session = new SaveConverters(new SaveConverter[] { new StubConverter>() },
new[] { typeof(BoxConverter<>) }).CreateSession();
CheckEqual("stub", session.ToNode(new Box(), typeof(Box)).Text);
CheckEqual("7", session.ToNode(new Box { Content = 7 }, typeof(Box))
.RequireEntry("content").Text);
}
public void ClosedConverter_IsMadeOnceAndReused()
{
SaveSession session = GenericSession();
CountedConverter.Made = 0;
session.ToNode(new Counted(), typeof(Counted));
session.ToNode(new Counted(), typeof(Counted));
session.FromNode(SaveNode.OfText("counted"), typeof(Counted));
CheckEqual(1, CountedConverter.Made);
}
public void FamilyType_IsWrittenByTheFamilysConverter()
{
SaveSession session = new SaveConverters(new SaveConverter[0], new[] { typeof(CreatureConverter<>) })
.CreateSession();
CheckEqual("Rex", session.ToNode(new Wolf { Name = "Rex" }, typeof(Wolf)).Text);
}
public void ExactConverter_WinsOverAFamily()
{
SaveSession session = new SaveConverters(new SaveConverter[] { new StubConverter() },
new[] { typeof(CreatureConverter<>) }).CreateSession();
CheckEqual("stub", session.ToNode(new Wolf { Name = "Rex" }, typeof(Wolf)).Text);
CheckEqual("Rex", session.ToNode(new AlphaWolf { Name = "Rex" }, typeof(AlphaWolf)).Text);
}
public void NearestBaseClassWithAFamily_Wins()
{
SaveSession session = new SaveConverters(new SaveConverter[0],
new[] { typeof(CreatureConverter<>), typeof(WolfConverter<>) }).CreateSession();
CheckEqual("Rex", session.ToNode(new Creature { Name = "Rex" }, typeof(Creature)).Text);
CheckEqual("wolf Rex", session.ToNode(new Wolf { Name = "Rex" }, typeof(Wolf)).Text);
CheckEqual("wolf Rex", session.ToNode(new AlphaWolf { Name = "Rex" }, typeof(AlphaWolf)).Text);
}
public void FamilyWhoseConstraintTheTypeBreaks_LeavesItToTheNextBaseClass()
{
SaveSession session = new SaveConverters(new SaveConverter[0],
new[] { typeof(StructLabeledHerdConverter<,,>), typeof(HerdConverter<,>) }).CreateSession();
var value = new NameHerd { Lead = "Rex" };
CheckEqual("Rex", session.ToNode(value, typeof(NameHerd)).RequireEntry("lead").Text);
}
public void FamilyOfAGenericBase_WritesWithTheBasesArguments()
{
SaveSession session = new SaveConverters(new SaveConverter[0], new[] { typeof(HerdConverter<,>) })
.CreateSession();
CheckEqual("4",session.ToNode(new IntHerd { Lead = 4 }, typeof(IntHerd)).RequireEntry("lead").Text);
}
public void FamilyConverter_IsMadeOncePerType()
{
SaveSession session = new SaveConverters(new SaveConverter[0], new[] { typeof(CreatureConverter<>) })
.CreateSession();
CreatureConverter.Made = 0;
CreatureConverter.Made = 0;
session.ToNode(new Wolf { Name = "Rex" }, typeof(Wolf));
session.ToNode(new Wolf { Name = "Fang" }, typeof(Wolf));
session.ToNode(new AlphaWolf { Name = "Rex" }, typeof(AlphaWolf));
CheckEqual(1, CreatureConverter.Made);
CheckEqual(1, CreatureConverter.Made);
}
public void TypeOutsideEveryFamily_IsSavedAsItWouldBeWithoutFamilies()
{
SaveSession session = new SaveConverters(new SaveConverter[0],
new[] { typeof(CreatureConverter<>), typeof(HerdConverter<,>) }).CreateSession();
CheckEqual("5", session.ToNode(5, typeof(int)).Text);
CheckEqual("five", session.ToNode("five", typeof(string)).Text);
CheckEqual("five", session.ToNode("five", typeof(string)).Text);
}
public static SaveSession WithInterfaceFamilies(params Type[] families)
{
return new SaveConverters(new SaveConverter[0], families).CreateSession();
}
public void InterfaceFamilyType_IsWrittenByTheFamilysConverter()
{
SaveSession session = WithInterfaceFamilies(typeof(CarnivoreConverter<>));
CheckEqual("carnivore", session.ToNode(new Lynx(), typeof(Lynx)).Text);
CheckEqual("carnivore", session.ToNode(new Orca(), typeof(Orca)).Text);
}
public void StructInAnInterfaceFamily_IsWrittenByTheFamilysConverter()
{
CheckEqual("aquatic", WithInterfaceFamilies(typeof(AquaticConverter<>))
.ToNode(new Sprat(), typeof(Sprat)).Text);
}
public void ExactConverter_WinsOverAnInterfaceFamily()
{
SaveSession session = new SaveConverters(new SaveConverter[] { new StubConverter() },
new[] { typeof(CarnivoreConverter<>) }).CreateSession();
CheckEqual("stub", session.ToNode(new Lynx(), typeof(Lynx)).Text);
}
public void ClassFamily_WinsOverAnInterfaceFamily()
{
SaveSession session = WithInterfaceFamilies(typeof(CarnivoreConverter<>),
typeof(CreatureConverter<>));
CheckEqual("Griff", session.ToNode(new Gryphon { Name = "Griff" }, typeof(Gryphon)).Text);
}
public void MostDerivedInterfaceFamily_WinsWhateverTheRegistrationOrder()
{
CheckEqual("predator", WithInterfaceFamilies(typeof(CarnivoreConverter<>),
typeof(PredatorConverter<>)).ToNode(new Orca(), typeof(Orca)).Text);
CheckEqual("predator", WithInterfaceFamilies(typeof(PredatorConverter<>),
typeof(CarnivoreConverter<>)).ToNode(new Orca(), typeof(Orca)).Text);
CheckEqual("carnivore", WithInterfaceFamilies(typeof(PredatorConverter<>),
typeof(CarnivoreConverter<>)).ToNode(new Lynx(), typeof(Lynx)).Text);
}
public void TypeInTwoUnrelatedInterfaceFamilies_ThrowsNamingBothConverters()
{
SaveSession session = WithInterfaceFamilies(typeof(CarnivoreConverter<>),
typeof(AquaticConverter<>));
string message = null;
try
{
session.ToNode(new Shark(), typeof(Shark));
}
catch (InvalidOperationException exception)
{
message = exception.Message;
}
CheckTrue(message != null && message.Contains("CarnivoreConverter")
&& message.Contains("AquaticConverter"));
}
public void TypeInOnlyOneOfTwoUnrelatedInterfaceFamilies_IsSaved()
{
SaveSession session = WithInterfaceFamilies(typeof(CarnivoreConverter<>),
typeof(AquaticConverter<>));
CheckEqual("aquatic", session.ToNode(new Otter(), typeof(Otter)).Text);
}
public void GenericInterfaceFamily_TakesItsTypeParameterFromTheInterface()
{
CheckEqual("vessel of Int32", WithInterfaceFamilies(typeof(VesselConverter<,>))
.ToNode(new Barrel(), typeof(Barrel)).Text);
}
public void TypeImplementingAGenericInterfaceTwice_Throws()
{
SaveSession session = WithInterfaceFamilies(typeof(VesselConverter<,>));
CheckThrow(typeof(InvalidOperationException));
session.ToNode(new Twin(), typeof(Twin));
}
public void InterfaceFamilyOfAnInterfaceEverythingImplements_LeavesVellumsOwnTypesAlone()
{
SaveSession session = WithInterfaceFamilies(typeof(ComparableConverter<>));
CheckEqual("5", session.ToNode(5, typeof(int)).Text);
CheckEqual("text", session.ToNode("text", typeof(string)).Text);
CheckEqual("Monday", session.ToNode(DayOfWeek.Monday, typeof(DayOfWeek)).Text);
CheckEqual("5", session.ToNode(new List { 5 }, typeof(List)).Items[0].Text);
}
public void GenericSubtypeOfAFamilyBase_Throws()
{
SaveSession session = new SaveConverters(new SaveConverter[0], new[] { typeof(HerdConverter<,>) })
.CreateSession();
CheckThrow(typeof(NotSupportedException));
session.ToNode(new LabeledHerd { Lead = "Rex" }, typeof(Herd));
}
public void SubtypeOfAClassNoFamilySaves_Throws()
{
SaveSession session = new SaveConverters(new SaveConverter[0], new[] { typeof(HerdConverter<,>) })
.CreateSession();
CheckThrow(typeof(NotSupportedException));
session.ToNode(new Wolf { Name = "Rex" }, typeof(Creature));
}
public void FamilyConstructorThatThrows_ThrowsItsOwnException()
{
SaveSession session = new SaveConverters(new SaveConverter[0],
new[] { typeof(ThrowingCreatureConverter<>) }).CreateSession();
CheckThrow(typeof(FormatException));
session.ToNode(new Wolf(), typeof(Wolf));
}
public static SaveConverters WithMarkedConverters()
{
return new SaveConverters(new SaveConverter[] { new PointConverter() }, new[]
{
typeof(MarkedBoxConverter<>), typeof(MarkedPairConverter<,>), typeof(MarkedHerdConverter<,>),
typeof(CreatureConverter<>),
});
}
public static SaveSession MarkedSession()
{
return WithMarkedConverters().CreateSession();
}
public void MarkedArgumentTheTypeCanSave_IsSaved()
{
SaveSession session = MarkedSession();
CheckEqual("marked", session.ToNode(new Box(), typeof(Box)).Text);
CheckEqual("marked", session.ToNode(new Box(), typeof(Box)).Text);
CheckEqual("marked", session.ToNode(new Box(), typeof(Box)).Text);
CheckEqual("marked", session.ToNode(new Box(), typeof(Box)).Text);
CheckEqual("marked", session.ToNode(new Box>(), typeof(Box>)).Text);
}
public void MarkedArgumentOnlyASubtypeCanSave_IsSaved()
{
CheckEqual("marked", MarkedSession().ToNode(new Box(),
typeof(Box)).Text);
}
public void MarkedArgumentNothingSaves_Throws()
{
CheckThrow(typeof(InvalidOperationException));
MarkedSession().ToNode(new Box(), typeof(Box));
}
public void MarkedArgumentNothingSaves_ThrowsEachTime()
{
SaveSession session = MarkedSession();
try
{
session.ToNode(new Box(), typeof(Box));
}
catch (InvalidOperationException)
{
}
CheckThrow(typeof(InvalidOperationException));
session.ToNode(new Box(), typeof(Box));
}
public void MarkedArgumentThatIsItselfMadeOfSomethingNothingSaves_Throws()
{
CheckThrow(typeof(InvalidOperationException));
MarkedSession().ToNode(new Box>(), typeof(Box>));
}
public void ParameterWithoutTheMark_AcceptsAnything()
{
SaveSession session = SessionWith(typeof(MarkedPairConverter<,>));
CheckEqual("marked", session.ToNode(new Pair(), typeof(Pair)).Text);
}
public void MarkedParameterOfAPairInEitherPlace_IsChecked()
{
SaveSession session = SessionWith(typeof(MarkedPairConverter<,>));
CheckThrow(typeof(InvalidOperationException));
session.ToNode(new Pair(), typeof(Pair));
}
public void MarkedFamilyParameterTheTypeCanSave_IsSaved()
{
SaveSession session = MarkedSession();
CheckEqual("marked", session.ToNode(new IntHerd(), typeof(IntHerd)).Text);
}
public void MarkedFamilyParameterNothingSaves_Throws()
{
CheckThrow(typeof(InvalidOperationException));
MarkedSession().ToNode(new PlanktonHerd(), typeof(PlanktonHerd));
}
public void TypeThatHoldsItselfThroughAMarkedFamilyParameter_IsSaved()
{
CheckEqual("marked", MarkedSession().ToNode(new Flock(), typeof(Flock)).Text);
}
public void NullOfAGenericType_IsNull()
{
CheckEqual(SaveNode.Kinds.Null, GenericSession().ToNode(null, typeof(Box)).Kind);
CheckEqual(SaveNode.Kinds.Null, GenericSession().ToNode(null, typeof(int[])).Kind);
}
public void NullableOfAGenericStruct_UsesTheStructsConverter()
{
SaveSession session = GenericSession();
var value = new Pair { First = 1, Second = 2 };
CheckEqual(SaveNode.Kinds.Null, session.ToNode(null, typeof(Pair?)).Kind);
CheckEqual("2", session.ToNode(value, typeof(Pair?)).RequireEntry("second").Text);
}
public void GenericTypeWithNoConverter_Throws()
{
CheckThrow(typeof(NotSupportedException));
GenericSession().ToNode(new Collection(), typeof(Collection));
}
public void TypeArgumentThatCantBeSaved_Throws()
{
var value = new Box> { Content = new Collection() };
CheckThrow(typeof(NotSupportedException));
GenericSession().ToNode(value, typeof(Box>));
}
public void ArrayWithADimensionNotStartingAtZero_Throws()
{
Array shifted = Array.CreateInstance(typeof(int), new[] { 1, 1 }, new[] { 1, 1 });
CheckThrow(typeof(NotSupportedException));
GenericSession().ToNode(shifted, typeof(int[,]));
}
public void ListOfConvertedValues_SavesEachWithItsConverter()
{
var value = new List> { new Box { Content = new Point(1.5, 0) } };
SaveNode node = GenericSession().ToNode(value, typeof(List>));
CheckEqual("0x3FF8000000000000", node.Items[0].RequireEntry("content").RequireEntry("x").Text);
}
public void ExactConverterForACollectionsItem_IsUsed()
{
SaveNode node = GenericSession().ToNode(new[] { new Point(1.5, 0) }, typeof(Point[]));
CheckEqual("0x3FF8000000000000", node.Items[0].RequireEntry("x").Text);
}
public void TypeArgumentThatBreaksTheConvertersConstraint_Throws()
{
SaveSession session = SessionWith(typeof(StructBoxConverter<>));
CheckThrow(typeof(NotSupportedException));
session.ToNode(new Box(), typeof(Box));
}
public void OpenGenericType_Throws()
{
CheckThrow(typeof(NotSupportedException));
GenericSession().ToNode(new Box(), typeof(Box<>));
}
public void GenericChainFarDeeperThanTheStackAllows_Saves()
{
SaveNode node = GenericSession().ToNode(CellChain(5000), typeof(Cell));
CheckEqual(5000, DepthOf(node, "next"));
}
public void ConvertedValue_IsWhatItsConverterWrites()
{
SaveNode node = Session().ToNode(new Point(1.5, -2), typeof(Point));
CheckEqual("0x3FF8000000000000", node.RequireEntry("x").Text);
CheckEqual("0xC000000000000000", node.RequireEntry("y").Text);
}
public void ConverterCanSaveAnotherConvertedType()
{
var value = new Waypoint { Name = "end", At = new Point(1.5, 0) };
SaveNode node = Session().ToNode(value, typeof(Waypoint));
CheckEqual("0x3FF8000000000000", node.RequireEntry("at").RequireEntry("x").Text);
}
public void NullReferenceOrNullable_IsNullWithoutAskingTheConverter()
{
CheckEqual(SaveNode.Kinds.Null, Session().ToNode(null, typeof(Waypoint)).Kind);
CheckEqual(SaveNode.Kinds.Null, Session().ToNode(null, typeof(Point?)).Kind);
}
public void BuiltInType_IsSavedAsBuiltIn()
{
CheckEqual("5", Session().ToNode(5, typeof(int)).Text);
}
public void UnsupportedType_Throws()
{
CheckThrow(typeof(NotSupportedException));
Session().ToNode(new Collection(), typeof(Collection));
}
public void ChainFarDeeperThanTheStackAllows_Saves()
{
SaveNode node = Session().ToNode(Chain(10000), typeof(Link));
CheckEqual(10000, DepthOf(node, "next"));
}
public void ChainFarDeeperThanTheStackAllows_SurvivesBeingWrittenAsLuaText()
{
SaveNode node = Session().ToNode(Chain(10000), typeof(Link));
CheckEqual(10000, DepthOf(ThroughLuaText(node), "next"));
}
public void ShortChains_Save()
{
SaveSession session = Session();
CheckEqual(1, DepthOf(session.ToNode(Chain(1), typeof(Link)), "next"));
CheckEqual(2, DepthOf(session.ToNode(Chain(2), typeof(Link)), "next"));
CheckEqual(3, DepthOf(session.ToNode(Chain(3), typeof(Link)), "next"));
}
public void FinishedSave_HoldsNoReferencesToNodesItShares()
{
var shared = new Link();
SaveNode list = Session().ToNode(new List { shared, shared }, typeof(List));
CheckEqual(SaveNode.Kinds.Table, list.Items[0].Kind);
CheckTrue(ReferenceEquals(list.Items[0], list.Items[1]));
}
public void LaterSaveInASession_ReusesTheNodeOfAnObjectSavedEarlier()
{
var shared = new Link { Next = new Link() };
SaveSession session = Session();
SaveNode first = session.ToNode(new List { shared }, typeof(List));
SaveNode second = session.ToNode(new List { shared }, typeof(List));
CheckTrue(ReferenceEquals(first.Items[0], second.Items[0]));
CheckEqual(SaveNode.Kinds.Table, first.Items[0].Kind);
}
public void ValueSharedDeepInsideTwoChains_IsOneNode()
{
var shared = new Link();
Link first = shared;
Link second = shared;
for (int i = 0; i < 30; ++i)
{
first = new Link { Next = first };
second = new Link { Next = second };
}
SaveNode list = Session().ToNode(new List { first, second }, typeof(List));
CheckTrue(ReferenceEquals(Descend(list.Items[0], "next", 30), Descend(list.Items[1], "next", 30)));
}
public void DeepChainThatLoopsBack_ReferencesTheNodeNotACopy()
{
Link head = Chain(40);
Link tail = head;
while (tail.Next != null)
{
tail = tail.Next;
}
tail.Next = head;
SaveNode node = Session().ToNode(head, typeof(Link));
CheckTrue(ReferenceEquals(node, Descend(node, "next", 40)));
}
public void ConverterThatMakesUpValuesForever_Throws()
{
SaveConverters registry = TestConverters();
registry.MaxDepth = 1000;
SaveSession session = registry.CreateSession();
CheckThrow(typeof(InvalidOperationException));
session.ToNode(new Endless(), typeof(Endless));
}
public void ChainWithinMaxDepth_Saves()
{
SaveConverters registry = TestConverters();
registry.MaxDepth = 1000;
SaveSession session = registry.CreateSession();
CheckEqual(900, DepthOf(session.ToNode(Chain(900), typeof(Link)), "next"));
}
public void ChainBeyondMaxDepth_Throws()
{
SaveConverters registry = TestConverters();
registry.MaxDepth = 1000;
SaveSession session = registry.CreateSession();
CheckThrow(typeof(InvalidOperationException));
session.ToNode(Chain(1100), typeof(Link));
}
public void ChainsAroundTheInlineDepth_SaveEveryLink()
{
int inline = SaveSession.InlineDepth;
foreach (int length in new[] { inline - 1, inline, inline + 1, 2 * inline, 3 * inline + 1 })
{
CheckEqual(length, DepthOf(Session().ToNode(Chain(length), typeof(Link)), "next"));
}
}
public void RingsAroundTheInlineDepth_CloseOnTheFirstNode()
{
int inline = SaveSession.InlineDepth;
foreach (int length in new[] { 2, inline - 1, inline, inline + 1, 2 * inline + 3 })
{
Link head = Chain(length);
Link tail = head;
while (tail.Next != null)
{
tail = tail.Next;
}
tail.Next = head;
SaveNode node = Session().ToNode(head, typeof(Link));
CheckTrue(ReferenceEquals(node, Descend(node, "next", length)));
}
}
public void ChainOfMaxDepthLevels_SavesWhetherOrNotItDefers()
{
foreach (int levels in new[] { 3, SaveSession.InlineDepth + 1, 2 * SaveSession.InlineDepth + 3 })
{
SaveConverters registry = TestConverters();
registry.MaxDepth = levels;
CheckEqual(levels, DepthOf(registry.CreateSession().ToNode(Chain(levels), typeof(Link)), "next"));
}
}
public void ChainOneLevelBeyondAMaxDepthThatNeverDefers_Throws()
{
SaveConverters registry = TestConverters();
registry.MaxDepth = 3;
CheckThrow(typeof(InvalidOperationException));
registry.CreateSession().ToNode(Chain(4), typeof(Link));
}
public void ChainOneLevelBeyondAMaxDepthThatDefers_Throws()
{
SaveConverters registry = TestConverters();
registry.MaxDepth = 2 * SaveSession.InlineDepth + 3;
CheckThrow(typeof(InvalidOperationException));
registry.CreateSession().ToNode(Chain(registry.MaxDepth + 1), typeof(Link));
}
public void AfterAConverterThatMadeUpValuesForever_StillSaves()
{
SaveConverters registry = TestConverters();
registry.MaxDepth = 1000;
SaveSession session = registry.CreateSession();
try
{
session.ToNode(new Endless(), typeof(Endless));
}
catch (InvalidOperationException)
{
}
CheckEqual(40, DepthOf(session.ToNode(Chain(40), typeof(Link)), "next"));
}
public void ValueContainingItself_IsANodeThatRefersToItself()
{
var loop = new Link();
loop.Next = loop;
SaveNode node = Session().ToNode(loop, typeof(Link));
CheckEqual(SaveNode.Kinds.Ref, node.RequireEntry("next").Kind);
CheckTrue(ReferenceEquals(node, node.RequireEntry("next").Resolved));
}
public void TwoValuesContainingEachOther_EachReferToTheOther()
{
var first = new Link();
var second = new Link { Next = first };
first.Next = second;
SaveNode node = Session().ToNode(first, typeof(Link));
SaveNode inner = node.RequireEntry("next");
CheckEqual(SaveNode.Kinds.Table, inner.Kind);
CheckTrue(ReferenceEquals(node, inner.RequireEntry("next").Resolved));
}
public void CycleThroughASharedValue_ReferencesTheNodeNotAnotherCopy()
{
var loop = new Link();
loop.Next = loop;
SaveNode list = Session().ToNode(new List { loop, loop }, typeof(List));
CheckTrue(ReferenceEquals(list.Items[0], list.Items[1]));
CheckTrue(ReferenceEquals(list.Items[0], list.Items[0].RequireEntry("next").Resolved));
}
public void ConverterThatReturnsTheNodeOfAValueThatCyclesBack_Throws()
{
var loop = new Echo();
loop.Other = loop;
CheckThrow(typeof(InvalidOperationException));
Session().ToNode(loop, typeof(Echo));
}
public void Subtype_IsSavedByItsOwnConverterAndNamed()
{
SaveNode node = Session().ToNode(new Dog { Name = "Rex", Barks = true }, typeof(Animal));
CheckEqual("Vellum.UnitTests.Dog", node.RequireEntry("__type").Text);
CheckEqual("Rex", node.RequireEntry("name").Text);
CheckEqual("true", node.RequireEntry("barks").Text);
}
public void ExactType_IsNotNamed()
{
SaveNode node = Session().ToNode(new Animal { Name = "Rex" }, typeof(Animal));
CheckFalse(node.Entries.ContainsKey("__type"));
}
public void NullableOfTheExactType_IsNotNamed()
{
SaveNode node = Session().ToNode(new Hamster { Nickname = "Bun" }, typeof(Hamster?));
CheckFalse(node.Entries.ContainsKey("__type"));
}
public void SubtypeWhoseConverterWritesNoTable_IsWrapped()
{
SaveNode node = Session().ToNode(new Fish { Name = "Nemo" }, typeof(Animal));
CheckEqual("Vellum.UnitTests.Fish", node.RequireEntry("__type").Text);
CheckEqual("Nemo", node.RequireEntry("__value").Text);
CheckEqual(2, node.Entries.Count);
}
public void StructInAnInterface_IsSavedByItsOwnConverterAndNamed()
{
SaveNode node = Session().ToNode(new Hamster { Nickname = "Bun" }, typeof(IPet));
CheckEqual("Vellum.UnitTests.Hamster", node.RequireEntry("__type").Text);
CheckEqual("Bun", node.RequireEntry("nickname").Text);
}
public void SubtypesInAList_AreEachNamed()
{
var value = new List { new Animal { Name = "Tom" }, new Dog { Name = "Rex" } };
SaveNode node = Session().ToNode(value, typeof(List));
CheckFalse(node.Items[0].Entries.ContainsKey("__type"));
CheckEqual("Vellum.UnitTests.Dog", node.Items[1].RequireEntry("__type").Text);
}
public void ObjectSavedTwiceInOneCall_IsTheSameNodeInBothPlaces()
{
var waypoint = new Waypoint { Name = "home" };
SaveNode node = Session().ToNode(new List { waypoint, waypoint },
typeof(List));
CheckTrue(ReferenceEquals(node.Items[0], node.Items[1]));
}
public void TwoObjectsWithEqualContents_AreTwoNodes()
{
SaveNode node = Session().ToNode(
new List { new Waypoint { Name = "home" }, new Waypoint { Name = "home" } },
typeof(List));
CheckFalse(ReferenceEquals(node.Items[0], node.Items[1]));
}
public void ListHeldTwice_IsTheSameNodeInBothPlaces()
{
var inner = new List { 1, 2 };
SaveNode node = Session().ToNode(new List> { inner, inner }, typeof(List>));
CheckTrue(ReferenceEquals(node.Items[0], node.Items[1]));
}
public void ClassSavedAsTextHeldTwice_IsTheSameNodeInBothPlaces()
{
var fish = new Fish { Name = "Nemo" };
SaveNode node = Session().ToNode(new List { fish, fish }, typeof(List));
CheckEqual(SaveNode.Kinds.Text, node.Items[0].Kind);
CheckTrue(ReferenceEquals(node.Items[0], node.Items[1]));
}
public void SameStringHeldTwice_IsTheSameNodeInBothPlaces()
{
string text = new string('s', 4);
SaveNode texts = Session().ToNode(new List { text, text }, typeof(List));
CheckTrue(ReferenceEquals(texts.Items[0], texts.Items[1]));
}
public void EqualStringsThatAreTwoObjects_AreTwoNodes()
{
SaveNode texts = Session().ToNode(
new List { new string('s', 4), new string('s', 4) }, typeof(List));
CheckFalse(ReferenceEquals(texts.Items[0], texts.Items[1]));
}
public void EmptyString_IsNeverShared()
{
SaveNode texts = Session().ToNode(new List { "", "" }, typeof(List));
CheckFalse(ReferenceEquals(texts.Items[0], texts.Items[1]));
}
public void Structs_AreNeverShared()
{
var point = new Point(1, 2);
SaveNode points = Session().ToNode(new List { point, point }, typeof(List));
CheckFalse(ReferenceEquals(points.Items[0], points.Items[1]));
}
public void EqualObjectsThatAreTwoObjects_AreTwoNodes()
{
SaveNode node = Session().ToNode(
new List { new Stamp { Id = "a" }, new Stamp { Id = "a" } }, typeof(List));
CheckFalse(ReferenceEquals(node.Items[0], node.Items[1]));
}
public void SubtypeHeldTwice_IsTheSameTaggedNode()
{
var dog = new Dog { Name = "Rex" };
SaveNode node = Session().ToNode(new List { dog, dog }, typeof(List));
CheckTrue(ReferenceEquals(node.Items[0], node.Items[1]));
CheckEqual("Vellum.UnitTests.Dog", node.Items[0].RequireEntry("__type").Text);
}
public void ObjectChangedBetweenSessions_IsSavedAgain()
{
SaveConverters registry = TestConverters();
var waypoint = new Waypoint { Name = "home" };
registry.CreateSession().ToNode(waypoint, typeof(Waypoint));
waypoint.Name = "away";
SaveNode saved = registry.CreateSession().ToNode(waypoint, typeof(Waypoint));
CheckEqual("away", saved.RequireEntry("name").Text);
}
public void CycleSavedTwiceInOneSession_IsTheSameNode()
{
SaveSession session = Session();
var loop = new Link();
loop.Next = loop;
SaveNode first = session.ToNode(loop, typeof(Link));
CheckTrue(ReferenceEquals(first, session.ToNode(loop, typeof(Link))));
}
public void AfterAFailedSaveInACycle_TheNextSaveIsUnaffected()
{
SaveSession session = Session();
var loop = new Echo();
loop.Other = loop;
try
{
session.ToNode(loop, typeof(Echo));
}
catch (InvalidOperationException)
{
}
CheckEqual(SaveNode.Kinds.Table, session.ToNode(Chain(2), typeof(Link)).Kind);
}
public void NullInAnInterfaceARegisteredTypeImplements_IsNull()
{
CheckEqual(SaveNode.Kinds.Null, Session().ToNode(null, typeof(IPet)).Kind);
}
public void NullInATypeNothingRegisteredHolds_Throws()
{
CheckThrow(typeof(NotSupportedException));
Session().ToNode(null, typeof(IDisposable));
}
public void SubtypeNothingConverts_Throws()
{
CheckThrow(typeof(NotSupportedException));
Session().ToNode(new Eel(), typeof(Animal));
}
public void SubtypeInAnInterfaceNothingConverts_Throws()
{
CheckThrow(typeof(NotSupportedException));
Session().ToNode(new Stray(), typeof(IPet));
}
public void BuiltInValueInAnObject_IsNamedWithItsKeyword()
{
SaveNode node = Session().ToNode(5, typeof(object));
CheckEqual("int", node.RequireEntry("__type").Text);
CheckEqual("5", node.RequireEntry("__value").Text);
}
public void BuiltInValueInAnObject_RoundTrips()
{
SaveSession session = Session();
CheckEqual(5, session.FromNode(session.ToNode(5, typeof(object)), typeof(object)));
CheckEqual("hi", session.FromNode(session.ToNode("hi", typeof(object)), typeof(object)));
CheckEqual(Mood.Angry, session.FromNode(session.ToNode(Mood.Angry, typeof(object)), typeof(object)));
}
public void BuiltInTableValueInAnObject_RoundTrips()
{
SaveSession session = Session();
var value = new DateTime(2020, 1, 2, 3, 4, 5, DateTimeKind.Utc);
CheckEqual(value, session.FromNode(session.ToNode(value, typeof(object)), typeof(object)));
}
public void BuiltInValueLoadedIntoAnIncompatibleType_Throws()
{
SaveSession session = Session();
SaveNode node = session.ToNode(5, typeof(object));
CheckThrow(typeof(InvalidDataException));
session.FromNode(node, typeof(Animal));
}
public void GenericSubtype_IsNamedWithItsArguments()
{
SaveNode node = GenericSession().ToNode(new Box { Content = 3 }, typeof(object));
CheckEqual("Vellum.UnitTests.Box", node.RequireEntry("__type").Text);
CheckEqual("3", node.RequireEntry("content").Text);
}
public void GenericSubtypeOfGenericTypes_IsNamedWithEachArgument()
{
var value = new Box>>();
SaveNode node = GenericSession().ToNode(value, typeof(object));
CheckEqual("Vellum.UnitTests.Box>>", node.RequireEntry("__type").Text);
}
public void ArraySubtype_IsNamedAndSavedAsTheArray()
{
SaveNode node = Session().ToNode(new[] { new Dog { Name = "Rex" } }, typeof(Animal[]));
CheckEqual("Vellum.UnitTests.Dog[]", node.RequireEntry("__type").Text);
CheckEqual("Rex", node.RequireEntry("__value").Items[0].RequireEntry("name").Text);
}
public void GenericSubtypeNestedInAGenericType_IsNamedWithAPlus()
{
SaveSession session = SessionWith(typeof(SlotConverter<,>));
var value = new Holder.Slot { Outer = 1, Inner = "x" };
SaveNode node = session.ToNode(value, typeof(object));
CheckEqual("Vellum.UnitTests.Holder+Slot", node.RequireEntry("__type").Text);
}
public void GenericSubtypeWithAnArgumentNothingNames_Throws()
{
CheckThrow(typeof(NotSupportedException));
GenericSession().ToNode(new Box(), typeof(object));
}
public void GenericSubtypeNoConverterHandles_Throws()
{
CheckThrow(typeof(NotSupportedException));
GenericSession().ToNode(new Collection(), typeof(object));
}
public void GenericSubtypeWithAnEnumArgument_IsNamedWithTheEnum()
{
SaveNode node = GenericSession().ToNode(new Box { Content = Mood.Angry }, typeof(object));
CheckEqual("Vellum.UnitTests.Box", node.RequireEntry("__type").Text);
CheckEqual("Angry", node.RequireEntry("content").Text);
}
public void GenericSubtypeOfAClosedConverter_IsNamedAndSaved()
{
SaveSession session = new SaveConverters(
new SaveConverter[] { new PointConverter(), new StubConverter>() }).CreateSession();
SaveNode node = session.ToNode(new Box(), typeof(object));
CheckEqual("Vellum.UnitTests.Box", node.RequireEntry("__type").Text);
CheckEqual("stub", node.RequireEntry("__value").Text);
}
public void SubtypeWhoseConverterWritesATypeEntry_Throws()
{
CheckThrow(typeof(InvalidOperationException));
Session().ToNode(new Tricky(), typeof(Animal));
}
public void SubtypeWhoseConverterWritesAValueEntry_Throws()
{
CheckThrow(typeof(InvalidOperationException));
Session().ToNode(new Sneaky(), typeof(Animal));
}
public void ExactTypeWhoseConverterWritesATypeEntry_IsLeftAlone()
{
SaveNode node = Session().ToNode(new Tricky(), typeof(Tricky));
CheckEqual("somewhere else", node.RequireEntry("__type").Text);
}
public void SubtypeChainFarDeeperThanTheStackAllows_SavesEveryLevelNamed()
{
SaveNode node = Session().ToNode(Mole.Chain(2000), typeof(Animal));
CheckEqual(2000, DepthOf(node, "inner"));
CheckEqual("Vellum.UnitTests.Mole", Descend(node, "inner", 1500).RequireEntry("__type").Text);
}
public void FailureDeepInAChain_IsNotRememberedByTheSession()
{
Animal inner = new Sneaky();
for (int i = 0; i < 40; ++i)
{
inner = new Mole { Inner = inner };
}
SaveSession session = Session();
try
{
session.ToNode(inner, typeof(Animal));
}
catch (InvalidOperationException)
{
}
CheckThrow(typeof(InvalidOperationException));
session.ToNode(inner, typeof(Animal));
}
}
public class FromNodeTests : TestFixture
{
public const int ManyItems = 1000;
public static readonly SaveNode ManyTexts = TextList(ManyItems);
public static readonly SaveConverters SharedConverters = TestConverters();
public static SaveNode TextList(int count)
{
SaveNode returnMe = SaveNode.NewList();
for (int i = 0; i < count; ++i)
{
returnMe.Items.Add(SaveNode.OfText("text" + i));
}
return returnMe;
}
[AllocationTest]
public void ManyStrings_AllocateOnlyTheirList()
{
object loaded = SharedConverters.CreateSession().FromNode(ManyTexts, typeof(List));
CheckAllocationsAtMost(maxBytesPerRun: ManyItems * IntPtr.Size + 4096);
CheckEqual(ManyItems, ((List)loaded).Count);
}
public void ReferenceToANode_LoadsWhatTheNodeHolds()
{
SaveSession session = Session();
SaveNode reference = SaveNode.NewRef();
reference.Resolve(session.ToNode(new Point(1.5, -2), typeof(Point)));
CheckEqual(new Point(1.5, -2), (Point)session.FromNode(reference, typeof(Point)));
}
public void GenericConverter_RoundTrips()
{
SaveSession session = GenericSession();
SaveNode node = session.ToNode(new Box { Content = "seven" }, typeof(Box));
CheckEqual("seven", ((Box)session.FromNode(node, typeof(Box))).Content);
}
public void FamilyType_RoundTrips()
{
SaveSession session = new SaveConverters(new SaveConverter[0],
new[] { typeof(CreatureConverter<>), typeof(WolfConverter<>) }).CreateSession();
SaveNode node = session.ToNode(new AlphaWolf { Name = "Rex" }, typeof(AlphaWolf));
object loaded = session.FromNode(node, typeof(AlphaWolf));
CheckTrue(loaded.GetType() == typeof(AlphaWolf));
CheckEqual("Rex", ((AlphaWolf)loaded).Name);
}
public void SubtypeInAFamily_RoundTripsAsItsBaseClass()
{
SaveSession session = new SaveConverters(new SaveConverter[0],
new[] { typeof(CreatureConverter<>), typeof(WolfConverter<>) }).CreateSession();
SaveNode node = session.ToNode(new AlphaWolf { Name = "Rex" }, typeof(Creature));
object loaded = session.FromNode(node, typeof(Creature));
CheckEqual(TypeNamesTests.Ns + "AlphaWolf", node.RequireEntry(SaveConverters.TypeKey).Text);
CheckTrue(loaded.GetType() == typeof(AlphaWolf));
CheckEqual("Rex", ((Creature)loaded).Name);
}
public void SubtypeInAFamily_RoundTripsAsAnAbstractBaseClass()
{
SaveSession session = SessionWith(typeof(CreatureConverter<>));
SaveNode node = session.ToNode(new Mastiff { Name = "Rex" }, typeof(AbstractCreature));
object loaded = session.FromNode(node, typeof(AbstractCreature));
CheckTrue(loaded.GetType() == typeof(Mastiff));
CheckEqual("Rex", ((Creature)loaded).Name);
}
public void InterfaceFamilyType_RoundTripsAsItsInterface()
{
SaveSession session = SessionWith(typeof(CarnivoreConverter<>));
SaveNode node = session.ToNode(new Lynx(), typeof(ICarnivore));
CheckEqual(TypeNamesTests.Ns + "Lynx", node.RequireEntry(SaveConverters.TypeKey).Text);
CheckTrue(session.FromNode(node, typeof(ICarnivore)).GetType() == typeof(Lynx));
}
public void InterfaceFamilyStruct_RoundTripsAsItsInterface()
{
SaveSession session = SessionWith(typeof(AquaticConverter<>));
SaveNode node = session.ToNode(new Sprat(), typeof(IAquatic));
CheckTrue(session.FromNode(node, typeof(IAquatic)) is Sprat);
}
public void InterfaceFamilyType_RoundTripsAsAnAbstractClassImplementingIt()
{
SaveSession session = SessionWith(typeof(AquaticConverter<>));
SaveNode node = session.ToNode(new Cod(), typeof(AbstractAquatic));
CheckTrue(session.FromNode(node, typeof(AbstractAquatic)) is Cod);
}
public void InterfaceFamilyType_RoundTripsAsAnInterfaceExtendingItsInterface()
{
SaveSession session = SessionWith(typeof(CarnivoreConverter<>), typeof(PredatorConverter<>));
SaveNode node = session.ToNode(new Orca(), typeof(IPredator));
CheckTrue(session.FromNode(node, typeof(IPredator)) is Orca);
}
public void InterfaceFamilyType_RoundTripsAsObject()
{
SaveSession session = SessionWith(typeof(CarnivoreConverter<>));
SaveNode node = session.ToNode(new Lynx(), typeof(object));
CheckTrue(session.FromNode(node, typeof(object)) is Lynx);
}
public void NullHeldAsAnInterfaceAFamilySaves_StaysNull()
{
SaveSession session = SessionWith(typeof(CarnivoreConverter<>), typeof(AquaticConverter<>));
foreach (Type declared in new[] { typeof(ICarnivore), typeof(IAquatic), typeof(AbstractAquatic),
typeof(object) })
{
CheckNull(session.FromNode(session.ToNode(null, declared), declared));
}
}
public void SubtypeNamedInASaveThatDoesNotImplementTheDeclaredInterface_Throws()
{
SaveSession session = SessionWith(typeof(CarnivoreConverter<>), typeof(AquaticConverter<>));
SaveNode node = SaveNode.NewTable();
node.Entries[SaveConverters.TypeKey] = SaveNode.OfText(TypeNamesTests.Ns + "Otter");
CheckThrow(typeof(InvalidDataException));
session.FromNode(node, typeof(ICarnivore));
}
public void SubtypeNamedInASaveThatNoFamilySaves_Throws()
{
SaveSession session = SessionWith(typeof(CreatureConverter<>));
SaveNode node = SaveNode.NewTable();
node.Entries[SaveConverters.TypeKey] = SaveNode.OfText("System.IO.Path");
CheckThrow(typeof(InvalidDataException));
session.FromNode(node, typeof(Creature));
}
public void SubtypeNamedInASaveThatIsntDerivedFromTheDeclaredType_Throws()
{
SaveSession session = new SaveConverters(new SaveConverter[0],
new[] { typeof(CreatureConverter<>), typeof(HerdConverter<,>) }).CreateSession();
SaveNode node = SaveNode.NewTable();
node.Entries[SaveConverters.TypeKey] = SaveNode.OfText(TypeNamesTests.Ns + "IntHerd");
CheckThrow(typeof(InvalidDataException));
session.FromNode(node, typeof(Creature));
}
public void SubtypeInAFamilyOfAGenericBase_RoundTrips()
{
SaveSession session = SessionWith(typeof(HerdConverter<,>));
SaveNode node = session.ToNode(new NameHerd { Lead = "Rex" }, typeof(Herd));
object loaded = session.FromNode(node, typeof(Herd));
CheckTrue(loaded.GetType() == typeof(NameHerd));
CheckEqual("Rex", ((Herd)loaded).Lead);
}
public void SubtypeInAFamilyInsideAList_RoundTrips()
{
SaveSession session = SessionWith(typeof(CreatureConverter<>));
var creatures = new List { new Wolf { Name = "Rex" }, new Creature { Name = "Hen" } };
SaveNode node = session.ToNode(creatures, typeof(List));
var loaded = (List)session.FromNode(node, typeof(List));
CheckTrue(loaded[0].GetType() == typeof(Wolf));
CheckTrue(loaded[1].GetType() == typeof(Creature));
}
public void NullAboveAFamilysBaseClass_RoundTripsAsNull()
{
SaveSession session = SessionWith(typeof(WolfConverter<>));
SaveNode node = session.ToNode(null, typeof(Creature));
CheckEqual(SaveNode.Kinds.Null, node.Kind);
CheckNull(session.FromNode(node, typeof(Creature)));
}
public void NullOfAnAbstractClassInAFamily_RoundTripsAsNull()
{
SaveSession session = SessionWith(typeof(CreatureConverter<>));
SaveNode node = session.ToNode(null, typeof(AbstractCreature));
CheckEqual(SaveNode.Kinds.Null, node.Kind);
CheckNull(session.FromNode(node, typeof(AbstractCreature)));
}
public void FamilyOfAGenericBase_RoundTrips()
{
SaveSession session = SessionWith(typeof(HerdConverter<,>));
SaveNode node = session.ToNode(new NameHerd { Lead = "Rex" }, typeof(NameHerd));
CheckEqual("Rex", ((NameHerd)session.FromNode(node, typeof(NameHerd))).Lead);
}
public void ConverterWithItsParametersInAnotherOrder_RoundTrips()
{
SaveSession session = GenericSession();
var value = new Pair { First = 1, Second = "one" };
SaveNode node = session.ToNode(value, typeof(Pair));
var loaded = (Pair)session.FromNode(node, typeof(Pair));
CheckEqual(1, loaded.First);
CheckEqual("one", loaded.Second);
}
public void GenericTypeInsideAGenericType_RoundTrips()
{
SaveSession session = GenericSession();
var value = new Box> { Content = new Box { Content = new Point(1.5, -2) } };
SaveNode node = session.ToNode(value, typeof(Box>));
var loaded = (Box>)session.FromNode(node, typeof(Box>));
CheckEqual(-2.0, loaded.Content.Content.Y);
}
public void ArrayOfArrays_RoundTrips()
{
SaveSession session = GenericSession();
var value = new[] { new[] { "a" }, new string[0], new[] { "b", "c" } };
SaveNode node = session.ToNode(value, typeof(string[][]));
var loaded = (string[][])session.FromNode(node, typeof(string[][]));
CheckEqual(3, loaded.Length);
CheckEqual(0, loaded[1].Length);
CheckEqual(new[] { "b", "c" }, loaded[2]);
}
public void NullForAGenericReferenceType_IsNull()
{
CheckNull(GenericSession().FromNode(SaveNode.Null, typeof(Box)));
CheckNull(GenericSession().FromNode(SaveNode.Null, typeof(int[])));
}
public void NullForAGenericValueType_Throws()
{
CheckThrow(typeof(InvalidDataException));
GenericSession().FromNode(SaveNode.Null, typeof(Pair));
}
public void NullForANullableGenericValueType_IsNull()
{
CheckNull(GenericSession().FromNode(SaveNode.Null, typeof(Pair?)));
}
public void GenericTypeWithNoConverter_Throws()
{
CheckThrow(typeof(NotSupportedException));
GenericSession().FromNode(SaveNode.NewList(), typeof(Collection));
}
public void GenericTreeTooDeep_Throws()
{
SaveSession session = GenericSession();
SaveNode deep = session.ToNode(CellChain(SaveNode.MaxDepth), typeof(Cell));
SaveNode deeper = SaveNode.NewTable();
deeper.Entries["next"] = deep;
CheckThrow(typeof(InvalidOperationException));
session.FromNode(deeper, typeof(Cell));
}
public void ConvertedValues_RoundTrip()
{
SaveSession session = Session();
var value = new Waypoint { Name = "end", At = new Point(1.5, -2) };
SaveNode node = session.ToNode(value, typeof(Waypoint));
var loaded = (Waypoint)session.FromNode(node, typeof(Waypoint));
CheckEqual("end", loaded.Name);
CheckEqual(1.5, loaded.At.X);
CheckEqual(-2.0, loaded.At.Y);
}
public void NullForAReferenceOrNullable_IsNull()
{
CheckNull(Session().FromNode(SaveNode.Null, typeof(Waypoint)));
CheckNull(Session().FromNode(SaveNode.Null, typeof(Point?)));
}
public void NullableWithAValue_IsReadByItsConverter()
{
SaveSession session = Session();
SaveNode node = session.ToNode(new Point(3, 4), typeof(Point));
CheckEqual(3.0, ((Point)session.FromNode(node, typeof(Point?))).X);
}
public void NullForAConvertedValueType_Throws()
{
CheckThrow(typeof(InvalidDataException));
Session().FromNode(SaveNode.Null, typeof(Point));
}
public void BuiltInType_IsReadAsBuiltIn()
{
CheckEqual(5, Session().FromNode(SaveNode.OfText("5"), typeof(int)));
}
public void TreeTooDeep_Throws()
{
SaveSession session = Session();
SaveNode deep = session.ToNode(Chain(SaveNode.MaxDepth), typeof(Link));
SaveNode deeper = SaveNode.NewTable();
deeper.Entries["next"] = deep;
CheckThrow(typeof(InvalidOperationException));
session.FromNode(deeper, typeof(Link));
}
public static SaveNode WaypointNode(string name)
{
SaveNode at = SaveNode.NewTable();
at.Entries["x"] = SaveNode.OfText("1");
at.Entries["y"] = SaveNode.OfText("2");
SaveNode returnMe = SaveNode.NewTable();
returnMe.Entries["name"] = SaveNode.OfText(name);
returnMe.Entries["at"] = at;
return returnMe;
}
public static SaveNode SharedWaypointList()
{
SaveNode waypoint = WaypointNode("home");
SaveNode list = SaveNode.NewList();
list.Items.Add(waypoint);
list.Items.Add(waypoint);
return list;
}
public void NodeHeldTwice_LoadsAsOneObject()
{
var loaded = (List)Session().FromNode(SharedWaypointList(), typeof(List));
CheckTrue(ReferenceEquals(loaded[0], loaded[1]));
CheckEqual("home", loaded[0].Name);
}
public void TextNodeOfAClassHeldTwice_LoadsAsOneObject()
{
SaveNode text = SaveNode.OfText("Nemo");
SaveNode list = SaveNode.NewList();
list.Items.Add(text);
list.Items.Add(text);
var loaded = (List)Session().FromNode(list, typeof(List));
CheckTrue(ReferenceEquals(loaded[0], loaded[1]));
CheckEqual("Nemo", loaded[0].Name);
}
public void TwoTextNodesOfAClassWithEqualText_LoadAsTwoObjects()
{
SaveNode list = SaveNode.NewList();
list.Items.Add(SaveNode.OfText("Nemo"));
list.Items.Add(SaveNode.OfText("Nemo"));
var loaded = (List)Session().FromNode(list, typeof(List));
CheckFalse(ReferenceEquals(loaded[0], loaded[1]));
}
public void ClassSavedAsTextHeldTwice_IsOneObjectAfterASaveFile()
{
SaveSession session = Session();
var fish = new Fish { Name = "Nemo" };
SaveNode save = SaveNode.NewTable();
save.Entries["school"] = session.ToNode(new List { fish, fish }, typeof(List));
SaveNode read = LuaSaveFormat.Parse(LuaSaveFormat.Format(save));
var loaded = (List)session.FromNode(read.RequireEntry("school"), typeof(List));
CheckTrue(ReferenceEquals(loaded[0], loaded[1]));
}
public void TextNodeHeldTwiceAndLoadedAsStrings_LoadsOneString()
{
SaveNode text = SaveNode.OfText("same");
SaveNode list = SaveNode.NewList();
list.Items.Add(text);
list.Items.Add(text);
var loaded = (List)Session().FromNode(list, typeof(List));
CheckTrue(ReferenceEquals(loaded[0], loaded[1]));
}
public void TwoTextNodesWithEqualText_LoadAsTwoStrings()
{
SaveNode list = SaveNode.NewList();
list.Items.Add(SaveNode.OfText(new string('s', 4)));
list.Items.Add(SaveNode.OfText(new string('s', 4)));
var loaded = (List)Session().FromNode(list, typeof(List));
CheckFalse(ReferenceEquals(loaded[0], loaded[1]));
CheckEqual(loaded[0], loaded[1]);
}
public void TextNodeLoadedAsAStringThenAsAnotherType_LoadsEachAsItsOwnType()
{
SaveSession session = Session();
SaveNode text = SaveNode.OfText("5");
object asString = session.FromNode(text, typeof(string));
CheckEqual(5, session.FromNode(text, typeof(int)));
CheckTrue(ReferenceEquals(asString, session.FromNode(text, typeof(string))));
}
public void SameStringHeldTwiceAfterASaveFile_IsOneString()
{
SaveSession session = Session();
string text = new string('s', 4);
SaveNode save = SaveNode.NewTable();
save.Entries["texts"] = session.ToNode(new List { text, text }, typeof(List));
SaveNode read = LuaSaveFormat.Parse(LuaSaveFormat.Format(save));
var loaded = (List)session.FromNode(read.RequireEntry("texts"), typeof(List));
CheckTrue(ReferenceEquals(loaded[0], loaded[1]));
}
public void TwoNodesWithEqualContents_LoadAsTwoObjects()
{
SaveNode list = SaveNode.NewList();
list.Items.Add(WaypointNode("home"));
list.Items.Add(WaypointNode("home"));
var loaded = (List)Session().FromNode(list, typeof(List));
CheckFalse(ReferenceEquals(loaded[0], loaded[1]));
}
public void ListNodeHeldTwice_LoadsAsOneList()
{
SaveNode inner = SaveNode.NewList();
inner.Items.Add(SaveNode.OfText("1"));
SaveNode outer = SaveNode.NewList();
outer.Items.Add(inner);
outer.Items.Add(inner);
var loaded = (List>)Session().FromNode(outer, typeof(List>));
CheckTrue(ReferenceEquals(loaded[0], loaded[1]));
}
public void NodeOfAStructHeldTwice_LoadsAsACopyEachTime()
{
SaveNode point = new PointConverter().ToNode(new Point(1, 2), Session());
SaveNode list = SaveNode.NewList();
list.Items.Add(point);
list.Items.Add(point);
var loaded = (List)Session().FromNode(list, typeof(List));
CheckEqual(1.0, loaded[0].X);
CheckEqual(1.0, loaded[1].X);
}
public void SubtypeNodeHeldTwice_LoadsAsOneObject()
{
SaveSession session = Session();
var dog = new Dog { Name = "Rex" };
SaveNode saved = session.ToNode(new List { dog, dog }, typeof(List));
var loaded = (List)session.FromNode(saved, typeof(List));
CheckTrue(ReferenceEquals(loaded[0], loaded[1]));
CheckEqual(typeof(Dog), loaded[0].GetType());
}
public void SeparateSessions_LoadSeparateObjects()
{
SaveNode waypoint = SharedWaypointList().Items[0];
CheckFalse(ReferenceEquals(Session().FromNode(waypoint, typeof(Waypoint)),
Session().FromNode(waypoint, typeof(Waypoint))));
}
public void FailedLoad_IsNotRememberedByTheSession()
{
SaveNode broken = SaveNode.NewTable();
broken.Entries["twin"] = SaveNode.OfText("not a chair");
SaveSession session = Session();
try
{
session.FromNode(broken, typeof(Chair));
}
catch (InvalidDataException)
{
}
CheckThrow(typeof(InvalidDataException));
session.FromNode(broken, typeof(Chair));
}
public void FailedLoad_ForgetsObjectsLoadedBeforeIt()
{
SaveNode waypoint = SharedWaypointList().Items[0];
SaveNode broken = SaveNode.NewTable();
broken.Entries["twin"] = SaveNode.OfText("not a chair");
SaveSession session = Session();
object first = session.FromNode(waypoint, typeof(Waypoint));
try
{
session.FromNode(broken, typeof(Chair));
}
catch (InvalidDataException)
{
}
CheckFalse(ReferenceEquals(first, session.FromNode(waypoint, typeof(Waypoint))));
}
public void NodeContainingItself_Throws()
{
SaveNode loop = SaveNode.NewTable();
loop.Entries["next"] = loop;
CheckThrow(typeof(InvalidDataException));
Session().FromNode(loop, typeof(Link));
}
public void AfterNodeContainingItself_TheNextLoadIsUnaffected()
{
SaveSession session = Session();
SaveNode loop = SaveNode.NewTable();
loop.Entries["next"] = loop;
try
{
session.FromNode(loop, typeof(Link));
}
catch (InvalidDataException)
{
}
SaveNode last = SaveNode.NewTable();
last.Entries["next"] = SaveNode.Null;
CheckNotNull(session.FromNode(last, typeof(Link)) as Link);
}
public void CycleThroughAConverterThatPublishes_LoadsAsACycle()
{
SaveNode chair = SaveNode.NewTable();
chair.Entries["twin"] = chair;
var loaded = (Chair)Session().FromNode(chair, typeof(Chair));
CheckTrue(ReferenceEquals(loaded, loaded.Twin));
}
public void TwoValuesContainingEachOther_LoadAsEachOthersTwin()
{
SaveNode first = SaveNode.NewTable();
SaveNode second = SaveNode.NewTable();
first.Entries["twin"] = second;
second.Entries["twin"] = first;
var loaded = (Chair)Session().FromNode(first, typeof(Chair));
CheckFalse(ReferenceEquals(loaded, loaded.Twin));
CheckTrue(ReferenceEquals(loaded, loaded.Twin.Twin));
}
public void CycleSavedAndWrittenAsText_LoadsAsACycle()
{
SaveSession session = Session();
var chair = new Chair();
chair.Twin = chair;
SaveNode save = SaveNode.NewTable();
save.Entries["chair"] = session.ToNode(chair, typeof(Chair));
SaveNode read = LuaSaveFormat.Parse(LuaSaveFormat.Format(save));
var loaded = (Chair)session.FromNode(read.RequireEntry("chair"), typeof(Chair));
CheckTrue(ReferenceEquals(loaded, loaded.Twin));
}
public void CycleThroughAConverterThatDoesNotPublish_ThrowsTellingWhatToDo()
{
SaveNode desk = SaveNode.NewTable();
desk.Entries["twin"] = desk;
string message = null;
try
{
Session().FromNode(desk, typeof(Desk));
}
catch (InvalidDataException exception)
{
message = exception.Message;
}
CheckTrue(message != null && message.Contains("Publish"));
}
public void NullForAnInterfaceARegisteredTypeImplements_IsNull()
{
CheckNull(Session().FromNode(SaveNode.Null, typeof(IPet)));
}
public void NullForATypeNothingRegisteredHolds_Throws()
{
CheckThrow(typeof(InvalidDataException));
Session().FromNode(SaveNode.Null, typeof(IDisposable));
}
public void NamedSubtype_LoadsAsItsType()
{
SaveSession session = Session();
SaveNode node = ThroughLuaText(session.ToNode(new Dog { Name = "Rex", Barks = true },
typeof(Animal)));
var loaded = (Dog)session.FromNode(node, typeof(Animal));
CheckEqual("Rex", loaded.Name);
CheckTrue(loaded.Barks);
}
public void WrappedSubtype_LoadsAsItsType()
{
SaveSession session = Session();
SaveNode node = ThroughLuaText(session.ToNode(new Fish { Name = "Nemo" }, typeof(Animal)));
var loaded = (Fish)session.FromNode(node, typeof(Animal));
CheckEqual("Nemo", loaded.Name);
}
public void StructInAnInterface_LoadsAsItsType()
{
SaveSession session = Session();
SaveNode node = ThroughLuaText(session.ToNode(new Hamster { Nickname = "Bun" }, typeof(IPet)));
var loaded = (Hamster)session.FromNode(node, typeof(IPet));
CheckEqual("Bun", loaded.Nickname);
}
public void SubtypesInAList_LoadAsTheirTypes()
{
SaveSession session = Session();
var value = new List { new Animal { Name = "Tom" }, new Dog { Name = "Rex" } };
SaveNode node = ThroughLuaText(session.ToNode(value, typeof(List)));
var loaded = (List)session.FromNode(node, typeof(List));
CheckEqual(typeof(Animal), loaded[0].GetType());
CheckEqual(typeof(Dog), loaded[1].GetType());
}
public void SubtypeSavedAsAnotherBase_LoadsWhenTheNewDeclaredTypeStillFits()
{
SaveSession session = Session();
SaveNode node = session.ToNode(new Dog { Name = "Rex" }, typeof(Animal));
CheckEqual(typeof(Dog), session.FromNode(node, typeof(object)).GetType());
}
public void NamedSubtype_LeavesTheNodeAsItWas()
{
SaveSession session = Session();
SaveNode node = session.ToNode(new Fish { Name = "Nemo" }, typeof(Animal));
session.FromNode(node, typeof(Animal));
CheckEqual(2, node.Entries.Count);
CheckEqual("Nemo", node.RequireEntry("__value").Text);
}
public static SaveNode Named(string name)
{
SaveNode returnMe = SaveNode.NewTable();
returnMe.Entries["__type"] = SaveNode.OfText(name);
return returnMe;
}
public void GenericSubtype_LoadsAsItsClosedTypeInConvertersThatNeverSavedIt()
{
var value = new Box> { Content = new Pair { First = 4, Second = "x" } };
SaveNode node = ThroughLuaText(GenericSession().ToNode(value, typeof(object)));
var loaded = (Box>)GenericSession().FromNode(node, typeof(object));
CheckEqual(4, loaded.Content.First);
CheckEqual("x", loaded.Content.Second);
}
public void GenericSubtypeWithAnEnumArgument_Loads()
{
var value = new Box { Content = Palette.Shade.Dark };
SaveNode node = ThroughLuaText(GenericSession().ToNode(value, typeof(object)));
var loaded = (Box)GenericSession().FromNode(node, typeof(object));
CheckEqual(Palette.Shade.Dark, loaded.Content);
}
public void GenericSubtypeInAList_LoadsEachAsItsClosedType()
{
var value = new List