An easy to use Unity 3D library for creating illustrated Interactive Fiction games and more.
You can not select more than 25 topics Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.

414 lines
13 KiB

using System;
using System.Collections.Generic;
using System.Linq;
using System.Reflection;
using UnityEngine;
#if UNITY_5_3_OR_NEWER
using UnityEngine.SceneManagement;
#endif
#if UNITY_EDITOR
using UnityEditor;
#endif
namespace UnityTest
{
public interface ITestComponent : IComparable<ITestComponent>
{
void EnableTest(bool enable);
bool IsTestGroup();
GameObject gameObject { get; }
string Name { get; }
ITestComponent GetTestGroup();
bool IsExceptionExpected(string exceptionType);
bool ShouldSucceedOnException();
double GetTimeout();
bool IsIgnored();
bool ShouldSucceedOnAssertions();
bool IsExludedOnThisPlatform();
}
public class TestComponent : MonoBehaviour, ITestComponent
{
public static ITestComponent NullTestComponent = new NullTestComponentImpl();
public float timeout = 5;
public bool ignored = false;
public bool succeedAfterAllAssertionsAreExecuted = false;
public bool expectException = false;
public string expectedExceptionList = "";
public bool succeedWhenExceptionIsThrown = false;
public IncludedPlatforms includedPlatforms = (IncludedPlatforms) ~0L;
public string[] platformsToIgnore = null;
public bool dynamic;
public string dynamicTypeName;
public bool IsExludedOnThisPlatform()
{
return platformsToIgnore != null && platformsToIgnore.Any(platform => platform == Application.platform.ToString());
}
static bool IsAssignableFrom(Type a, Type b)
{
#if !UNITY_METRO
return a.IsAssignableFrom(b);
#else
return false;
#endif
}
public bool IsExceptionExpected(string exception)
{
exception = exception.Trim();
if (!expectException)
return false;
if(string.IsNullOrEmpty(expectedExceptionList.Trim()))
return true;
foreach (var expectedException in expectedExceptionList.Split(',').Select(e => e.Trim()))
{
if (exception == expectedException)
return true;
var exceptionType = Type.GetType(exception) ?? GetTypeByName(exception);
var expectedExceptionType = Type.GetType(expectedException) ?? GetTypeByName(expectedException);
if (exceptionType != null && expectedExceptionType != null && IsAssignableFrom(expectedExceptionType, exceptionType))
return true;
}
return false;
}
public bool ShouldSucceedOnException()
{
return succeedWhenExceptionIsThrown;
}
public double GetTimeout()
{
return timeout;
}
public bool IsIgnored()
{
return ignored;
}
public bool ShouldSucceedOnAssertions()
{
return succeedAfterAllAssertionsAreExecuted;
}
private static Type GetTypeByName(string className)
{
#if !UNITY_METRO
return AppDomain.CurrentDomain.GetAssemblies().SelectMany(a => a.GetTypes()).FirstOrDefault(type => type.Name == className);
#else
return null;
#endif
}
public void OnValidate()
{
if (timeout < 0.01f) timeout = 0.01f;
}
// Legacy
[Flags]
public enum IncludedPlatforms
{
WindowsEditor = 1 << 0,
OSXEditor = 1 << 1,
WindowsPlayer = 1 << 2,
OSXPlayer = 1 << 3,
LinuxPlayer = 1 << 4,
MetroPlayerX86 = 1 << 5,
MetroPlayerX64 = 1 << 6,
MetroPlayerARM = 1 << 7,
WindowsWebPlayer = 1 << 8,
OSXWebPlayer = 1 << 9,
Android = 1 << 10,
// ReSharper disable once InconsistentNaming
IPhonePlayer = 1 << 11,
TizenPlayer = 1 << 12,
WP8Player = 1 << 13,
BB10Player = 1 << 14,
NaCl = 1 << 15,
PS3 = 1 << 16,
XBOX360 = 1 << 17,
WiiPlayer = 1 << 18,
PSP2 = 1 << 19,
PS4 = 1 << 20,
PSMPlayer = 1 << 21,
XboxOne = 1 << 22,
}
#region ITestComponent implementation
public void EnableTest(bool enable)
{
if (enable && dynamic)
{
Type t = Type.GetType(dynamicTypeName);
var s = gameObject.GetComponent(t) as MonoBehaviour;
if (s != null)
DestroyImmediate(s);
gameObject.AddComponent(t);
}
if (gameObject.activeSelf != enable) gameObject.SetActive(enable);
}
public int CompareTo(ITestComponent obj)
{
if (obj == NullTestComponent)
return 1;
var result = gameObject.name.CompareTo(obj.gameObject.name);
if (result == 0)
result = gameObject.GetInstanceID().CompareTo(obj.gameObject.GetInstanceID());
return result;
}
public bool IsTestGroup()
{
for (int i = 0; i < gameObject.transform.childCount; i++)
{
var childTc = gameObject.transform.GetChild(i).GetComponent(typeof(TestComponent));
if (childTc != null)
return true;
}
return false;
}
public string Name { get { return gameObject == null ? "" : gameObject.name; } }
public ITestComponent GetTestGroup()
{
var parent = gameObject.transform.parent;
if (parent == null)
return NullTestComponent;
return parent.GetComponent<TestComponent>();
}
public override bool Equals(object o)
{
if (o is TestComponent)
return this == (o as TestComponent);
return false;
}
public override int GetHashCode()
{
return base.GetHashCode();
}
public static bool operator ==(TestComponent a, TestComponent b)
{
if (ReferenceEquals(a, b))
return true;
if (((object)a == null) || ((object)b == null))
return false;
if (a.dynamic && b.dynamic)
return a.dynamicTypeName == b.dynamicTypeName;
if (a.dynamic || b.dynamic)
return false;
return a.gameObject == b.gameObject;
}
public static bool operator !=(TestComponent a, TestComponent b)
{
return !(a == b);
}
#endregion
#region Static helpers
public static TestComponent CreateDynamicTest(Type type)
{
var go = CreateTest(type.Name);
go.hideFlags |= HideFlags.DontSave;
go.SetActive(false);
var tc = go.GetComponent<TestComponent>();
tc.dynamic = true;
tc.dynamicTypeName = type.AssemblyQualifiedName;
#if !UNITY_METRO
foreach (var typeAttribute in type.GetCustomAttributes(false))
{
if (typeAttribute is IntegrationTest.TimeoutAttribute)
tc.timeout = (typeAttribute as IntegrationTest.TimeoutAttribute).timeout;
else if (typeAttribute is IntegrationTest.IgnoreAttribute)
tc.ignored = true;
else if (typeAttribute is IntegrationTest.SucceedWithAssertions)
tc.succeedAfterAllAssertionsAreExecuted = true;
else if (typeAttribute is IntegrationTest.ExcludePlatformAttribute)
tc.platformsToIgnore = (typeAttribute as IntegrationTest.ExcludePlatformAttribute).platformsToExclude;
else if (typeAttribute is IntegrationTest.ExpectExceptions)
{
var attribute = (typeAttribute as IntegrationTest.ExpectExceptions);
tc.expectException = true;
tc.expectedExceptionList = string.Join(",", attribute.exceptionTypeNames);
tc.succeedWhenExceptionIsThrown = attribute.succeedOnException;
}
}
go.AddComponent(type);
#endif // if !UNITY_METRO
return tc;
}
public static GameObject CreateTest()
{
return CreateTest("New Test");
}
private static GameObject CreateTest(string name)
{
var go = new GameObject(name);
go.AddComponent<TestComponent>();
#if UNITY_EDITOR
Undo.RegisterCreatedObjectUndo(go, "Created test");
#endif
return go;
}
public static List<TestComponent> FindAllTestsOnScene()
{
var tests = Resources.FindObjectsOfTypeAll (typeof(TestComponent)).Cast<TestComponent> ();
#if UNITY_EDITOR
tests = tests.Where( t => {var p = PrefabUtility.GetPrefabType(t); return p != PrefabType.Prefab && p != PrefabType.ModelPrefab;} );
#endif
return tests.ToList ();
}
public static List<TestComponent> FindAllTopTestsOnScene()
{
return FindAllTestsOnScene().Where(component => component.gameObject.transform.parent == null).ToList();
}
public static List<TestComponent> FindAllDynamicTestsOnScene()
{
return FindAllTestsOnScene().Where(t => t.dynamic).ToList();
}
public static void DestroyAllDynamicTests()
{
foreach (var dynamicTestComponent in FindAllDynamicTestsOnScene())
DestroyImmediate(dynamicTestComponent.gameObject);
}
public static void DisableAllTests()
{
foreach (var t in FindAllTestsOnScene()) t.EnableTest(false);
}
public static bool AnyTestsOnScene()
{
return FindAllTestsOnScene().Any();
}
public static bool AnyDynamicTestForCurrentScene()
{
#if UNITY_5_3_OR_NEWER
return TestComponent.GetTypesWithHelpAttribute(SceneManager.GetActiveScene().name).Any();
#else
return TestComponent.GetTypesWithHelpAttribute(Application.loadedLevelName).Any();
#endif
}
#endregion
private sealed class NullTestComponentImpl : ITestComponent
{
public int CompareTo(ITestComponent other)
{
if (other == this) return 0;
return -1;
}
public void EnableTest(bool enable)
{
}
public bool IsTestGroup()
{
throw new NotImplementedException();
}
public GameObject gameObject { get; private set; }
public string Name { get { return ""; } }
public ITestComponent GetTestGroup()
{
return null;
}
public bool IsExceptionExpected(string exceptionType)
{
throw new NotImplementedException();
}
public bool ShouldSucceedOnException()
{
throw new NotImplementedException();
}
public double GetTimeout()
{
throw new NotImplementedException();
}
public bool IsIgnored()
{
throw new NotImplementedException();
}
public bool ShouldSucceedOnAssertions()
{
throw new NotImplementedException();
}
public bool IsExludedOnThisPlatform()
{
throw new NotImplementedException();
}
}
public static IEnumerable<Type> GetTypesWithHelpAttribute(string sceneName)
{
#if !UNITY_METRO
foreach (var assembly in AppDomain.CurrentDomain.GetAssemblies())
{
Type[] types = null;
try
{
types = assembly.GetTypes();
}
catch (ReflectionTypeLoadException ex)
{
Debug.LogError("Failed to load types from: " + assembly.FullName);
foreach (Exception loadEx in ex.LoaderExceptions)
Debug.LogException(loadEx);
}
if (types == null)
continue;
foreach (Type type in types)
{
var attributes = type.GetCustomAttributes(typeof(IntegrationTest.DynamicTestAttribute), true);
if (attributes.Length == 1)
{
var a = attributes.Single() as IntegrationTest.DynamicTestAttribute;
if (a.IncludeOnScene(sceneName)) yield return type;
}
}
}
#else // if !UNITY_METRO
yield break;
#endif // if !UNITY_METRO
}
}
}