using System.Collections.Generic;
using System.Collections.Immutable;
using System.Linq;
using Microsoft.CodeAnalysis;
using Microsoft.CodeAnalysis.CSharp;
using UnitTestSharp;
namespace Vellum.Generator.UnitTests
{
public class SavedTypeAnalyzerTests : TestFixture
{
///
/// Holds a store with some generic methods registered as saving a type argument, and some not.
///
public const string Store =
"[assembly: RegisterSaveMethod(\"M:IStore.Put``1(System.String,``0)\", typeof(SaveMethodParameter0))]\n" +
"[assembly: RegisterSaveMethod(\"M:IStore.Make``1\", typeof(SaveMethodParameter0))]\n" +
"[assembly: RegisterSaveMethod(\"M:IStore.Pair``2(``0,``1)\", typeof(SaveMethodParameter1))]\n" +
"public interface IStore\n{\n" +
" void Put(string key, T value);\n" +
" T Make();\n" +
" void Put(object value);\n" +
" void Pair(TKey key, T value);\n" +
" void Note(string text);\n" +
"}\n";
public static readonly MetadataReference StoreLibrary = VellumRun.CompileLibrary("Store", Store);
///
/// Returns VEL0004's message for holding a .
///
public static string Unsaveable(string name, string type, string inside = "")
{
return "VEL0004:'" + name + "' is saved, but a " + type + inside + " can't be saved. Mark it " +
"[Saveable], or write a SaveConverter<" + type + "> for it.";
}
public class RegisteredTests : TestFixture
{
///
/// Returns each registration in , as "methodId=savedType".
///
public static List RegisteredIncludingVellums(MetadataReference[] libraries, string source)
{
return SavedTypeAnalyzer.Registered(VellumRun.Compile(libraries, ("Source.cs", source)))
.SelectMany(pair => pair.Value.Select(saved => pair.Key + "=" + saved.ToDisplayString()))
.ToList();
}
public static List Registered(MetadataReference[] libraries, string source)
{
return RegisteredIncludingVellums(libraries, source)
.Where(name => !name.StartsWith("M:Vellum.", System.StringComparison.Ordinal))
.ToList();
}
public static List Registered(string source)
{
return Registered(new MetadataReference[0], source);
}
public void Vellum_RegistersItsOwnSavingAndLoadingCalls()
{
List registered = RegisteredIncludingVellums(new MetadataReference[0],
"public class Plain { }");
CheckTrue(registered.Contains("M:Vellum.SaveSession.ToNode``1(``0)=Vellum.SaveMethodParameter0"));
CheckTrue(registered.Contains(
"M:Vellum.SaveSession.FromNode``1(Vellum.SaveNode)=Vellum.SaveMethodParameter0"));
CheckTrue(registered.Contains(
"M:Vellum.SaveReader.Require``1(System.String)=Vellum.SaveMethodParameter0"));
}
public void FindsRegistrationsInTheCompilationAndItsReferences()
{
CheckEqualCommutative(new[]
{
"M:IStore.Put``1(System.String,``0)=Vellum.SaveMethodParameter0",
"M:IStore.Make``1=Vellum.SaveMethodParameter0",
"M:IStore.Pair``2(``0,``1)=Vellum.SaveMethodParameter1",
"M:IOther.Put``1(``0)=Vellum.SaveMethodParameter0",
}, Registered(new[] { StoreLibrary }, "[assembly: RegisterSaveMethod(\"M:IOther.Put``1(``0)\", " +
"typeof(SaveMethodParameter0))]\npublic interface IOther { }"));
}
public void SavedTypeBuiltFromTheStandIns_IsKeptWhole()
{
CheckEqual(new[]
{
"M:Bag.Put``2(``0,``1)=System.Collections.Generic.Dictionary",
}, Registered("[assembly: RegisterSaveMethod(\"M:Bag.Put``2(``0,``1)\", " +
"typeof(Dictionary))]\npublic class Bag { }"));
}
public void RegistrationOfNoMethodOrNoType_IsIgnored()
{
CheckEqual(0, Registered("[assembly: RegisterSaveMethod(null, typeof(SaveMethodParameter0))]\n" +
"[assembly: RegisterSaveMethod(\"M:Plain.Put``1(``0)\", null)]\npublic class Plain { }").Count);
}
public void RegistrationThatDoesntCompile_IsIgnored()
{
CheckEqual(0, Registered("[assembly: RegisterSaveMethod(\"M:Plain.Put``1(``0)\")]\n" +
"public class Plain { }").Count);
}
public void AttributeNothingDefines_IsIgnored()
{
CheckEqual(0, Registered("[assembly: Missing]\npublic class Plain { }").Count);
}
public void OtherAssemblyAttributes_AreIgnored()
{
CheckEqual(0, Registered(
"[assembly: System.Reflection.AssemblyTitle(\"Plain\")]\npublic class Plain { }").Count);
}
}
public class FillTests : TestFixture
{
public static CSharpCompilation Compilation()
{
return VellumRun.Compile(("Source.cs",
"public class Outer { public class Inner { public class Deep { } } }"));
}
public static ITypeSymbol Digit(CSharpCompilation compilation, int digit)
{
return compilation.GetTypeByMetadataName("Vellum.SaveMethodParameter" + digit);
}
public static ImmutableArray Arguments(CSharpCompilation compilation,
params SpecialType[] types)
{
return types.Select(type => (ITypeSymbol)compilation.GetSpecialType(type)).ToImmutableArray();
}
public void StandIn_IsTheArgumentOfItsNumber()
{
CSharpCompilation compilation = Compilation();
ITypeSymbol filled = SavedTypeAnalyzer.Fill(compilation, Digit(compilation, 1),
Arguments(compilation, SpecialType.System_Int32, SpecialType.System_String));
CheckEqual("string", filled.ToDisplayString());
}
public void ClassStandIn_IsTheClassArgumentOfItsNumber()
{
CSharpCompilation compilation = Compilation();
INamedTypeSymbol wrapper = compilation.GetTypeByMetadataName("Vellum.SaveClassParameter`1");
ImmutableArray classArguments = Arguments(compilation, SpecialType.System_Int32,
SpecialType.System_String);
ITypeSymbol filled = SavedTypeAnalyzer.Fill(compilation, wrapper.Construct(Digit(compilation, 1)),
Arguments(compilation, SpecialType.System_Double), classArguments);
CheckEqual("string", filled.ToDisplayString());
}
public void ClassStandInWithoutAClassArgument_IsNull()
{
CSharpCompilation compilation = Compilation();
INamedTypeSymbol wrapper = compilation.GetTypeByMetadataName("Vellum.SaveClassParameter`1");
CheckNull(SavedTypeAnalyzer.Fill(compilation, wrapper.Construct(Digit(compilation, 0)),
Arguments(compilation, SpecialType.System_Int32)));
}
public void ClassAndMethodStandInsInOneType_AreFilledFromTheirOwnArguments()
{
CSharpCompilation compilation = Compilation();
INamedTypeSymbol wrapper = compilation.GetTypeByMetadataName("Vellum.SaveClassParameter`1");
INamedTypeSymbol open = compilation.GetTypeByMetadataName("System.Collections.Generic.Dictionary`2");
INamedTypeSymbol template = open.Construct(wrapper.Construct(Digit(compilation, 0)),
Digit(compilation, 0));
ITypeSymbol filled = SavedTypeAnalyzer.Fill(compilation, template,
Arguments(compilation, SpecialType.System_String),
Arguments(compilation, SpecialType.System_Int32));
CheckEqual("System.Collections.Generic.Dictionary", filled.ToDisplayString());
}
public void ClassArgumentsOfANestedType_ListTheOuterTypesFirst()
{
CSharpCompilation compilation = Compilation();
INamedTypeSymbol outer = compilation.GetTypeByMetadataName("Outer`1")
.Construct(compilation.GetSpecialType(SpecialType.System_Int32));
INamedTypeSymbol inner = outer.GetTypeMembers("Inner").Single()
.Construct(compilation.GetSpecialType(SpecialType.System_String));
CheckEqual(new[] { "int", "string" },
SavedTypeAnalyzer.ClassArgumentsOf(inner).Select(type => type.ToDisplayString()).ToArray());
}
public void ClassArgumentsOfATypeWithNone_AreEmpty()
{
CSharpCompilation compilation = Compilation();
CheckEqual(0, SavedTypeAnalyzer.ClassArgumentsOf(
compilation.GetSpecialType(SpecialType.System_String)).Length);
}
public void StandInWithoutAnArgument_IsNull()
{
CSharpCompilation compilation = Compilation();
CheckNull(SavedTypeAnalyzer.Fill(compilation, Digit(compilation, 2),
Arguments(compilation, SpecialType.System_Int32)));
}
public void TypeWithoutStandIns_IsItself()
{
CSharpCompilation compilation = Compilation();
ITypeSymbol closed = compilation.GetSpecialType(SpecialType.System_String);
CheckTrue(ReferenceEquals(closed, SavedTypeAnalyzer.Fill(compilation, closed,
Arguments(compilation))));
}
public void ArrayOfAStandIn_IsAnArrayOfTheArgument()
{
CSharpCompilation compilation = Compilation();
IArrayTypeSymbol template = compilation.CreateArrayTypeSymbol(Digit(compilation, 0), rank: 2);
ITypeSymbol filled = SavedTypeAnalyzer.Fill(compilation, template,
Arguments(compilation, SpecialType.System_Int32));
CheckEqual("int[*,*]", filled.ToDisplayString());
}
public void GenericOfStandIns_IsMadeFromTheArguments()
{
CSharpCompilation compilation = Compilation();
INamedTypeSymbol open = compilation.GetTypeByMetadataName("System.Collections.Generic.Dictionary`2");
INamedTypeSymbol dictionary = open.Construct(Digit(compilation, 0), Digit(compilation, 1));
ITypeSymbol filled = SavedTypeAnalyzer.Fill(compilation, dictionary,
Arguments(compilation, SpecialType.System_Int32, SpecialType.System_String));
CheckEqual("System.Collections.Generic.Dictionary", filled.ToDisplayString());
}
public void TypeNestedInAGenericOne_KeepsTheOuterTypesArgument()
{
CSharpCompilation compilation = Compilation();
INamedTypeSymbol outer = compilation.GetTypeByMetadataName("Outer`1").Construct(Digit(compilation, 0));
INamedTypeSymbol inner = outer.GetTypeMembers("Inner").Single().Construct(Digit(compilation, 1));
ITypeSymbol filled = SavedTypeAnalyzer.Fill(compilation, inner,
Arguments(compilation, SpecialType.System_Int32, SpecialType.System_String));
CheckEqual("Outer.Inner", filled.ToDisplayString());
}
public void TypeNestedInAClosedGenericOne_KeepsTheOuterTypesArgument()
{
CSharpCompilation compilation = Compilation();
INamedTypeSymbol outer = compilation.GetTypeByMetadataName("Outer`1")
.Construct(compilation.GetSpecialType(SpecialType.System_Int32));
INamedTypeSymbol inner = outer.GetTypeMembers("Inner").Single().Construct(Digit(compilation, 0));
ITypeSymbol filled = SavedTypeAnalyzer.Fill(compilation, inner,
Arguments(compilation, SpecialType.System_String));
CheckEqual("Outer.Inner", filled.ToDisplayString());
}
public void NonGenericTypeNestedInAGenericOne_KeepsTheOuterTypesArguments()
{
CSharpCompilation compilation = Compilation();
INamedTypeSymbol outer = compilation.GetTypeByMetadataName("Outer`1").Construct(Digit(compilation, 0));
INamedTypeSymbol inner = outer.GetTypeMembers("Inner").Single().Construct(Digit(compilation, 1));
INamedTypeSymbol deep = inner.GetTypeMembers("Deep").Single();
ITypeSymbol filled = SavedTypeAnalyzer.Fill(compilation, deep,
Arguments(compilation, SpecialType.System_Int32, SpecialType.System_String));
CheckEqual("Outer.Inner.Deep", filled.ToDisplayString());
}
public void NestedTypeWhoseOuterTypeNeedsAMissingArgument_IsNull()
{
CSharpCompilation compilation = Compilation();
INamedTypeSymbol outer = compilation.GetTypeByMetadataName("Outer`1").Construct(Digit(compilation, 3));
INamedTypeSymbol inner = outer.GetTypeMembers("Inner").Single().Construct(Digit(compilation, 0));
CheckNull(SavedTypeAnalyzer.Fill(compilation, inner,
Arguments(compilation, SpecialType.System_Int32)));
}
public void NestingPastTheLimit_IsNull()
{
CSharpCompilation compilation = Compilation();
INamedTypeSymbol list = compilation.GetTypeByMetadataName("System.Collections.Generic.List`1");
ITypeSymbol template = Digit(compilation, 0);
for (int i = 0; i < SaveTemplate.MaxDepth; ++i)
{
template = list.Construct(template);
}
CheckNull(SavedTypeAnalyzer.Fill(compilation, template,
Arguments(compilation, SpecialType.System_Int32)));
}
public void NestingAtTheLimit_IsFilled()
{
CSharpCompilation compilation = Compilation();
INamedTypeSymbol list = compilation.GetTypeByMetadataName("System.Collections.Generic.List`1");
ITypeSymbol template = Digit(compilation, 0);
for (int i = 0; i < SaveTemplate.MaxDepth - 1; ++i)
{
template = list.Construct(template);
}
CheckNotNull(SavedTypeAnalyzer.Fill(compilation, template,
Arguments(compilation, SpecialType.System_Int32)));
}
}
public class InitializeTests : TestFixture
{
public void UnsaveableTypeArgument_IsReportedNamingWhatsSaved()
{
VellumRun run = VellumRun.Of(StoreLibrary, "public class User { " +
"public void Save(IStore store) { store.Put(\"visited\", new System.Collections.ObjectModel.Collection()); } }");
CheckEqual(new[] { "VEL0004:store.Put(\"visited\", new System.Collections.ObjectModel.Collection())" }, run.Compiled);
CheckEqual(new[] { Unsaveable("visited", "System.Collections.ObjectModel.Collection") },
run.CompiledMessages);
}
public void SaveableTypeArguments_AreAllowed()
{
VellumRun run = VellumRun.Of(StoreLibrary, ConverterScanTests.Point +
ConverterScanTests.Converter("PointConverter", "Point") + @"
public class User
{
public void Save(IStore store)
{
store.Put(""count"", 1);
store.Put(""name"", ""text"");
store.Put(""day"", System.DayOfWeek.Monday);
store.Put(""at"", new Point());
store.Put(""maybe"", (Point?)null);
}
}");
CheckEqual(0, run.Compiled.Count);
}
public void GenericSaveable_IsJudgedByItsSavedMembers()
{
VellumRun run = VellumRun.Of(StoreLibrary, @"
[Saveable] public partial struct Tagged { public ValueType Value; [NoSave] public TagType Tag; }
public class User
{
public void Save(IStore store)
{
store.Put(""fine"", new Tagged());
store.Put(""lost"", new Tagged