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
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 |
|
} |
|
} |
|
}
|
|
|