using System;
using System.Collections.Generic;
using System.Reflection;
using System.Runtime.ExceptionServices;
namespace Glass.Testbed
{
///
/// Finds the test cases in an assembly without building any of them.
///
public static class TestCaseCatalog
{
///
/// The namespace a case's category sits under. A case in Glass.Testbed.TestCases.Text is in the Text
/// category.
///
public const string RootNamespace = "Glass.Testbed.TestCases";
///
/// What a menu needs to list a case before it exists.
///
public sealed class Entry
{
public readonly string Category;
public readonly string Name;
public readonly Type Type;
public Entry(string category, string name, Type type)
{
Category = category;
Name = name;
Type = type;
}
}
private static string CategoryOf(Type caseType)
{
string prefix = RootNamespace + ".";
if (caseType.Namespace == null || !caseType.Namespace.StartsWith(prefix, StringComparison.Ordinal))
{
throw new InvalidOperationException(
$"{caseType.FullName} is a test case, so it belongs in a category namespace under " +
$"{RootNamespace}.");
}
string returnMe = caseType.Namespace.Substring(prefix.Length);
if (returnMe.Contains("."))
{
throw new InvalidOperationException(
$"{caseType.FullName} is in a namespace nested below its category {returnMe}.");
}
return returnMe;
}
private static ConstructorInfo ConstructorOf(Type caseType)
{
ConstructorInfo returnMe = caseType.GetConstructor(new[] { typeof(World) }) ??
caseType.GetConstructor(Type.EmptyTypes);
if (returnMe == null)
{
throw new InvalidOperationException(
$"{caseType.FullName} needs a public constructor that takes a {nameof(World)}, or none at all.");
}
return returnMe;
}
///
/// Returns every concrete test case in , ordered by category and then name. Throws
/// for one that couldn't be built or placed, so a broken case fails at startup instead of when it's clicked.
///
public static List Find(Assembly assembly)
{
return Find(assembly.GetTypes());
}
///
/// Returns the concrete test cases among , as the assembly overload does.
///
public static List Find(IEnumerable types)
{
var returnMe = new List();
foreach (Type type in types)
{
if (type.IsAbstract || type.IsGenericTypeDefinition || !typeof(TestCase).IsAssignableFrom(type))
{
continue;
}
ConstructorOf(type);
returnMe.Add(new Entry(CategoryOf(type), TestCase.NameFor(type), type));
}
returnMe.Sort((left, right) =>
{
int byCategory = string.CompareOrdinal(left.Category, right.Category);
return byCategory != 0 ? byCategory : string.CompareOrdinal(left.Name, right.Name);
});
return returnMe;
}
///
/// Builds the case describes, handing it if it takes one.
///
public static TestCase Create(Entry entry, World world)
{
ConstructorInfo constructor = ConstructorOf(entry.Type);
try
{
if (constructor.GetParameters().Length == 0)
{
return (TestCase)constructor.Invoke(null);
}
return (TestCase)constructor.Invoke(new object[] { world });
}
catch (TargetInvocationException exception)
{
ExceptionDispatchInfo.Capture(exception.InnerException).Throw();
throw;
}
}
}
}