diff --git a/Assets/UnityTestTools.meta b/Assets/UnityTestTools.meta new file mode 100644 index 00000000..a6d0ba47 --- /dev/null +++ b/Assets/UnityTestTools.meta @@ -0,0 +1,9 @@ +fileFormatVersion: 2 +guid: 5c8e5d66ff19140a1bf3cfdc3955859c +folderAsset: yes +timeCreated: 1438859672 +licenseType: Free +DefaultImporter: + userData: + assetBundleName: + assetBundleVariant: diff --git a/Assets/UnityTestTools/Assertions.meta b/Assets/UnityTestTools/Assertions.meta new file mode 100644 index 00000000..229b8b81 --- /dev/null +++ b/Assets/UnityTestTools/Assertions.meta @@ -0,0 +1,5 @@ +fileFormatVersion: 2 +guid: b27b28700d3365146808b6e082748201 +folderAsset: yes +DefaultImporter: + userData: diff --git a/Assets/UnityTestTools/Assertions/AssertionComponent.cs b/Assets/UnityTestTools/Assertions/AssertionComponent.cs new file mode 100644 index 00000000..fa40d488 --- /dev/null +++ b/Assets/UnityTestTools/Assertions/AssertionComponent.cs @@ -0,0 +1,379 @@ +using System; +using System.Collections; +using System.Collections.Generic; +using System.Diagnostics; +using System.Linq; +using UnityEngine; +using Debug = UnityEngine.Debug; +using Object = UnityEngine.Object; + +namespace UnityTest +{ + [Serializable] + public class AssertionComponent : MonoBehaviour, IAssertionComponentConfigurator + { + [SerializeField] public float checkAfterTime = 1f; + [SerializeField] public bool repeatCheckTime = true; + [SerializeField] public float repeatEveryTime = 1f; + [SerializeField] public int checkAfterFrames = 1; + [SerializeField] public bool repeatCheckFrame = true; + [SerializeField] public int repeatEveryFrame = 1; + [SerializeField] public bool hasFailed; + + [SerializeField] public CheckMethod checkMethods = CheckMethod.Start; + [SerializeField] private ActionBase m_ActionBase; + + [SerializeField] public int checksPerformed = 0; + + private int m_CheckOnFrame; + + private string m_CreatedInFilePath = ""; + private int m_CreatedInFileLine = -1; + + public ActionBase Action + { + get { return m_ActionBase; } + set + { + m_ActionBase = value; + m_ActionBase.go = gameObject; + } + } + + public Object GetFailureReferenceObject() + { + #if UNITY_EDITOR + if (!string.IsNullOrEmpty(m_CreatedInFilePath)) + { + return UnityEditor.AssetDatabase.LoadAssetAtPath(m_CreatedInFilePath, typeof(Object)); + } + #endif + return this; + } + + public string GetCreationLocation() + { + if (!string.IsNullOrEmpty(m_CreatedInFilePath)) + { + var idx = m_CreatedInFilePath.LastIndexOf("\\") + 1; + return string.Format("{0}, line {1} ({2})", m_CreatedInFilePath.Substring(idx), m_CreatedInFileLine, m_CreatedInFilePath); + } + return ""; + } + + public void Awake() + { + if (!Debug.isDebugBuild) + Destroy(this); + OnComponentCopy(); + } + + public void OnValidate() + { + if (Application.isEditor) + OnComponentCopy(); + } + + private void OnComponentCopy() + { + if (m_ActionBase == null) return; + var oldActionList = Resources.FindObjectsOfTypeAll(typeof(AssertionComponent)).Where(o => ((AssertionComponent)o).m_ActionBase == m_ActionBase && o != this); + + // if it's not a copy but a new component don't do anything + if (!oldActionList.Any()) return; + if (oldActionList.Count() > 1) + Debug.LogWarning("More than one refence to comparer found. This shouldn't happen"); + + var oldAction = oldActionList.First() as AssertionComponent; + m_ActionBase = oldAction.m_ActionBase.CreateCopy(oldAction.gameObject, gameObject); + } + + public void Start() + { + CheckAssertionFor(CheckMethod.Start); + + if (IsCheckMethodSelected(CheckMethod.AfterPeriodOfTime)) + { + StartCoroutine("CheckPeriodically"); + } + if (IsCheckMethodSelected(CheckMethod.Update)) + { + m_CheckOnFrame = Time.frameCount + checkAfterFrames; + } + } + + public IEnumerator CheckPeriodically() + { + yield return new WaitForSeconds(checkAfterTime); + CheckAssertionFor(CheckMethod.AfterPeriodOfTime); + while (repeatCheckTime) + { + yield return new WaitForSeconds(repeatEveryTime); + CheckAssertionFor(CheckMethod.AfterPeriodOfTime); + } + } + + public bool ShouldCheckOnFrame() + { + if (Time.frameCount > m_CheckOnFrame) + { + if (repeatCheckFrame) + m_CheckOnFrame += repeatEveryFrame; + else + m_CheckOnFrame = Int32.MaxValue; + return true; + } + return false; + } + + public void OnDisable() + { + CheckAssertionFor(CheckMethod.OnDisable); + } + + public void OnEnable() + { + CheckAssertionFor(CheckMethod.OnEnable); + } + + public void OnDestroy() + { + CheckAssertionFor(CheckMethod.OnDestroy); + } + + public void Update() + { + if (IsCheckMethodSelected(CheckMethod.Update) && ShouldCheckOnFrame()) + { + CheckAssertionFor(CheckMethod.Update); + } + } + + public void FixedUpdate() + { + CheckAssertionFor(CheckMethod.FixedUpdate); + } + + public void LateUpdate() + { + CheckAssertionFor(CheckMethod.LateUpdate); + } + + public void OnControllerColliderHit() + { + CheckAssertionFor(CheckMethod.OnControllerColliderHit); + } + + public void OnParticleCollision() + { + CheckAssertionFor(CheckMethod.OnParticleCollision); + } + + public void OnJointBreak() + { + CheckAssertionFor(CheckMethod.OnJointBreak); + } + + public void OnBecameInvisible() + { + CheckAssertionFor(CheckMethod.OnBecameInvisible); + } + + public void OnBecameVisible() + { + CheckAssertionFor(CheckMethod.OnBecameVisible); + } + + public void OnTriggerEnter() + { + CheckAssertionFor(CheckMethod.OnTriggerEnter); + } + + public void OnTriggerExit() + { + CheckAssertionFor(CheckMethod.OnTriggerExit); + } + + public void OnTriggerStay() + { + CheckAssertionFor(CheckMethod.OnTriggerStay); + } + + public void OnCollisionEnter() + { + CheckAssertionFor(CheckMethod.OnCollisionEnter); + } + + public void OnCollisionExit() + { + CheckAssertionFor(CheckMethod.OnCollisionExit); + } + + public void OnCollisionStay() + { + CheckAssertionFor(CheckMethod.OnCollisionStay); + } + + public void OnTriggerEnter2D() + { + CheckAssertionFor(CheckMethod.OnTriggerEnter2D); + } + + public void OnTriggerExit2D() + { + CheckAssertionFor(CheckMethod.OnTriggerExit2D); + } + + public void OnTriggerStay2D() + { + CheckAssertionFor(CheckMethod.OnTriggerStay2D); + } + + public void OnCollisionEnter2D() + { + CheckAssertionFor(CheckMethod.OnCollisionEnter2D); + } + + public void OnCollisionExit2D() + { + CheckAssertionFor(CheckMethod.OnCollisionExit2D); + } + + public void OnCollisionStay2D() + { + CheckAssertionFor(CheckMethod.OnCollisionStay2D); + } + + private void CheckAssertionFor(CheckMethod checkMethod) + { + if (IsCheckMethodSelected(checkMethod)) + { + Assertions.CheckAssertions(this); + } + } + + public bool IsCheckMethodSelected(CheckMethod method) + { + return method == (checkMethods & method); + } + + + #region Assertion Component create methods + + public static T Create(CheckMethod checkOnMethods, GameObject gameObject, string propertyPath) where T : ActionBase + { + IAssertionComponentConfigurator configurator; + return Create(out configurator, checkOnMethods, gameObject, propertyPath); + } + + public static T Create(out IAssertionComponentConfigurator configurator, CheckMethod checkOnMethods, GameObject gameObject, string propertyPath) where T : ActionBase + { + return CreateAssertionComponent(out configurator, checkOnMethods, gameObject, propertyPath); + } + + public static T Create(CheckMethod checkOnMethods, GameObject gameObject, string propertyPath, GameObject gameObject2, string propertyPath2) where T : ComparerBase + { + IAssertionComponentConfigurator configurator; + return Create(out configurator, checkOnMethods, gameObject, propertyPath, gameObject2, propertyPath2); + } + + public static T Create(out IAssertionComponentConfigurator configurator, CheckMethod checkOnMethods, GameObject gameObject, string propertyPath, GameObject gameObject2, string propertyPath2) where T : ComparerBase + { + var comparer = CreateAssertionComponent(out configurator, checkOnMethods, gameObject, propertyPath); + comparer.compareToType = ComparerBase.CompareToType.CompareToObject; + comparer.other = gameObject2; + comparer.otherPropertyPath = propertyPath2; + return comparer; + } + + public static T Create(CheckMethod checkOnMethods, GameObject gameObject, string propertyPath, object constValue) where T : ComparerBase + { + IAssertionComponentConfigurator configurator; + return Create(out configurator, checkOnMethods, gameObject, propertyPath, constValue); + } + + public static T Create(out IAssertionComponentConfigurator configurator, CheckMethod checkOnMethods, GameObject gameObject, string propertyPath, object constValue) where T : ComparerBase + { + var comparer = CreateAssertionComponent(out configurator, checkOnMethods, gameObject, propertyPath); + if (constValue == null) + { + comparer.compareToType = ComparerBase.CompareToType.CompareToNull; + return comparer; + } + comparer.compareToType = ComparerBase.CompareToType.CompareToConstantValue; + comparer.ConstValue = constValue; + return comparer; + } + + private static T CreateAssertionComponent(out IAssertionComponentConfigurator configurator, CheckMethod checkOnMethods, GameObject gameObject, string propertyPath) where T : ActionBase + { + var ac = gameObject.AddComponent(); + ac.checkMethods = checkOnMethods; + var comparer = ScriptableObject.CreateInstance(); + ac.Action = comparer; + ac.Action.go = gameObject; + ac.Action.thisPropertyPath = propertyPath; + configurator = ac; + +#if !UNITY_METRO + var stackTrace = new StackTrace(true); + var thisFileName = stackTrace.GetFrame(0).GetFileName(); + for (int i = 1; i < stackTrace.FrameCount; i++) + { + var stackFrame = stackTrace.GetFrame(i); + if (stackFrame.GetFileName() != thisFileName) + { + string filePath = stackFrame.GetFileName().Substring(Application.dataPath.Length - "Assets".Length); + ac.m_CreatedInFilePath = filePath; + ac.m_CreatedInFileLine = stackFrame.GetFileLineNumber(); + break; + } + } +#endif // if !UNITY_METRO + return comparer; + } + + #endregion + + #region AssertionComponentConfigurator + public int UpdateCheckStartOnFrame { set { checkAfterFrames = value; } } + public int UpdateCheckRepeatFrequency { set { repeatEveryFrame = value; } } + public bool UpdateCheckRepeat { set { repeatCheckFrame = value; } } + public float TimeCheckStartAfter { set { checkAfterTime = value; } } + public float TimeCheckRepeatFrequency { set { repeatEveryTime = value; } } + public bool TimeCheckRepeat { set { repeatCheckTime = value; } } + public AssertionComponent Component { get { return this; } } + #endregion + } + + public interface IAssertionComponentConfigurator + { + /// + /// If the assertion is evaluated in Update, after how many frame should the evaluation start. Deafult is 1 (first frame) + /// + int UpdateCheckStartOnFrame { set; } + /// + /// If the assertion is evaluated in Update and UpdateCheckRepeat is true, how many frame should pass between evaluations + /// + int UpdateCheckRepeatFrequency { set; } + /// + /// If the assertion is evaluated in Update, should the evaluation be repeated after UpdateCheckRepeatFrequency frames + /// + bool UpdateCheckRepeat { set; } + + /// + /// If the assertion is evaluated after a period of time, after how many seconds the first evaluation should be done + /// + float TimeCheckStartAfter { set; } + /// + /// If the assertion is evaluated after a period of time and TimeCheckRepeat is true, after how many seconds should the next evaluation happen + /// + float TimeCheckRepeatFrequency { set; } + /// + /// If the assertion is evaluated after a period, should the evaluation happen again after TimeCheckRepeatFrequency seconds + /// + bool TimeCheckRepeat { set; } + + AssertionComponent Component { get; } + } +} diff --git a/Assets/UnityTestTools/Assertions/AssertionComponent.cs.meta b/Assets/UnityTestTools/Assertions/AssertionComponent.cs.meta new file mode 100644 index 00000000..26f9ab45 --- /dev/null +++ b/Assets/UnityTestTools/Assertions/AssertionComponent.cs.meta @@ -0,0 +1,8 @@ +fileFormatVersion: 2 +guid: 8bafa54482a87ac4cbd7ff1bfd1ac93a +MonoImporter: + serializedVersion: 2 + defaultReferences: [] + executionOrder: 0 + icon: {instanceID: 0} + userData: diff --git a/Assets/UnityTestTools/Assertions/AssertionException.cs b/Assets/UnityTestTools/Assertions/AssertionException.cs new file mode 100644 index 00000000..00c6d588 --- /dev/null +++ b/Assets/UnityTestTools/Assertions/AssertionException.cs @@ -0,0 +1,24 @@ +using System; +using System.Collections.Generic; +using UnityEngine; + +namespace UnityTest +{ + public class AssertionException : Exception + { + private readonly AssertionComponent m_Assertion; + + public AssertionException(AssertionComponent assertion) : base(assertion.Action.GetFailureMessage()) + { + m_Assertion = assertion; + } + + public override string StackTrace + { + get + { + return "Created in " + m_Assertion.GetCreationLocation(); + } + } + } +} diff --git a/Assets/UnityTestTools/Assertions/AssertionException.cs.meta b/Assets/UnityTestTools/Assertions/AssertionException.cs.meta new file mode 100644 index 00000000..9605bf01 --- /dev/null +++ b/Assets/UnityTestTools/Assertions/AssertionException.cs.meta @@ -0,0 +1,8 @@ +fileFormatVersion: 2 +guid: ef3769ab00d50bc4fbb05a9a91c741d9 +MonoImporter: + serializedVersion: 2 + defaultReferences: [] + executionOrder: 0 + icon: {instanceID: 0} + userData: diff --git a/Assets/UnityTestTools/Assertions/Assertions.cs b/Assets/UnityTestTools/Assertions/Assertions.cs new file mode 100644 index 00000000..14b3fd6c --- /dev/null +++ b/Assets/UnityTestTools/Assertions/Assertions.cs @@ -0,0 +1,42 @@ +using System; +using System.Collections.Generic; +using UnityEngine; +using Object = UnityEngine.Object; + +namespace UnityTest +{ + public static class Assertions + { + public static void CheckAssertions() + { + var assertions = Object.FindObjectsOfType(typeof(AssertionComponent)) as AssertionComponent[]; + CheckAssertions(assertions); + } + + public static void CheckAssertions(AssertionComponent assertion) + { + CheckAssertions(new[] {assertion}); + } + + public static void CheckAssertions(GameObject gameObject) + { + CheckAssertions(gameObject.GetComponents()); + } + + public static void CheckAssertions(AssertionComponent[] assertions) + { + if (!Debug.isDebugBuild) + return; + foreach (var assertion in assertions) + { + assertion.checksPerformed++; + var result = assertion.Action.Compare(); + if (!result) + { + assertion.hasFailed = true; + assertion.Action.Fail(assertion); + } + } + } + } +} diff --git a/Assets/UnityTestTools/Assertions/Assertions.cs.meta b/Assets/UnityTestTools/Assertions/Assertions.cs.meta new file mode 100644 index 00000000..00878a4f --- /dev/null +++ b/Assets/UnityTestTools/Assertions/Assertions.cs.meta @@ -0,0 +1,8 @@ +fileFormatVersion: 2 +guid: 85280dad1e618c143bd3fb07a197b469 +MonoImporter: + serializedVersion: 2 + defaultReferences: [] + executionOrder: 0 + icon: {instanceID: 0} + userData: diff --git a/Assets/UnityTestTools/Assertions/CheckMethod.cs b/Assets/UnityTestTools/Assertions/CheckMethod.cs new file mode 100644 index 00000000..07583dd3 --- /dev/null +++ b/Assets/UnityTestTools/Assertions/CheckMethod.cs @@ -0,0 +1,36 @@ +using System; +using System.Collections.Generic; +using UnityEngine; + +namespace UnityTest +{ + [Flags] + public enum CheckMethod + { + AfterPeriodOfTime = 1 << 0, + Start = 1 << 1, + Update = 1 << 2, + FixedUpdate = 1 << 3, + LateUpdate = 1 << 4, + OnDestroy = 1 << 5, + OnEnable = 1 << 6, + OnDisable = 1 << 7, + OnControllerColliderHit = 1 << 8, + OnParticleCollision = 1 << 9, + OnJointBreak = 1 << 10, + OnBecameInvisible = 1 << 11, + OnBecameVisible = 1 << 12, + OnTriggerEnter = 1 << 13, + OnTriggerExit = 1 << 14, + OnTriggerStay = 1 << 15, + OnCollisionEnter = 1 << 16, + OnCollisionExit = 1 << 17, + OnCollisionStay = 1 << 18, + OnTriggerEnter2D = 1 << 19, + OnTriggerExit2D = 1 << 20, + OnTriggerStay2D = 1 << 21, + OnCollisionEnter2D = 1 << 22, + OnCollisionExit2D = 1 << 23, + OnCollisionStay2D = 1 << 24, + } +} diff --git a/Assets/UnityTestTools/Assertions/CheckMethod.cs.meta b/Assets/UnityTestTools/Assertions/CheckMethod.cs.meta new file mode 100644 index 00000000..d3f6ec9d --- /dev/null +++ b/Assets/UnityTestTools/Assertions/CheckMethod.cs.meta @@ -0,0 +1,8 @@ +fileFormatVersion: 2 +guid: cbb75d1643c5a55439f8861a827f411b +MonoImporter: + serializedVersion: 2 + defaultReferences: [] + executionOrder: 0 + icon: {instanceID: 0} + userData: diff --git a/Assets/UnityTestTools/Assertions/Comparers.meta b/Assets/UnityTestTools/Assertions/Comparers.meta new file mode 100644 index 00000000..15d3a928 --- /dev/null +++ b/Assets/UnityTestTools/Assertions/Comparers.meta @@ -0,0 +1,5 @@ +fileFormatVersion: 2 +guid: bb9e10c25f478c84f826ea85b03ec179 +folderAsset: yes +DefaultImporter: + userData: diff --git a/Assets/UnityTestTools/Assertions/Comparers/ActionBase.cs b/Assets/UnityTestTools/Assertions/Comparers/ActionBase.cs new file mode 100644 index 00000000..a73e0e25 --- /dev/null +++ b/Assets/UnityTestTools/Assertions/Comparers/ActionBase.cs @@ -0,0 +1,121 @@ +using System; +using System.Collections.Generic; +using System.Linq; +using System.Reflection; +using UnityEngine; + +namespace UnityTest +{ + public abstract class ActionBase : ScriptableObject + { + public GameObject go; + protected object m_ObjVal; + + private MemberResolver m_MemberResolver; + + public string thisPropertyPath = ""; + public virtual Type[] GetAccepatbleTypesForA() + { + return null; + } + public virtual int GetDepthOfSearch() { return 2; } + + public virtual string[] GetExcludedFieldNames() + { + return new string[] { }; + } + + public bool Compare() + { + if (m_MemberResolver == null) + m_MemberResolver = new MemberResolver(go, thisPropertyPath); + m_ObjVal = m_MemberResolver.GetValue(UseCache); + var result = Compare(m_ObjVal); + return result; + } + + protected abstract bool Compare(object objVal); + + virtual protected bool UseCache { get { return false; } } + + public virtual Type GetParameterType() { return typeof(object); } + + public virtual string GetConfigurationDescription() + { + string result = ""; +#if !UNITY_METRO + foreach (var prop in GetType().GetFields(BindingFlags.Public | BindingFlags.Instance | BindingFlags.DeclaredOnly) + .Where(info => info.FieldType.IsSerializable)) + { + var value = prop.GetValue(this); + if (value is double) + value = ((double)value).ToString("0.########"); + if (value is float) + value = ((float)value).ToString("0.########"); + result += value + " "; + } +#endif // if !UNITY_METRO + return result; + } + + IEnumerable GetFields(Type type) + { +#if !UNITY_METRO + return type.GetFields(BindingFlags.Public | BindingFlags.Instance); +#else + return null; +#endif + } + + public ActionBase CreateCopy(GameObject oldGameObject, GameObject newGameObject) + { +#if !UNITY_METRO + var newObj = CreateInstance(GetType()) as ActionBase; +#else + var newObj = (ActionBase) this.MemberwiseClone(); +#endif + var fields = GetFields(GetType()); + foreach (var field in fields) + { + var value = field.GetValue(this); + if (value is GameObject) + { + if (value as GameObject == oldGameObject) + value = newGameObject; + } + field.SetValue(newObj, value); + } + return newObj; + } + + public virtual void Fail(AssertionComponent assertion) + { + Debug.LogException(new AssertionException(assertion), assertion.GetFailureReferenceObject()); + } + + public virtual string GetFailureMessage() + { + return GetType().Name + " assertion failed.\n(" + go + ")." + thisPropertyPath + " failed. Value: " + m_ObjVal; + } + } + + public abstract class ActionBaseGeneric : ActionBase + { + protected override bool Compare(object objVal) + { + return Compare((T)objVal); + } + protected abstract bool Compare(T objVal); + + public override Type[] GetAccepatbleTypesForA() + { + return new[] { typeof(T) }; + } + + public override Type GetParameterType() + { + return typeof(T); + } + protected override bool UseCache { get { return true; } } + } +} diff --git a/Assets/UnityTestTools/Assertions/Comparers/ActionBase.cs.meta b/Assets/UnityTestTools/Assertions/Comparers/ActionBase.cs.meta new file mode 100644 index 00000000..6d4c3ab0 --- /dev/null +++ b/Assets/UnityTestTools/Assertions/Comparers/ActionBase.cs.meta @@ -0,0 +1,8 @@ +fileFormatVersion: 2 +guid: b4995756bd539804e8143ff1e730f806 +MonoImporter: + serializedVersion: 2 + defaultReferences: [] + executionOrder: 0 + icon: {instanceID: 0} + userData: diff --git a/Assets/UnityTestTools/Assertions/Comparers/BoolComparer.cs b/Assets/UnityTestTools/Assertions/Comparers/BoolComparer.cs new file mode 100644 index 00000000..3987cc27 --- /dev/null +++ b/Assets/UnityTestTools/Assertions/Comparers/BoolComparer.cs @@ -0,0 +1,14 @@ +using System; +using System.Collections.Generic; +using UnityEngine; + +namespace UnityTest +{ + public class BoolComparer : ComparerBaseGeneric + { + protected override bool Compare(bool a, bool b) + { + return a == b; + } + } +} diff --git a/Assets/UnityTestTools/Assertions/Comparers/BoolComparer.cs.meta b/Assets/UnityTestTools/Assertions/Comparers/BoolComparer.cs.meta new file mode 100644 index 00000000..7ee21b60 --- /dev/null +++ b/Assets/UnityTestTools/Assertions/Comparers/BoolComparer.cs.meta @@ -0,0 +1,8 @@ +fileFormatVersion: 2 +guid: 2586c8e41f35d2f4fadde53020bf4207 +MonoImporter: + serializedVersion: 2 + defaultReferences: [] + executionOrder: 0 + icon: {instanceID: 0} + userData: diff --git a/Assets/UnityTestTools/Assertions/Comparers/ColliderComparer.cs b/Assets/UnityTestTools/Assertions/Comparers/ColliderComparer.cs new file mode 100644 index 00000000..cfca05df --- /dev/null +++ b/Assets/UnityTestTools/Assertions/Comparers/ColliderComparer.cs @@ -0,0 +1,29 @@ +using System; +using System.Collections.Generic; +using UnityEngine; + +namespace UnityTest +{ + public class ColliderComparer : ComparerBaseGeneric + { + public enum CompareType + { + Intersects, + DoesNotIntersect + }; + + public CompareType compareType; + + protected override bool Compare(Bounds a, Bounds b) + { + switch (compareType) + { + case CompareType.Intersects: + return a.Intersects(b); + case CompareType.DoesNotIntersect: + return !a.Intersects(b); + } + throw new Exception(); + } + } +} diff --git a/Assets/UnityTestTools/Assertions/Comparers/ColliderComparer.cs.meta b/Assets/UnityTestTools/Assertions/Comparers/ColliderComparer.cs.meta new file mode 100644 index 00000000..ab3aa477 --- /dev/null +++ b/Assets/UnityTestTools/Assertions/Comparers/ColliderComparer.cs.meta @@ -0,0 +1,8 @@ +fileFormatVersion: 2 +guid: 4eff45b2ac4067b469d7994298341db6 +MonoImporter: + serializedVersion: 2 + defaultReferences: [] + executionOrder: 0 + icon: {instanceID: 0} + userData: diff --git a/Assets/UnityTestTools/Assertions/Comparers/ComparerBase.cs b/Assets/UnityTestTools/Assertions/Comparers/ComparerBase.cs new file mode 100644 index 00000000..db902118 --- /dev/null +++ b/Assets/UnityTestTools/Assertions/Comparers/ComparerBase.cs @@ -0,0 +1,145 @@ +using System; +using System.Collections.Generic; +using UnityEngine; +using Object = System.Object; + +namespace UnityTest +{ + public abstract class ComparerBase : ActionBase + { + public enum CompareToType + { + CompareToObject, + CompareToConstantValue, + CompareToNull + } + + public CompareToType compareToType = CompareToType.CompareToObject; + + public GameObject other; + protected object m_ObjOtherVal; + public string otherPropertyPath = ""; + private MemberResolver m_MemberResolverB; + + protected abstract bool Compare(object a, object b); + + protected override bool Compare(object objValue) + { + if (compareToType == CompareToType.CompareToConstantValue) + { + m_ObjOtherVal = ConstValue; + } + else if (compareToType == CompareToType.CompareToNull) + { + m_ObjOtherVal = null; + } + else + { + if (other == null) + m_ObjOtherVal = null; + else + { + if (m_MemberResolverB == null) + m_MemberResolverB = new MemberResolver(other, otherPropertyPath); + m_ObjOtherVal = m_MemberResolverB.GetValue(UseCache); + } + } + return Compare(objValue, m_ObjOtherVal); + } + + public virtual Type[] GetAccepatbleTypesForB() + { + return null; + } + + #region Const value + + public virtual object ConstValue { get; set; } + public virtual object GetDefaultConstValue() + { + throw new NotImplementedException(); + } + + #endregion + + public override string GetFailureMessage() + { + var message = GetType().Name + " assertion failed.\n" + go.name + "." + thisPropertyPath + " " + compareToType; + switch (compareToType) + { + case CompareToType.CompareToObject: + message += " (" + other + ")." + otherPropertyPath + " failed."; + break; + case CompareToType.CompareToConstantValue: + message += " " + ConstValue + " failed."; + break; + case CompareToType.CompareToNull: + message += " failed."; + break; + } + message += " Expected: " + m_ObjOtherVal + " Actual: " + m_ObjVal; + return message; + } + } + + [Serializable] + public abstract class ComparerBaseGeneric : ComparerBaseGeneric + { + } + + [Serializable] + public abstract class ComparerBaseGeneric : ComparerBase + { + public T2 constantValueGeneric = default(T2); + + public override Object ConstValue + { + get + { + return constantValueGeneric; + } + set + { + constantValueGeneric = (T2)value; + } + } + + public override Object GetDefaultConstValue() + { + return default(T2); + } + + static bool IsValueType(Type type) + { +#if !UNITY_METRO + return type.IsValueType; +#else + return false; +#endif + } + + protected override bool Compare(object a, object b) + { + var type = typeof(T2); + if (b == null && IsValueType(type)) + { + throw new ArgumentException("Null was passed to a value-type argument"); + } + return Compare((T1)a, (T2)b); + } + + protected abstract bool Compare(T1 a, T2 b); + + public override Type[] GetAccepatbleTypesForA() + { + return new[] { typeof(T1) }; + } + + public override Type[] GetAccepatbleTypesForB() + { + return new[] {typeof(T2)}; + } + + protected override bool UseCache { get { return true; } } + } +} diff --git a/Assets/UnityTestTools/Assertions/Comparers/ComparerBase.cs.meta b/Assets/UnityTestTools/Assertions/Comparers/ComparerBase.cs.meta new file mode 100644 index 00000000..65909eb9 --- /dev/null +++ b/Assets/UnityTestTools/Assertions/Comparers/ComparerBase.cs.meta @@ -0,0 +1,8 @@ +fileFormatVersion: 2 +guid: c86508f389d643b40b6e1d7dcc1d4df2 +MonoImporter: + serializedVersion: 2 + defaultReferences: [] + executionOrder: 0 + icon: {instanceID: 0} + userData: diff --git a/Assets/UnityTestTools/Assertions/Comparers/FloatComparer.cs b/Assets/UnityTestTools/Assertions/Comparers/FloatComparer.cs new file mode 100644 index 00000000..ce0a2c24 --- /dev/null +++ b/Assets/UnityTestTools/Assertions/Comparers/FloatComparer.cs @@ -0,0 +1,40 @@ +using System; +using System.Collections.Generic; +using UnityEngine; + +namespace UnityTest +{ + public class FloatComparer : ComparerBaseGeneric + { + public enum CompareTypes + { + Equal, + NotEqual, + Greater, + Less + } + + public CompareTypes compareTypes; + public double floatingPointError = 0.0001f; + + protected override bool Compare(float a, float b) + { + switch (compareTypes) + { + case CompareTypes.Equal: + return Math.Abs(a - b) < floatingPointError; + case CompareTypes.NotEqual: + return Math.Abs(a - b) > floatingPointError; + case CompareTypes.Greater: + return a > b; + case CompareTypes.Less: + return a < b; + } + throw new Exception(); + } + public override int GetDepthOfSearch() + { + return 3; + } + } +} diff --git a/Assets/UnityTestTools/Assertions/Comparers/FloatComparer.cs.meta b/Assets/UnityTestTools/Assertions/Comparers/FloatComparer.cs.meta new file mode 100644 index 00000000..07353adf --- /dev/null +++ b/Assets/UnityTestTools/Assertions/Comparers/FloatComparer.cs.meta @@ -0,0 +1,8 @@ +fileFormatVersion: 2 +guid: a4928c6c2b973874c8d4e6c9a69bb5b4 +MonoImporter: + serializedVersion: 2 + defaultReferences: [] + executionOrder: 0 + icon: {instanceID: 0} + userData: diff --git a/Assets/UnityTestTools/Assertions/Comparers/GeneralComparer.cs b/Assets/UnityTestTools/Assertions/Comparers/GeneralComparer.cs new file mode 100644 index 00000000..96892aa6 --- /dev/null +++ b/Assets/UnityTestTools/Assertions/Comparers/GeneralComparer.cs @@ -0,0 +1,22 @@ +using System; +using System.Collections.Generic; +using UnityEngine; + +namespace UnityTest +{ + public class GeneralComparer : ComparerBase + { + public enum CompareType { AEqualsB, ANotEqualsB } + + public CompareType compareType; + + protected override bool Compare(object a, object b) + { + if (compareType == CompareType.AEqualsB) + return a.Equals(b); + if (compareType == CompareType.ANotEqualsB) + return !a.Equals(b); + throw new Exception(); + } + } +} diff --git a/Assets/UnityTestTools/Assertions/Comparers/GeneralComparer.cs.meta b/Assets/UnityTestTools/Assertions/Comparers/GeneralComparer.cs.meta new file mode 100644 index 00000000..6b7edb32 --- /dev/null +++ b/Assets/UnityTestTools/Assertions/Comparers/GeneralComparer.cs.meta @@ -0,0 +1,8 @@ +fileFormatVersion: 2 +guid: 902961c69f102f4409c29b9e54258701 +MonoImporter: + serializedVersion: 2 + defaultReferences: [] + executionOrder: 0 + icon: {instanceID: 0} + userData: diff --git a/Assets/UnityTestTools/Assertions/Comparers/IntComparer.cs b/Assets/UnityTestTools/Assertions/Comparers/IntComparer.cs new file mode 100644 index 00000000..25c43aad --- /dev/null +++ b/Assets/UnityTestTools/Assertions/Comparers/IntComparer.cs @@ -0,0 +1,41 @@ +using System; +using System.Collections.Generic; +using UnityEngine; + +namespace UnityTest +{ + public class IntComparer : ComparerBaseGeneric + { + public enum CompareType + { + Equal, + NotEqual, + Greater, + GreaterOrEqual, + Less, + LessOrEqual + }; + + public CompareType compareType; + + protected override bool Compare(int a, int b) + { + switch (compareType) + { + case CompareType.Equal: + return a == b; + case CompareType.NotEqual: + return a != b; + case CompareType.Greater: + return a > b; + case CompareType.GreaterOrEqual: + return a >= b; + case CompareType.Less: + return a < b; + case CompareType.LessOrEqual: + return a <= b; + } + throw new Exception(); + } + } +} diff --git a/Assets/UnityTestTools/Assertions/Comparers/IntComparer.cs.meta b/Assets/UnityTestTools/Assertions/Comparers/IntComparer.cs.meta new file mode 100644 index 00000000..64f4fc3e --- /dev/null +++ b/Assets/UnityTestTools/Assertions/Comparers/IntComparer.cs.meta @@ -0,0 +1,8 @@ +fileFormatVersion: 2 +guid: da4a3a521c5c1494aae123742ca5c8f5 +MonoImporter: + serializedVersion: 2 + defaultReferences: [] + executionOrder: 0 + icon: {instanceID: 0} + userData: diff --git a/Assets/UnityTestTools/Assertions/Comparers/IsRenderedByCamera.cs b/Assets/UnityTestTools/Assertions/Comparers/IsRenderedByCamera.cs new file mode 100644 index 00000000..bc5d370e --- /dev/null +++ b/Assets/UnityTestTools/Assertions/Comparers/IsRenderedByCamera.cs @@ -0,0 +1,31 @@ +using System; +using System.Collections.Generic; +using UnityEngine; + +namespace UnityTest +{ + public class IsRenderedByCamera : ComparerBaseGeneric + { + public enum CompareType + { + IsVisible, + IsNotVisible, + }; + + public CompareType compareType; + + protected override bool Compare(Renderer renderer, Camera camera) + { + var planes = GeometryUtility.CalculateFrustumPlanes(camera); + var isVisible = GeometryUtility.TestPlanesAABB(planes, renderer.bounds); + switch (compareType) + { + case CompareType.IsVisible: + return isVisible; + case CompareType.IsNotVisible: + return !isVisible; + } + throw new Exception(); + } + } +} diff --git a/Assets/UnityTestTools/Assertions/Comparers/IsRenderedByCamera.cs.meta b/Assets/UnityTestTools/Assertions/Comparers/IsRenderedByCamera.cs.meta new file mode 100644 index 00000000..9cfc1f22 --- /dev/null +++ b/Assets/UnityTestTools/Assertions/Comparers/IsRenderedByCamera.cs.meta @@ -0,0 +1,8 @@ +fileFormatVersion: 2 +guid: 8d45a1674f5e2e04485eafef922fac41 +MonoImporter: + serializedVersion: 2 + defaultReferences: [] + executionOrder: 0 + icon: {instanceID: 0} + userData: diff --git a/Assets/UnityTestTools/Assertions/Comparers/StringComparer.cs b/Assets/UnityTestTools/Assertions/Comparers/StringComparer.cs new file mode 100644 index 00000000..398f3e9f --- /dev/null +++ b/Assets/UnityTestTools/Assertions/Comparers/StringComparer.cs @@ -0,0 +1,42 @@ +using System; +using System.Collections.Generic; +using UnityEngine; + +namespace UnityTest +{ + public class StringComparer : ComparerBaseGeneric + { + public enum CompareType + { + Equal, + NotEqual, + Shorter, + Longer + } + + public CompareType compareType; + public StringComparison comparisonType = StringComparison.Ordinal; + public bool ignoreCase = false; + + protected override bool Compare(string a, string b) + { + if (ignoreCase) + { + a = a.ToLower(); + b = b.ToLower(); + } + switch (compareType) + { + case CompareType.Equal: + return String.Compare(a, b, comparisonType) == 0; + case CompareType.NotEqual: + return String.Compare(a, b, comparisonType) != 0; + case CompareType.Longer: + return String.Compare(a, b, comparisonType) > 0; + case CompareType.Shorter: + return String.Compare(a, b, comparisonType) < 0; + } + throw new Exception(); + } + } +} diff --git a/Assets/UnityTestTools/Assertions/Comparers/StringComparer.cs.meta b/Assets/UnityTestTools/Assertions/Comparers/StringComparer.cs.meta new file mode 100644 index 00000000..a414f61c --- /dev/null +++ b/Assets/UnityTestTools/Assertions/Comparers/StringComparer.cs.meta @@ -0,0 +1,8 @@ +fileFormatVersion: 2 +guid: 58783f051e477fd4e93b42ec7a43bb64 +MonoImporter: + serializedVersion: 2 + defaultReferences: [] + executionOrder: 0 + icon: {instanceID: 0} + userData: diff --git a/Assets/UnityTestTools/Assertions/Comparers/TransformComparer.cs b/Assets/UnityTestTools/Assertions/Comparers/TransformComparer.cs new file mode 100644 index 00000000..5221c032 --- /dev/null +++ b/Assets/UnityTestTools/Assertions/Comparers/TransformComparer.cs @@ -0,0 +1,26 @@ +using System; +using System.Collections.Generic; +using UnityEngine; + +namespace UnityTest +{ + public class TransformComparer : ComparerBaseGeneric + { + public enum CompareType { Equals, NotEquals } + + public CompareType compareType; + + protected override bool Compare(Transform a, Transform b) + { + if (compareType == CompareType.Equals) + { + return a.position == b.position; + } + if (compareType == CompareType.NotEquals) + { + return a.position != b.position; + } + throw new Exception(); + } + } +} diff --git a/Assets/UnityTestTools/Assertions/Comparers/TransformComparer.cs.meta b/Assets/UnityTestTools/Assertions/Comparers/TransformComparer.cs.meta new file mode 100644 index 00000000..f3d72e46 --- /dev/null +++ b/Assets/UnityTestTools/Assertions/Comparers/TransformComparer.cs.meta @@ -0,0 +1,8 @@ +fileFormatVersion: 2 +guid: 927f2d7e4f63632448b2a63d480e601a +MonoImporter: + serializedVersion: 2 + defaultReferences: [] + executionOrder: 0 + icon: {instanceID: 0} + userData: diff --git a/Assets/UnityTestTools/Assertions/Comparers/ValueDoesNotChange.cs b/Assets/UnityTestTools/Assertions/Comparers/ValueDoesNotChange.cs new file mode 100644 index 00000000..49a3cc76 --- /dev/null +++ b/Assets/UnityTestTools/Assertions/Comparers/ValueDoesNotChange.cs @@ -0,0 +1,20 @@ +using System; +using System.Collections.Generic; +using UnityEngine; + +namespace UnityTest +{ + public class ValueDoesNotChange : ActionBase + { + private object m_Value; + + protected override bool Compare(object a) + { + if (m_Value == null) + m_Value = a; + if (!m_Value.Equals(a)) + return false; + return true; + } + } +} diff --git a/Assets/UnityTestTools/Assertions/Comparers/ValueDoesNotChange.cs.meta b/Assets/UnityTestTools/Assertions/Comparers/ValueDoesNotChange.cs.meta new file mode 100644 index 00000000..b913d35d --- /dev/null +++ b/Assets/UnityTestTools/Assertions/Comparers/ValueDoesNotChange.cs.meta @@ -0,0 +1,8 @@ +fileFormatVersion: 2 +guid: 9d6d16a58a17940419a1dcbff3c60ca5 +MonoImporter: + serializedVersion: 2 + defaultReferences: [] + executionOrder: 0 + icon: {instanceID: 0} + userData: diff --git a/Assets/UnityTestTools/Assertions/Comparers/Vector2Comparer.cs b/Assets/UnityTestTools/Assertions/Comparers/Vector2Comparer.cs new file mode 100644 index 00000000..345d76d1 --- /dev/null +++ b/Assets/UnityTestTools/Assertions/Comparers/Vector2Comparer.cs @@ -0,0 +1,36 @@ +using System; +using System.Collections.Generic; +using UnityEngine; + +namespace UnityTest +{ + public class Vector2Comparer : VectorComparerBase + { + public enum CompareType + { + MagnitudeEquals, + MagnitudeNotEquals + } + + public CompareType compareType; + public float floatingPointError = 0.0001f; + + protected override bool Compare(Vector2 a, Vector2 b) + { + switch (compareType) + { + case CompareType.MagnitudeEquals: + return AreVectorMagnitudeEqual(a.magnitude, + b.magnitude, floatingPointError); + case CompareType.MagnitudeNotEquals: + return !AreVectorMagnitudeEqual(a.magnitude, + b.magnitude, floatingPointError); + } + throw new Exception(); + } + public override int GetDepthOfSearch() + { + return 3; + } + } +} diff --git a/Assets/UnityTestTools/Assertions/Comparers/Vector2Comparer.cs.meta b/Assets/UnityTestTools/Assertions/Comparers/Vector2Comparer.cs.meta new file mode 100644 index 00000000..19ef5d2e --- /dev/null +++ b/Assets/UnityTestTools/Assertions/Comparers/Vector2Comparer.cs.meta @@ -0,0 +1,8 @@ +fileFormatVersion: 2 +guid: a713db190443e814f8254a5a59014ec4 +MonoImporter: + serializedVersion: 2 + defaultReferences: [] + executionOrder: 0 + icon: {instanceID: 0} + userData: diff --git a/Assets/UnityTestTools/Assertions/Comparers/Vector3Comparer.cs b/Assets/UnityTestTools/Assertions/Comparers/Vector3Comparer.cs new file mode 100644 index 00000000..56f0b5b9 --- /dev/null +++ b/Assets/UnityTestTools/Assertions/Comparers/Vector3Comparer.cs @@ -0,0 +1,32 @@ +using System; +using System.Collections.Generic; +using UnityEngine; + +namespace UnityTest +{ + public class Vector3Comparer : VectorComparerBase + { + public enum CompareType + { + MagnitudeEquals, + MagnitudeNotEquals + } + + public CompareType compareType; + public double floatingPointError = 0.0001f; + + protected override bool Compare(Vector3 a, Vector3 b) + { + switch (compareType) + { + case CompareType.MagnitudeEquals: + return AreVectorMagnitudeEqual(a.magnitude, + b.magnitude, floatingPointError); + case CompareType.MagnitudeNotEquals: + return !AreVectorMagnitudeEqual(a.magnitude, + b.magnitude, floatingPointError); + } + throw new Exception(); + } + } +} diff --git a/Assets/UnityTestTools/Assertions/Comparers/Vector3Comparer.cs.meta b/Assets/UnityTestTools/Assertions/Comparers/Vector3Comparer.cs.meta new file mode 100644 index 00000000..b871f248 --- /dev/null +++ b/Assets/UnityTestTools/Assertions/Comparers/Vector3Comparer.cs.meta @@ -0,0 +1,8 @@ +fileFormatVersion: 2 +guid: 6febd2d5046657040b3da98b7010ee29 +MonoImporter: + serializedVersion: 2 + defaultReferences: [] + executionOrder: 0 + icon: {instanceID: 0} + userData: diff --git a/Assets/UnityTestTools/Assertions/Comparers/Vector4Comparer.cs b/Assets/UnityTestTools/Assertions/Comparers/Vector4Comparer.cs new file mode 100644 index 00000000..4eda0439 --- /dev/null +++ b/Assets/UnityTestTools/Assertions/Comparers/Vector4Comparer.cs @@ -0,0 +1,38 @@ +using System; +using System.Collections.Generic; +using UnityEngine; + +namespace UnityTest +{ + public class Vector4Comparer : VectorComparerBase + { + public enum CompareType + { + MagnitudeEquals, + MagnitudeNotEquals + } + + public CompareType compareType; + public double floatingPointError; + + protected override bool Compare(Vector4 a, Vector4 b) + { + switch (compareType) + { + case CompareType.MagnitudeEquals: + return AreVectorMagnitudeEqual(a.magnitude, + b.magnitude, + floatingPointError); + case CompareType.MagnitudeNotEquals: + return !AreVectorMagnitudeEqual(a.magnitude, + b.magnitude, + floatingPointError); + } + throw new Exception(); + } + public override int GetDepthOfSearch() + { + return 3; + } + } +} diff --git a/Assets/UnityTestTools/Assertions/Comparers/Vector4Comparer.cs.meta b/Assets/UnityTestTools/Assertions/Comparers/Vector4Comparer.cs.meta new file mode 100644 index 00000000..1e0314f2 --- /dev/null +++ b/Assets/UnityTestTools/Assertions/Comparers/Vector4Comparer.cs.meta @@ -0,0 +1,8 @@ +fileFormatVersion: 2 +guid: 383a85a79f164d04b8a56b0ff4e04cb7 +MonoImporter: + serializedVersion: 2 + defaultReferences: [] + executionOrder: 0 + icon: {instanceID: 0} + userData: diff --git a/Assets/UnityTestTools/Assertions/Comparers/VectorComparerBase.cs b/Assets/UnityTestTools/Assertions/Comparers/VectorComparerBase.cs new file mode 100644 index 00000000..cb394dfb --- /dev/null +++ b/Assets/UnityTestTools/Assertions/Comparers/VectorComparerBase.cs @@ -0,0 +1,18 @@ +using System; +using System.Collections.Generic; +using UnityEngine; + +namespace UnityTest +{ + public abstract class VectorComparerBase : ComparerBaseGeneric + { + protected bool AreVectorMagnitudeEqual(float a, float b, double floatingPointError) + { + if (Math.Abs(a) < floatingPointError && Math.Abs(b) < floatingPointError) + return true; + if (Math.Abs(a - b) < floatingPointError) + return true; + return false; + } + } +} diff --git a/Assets/UnityTestTools/Assertions/Comparers/VectorComparerBase.cs.meta b/Assets/UnityTestTools/Assertions/Comparers/VectorComparerBase.cs.meta new file mode 100644 index 00000000..d4da9f79 --- /dev/null +++ b/Assets/UnityTestTools/Assertions/Comparers/VectorComparerBase.cs.meta @@ -0,0 +1,8 @@ +fileFormatVersion: 2 +guid: 7b35a237804d5eb42bd8c4e67568ae24 +MonoImporter: + serializedVersion: 2 + defaultReferences: [] + executionOrder: 0 + icon: {instanceID: 0} + userData: diff --git a/Assets/UnityTestTools/Assertions/Editor.meta b/Assets/UnityTestTools/Assertions/Editor.meta new file mode 100644 index 00000000..2fa5238d --- /dev/null +++ b/Assets/UnityTestTools/Assertions/Editor.meta @@ -0,0 +1,5 @@ +fileFormatVersion: 2 +guid: a28bb39b4fb20514990895d9cb4eaea9 +folderAsset: yes +DefaultImporter: + userData: diff --git a/Assets/UnityTestTools/Assertions/Editor/AssertionComponentEditor.cs b/Assets/UnityTestTools/Assertions/Editor/AssertionComponentEditor.cs new file mode 100644 index 00000000..7ba8f869 --- /dev/null +++ b/Assets/UnityTestTools/Assertions/Editor/AssertionComponentEditor.cs @@ -0,0 +1,212 @@ +using System; +using System.Collections.Generic; +using System.Linq; +using System.Reflection; +using UnityEditor; +using UnityEngine; + +namespace UnityTest +{ + [CustomEditor(typeof(AssertionComponent))] + public class AssertionComponentEditor : Editor + { + private readonly DropDownControl m_ComparerDropDown = new DropDownControl(); + + private readonly PropertyPathSelector m_ThisPathSelector = new PropertyPathSelector("Compare"); + private readonly PropertyPathSelector m_OtherPathSelector = new PropertyPathSelector("Compare to"); + + #region GUI Contents + private readonly GUIContent m_GUICheckAfterTimeGuiContent = new GUIContent("Check after (seconds)", "After how many seconds the assertion should be checked"); + private readonly GUIContent m_GUIRepeatCheckTimeGuiContent = new GUIContent("Repeat check", "Should the check be repeated."); + private readonly GUIContent m_GUIRepeatEveryTimeGuiContent = new GUIContent("Frequency of repetitions", "How often should the check be done"); + private readonly GUIContent m_GUICheckAfterFramesGuiContent = new GUIContent("Check after (frames)", "After how many frames the assertion should be checked"); + private readonly GUIContent m_GUIRepeatCheckFrameGuiContent = new GUIContent("Repeat check", "Should the check be repeated."); + #endregion + + public AssertionComponentEditor() + { + m_ComparerDropDown.convertForButtonLabel = type => type.Name; + m_ComparerDropDown.convertForGUIContent = type => type.Name; + m_ComparerDropDown.ignoreConvertForGUIContent = types => false; + m_ComparerDropDown.tooltip = "Comparer that will be used to compare values and determine the result of assertion."; + } + + public override void OnInspectorGUI() + { + var script = (AssertionComponent)target; + EditorGUILayout.BeginHorizontal(); + var obj = DrawComparerSelection(script); + script.checkMethods = (CheckMethod)EditorGUILayout.EnumMaskField(script.checkMethods, + EditorStyles.popup, + GUILayout.ExpandWidth(false)); + EditorGUILayout.EndHorizontal(); + + if (script.IsCheckMethodSelected(CheckMethod.AfterPeriodOfTime)) + { + DrawOptionsForAfterPeriodOfTime(script); + } + + if (script.IsCheckMethodSelected(CheckMethod.Update)) + { + DrawOptionsForOnUpdate(script); + } + + if (obj) + { + EditorGUILayout.Space(); + + m_ThisPathSelector.Draw(script.Action.go, script.Action, + script.Action.thisPropertyPath, script.Action.GetAccepatbleTypesForA(), + go => + { + script.Action.go = go; + AssertionExplorerWindow.Reload(); + }, + s => + { + script.Action.thisPropertyPath = s; + AssertionExplorerWindow.Reload(); + }); + + EditorGUILayout.Space(); + + DrawCustomFields(script); + + EditorGUILayout.Space(); + + if (script.Action is ComparerBase) + { + DrawCompareToType(script.Action as ComparerBase); + } + } + } + + private void DrawOptionsForAfterPeriodOfTime(AssertionComponent script) + { + EditorGUILayout.Space(); + script.checkAfterTime = EditorGUILayout.FloatField(m_GUICheckAfterTimeGuiContent, + script.checkAfterTime); + if (script.checkAfterTime < 0) + script.checkAfterTime = 0; + script.repeatCheckTime = EditorGUILayout.Toggle(m_GUIRepeatCheckTimeGuiContent, + script.repeatCheckTime); + if (script.repeatCheckTime) + { + script.repeatEveryTime = EditorGUILayout.FloatField(m_GUIRepeatEveryTimeGuiContent, + script.repeatEveryTime); + if (script.repeatEveryTime < 0) + script.repeatEveryTime = 0; + } + } + + private void DrawOptionsForOnUpdate(AssertionComponent script) + { + EditorGUILayout.Space(); + script.checkAfterFrames = EditorGUILayout.IntField(m_GUICheckAfterFramesGuiContent, + script.checkAfterFrames); + if (script.checkAfterFrames < 1) + script.checkAfterFrames = 1; + script.repeatCheckFrame = EditorGUILayout.Toggle(m_GUIRepeatCheckFrameGuiContent, + script.repeatCheckFrame); + if (script.repeatCheckFrame) + { + script.repeatEveryFrame = EditorGUILayout.IntField(m_GUIRepeatEveryTimeGuiContent, + script.repeatEveryFrame); + if (script.repeatEveryFrame < 1) + script.repeatEveryFrame = 1; + } + } + + private void DrawCompareToType(ComparerBase comparer) + { + comparer.compareToType = (ComparerBase.CompareToType)EditorGUILayout.EnumPopup("Compare to type", + comparer.compareToType, + EditorStyles.popup); + + if (comparer.compareToType == ComparerBase.CompareToType.CompareToConstantValue) + { + try + { + DrawConstCompareField(comparer); + } + catch (NotImplementedException) + { + Debug.LogWarning("This comparer can't compare to static value"); + comparer.compareToType = ComparerBase.CompareToType.CompareToObject; + } + } + else if (comparer.compareToType == ComparerBase.CompareToType.CompareToObject) + { + DrawObjectCompareField(comparer); + } + } + + private void DrawObjectCompareField(ComparerBase comparer) + { + m_OtherPathSelector.Draw(comparer.other, comparer, + comparer.otherPropertyPath, comparer.GetAccepatbleTypesForB(), + go => + { + comparer.other = go; + AssertionExplorerWindow.Reload(); + }, + s => + { + comparer.otherPropertyPath = s; + AssertionExplorerWindow.Reload(); + } + ); + } + + private void DrawConstCompareField(ComparerBase comparer) + { + if (comparer.ConstValue == null) + { + comparer.ConstValue = comparer.GetDefaultConstValue(); + } + + var so = new SerializedObject(comparer); + var sp = so.FindProperty("constantValueGeneric"); + if (sp != null) + { + EditorGUILayout.PropertyField(sp, new GUIContent("Constant"), true); + so.ApplyModifiedProperties(); + } + } + + private bool DrawComparerSelection(AssertionComponent script) + { + var types = typeof(ActionBase).Assembly.GetTypes(); + var allComparers = types.Where(type => type.IsSubclassOf(typeof(ActionBase)) && !type.IsAbstract).ToArray(); + + if (script.Action == null) + script.Action = (ActionBase)CreateInstance(allComparers.First()); + + m_ComparerDropDown.Draw(script.Action.GetType(), allComparers, + type => + { + if (script.Action == null || script.Action.GetType().Name != type.Name) + { + script.Action = (ActionBase)CreateInstance(type); + AssertionExplorerWindow.Reload(); + } + }); + + return script.Action != null; + } + + private void DrawCustomFields(AssertionComponent script) + { + foreach (var prop in script.Action.GetType().GetFields(BindingFlags.Public | BindingFlags.Instance | BindingFlags.DeclaredOnly)) + { + var so = new SerializedObject(script.Action); + var sp = so.FindProperty(prop.Name); + if (sp != null) + { + EditorGUILayout.PropertyField(sp, true); + so.ApplyModifiedProperties(); + } + } + } + } +} diff --git a/Assets/UnityTestTools/Assertions/Editor/AssertionComponentEditor.cs.meta b/Assets/UnityTestTools/Assertions/Editor/AssertionComponentEditor.cs.meta new file mode 100644 index 00000000..eb4174d2 --- /dev/null +++ b/Assets/UnityTestTools/Assertions/Editor/AssertionComponentEditor.cs.meta @@ -0,0 +1,8 @@ +fileFormatVersion: 2 +guid: fd1cabf2c45d0a8489635607a6048621 +MonoImporter: + serializedVersion: 2 + defaultReferences: [] + executionOrder: 0 + icon: {instanceID: 0} + userData: diff --git a/Assets/UnityTestTools/Assertions/Editor/AssertionExplorerWindow.cs b/Assets/UnityTestTools/Assertions/Editor/AssertionExplorerWindow.cs new file mode 100644 index 00000000..ecd7fdaf --- /dev/null +++ b/Assets/UnityTestTools/Assertions/Editor/AssertionExplorerWindow.cs @@ -0,0 +1,194 @@ +using System; +using System.Collections.Generic; +using System.Linq; +using UnityEditor; +using UnityEngine; + +#if UNITY_METRO +#warning Assertion component is not supported on Windows Store apps +#endif + +namespace UnityTest +{ + [Serializable] + public class AssertionExplorerWindow : EditorWindow + { + private List m_AllAssertions = new List(); + [SerializeField] + private string m_FilterText = ""; + [SerializeField] + private FilterType m_FilterType; + [SerializeField] + private List m_FoldMarkers = new List(); + [SerializeField] + private GroupByType m_GroupBy; + [SerializeField] + private Vector2 m_ScrollPosition = Vector2.zero; + private DateTime m_NextReload = DateTime.Now; + [SerializeField] + private static bool s_ShouldReload; + [SerializeField] + private ShowType m_ShowType; + + public AssertionExplorerWindow() + { + titleContent = new GUIContent("Assertion Explorer"); + } + + public void OnDidOpenScene() + { + ReloadAssertionList(); + } + + public void OnFocus() + { + ReloadAssertionList(); + } + + private void ReloadAssertionList() + { + m_NextReload = DateTime.Now.AddSeconds(1); + s_ShouldReload = true; + } + + public void OnHierarchyChange() + { + ReloadAssertionList(); + } + + public void OnInspectorUpdate() + { + if (s_ShouldReload && m_NextReload < DateTime.Now) + { + s_ShouldReload = false; + m_AllAssertions = new List((AssertionComponent[])Resources.FindObjectsOfTypeAll(typeof(AssertionComponent))); + Repaint(); + } + } + + public void OnGUI() + { + DrawMenuPanel(); + + m_ScrollPosition = EditorGUILayout.BeginScrollView(m_ScrollPosition); + if (m_AllAssertions != null) + GetResultRendere().Render(FilterResults(m_AllAssertions, m_FilterText.ToLower()), m_FoldMarkers); + EditorGUILayout.EndScrollView(); + } + + private IEnumerable FilterResults(List assertionComponents, string text) + { + if (m_ShowType == ShowType.ShowDisabled) + assertionComponents = assertionComponents.Where(c => !c.enabled).ToList(); + else if (m_ShowType == ShowType.ShowEnabled) + assertionComponents = assertionComponents.Where(c => c.enabled).ToList(); + + if (string.IsNullOrEmpty(text)) + return assertionComponents; + + switch (m_FilterType) + { + case FilterType.ComparerName: + return assertionComponents.Where(c => c.Action.GetType().Name.ToLower().Contains(text)); + case FilterType.AttachedGameObject: + return assertionComponents.Where(c => c.gameObject.name.ToLower().Contains(text)); + case FilterType.FirstComparedGameObjectPath: + return assertionComponents.Where(c => c.Action.thisPropertyPath.ToLower().Contains(text)); + case FilterType.FirstComparedGameObject: + return assertionComponents.Where(c => c.Action.go != null + && c.Action.go.name.ToLower().Contains(text)); + case FilterType.SecondComparedGameObjectPath: + return assertionComponents.Where(c => + c.Action is ComparerBase + && (c.Action as ComparerBase).otherPropertyPath.ToLower().Contains(text)); + case FilterType.SecondComparedGameObject: + return assertionComponents.Where(c => + c.Action is ComparerBase + && (c.Action as ComparerBase).other != null + && (c.Action as ComparerBase).other.name.ToLower().Contains(text)); + default: + return assertionComponents; + } + } + + private readonly IListRenderer m_GroupByComparerRenderer = new GroupByComparerRenderer(); + private readonly IListRenderer m_GroupByExecutionMethodRenderer = new GroupByExecutionMethodRenderer(); + private readonly IListRenderer m_GroupByGoRenderer = new GroupByGoRenderer(); + private readonly IListRenderer m_GroupByTestsRenderer = new GroupByTestsRenderer(); + private readonly IListRenderer m_GroupByNothingRenderer = new GroupByNothingRenderer(); + + private IListRenderer GetResultRendere() + { + switch (m_GroupBy) + { + case GroupByType.Comparer: + return m_GroupByComparerRenderer; + case GroupByType.ExecutionMethod: + return m_GroupByExecutionMethodRenderer; + case GroupByType.GameObjects: + return m_GroupByGoRenderer; + case GroupByType.Tests: + return m_GroupByTestsRenderer; + default: + return m_GroupByNothingRenderer; + } + } + + private void DrawMenuPanel() + { + EditorGUILayout.BeginHorizontal(EditorStyles.toolbar); + EditorGUILayout.LabelField("Group by:", Styles.toolbarLabel, GUILayout.MaxWidth(60)); + m_GroupBy = (GroupByType)EditorGUILayout.EnumPopup(m_GroupBy, EditorStyles.toolbarPopup, GUILayout.MaxWidth(150)); + + GUILayout.FlexibleSpace(); + + m_ShowType = (ShowType)EditorGUILayout.EnumPopup(m_ShowType, EditorStyles.toolbarPopup, GUILayout.MaxWidth(100)); + + EditorGUILayout.LabelField("Filter by:", Styles.toolbarLabel, GUILayout.MaxWidth(50)); + m_FilterType = (FilterType)EditorGUILayout.EnumPopup(m_FilterType, EditorStyles.toolbarPopup, GUILayout.MaxWidth(100)); + m_FilterText = GUILayout.TextField(m_FilterText, "ToolbarSeachTextField", GUILayout.MaxWidth(100)); + if (GUILayout.Button(GUIContent.none, string.IsNullOrEmpty(m_FilterText) ? "ToolbarSeachCancelButtonEmpty" : "ToolbarSeachCancelButton", GUILayout.ExpandWidth(false))) + m_FilterText = ""; + EditorGUILayout.EndHorizontal(); + } + + [MenuItem("Unity Test Tools/Assertion Explorer")] + public static AssertionExplorerWindow ShowWindow() + { + var w = GetWindow(typeof(AssertionExplorerWindow)); + w.Show(); + return w as AssertionExplorerWindow; + } + + private enum FilterType + { + ComparerName, + FirstComparedGameObject, + FirstComparedGameObjectPath, + SecondComparedGameObject, + SecondComparedGameObjectPath, + AttachedGameObject + } + + private enum ShowType + { + ShowAll, + ShowEnabled, + ShowDisabled + } + + private enum GroupByType + { + Nothing, + Comparer, + GameObjects, + ExecutionMethod, + Tests + } + + public static void Reload() + { + s_ShouldReload = true; + } + } +} diff --git a/Assets/UnityTestTools/Assertions/Editor/AssertionExplorerWindow.cs.meta b/Assets/UnityTestTools/Assertions/Editor/AssertionExplorerWindow.cs.meta new file mode 100644 index 00000000..f5591ab4 --- /dev/null +++ b/Assets/UnityTestTools/Assertions/Editor/AssertionExplorerWindow.cs.meta @@ -0,0 +1,8 @@ +fileFormatVersion: 2 +guid: 1a1e855053e7e2f46ace1dc93f2036f2 +MonoImporter: + serializedVersion: 2 + defaultReferences: [] + executionOrder: 0 + icon: {instanceID: 0} + userData: diff --git a/Assets/UnityTestTools/Assertions/Editor/AssertionListRenderer.cs b/Assets/UnityTestTools/Assertions/Editor/AssertionListRenderer.cs new file mode 100644 index 00000000..31e1b1e5 --- /dev/null +++ b/Assets/UnityTestTools/Assertions/Editor/AssertionListRenderer.cs @@ -0,0 +1,251 @@ +using System; +using System.Collections.Generic; +using System.Linq; +using UnityEditor; +using UnityEngine; + +namespace UnityTest +{ + public interface IListRenderer + { + void Render(IEnumerable allAssertions, List foldMarkers); + } + + public abstract class AssertionListRenderer : IListRenderer + { + private static class Styles + { + public static readonly GUIStyle redLabel; + static Styles() + { + redLabel = new GUIStyle(EditorStyles.label); + redLabel.normal.textColor = Color.red; + } + } + + public void Render(IEnumerable allAssertions, List foldMarkers) + { + foreach (var grouping in GroupResult(allAssertions)) + { + var key = GetStringKey(grouping.Key); + bool isFolded = foldMarkers.Contains(key); + if (key != "") + { + EditorGUILayout.BeginHorizontal(); + + EditorGUI.BeginChangeCheck(); + isFolded = PrintFoldout(isFolded, + grouping.Key); + if (EditorGUI.EndChangeCheck()) + { + if (isFolded) + foldMarkers.Add(key); + else + foldMarkers.Remove(key); + } + EditorGUILayout.EndHorizontal(); + if (isFolded) + continue; + } + foreach (var assertionComponent in grouping) + { + EditorGUILayout.BeginVertical(); + EditorGUILayout.BeginHorizontal(); + + if (key != "") + GUILayout.Space(15); + + var assertionKey = assertionComponent.GetHashCode().ToString(); + bool isDetailsFolded = foldMarkers.Contains(assertionKey); + + EditorGUI.BeginChangeCheck(); + if (GUILayout.Button("", + EditorStyles.foldout, + GUILayout.Width(15))) + { + isDetailsFolded = !isDetailsFolded; + } + if (EditorGUI.EndChangeCheck()) + { + if (isDetailsFolded) + foldMarkers.Add(assertionKey); + else + foldMarkers.Remove(assertionKey); + } + PrintFoldedAssertionLine(assertionComponent); + EditorGUILayout.EndHorizontal(); + + if (isDetailsFolded) + { + EditorGUILayout.BeginHorizontal(); + if (key != "") + GUILayout.Space(15); + PrintAssertionLineDetails(assertionComponent); + EditorGUILayout.EndHorizontal(); + } + GUILayout.Box("", new[] {GUILayout.ExpandWidth(true), GUILayout.Height(1)}); + + EditorGUILayout.EndVertical(); + } + } + } + + protected abstract IEnumerable> GroupResult(IEnumerable assertionComponents); + + protected virtual string GetStringKey(T key) + { + return key.GetHashCode().ToString(); + } + + protected virtual bool PrintFoldout(bool isFolded, T key) + { + var content = new GUIContent(GetFoldoutDisplayName(key)); + var size = EditorStyles.foldout.CalcSize(content); + + var rect = GUILayoutUtility.GetRect(content, + EditorStyles.foldout, + GUILayout.MaxWidth(size.x)); + var res = EditorGUI.Foldout(rect, + !isFolded, + content, + true); + + return !res; + } + + protected virtual string GetFoldoutDisplayName(T key) + { + return key.ToString(); + } + + protected virtual void PrintFoldedAssertionLine(AssertionComponent assertionComponent) + { + EditorGUILayout.BeginHorizontal(); + + EditorGUILayout.BeginVertical(GUILayout.MaxWidth(300)); + EditorGUILayout.BeginHorizontal(GUILayout.MaxWidth(300)); + PrintPath(assertionComponent.Action.go, + assertionComponent.Action.thisPropertyPath); + EditorGUILayout.EndHorizontal(); + EditorGUILayout.EndVertical(); + + EditorGUILayout.BeginVertical(GUILayout.MaxWidth(250)); + var labelStr = assertionComponent.Action.GetType().Name; + var labelStr2 = assertionComponent.Action.GetConfigurationDescription(); + if (labelStr2 != "") + labelStr += "( " + labelStr2 + ")"; + EditorGUILayout.LabelField(labelStr); + EditorGUILayout.EndVertical(); + + if (assertionComponent.Action is ComparerBase) + { + var comparer = assertionComponent.Action as ComparerBase; + + var otherStrVal = "(no value selected)"; + EditorGUILayout.BeginVertical(); + EditorGUILayout.BeginHorizontal(GUILayout.MaxWidth(300)); + switch (comparer.compareToType) + { + case ComparerBase.CompareToType.CompareToObject: + if (comparer.other != null) + { + PrintPath(comparer.other, + comparer.otherPropertyPath); + } + else + { + EditorGUILayout.LabelField(otherStrVal, + Styles.redLabel); + } + break; + case ComparerBase.CompareToType.CompareToConstantValue: + otherStrVal = comparer.ConstValue.ToString(); + EditorGUILayout.LabelField(otherStrVal); + break; + case ComparerBase.CompareToType.CompareToNull: + otherStrVal = "null"; + EditorGUILayout.LabelField(otherStrVal); + break; + } + EditorGUILayout.EndHorizontal(); + EditorGUILayout.EndVertical(); + } + else + { + EditorGUILayout.LabelField(""); + } + EditorGUILayout.EndHorizontal(); + EditorGUILayout.Space(); + } + + protected virtual void PrintAssertionLineDetails(AssertionComponent assertionComponent) + { + EditorGUILayout.BeginHorizontal(); + + + EditorGUILayout.BeginVertical(GUILayout.MaxWidth(320)); + EditorGUILayout.BeginHorizontal(); + EditorGUILayout.LabelField("Attached to", + GUILayout.Width(70)); + var sss = EditorStyles.objectField.CalcSize(new GUIContent(assertionComponent.gameObject.name)); + EditorGUILayout.ObjectField(assertionComponent.gameObject, + typeof(GameObject), + true, + GUILayout.Width(sss.x)); + EditorGUILayout.EndHorizontal(); + EditorGUILayout.EndVertical(); + + + EditorGUILayout.BeginVertical(GUILayout.MaxWidth(250)); + EditorGUILayout.EnumMaskField(assertionComponent.checkMethods, + EditorStyles.popup, + GUILayout.MaxWidth(150)); + EditorGUILayout.EndVertical(); + + + EditorGUILayout.BeginVertical(); + EditorGUILayout.BeginHorizontal(); + EditorGUILayout.LabelField("Disabled", + GUILayout.Width(55)); + assertionComponent.enabled = !EditorGUILayout.Toggle(!assertionComponent.enabled, + GUILayout.Width(15)); + EditorGUILayout.EndHorizontal(); + EditorGUILayout.EndVertical(); + + EditorGUILayout.EndHorizontal(); + } + + private void PrintPath(GameObject go, string propertyPath) + { + string contentString = ""; + GUIStyle styleThisPath = EditorStyles.label; + if (go != null) + { + var sss = EditorStyles.objectField.CalcSize(new GUIContent(go.name)); + EditorGUILayout.ObjectField( + go, + typeof(GameObject), + true, + GUILayout.Width(sss.x)); + + if (!string.IsNullOrEmpty(propertyPath)) + contentString = "." + propertyPath; + } + else + { + contentString = "(no value selected)"; + styleThisPath = Styles.redLabel; + } + + var content = new GUIContent(contentString, + contentString); + var rect = GUILayoutUtility.GetRect(content, + EditorStyles.label, + GUILayout.MaxWidth(200)); + + EditorGUI.LabelField(rect, + content, + styleThisPath); + } + } +} diff --git a/Assets/UnityTestTools/Assertions/Editor/AssertionListRenderer.cs.meta b/Assets/UnityTestTools/Assertions/Editor/AssertionListRenderer.cs.meta new file mode 100644 index 00000000..8e6a0d45 --- /dev/null +++ b/Assets/UnityTestTools/Assertions/Editor/AssertionListRenderer.cs.meta @@ -0,0 +1,8 @@ +fileFormatVersion: 2 +guid: d83c02fb0f220344da42a8213ed36cb5 +MonoImporter: + serializedVersion: 2 + defaultReferences: [] + executionOrder: 0 + icon: {instanceID: 0} + userData: diff --git a/Assets/UnityTestTools/Assertions/Editor/AssertionStripper.cs b/Assets/UnityTestTools/Assertions/Editor/AssertionStripper.cs new file mode 100644 index 00000000..1b6bd04d --- /dev/null +++ b/Assets/UnityTestTools/Assertions/Editor/AssertionStripper.cs @@ -0,0 +1,25 @@ +using System; +using System.Collections.Generic; +using UnityEditor.Callbacks; +using UnityEngine; +using UnityTest; +using Object = UnityEngine.Object; + +public class AssertionStripper +{ + [PostProcessScene] + public static void OnPostprocessScene() + { + if (Debug.isDebugBuild) return; + RemoveAssertionsFromGameObjects(); + } + + private static void RemoveAssertionsFromGameObjects() + { + var allAssertions = Resources.FindObjectsOfTypeAll(typeof(AssertionComponent)) as AssertionComponent[]; + foreach (var assertion in allAssertions) + { + Object.DestroyImmediate(assertion); + } + } +} diff --git a/Assets/UnityTestTools/Assertions/Editor/AssertionStripper.cs.meta b/Assets/UnityTestTools/Assertions/Editor/AssertionStripper.cs.meta new file mode 100644 index 00000000..bf18bbed --- /dev/null +++ b/Assets/UnityTestTools/Assertions/Editor/AssertionStripper.cs.meta @@ -0,0 +1,8 @@ +fileFormatVersion: 2 +guid: 95c9cd9570a6fba4198b6e4f15e11e5e +MonoImporter: + serializedVersion: 2 + defaultReferences: [] + executionOrder: 0 + icon: {instanceID: 0} + userData: diff --git a/Assets/UnityTestTools/Assertions/Editor/DropDownControl.cs b/Assets/UnityTestTools/Assertions/Editor/DropDownControl.cs new file mode 100644 index 00000000..79804f95 --- /dev/null +++ b/Assets/UnityTestTools/Assertions/Editor/DropDownControl.cs @@ -0,0 +1,78 @@ +using System; +using System.Collections.Generic; +using UnityEditor; +using UnityEngine; + +namespace UnityTest +{ + [Serializable] + internal class DropDownControl + { + private readonly GUILayoutOption[] m_ButtonLayoutOptions = { GUILayout.ExpandWidth(true) }; + public Func convertForButtonLabel = s => s.ToString(); + public Func convertForGUIContent = s => s.ToString(); + public Func ignoreConvertForGUIContent = t => t.Length <= 40; + public Action printContextMenu = null; + public string tooltip = ""; + + private object m_SelectedValue; + + + public void Draw(T selected, T[] options, Action onValueSelected) + { + Draw(null, + selected, + options, + onValueSelected); + } + + public void Draw(string label, T selected, T[] options, Action onValueSelected) + { + Draw(label, selected, () => options, onValueSelected); + } + + public void Draw(string label, T selected, Func loadOptions, Action onValueSelected) + { + if (!string.IsNullOrEmpty(label)) + EditorGUILayout.BeginHorizontal(); + var guiContent = new GUIContent(label); + var labelSize = EditorStyles.label.CalcSize(guiContent); + + if (!string.IsNullOrEmpty(label)) + GUILayout.Label(label, EditorStyles.label, GUILayout.Width(labelSize.x)); + + if (GUILayout.Button(new GUIContent(convertForButtonLabel(selected), tooltip), + EditorStyles.popup, m_ButtonLayoutOptions)) + { + if (Event.current.button == 0) + { + PrintMenu(loadOptions()); + } + else if (printContextMenu != null && Event.current.button == 1) + printContextMenu(selected); + } + + if (m_SelectedValue != null) + { + onValueSelected((T)m_SelectedValue); + m_SelectedValue = null; + } + if (!string.IsNullOrEmpty(label)) + EditorGUILayout.EndHorizontal(); + } + + public void PrintMenu(T[] options) + { + var menu = new GenericMenu(); + foreach (var s in options) + { + var localS = s; + menu.AddItem(new GUIContent((ignoreConvertForGUIContent(options) ? localS.ToString() : convertForGUIContent(localS))), + false, + () => { m_SelectedValue = localS; } + ); + } + menu.ShowAsContext(); + } + } +} diff --git a/Assets/UnityTestTools/Assertions/Editor/DropDownControl.cs.meta b/Assets/UnityTestTools/Assertions/Editor/DropDownControl.cs.meta new file mode 100644 index 00000000..424d2437 --- /dev/null +++ b/Assets/UnityTestTools/Assertions/Editor/DropDownControl.cs.meta @@ -0,0 +1,8 @@ +fileFormatVersion: 2 +guid: 83ec3ed09f8f2f34ea7483e055f6d76d +MonoImporter: + serializedVersion: 2 + defaultReferences: [] + executionOrder: 0 + icon: {instanceID: 0} + userData: diff --git a/Assets/UnityTestTools/Assertions/Editor/GroupByComparerRenderer.cs b/Assets/UnityTestTools/Assertions/Editor/GroupByComparerRenderer.cs new file mode 100644 index 00000000..6d7875bc --- /dev/null +++ b/Assets/UnityTestTools/Assertions/Editor/GroupByComparerRenderer.cs @@ -0,0 +1,20 @@ +using System; +using System.Collections.Generic; +using System.Linq; +using UnityEngine; + +namespace UnityTest +{ + public class GroupByComparerRenderer : AssertionListRenderer + { + protected override IEnumerable> GroupResult(IEnumerable assertionComponents) + { + return assertionComponents.GroupBy(c => c.Action.GetType()); + } + + protected override string GetStringKey(Type key) + { + return key.Name; + } + } +} diff --git a/Assets/UnityTestTools/Assertions/Editor/GroupByComparerRenderer.cs.meta b/Assets/UnityTestTools/Assertions/Editor/GroupByComparerRenderer.cs.meta new file mode 100644 index 00000000..e9173993 --- /dev/null +++ b/Assets/UnityTestTools/Assertions/Editor/GroupByComparerRenderer.cs.meta @@ -0,0 +1,8 @@ +fileFormatVersion: 2 +guid: efab536803bd0154a8a7dc78e8767ad9 +MonoImporter: + serializedVersion: 2 + defaultReferences: [] + executionOrder: 0 + icon: {instanceID: 0} + userData: diff --git a/Assets/UnityTestTools/Assertions/Editor/GroupByExecutionMethodRenderer.cs b/Assets/UnityTestTools/Assertions/Editor/GroupByExecutionMethodRenderer.cs new file mode 100644 index 00000000..b4b6d3fc --- /dev/null +++ b/Assets/UnityTestTools/Assertions/Editor/GroupByExecutionMethodRenderer.cs @@ -0,0 +1,31 @@ +using System; +using System.Collections.Generic; +using System.Linq; +using UnityEngine; + +namespace UnityTest +{ + public class GroupByExecutionMethodRenderer : AssertionListRenderer + { + protected override IEnumerable> GroupResult(IEnumerable assertionComponents) + { + var enumVals = Enum.GetValues(typeof(CheckMethod)).Cast(); + var pairs = new List(); + + foreach (var checkMethod in enumVals) + { + var components = assertionComponents.Where(c => (c.checkMethods & checkMethod) == checkMethod); + var componentPairs = components.Select(a => new CheckFunctionAssertionPair {checkMethod = checkMethod, assertionComponent = a}); + pairs.AddRange(componentPairs); + } + return pairs.GroupBy(pair => pair.checkMethod, + pair => pair.assertionComponent); + } + + private class CheckFunctionAssertionPair + { + public AssertionComponent assertionComponent; + public CheckMethod checkMethod; + } + } +} diff --git a/Assets/UnityTestTools/Assertions/Editor/GroupByExecutionMethodRenderer.cs.meta b/Assets/UnityTestTools/Assertions/Editor/GroupByExecutionMethodRenderer.cs.meta new file mode 100644 index 00000000..e542ae1d --- /dev/null +++ b/Assets/UnityTestTools/Assertions/Editor/GroupByExecutionMethodRenderer.cs.meta @@ -0,0 +1,8 @@ +fileFormatVersion: 2 +guid: 97340abf816b1424fa835a4f26bbdc78 +MonoImporter: + serializedVersion: 2 + defaultReferences: [] + executionOrder: 0 + icon: {instanceID: 0} + userData: diff --git a/Assets/UnityTestTools/Assertions/Editor/GroupByGORenderer.cs b/Assets/UnityTestTools/Assertions/Editor/GroupByGORenderer.cs new file mode 100644 index 00000000..6d76ca51 --- /dev/null +++ b/Assets/UnityTestTools/Assertions/Editor/GroupByGORenderer.cs @@ -0,0 +1,34 @@ +using System; +using System.Collections.Generic; +using System.Linq; +using UnityEditor; +using UnityEngine; + +namespace UnityTest +{ + public class GroupByGoRenderer : AssertionListRenderer + { + protected override IEnumerable> GroupResult(IEnumerable assertionComponents) + { + return assertionComponents.GroupBy(c => c.gameObject); + } + + protected override bool PrintFoldout(bool isFolded, GameObject key) + { + isFolded = base.PrintFoldout(isFolded, + key); + + EditorGUILayout.ObjectField(key, + typeof(GameObject), + true, + GUILayout.ExpandWidth(false)); + + return isFolded; + } + + protected override string GetFoldoutDisplayName(GameObject key) + { + return key.name; + } + } +} diff --git a/Assets/UnityTestTools/Assertions/Editor/GroupByGORenderer.cs.meta b/Assets/UnityTestTools/Assertions/Editor/GroupByGORenderer.cs.meta new file mode 100644 index 00000000..a11d1dca --- /dev/null +++ b/Assets/UnityTestTools/Assertions/Editor/GroupByGORenderer.cs.meta @@ -0,0 +1,8 @@ +fileFormatVersion: 2 +guid: cb824de9146b42343a985aaf63beffd1 +MonoImporter: + serializedVersion: 2 + defaultReferences: [] + executionOrder: 0 + icon: {instanceID: 0} + userData: diff --git a/Assets/UnityTestTools/Assertions/Editor/GroupByNothingRenderer.cs b/Assets/UnityTestTools/Assertions/Editor/GroupByNothingRenderer.cs new file mode 100644 index 00000000..db5d824a --- /dev/null +++ b/Assets/UnityTestTools/Assertions/Editor/GroupByNothingRenderer.cs @@ -0,0 +1,20 @@ +using System; +using System.Collections.Generic; +using System.Linq; +using UnityEngine; + +namespace UnityTest +{ + public class GroupByNothingRenderer : AssertionListRenderer + { + protected override IEnumerable> GroupResult(IEnumerable assertionComponents) + { + return assertionComponents.GroupBy(c => ""); + } + + protected override string GetStringKey(string key) + { + return ""; + } + } +} diff --git a/Assets/UnityTestTools/Assertions/Editor/GroupByNothingRenderer.cs.meta b/Assets/UnityTestTools/Assertions/Editor/GroupByNothingRenderer.cs.meta new file mode 100644 index 00000000..f7d9d2ac --- /dev/null +++ b/Assets/UnityTestTools/Assertions/Editor/GroupByNothingRenderer.cs.meta @@ -0,0 +1,8 @@ +fileFormatVersion: 2 +guid: 33bf96aa461ea1d478bb757c52f51c95 +MonoImporter: + serializedVersion: 2 + defaultReferences: [] + executionOrder: 0 + icon: {instanceID: 0} + userData: diff --git a/Assets/UnityTestTools/Assertions/Editor/GroupByTestsRenderer.cs b/Assets/UnityTestTools/Assertions/Editor/GroupByTestsRenderer.cs new file mode 100644 index 00000000..a126a513 --- /dev/null +++ b/Assets/UnityTestTools/Assertions/Editor/GroupByTestsRenderer.cs @@ -0,0 +1,29 @@ +using System; +using System.Collections.Generic; +using System.Linq; +using UnityEngine; + +namespace UnityTest +{ + public class GroupByTestsRenderer : AssertionListRenderer + { + protected override IEnumerable> GroupResult(IEnumerable assertionComponents) + { + return assertionComponents.GroupBy(c => + { + var temp = c.transform; + while (temp != null) + { + if (temp.GetComponent("TestComponent") != null) return c.gameObject; + temp = temp.parent.transform; + } + return null; + }); + } + + protected override string GetFoldoutDisplayName(GameObject key) + { + return key.name; + } + } +} diff --git a/Assets/UnityTestTools/Assertions/Editor/GroupByTestsRenderer.cs.meta b/Assets/UnityTestTools/Assertions/Editor/GroupByTestsRenderer.cs.meta new file mode 100644 index 00000000..cbc31246 --- /dev/null +++ b/Assets/UnityTestTools/Assertions/Editor/GroupByTestsRenderer.cs.meta @@ -0,0 +1,8 @@ +fileFormatVersion: 2 +guid: 5e577f31e55208b4d8a1774b958e6ed5 +MonoImporter: + serializedVersion: 2 + defaultReferences: [] + executionOrder: 0 + icon: {instanceID: 0} + userData: diff --git a/Assets/UnityTestTools/Assertions/Editor/PropertyPathSelector.cs b/Assets/UnityTestTools/Assertions/Editor/PropertyPathSelector.cs new file mode 100644 index 00000000..3bf3911b --- /dev/null +++ b/Assets/UnityTestTools/Assertions/Editor/PropertyPathSelector.cs @@ -0,0 +1,207 @@ +using System; +using System.Collections.Generic; +using System.Linq; +using System.Reflection; +using UnityEditor; +using UnityEngine; + +namespace UnityTest +{ + public class PropertyPathSelector + { + private readonly DropDownControl m_ThisDropDown = new DropDownControl(); + private readonly Func m_ReplaceDotWithSlashAndAddGoGroup = s => s.Replace('.', '/'); + + private readonly string m_Name; + private bool m_FocusBackToEdit; + private SelectedPathError m_Error; + + public PropertyPathSelector(string name) + { + m_Name = name; + m_ThisDropDown.convertForGUIContent = m_ReplaceDotWithSlashAndAddGoGroup; + m_ThisDropDown.tooltip = "Select the path to the value you want to use for comparison."; + } + + public void Draw(GameObject go, ActionBase comparer, string propertPath, Type[] accepatbleTypes, Action onSelectedGo, Action onSelectedPath) + { + var newGo = (GameObject)EditorGUILayout.ObjectField(m_Name, go, typeof(GameObject), true); + if (newGo != go) + onSelectedGo(newGo); + + if (go != null) + { + var newPath = DrawListOfMethods(go, comparer, propertPath, accepatbleTypes, m_ThisDropDown); + + if (newPath != propertPath) + onSelectedPath(newPath); + } + } + + private string DrawListOfMethods(GameObject go, ActionBase comparer, string propertPath, Type[] accepatbleTypes, DropDownControl dropDown) + { + string result = propertPath; + if (accepatbleTypes == null) + { + result = DrawManualPropertyEditField(go, propertPath, accepatbleTypes, dropDown); + } + else + { + bool isPropertyOrFieldFound = true; + if (string.IsNullOrEmpty(result)) + { + var options = GetFieldsAndProperties(go, comparer, result, accepatbleTypes); + isPropertyOrFieldFound = options.Any(); + if (isPropertyOrFieldFound) + { + result = options.First(); + } + } + + if (isPropertyOrFieldFound) + { + dropDown.Draw(go.name + '.', result, + () => + { + try + { + var options = GetFieldsAndProperties(go, comparer, result, accepatbleTypes); + return options.ToArray(); + } + catch (Exception) + { + Debug.LogWarning("An exception was thrown while resolving a property list. Resetting property path."); + result = ""; + return new string[0]; + } + }, s => result = s); + } + else + { + result = DrawManualPropertyEditField(go, propertPath, accepatbleTypes, dropDown); + } + } + return result; + } + + private static List GetFieldsAndProperties(GameObject go, ActionBase comparer, string extendPath, Type[] accepatbleTypes) + { + var propertyResolver = new PropertyResolver {AllowedTypes = accepatbleTypes, ExcludedFieldNames = comparer.GetExcludedFieldNames()}; + var options = propertyResolver.GetFieldsAndPropertiesFromGameObject(go, comparer.GetDepthOfSearch(), extendPath).ToList(); + options.Sort((x, y) => + { + if (char.IsLower(x[0])) + return -1; + if (char.IsLower(y[0])) + return 1; + return x.CompareTo(y); + }); + return options; + } + + private string DrawManualPropertyEditField(GameObject go, string propertPath, Type[] acceptableTypes, DropDownControl dropDown) + { + var propertyResolver = new PropertyResolver { AllowedTypes = acceptableTypes }; + IList list; + + var loadProps = new Func(() => + { + try + { + list = propertyResolver.GetFieldsAndPropertiesUnderPath(go, propertPath); + } + catch (ArgumentException) + { + list = propertyResolver.GetFieldsAndPropertiesUnderPath(go, ""); + } + return list.ToArray(); + }); + + EditorGUILayout.BeginHorizontal(); + + var labelSize = EditorStyles.label.CalcSize(new GUIContent(go.name + '.')); + GUILayout.Label(go.name + (propertPath.Length > 0 ? "." : ""), EditorStyles.label, GUILayout.Width(labelSize.x)); + + string btnName = "hintBtn"; + if (GUI.GetNameOfFocusedControl() == btnName + && Event.current.type == EventType.KeyDown + && Event.current.keyCode == KeyCode.DownArrow) + { + Event.current.Use(); + dropDown.PrintMenu(loadProps()); + GUI.FocusControl(""); + m_FocusBackToEdit = true; + } + + EditorGUI.BeginChangeCheck(); + GUI.SetNextControlName(btnName); + var result = GUILayout.TextField(propertPath, EditorStyles.textField); + if (EditorGUI.EndChangeCheck()) + { + m_Error = DoesPropertyExist(go, result); + } + + if (m_FocusBackToEdit) + { + m_FocusBackToEdit = false; + GUI.FocusControl(btnName); + } + + if (GUILayout.Button("Clear", EditorStyles.miniButton, GUILayout.Width(38))) + { + result = ""; + GUI.FocusControl(null); + m_FocusBackToEdit = true; + m_Error = DoesPropertyExist(go, result); + } + EditorGUILayout.EndHorizontal(); + EditorGUILayout.BeginHorizontal(); + GUILayout.Label("", GUILayout.Width(labelSize.x)); + + dropDown.Draw("", result ?? "", loadProps, s => + { + result = s; + GUI.FocusControl(null); + m_FocusBackToEdit = true; + m_Error = DoesPropertyExist(go, result); + }); + EditorGUILayout.EndHorizontal(); + + switch (m_Error) + { + case SelectedPathError.InvalidPath: + EditorGUILayout.HelpBox("This property does not exist", MessageType.Error); + break; + case SelectedPathError.MissingComponent: + EditorGUILayout.HelpBox("This property or field is not attached or set. It will fail unless it will be attached before the check is perfomed.", MessageType.Warning); + break; + } + + return result; + } + + private SelectedPathError DoesPropertyExist(GameObject go, string propertPath) + { + try + { + object obj; + if (MemberResolver.TryGetValue(go, propertPath, out obj)) + return SelectedPathError.None; + return SelectedPathError.InvalidPath; + } + catch (TargetInvocationException e) + { + if (e.InnerException is MissingComponentException) + return SelectedPathError.MissingComponent; + throw; + } + } + + private enum SelectedPathError + { + None, + MissingComponent, + InvalidPath + } + } +} diff --git a/Assets/UnityTestTools/Assertions/Editor/PropertyPathSelector.cs.meta b/Assets/UnityTestTools/Assertions/Editor/PropertyPathSelector.cs.meta new file mode 100644 index 00000000..b1998a87 --- /dev/null +++ b/Assets/UnityTestTools/Assertions/Editor/PropertyPathSelector.cs.meta @@ -0,0 +1,8 @@ +fileFormatVersion: 2 +guid: 6619da1897737044080bdb8bc60eff87 +MonoImporter: + serializedVersion: 2 + defaultReferences: [] + executionOrder: 0 + icon: {instanceID: 0} + userData: diff --git a/Assets/UnityTestTools/Assertions/Editor/PropertyResolver.cs b/Assets/UnityTestTools/Assertions/Editor/PropertyResolver.cs new file mode 100644 index 00000000..5d705daa --- /dev/null +++ b/Assets/UnityTestTools/Assertions/Editor/PropertyResolver.cs @@ -0,0 +1,188 @@ +using System; +using System.Collections.Generic; +using System.Linq; +using System.Reflection; +using System.Text.RegularExpressions; +using UnityEngine; + +namespace UnityTest +{ + [Serializable] + public class PropertyResolver + { + public string[] ExcludedFieldNames { get; set; } + public Type[] ExcludedTypes { get; set; } + public Type[] AllowedTypes { get; set; } + + public PropertyResolver() + { + ExcludedFieldNames = new string[] { }; + ExcludedTypes = new Type[] { }; + AllowedTypes = new Type[] { }; + } + + public IList GetFieldsAndPropertiesUnderPath(GameObject go, string propertPath) + { + propertPath = propertPath.Trim(); + if (!PropertyPathIsValid(propertPath)) + { + throw new ArgumentException("Incorrect property path: " + propertPath); + } + + var idx = propertPath.LastIndexOf('.'); + + if (idx < 0) + { + var components = GetFieldsAndPropertiesFromGameObject(go, 2, null); + return components; + } + + var propertyToSearch = propertPath; + Type type; + if (MemberResolver.TryGetMemberType(go, propertyToSearch, out type)) + { + idx = propertPath.Length - 1; + } + else + { + propertyToSearch = propertPath.Substring(0, idx); + if (!MemberResolver.TryGetMemberType(go, propertyToSearch, out type)) + { + var components = GetFieldsAndPropertiesFromGameObject(go, 2, null); + return components.Where(s => s.StartsWith(propertPath.Substring(idx + 1))).ToArray(); + } + } + + var resultList = new List(); + var path = ""; + if (propertyToSearch.EndsWith(".")) + propertyToSearch = propertyToSearch.Substring(0, propertyToSearch.Length - 1); + foreach (var c in propertyToSearch) + { + if (c == '.') + resultList.Add(path); + path += c; + } + resultList.Add(path); + foreach (var prop in type.GetProperties().Where(info => info.GetIndexParameters().Length == 0)) + { + if (prop.Name.StartsWith(propertPath.Substring(idx + 1))) + resultList.Add(propertyToSearch + "." + prop.Name); + } + foreach (var prop in type.GetFields()) + { + if (prop.Name.StartsWith(propertPath.Substring(idx + 1))) + resultList.Add(propertyToSearch + "." + prop.Name); + } + return resultList.ToArray(); + } + + internal bool PropertyPathIsValid(string propertPath) + { + if (propertPath.StartsWith(".")) + return false; + if (propertPath.IndexOf("..") >= 0) + return false; + if (Regex.IsMatch(propertPath, @"\s")) + return false; + return true; + } + + public IList GetFieldsAndPropertiesFromGameObject(GameObject gameObject, int depthOfSearch, string extendPath) + { + if (depthOfSearch < 1) throw new ArgumentOutOfRangeException("depthOfSearch has to be greater than 0"); + + var goVals = GetPropertiesAndFieldsFromType(typeof(GameObject), + depthOfSearch - 1).Select(s => "gameObject." + s); + + var result = new List(); + if (AllowedTypes == null || !AllowedTypes.Any() || AllowedTypes.Contains(typeof(GameObject))) + result.Add("gameObject"); + result.AddRange(goVals); + + foreach (var componentType in GetAllComponents(gameObject)) + { + if (AllowedTypes == null || !AllowedTypes.Any() || AllowedTypes.Any(t => t.IsAssignableFrom(componentType))) + result.Add(componentType.Name); + + if (depthOfSearch > 1) + { + var vals = GetPropertiesAndFieldsFromType(componentType, depthOfSearch - 1); + var valsFullName = vals.Select(s => componentType.Name + "." + s); + result.AddRange(valsFullName); + } + } + + if (!string.IsNullOrEmpty(extendPath)) + { + var memberResolver = new MemberResolver(gameObject, extendPath); + var pathType = memberResolver.GetMemberType(); + var vals = GetPropertiesAndFieldsFromType(pathType, depthOfSearch - 1); + var valsFullName = vals.Select(s => extendPath + "." + s); + result.AddRange(valsFullName); + } + + return result; + } + + private string[] GetPropertiesAndFieldsFromType(Type type, int level) + { + level--; + + var result = new List(); + var fields = new List(); + fields.AddRange(type.GetFields().Where(f => !Attribute.IsDefined(f, typeof(ObsoleteAttribute))).ToArray()); + fields.AddRange(type.GetProperties().Where(info => info.GetIndexParameters().Length == 0 && !Attribute.IsDefined(info, typeof(ObsoleteAttribute))).ToArray()); + + foreach (var member in fields) + { + var memberType = GetMemberFieldType(member); + var memberTypeName = memberType.Name; + + if (AllowedTypes == null + || !AllowedTypes.Any() + || (AllowedTypes.Any(t => t.IsAssignableFrom(memberType)) && !ExcludedFieldNames.Contains(memberTypeName))) + { + result.Add(member.Name); + } + + if (level > 0 && IsTypeOrNameNotExcluded(memberType, memberTypeName)) + { + var vals = GetPropertiesAndFieldsFromType(memberType, level); + var valsFullName = vals.Select(s => member.Name + "." + s); + result.AddRange(valsFullName); + } + } + return result.ToArray(); + } + + private Type GetMemberFieldType(MemberInfo info) + { + if (info.MemberType == MemberTypes.Property) + return (info as PropertyInfo).PropertyType; + if (info.MemberType == MemberTypes.Field) + return (info as FieldInfo).FieldType; + throw new Exception("Only properties and fields are allowed"); + } + + internal Type[] GetAllComponents(GameObject gameObject) + { + var result = new List(); + var components = gameObject.GetComponents(typeof(Component)); + foreach (var component in components) + { + var componentType = component.GetType(); + if (IsTypeOrNameNotExcluded(componentType, null)) + { + result.Add(componentType); + } + } + return result.ToArray(); + } + + private bool IsTypeOrNameNotExcluded(Type memberType, string memberTypeName) + { + return !ExcludedTypes.Contains(memberType) && !ExcludedFieldNames.Contains(memberTypeName); + } + } +} diff --git a/Assets/UnityTestTools/Assertions/Editor/PropertyResolver.cs.meta b/Assets/UnityTestTools/Assertions/Editor/PropertyResolver.cs.meta new file mode 100644 index 00000000..22210c77 --- /dev/null +++ b/Assets/UnityTestTools/Assertions/Editor/PropertyResolver.cs.meta @@ -0,0 +1,8 @@ +fileFormatVersion: 2 +guid: bbbd193a27920d9478c2a766a7291d72 +MonoImporter: + serializedVersion: 2 + defaultReferences: [] + executionOrder: 0 + icon: {instanceID: 0} + userData: diff --git a/Assets/UnityTestTools/Assertions/InvalidPathException.cs b/Assets/UnityTestTools/Assertions/InvalidPathException.cs new file mode 100644 index 00000000..9ddde07e --- /dev/null +++ b/Assets/UnityTestTools/Assertions/InvalidPathException.cs @@ -0,0 +1,14 @@ +using System; +using System.Collections.Generic; +using UnityEngine; + +namespace UnityTest +{ + public class InvalidPathException : Exception + { + public InvalidPathException(string path) + : base("Invalid path part " + path) + { + } + } +} diff --git a/Assets/UnityTestTools/Assertions/InvalidPathException.cs.meta b/Assets/UnityTestTools/Assertions/InvalidPathException.cs.meta new file mode 100644 index 00000000..a5f882dd --- /dev/null +++ b/Assets/UnityTestTools/Assertions/InvalidPathException.cs.meta @@ -0,0 +1,8 @@ +fileFormatVersion: 2 +guid: 3b85786dfd1aef544bf8bb873d6a4ebb +MonoImporter: + serializedVersion: 2 + defaultReferences: [] + executionOrder: 0 + icon: {instanceID: 0} + userData: diff --git a/Assets/UnityTestTools/Assertions/MemberResolver.cs b/Assets/UnityTestTools/Assertions/MemberResolver.cs new file mode 100644 index 00000000..65f7351c --- /dev/null +++ b/Assets/UnityTestTools/Assertions/MemberResolver.cs @@ -0,0 +1,208 @@ +using System; +using System.Collections.Generic; +using System.Reflection; +using System.Text.RegularExpressions; +using UnityEngine; + +namespace UnityTest +{ + public class MemberResolver + { + private object m_CallingObjectRef; + private MemberInfo[] m_Callstack; + private readonly GameObject m_GameObject; + private readonly string m_Path; + + public MemberResolver(GameObject gameObject, string path) + { + path = path.Trim(); + ValidatePath(path); + + m_GameObject = gameObject; + m_Path = path.Trim(); + } + + public object GetValue(bool useCache) + { + if (useCache && m_CallingObjectRef != null) + { + object val = m_CallingObjectRef; + for (int i = 0; i < m_Callstack.Length; i++) + val = GetValueFromMember(val, m_Callstack[i]); + return val; + } + + object result = GetBaseObject(); + var fullCallStack = GetCallstack(); + + m_CallingObjectRef = result; + var tempCallstack = new List(); + for (int i = 0; i < fullCallStack.Length; i++) + { + var member = fullCallStack[i]; + result = GetValueFromMember(result, member); + tempCallstack.Add(member); + if (result == null) return null; + var type = result.GetType(); + + //String is not a value type but we don't want to cache it + if (!IsValueType(type) && type != typeof(System.String)) + { + tempCallstack.Clear(); + m_CallingObjectRef = result; + } + } + m_Callstack = tempCallstack.ToArray(); + return result; + } + + public Type GetMemberType() + { + var callstack = GetCallstack(); + if (callstack.Length == 0) return GetBaseObject().GetType(); + + var member = callstack[callstack.Length - 1]; + if (member is FieldInfo) + return (member as FieldInfo).FieldType; + if (member is MethodInfo) + return (member as MethodInfo).ReturnType; + return null; + } + + #region Static wrappers + public static bool TryGetMemberType(GameObject gameObject, string path, out Type value) + { + try + { + var mr = new MemberResolver(gameObject, path); + value = mr.GetMemberType(); + return true; + } + catch (InvalidPathException) + { + value = null; + return false; + } + } + + public static bool TryGetValue(GameObject gameObject, string path, out object value) + { + try + { + var mr = new MemberResolver(gameObject, path); + value = mr.GetValue(false); + return true; + } + catch (InvalidPathException) + { + value = null; + return false; + } + } + #endregion + + private object GetValueFromMember(object obj, MemberInfo memberInfo) + { + if (memberInfo is FieldInfo) + return (memberInfo as FieldInfo).GetValue(obj); + if (memberInfo is MethodInfo) + return (memberInfo as MethodInfo).Invoke(obj, null); + throw new InvalidPathException(memberInfo.Name); + } + + private object GetBaseObject() + { + if (string.IsNullOrEmpty(m_Path)) return m_GameObject; + var firstElement = m_Path.Split('.')[0]; + var comp = m_GameObject.GetComponent(firstElement); + if (comp != null) + return comp; + return m_GameObject; + } + + private MemberInfo[] GetCallstack() + { + if (m_Path == "") return new MemberInfo[0]; + var propsQueue = new Queue(m_Path.Split('.')); + + Type type = GetBaseObject().GetType(); + if (type != typeof(GameObject)) + propsQueue.Dequeue(); + + PropertyInfo propertyTemp; + FieldInfo fieldTemp; + var list = new List(); + while (propsQueue.Count != 0) + { + var nameToFind = propsQueue.Dequeue(); + fieldTemp = GetField(type, nameToFind); + if (fieldTemp != null) + { + type = fieldTemp.FieldType; + list.Add(fieldTemp); + continue; + } + propertyTemp = GetProperty(type, nameToFind); + if (propertyTemp != null) + { + type = propertyTemp.PropertyType; + var getMethod = GetGetMethod(propertyTemp); + list.Add(getMethod); + continue; + } + throw new InvalidPathException(nameToFind); + } + return list.ToArray(); + } + + private void ValidatePath(string path) + { + bool invalid = false; + if (path.StartsWith(".") || path.EndsWith(".")) + invalid = true; + if (path.IndexOf("..") >= 0) + invalid = true; + if (Regex.IsMatch(path, @"\s")) + invalid = true; + + if (invalid) + throw new InvalidPathException(path); + } + + private static bool IsValueType(Type type) + { + #if !UNITY_METRO + return type.IsValueType; + #else + return false; + #endif + } + + private static FieldInfo GetField(Type type, string fieldName) + { + #if !UNITY_METRO + return type.GetField(fieldName); + #else + return null; + #endif + } + + private static PropertyInfo GetProperty(Type type, string propertyName) + { + #if !UNITY_METRO + return type.GetProperty(propertyName); + #else + return null; + #endif + } + + private static MethodInfo GetGetMethod(PropertyInfo propertyInfo) + { + #if !UNITY_METRO + return propertyInfo.GetGetMethod(); + #else + return null; + #endif + } + } +} diff --git a/Assets/UnityTestTools/Assertions/MemberResolver.cs.meta b/Assets/UnityTestTools/Assertions/MemberResolver.cs.meta new file mode 100644 index 00000000..6b1ea425 --- /dev/null +++ b/Assets/UnityTestTools/Assertions/MemberResolver.cs.meta @@ -0,0 +1,8 @@ +fileFormatVersion: 2 +guid: 80df8ef907961e34dbcc7c89b22729b9 +MonoImporter: + serializedVersion: 2 + defaultReferences: [] + executionOrder: 0 + icon: {instanceID: 0} + userData: diff --git a/Assets/UnityTestTools/Common.meta b/Assets/UnityTestTools/Common.meta new file mode 100644 index 00000000..5f0acfe7 --- /dev/null +++ b/Assets/UnityTestTools/Common.meta @@ -0,0 +1,5 @@ +fileFormatVersion: 2 +guid: a2caba6436df568499c84c1c607ce766 +folderAsset: yes +DefaultImporter: + userData: diff --git a/Assets/UnityTestTools/Common/Editor.meta b/Assets/UnityTestTools/Common/Editor.meta new file mode 100644 index 00000000..2021d4fe --- /dev/null +++ b/Assets/UnityTestTools/Common/Editor.meta @@ -0,0 +1,4 @@ +fileFormatVersion: 2 +guid: f4ab061d0035ee545a936bdf8f3f8620 +DefaultImporter: + userData: diff --git a/Assets/UnityTestTools/Common/Editor/Icons.cs b/Assets/UnityTestTools/Common/Editor/Icons.cs new file mode 100644 index 00000000..8fd7bfae --- /dev/null +++ b/Assets/UnityTestTools/Common/Editor/Icons.cs @@ -0,0 +1,57 @@ +using System; +using System.Collections.Generic; +using System.IO; +using System.Linq; +using UnityEditor; +using UnityEngine; + +namespace UnityTest +{ + public static class Icons + { + const string k_IconsFolderName = "icons"; + private static readonly string k_IconsFolderPath = String.Format("UnityTestTools{0}Common{0}Editor{0}{1}", Path.DirectorySeparatorChar, k_IconsFolderName); + + private static readonly string k_IconsAssetsPath = ""; + + public static readonly Texture2D FailImg; + public static readonly Texture2D IgnoreImg; + public static readonly Texture2D SuccessImg; + public static readonly Texture2D UnknownImg; + public static readonly Texture2D InconclusiveImg; + public static readonly Texture2D StopwatchImg; + + public static readonly GUIContent GUIUnknownImg; + public static readonly GUIContent GUIInconclusiveImg; + public static readonly GUIContent GUIIgnoreImg; + public static readonly GUIContent GUISuccessImg; + public static readonly GUIContent GUIFailImg; + + static Icons() + { + var dirs = Directory.GetDirectories("Assets", k_IconsFolderName, SearchOption.AllDirectories).Where(s => s.EndsWith(k_IconsFolderPath)); + if (dirs.Any()) + k_IconsAssetsPath = dirs.First(); + else + Debug.LogWarning("The UnityTestTools asset folder path is incorrect. If you relocated the tools please change the path accordingly (Icons.cs)."); + + FailImg = LoadTexture("failed.png"); + IgnoreImg = LoadTexture("ignored.png"); + SuccessImg = LoadTexture("passed.png"); + UnknownImg = LoadTexture("normal.png"); + InconclusiveImg = LoadTexture("inconclusive.png"); + StopwatchImg = LoadTexture("stopwatch.png"); + + GUIUnknownImg = new GUIContent(UnknownImg); + GUIInconclusiveImg = new GUIContent(InconclusiveImg); + GUIIgnoreImg = new GUIContent(IgnoreImg); + GUISuccessImg = new GUIContent(SuccessImg); + GUIFailImg = new GUIContent(FailImg); + } + + private static Texture2D LoadTexture(string fileName) + { + return (Texture2D)AssetDatabase.LoadAssetAtPath(k_IconsAssetsPath + Path.DirectorySeparatorChar + fileName, typeof(Texture2D)); + } + } +} diff --git a/Assets/UnityTestTools/Common/Editor/Icons.cs.meta b/Assets/UnityTestTools/Common/Editor/Icons.cs.meta new file mode 100644 index 00000000..267269a9 --- /dev/null +++ b/Assets/UnityTestTools/Common/Editor/Icons.cs.meta @@ -0,0 +1,8 @@ +fileFormatVersion: 2 +guid: 8571844b0c115b84cbe8b3f67e8dec04 +MonoImporter: + serializedVersion: 2 + defaultReferences: [] + executionOrder: 0 + icon: {instanceID: 0} + userData: diff --git a/Assets/UnityTestTools/Common/Editor/ProjectSettingsBase.cs b/Assets/UnityTestTools/Common/Editor/ProjectSettingsBase.cs new file mode 100644 index 00000000..99cafad8 --- /dev/null +++ b/Assets/UnityTestTools/Common/Editor/ProjectSettingsBase.cs @@ -0,0 +1,39 @@ +using System; +using System.Collections.Generic; +using System.IO; +using System.Linq; +using UnityEditor; +using UnityEngine; + +namespace UnityTest +{ + public abstract class ProjectSettingsBase : ScriptableObject + { + private static readonly string k_SettingsPath = Path.Combine("UnityTestTools", "Common"); + const string k_SettingsFolder = "Settings"; + + public virtual void Save() + { + EditorUtility.SetDirty(this); + } + + public static T Load() where T : ProjectSettingsBase, new () + { + var pathsInProject = Directory.GetDirectories("Assets", "*", SearchOption.AllDirectories) + .Where(s => s.Contains(k_SettingsPath)); + + if (pathsInProject.Count() == 0) Debug.LogError("Can't find settings path: " + k_SettingsPath); + + string pathInProject = Path.Combine(pathsInProject.First(), k_SettingsFolder); + var assetPath = Path.Combine(pathInProject, typeof(T).Name) + ".asset"; + var settings = AssetDatabase.LoadAssetAtPath(assetPath, typeof(T)) as T; + + if (settings != null) return settings; + + settings = CreateInstance(); + Directory.CreateDirectory(pathInProject); + AssetDatabase.CreateAsset(settings, assetPath); + return settings; + } + } +} diff --git a/Assets/UnityTestTools/Common/Editor/ProjectSettingsBase.cs.meta b/Assets/UnityTestTools/Common/Editor/ProjectSettingsBase.cs.meta new file mode 100644 index 00000000..db5944b9 --- /dev/null +++ b/Assets/UnityTestTools/Common/Editor/ProjectSettingsBase.cs.meta @@ -0,0 +1,8 @@ +fileFormatVersion: 2 +guid: 9ac961be07107124a88dcb81927143d4 +MonoImporter: + serializedVersion: 2 + defaultReferences: [] + executionOrder: 0 + icon: {instanceID: 0} + userData: diff --git a/Assets/UnityTestTools/Common/Editor/ResultWriter.meta b/Assets/UnityTestTools/Common/Editor/ResultWriter.meta new file mode 100644 index 00000000..9b2e13b3 --- /dev/null +++ b/Assets/UnityTestTools/Common/Editor/ResultWriter.meta @@ -0,0 +1,5 @@ +fileFormatVersion: 2 +guid: 4ffbf5a07740aa5479651bd415f52ebb +folderAsset: yes +DefaultImporter: + userData: diff --git a/Assets/UnityTestTools/Common/Editor/ResultWriter/ResultSummarizer.cs b/Assets/UnityTestTools/Common/Editor/ResultWriter/ResultSummarizer.cs new file mode 100644 index 00000000..cfd39ca3 --- /dev/null +++ b/Assets/UnityTestTools/Common/Editor/ResultWriter/ResultSummarizer.cs @@ -0,0 +1,173 @@ +// **************************************************************** +// Based on nUnit 2.6.2 (http://www.nunit.org/) +// **************************************************************** + +using System; +using System.Collections.Generic; +using UnityEngine; + +namespace UnityTest +{ + /// + /// Summary description for ResultSummarizer. + /// + public class ResultSummarizer + { + private int m_ErrorCount; + private int m_FailureCount; + private int m_IgnoreCount; + private int m_InconclusiveCount; + private int m_NotRunnable; + private int m_ResultCount; + private int m_SkipCount; + private int m_SuccessCount; + private int m_TestsRun; + + private TimeSpan m_Duration; + + public ResultSummarizer(IEnumerable results) + { + foreach (var result in results) + Summarize(result); + } + + public bool Success + { + get { return m_FailureCount == 0; } + } + + /// + /// Returns the number of test cases for which results + /// have been summarized. Any tests excluded by use of + /// Category or Explicit attributes are not counted. + /// + public int ResultCount + { + get { return m_ResultCount; } + } + + /// + /// Returns the number of test cases actually run, which + /// is the same as ResultCount, less any Skipped, Ignored + /// or NonRunnable tests. + /// + public int TestsRun + { + get { return m_TestsRun; } + } + + /// + /// Returns the number of tests that passed + /// + public int Passed + { + get { return m_SuccessCount; } + } + + /// + /// Returns the number of test cases that had an error. + /// + public int Errors + { + get { return m_ErrorCount; } + } + + /// + /// Returns the number of test cases that failed. + /// + public int Failures + { + get { return m_FailureCount; } + } + + /// + /// Returns the number of test cases that failed. + /// + public int Inconclusive + { + get { return m_InconclusiveCount; } + } + + /// + /// Returns the number of test cases that were not runnable + /// due to errors in the signature of the class or method. + /// Such tests are also counted as Errors. + /// + public int NotRunnable + { + get { return m_NotRunnable; } + } + + /// + /// Returns the number of test cases that were skipped. + /// + public int Skipped + { + get { return m_SkipCount; } + } + + public int Ignored + { + get { return m_IgnoreCount; } + } + + public double Duration + { + get { return m_Duration.TotalSeconds; } + } + + public int TestsNotRun + { + get { return m_SkipCount + m_IgnoreCount + m_NotRunnable; } + } + + public void Summarize(ITestResult result) + { + m_Duration += TimeSpan.FromSeconds(result.Duration); + m_ResultCount++; + + if(!result.Executed) + { + if(result.IsIgnored) + { + m_IgnoreCount++; + return; + } + + m_SkipCount++; + return; + } + + switch (result.ResultState) + { + case TestResultState.Success: + m_SuccessCount++; + m_TestsRun++; + break; + case TestResultState.Failure: + m_FailureCount++; + m_TestsRun++; + break; + case TestResultState.Error: + case TestResultState.Cancelled: + m_ErrorCount++; + m_TestsRun++; + break; + case TestResultState.Inconclusive: + m_InconclusiveCount++; + m_TestsRun++; + break; + case TestResultState.NotRunnable: + m_NotRunnable++; + // errorCount++; + break; + case TestResultState.Ignored: + m_IgnoreCount++; + break; + default: + m_SkipCount++; + break; + } + } + } +} diff --git a/Assets/UnityTestTools/Common/Editor/ResultWriter/ResultSummarizer.cs.meta b/Assets/UnityTestTools/Common/Editor/ResultWriter/ResultSummarizer.cs.meta new file mode 100644 index 00000000..ca3c41ff --- /dev/null +++ b/Assets/UnityTestTools/Common/Editor/ResultWriter/ResultSummarizer.cs.meta @@ -0,0 +1,8 @@ +fileFormatVersion: 2 +guid: ce89106be5bd4204388d58510e4e55da +MonoImporter: + serializedVersion: 2 + defaultReferences: [] + executionOrder: 0 + icon: {instanceID: 0} + userData: diff --git a/Assets/UnityTestTools/Common/Editor/ResultWriter/StackTraceFilter.cs b/Assets/UnityTestTools/Common/Editor/ResultWriter/StackTraceFilter.cs new file mode 100644 index 00000000..686de926 --- /dev/null +++ b/Assets/UnityTestTools/Common/Editor/ResultWriter/StackTraceFilter.cs @@ -0,0 +1,62 @@ +// **************************************************************** +// Based on nUnit 2.6.2 (http://www.nunit.org/) +// **************************************************************** + +using System; +using System.Collections.Generic; +using System.IO; +using UnityEngine; + +namespace UnityTest +{ + /// + /// Summary description for StackTraceFilter. + /// + public class StackTraceFilter + { + public static string Filter(string stack) + { + if (stack == null) return null; + var sw = new StringWriter(); + var sr = new StringReader(stack); + + try + { + string line; + while ((line = sr.ReadLine()) != null) + { + if (!FilterLine(line)) + sw.WriteLine(line.Trim()); + } + } + catch (Exception) + { + return stack; + } + return sw.ToString(); + } + + static bool FilterLine(string line) + { + string[] patterns = + { + "NUnit.Core.TestCase", + "NUnit.Core.ExpectedExceptionTestCase", + "NUnit.Core.TemplateTestCase", + "NUnit.Core.TestResult", + "NUnit.Core.TestSuite", + "NUnit.Framework.Assertion", + "NUnit.Framework.Assert", + "System.Reflection.MonoMethod" + }; + + for (int i = 0; i < patterns.Length; i++) + { + if (line.IndexOf(patterns[i]) > 0) + return true; + } + + return false; + } + } +} diff --git a/Assets/UnityTestTools/Common/Editor/ResultWriter/StackTraceFilter.cs.meta b/Assets/UnityTestTools/Common/Editor/ResultWriter/StackTraceFilter.cs.meta new file mode 100644 index 00000000..70518439 --- /dev/null +++ b/Assets/UnityTestTools/Common/Editor/ResultWriter/StackTraceFilter.cs.meta @@ -0,0 +1,8 @@ +fileFormatVersion: 2 +guid: fe6b4d68575d4ba44b1d5c5c3f0e96d3 +MonoImporter: + serializedVersion: 2 + defaultReferences: [] + executionOrder: 0 + icon: {instanceID: 0} + userData: diff --git a/Assets/UnityTestTools/Common/Editor/ResultWriter/XmlResultWriter.cs b/Assets/UnityTestTools/Common/Editor/ResultWriter/XmlResultWriter.cs new file mode 100644 index 00000000..3115e4f2 --- /dev/null +++ b/Assets/UnityTestTools/Common/Editor/ResultWriter/XmlResultWriter.cs @@ -0,0 +1,303 @@ +using System; +using System.Collections.Generic; +using System.Globalization; +using System.IO; +using System.Security; +using System.Text; +using UnityEngine; + +namespace UnityTest +{ + public class XmlResultWriter + { + private readonly StringBuilder m_ResultWriter = new StringBuilder(); + private int m_Indend; + private readonly string m_SuiteName; + private readonly ITestResult[] m_Results; + string m_Platform; + + public XmlResultWriter(string suiteName, string platform, ITestResult[] results) + { + m_SuiteName = suiteName; + m_Results = results; + m_Platform = platform; + } + + private const string k_NUnitVersion = "2.6.2-Unity"; + + public string GetTestResult() + { + InitializeXmlFile(m_SuiteName, new ResultSummarizer(m_Results)); + foreach (var result in m_Results) + { + WriteResultElement(result); + } + TerminateXmlFile(); + return m_ResultWriter.ToString(); + } + + private void InitializeXmlFile(string resultsName, ResultSummarizer summaryResults) + { + WriteHeader(); + + DateTime now = DateTime.Now; + var attributes = new Dictionary + { + {"name", "Unity Tests"}, + {"total", summaryResults.TestsRun.ToString()}, + {"errors", summaryResults.Errors.ToString()}, + {"failures", summaryResults.Failures.ToString()}, + {"not-run", summaryResults.TestsNotRun.ToString()}, + {"inconclusive", summaryResults.Inconclusive.ToString()}, + {"ignored", summaryResults.Ignored.ToString()}, + {"skipped", summaryResults.Skipped.ToString()}, + {"invalid", summaryResults.NotRunnable.ToString()}, + {"date", now.ToString("yyyy-MM-dd")}, + {"time", now.ToString("HH:mm:ss")} + }; + + WriteOpeningElement("test-results", attributes); + + WriteEnvironment(m_Platform); + WriteCultureInfo(); + WriteTestSuite(resultsName, summaryResults); + WriteOpeningElement("results"); + } + + private void WriteOpeningElement(string elementName) + { + WriteOpeningElement(elementName, new Dictionary()); + } + + private void WriteOpeningElement(string elementName, Dictionary attributes) + { + WriteOpeningElement(elementName, attributes, false); + } + + + private void WriteOpeningElement(string elementName, Dictionary attributes, bool closeImmediatelly) + { + WriteIndend(); + m_Indend++; + m_ResultWriter.Append("<"); + m_ResultWriter.Append(elementName); + foreach (var attribute in attributes) + { + m_ResultWriter.AppendFormat(" {0}=\"{1}\"", attribute.Key, SecurityElement.Escape(attribute.Value)); + } + if (closeImmediatelly) + { + m_ResultWriter.Append(" /"); + m_Indend--; + } + m_ResultWriter.AppendLine(">"); + } + + private void WriteIndend() + { + for (int i = 0; i < m_Indend; i++) + { + m_ResultWriter.Append(" "); + } + } + + private void WriteClosingElement(string elementName) + { + m_Indend--; + WriteIndend(); + m_ResultWriter.AppendLine(""); + } + + private void WriteHeader() + { + m_ResultWriter.AppendLine(""); + m_ResultWriter.AppendLine(""); + } + + static string GetEnvironmentUserName() + { + return Environment.UserName; + } + + static string GetEnvironmentMachineName() + { + return Environment.MachineName; + } + + static string GetEnvironmentUserDomainName() + { + return Environment.UserDomainName; + } + + static string GetEnvironmentVersion() + { + return Environment.Version.ToString(); + } + + static string GetEnvironmentOSVersion() + { + return Environment.OSVersion.ToString(); + } + + static string GetEnvironmentOSVersionPlatform() + { + return Environment.OSVersion.Platform.ToString(); + } + + static string EnvironmentGetCurrentDirectory() + { + return Environment.CurrentDirectory; + } + + private void WriteEnvironment( string targetPlatform ) + { + var attributes = new Dictionary + { + {"nunit-version", k_NUnitVersion}, + {"clr-version", GetEnvironmentVersion()}, + {"os-version", GetEnvironmentOSVersion()}, + {"platform", GetEnvironmentOSVersionPlatform()}, + {"cwd", EnvironmentGetCurrentDirectory()}, + {"machine-name", GetEnvironmentMachineName()}, + {"user", GetEnvironmentUserName()}, + {"user-domain", GetEnvironmentUserDomainName()}, + {"unity-version", Application.unityVersion}, + {"unity-platform", targetPlatform} + }; + WriteOpeningElement("environment", attributes, true); + } + + private void WriteCultureInfo() + { + var attributes = new Dictionary + { + {"current-culture", CultureInfo.CurrentCulture.ToString()}, + {"current-uiculture", CultureInfo.CurrentUICulture.ToString()} + }; + WriteOpeningElement("culture-info", attributes, true); + } + + private void WriteTestSuite(string resultsName, ResultSummarizer summaryResults) + { + var attributes = new Dictionary + { + {"name", resultsName}, + {"type", "Assembly"}, + {"executed", "True"}, + {"result", summaryResults.Success ? "Success" : "Failure"}, + {"success", summaryResults.Success ? "True" : "False"}, + {"time", summaryResults.Duration.ToString("#####0.000", NumberFormatInfo.InvariantInfo)} + }; + WriteOpeningElement("test-suite", attributes); + } + + private void WriteResultElement(ITestResult result) + { + StartTestElement(result); + + switch (result.ResultState) + { + case TestResultState.Ignored: + case TestResultState.NotRunnable: + case TestResultState.Skipped: + WriteReasonElement(result); + break; + + case TestResultState.Failure: + case TestResultState.Error: + case TestResultState.Cancelled: + WriteFailureElement(result); + break; + case TestResultState.Success: + case TestResultState.Inconclusive: + if (result.Message != null) + WriteReasonElement(result); + break; + }; + + WriteClosingElement("test-case"); + } + + private void TerminateXmlFile() + { + WriteClosingElement("results"); + WriteClosingElement("test-suite"); + WriteClosingElement("test-results"); + } + + #region Element Creation Helpers + + private void StartTestElement(ITestResult result) + { + var attributes = new Dictionary + { + {"name", result.FullName}, + {"executed", result.Executed.ToString()} + }; + string resultString; + switch (result.ResultState) + { + case TestResultState.Cancelled: + resultString = TestResultState.Failure.ToString(); + break; + default: + resultString = result.ResultState.ToString(); + break; + } + attributes.Add("result", resultString); + if (result.Executed) + { + attributes.Add("success", result.IsSuccess.ToString()); + attributes.Add("time", result.Duration.ToString("#####0.000", NumberFormatInfo.InvariantInfo)); + } + WriteOpeningElement("test-case", attributes); + } + + private void WriteReasonElement(ITestResult result) + { + WriteOpeningElement("reason"); + WriteOpeningElement("message"); + WriteCData(result.Message); + WriteClosingElement("message"); + WriteClosingElement("reason"); + } + + private void WriteFailureElement(ITestResult result) + { + WriteOpeningElement("failure"); + WriteOpeningElement("message"); + WriteCData(result.Message); + WriteClosingElement("message"); + WriteOpeningElement("stack-trace"); + if (result.StackTrace != null) + WriteCData(StackTraceFilter.Filter(result.StackTrace)); + WriteClosingElement("stack-trace"); + WriteClosingElement("failure"); + } + + #endregion + + private void WriteCData(string text) + { + if (string.IsNullOrEmpty(text)) + return; + m_ResultWriter.AppendFormat("", text); + m_ResultWriter.AppendLine(); + } + + public void WriteToFile(string resultDestiantion, string resultFileName) + { + try + { + var path = Path.Combine(resultDestiantion, resultFileName); + Debug.Log("Saving results in " + path); + File.WriteAllText(path, GetTestResult(), Encoding.UTF8); + } + catch (Exception e) + { + Debug.LogError("Error while opening file"); + Debug.LogException(e); + } + } + } +} diff --git a/Assets/UnityTestTools/Common/Editor/ResultWriter/XmlResultWriter.cs.meta b/Assets/UnityTestTools/Common/Editor/ResultWriter/XmlResultWriter.cs.meta new file mode 100644 index 00000000..2fffa90d --- /dev/null +++ b/Assets/UnityTestTools/Common/Editor/ResultWriter/XmlResultWriter.cs.meta @@ -0,0 +1,8 @@ +fileFormatVersion: 2 +guid: e9bba41ace7686d4ab0c400d1e7f55b7 +MonoImporter: + serializedVersion: 2 + defaultReferences: [] + executionOrder: 0 + icon: {instanceID: 0} + userData: diff --git a/Assets/UnityTestTools/Common/Editor/Styles.cs b/Assets/UnityTestTools/Common/Editor/Styles.cs new file mode 100644 index 00000000..0caf6e1a --- /dev/null +++ b/Assets/UnityTestTools/Common/Editor/Styles.cs @@ -0,0 +1,47 @@ +using System; +using System.Collections.Generic; +using UnityEditor; +using UnityEngine; + +namespace UnityTest +{ + public static class Styles + { + public static GUIStyle info; + public static GUIStyle testList; + + public static GUIStyle selectedFoldout; + public static GUIStyle foldout; + public static GUIStyle toolbarLabel; + + public static GUIStyle testName; + + private static readonly Color k_SelectedColor = new Color(0.3f, 0.5f, 0.85f); + + static Styles() + { + info = new GUIStyle(EditorStyles.wordWrappedLabel); + info.wordWrap = false; + info.stretchHeight = true; + info.margin.right = 15; + + testList = new GUIStyle("CN Box"); + testList.margin.top = 0; + testList.padding.left = 3; + + foldout = new GUIStyle(EditorStyles.foldout); + selectedFoldout = new GUIStyle(EditorStyles.foldout); + selectedFoldout.onFocused.textColor = selectedFoldout.focused.textColor = + selectedFoldout.onActive.textColor = selectedFoldout.active.textColor = + selectedFoldout.onNormal.textColor = selectedFoldout.normal.textColor = k_SelectedColor; + + toolbarLabel = new GUIStyle(EditorStyles.toolbarButton); + toolbarLabel.normal.background = null; + toolbarLabel.contentOffset = new Vector2(0, -2); + + testName = new GUIStyle(EditorStyles.label); + testName.padding.left += 12; + testName.focused.textColor = testName.onFocused.textColor = k_SelectedColor; + } + } +} diff --git a/Assets/UnityTestTools/Common/Editor/Styles.cs.meta b/Assets/UnityTestTools/Common/Editor/Styles.cs.meta new file mode 100644 index 00000000..294a6194 --- /dev/null +++ b/Assets/UnityTestTools/Common/Editor/Styles.cs.meta @@ -0,0 +1,8 @@ +fileFormatVersion: 2 +guid: a8b92379e11501742b1badcbb08da812 +MonoImporter: + serializedVersion: 2 + defaultReferences: [] + executionOrder: 0 + icon: {instanceID: 0} + userData: diff --git a/Assets/UnityTestTools/Common/Editor/TestFilterSettings.cs b/Assets/UnityTestTools/Common/Editor/TestFilterSettings.cs new file mode 100644 index 00000000..cef016a0 --- /dev/null +++ b/Assets/UnityTestTools/Common/Editor/TestFilterSettings.cs @@ -0,0 +1,104 @@ +using System; +using System.Collections.Generic; +using UnityEngine; +using UnityEditor; +using System.Linq; + +namespace UnityTest +{ + public class TestFilterSettings + { + public bool ShowSucceeded; + public bool ShowFailed; + public bool ShowIgnored; + public bool ShowNotRun; + + public string FilterByName; + public int FilterByCategory; + + private GUIContent _succeededBtn; + private GUIContent _failedBtn; + private GUIContent _ignoredBtn; + private GUIContent _notRunBtn; + + public string[] AvailableCategories; + + private readonly string _prefsKey; + + public TestFilterSettings(string prefsKey) + { + _prefsKey = prefsKey; + Load(); + UpdateCounters(Enumerable.Empty()); + } + + public void Load() + { + ShowSucceeded = EditorPrefs.GetBool(_prefsKey + ".ShowSucceeded", true); + ShowFailed = EditorPrefs.GetBool(_prefsKey + ".ShowFailed", true); + ShowIgnored = EditorPrefs.GetBool(_prefsKey + ".ShowIgnored", true); + ShowNotRun = EditorPrefs.GetBool(_prefsKey + ".ShowNotRun", true); + FilterByName = EditorPrefs.GetString(_prefsKey + ".FilterByName", string.Empty); + FilterByCategory = EditorPrefs.GetInt(_prefsKey + ".FilterByCategory", 0); + } + + public void Save() + { + EditorPrefs.SetBool(_prefsKey + ".ShowSucceeded", ShowSucceeded); + EditorPrefs.SetBool(_prefsKey + ".ShowFailed", ShowFailed); + EditorPrefs.SetBool(_prefsKey + ".ShowIgnored", ShowIgnored); + EditorPrefs.SetBool(_prefsKey + ".ShowNotRun", ShowNotRun); + EditorPrefs.SetString(_prefsKey + ".FilterByName", FilterByName); + EditorPrefs.SetInt(_prefsKey + ".FilterByCategory", FilterByCategory); + } + + public void UpdateCounters(IEnumerable results) + { + var summary = new ResultSummarizer(results); + + _succeededBtn = new GUIContent(summary.Passed.ToString(), Icons.SuccessImg, "Show tests that succeeded"); + _failedBtn = new GUIContent((summary.Errors + summary.Failures + summary.Inconclusive).ToString(), Icons.FailImg, "Show tests that failed"); + _ignoredBtn = new GUIContent((summary.Ignored + summary.NotRunnable).ToString(), Icons.IgnoreImg, "Show tests that are ignored"); + _notRunBtn = new GUIContent((summary.TestsNotRun - summary.Ignored - summary.NotRunnable).ToString(), Icons.UnknownImg, "Show tests that didn't run"); + } + + public string[] GetSelectedCategories() + { + if(AvailableCategories == null) return new string[0]; + + return AvailableCategories.Where ((c, i) => (FilterByCategory & (1 << i)) != 0).ToArray(); + } + + public void OnGUI() + { + EditorGUI.BeginChangeCheck(); + + FilterByName = GUILayout.TextField(FilterByName, "ToolbarSeachTextField", GUILayout.MinWidth(100), GUILayout.MaxWidth(250), GUILayout.ExpandWidth(true)); + if(GUILayout.Button (GUIContent.none, string.IsNullOrEmpty(FilterByName) ? "ToolbarSeachCancelButtonEmpty" : "ToolbarSeachCancelButton")) + FilterByName = string.Empty; + + if (AvailableCategories != null && AvailableCategories.Length > 0) + FilterByCategory = EditorGUILayout.MaskField(FilterByCategory, AvailableCategories, EditorStyles.toolbarDropDown, GUILayout.MaxWidth(90)); + + ShowSucceeded = GUILayout.Toggle(ShowSucceeded, _succeededBtn, EditorStyles.toolbarButton); + ShowFailed = GUILayout.Toggle(ShowFailed, _failedBtn, EditorStyles.toolbarButton); + ShowIgnored = GUILayout.Toggle(ShowIgnored, _ignoredBtn, EditorStyles.toolbarButton); + ShowNotRun = GUILayout.Toggle(ShowNotRun, _notRunBtn, EditorStyles.toolbarButton); + + if(EditorGUI.EndChangeCheck()) Save (); + } + + public RenderingOptions BuildRenderingOptions() + { + var options = new RenderingOptions(); + options.showSucceeded = ShowSucceeded; + options.showFailed = ShowFailed; + options.showIgnored = ShowIgnored; + options.showNotRunned = ShowNotRun; + options.nameFilter = FilterByName; + options.categories = GetSelectedCategories(); + return options; + } + } + +} diff --git a/Assets/UnityTestTools/Common/Editor/TestFilterSettings.cs.meta b/Assets/UnityTestTools/Common/Editor/TestFilterSettings.cs.meta new file mode 100644 index 00000000..9a7a0e32 --- /dev/null +++ b/Assets/UnityTestTools/Common/Editor/TestFilterSettings.cs.meta @@ -0,0 +1,8 @@ +fileFormatVersion: 2 +guid: 5a2d025e58bff433e963d0a4cd7599ef +MonoImporter: + serializedVersion: 2 + defaultReferences: [] + executionOrder: 0 + icon: {instanceID: 0} + userData: diff --git a/Assets/UnityTestTools/Common/Editor/icons.meta b/Assets/UnityTestTools/Common/Editor/icons.meta new file mode 100644 index 00000000..58c52486 --- /dev/null +++ b/Assets/UnityTestTools/Common/Editor/icons.meta @@ -0,0 +1,4 @@ +fileFormatVersion: 2 +guid: e8bb6eae11352f44da0d6d8a8959b69e +DefaultImporter: + userData: diff --git a/Assets/UnityTestTools/Common/Editor/icons/failed.png b/Assets/UnityTestTools/Common/Editor/icons/failed.png new file mode 100644 index 00000000..7c0aba4c Binary files /dev/null and b/Assets/UnityTestTools/Common/Editor/icons/failed.png differ diff --git a/Assets/UnityTestTools/Common/Editor/icons/failed.png.meta b/Assets/UnityTestTools/Common/Editor/icons/failed.png.meta new file mode 100644 index 00000000..03673daa --- /dev/null +++ b/Assets/UnityTestTools/Common/Editor/icons/failed.png.meta @@ -0,0 +1,35 @@ +fileFormatVersion: 2 +guid: 41488feb372865440b7c01773f04c0cf +TextureImporter: + serializedVersion: 2 + mipmaps: + mipMapMode: 0 + enableMipMap: 0 + linearTexture: 1 + correctGamma: 0 + fadeOut: 0 + borderMipMap: 0 + mipMapFadeDistanceStart: 1 + mipMapFadeDistanceEnd: 3 + bumpmap: + convertToNormalMap: 0 + externalNormalMap: 0 + heightScale: .25 + normalMapFilter: 0 + isReadable: 0 + grayScaleToAlpha: 0 + generateCubemap: 0 + seamlessCubemap: 0 + textureFormat: -1 + maxTextureSize: 1024 + textureSettings: + filterMode: -1 + aniso: 1 + mipBias: -1 + wrapMode: 1 + nPOTScale: 0 + lightmap: 0 + compressionQuality: 50 + textureType: 2 + buildTargetSettings: [] + userData: diff --git a/Assets/UnityTestTools/Common/Editor/icons/ignored.png b/Assets/UnityTestTools/Common/Editor/icons/ignored.png new file mode 100644 index 00000000..0190e59b Binary files /dev/null and b/Assets/UnityTestTools/Common/Editor/icons/ignored.png differ diff --git a/Assets/UnityTestTools/Common/Editor/icons/ignored.png.meta b/Assets/UnityTestTools/Common/Editor/icons/ignored.png.meta new file mode 100644 index 00000000..d14cc3b7 --- /dev/null +++ b/Assets/UnityTestTools/Common/Editor/icons/ignored.png.meta @@ -0,0 +1,35 @@ +fileFormatVersion: 2 +guid: 0076bfa6073f17546b3535ac1b456b0b +TextureImporter: + serializedVersion: 2 + mipmaps: + mipMapMode: 0 + enableMipMap: 0 + linearTexture: 1 + correctGamma: 0 + fadeOut: 0 + borderMipMap: 0 + mipMapFadeDistanceStart: 1 + mipMapFadeDistanceEnd: 3 + bumpmap: + convertToNormalMap: 0 + externalNormalMap: 0 + heightScale: .25 + normalMapFilter: 0 + isReadable: 0 + grayScaleToAlpha: 0 + generateCubemap: 0 + seamlessCubemap: 0 + textureFormat: -1 + maxTextureSize: 1024 + textureSettings: + filterMode: -1 + aniso: 1 + mipBias: -1 + wrapMode: 1 + nPOTScale: 0 + lightmap: 0 + compressionQuality: 50 + textureType: 2 + buildTargetSettings: [] + userData: diff --git a/Assets/UnityTestTools/Common/Editor/icons/inconclusive.png b/Assets/UnityTestTools/Common/Editor/icons/inconclusive.png new file mode 100644 index 00000000..df398ddb Binary files /dev/null and b/Assets/UnityTestTools/Common/Editor/icons/inconclusive.png differ diff --git a/Assets/UnityTestTools/Common/Editor/icons/inconclusive.png.meta b/Assets/UnityTestTools/Common/Editor/icons/inconclusive.png.meta new file mode 100644 index 00000000..7c93bc45 --- /dev/null +++ b/Assets/UnityTestTools/Common/Editor/icons/inconclusive.png.meta @@ -0,0 +1,35 @@ +fileFormatVersion: 2 +guid: e28761099904678488cdddf7b6be2ceb +TextureImporter: + serializedVersion: 2 + mipmaps: + mipMapMode: 0 + enableMipMap: 0 + linearTexture: 1 + correctGamma: 0 + fadeOut: 0 + borderMipMap: 0 + mipMapFadeDistanceStart: 1 + mipMapFadeDistanceEnd: 3 + bumpmap: + convertToNormalMap: 0 + externalNormalMap: 0 + heightScale: .25 + normalMapFilter: 0 + isReadable: 0 + grayScaleToAlpha: 0 + generateCubemap: 0 + seamlessCubemap: 0 + textureFormat: -1 + maxTextureSize: 1024 + textureSettings: + filterMode: -1 + aniso: 1 + mipBias: -1 + wrapMode: 1 + nPOTScale: 0 + lightmap: 0 + compressionQuality: 50 + textureType: 2 + buildTargetSettings: [] + userData: diff --git a/Assets/UnityTestTools/Common/Editor/icons/normal.png b/Assets/UnityTestTools/Common/Editor/icons/normal.png new file mode 100644 index 00000000..6a04f795 Binary files /dev/null and b/Assets/UnityTestTools/Common/Editor/icons/normal.png differ diff --git a/Assets/UnityTestTools/Common/Editor/icons/normal.png.meta b/Assets/UnityTestTools/Common/Editor/icons/normal.png.meta new file mode 100644 index 00000000..34895eb5 --- /dev/null +++ b/Assets/UnityTestTools/Common/Editor/icons/normal.png.meta @@ -0,0 +1,35 @@ +fileFormatVersion: 2 +guid: a9f3c491f4c2f9f43ac33a27c16913dd +TextureImporter: + serializedVersion: 2 + mipmaps: + mipMapMode: 0 + enableMipMap: 0 + linearTexture: 1 + correctGamma: 0 + fadeOut: 0 + borderMipMap: 0 + mipMapFadeDistanceStart: 1 + mipMapFadeDistanceEnd: 3 + bumpmap: + convertToNormalMap: 0 + externalNormalMap: 0 + heightScale: .25 + normalMapFilter: 0 + isReadable: 0 + grayScaleToAlpha: 0 + generateCubemap: 0 + seamlessCubemap: 0 + textureFormat: -1 + maxTextureSize: 1024 + textureSettings: + filterMode: -1 + aniso: 1 + mipBias: -1 + wrapMode: 1 + nPOTScale: 0 + lightmap: 0 + compressionQuality: 50 + textureType: 2 + buildTargetSettings: [] + userData: diff --git a/Assets/UnityTestTools/Common/Editor/icons/passed.png b/Assets/UnityTestTools/Common/Editor/icons/passed.png new file mode 100644 index 00000000..1edd2860 Binary files /dev/null and b/Assets/UnityTestTools/Common/Editor/icons/passed.png differ diff --git a/Assets/UnityTestTools/Common/Editor/icons/passed.png.meta b/Assets/UnityTestTools/Common/Editor/icons/passed.png.meta new file mode 100644 index 00000000..876d32d9 --- /dev/null +++ b/Assets/UnityTestTools/Common/Editor/icons/passed.png.meta @@ -0,0 +1,35 @@ +fileFormatVersion: 2 +guid: 31f7928179ee46d4690d274579efb037 +TextureImporter: + serializedVersion: 2 + mipmaps: + mipMapMode: 0 + enableMipMap: 0 + linearTexture: 1 + correctGamma: 0 + fadeOut: 0 + borderMipMap: 0 + mipMapFadeDistanceStart: 1 + mipMapFadeDistanceEnd: 3 + bumpmap: + convertToNormalMap: 0 + externalNormalMap: 0 + heightScale: .25 + normalMapFilter: 0 + isReadable: 0 + grayScaleToAlpha: 0 + generateCubemap: 0 + seamlessCubemap: 0 + textureFormat: -1 + maxTextureSize: 1024 + textureSettings: + filterMode: -1 + aniso: 1 + mipBias: -1 + wrapMode: 1 + nPOTScale: 0 + lightmap: 0 + compressionQuality: 50 + textureType: 2 + buildTargetSettings: [] + userData: diff --git a/Assets/UnityTestTools/Common/Editor/icons/stopwatch.png b/Assets/UnityTestTools/Common/Editor/icons/stopwatch.png new file mode 100644 index 00000000..ac5721c5 Binary files /dev/null and b/Assets/UnityTestTools/Common/Editor/icons/stopwatch.png differ diff --git a/Assets/UnityTestTools/Common/Editor/icons/stopwatch.png.meta b/Assets/UnityTestTools/Common/Editor/icons/stopwatch.png.meta new file mode 100644 index 00000000..f39adad6 --- /dev/null +++ b/Assets/UnityTestTools/Common/Editor/icons/stopwatch.png.meta @@ -0,0 +1,35 @@ +fileFormatVersion: 2 +guid: f73f95ae19d51af47ad56044f2779aa1 +TextureImporter: + serializedVersion: 2 + mipmaps: + mipMapMode: 0 + enableMipMap: 0 + linearTexture: 1 + correctGamma: 0 + fadeOut: 0 + borderMipMap: 0 + mipMapFadeDistanceStart: 1 + mipMapFadeDistanceEnd: 3 + bumpmap: + convertToNormalMap: 0 + externalNormalMap: 0 + heightScale: .25 + normalMapFilter: 0 + isReadable: 0 + grayScaleToAlpha: 0 + generateCubemap: 0 + seamlessCubemap: 0 + textureFormat: -1 + maxTextureSize: 1024 + textureSettings: + filterMode: -1 + aniso: 1 + mipBias: -1 + wrapMode: 1 + nPOTScale: 0 + lightmap: 0 + compressionQuality: 50 + textureType: 2 + buildTargetSettings: [] + userData: diff --git a/Assets/UnityTestTools/Common/ITestResult.cs b/Assets/UnityTestTools/Common/ITestResult.cs new file mode 100644 index 00000000..13f5fc3a --- /dev/null +++ b/Assets/UnityTestTools/Common/ITestResult.cs @@ -0,0 +1,29 @@ +using System; +using System.Collections.Generic; +using UnityEngine; +using UnityTest; + +public interface ITestResult +{ + TestResultState ResultState { get; } + + string Message { get; } + + string Logs { get; } + + bool Executed { get; } + + string Name { get; } + + string FullName { get; } + + string Id { get; } + + bool IsSuccess { get; } + + double Duration { get; } + + string StackTrace { get; } + + bool IsIgnored { get; } +} diff --git a/Assets/UnityTestTools/Common/ITestResult.cs.meta b/Assets/UnityTestTools/Common/ITestResult.cs.meta new file mode 100644 index 00000000..4864197a --- /dev/null +++ b/Assets/UnityTestTools/Common/ITestResult.cs.meta @@ -0,0 +1,8 @@ +fileFormatVersion: 2 +guid: d1e4e2c4d00b3f2469494fc0f67cdeae +MonoImporter: + serializedVersion: 2 + defaultReferences: [] + executionOrder: 0 + icon: {instanceID: 0} + userData: diff --git a/Assets/UnityTestTools/Common/Settings.meta b/Assets/UnityTestTools/Common/Settings.meta new file mode 100644 index 00000000..a208ce0f --- /dev/null +++ b/Assets/UnityTestTools/Common/Settings.meta @@ -0,0 +1,9 @@ +fileFormatVersion: 2 +guid: 4f7795beea73b4d50bd536d0012e8e85 +folderAsset: yes +timeCreated: 1438859728 +licenseType: Free +DefaultImporter: + userData: + assetBundleName: + assetBundleVariant: diff --git a/Assets/UnityTestTools/Common/Settings/IntegrationTestsRunnerSettings.asset b/Assets/UnityTestTools/Common/Settings/IntegrationTestsRunnerSettings.asset new file mode 100644 index 00000000..2f33e8b0 --- /dev/null +++ b/Assets/UnityTestTools/Common/Settings/IntegrationTestsRunnerSettings.asset @@ -0,0 +1,15 @@ +%YAML 1.1 +%TAG !u! tag:unity3d.com,2011: +--- !u!114 &11400000 +MonoBehaviour: + m_ObjectHideFlags: 0 + m_PrefabParentObject: {fileID: 0} + m_PrefabInternal: {fileID: 0} + m_GameObject: {fileID: 0} + m_Enabled: 1 + m_EditorHideFlags: 0 + m_Script: {fileID: 11500000, guid: 5d01dc4c8f278da489d7d54c83f19cb9, type: 3} + m_Name: IntegrationTestsRunnerSettings + m_EditorClassIdentifier: + blockUIWhenRunning: 1 + pauseOnTestFailure: 0 diff --git a/Assets/UnityTestTools/Common/Settings/IntegrationTestsRunnerSettings.asset.meta b/Assets/UnityTestTools/Common/Settings/IntegrationTestsRunnerSettings.asset.meta new file mode 100644 index 00000000..05dee3d0 --- /dev/null +++ b/Assets/UnityTestTools/Common/Settings/IntegrationTestsRunnerSettings.asset.meta @@ -0,0 +1,8 @@ +fileFormatVersion: 2 +guid: ad7c6353939274fc8a5e756095b99a64 +timeCreated: 1438859728 +licenseType: Free +NativeFormatImporter: + userData: + assetBundleName: + assetBundleVariant: diff --git a/Assets/UnityTestTools/Common/Settings/UnitTestsRunnerSettings.asset b/Assets/UnityTestTools/Common/Settings/UnitTestsRunnerSettings.asset new file mode 100644 index 00000000..1e00d2e2 --- /dev/null +++ b/Assets/UnityTestTools/Common/Settings/UnitTestsRunnerSettings.asset @@ -0,0 +1,17 @@ +%YAML 1.1 +%TAG !u! tag:unity3d.com,2011: +--- !u!114 &11400000 +MonoBehaviour: + m_ObjectHideFlags: 0 + m_PrefabParentObject: {fileID: 0} + m_PrefabInternal: {fileID: 0} + m_GameObject: {fileID: 0} + m_Enabled: 1 + m_EditorHideFlags: 0 + m_Script: {fileID: 11500000, guid: 4a24a0b0a24461a4ab99853f8b145e5c, type: 3} + m_Name: UnitTestsRunnerSettings + m_EditorClassIdentifier: + runOnRecompilation: 0 + horizontalSplit: 1 + autoSaveSceneBeforeRun: 0 + runTestOnANewScene: 0 diff --git a/Assets/UnityTestTools/Common/Settings/UnitTestsRunnerSettings.asset.meta b/Assets/UnityTestTools/Common/Settings/UnitTestsRunnerSettings.asset.meta new file mode 100644 index 00000000..108d02cc --- /dev/null +++ b/Assets/UnityTestTools/Common/Settings/UnitTestsRunnerSettings.asset.meta @@ -0,0 +1,8 @@ +fileFormatVersion: 2 +guid: 41cffd1c0774e41f1b80ef7fb6f3f158 +timeCreated: 1438859733 +licenseType: Free +NativeFormatImporter: + userData: + assetBundleName: + assetBundleVariant: diff --git a/Assets/UnityTestTools/Common/TestResultState.cs b/Assets/UnityTestTools/Common/TestResultState.cs new file mode 100644 index 00000000..3dc4eb8c --- /dev/null +++ b/Assets/UnityTestTools/Common/TestResultState.cs @@ -0,0 +1,46 @@ +using System; +using System.Collections.Generic; +using UnityEngine; + +namespace UnityTest +{ + public enum TestResultState : byte + { + Inconclusive = 0, + + /// + /// The test was not runnable. + /// + NotRunnable = 1, + + /// + /// The test has been skipped. + /// + Skipped = 2, + + /// + /// The test has been ignored. + /// + Ignored = 3, + + /// + /// The test succeeded + /// + Success = 4, + + /// + /// The test failed + /// + Failure = 5, + + /// + /// The test encountered an unexpected exception + /// + Error = 6, + + /// + /// The test was cancelled by the user + /// + Cancelled = 7 + } +} diff --git a/Assets/UnityTestTools/Common/TestResultState.cs.meta b/Assets/UnityTestTools/Common/TestResultState.cs.meta new file mode 100644 index 00000000..e1576c7c --- /dev/null +++ b/Assets/UnityTestTools/Common/TestResultState.cs.meta @@ -0,0 +1,8 @@ +fileFormatVersion: 2 +guid: da3ca54ee4cce064989d27165f3081fb +MonoImporter: + serializedVersion: 2 + defaultReferences: [] + executionOrder: 0 + icon: {instanceID: 0} + userData: diff --git a/Assets/UnityTestTools/Documentation.url b/Assets/UnityTestTools/Documentation.url new file mode 100644 index 00000000..9ebdf1ad --- /dev/null +++ b/Assets/UnityTestTools/Documentation.url @@ -0,0 +1,3 @@ +[InternetShortcut] +URL=https://bitbucket.org/Unity-Technologies/unitytesttools/wiki +IconIndex=0 \ No newline at end of file diff --git a/Assets/UnityTestTools/Documentation.url.meta b/Assets/UnityTestTools/Documentation.url.meta new file mode 100644 index 00000000..b5db4964 --- /dev/null +++ b/Assets/UnityTestTools/Documentation.url.meta @@ -0,0 +1,4 @@ +fileFormatVersion: 2 +guid: 28f1b62e1364e5a4e88f7bb94dbcf183 +DefaultImporter: + userData: diff --git a/Assets/UnityTestTools/IntegrationTestsFramework.meta b/Assets/UnityTestTools/IntegrationTestsFramework.meta new file mode 100644 index 00000000..da228722 --- /dev/null +++ b/Assets/UnityTestTools/IntegrationTestsFramework.meta @@ -0,0 +1,5 @@ +fileFormatVersion: 2 +guid: 241054a0fe63fbb4bb51609fce9b3112 +folderAsset: yes +DefaultImporter: + userData: diff --git a/Assets/UnityTestTools/IntegrationTestsFramework/TestRunner.meta b/Assets/UnityTestTools/IntegrationTestsFramework/TestRunner.meta new file mode 100644 index 00000000..c65a67d5 --- /dev/null +++ b/Assets/UnityTestTools/IntegrationTestsFramework/TestRunner.meta @@ -0,0 +1,5 @@ +fileFormatVersion: 2 +guid: da93545c3ab1aa043bcfb22281b1f66c +folderAsset: yes +DefaultImporter: + userData: diff --git a/Assets/UnityTestTools/IntegrationTestsFramework/TestRunner/DTOFormatter.cs b/Assets/UnityTestTools/IntegrationTestsFramework/TestRunner/DTOFormatter.cs new file mode 100644 index 00000000..c3f72747 --- /dev/null +++ b/Assets/UnityTestTools/IntegrationTestsFramework/TestRunner/DTOFormatter.cs @@ -0,0 +1,131 @@ +using UnityEngine; +using System; +using System.IO; + +namespace UnityTest +{ + + public class DTOFormatter { + + private interface ITransferInterface + { + void Transfer(ref ResultDTO.MessageType val); + void Transfer(ref TestResultState val); + void Transfer(ref byte val); + void Transfer(ref bool val); + void Transfer(ref int val); + void Transfer(ref float val); + void Transfer(ref double val); + void Transfer(ref string val); + } + + private class Writer : ITransferInterface + { + private readonly Stream _stream; + public Writer(Stream stream) { _stream = stream; } + + private void WriteConvertedNumber(byte[] bytes) + { + if(BitConverter.IsLittleEndian) + Array.Reverse(bytes); + _stream.Write(bytes, 0, bytes.Length); + } + + public void Transfer(ref ResultDTO.MessageType val) { _stream.WriteByte((byte)val); } + public void Transfer(ref TestResultState val) { _stream.WriteByte((byte)val); } + public void Transfer(ref byte val) { _stream.WriteByte(val); } + public void Transfer(ref bool val) { _stream.WriteByte((byte)(val ? 0x01 : 0x00)); } + public void Transfer(ref int val) { WriteConvertedNumber(BitConverter.GetBytes(val)); } + public void Transfer(ref float val) { WriteConvertedNumber(BitConverter.GetBytes(val)); } + public void Transfer(ref double val) { WriteConvertedNumber(BitConverter.GetBytes(val)); } + + public void Transfer(ref string val) + { + var bytes = System.Text.Encoding.BigEndianUnicode.GetBytes(val); + int length = bytes.Length; + Transfer(ref length); + _stream.Write(bytes, 0, bytes.Length); + } + } + + private class Reader : ITransferInterface + { + private readonly Stream _stream; + public Reader(Stream stream) { _stream = stream; } + + private byte[] ReadConvertedNumber(int size) + { + byte[] buffer = new byte[size]; + _stream.Read (buffer, 0, buffer.Length); + if(BitConverter.IsLittleEndian) + Array.Reverse(buffer); + return buffer; + } + + public void Transfer(ref ResultDTO.MessageType val) { val = (ResultDTO.MessageType)_stream.ReadByte(); } + public void Transfer(ref TestResultState val) { val = (TestResultState)_stream.ReadByte(); } + public void Transfer(ref byte val) { val = (byte)_stream.ReadByte(); } + public void Transfer(ref bool val) { val = (_stream.ReadByte() != 0); } + public void Transfer(ref int val) { val = BitConverter.ToInt32(ReadConvertedNumber(4), 0); } + public void Transfer(ref float val) { val = BitConverter.ToSingle(ReadConvertedNumber(4), 0); } + public void Transfer(ref double val) { val = BitConverter.ToDouble(ReadConvertedNumber(8), 0); } + + public void Transfer(ref string val) + { + int length = 0; + Transfer (ref length); + var bytes = new byte[length]; + _stream.Read(bytes, 0, length); + val = System.Text.Encoding.BigEndianUnicode.GetString(bytes); + } + } + + private void Transfer(ResultDTO dto, ITransferInterface transfer) + { + transfer.Transfer(ref dto.messageType); + + transfer.Transfer(ref dto.levelCount); + transfer.Transfer(ref dto.loadedLevel); + transfer.Transfer(ref dto.loadedLevelName); + + if(dto.messageType == ResultDTO.MessageType.Ping + || dto.messageType == ResultDTO.MessageType.RunStarted + || dto.messageType == ResultDTO.MessageType.RunFinished + || dto.messageType == ResultDTO.MessageType.RunInterrupted) + return; + + transfer.Transfer(ref dto.testName); + transfer.Transfer(ref dto.testTimeout); + + if(dto.messageType == ResultDTO.MessageType.TestStarted) + return; + + if(transfer is Reader) + dto.testResult = new SerializableTestResult(); + SerializableTestResult str = (SerializableTestResult)dto.testResult; + + transfer.Transfer(ref str.resultState); + transfer.Transfer(ref str.message); + transfer.Transfer(ref str.executed); + transfer.Transfer(ref str.name); + transfer.Transfer(ref str.fullName); + transfer.Transfer(ref str.id); + transfer.Transfer(ref str.isSuccess); + transfer.Transfer(ref str.duration); + transfer.Transfer(ref str.stackTrace); + } + + public void Serialize (System.IO.Stream stream, ResultDTO dto) + { + Transfer(dto, new Writer(stream)); + } + + public object Deserialize (System.IO.Stream stream) + { + var result = (ResultDTO)System.Runtime.Serialization.FormatterServices.GetSafeUninitializedObject(typeof(ResultDTO)); + Transfer (result, new Reader(stream)); + return result; + } + } + +} \ No newline at end of file diff --git a/Assets/UnityTestTools/IntegrationTestsFramework/TestRunner/DTOFormatter.cs.meta b/Assets/UnityTestTools/IntegrationTestsFramework/TestRunner/DTOFormatter.cs.meta new file mode 100644 index 00000000..f83bde0c --- /dev/null +++ b/Assets/UnityTestTools/IntegrationTestsFramework/TestRunner/DTOFormatter.cs.meta @@ -0,0 +1,8 @@ +fileFormatVersion: 2 +guid: 7ae2470508a854b1c9df5375d03f8f58 +MonoImporter: + serializedVersion: 2 + defaultReferences: [] + executionOrder: 0 + icon: {instanceID: 0} + userData: diff --git a/Assets/UnityTestTools/IntegrationTestsFramework/TestRunner/Editor.meta b/Assets/UnityTestTools/IntegrationTestsFramework/TestRunner/Editor.meta new file mode 100644 index 00000000..bd38839c --- /dev/null +++ b/Assets/UnityTestTools/IntegrationTestsFramework/TestRunner/Editor.meta @@ -0,0 +1,5 @@ +fileFormatVersion: 2 +guid: caee08596a5965747b8edfde19e2f873 +folderAsset: yes +DefaultImporter: + userData: diff --git a/Assets/UnityTestTools/IntegrationTestsFramework/TestRunner/Editor/Batch.cs b/Assets/UnityTestTools/IntegrationTestsFramework/TestRunner/Editor/Batch.cs new file mode 100644 index 00000000..aef16a90 --- /dev/null +++ b/Assets/UnityTestTools/IntegrationTestsFramework/TestRunner/Editor/Batch.cs @@ -0,0 +1,166 @@ +using System; +using System.Collections.Generic; +using System.IO; +using System.Linq; +using UnityEditor; +using UnityEditorInternal; +using UnityEngine; +using UnityTest.IntegrationTests; + +namespace UnityTest +{ + public static partial class Batch + { + private const string k_TestScenesParam = "-testscenes="; + const string k_TargetPlatformParam = "-targetPlatform="; + const string k_ResultFileDirParam = "-resultsFileDirectory="; + + public static void RunIntegrationTests() + { + var targetPlatform = GetTargetPlatform(); + var sceneList = GetTestScenesList(); + if (sceneList.Count == 0) + sceneList = FindTestScenesInProject(); + RunIntegrationTests(targetPlatform, sceneList); + } + + public static void RunIntegrationTests(BuildTarget ? targetPlatform) + { + var sceneList = FindTestScenesInProject(); + RunIntegrationTests(targetPlatform, sceneList); + } + + + public static void RunIntegrationTests(BuildTarget? targetPlatform, List sceneList) + { + if (targetPlatform.HasValue) + BuildAndRun(targetPlatform.Value, sceneList); + else + RunInEditor(sceneList); + } + + private static void BuildAndRun(BuildTarget target, List sceneList) + { + var resultFilePath = GetParameterArgument(k_ResultFileDirParam); + + const int port = 0; + var ipList = TestRunnerConfigurator.GetAvailableNetworkIPs(); + + var config = new PlatformRunnerConfiguration + { + buildTarget = target, + scenes = sceneList.ToArray(), + projectName = "IntegrationTests", + resultsDir = resultFilePath, + sendResultsOverNetwork = InternalEditorUtility.inBatchMode, + ipList = ipList, + port = port + }; + + if (Application.isWebPlayer) + { + config.sendResultsOverNetwork = false; + Debug.Log("You can't use WebPlayer as active platform for running integration tests. Switching to Standalone"); + EditorUserBuildSettings.SwitchActiveBuildTarget(BuildTarget.StandaloneWindows); + } + + PlatformRunner.BuildAndRunInPlayer(config); + } + + private static void RunInEditor(List sceneList) + { + CheckActiveBuildTarget(); + + NetworkResultsReceiver.StopReceiver(); + if (sceneList == null || sceneList.Count == 0) + { + Debug.Log("No scenes on the list"); + EditorApplication.Exit(returnCodeRunError); + return; + } + EditorBuildSettings.scenes = sceneList.Select(s => new EditorBuildSettingsScene(s, true)).ToArray(); + EditorApplication.OpenScene(sceneList.First()); + GuiHelper.SetConsoleErrorPause(false); + + var config = new PlatformRunnerConfiguration + { + resultsDir = GetParameterArgument(k_ResultFileDirParam), + ipList = TestRunnerConfigurator.GetAvailableNetworkIPs(), + port = PlatformRunnerConfiguration.TryToGetFreePort(), + runInEditor = true, + }; + + var settings = new PlayerSettingConfigurator(true); + settings.AddConfigurationFile(TestRunnerConfigurator.integrationTestsNetwork, string.Join("\n", config.GetConnectionIPs())); + + NetworkResultsReceiver.StartReceiver(config); + + EditorApplication.isPlaying = true; + } + + static void CheckActiveBuildTarget() + { + var notSupportedPlatforms = new[] { "MetroPlayer", "WebPlayer", "WebPlayerStreamed" }; + if (notSupportedPlatforms.Contains(EditorUserBuildSettings.activeBuildTarget.ToString())) + { + Debug.Log("activeBuildTarget can not be " + + EditorUserBuildSettings.activeBuildTarget + + " use buildTarget parameter to open Unity."); + } + } + + private static BuildTarget ? GetTargetPlatform() + { + string platformString = null; + BuildTarget buildTarget; + foreach (var arg in Environment.GetCommandLineArgs()) + { + if (arg.ToLower().StartsWith(k_TargetPlatformParam.ToLower())) + { + platformString = arg.Substring(k_ResultFilePathParam.Length); + break; + } + } + try + { + if (platformString == null) return null; + buildTarget = (BuildTarget)Enum.Parse(typeof(BuildTarget), platformString); + } + catch + { + return null; + } + return buildTarget; + } + + private static List FindTestScenesInProject() + { + var integrationTestScenePattern = "*Test?.unity"; + return Directory.GetFiles("Assets", integrationTestScenePattern, SearchOption.AllDirectories).ToList(); + } + + private static List GetTestScenesList() + { + var sceneList = new List(); + foreach (var arg in Environment.GetCommandLineArgs()) + { + if (arg.ToLower().StartsWith(k_TestScenesParam)) + { + var scenesFromParam = arg.Substring(k_TestScenesParam.Length).Split(','); + foreach (var scene in scenesFromParam) + { + var sceneName = scene; + if (!sceneName.EndsWith(".unity")) + sceneName += ".unity"; + var foundScenes = Directory.GetFiles(Directory.GetCurrentDirectory(), sceneName, SearchOption.AllDirectories); + if (foundScenes.Length == 1) + sceneList.Add(foundScenes[0].Substring(Directory.GetCurrentDirectory().Length + 1)); + else + Debug.Log(sceneName + " not found or multiple entries found"); + } + } + } + return sceneList.Where(s => !string.IsNullOrEmpty(s)).Distinct().ToList(); + } + } +} diff --git a/Assets/UnityTestTools/IntegrationTestsFramework/TestRunner/Editor/Batch.cs.meta b/Assets/UnityTestTools/IntegrationTestsFramework/TestRunner/Editor/Batch.cs.meta new file mode 100644 index 00000000..248a6ce2 --- /dev/null +++ b/Assets/UnityTestTools/IntegrationTestsFramework/TestRunner/Editor/Batch.cs.meta @@ -0,0 +1,8 @@ +fileFormatVersion: 2 +guid: 29d4fb050362c5b43aea52342045543a +MonoImporter: + serializedVersion: 2 + defaultReferences: [] + executionOrder: 0 + icon: {instanceID: 0} + userData: diff --git a/Assets/UnityTestTools/IntegrationTestsFramework/TestRunner/Editor/IntegrationTestsHierarchyAnnotation.cs b/Assets/UnityTestTools/IntegrationTestsFramework/TestRunner/Editor/IntegrationTestsHierarchyAnnotation.cs new file mode 100644 index 00000000..4aa5dc41 --- /dev/null +++ b/Assets/UnityTestTools/IntegrationTestsFramework/TestRunner/Editor/IntegrationTestsHierarchyAnnotation.cs @@ -0,0 +1,44 @@ +using UnityEngine; +using System.Collections; +using UnityEditor; + +namespace UnityTest +{ + + [InitializeOnLoad] + public class IntegrationTestsHierarchyAnnotation { + + static IntegrationTestsHierarchyAnnotation() + { + EditorApplication.hierarchyWindowItemOnGUI += DoAnnotationGUI; + } + + public static void DoAnnotationGUI(int id, Rect rect) + { + var obj = EditorUtility.InstanceIDToObject(id) as GameObject; + if(!obj) return; + + var tc = obj.GetComponent(); + if(!tc) return; + + if (!EditorApplication.isPlayingOrWillChangePlaymode + && rect.Contains(Event.current.mousePosition) + && Event.current.type == EventType.MouseDown + && Event.current.button == 1) + { + IntegrationTestRendererBase.DrawContextMenu(tc); + Event.current.Use (); + } + + EditorGUIUtility.SetIconSize(new Vector2(15, 15)); + var result = IntegrationTestsRunnerWindow.GetResultForTest(tc); + if (result != null) + { + var icon = result.Executed ? IntegrationTestRendererBase.GetIconForResult(result.resultType) : Icons.UnknownImg; + EditorGUI.LabelField(new Rect(rect.xMax - 18, rect.yMin - 2, rect.width, rect.height), new GUIContent(icon)); + } + EditorGUIUtility.SetIconSize(Vector2.zero); + } + } + +} \ No newline at end of file diff --git a/Assets/UnityTestTools/IntegrationTestsFramework/TestRunner/Editor/IntegrationTestsHierarchyAnnotation.cs.meta b/Assets/UnityTestTools/IntegrationTestsFramework/TestRunner/Editor/IntegrationTestsHierarchyAnnotation.cs.meta new file mode 100644 index 00000000..4154bdc9 --- /dev/null +++ b/Assets/UnityTestTools/IntegrationTestsFramework/TestRunner/Editor/IntegrationTestsHierarchyAnnotation.cs.meta @@ -0,0 +1,8 @@ +fileFormatVersion: 2 +guid: 219cdb080b08741948fc5deb8c7d47f0 +MonoImporter: + serializedVersion: 2 + defaultReferences: [] + executionOrder: 0 + icon: {instanceID: 0} + userData: diff --git a/Assets/UnityTestTools/IntegrationTestsFramework/TestRunner/Editor/IntegrationTestsRunnerSettings.cs b/Assets/UnityTestTools/IntegrationTestsFramework/TestRunner/Editor/IntegrationTestsRunnerSettings.cs new file mode 100644 index 00000000..1e8e466d --- /dev/null +++ b/Assets/UnityTestTools/IntegrationTestsFramework/TestRunner/Editor/IntegrationTestsRunnerSettings.cs @@ -0,0 +1,25 @@ +using System; +using System.Collections.Generic; +using UnityEngine; +using UnityEditor; + +namespace UnityTest +{ + public class IntegrationTestsRunnerSettings : ProjectSettingsBase + { + public bool blockUIWhenRunning = true; + public bool pauseOnTestFailure; + + public void ToggleBlockUIWhenRunning () + { + blockUIWhenRunning = !blockUIWhenRunning; + Save (); + } + + public void TogglePauseOnTestFailure() + { + pauseOnTestFailure = !pauseOnTestFailure; + Save (); + } + } +} diff --git a/Assets/UnityTestTools/IntegrationTestsFramework/TestRunner/Editor/IntegrationTestsRunnerSettings.cs.meta b/Assets/UnityTestTools/IntegrationTestsFramework/TestRunner/Editor/IntegrationTestsRunnerSettings.cs.meta new file mode 100644 index 00000000..d18086a7 --- /dev/null +++ b/Assets/UnityTestTools/IntegrationTestsFramework/TestRunner/Editor/IntegrationTestsRunnerSettings.cs.meta @@ -0,0 +1,8 @@ +fileFormatVersion: 2 +guid: 5d01dc4c8f278da489d7d54c83f19cb9 +MonoImporter: + serializedVersion: 2 + defaultReferences: [] + executionOrder: 0 + icon: {instanceID: 0} + userData: diff --git a/Assets/UnityTestTools/IntegrationTestsFramework/TestRunner/Editor/IntegrationTestsRunnerWindow.cs b/Assets/UnityTestTools/IntegrationTestsFramework/TestRunner/Editor/IntegrationTestsRunnerWindow.cs new file mode 100644 index 00000000..d2fa3403 --- /dev/null +++ b/Assets/UnityTestTools/IntegrationTestsFramework/TestRunner/Editor/IntegrationTestsRunnerWindow.cs @@ -0,0 +1,583 @@ +using System; +using System.Collections.Generic; +using System.Linq; +using System.Text; +using System.Text.RegularExpressions; +using UnityEditor; +using UnityEngine; +using UnityTest.IntegrationTestRunner; + +namespace UnityTest +{ + [Serializable] + public class IntegrationTestsRunnerWindow : EditorWindow, IHasCustomMenu + { + #region GUI Contents + private readonly GUIContent m_GUICreateNewTest = new GUIContent("Create", "Create new test"); + private readonly GUIContent m_GUIRunSelectedTests = new GUIContent("Run Selected", "Run selected test(s)"); + private readonly GUIContent m_GUIRunAllTests = new GUIContent("Run All", "Run all tests"); + private readonly GUIContent m_GUIBlockUI = new GUIContent("Block UI when running", "Block UI when running tests"); + private readonly GUIContent m_GUIPauseOnFailure = new GUIContent("Pause on test failure"); + #endregion + + #region runner steerign vars + private static IntegrationTestsRunnerWindow s_Instance; + [SerializeField] private List m_TestsToRun; + [SerializeField] private List m_DynamicTestsToRun; + [SerializeField] private bool m_ReadyToRun; + private bool m_IsBuilding; + public static bool selectedInHierarchy; + private float m_HorizontalSplitBarPosition = 200; + private Vector2 m_TestInfoScroll, m_TestListScroll; + private IntegrationTestRendererBase[] m_TestLines; + private string m_CurrectSceneName; + private TestFilterSettings m_FilterSettings; + + Vector2 m_resultTextSize; + string m_resultText; + GameObject m_lastSelectedGO; + int m_resultTestMaxLength = 15000; + + [SerializeField] private GameObject m_SelectedLine; + [SerializeField] private List m_ResultList = new List(); + [SerializeField] private List m_FoldMarkers = new List(); + + private IntegrationTestsRunnerSettings m_Settings; + + #endregion + + + static IntegrationTestsRunnerWindow() + { + InitBackgroundRunners(); + } + + private static void InitBackgroundRunners() + { + EditorApplication.hierarchyWindowItemOnGUI -= OnHierarchyWindowItemDraw; + EditorApplication.hierarchyWindowItemOnGUI += OnHierarchyWindowItemDraw; + EditorApplication.hierarchyWindowChanged -= OnHierarchyChangeUpdate; + EditorApplication.hierarchyWindowChanged += OnHierarchyChangeUpdate; + EditorApplication.update -= BackgroundSceneChangeWatch; + EditorApplication.update += BackgroundSceneChangeWatch; + EditorApplication.playmodeStateChanged -= OnPlaymodeStateChanged; + EditorApplication.playmodeStateChanged += OnPlaymodeStateChanged; + } + + private static void OnPlaymodeStateChanged() + { + if (s_Instance && EditorApplication.isPlaying == EditorApplication.isPlayingOrWillChangePlaymode) + s_Instance.RebuildTestList(); + } + + public void OnDestroy() + { + EditorApplication.hierarchyWindowItemOnGUI -= OnHierarchyWindowItemDraw; + EditorApplication.update -= BackgroundSceneChangeWatch; + EditorApplication.hierarchyWindowChanged -= OnHierarchyChangeUpdate; + EditorApplication.playmodeStateChanged -= OnPlaymodeStateChanged; + + TestComponent.DestroyAllDynamicTests(); + } + + private static void BackgroundSceneChangeWatch() + { + if (!s_Instance) return; + if (s_Instance.m_CurrectSceneName != null && s_Instance.m_CurrectSceneName == EditorApplication.currentScene) return; + if (EditorApplication.isPlayingOrWillChangePlaymode) return; + TestComponent.DestroyAllDynamicTests(); + s_Instance.m_CurrectSceneName = EditorApplication.currentScene; + s_Instance.m_ResultList.Clear(); + s_Instance.RebuildTestList(); + } + + public void OnEnable() + { + titleContent = new GUIContent("Integration Tests"); + s_Instance = this; + + m_Settings = ProjectSettingsBase.Load(); + m_FilterSettings = new TestFilterSettings("UnityTest.IntegrationTestsRunnerWindow"); + + InitBackgroundRunners(); + if (!EditorApplication.isPlayingOrWillChangePlaymode && !m_ReadyToRun) RebuildTestList(); + } + + public void OnSelectionChange() + { + if (EditorApplication.isPlayingOrWillChangePlaymode + || Selection.objects == null + || Selection.objects.Length == 0) return; + + if (Selection.gameObjects.Length == 1) + { + var go = Selection.gameObjects.Single(); + var temp = go.transform; + while (temp != null) + { + var tc = temp.GetComponent(); + if (tc != null) break; + temp = temp.parent; + } + + if (temp != null) + { + SelectInHierarchy(temp.gameObject); + Selection.activeGameObject = temp.gameObject; + m_SelectedLine = temp.gameObject; + } + } + } + + public static void OnHierarchyChangeUpdate() + { + if (!s_Instance || s_Instance.m_TestLines == null || EditorApplication.isPlayingOrWillChangePlaymode) return; + + // create a test runner if it doesn't exist + TestRunner.GetTestRunner(); + + // make tests are not places under a go that is not a test itself + foreach (var test in TestComponent.FindAllTestsOnScene()) + { + if (test.gameObject.transform.parent != null && test.gameObject.transform.parent.gameObject.GetComponent() == null) + { + test.gameObject.transform.parent = null; + Debug.LogWarning("Tests need to be on top of the hierarchy or directly under another test."); + } + } + if (selectedInHierarchy) selectedInHierarchy = false; + else s_Instance.RebuildTestList(); + } + + public static TestResult GetResultForTest(TestComponent tc) + { + if(!s_Instance) return new TestResult(tc); + return s_Instance.m_ResultList.FirstOrDefault(r => r.GameObject == tc.gameObject); + } + + public static void OnHierarchyWindowItemDraw(int id, Rect rect) + { + var o = EditorUtility.InstanceIDToObject(id); + if (o is GameObject) + { + var go = o as GameObject; + + if (Event.current.type == EventType.MouseDown + && Event.current.button == 0 + && rect.Contains(Event.current.mousePosition)) + { + var temp = go.transform; + while (temp != null) + { + var c = temp.GetComponent(); + if (c != null) break; + temp = temp.parent; + } + if (temp != null) SelectInHierarchy(temp.gameObject); + } + } + } + + private static void SelectInHierarchy(GameObject gameObject) + { + if (!s_Instance) return; + if (gameObject == s_Instance.m_SelectedLine && gameObject.activeInHierarchy) return; + if (EditorApplication.isPlayingOrWillChangePlaymode) return; + if (!gameObject.activeSelf) + { + selectedInHierarchy = true; + gameObject.SetActive(true); + } + + var tests = TestComponent.FindAllTestsOnScene(); + var skipList = gameObject.GetComponentsInChildren(typeof(TestComponent), true).ToList(); + tests.RemoveAll(skipList.Contains); + foreach (var test in tests) + { + var enable = test.GetComponentsInChildren(typeof(TestComponent), true).Any(c => c.gameObject == gameObject); + if (test.gameObject.activeSelf != enable) test.gameObject.SetActive(enable); + } + } + + private void RunTests(IList tests) + { + if (!tests.Any() || EditorApplication.isCompiling || EditorApplication.isPlayingOrWillChangePlaymode) + return; + FocusWindowIfItsOpen(GetType()); + + var testComponents = tests.Where(t => t is TestComponent).Cast().ToList(); + var dynaminTests = testComponents.Where(t => t.dynamic).ToList(); + m_DynamicTestsToRun = dynaminTests.Select(c => c.dynamicTypeName).ToList(); + testComponents.RemoveAll(dynaminTests.Contains); + + m_TestsToRun = testComponents.Select( tc => tc.gameObject ).ToList(); + + m_ReadyToRun = true; + TestComponent.DisableAllTests(); + + EditorApplication.isPlaying = true; + } + + public void Update() + { + if (m_ReadyToRun && EditorApplication.isPlaying) + { + m_ReadyToRun = false; + var testRunner = TestRunner.GetTestRunner(); + testRunner.TestRunnerCallback.Add(new RunnerCallback(this)); + var testComponents = m_TestsToRun.Select(go => go.GetComponent()).ToList(); + testRunner.InitRunner(testComponents, m_DynamicTestsToRun); + } + } + + private void RebuildTestList() + { + m_TestLines = null; + if (!TestComponent.AnyTestsOnScene() + && !TestComponent.AnyDynamicTestForCurrentScene()) return; + + if (!EditorApplication.isPlayingOrWillChangePlaymode) + { + var dynamicTestsOnScene = TestComponent.FindAllDynamicTestsOnScene(); + var dynamicTestTypes = TestComponent.GetTypesWithHelpAttribute(EditorApplication.currentScene); + + foreach (var dynamicTestType in dynamicTestTypes) + { + var existingTests = dynamicTestsOnScene.Where(component => component.dynamicTypeName == dynamicTestType.AssemblyQualifiedName); + if (existingTests.Any()) + { + var testComponent = existingTests.Single(); + foreach (var c in testComponent.gameObject.GetComponents()) + { + var type = Type.GetType(testComponent.dynamicTypeName); + if (c is TestComponent || c is Transform || type.IsInstanceOfType(c)) continue; + DestroyImmediate(c); + } + dynamicTestsOnScene.Remove(existingTests.Single()); + continue; + } + TestComponent.CreateDynamicTest(dynamicTestType); + } + + foreach (var testComponent in dynamicTestsOnScene) + DestroyImmediate(testComponent.gameObject); + } + + var topTestList = TestComponent.FindAllTopTestsOnScene(); + + var newResultList = new List(); + m_TestLines = ParseTestList(topTestList, newResultList); + + var oldDynamicResults = m_ResultList.Where(result => result.dynamicTest); + foreach (var oldResult in m_ResultList) + { + var result = newResultList.Find(r => r.Id == oldResult.Id); + if (result == null) continue; + result.Update(oldResult); + } + newResultList.AddRange(oldDynamicResults.Where(r => !newResultList.Contains(r))); + m_ResultList = newResultList; + + IntegrationTestRendererBase.RunTest = RunTests; + IntegrationTestGroupLine.FoldMarkers = m_FoldMarkers; + IntegrationTestLine.Results = m_ResultList; + + m_FilterSettings.UpdateCounters(m_ResultList.Cast()); + + m_FoldMarkers.RemoveAll(o => o == null); + + selectedInHierarchy = true; + Repaint(); + } + + + private IntegrationTestRendererBase[] ParseTestList(List testList, List results) + { + var tempList = new List(); + foreach (var testObject in testList) + { + if (!testObject.IsTestGroup()) + { + var result = new TestResult(testObject); + if (results != null) + results.Add(result); + tempList.Add(new IntegrationTestLine(testObject.gameObject, result)); + continue; + } + var group = new IntegrationTestGroupLine(testObject.gameObject); + var children = testObject.gameObject.GetComponentsInChildren(typeof(TestComponent), true).Cast().ToList(); + children = children.Where(c => c.gameObject.transform.parent == testObject.gameObject.transform).ToList(); + group.AddChildren(ParseTestList(children, results)); + tempList.Add(group); + } + tempList.Sort(); + return tempList.ToArray(); + } + + public void OnGUI() + { + if (BuildPipeline.isBuildingPlayer) + { + m_IsBuilding = true; + } + else if (m_IsBuilding) + { + m_IsBuilding = false; + Repaint(); + } + + PrintHeadPanel(); + + EditorGUILayout.BeginVertical(Styles.testList); + m_TestListScroll = EditorGUILayout.BeginScrollView(m_TestListScroll); + bool repaint = PrintTestList(m_TestLines); + GUILayout.FlexibleSpace(); + EditorGUILayout.EndScrollView(); + EditorGUILayout.EndVertical(); + + RenderDetails(); + + if (repaint) Repaint(); + } + + public void PrintHeadPanel() + { + EditorGUILayout.BeginHorizontal(EditorStyles.toolbar); + EditorGUI.BeginDisabledGroup(EditorApplication.isPlayingOrWillChangePlaymode); + if (GUILayout.Button(m_GUIRunAllTests, EditorStyles.toolbarButton)) + { + RunTests(TestComponent.FindAllTestsOnScene().Cast().ToList()); + } + EditorGUI.BeginDisabledGroup(!Selection.gameObjects.Any (t => t.GetComponent(typeof(ITestComponent)))); + if (GUILayout.Button(m_GUIRunSelectedTests, EditorStyles.toolbarButton)) + { + RunTests(Selection.gameObjects.Select(t => t.GetComponent(typeof(TestComponent))).Cast().ToList()); + } + EditorGUI.EndDisabledGroup(); + if (GUILayout.Button(m_GUICreateNewTest, EditorStyles.toolbarButton)) + { + var test = TestComponent.CreateTest(); + if (Selection.gameObjects.Length == 1 + && Selection.activeGameObject != null + && Selection.activeGameObject.GetComponent()) + { + test.transform.parent = Selection.activeGameObject.transform.parent; + } + Selection.activeGameObject = test; + RebuildTestList(); + } + EditorGUI.EndDisabledGroup(); + + GUILayout.FlexibleSpace (); + + m_FilterSettings.OnGUI (); + + EditorGUILayout.EndHorizontal (); + } + + public void AddItemsToMenu(GenericMenu menu) + { + menu.AddItem(m_GUIBlockUI, m_Settings.blockUIWhenRunning, m_Settings.ToggleBlockUIWhenRunning); + menu.AddItem(m_GUIPauseOnFailure, m_Settings.pauseOnTestFailure, m_Settings.TogglePauseOnTestFailure); + } + + private bool PrintTestList(IntegrationTestRendererBase[] renderedLines) + { + if (renderedLines == null) return false; + + var filter = m_FilterSettings.BuildRenderingOptions(); + + bool repaint = false; + foreach (var renderedLine in renderedLines) + { + repaint |= renderedLine.Render(filter); + } + return repaint; + } + + private void RenderDetails() + { + var ctrlId = GUIUtility.GetControlID(FocusType.Passive); + + Rect rect = GUILayoutUtility.GetLastRect(); + rect.y = rect.height + rect.y - 1; + rect.height = 3; + + EditorGUIUtility.AddCursorRect(rect, MouseCursor.ResizeVertical); + var e = Event.current; + switch (e.type) + { + case EventType.MouseDown: + if (GUIUtility.hotControl == 0 && rect.Contains(e.mousePosition)) + GUIUtility.hotControl = ctrlId; + break; + case EventType.MouseDrag: + if (GUIUtility.hotControl == ctrlId) + { + m_HorizontalSplitBarPosition -= e.delta.y; + if (m_HorizontalSplitBarPosition < 20) m_HorizontalSplitBarPosition = 20; + Repaint(); + } + break; + case EventType.MouseUp: + if (GUIUtility.hotControl == ctrlId) + GUIUtility.hotControl = 0; + break; + } + + m_TestInfoScroll = EditorGUILayout.BeginScrollView(m_TestInfoScroll, GUILayout.MinHeight(m_HorizontalSplitBarPosition)); + + if (m_SelectedLine != null) + UpdateResultText(m_SelectedLine); + + EditorGUILayout.SelectableLabel(m_resultText, Styles.info, + GUILayout.ExpandHeight(true), + GUILayout.ExpandWidth(true), + GUILayout.MinWidth(m_resultTextSize.x), + GUILayout.MinHeight(m_resultTextSize.y)); + EditorGUILayout.EndScrollView(); + } + + private void UpdateResultText(GameObject go) + { + if(go == m_lastSelectedGO) return; + m_lastSelectedGO = go; + var result = m_ResultList.Find(r => r.GameObject == go); + if (result == null) + { + m_resultText = string.Empty; + m_resultTextSize = Styles.info.CalcSize(new GUIContent(string.Empty)); + return; + } + var sb = new StringBuilder(result.Name.Trim()); + if (!string.IsNullOrEmpty(result.messages)) + { + sb.Append("\n---\n"); + sb.Append(result.messages.Trim()); + } + if (!string.IsNullOrEmpty(result.stacktrace)) + { + sb.Append("\n---\n"); + sb.Append(result.stacktrace.Trim()); + } + if(sb.Length>m_resultTestMaxLength) + { + sb.Length = m_resultTestMaxLength; + sb.AppendFormat("...\n\n---MESSAGE TRUNCATED AT {0} CHARACTERS---", m_resultTestMaxLength); + } + m_resultText = sb.ToString().Trim(); + m_resultTextSize = Styles.info.CalcSize(new GUIContent(m_resultText)); + } + + public void OnInspectorUpdate() + { + if (focusedWindow != this) Repaint(); + } + + private void SetCurrentTest(TestComponent tc) + { + foreach (var line in m_TestLines) + line.SetCurrentTest(tc); + } + + class RunnerCallback : ITestRunnerCallback + { + private readonly IntegrationTestsRunnerWindow m_Window; + private int m_TestNumber; + private int m_CurrentTestNumber; + + private readonly bool m_ConsoleErrorOnPauseValue; + private readonly bool m_RunInBackground; + private TestComponent m_CurrentTest; + + public RunnerCallback(IntegrationTestsRunnerWindow window) + { + m_Window = window; + + m_ConsoleErrorOnPauseValue = GuiHelper.GetConsoleErrorPause(); + GuiHelper.SetConsoleErrorPause(false); + m_RunInBackground = PlayerSettings.runInBackground; + PlayerSettings.runInBackground = true; + } + + public void RunStarted(string platform, List testsToRun) + { + EditorApplication.update += OnEditorUpdate; + m_TestNumber = testsToRun.Count; + foreach (var test in testsToRun) + { + var result = m_Window.m_ResultList.Find(r => r.TestComponent == test); + if (result != null) result.Reset(); + } + } + + public void RunFinished(List testResults) + { + m_Window.SetCurrentTest(null); + m_CurrentTest = null; + EditorApplication.update -= OnEditorUpdate; + EditorApplication.isPlaying = false; + EditorUtility.ClearProgressBar(); + GuiHelper.SetConsoleErrorPause(m_ConsoleErrorOnPauseValue); + PlayerSettings.runInBackground = m_RunInBackground; + } + + public void TestStarted(TestResult test) + { + m_Window.SetCurrentTest(test.TestComponent); + m_CurrentTest = test.TestComponent; + } + + + public void TestFinished(TestResult test) + { + m_CurrentTestNumber++; + + var result = m_Window.m_ResultList.Find(r => r.Id == test.Id); + if (result != null) + result.Update(test); + else + m_Window.m_ResultList.Add(test); + + if(test.IsFailure && m_Window.m_Settings.pauseOnTestFailure) + { + EditorUtility.ClearProgressBar(); + EditorApplication.isPaused = true; + } + } + + public void TestRunInterrupted(List testsNotRun) + { + Debug.Log("Test run interrupted"); + RunFinished(new List()); + } + + private void OnEditorUpdate() + { + if(!EditorApplication.isPlaying) + { + TestRunInterrupted(null); + return; + } + + if (m_Window.m_Settings.blockUIWhenRunning + && m_CurrentTest != null + && !EditorApplication.isPaused + && EditorUtility.DisplayCancelableProgressBar("Integration Test Runner", + "Running " + m_CurrentTest.Name, + (float)m_CurrentTestNumber / m_TestNumber)) + { + TestRunInterrupted(null); + } + } + } + + [MenuItem("Unity Test Tools/Integration Test Runner %#&t")] + public static IntegrationTestsRunnerWindow ShowWindow() + { + var w = GetWindow(typeof(IntegrationTestsRunnerWindow)); + w.Show(); + return w as IntegrationTestsRunnerWindow; + } + } +} diff --git a/Assets/UnityTestTools/IntegrationTestsFramework/TestRunner/Editor/IntegrationTestsRunnerWindow.cs.meta b/Assets/UnityTestTools/IntegrationTestsFramework/TestRunner/Editor/IntegrationTestsRunnerWindow.cs.meta new file mode 100644 index 00000000..86b5775e --- /dev/null +++ b/Assets/UnityTestTools/IntegrationTestsFramework/TestRunner/Editor/IntegrationTestsRunnerWindow.cs.meta @@ -0,0 +1,8 @@ +fileFormatVersion: 2 +guid: 2c898357efb599944818326bb43ba879 +MonoImporter: + serializedVersion: 2 + defaultReferences: [] + executionOrder: 0 + icon: {instanceID: 0} + userData: diff --git a/Assets/UnityTestTools/IntegrationTestsFramework/TestRunner/Editor/PlatformRunner.meta b/Assets/UnityTestTools/IntegrationTestsFramework/TestRunner/Editor/PlatformRunner.meta new file mode 100644 index 00000000..b7631ee5 --- /dev/null +++ b/Assets/UnityTestTools/IntegrationTestsFramework/TestRunner/Editor/PlatformRunner.meta @@ -0,0 +1,4 @@ +fileFormatVersion: 2 +guid: c44e9167d633ee94bb6e078238178308 +DefaultImporter: + userData: diff --git a/Assets/UnityTestTools/IntegrationTestsFramework/TestRunner/Editor/PlatformRunner/NetworkResultsReceiver.cs b/Assets/UnityTestTools/IntegrationTestsFramework/TestRunner/Editor/PlatformRunner/NetworkResultsReceiver.cs new file mode 100644 index 00000000..99228fe9 --- /dev/null +++ b/Assets/UnityTestTools/IntegrationTestsFramework/TestRunner/Editor/PlatformRunner/NetworkResultsReceiver.cs @@ -0,0 +1,262 @@ +using System; +using System.Collections.Generic; +using System.IO; +using System.Linq; +using System.Net; +using System.Net.Sockets; +using System.Runtime.Serialization.Formatters.Binary; +using UnityEditor; +using UnityEditorInternal; +using UnityEngine; + +namespace UnityTest +{ + [Serializable] + public class NetworkResultsReceiver : EditorWindow + { + public static NetworkResultsReceiver Instance = null; + + private string m_StatusLabel; + private TcpListener m_Listener; + + [SerializeField] + private PlatformRunnerConfiguration m_Configuration; + + private List m_TestResults = new List(); + + #region steering variables + private bool m_RunFinished; + private bool m_Repaint; + + private TimeSpan m_TestTimeout = TimeSpan.Zero; + private DateTime m_LastMessageReceived; + private bool m_Running; + + public TimeSpan ReceiveMessageTimeout = TimeSpan.FromSeconds(30); + private readonly TimeSpan m_InitialConnectionTimeout = TimeSpan.FromSeconds(300); + private bool m_TestFailed; + #endregion + + private void AcceptCallback(TcpClient client) + { + m_Repaint = true; + ResultDTO dto; + try + { + m_LastMessageReceived = DateTime.Now; + using (var stream = client.GetStream()) + { + var bf = new DTOFormatter(); + dto = (ResultDTO)bf.Deserialize(stream); + stream.Close(); + } + client.Close(); + } + catch (ObjectDisposedException e) + { + Debug.LogException(e); + m_StatusLabel = "Got disconnected"; + return; + } + catch (Exception e) + { + Debug.LogException(e); + return; + } + + switch (dto.messageType) + { + case ResultDTO.MessageType.TestStarted: + m_StatusLabel = dto.testName; + m_TestTimeout = TimeSpan.FromSeconds(dto.testTimeout); + break; + case ResultDTO.MessageType.TestFinished: + m_TestResults.Add(dto.testResult); + m_TestTimeout = TimeSpan.Zero; + if (dto.testResult.Executed && dto.testResult.ResultState != TestResultState.Ignored && !dto.testResult.IsSuccess) + m_TestFailed = true; + break; + case ResultDTO.MessageType.RunStarted: + m_TestResults = new List(); + m_StatusLabel = "Run started: " + dto.loadedLevelName; + break; + case ResultDTO.MessageType.RunFinished: + WriteResultsToLog(dto, m_TestResults); + if (!string.IsNullOrEmpty(m_Configuration.resultsDir)) + { + var platform = m_Configuration.runInEditor ? "Editor" : m_Configuration.buildTarget.ToString(); + var resultWriter = new XmlResultWriter(dto.loadedLevelName, platform, m_TestResults.ToArray()); + try + { + if (!Directory.Exists(m_Configuration.resultsDir)) + { + Directory.CreateDirectory(m_Configuration.resultsDir); + } + var filePath = Path.Combine(m_Configuration.resultsDir, dto.loadedLevelName + ".xml"); + File.WriteAllText(filePath, resultWriter.GetTestResult()); + } + catch (Exception e) + { + Debug.LogException(e); + } + } + if (dto.levelCount - dto.loadedLevel == 1) + { + m_Running = false; + m_RunFinished = true; + } + break; + case ResultDTO.MessageType.Ping: + break; + } + } + + private void WriteResultsToLog(ResultDTO dto, List list) + { + string result = "Run finished for: " + dto.loadedLevelName; + var failCount = list.Count(t => t.Executed && !t.IsSuccess); + if (failCount == 0) + result += "\nAll tests passed"; + else + result += "\n" + failCount + " tests failed"; + + if (failCount == 0) + Debug.Log(result); + else + Debug.LogWarning(result); + } + + public void Update() + { + if (EditorApplication.isCompiling + && m_Listener != null) + { + m_Running = false; + m_Listener.Stop(); + return; + } + + if (m_Running) + { + try + { + if (m_Listener != null && m_Listener.Pending()) + { + using (var client = m_Listener.AcceptTcpClient()) + { + AcceptCallback(client); + client.Close(); + } + } + } + catch (InvalidOperationException e) + { + m_StatusLabel = "Exception happened: " + e.Message; + Repaint(); + Debug.LogException(e); + } + } + + if (m_Running) + { + var adjustedtestTimeout = m_TestTimeout.Add(m_TestTimeout); + var timeout = ReceiveMessageTimeout > adjustedtestTimeout ? ReceiveMessageTimeout : adjustedtestTimeout; + if ((DateTime.Now - m_LastMessageReceived) > timeout) + { + Debug.LogError("Timeout when waiting for test results"); + m_RunFinished = true; + } + } + if (m_RunFinished) + { + if (InternalEditorUtility.inBatchMode) + EditorApplication.Exit(m_TestFailed ? Batch.returnCodeTestsFailed : Batch.returnCodeTestsOk); + Close(); + } + if (m_Repaint) Repaint(); + } + + public void OnEnable() + { + minSize = new Vector2(300, 100); + + //Unity 5.0.0 quirk throws an exception on setting the postion when in batch mode + if( !UnityEditorInternal.InternalEditorUtility.inBatchMode ) + position = new Rect(position.xMin, position.yMin, 300, 100); + titleContent = new GUIContent("Test run monitor"); + Instance = this; + m_StatusLabel = "Initializing..."; + if (EditorApplication.isCompiling) return; + EnableServer(); + } + + private void EnableServer() + { + if (m_Configuration == null) throw new Exception("No result receiver server configuration."); + + var ipAddress = IPAddress.Any; + if (m_Configuration.ipList != null && m_Configuration.ipList.Count == 1) + ipAddress = IPAddress.Parse(m_Configuration.ipList.Single()); + + var ipAddStr = Equals(ipAddress, IPAddress.Any) ? "[All interfaces]" : ipAddress.ToString(); + + m_Listener = new TcpListener(ipAddress, m_Configuration.port); + m_StatusLabel = "Waiting for connection on: " + ipAddStr + ":" + m_Configuration.port; + + try + { + m_Listener.Start(100); + } + catch (SocketException e) + { + m_StatusLabel = "Exception happened: " + e.Message; + Repaint(); + Debug.LogException(e); + } + m_Running = true; + m_LastMessageReceived = DateTime.Now + m_InitialConnectionTimeout; + } + + public void OnDisable() + { + Instance = null; + if (m_Listener != null) + m_Listener.Stop(); + } + + public void OnGUI() + { + EditorGUILayout.LabelField("Status:", EditorStyles.boldLabel); + EditorGUILayout.LabelField(m_StatusLabel); + GUILayout.FlexibleSpace(); + if (GUILayout.Button("Stop")) + { + StopReceiver(); + if (InternalEditorUtility.inBatchMode) + EditorApplication.Exit(Batch.returnCodeRunError); + } + } + + public static void StartReceiver(PlatformRunnerConfiguration configuration) + { + var w = (NetworkResultsReceiver)GetWindow(typeof(NetworkResultsReceiver), false); + w.SetConfiguration(configuration); + if (!EditorApplication.isCompiling) + { + w.EnableServer(); + } + w.Show(true); + } + + private void SetConfiguration(PlatformRunnerConfiguration configuration) + { + m_Configuration = configuration; + } + + public static void StopReceiver() + { + if (Instance == null) return; + Instance.Close(); + } + } +} diff --git a/Assets/UnityTestTools/IntegrationTestsFramework/TestRunner/Editor/PlatformRunner/NetworkResultsReceiver.cs.meta b/Assets/UnityTestTools/IntegrationTestsFramework/TestRunner/Editor/PlatformRunner/NetworkResultsReceiver.cs.meta new file mode 100644 index 00000000..cfc201e5 --- /dev/null +++ b/Assets/UnityTestTools/IntegrationTestsFramework/TestRunner/Editor/PlatformRunner/NetworkResultsReceiver.cs.meta @@ -0,0 +1,8 @@ +fileFormatVersion: 2 +guid: ade4197221f35dc44adb7649f99af2e7 +MonoImporter: + serializedVersion: 2 + defaultReferences: [] + executionOrder: 0 + icon: {instanceID: 0} + userData: diff --git a/Assets/UnityTestTools/IntegrationTestsFramework/TestRunner/Editor/PlatformRunner/PlatformRunner.cs b/Assets/UnityTestTools/IntegrationTestsFramework/TestRunner/Editor/PlatformRunner/PlatformRunner.cs new file mode 100644 index 00000000..230a6a1a --- /dev/null +++ b/Assets/UnityTestTools/IntegrationTestsFramework/TestRunner/Editor/PlatformRunner/PlatformRunner.cs @@ -0,0 +1,127 @@ +using System; +using System.Collections.Generic; +using System.Net; +using System.Net.Sockets; +using UnityEditor; +using UnityEditorInternal; +using UnityEngine; + +namespace UnityTest.IntegrationTests +{ + public class PlatformRunner + { + public static BuildTarget defaultBuildTarget + { + get + { + var target = EditorPrefs.GetString("ITR-platformRunnerBuildTarget"); + BuildTarget buildTarget; + try + { + buildTarget = (BuildTarget)Enum.Parse(typeof(BuildTarget), target); + } + catch + { + return GetDefaultBuildTarget(); + } + return buildTarget; + } + set { EditorPrefs.SetString("ITR-platformRunnerBuildTarget", value.ToString()); } + } + + [MenuItem("Unity Test Tools/Platform Runner/Run current scene %#&r")] + public static void BuildAndRunCurrentScene() + { + Debug.Log("Building and running current test for " + defaultBuildTarget); + BuildAndRunInPlayer(new PlatformRunnerConfiguration(defaultBuildTarget)); + } + + [MenuItem("Unity Test Tools/Platform Runner/Run on platform %#r")] + public static void RunInPlayer() + { + var w = EditorWindow.GetWindow(typeof(PlatformRunnerSettingsWindow)); + w.Show(); + } + + public static void BuildAndRunInPlayer(PlatformRunnerConfiguration configuration) + { + NetworkResultsReceiver.StopReceiver(); + + var settings = new PlayerSettingConfigurator(false); + + if (configuration.sendResultsOverNetwork) + { + try + { + var l = new TcpListener(IPAddress.Any, configuration.port); + l.Start(); + configuration.port = ((IPEndPoint)l.Server.LocalEndPoint).Port; + l.Stop(); + } + catch (SocketException e) + { + Debug.LogException(e); + if (InternalEditorUtility.inBatchMode) + EditorApplication.Exit(Batch.returnCodeRunError); + } + } + + if (InternalEditorUtility.inBatchMode) + settings.AddConfigurationFile(TestRunnerConfigurator.batchRunFileMarker, ""); + + if (configuration.sendResultsOverNetwork) + settings.AddConfigurationFile(TestRunnerConfigurator.integrationTestsNetwork, + string.Join("\n", configuration.GetConnectionIPs())); + + settings.ChangeSettingsForIntegrationTests(); + + AssetDatabase.Refresh(); + + var result = BuildPipeline.BuildPlayer(configuration.scenes, + configuration.GetTempPath(), + configuration.buildTarget, + BuildOptions.AutoRunPlayer | BuildOptions.Development); + + settings.RevertSettingsChanges(); + settings.RemoveAllConfigurationFiles(); + + AssetDatabase.Refresh(); + + if (!string.IsNullOrEmpty(result)) + { + if (InternalEditorUtility.inBatchMode) + EditorApplication.Exit(Batch.returnCodeRunError); + return; + } + + if (configuration.sendResultsOverNetwork) + NetworkResultsReceiver.StartReceiver(configuration); + else if (InternalEditorUtility.inBatchMode) + EditorApplication.Exit(Batch.returnCodeTestsOk); + } + + private static BuildTarget GetDefaultBuildTarget() + { + switch (EditorUserBuildSettings.selectedBuildTargetGroup) + { + case BuildTargetGroup.Android: + return BuildTarget.Android; + case BuildTargetGroup.WebPlayer: + return BuildTarget.WebPlayer; + default: + { + switch (Application.platform) + { + case RuntimePlatform.WindowsPlayer: + return BuildTarget.StandaloneWindows; + case RuntimePlatform.OSXPlayer: + return BuildTarget.StandaloneOSXIntel; + case RuntimePlatform.LinuxPlayer: + return BuildTarget.StandaloneLinux; + } + return BuildTarget.WebPlayer; + } + } + } + } +} diff --git a/Assets/UnityTestTools/IntegrationTestsFramework/TestRunner/Editor/PlatformRunner/PlatformRunner.cs.meta b/Assets/UnityTestTools/IntegrationTestsFramework/TestRunner/Editor/PlatformRunner/PlatformRunner.cs.meta new file mode 100644 index 00000000..5ecced0b --- /dev/null +++ b/Assets/UnityTestTools/IntegrationTestsFramework/TestRunner/Editor/PlatformRunner/PlatformRunner.cs.meta @@ -0,0 +1,8 @@ +fileFormatVersion: 2 +guid: a3581fa3f207a8a4c9988b9f59a510d3 +MonoImporter: + serializedVersion: 2 + defaultReferences: [] + executionOrder: 0 + icon: {instanceID: 0} + userData: diff --git a/Assets/UnityTestTools/IntegrationTestsFramework/TestRunner/Editor/PlatformRunner/PlatformRunnerConfiguration.cs b/Assets/UnityTestTools/IntegrationTestsFramework/TestRunner/Editor/PlatformRunner/PlatformRunnerConfiguration.cs new file mode 100644 index 00000000..01d96098 --- /dev/null +++ b/Assets/UnityTestTools/IntegrationTestsFramework/TestRunner/Editor/PlatformRunner/PlatformRunnerConfiguration.cs @@ -0,0 +1,77 @@ +using System; +using System.Collections.Generic; +using System.IO; +using System.Linq; +using System.Net; +using System.Net.Sockets; +using UnityEditor; +using UnityEngine; + +[Serializable] +public class PlatformRunnerConfiguration +{ + public string[] scenes = new string[0]; + public BuildTarget buildTarget; + public bool runInEditor; + public string projectName = EditorApplication.currentScene; + + public string resultsDir = null; + public bool sendResultsOverNetwork; + public List ipList; + public int port; + + public PlatformRunnerConfiguration(BuildTarget buildTarget) + { + this.buildTarget = buildTarget; + projectName = EditorApplication.currentScene; + } + + public PlatformRunnerConfiguration() + : this(BuildTarget.StandaloneWindows) + { + } + + public string GetTempPath() + { + if (string.IsNullOrEmpty(projectName)) + projectName = Path.GetTempFileName(); + + var path = Path.Combine("Temp", projectName); + switch (buildTarget) + { + case BuildTarget.StandaloneWindows: + case BuildTarget.StandaloneWindows64: + return path + ".exe"; + case BuildTarget.StandaloneOSXIntel: + return path + ".app"; + case BuildTarget.Android: + return path + ".apk"; + default: + if (buildTarget.ToString() == "BlackBerry" || buildTarget.ToString() == "BB10") + return path + ".bar"; + return path; + } + } + + public string[] GetConnectionIPs() + { + return ipList.Select(ip => ip + ":" + port).ToArray(); + } + + public static int TryToGetFreePort() + { + var port = -1; + try + { + var l = new TcpListener(IPAddress.Any, 0); + l.Start(); + port = ((IPEndPoint)l.Server.LocalEndPoint).Port; + l.Stop(); + } + catch (SocketException e) + { + Debug.LogException(e); + } + return port; + } +} diff --git a/Assets/UnityTestTools/IntegrationTestsFramework/TestRunner/Editor/PlatformRunner/PlatformRunnerConfiguration.cs.meta b/Assets/UnityTestTools/IntegrationTestsFramework/TestRunner/Editor/PlatformRunner/PlatformRunnerConfiguration.cs.meta new file mode 100644 index 00000000..f747c6e5 --- /dev/null +++ b/Assets/UnityTestTools/IntegrationTestsFramework/TestRunner/Editor/PlatformRunner/PlatformRunnerConfiguration.cs.meta @@ -0,0 +1,8 @@ +fileFormatVersion: 2 +guid: b98fe8c3761da2d4b8cfd8bd6df7050f +MonoImporter: + serializedVersion: 2 + defaultReferences: [] + executionOrder: 0 + icon: {instanceID: 0} + userData: diff --git a/Assets/UnityTestTools/IntegrationTestsFramework/TestRunner/Editor/PlatformRunner/PlatformRunnerSettings.cs b/Assets/UnityTestTools/IntegrationTestsFramework/TestRunner/Editor/PlatformRunner/PlatformRunnerSettings.cs new file mode 100644 index 00000000..bce27d8f --- /dev/null +++ b/Assets/UnityTestTools/IntegrationTestsFramework/TestRunner/Editor/PlatformRunner/PlatformRunnerSettings.cs @@ -0,0 +1,13 @@ +using System; +using System.Collections.Generic; +using UnityEngine; + +namespace UnityTest +{ + public class PlatformRunnerSettings : ProjectSettingsBase + { + public string resultsPath; + public bool sendResultsOverNetwork = true; + public int port = 0; + } +} diff --git a/Assets/UnityTestTools/IntegrationTestsFramework/TestRunner/Editor/PlatformRunner/PlatformRunnerSettings.cs.meta b/Assets/UnityTestTools/IntegrationTestsFramework/TestRunner/Editor/PlatformRunner/PlatformRunnerSettings.cs.meta new file mode 100644 index 00000000..1cc9a283 --- /dev/null +++ b/Assets/UnityTestTools/IntegrationTestsFramework/TestRunner/Editor/PlatformRunner/PlatformRunnerSettings.cs.meta @@ -0,0 +1,8 @@ +fileFormatVersion: 2 +guid: 964f5f0db2c95bb41aa3dc3beba1f06b +MonoImporter: + serializedVersion: 2 + defaultReferences: [] + executionOrder: 0 + icon: {instanceID: 0} + userData: diff --git a/Assets/UnityTestTools/IntegrationTestsFramework/TestRunner/Editor/PlatformRunner/PlatformRunnerSettingsWindow.cs b/Assets/UnityTestTools/IntegrationTestsFramework/TestRunner/Editor/PlatformRunner/PlatformRunnerSettingsWindow.cs new file mode 100644 index 00000000..708f6ef3 --- /dev/null +++ b/Assets/UnityTestTools/IntegrationTestsFramework/TestRunner/Editor/PlatformRunner/PlatformRunnerSettingsWindow.cs @@ -0,0 +1,163 @@ +using System; +using System.Collections.Generic; +using System.IO; +using System.Linq; +using System.Net; +using UnityEditor; +using UnityEngine; + +namespace UnityTest.IntegrationTests +{ + [Serializable] + public class PlatformRunnerSettingsWindow : EditorWindow + { + private BuildTarget m_BuildTarget; + private readonly List m_SceneList; + private Vector2 m_ScrollPosition; + private readonly List m_Interfaces = new List(); + private readonly List m_SelectedScenes = new List(); + private int m_SelectedInterface; + [SerializeField] + private bool m_AdvancedNetworkingSettings; + + private PlatformRunnerSettings m_Settings; + + readonly GUIContent m_Label = new GUIContent("Results target directory", "Directory where the results will be saved. If no value is specified, the results will be generated in project's data folder."); + + + public PlatformRunnerSettingsWindow() + { + titleContent = new GUIContent("Platform runner"); + m_BuildTarget = PlatformRunner.defaultBuildTarget; + position.Set(position.xMin, position.yMin, 200, position.height); + m_SceneList = Directory.GetFiles(Directory.GetCurrentDirectory(), "*.unity", SearchOption.AllDirectories).ToList(); + m_SceneList.Sort(); + var currentScene = (Directory.GetCurrentDirectory() + EditorApplication.currentScene).Replace("\\", "").Replace("/", ""); + var currentScenePath = m_SceneList.Where(s => s.Replace("\\", "").Replace("/", "") == currentScene); + m_SelectedScenes.AddRange(currentScenePath); + + m_Interfaces.Add("(Any)"); + m_Interfaces.AddRange(TestRunnerConfigurator.GetAvailableNetworkIPs()); + m_Interfaces.Add("127.0.0.1"); + } + + public void OnEnable() + { + m_Settings = ProjectSettingsBase.Load(); + } + + public void OnGUI() + { + EditorGUILayout.BeginVertical(); + + EditorGUILayout.LabelField("List of scenes to build:", EditorStyles.boldLabel); + m_ScrollPosition = EditorGUILayout.BeginScrollView(m_ScrollPosition, Styles.testList); + EditorGUI.indentLevel++; + foreach (var scenePath in m_SceneList) + { + var path = Path.GetFileNameWithoutExtension(scenePath); + var guiContent = new GUIContent(path, scenePath); + var rect = GUILayoutUtility.GetRect(guiContent, EditorStyles.label); + if (rect.Contains(Event.current.mousePosition)) + { + if (Event.current.type == EventType.mouseDown && Event.current.button == 0) + { + if (!Event.current.control && !Event.current.command) + m_SelectedScenes.Clear(); + if (!m_SelectedScenes.Contains(scenePath)) + m_SelectedScenes.Add(scenePath); + else + m_SelectedScenes.Remove(scenePath); + Event.current.Use(); + } + } + var style = new GUIStyle(EditorStyles.label); + if (m_SelectedScenes.Contains(scenePath)) + style.normal.textColor = new Color(0.3f, 0.5f, 0.85f); + EditorGUI.LabelField(rect, guiContent, style); + } + EditorGUI.indentLevel--; + EditorGUILayout.EndScrollView(); + + GUILayout.Space(3); + + m_BuildTarget = (BuildTarget)EditorGUILayout.EnumPopup("Build tests for", m_BuildTarget); + + if (PlatformRunner.defaultBuildTarget != m_BuildTarget) + { + if (GUILayout.Button("Make default target platform")) + { + PlatformRunner.defaultBuildTarget = m_BuildTarget; + } + } + DrawSetting(); + var build = GUILayout.Button("Build and run tests"); + EditorGUILayout.EndVertical(); + + if (!build) return; + + var config = new PlatformRunnerConfiguration + { + buildTarget = m_BuildTarget, + scenes = m_SelectedScenes.ToArray(), + projectName = m_SelectedScenes.Count > 1 ? "IntegrationTests" : Path.GetFileNameWithoutExtension(EditorApplication.currentScene), + resultsDir = m_Settings.resultsPath, + sendResultsOverNetwork = m_Settings.sendResultsOverNetwork, + ipList = m_Interfaces.Skip(1).ToList(), + port = m_Settings.port + }; + + if (m_SelectedInterface > 0) + config.ipList = new List {m_Interfaces.ElementAt(m_SelectedInterface)}; + + PlatformRunner.BuildAndRunInPlayer(config); + Close(); + } + + private void DrawSetting() + { + EditorGUI.BeginChangeCheck(); + + EditorGUILayout.BeginHorizontal(); + m_Settings.resultsPath = EditorGUILayout.TextField(m_Label, m_Settings.resultsPath); + if (GUILayout.Button("Search", EditorStyles.miniButton, GUILayout.Width(50))) + { + var selectedPath = EditorUtility.SaveFolderPanel("Result files destination", m_Settings.resultsPath, ""); + if (!string.IsNullOrEmpty(selectedPath)) + m_Settings.resultsPath = Path.GetFullPath(selectedPath); + } + EditorGUILayout.EndHorizontal(); + + if (!string.IsNullOrEmpty(m_Settings.resultsPath)) + { + Uri uri; + if (!Uri.TryCreate(m_Settings.resultsPath, UriKind.Absolute, out uri) || !uri.IsFile || uri.IsWellFormedOriginalString()) + { + EditorGUILayout.HelpBox("Invalid URI path", MessageType.Warning); + } + } + + m_Settings.sendResultsOverNetwork = EditorGUILayout.Toggle("Send results to editor", m_Settings.sendResultsOverNetwork); + EditorGUI.BeginDisabledGroup(!m_Settings.sendResultsOverNetwork); + m_AdvancedNetworkingSettings = EditorGUILayout.Foldout(m_AdvancedNetworkingSettings, "Advanced network settings"); + if (m_AdvancedNetworkingSettings) + { + m_SelectedInterface = EditorGUILayout.Popup("Network interface", m_SelectedInterface, m_Interfaces.ToArray()); + EditorGUI.BeginChangeCheck(); + m_Settings.port = EditorGUILayout.IntField("Network port", m_Settings.port); + if (EditorGUI.EndChangeCheck()) + { + if (m_Settings.port > IPEndPoint.MaxPort) + m_Settings.port = IPEndPoint.MaxPort; + else if (m_Settings.port < IPEndPoint.MinPort) + m_Settings.port = IPEndPoint.MinPort; + } + EditorGUI.EndDisabledGroup(); + } + if (EditorGUI.EndChangeCheck()) + { + m_Settings.Save(); + } + } + } +} diff --git a/Assets/UnityTestTools/IntegrationTestsFramework/TestRunner/Editor/PlatformRunner/PlatformRunnerSettingsWindow.cs.meta b/Assets/UnityTestTools/IntegrationTestsFramework/TestRunner/Editor/PlatformRunner/PlatformRunnerSettingsWindow.cs.meta new file mode 100644 index 00000000..8fed609f --- /dev/null +++ b/Assets/UnityTestTools/IntegrationTestsFramework/TestRunner/Editor/PlatformRunner/PlatformRunnerSettingsWindow.cs.meta @@ -0,0 +1,8 @@ +fileFormatVersion: 2 +guid: 3819282b0887bc742911b89745080acb +MonoImporter: + serializedVersion: 2 + defaultReferences: [] + executionOrder: 0 + icon: {instanceID: 0} + userData: diff --git a/Assets/UnityTestTools/IntegrationTestsFramework/TestRunner/Editor/PlatformRunner/PlayerSettingConfigurator.cs b/Assets/UnityTestTools/IntegrationTestsFramework/TestRunner/Editor/PlatformRunner/PlayerSettingConfigurator.cs new file mode 100644 index 00000000..77aea814 --- /dev/null +++ b/Assets/UnityTestTools/IntegrationTestsFramework/TestRunner/Editor/PlatformRunner/PlayerSettingConfigurator.cs @@ -0,0 +1,73 @@ +using System; +using System.Collections.Generic; +using System.IO; +using UnityEditor; +using UnityEngine; + +namespace UnityTest +{ + class PlayerSettingConfigurator + { + private string resourcesPath { + get { return m_Temp ? k_TempPath : m_ProjectResourcesPath; } + } + + private readonly string m_ProjectResourcesPath = Path.Combine("Assets", "Resources"); + const string k_TempPath = "Temp"; + private readonly bool m_Temp; + + private ResolutionDialogSetting m_DisplayResolutionDialog; + private bool m_RunInBackground; + private bool m_FullScreen; + private bool m_ResizableWindow; + private readonly List m_TempFileList = new List(); + + public PlayerSettingConfigurator(bool saveInTempFolder) + { + m_Temp = saveInTempFolder; + } + + public void ChangeSettingsForIntegrationTests() + { + m_DisplayResolutionDialog = PlayerSettings.displayResolutionDialog; + PlayerSettings.displayResolutionDialog = ResolutionDialogSetting.Disabled; + + m_RunInBackground = PlayerSettings.runInBackground; + PlayerSettings.runInBackground = true; + + m_FullScreen = PlayerSettings.defaultIsFullScreen; + PlayerSettings.defaultIsFullScreen = false; + + m_ResizableWindow = PlayerSettings.resizableWindow; + PlayerSettings.resizableWindow = true; + } + + public void RevertSettingsChanges() + { + PlayerSettings.defaultIsFullScreen = m_FullScreen; + PlayerSettings.runInBackground = m_RunInBackground; + PlayerSettings.displayResolutionDialog = m_DisplayResolutionDialog; + PlayerSettings.resizableWindow = m_ResizableWindow; + } + + public void AddConfigurationFile(string fileName, string content) + { + var resourcesPathExists = Directory.Exists(resourcesPath); + if (!resourcesPathExists) AssetDatabase.CreateFolder("Assets", "Resources"); + + var filePath = Path.Combine(resourcesPath, fileName); + File.WriteAllText(filePath, content); + + m_TempFileList.Add(filePath); + } + + public void RemoveAllConfigurationFiles() + { + foreach (var filePath in m_TempFileList) + AssetDatabase.DeleteAsset(filePath); + if (Directory.Exists(resourcesPath) + && Directory.GetFiles(resourcesPath).Length == 0) + AssetDatabase.DeleteAsset(resourcesPath); + } + } +} diff --git a/Assets/UnityTestTools/IntegrationTestsFramework/TestRunner/Editor/PlatformRunner/PlayerSettingConfigurator.cs.meta b/Assets/UnityTestTools/IntegrationTestsFramework/TestRunner/Editor/PlatformRunner/PlayerSettingConfigurator.cs.meta new file mode 100644 index 00000000..732b8be9 --- /dev/null +++ b/Assets/UnityTestTools/IntegrationTestsFramework/TestRunner/Editor/PlatformRunner/PlayerSettingConfigurator.cs.meta @@ -0,0 +1,8 @@ +fileFormatVersion: 2 +guid: c7adbe43058d54047b6109b2e66894fd +MonoImporter: + serializedVersion: 2 + defaultReferences: [] + executionOrder: 0 + icon: {instanceID: 0} + userData: diff --git a/Assets/UnityTestTools/IntegrationTestsFramework/TestRunner/Editor/Renderer.meta b/Assets/UnityTestTools/IntegrationTestsFramework/TestRunner/Editor/Renderer.meta new file mode 100644 index 00000000..ccb3c143 --- /dev/null +++ b/Assets/UnityTestTools/IntegrationTestsFramework/TestRunner/Editor/Renderer.meta @@ -0,0 +1,4 @@ +fileFormatVersion: 2 +guid: 5944b82e46f1682439d20b4c3a4f029c +DefaultImporter: + userData: diff --git a/Assets/UnityTestTools/IntegrationTestsFramework/TestRunner/Editor/Renderer/IntegrationTestGroupLine.cs b/Assets/UnityTestTools/IntegrationTestsFramework/TestRunner/Editor/Renderer/IntegrationTestGroupLine.cs new file mode 100644 index 00000000..a7d86194 --- /dev/null +++ b/Assets/UnityTestTools/IntegrationTestsFramework/TestRunner/Editor/Renderer/IntegrationTestGroupLine.cs @@ -0,0 +1,84 @@ +using System; +using System.Collections.Generic; +using System.Linq; +using UnityEditor; +using UnityEngine; + +namespace UnityTest +{ + class IntegrationTestGroupLine : IntegrationTestRendererBase + { + public static List FoldMarkers; + private IntegrationTestRendererBase[] m_Children; + + public IntegrationTestGroupLine(GameObject gameObject) : base(gameObject) + { + } + + protected internal override void DrawLine(Rect rect, GUIContent label, bool isSelected, RenderingOptions options) + { + EditorGUI.BeginChangeCheck(); + var isClassFolded = !EditorGUI.Foldout(rect, !Folded, label, isSelected ? Styles.selectedFoldout : Styles.foldout); + if (EditorGUI.EndChangeCheck()) Folded = isClassFolded; + } + + private bool Folded + { + get { return FoldMarkers.Contains(m_GameObject); } + + set + { + if (value) FoldMarkers.Add(m_GameObject); + else FoldMarkers.RemoveAll(s => s == m_GameObject); + } + } + + protected internal override void Render(int indend, RenderingOptions options) + { + base.Render(indend, options); + if (!Folded) + foreach (var child in m_Children) + child.Render(indend + 1, options); + } + + protected internal override TestResult.ResultType GetResult() + { + bool ignored = false; + bool success = false; + foreach (var child in m_Children) + { + var result = child.GetResult(); + + if (result == TestResult.ResultType.Failed || result == TestResult.ResultType.FailedException || result == TestResult.ResultType.Timeout) + return TestResult.ResultType.Failed; + if (result == TestResult.ResultType.Success) + success = true; + else if (result == TestResult.ResultType.Ignored) + ignored = true; + else + ignored = false; + } + if (success) return TestResult.ResultType.Success; + if (ignored) return TestResult.ResultType.Ignored; + return TestResult.ResultType.NotRun; + } + + protected internal override bool IsVisible(RenderingOptions options) + { + return m_Children.Any(c => c.IsVisible(options)); + } + + public override bool SetCurrentTest(TestComponent tc) + { + m_IsRunning = false; + foreach (var child in m_Children) + m_IsRunning |= child.SetCurrentTest(tc); + return m_IsRunning; + } + + public void AddChildren(IntegrationTestRendererBase[] parseTestList) + { + m_Children = parseTestList; + } + } +} diff --git a/Assets/UnityTestTools/IntegrationTestsFramework/TestRunner/Editor/Renderer/IntegrationTestGroupLine.cs.meta b/Assets/UnityTestTools/IntegrationTestsFramework/TestRunner/Editor/Renderer/IntegrationTestGroupLine.cs.meta new file mode 100644 index 00000000..00510641 --- /dev/null +++ b/Assets/UnityTestTools/IntegrationTestsFramework/TestRunner/Editor/Renderer/IntegrationTestGroupLine.cs.meta @@ -0,0 +1,8 @@ +fileFormatVersion: 2 +guid: f6dc74195aa98ef4da8901199cda4a63 +MonoImporter: + serializedVersion: 2 + defaultReferences: [] + executionOrder: 0 + icon: {instanceID: 0} + userData: diff --git a/Assets/UnityTestTools/IntegrationTestsFramework/TestRunner/Editor/Renderer/IntegrationTestLine.cs b/Assets/UnityTestTools/IntegrationTestsFramework/TestRunner/Editor/Renderer/IntegrationTestLine.cs new file mode 100644 index 00000000..e0b5d1ac --- /dev/null +++ b/Assets/UnityTestTools/IntegrationTestsFramework/TestRunner/Editor/Renderer/IntegrationTestLine.cs @@ -0,0 +1,57 @@ +using System; +using System.Collections.Generic; +using UnityEditor; +using UnityEngine; + +namespace UnityTest +{ + class IntegrationTestLine : IntegrationTestRendererBase + { + public static List Results; + protected TestResult m_Result; + + public IntegrationTestLine(GameObject gameObject, TestResult testResult) : base(gameObject) + { + m_Result = testResult; + } + + protected internal override void DrawLine(Rect rect, GUIContent label, bool isSelected, RenderingOptions options) + { + if(Event.current.type != EventType.repaint) + return; + + Styles.testName.Draw (rect, label, false, false, false, isSelected); + + if (m_Result.IsTimeout) + { + float min, max; + Styles.testName.CalcMinMaxWidth(label, out min, out max); + var timeoutRect = new Rect(rect); + timeoutRect.x += min - 12; + Styles.testName.Draw(timeoutRect, s_GUITimeoutIcon, false, false, false, isSelected); + } + } + + protected internal override TestResult.ResultType GetResult() + { + if (!m_Result.Executed && test.ignored) return TestResult.ResultType.Ignored; + return m_Result.resultType; + } + + protected internal override bool IsVisible(RenderingOptions options) + { + if (!string.IsNullOrEmpty(options.nameFilter) && !m_GameObject.name.ToLower().Contains(options.nameFilter.ToLower())) return false; + if (!options.showSucceeded && m_Result.IsSuccess) return false; + if (!options.showFailed && m_Result.IsFailure) return false; + if (!options.showNotRunned && !m_Result.Executed) return false; + if (!options.showIgnored && test.ignored) return false; + return true; + } + + public override bool SetCurrentTest(TestComponent tc) + { + m_IsRunning = test == tc; + return m_IsRunning; + } + } +} diff --git a/Assets/UnityTestTools/IntegrationTestsFramework/TestRunner/Editor/Renderer/IntegrationTestLine.cs.meta b/Assets/UnityTestTools/IntegrationTestsFramework/TestRunner/Editor/Renderer/IntegrationTestLine.cs.meta new file mode 100644 index 00000000..25c9f112 --- /dev/null +++ b/Assets/UnityTestTools/IntegrationTestsFramework/TestRunner/Editor/Renderer/IntegrationTestLine.cs.meta @@ -0,0 +1,8 @@ +fileFormatVersion: 2 +guid: 212be02e4a7da194688b08ab0c946fbd +MonoImporter: + serializedVersion: 2 + defaultReferences: [] + executionOrder: 0 + icon: {instanceID: 0} + userData: diff --git a/Assets/UnityTestTools/IntegrationTestsFramework/TestRunner/Editor/Renderer/IntegrationTestRendererBase.cs b/Assets/UnityTestTools/IntegrationTestsFramework/TestRunner/Editor/Renderer/IntegrationTestRendererBase.cs new file mode 100644 index 00000000..5b026987 --- /dev/null +++ b/Assets/UnityTestTools/IntegrationTestsFramework/TestRunner/Editor/Renderer/IntegrationTestRendererBase.cs @@ -0,0 +1,161 @@ +using System; +using System.Collections.Generic; +using System.Linq; +using UnityEditor; +using UnityEngine; +using Object = UnityEngine.Object; + +namespace UnityTest +{ + public abstract class IntegrationTestRendererBase : IComparable + { + public static Action> RunTest; + + protected static bool s_Refresh; + + private static readonly GUIContent k_GUIRunSelected = new GUIContent("Run Selected"); + private static readonly GUIContent k_GUIRun = new GUIContent("Run"); + private static readonly GUIContent k_GUIDelete = new GUIContent("Delete"); + private static readonly GUIContent k_GUIDeleteSelected = new GUIContent("Delete selected"); + + protected static GUIContent s_GUITimeoutIcon = new GUIContent(Icons.StopwatchImg, "Timeout"); + + protected GameObject m_GameObject; + public TestComponent test; + private readonly string m_Name; + + protected IntegrationTestRendererBase(GameObject gameObject) + { + test = gameObject.GetComponent(typeof(TestComponent)) as TestComponent; + if (test == null) throw new ArgumentException("Provided GameObject is not a test object"); + m_GameObject = gameObject; + m_Name = test.Name; + } + + public int CompareTo(IntegrationTestRendererBase other) + { + return test.CompareTo(other.test); + } + + public bool Render(RenderingOptions options) + { + s_Refresh = false; + EditorGUIUtility.SetIconSize(new Vector2(15, 15)); + Render(0, options); + EditorGUIUtility.SetIconSize(Vector2.zero); + return s_Refresh; + } + + protected internal virtual void Render(int indend, RenderingOptions options) + { + if (!IsVisible(options)) return; + EditorGUILayout.BeginHorizontal(); + GUILayout.Space(indend * 10); + + var tempColor = GUI.color; + if (m_IsRunning) + { + var frame = Mathf.Abs(Mathf.Cos(Time.realtimeSinceStartup * 4)) * 0.6f + 0.4f; + GUI.color = new Color(1, 1, 1, frame); + } + + var isSelected = Selection.gameObjects.Contains(m_GameObject); + + var value = GetResult(); + var icon = GetIconForResult(value); + + var label = new GUIContent(m_Name, icon); + var labelRect = GUILayoutUtility.GetRect(label, EditorStyles.label, GUILayout.ExpandWidth(true), GUILayout.Height(18)); + + OnLeftMouseButtonClick(labelRect); + OnContextClick(labelRect); + DrawLine(labelRect, label, isSelected, options); + + if (m_IsRunning) GUI.color = tempColor; + EditorGUILayout.EndHorizontal(); + } + + protected void OnSelect() + { + if (!Event.current.control && !Event.current.command) + { + Selection.objects = new Object[0]; + GUIUtility.keyboardControl = 0; + } + + if ((Event.current.control || Event.current.command) && Selection.gameObjects.Contains(test.gameObject)) + Selection.objects = Selection.gameObjects.Where(o => o != test.gameObject).ToArray(); + else + Selection.objects = Selection.gameObjects.Concat(new[] { test.gameObject }).ToArray(); + } + + protected void OnLeftMouseButtonClick(Rect rect) + { + if (rect.Contains(Event.current.mousePosition) && Event.current.type == EventType.mouseDown && Event.current.button == 0) + { + rect.width = 20; + if (rect.Contains(Event.current.mousePosition)) return; + Event.current.Use(); + OnSelect(); + } + } + + protected void OnContextClick(Rect rect) + { + if (rect.Contains(Event.current.mousePosition) && Event.current.type == EventType.ContextClick) + { + DrawContextMenu(test); + } + } + + public static void DrawContextMenu(TestComponent testComponent) + { + if (EditorApplication.isPlayingOrWillChangePlaymode) return; + + var selectedTests = Selection.gameObjects.Where(go => go.GetComponent(typeof(TestComponent))); + var manySelected = selectedTests.Count() > 1; + + var m = new GenericMenu(); + if (manySelected) + { + // var testsToRun + m.AddItem(k_GUIRunSelected, false, data => RunTest(selectedTests.Select(o => o.GetComponent(typeof(TestComponent))).Cast().ToList()), null); + } + m.AddItem(k_GUIRun, false, data => RunTest(new[] { testComponent }), null); + m.AddSeparator(""); + m.AddItem(manySelected ? k_GUIDeleteSelected : k_GUIDelete, false, data => RemoveTests(selectedTests.ToArray()), null); + m.ShowAsContext(); + } + + private static void RemoveTests(GameObject[] testsToDelete) + { + foreach (var t in testsToDelete) + { + Undo.DestroyObjectImmediate(t); + } + } + + public static Texture GetIconForResult(TestResult.ResultType resultState) + { + switch (resultState) + { + case TestResult.ResultType.Success: + return Icons.SuccessImg; + case TestResult.ResultType.Timeout: + case TestResult.ResultType.Failed: + case TestResult.ResultType.FailedException: + return Icons.FailImg; + case TestResult.ResultType.Ignored: + return Icons.IgnoreImg; + default: + return Icons.UnknownImg; + } + } + + protected internal bool m_IsRunning; + protected internal abstract void DrawLine(Rect rect, GUIContent label, bool isSelected, RenderingOptions options); + protected internal abstract TestResult.ResultType GetResult(); + protected internal abstract bool IsVisible(RenderingOptions options); + public abstract bool SetCurrentTest(TestComponent tc); + } +} diff --git a/Assets/UnityTestTools/IntegrationTestsFramework/TestRunner/Editor/Renderer/IntegrationTestRendererBase.cs.meta b/Assets/UnityTestTools/IntegrationTestsFramework/TestRunner/Editor/Renderer/IntegrationTestRendererBase.cs.meta new file mode 100644 index 00000000..1fb186ec --- /dev/null +++ b/Assets/UnityTestTools/IntegrationTestsFramework/TestRunner/Editor/Renderer/IntegrationTestRendererBase.cs.meta @@ -0,0 +1,8 @@ +fileFormatVersion: 2 +guid: 604645a3b57179a4d873906b625ef8ec +MonoImporter: + serializedVersion: 2 + defaultReferences: [] + executionOrder: 0 + icon: {instanceID: 0} + userData: diff --git a/Assets/UnityTestTools/IntegrationTestsFramework/TestRunner/Editor/TestComponentEditor.cs b/Assets/UnityTestTools/IntegrationTestsFramework/TestRunner/Editor/TestComponentEditor.cs new file mode 100644 index 00000000..8502eca4 --- /dev/null +++ b/Assets/UnityTestTools/IntegrationTestsFramework/TestRunner/Editor/TestComponentEditor.cs @@ -0,0 +1,128 @@ +using System; +using System.Collections.Generic; +using System.Linq; +using UnityEditor; +using UnityEngine; +using Object = UnityEngine.Object; + +namespace UnityTest +{ + [CanEditMultipleObjects] + [CustomEditor(typeof(TestComponent))] + public class TestComponentEditor : Editor + { + private SerializedProperty m_ExpectException; + private SerializedProperty m_ExpectedExceptionList; + private SerializedProperty m_Ignored; + private SerializedProperty m_SucceedAssertions; + private SerializedProperty m_SucceedWhenExceptionIsThrown; + private SerializedProperty m_Timeout; + + #region GUI Contens + + private readonly GUIContent m_GUIExpectException = new GUIContent("Expect exception", "Should the test expect an exception"); + private readonly GUIContent m_GUIExpectExceptionList = new GUIContent("Expected exception list", "A comma separated list of exception types which will not fail the test when thrown"); + private readonly GUIContent m_GUIIgnore = new GUIContent("Ignore", "Ignore the tests in runs"); + private readonly GUIContent m_GUIIncludePlatforms = new GUIContent("Included platforms", "Platform on which the test should run"); + private readonly GUIContent m_GUISuccedOnAssertions = new GUIContent("Succeed on assertions", "Succeed after all assertions are executed"); + private readonly GUIContent m_GUISucceedWhenExceptionIsThrown = new GUIContent("Succeed when exception is thrown", "Should the test succeed when an expected exception is thrown"); + private readonly GUIContent m_GUITestName = new GUIContent("Test name", "Name of the test (is equal to the GameObject name)"); + private readonly GUIContent m_GUITimeout = new GUIContent("Timeout", "Number of seconds after which the test will timeout"); + + #endregion + + public void OnEnable() + { + m_Timeout = serializedObject.FindProperty("timeout"); + m_Ignored = serializedObject.FindProperty("ignored"); + m_SucceedAssertions = serializedObject.FindProperty("succeedAfterAllAssertionsAreExecuted"); + m_ExpectException = serializedObject.FindProperty("expectException"); + m_ExpectedExceptionList = serializedObject.FindProperty("expectedExceptionList"); + m_SucceedWhenExceptionIsThrown = serializedObject.FindProperty("succeedWhenExceptionIsThrown"); + } + + public override void OnInspectorGUI() + { + var component = (TestComponent)target; + + if (component.dynamic) + { + if(GUILayout.Button("Reload dynamic tests")) + { + TestComponent.DestroyAllDynamicTests(); + Selection.objects = new Object[0]; + IntegrationTestsRunnerWindow.selectedInHierarchy = false; + GUIUtility.ExitGUI(); + return; + } + EditorGUILayout.HelpBox("This is a test generated from code. No changes in the component will be persisted.", MessageType.Info); + } + + if (component.IsTestGroup()) + { + EditorGUI.BeginChangeCheck(); + var newGroupName = EditorGUILayout.TextField(m_GUITestName, component.name); + if (EditorGUI.EndChangeCheck()) component.name = newGroupName; + + serializedObject.ApplyModifiedProperties(); + return; + } + + serializedObject.Update(); + + EditorGUI.BeginDisabledGroup(serializedObject.isEditingMultipleObjects); + + EditorGUI.BeginChangeCheck(); + var newName = EditorGUILayout.TextField(m_GUITestName, component.name); + if (EditorGUI.EndChangeCheck()) component.name = newName; + + if (component.platformsToIgnore == null) + { + component.platformsToIgnore = GetListOfIgnoredPlatforms(Enum.GetNames(typeof(TestComponent.IncludedPlatforms)), (int)component.includedPlatforms); + } + + var enumList = Enum.GetNames(typeof(RuntimePlatform)); + var flags = GetFlagList(enumList, component.platformsToIgnore); + flags = EditorGUILayout.MaskField(m_GUIIncludePlatforms, flags, enumList, EditorStyles.popup); + var newList = GetListOfIgnoredPlatforms(enumList, flags); + if (!component.dynamic) + component.platformsToIgnore = newList; + EditorGUI.EndDisabledGroup(); + + EditorGUILayout.PropertyField(m_Timeout, m_GUITimeout); + EditorGUILayout.PropertyField(m_Ignored, m_GUIIgnore); + EditorGUILayout.PropertyField(m_SucceedAssertions, m_GUISuccedOnAssertions); + EditorGUILayout.PropertyField(m_ExpectException, m_GUIExpectException); + + EditorGUI.BeginDisabledGroup(!m_ExpectException.boolValue); + EditorGUILayout.PropertyField(m_ExpectedExceptionList, m_GUIExpectExceptionList); + EditorGUILayout.PropertyField(m_SucceedWhenExceptionIsThrown, m_GUISucceedWhenExceptionIsThrown); + EditorGUI.EndDisabledGroup(); + + if (!component.dynamic) + serializedObject.ApplyModifiedProperties(); + if(GUI.changed) + EditorApplication.MarkSceneDirty(); + } + + private string[] GetListOfIgnoredPlatforms(string[] enumList, int flags) + { + var notSelectedPlatforms = new List(); + for (int i = 0; i < enumList.Length; i++) + { + var sel = (flags & (1 << i)) != 0; + if (!sel) notSelectedPlatforms.Add(enumList[i]); + } + return notSelectedPlatforms.ToArray(); + } + + private int GetFlagList(string[] enumList, string[] platformsToIgnore) + { + int flags = ~0; + for (int i = 0; i < enumList.Length; i++) + if (platformsToIgnore != null && platformsToIgnore.Any(s => s == enumList[i])) + flags &= ~(1 << i); + return flags; + } + } +} diff --git a/Assets/UnityTestTools/IntegrationTestsFramework/TestRunner/Editor/TestComponentEditor.cs.meta b/Assets/UnityTestTools/IntegrationTestsFramework/TestRunner/Editor/TestComponentEditor.cs.meta new file mode 100644 index 00000000..e3a1348a --- /dev/null +++ b/Assets/UnityTestTools/IntegrationTestsFramework/TestRunner/Editor/TestComponentEditor.cs.meta @@ -0,0 +1,8 @@ +fileFormatVersion: 2 +guid: 160889f21f4d5944b9f6fcaf9c33f684 +MonoImporter: + serializedVersion: 2 + defaultReferences: [] + executionOrder: 0 + icon: {instanceID: 0} + userData: diff --git a/Assets/UnityTestTools/IntegrationTestsFramework/TestRunner/ITestRunnerCallback.cs b/Assets/UnityTestTools/IntegrationTestsFramework/TestRunner/ITestRunnerCallback.cs new file mode 100644 index 00000000..c9278e3b --- /dev/null +++ b/Assets/UnityTestTools/IntegrationTestsFramework/TestRunner/ITestRunnerCallback.cs @@ -0,0 +1,15 @@ +using System; +using System.Collections.Generic; +using UnityEngine; + +namespace UnityTest.IntegrationTestRunner +{ + public interface ITestRunnerCallback + { + void RunStarted(string platform, List testsToRun); + void RunFinished(List testResults); + void TestStarted(TestResult test); + void TestFinished(TestResult test); + void TestRunInterrupted(List testsNotRun); + } +} diff --git a/Assets/UnityTestTools/IntegrationTestsFramework/TestRunner/ITestRunnerCallback.cs.meta b/Assets/UnityTestTools/IntegrationTestsFramework/TestRunner/ITestRunnerCallback.cs.meta new file mode 100644 index 00000000..3a1c54bd --- /dev/null +++ b/Assets/UnityTestTools/IntegrationTestsFramework/TestRunner/ITestRunnerCallback.cs.meta @@ -0,0 +1,8 @@ +fileFormatVersion: 2 +guid: 35af7d395e501a348ae1a0aa3c91dee4 +MonoImporter: + serializedVersion: 2 + defaultReferences: [] + executionOrder: 0 + icon: {instanceID: 0} + userData: diff --git a/Assets/UnityTestTools/IntegrationTestsFramework/TestRunner/IntegrationTest.cs b/Assets/UnityTestTools/IntegrationTestsFramework/TestRunner/IntegrationTest.cs new file mode 100644 index 00000000..eaf03e10 --- /dev/null +++ b/Assets/UnityTestTools/IntegrationTestsFramework/TestRunner/IntegrationTest.cs @@ -0,0 +1,176 @@ +using System; +using System.Collections.Generic; +using System.IO; +using System.Linq; +using UnityEngine; + +public static class IntegrationTest +{ + public const string passMessage = "IntegrationTest Pass"; + public const string failMessage = "IntegrationTest Fail"; + + public static void Pass() + { + LogResult(passMessage); + } + + public static void Pass(GameObject go) + { + LogResult(go, passMessage); + } + + public static void Fail(string reason) + { + Fail(); + if (!string.IsNullOrEmpty(reason)) Debug.Log(reason); + } + + public static void Fail(GameObject go, string reason) + { + Fail(go); + if (!string.IsNullOrEmpty(reason)) Debug.Log(reason); + } + + public static void Fail() + { + LogResult(failMessage); + } + + public static void Fail(GameObject go) + { + LogResult(go, failMessage); + } + + public static void Assert(bool condition) + { + Assert(condition, ""); + } + + public static void Assert(GameObject go, bool condition) + { + Assert(go, condition, ""); + } + + public static void Assert(bool condition, string message) + { + if (condition) Pass(); + else Fail(message); + } + + public static void Assert(GameObject go, bool condition, string message) + { + if (condition) Pass(go); + else Fail(go, message); + } + + private static void LogResult(string message) + { + Debug.Log(message); + } + + private static void LogResult(GameObject go, string message) + { + Debug.Log(message + " (" + FindTestObject(go).name + ")", go); + } + + private static GameObject FindTestObject(GameObject go) + { + var temp = go; + while (temp.transform.parent != null) + { + if (temp.GetComponent("TestComponent") != null) + return temp; + temp = temp.transform.parent.gameObject; + } + return go; + } + + #region Dynamic test attributes + + [AttributeUsage(AttributeTargets.Class, AllowMultiple = false)] + public class ExcludePlatformAttribute : Attribute + { + public string[] platformsToExclude; + + public ExcludePlatformAttribute(params RuntimePlatform[] platformsToExclude) + { + this.platformsToExclude = platformsToExclude.Select(platform => platform.ToString()).ToArray(); + } + } + + [AttributeUsage(AttributeTargets.Class, AllowMultiple = false)] + public class ExpectExceptions : Attribute + { + public string[] exceptionTypeNames; + public bool succeedOnException; + + public ExpectExceptions() : this(false) + { + } + + public ExpectExceptions(bool succeedOnException) : this(succeedOnException, new string[0]) + { + } + + public ExpectExceptions(bool succeedOnException, params string[] exceptionTypeNames) + { + this.succeedOnException = succeedOnException; + this.exceptionTypeNames = exceptionTypeNames; + } + + public ExpectExceptions(bool succeedOnException, params Type[] exceptionTypes) + : this(succeedOnException, exceptionTypes.Select(type => type.FullName).ToArray()) + { + } + + public ExpectExceptions(params string[] exceptionTypeNames) : this(false, exceptionTypeNames) + { + } + + public ExpectExceptions(params Type[] exceptionTypes) : this(false, exceptionTypes) + { + } + } + + [AttributeUsage(AttributeTargets.Class, AllowMultiple = false)] + public class IgnoreAttribute : Attribute + { + } + + [AttributeUsage(AttributeTargets.Class, AllowMultiple = false)] + public class DynamicTestAttribute : Attribute + { + private readonly string m_SceneName; + + public DynamicTestAttribute(string sceneName) + { + if (sceneName.EndsWith(".unity")) + sceneName = sceneName.Substring(0, sceneName.Length - ".unity".Length); + m_SceneName = sceneName; + } + + public bool IncludeOnScene(string sceneName) + { + var fileName = Path.GetFileNameWithoutExtension(sceneName); + return fileName == m_SceneName; + } + } + + [AttributeUsage(AttributeTargets.Class, AllowMultiple = false)] + public class SucceedWithAssertions : Attribute + { + } + + [AttributeUsage(AttributeTargets.Class, AllowMultiple = false)] + public class TimeoutAttribute : Attribute + { + public float timeout; + + public TimeoutAttribute(float seconds) + { + timeout = seconds; + } + } + + #endregion +} diff --git a/Assets/UnityTestTools/IntegrationTestsFramework/TestRunner/IntegrationTest.cs.meta b/Assets/UnityTestTools/IntegrationTestsFramework/TestRunner/IntegrationTest.cs.meta new file mode 100644 index 00000000..b6974b87 --- /dev/null +++ b/Assets/UnityTestTools/IntegrationTestsFramework/TestRunner/IntegrationTest.cs.meta @@ -0,0 +1,8 @@ +fileFormatVersion: 2 +guid: eb367bbc76e489443a4ebc8b0a8642f4 +MonoImporter: + serializedVersion: 2 + defaultReferences: [] + executionOrder: 0 + icon: {instanceID: 0} + userData: diff --git a/Assets/UnityTestTools/IntegrationTestsFramework/TestRunner/IntegrationTestAttribute.cs b/Assets/UnityTestTools/IntegrationTestsFramework/TestRunner/IntegrationTestAttribute.cs new file mode 100644 index 00000000..9ca4e45f --- /dev/null +++ b/Assets/UnityTestTools/IntegrationTestsFramework/TestRunner/IntegrationTestAttribute.cs @@ -0,0 +1,24 @@ +using System; +using System.Collections.Generic; +using System.IO; +using UnityEngine; + +[AttributeUsage(AttributeTargets.Class, AllowMultiple = false)] +public class IntegrationTestAttribute : Attribute +{ + private readonly string m_Path; + + public IntegrationTestAttribute(string path) + { + if (path.EndsWith(".unity")) + path = path.Substring(0, path.Length - ".unity".Length); + m_Path = path; + } + + public bool IncludeOnScene(string scenePath) + { + if (scenePath == m_Path) return true; + var fileName = Path.GetFileNameWithoutExtension(scenePath); + return fileName == m_Path; + } +} diff --git a/Assets/UnityTestTools/IntegrationTestsFramework/TestRunner/IntegrationTestAttribute.cs.meta b/Assets/UnityTestTools/IntegrationTestsFramework/TestRunner/IntegrationTestAttribute.cs.meta new file mode 100644 index 00000000..1c80721e --- /dev/null +++ b/Assets/UnityTestTools/IntegrationTestsFramework/TestRunner/IntegrationTestAttribute.cs.meta @@ -0,0 +1,8 @@ +fileFormatVersion: 2 +guid: f1a5c61a06ed66e41a6ee1b5f88b5afd +MonoImporter: + serializedVersion: 2 + defaultReferences: [] + executionOrder: 0 + icon: {instanceID: 0} + userData: diff --git a/Assets/UnityTestTools/IntegrationTestsFramework/TestRunner/IntegrationTestsProvider.cs b/Assets/UnityTestTools/IntegrationTestsFramework/TestRunner/IntegrationTestsProvider.cs new file mode 100644 index 00000000..c38d3c46 --- /dev/null +++ b/Assets/UnityTestTools/IntegrationTestsFramework/TestRunner/IntegrationTestsProvider.cs @@ -0,0 +1,109 @@ +using System; +using System.Collections.Generic; +using System.Linq; +using UnityEngine; + +namespace UnityTest.IntegrationTestRunner +{ + class IntegrationTestsProvider + { + internal Dictionary> testCollection = new Dictionary>(); + internal ITestComponent currentTestGroup; + internal IEnumerable testToRun; + + public IntegrationTestsProvider(IEnumerable tests) + { + testToRun = tests; + foreach (var test in tests.OrderBy(component => component)) + { + if (test.IsTestGroup()) + { + throw new Exception(test.Name + " is test a group"); + } + AddTestToList(test); + } + if (currentTestGroup == null) + { + currentTestGroup = FindInnerTestGroup(TestComponent.NullTestComponent); + } + } + + private void AddTestToList(ITestComponent test) + { + var group = test.GetTestGroup(); + if (!testCollection.ContainsKey(group)) + testCollection.Add(group, new HashSet()); + testCollection[group].Add(test); + if (group == TestComponent.NullTestComponent) return; + AddTestToList(group); + } + + public ITestComponent GetNextTest() + { + var test = testCollection[currentTestGroup].First(); + testCollection[currentTestGroup].Remove(test); + test.EnableTest(true); + return test; + } + + public void FinishTest(ITestComponent test) + { + try + { + test.EnableTest(false); + currentTestGroup = FindNextTestGroup(currentTestGroup); + } + catch (MissingReferenceException e) + { + Debug.LogException(e); + } + } + + private ITestComponent FindNextTestGroup(ITestComponent testGroup) + { + if (testGroup == null) + throw new Exception ("No test left"); + + if (testCollection[testGroup].Any()) + { + testGroup.EnableTest(true); + return FindInnerTestGroup(testGroup); + } + testCollection.Remove(testGroup); + testGroup.EnableTest(false); + + var parentTestGroup = testGroup.GetTestGroup(); + if (parentTestGroup == null) return null; + + testCollection[parentTestGroup].Remove(testGroup); + return FindNextTestGroup(parentTestGroup); + } + + private ITestComponent FindInnerTestGroup(ITestComponent group) + { + var innerGroups = testCollection[group]; + foreach (var innerGroup in innerGroups) + { + if (!innerGroup.IsTestGroup()) continue; + innerGroup.EnableTest(true); + return FindInnerTestGroup(innerGroup); + } + return group; + } + + public bool AnyTestsLeft() + { + return testCollection.Count != 0; + } + + public List GetRemainingTests() + { + var remainingTests = new List(); + foreach (var test in testCollection) + { + remainingTests.AddRange(test.Value); + } + return remainingTests; + } + } +} diff --git a/Assets/UnityTestTools/IntegrationTestsFramework/TestRunner/IntegrationTestsProvider.cs.meta b/Assets/UnityTestTools/IntegrationTestsFramework/TestRunner/IntegrationTestsProvider.cs.meta new file mode 100644 index 00000000..d444629a --- /dev/null +++ b/Assets/UnityTestTools/IntegrationTestsFramework/TestRunner/IntegrationTestsProvider.cs.meta @@ -0,0 +1,8 @@ +fileFormatVersion: 2 +guid: 21d32637b19ee51489062a66ad922193 +MonoImporter: + serializedVersion: 2 + defaultReferences: [] + executionOrder: 0 + icon: {instanceID: 0} + userData: diff --git a/Assets/UnityTestTools/IntegrationTestsFramework/TestRunner/NetworkResultSender.cs b/Assets/UnityTestTools/IntegrationTestsFramework/TestRunner/NetworkResultSender.cs new file mode 100644 index 00000000..d7024d91 --- /dev/null +++ b/Assets/UnityTestTools/IntegrationTestsFramework/TestRunner/NetworkResultSender.cs @@ -0,0 +1,107 @@ +#if !UNITY_METRO && (UNITY_PRO_LICENSE || !(UNITY_ANDROID || UNITY_IPHONE)) +#define UTT_SOCKETS_SUPPORTED +#endif +using System; +using System.Collections.Generic; +using UnityEngine; +using UnityTest.IntegrationTestRunner; + +#if UTT_SOCKETS_SUPPORTED +using System.Net.Sockets; +using System.Runtime.Serialization.Formatters.Binary; +#endif + +namespace UnityTest +{ + public class NetworkResultSender : ITestRunnerCallback + { +#if UTT_SOCKETS_SUPPORTED + private readonly TimeSpan m_ConnectionTimeout = TimeSpan.FromSeconds(5); + + private readonly string m_Ip; + private readonly int m_Port; +#endif + private bool m_LostConnection; + + public NetworkResultSender(string ip, int port) + { +#if UTT_SOCKETS_SUPPORTED + m_Ip = ip; + m_Port = port; +#endif + } + + private bool SendDTO(ResultDTO dto) + { + if (m_LostConnection) return false; +#if UTT_SOCKETS_SUPPORTED + try + { + using (var tcpClient = new TcpClient()) + { + var result = tcpClient.BeginConnect(m_Ip, m_Port, null, null); + var success = result.AsyncWaitHandle.WaitOne(m_ConnectionTimeout); + if (!success) + { + return false; + } + try + { + tcpClient.EndConnect(result); + } + catch (SocketException) + { + m_LostConnection = true; + return false; + } + + var bf = new DTOFormatter(); + bf.Serialize(tcpClient.GetStream(), dto); + tcpClient.GetStream().Close(); + tcpClient.Close(); + Debug.Log("Sent " + dto.messageType); + } + } + catch (SocketException e) + { + Debug.LogException(e); + m_LostConnection = true; + return false; + } +#endif // if UTT_SOCKETS_SUPPORTED + return true; + } + + public bool Ping() + { + var result = SendDTO(ResultDTO.CreatePing()); + m_LostConnection = false; + return result; + } + + public void RunStarted(string platform, List testsToRun) + { + SendDTO(ResultDTO.CreateRunStarted()); + } + + public void RunFinished(List testResults) + { + SendDTO(ResultDTO.CreateRunFinished(testResults)); + } + + public void TestStarted(TestResult test) + { + SendDTO(ResultDTO.CreateTestStarted(test)); + } + + public void TestFinished(TestResult test) + { + SendDTO(ResultDTO.CreateTestFinished(test)); + } + + public void TestRunInterrupted(List testsNotRun) + { + RunFinished(new List()); + } + } +} diff --git a/Assets/UnityTestTools/IntegrationTestsFramework/TestRunner/NetworkResultSender.cs.meta b/Assets/UnityTestTools/IntegrationTestsFramework/TestRunner/NetworkResultSender.cs.meta new file mode 100644 index 00000000..2ed06e13 --- /dev/null +++ b/Assets/UnityTestTools/IntegrationTestsFramework/TestRunner/NetworkResultSender.cs.meta @@ -0,0 +1,8 @@ +fileFormatVersion: 2 +guid: 80b91644bbbc487479429368d4e8d596 +MonoImporter: + serializedVersion: 2 + defaultReferences: [] + executionOrder: 0 + icon: {instanceID: 0} + userData: diff --git a/Assets/UnityTestTools/IntegrationTestsFramework/TestRunner/ResultDTO.cs b/Assets/UnityTestTools/IntegrationTestsFramework/TestRunner/ResultDTO.cs new file mode 100644 index 00000000..8922b2e2 --- /dev/null +++ b/Assets/UnityTestTools/IntegrationTestsFramework/TestRunner/ResultDTO.cs @@ -0,0 +1,160 @@ +using System; +using System.Collections.Generic; +using UnityEngine; + +namespace UnityTest +{ + [Serializable] + public class ResultDTO + { + public MessageType messageType; + public int levelCount; + public int loadedLevel; + public string loadedLevelName; + public string testName; + public float testTimeout; + public ITestResult testResult; + + private ResultDTO(MessageType messageType) + { + this.messageType = messageType; + levelCount = Application.levelCount; + loadedLevel = Application.loadedLevel; + loadedLevelName = Application.loadedLevelName; + } + + public enum MessageType : byte + { + Ping, + RunStarted, + RunFinished, + TestStarted, + TestFinished, + RunInterrupted + } + + public static ResultDTO CreatePing() + { + var dto = new ResultDTO(MessageType.Ping); + return dto; + } + + public static ResultDTO CreateRunStarted() + { + var dto = new ResultDTO(MessageType.RunStarted); + return dto; + } + + public static ResultDTO CreateRunFinished(List testResults) + { + var dto = new ResultDTO(MessageType.RunFinished); + return dto; + } + + public static ResultDTO CreateTestStarted(TestResult test) + { + var dto = new ResultDTO(MessageType.TestStarted); + dto.testName = test.FullName; + dto.testTimeout = test.TestComponent.timeout; + return dto; + } + + public static ResultDTO CreateTestFinished(TestResult test) + { + var dto = new ResultDTO(MessageType.TestFinished); + dto.testName = test.FullName; + dto.testResult = GetSerializableTestResult(test); + return dto; + } + + private static ITestResult GetSerializableTestResult(TestResult test) + { + var str = new SerializableTestResult(); + + str.resultState = test.ResultState; + str.message = test.messages; + str.executed = test.Executed; + str.name = test.Name; + str.fullName = test.FullName; + str.id = test.id; + str.isSuccess = test.IsSuccess; + str.duration = test.duration; + str.stackTrace = test.stacktrace; + str.isIgnored = test.IsIgnored; + + return str; + } + } + + #region SerializableTestResult + [Serializable] + internal class SerializableTestResult : ITestResult + { + public TestResultState resultState; + public string message; + public bool executed; + public string name; + public string fullName; + public string id; + public bool isSuccess; + public double duration; + public string stackTrace; + public bool isIgnored; + + public TestResultState ResultState + { + get { return resultState; } + } + + public string Message + { + get { return message; } + } + + public string Logs + { + get { return null; } + } + + public bool Executed + { + get { return executed; } + } + + public string Name + { + get { return name; } + } + + public string FullName + { + get { return fullName; } + } + + public string Id + { + get { return id; } + } + + public bool IsSuccess + { + get { return isSuccess; } + } + + public double Duration + { + get { return duration; } + } + + public string StackTrace + { + get { return stackTrace; } + } + + public bool IsIgnored + { + get { return isIgnored; } + } + } + #endregion +} diff --git a/Assets/UnityTestTools/IntegrationTestsFramework/TestRunner/ResultDTO.cs.meta b/Assets/UnityTestTools/IntegrationTestsFramework/TestRunner/ResultDTO.cs.meta new file mode 100644 index 00000000..e34e61ff --- /dev/null +++ b/Assets/UnityTestTools/IntegrationTestsFramework/TestRunner/ResultDTO.cs.meta @@ -0,0 +1,8 @@ +fileFormatVersion: 2 +guid: 37c772b6d1ba4274aa96c83710cb27e8 +MonoImporter: + serializedVersion: 2 + defaultReferences: [] + executionOrder: 0 + icon: {instanceID: 0} + userData: diff --git a/Assets/UnityTestTools/IntegrationTestsFramework/TestRunner/TestComponent.cs b/Assets/UnityTestTools/IntegrationTestsFramework/TestRunner/TestComponent.cs new file mode 100644 index 00000000..b083674e --- /dev/null +++ b/Assets/UnityTestTools/IntegrationTestsFramework/TestRunner/TestComponent.cs @@ -0,0 +1,411 @@ +using System; +using System.Collections.Generic; +using System.Linq; +using System.Reflection; +using UnityEngine; + +#if UNITY_EDITOR +using UnityEditor; +#endif + +namespace UnityTest +{ + public interface ITestComponent : IComparable + { + 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(); + } + + 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(); + 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(); +#if UNITY_EDITOR + Undo.RegisterCreatedObjectUndo(go, "Created test"); +#endif + return go; + } + + public static List FindAllTestsOnScene() + { + var tests = Resources.FindObjectsOfTypeAll (typeof(TestComponent)).Cast (); +#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 FindAllTopTestsOnScene() + { + return FindAllTestsOnScene().Where(component => component.gameObject.transform.parent == null).ToList(); + } + + public static List 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_EDITOR + return TestComponent.GetTypesWithHelpAttribute(EditorApplication.currentScene).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 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 + } + } +} diff --git a/Assets/UnityTestTools/IntegrationTestsFramework/TestRunner/TestComponent.cs.meta b/Assets/UnityTestTools/IntegrationTestsFramework/TestRunner/TestComponent.cs.meta new file mode 100644 index 00000000..fd67c93b --- /dev/null +++ b/Assets/UnityTestTools/IntegrationTestsFramework/TestRunner/TestComponent.cs.meta @@ -0,0 +1,8 @@ +fileFormatVersion: 2 +guid: b1dba0b27b0864740a8720e920aa88c0 +MonoImporter: + serializedVersion: 2 + defaultReferences: [] + executionOrder: 0 + icon: {instanceID: 0} + userData: diff --git a/Assets/UnityTestTools/IntegrationTestsFramework/TestRunner/TestResult.cs b/Assets/UnityTestTools/IntegrationTestsFramework/TestRunner/TestResult.cs new file mode 100644 index 00000000..df67f714 --- /dev/null +++ b/Assets/UnityTestTools/IntegrationTestsFramework/TestRunner/TestResult.cs @@ -0,0 +1,141 @@ +using System; +using System.Collections.Generic; +using UnityEngine; + +namespace UnityTest +{ + [Serializable] + public class TestResult : ITestResult, IComparable + { + private readonly GameObject m_Go; + private string m_Name; + public ResultType resultType = ResultType.NotRun; + public double duration; + public string messages; + public string stacktrace; + public string id; + public bool dynamicTest; + + public TestComponent TestComponent; + + public GameObject GameObject + { + get { return m_Go; } + } + + public TestResult(TestComponent testComponent) + { + TestComponent = testComponent; + m_Go = testComponent.gameObject; + id = testComponent.gameObject.GetInstanceID().ToString(); + dynamicTest = testComponent.dynamic; + + if (m_Go != null) m_Name = m_Go.name; + + if (dynamicTest) + id = testComponent.dynamicTypeName; + } + + public void Update(TestResult oldResult) + { + resultType = oldResult.resultType; + duration = oldResult.duration; + messages = oldResult.messages; + stacktrace = oldResult.stacktrace; + } + + public enum ResultType + { + Success, + Failed, + Timeout, + NotRun, + FailedException, + Ignored + } + + public void Reset() + { + resultType = ResultType.NotRun; + duration = 0f; + messages = ""; + stacktrace = ""; + } + + #region ITestResult implementation + public TestResultState ResultState { + get + { + switch (resultType) + { + case ResultType.Success: return TestResultState.Success; + case ResultType.Failed: return TestResultState.Failure; + case ResultType.FailedException: return TestResultState.Error; + case ResultType.Ignored: return TestResultState.Ignored; + case ResultType.NotRun: return TestResultState.Skipped; + case ResultType.Timeout: return TestResultState.Cancelled; + default: throw new Exception(); + } + } + } + public string Message { get { return messages; } } + public string Logs { get { return null; } } + public bool Executed { get { return resultType != ResultType.NotRun; } } + public string Name { get { if (m_Go != null) m_Name = m_Go.name; return m_Name; } } + public string Id { get { return id; } } + public bool IsSuccess { get { return resultType == ResultType.Success; } } + public bool IsTimeout { get { return resultType == ResultType.Timeout; } } + public double Duration { get { return duration; } } + public string StackTrace { get { return stacktrace; } } + public string FullName { + get + { + var fullName = Name; + if (m_Go != null) + { + var tempGo = m_Go.transform.parent; + while (tempGo != null) + { + fullName = tempGo.name + "." + fullName; + tempGo = tempGo.transform.parent; + } + } + return fullName; + } + } + + public bool IsIgnored { get { return resultType == ResultType.Ignored; } } + public bool IsFailure + { + get + { + return resultType == ResultType.Failed + || resultType == ResultType.FailedException + || resultType == ResultType.Timeout; + } + } + #endregion + + #region IComparable, GetHashCode and Equals implementation + public override int GetHashCode() + { + return id.GetHashCode(); + } + + public int CompareTo(TestResult other) + { + var result = Name.CompareTo(other.Name); + if (result == 0) + result = m_Go.GetInstanceID().CompareTo(other.m_Go.GetInstanceID()); + return result; + } + + public override bool Equals(object obj) + { + if (obj is TestResult) + return GetHashCode() == obj.GetHashCode(); + return base.Equals(obj); + } + #endregion + } +} diff --git a/Assets/UnityTestTools/IntegrationTestsFramework/TestRunner/TestResult.cs.meta b/Assets/UnityTestTools/IntegrationTestsFramework/TestRunner/TestResult.cs.meta new file mode 100644 index 00000000..c604beaa --- /dev/null +++ b/Assets/UnityTestTools/IntegrationTestsFramework/TestRunner/TestResult.cs.meta @@ -0,0 +1,8 @@ +fileFormatVersion: 2 +guid: 68740a702763aaa4594e8319a05ae0d3 +MonoImporter: + serializedVersion: 2 + defaultReferences: [] + executionOrder: 0 + icon: {instanceID: 0} + userData: diff --git a/Assets/UnityTestTools/IntegrationTestsFramework/TestRunner/TestResultRenderer.cs b/Assets/UnityTestTools/IntegrationTestsFramework/TestRunner/TestResultRenderer.cs new file mode 100644 index 00000000..e838d7e2 --- /dev/null +++ b/Assets/UnityTestTools/IntegrationTestsFramework/TestRunner/TestResultRenderer.cs @@ -0,0 +1,83 @@ +using System; +using System.Collections.Generic; +using System.Linq; +using UnityEngine; + +public class TestResultRenderer +{ + private static class Styles + { + public static readonly GUIStyle SucceedLabelStyle; + public static readonly GUIStyle FailedLabelStyle; + public static readonly GUIStyle FailedMessagesStyle; + + static Styles() + { + SucceedLabelStyle = new GUIStyle("label"); + SucceedLabelStyle.normal.textColor = Color.green; + SucceedLabelStyle.fontSize = 48; + + FailedLabelStyle = new GUIStyle("label"); + FailedLabelStyle.normal.textColor = Color.red; + FailedLabelStyle.fontSize = 32; + + FailedMessagesStyle = new GUIStyle("label"); + FailedMessagesStyle.wordWrap = false; + FailedMessagesStyle.richText = true; + } + } + private readonly Dictionary> m_TestCollection = new Dictionary>(); + + private bool m_ShowResults; + Vector2 m_ScrollPosition; + private int m_FailureCount; + + public void ShowResults() + { + m_ShowResults = true; + Cursor.visible = true; + } + + public void AddResults(string sceneName, ITestResult result) + { + if (!m_TestCollection.ContainsKey(sceneName)) + m_TestCollection.Add(sceneName, new List()); + m_TestCollection[sceneName].Add(result); + if (result.Executed && !result.IsSuccess) + m_FailureCount++; + } + + public void Draw() + { + if (!m_ShowResults) return; + if (m_TestCollection.Count == 0) + { + GUILayout.Label("All test succeeded", Styles.SucceedLabelStyle, GUILayout.Width(600)); + } + else + { + int count = m_TestCollection.Sum (testGroup => testGroup.Value.Count); + GUILayout.Label(count + " tests failed!", Styles.FailedLabelStyle); + + m_ScrollPosition = GUILayout.BeginScrollView(m_ScrollPosition, GUILayout.ExpandWidth(true)); + var text = ""; + foreach (var testGroup in m_TestCollection) + { + text += "" + testGroup.Key + "\n"; + text += string.Join("\n", testGroup.Value + .Where(result => !result.IsSuccess) + .Select(result => result.Name + " " + result.ResultState + "\n" + result.Message) + .ToArray()); + } + GUILayout.TextArea(text, Styles.FailedMessagesStyle); + GUILayout.EndScrollView(); + } + if (GUILayout.Button("Close")) + Application.Quit(); + } + + public int FailureCount() + { + return m_FailureCount; + } +} diff --git a/Assets/UnityTestTools/IntegrationTestsFramework/TestRunner/TestResultRenderer.cs.meta b/Assets/UnityTestTools/IntegrationTestsFramework/TestRunner/TestResultRenderer.cs.meta new file mode 100644 index 00000000..7d70150a --- /dev/null +++ b/Assets/UnityTestTools/IntegrationTestsFramework/TestRunner/TestResultRenderer.cs.meta @@ -0,0 +1,8 @@ +fileFormatVersion: 2 +guid: 7ae9d3b4b57cae343b7ff360f9deb628 +MonoImporter: + serializedVersion: 2 + defaultReferences: [] + executionOrder: 0 + icon: {instanceID: 0} + userData: diff --git a/Assets/UnityTestTools/IntegrationTestsFramework/TestRunner/TestRunner.cs b/Assets/UnityTestTools/IntegrationTestsFramework/TestRunner/TestRunner.cs new file mode 100644 index 00000000..be7a2f47 --- /dev/null +++ b/Assets/UnityTestTools/IntegrationTestsFramework/TestRunner/TestRunner.cs @@ -0,0 +1,415 @@ +// #define IMITATE_BATCH_MODE //uncomment if you want to imitate batch mode behaviour in non-batch mode mode run + +using System; +using System.Collections; +using System.Collections.Generic; +using System.Linq; +using System.Reflection; +using UnityEngine; +using UnityTest.IntegrationTestRunner; + +namespace UnityTest +{ + [Serializable] + public class TestRunner : MonoBehaviour + { + static private readonly TestResultRenderer k_ResultRenderer = new TestResultRenderer(); + + public TestComponent currentTest; + private List m_ResultList = new List(); + private List m_TestComponents; + + public bool isInitializedByRunner + { + get + { +#if !IMITATE_BATCH_MODE + if (Application.isEditor && !IsBatchMode()) + return true; +#endif + return false; + } + } + + private double m_StartTime; + private bool m_ReadyToRun; + + private string m_TestMessages; + private string m_Stacktrace; + private TestState m_TestState = TestState.Running; + + private TestRunnerConfigurator m_Configurator; + + public TestRunnerCallbackList TestRunnerCallback = new TestRunnerCallbackList(); + private IntegrationTestsProvider m_TestsProvider; + + private const string k_Prefix = "IntegrationTest"; + private const string k_StartedMessage = k_Prefix + " Started"; + private const string k_FinishedMessage = k_Prefix + " Finished"; + private const string k_TimeoutMessage = k_Prefix + " Timeout"; + private const string k_FailedMessage = k_Prefix + " Failed"; + private const string k_FailedExceptionMessage = k_Prefix + " Failed with exception"; + private const string k_IgnoredMessage = k_Prefix + " Ignored"; + private const string k_InterruptedMessage = k_Prefix + " Run interrupted"; + + + public void Awake() + { + m_Configurator = new TestRunnerConfigurator(); + if (isInitializedByRunner) return; + TestComponent.DisableAllTests(); + } + + public void Start() + { + if (isInitializedByRunner) return; + + if (m_Configurator.sendResultsOverNetwork) + { + var nrs = m_Configurator.ResolveNetworkConnection(); + if (nrs != null) + TestRunnerCallback.Add(nrs); + } + + TestComponent.DestroyAllDynamicTests(); + var dynamicTestTypes = TestComponent.GetTypesWithHelpAttribute(Application.loadedLevelName); + foreach (var dynamicTestType in dynamicTestTypes) + TestComponent.CreateDynamicTest(dynamicTestType); + + var tests = TestComponent.FindAllTestsOnScene(); + + InitRunner(tests, dynamicTestTypes.Select(type => type.AssemblyQualifiedName).ToList()); + } + + public void InitRunner(List tests, List dynamicTestsToRun) + { + Application.logMessageReceived += LogHandler; + + // Init dynamic tests + foreach (var typeName in dynamicTestsToRun) + { + var t = Type.GetType(typeName); + if (t == null) continue; + var scriptComponents = Resources.FindObjectsOfTypeAll(t) as MonoBehaviour[]; + if (scriptComponents.Length == 0) + { + Debug.LogWarning(t + " not found. Skipping."); + continue; + } + if (scriptComponents.Length > 1) Debug.LogWarning("Multiple GameObjects refer to " + typeName); + tests.Add(scriptComponents.First().GetComponent()); + } + // create test structure + m_TestComponents = ParseListForGroups(tests).ToList(); + // create results for tests + m_ResultList = m_TestComponents.Select(component => new TestResult(component)).ToList(); + // init test provider + m_TestsProvider = new IntegrationTestsProvider(m_ResultList.Select(result => result.TestComponent as ITestComponent)); + m_ReadyToRun = true; + } + + private static IEnumerable ParseListForGroups(IEnumerable tests) + { + var results = new HashSet(); + foreach (var testResult in tests) + { + if (testResult.IsTestGroup()) + { + var childrenTestResult = testResult.gameObject.GetComponentsInChildren(typeof(TestComponent), true) + .Where(t => t != testResult) + .Cast() + .ToArray(); + foreach (var result in childrenTestResult) + { + if (!result.IsTestGroup()) + results.Add(result); + } + continue; + } + results.Add(testResult); + } + return results; + } + + public void Update() + { + if (m_ReadyToRun && Time.frameCount > 1) + { + m_ReadyToRun = false; + StartCoroutine("StateMachine"); + } + } + + public void OnDestroy() + { + if (currentTest != null) + { + var testResult = m_ResultList.Single(result => result.TestComponent == currentTest); + testResult.messages += "Test run interrupted (crash?)"; + LogMessage(k_InterruptedMessage); + FinishTest(TestResult.ResultType.Failed); + } + if (currentTest != null || (m_TestsProvider != null && m_TestsProvider.AnyTestsLeft())) + { + var remainingTests = m_TestsProvider.GetRemainingTests(); + TestRunnerCallback.TestRunInterrupted(remainingTests.ToList()); + } + Application.logMessageReceived -= LogHandler; + } + + private void LogHandler(string condition, string stacktrace, LogType type) + { + if (!condition.StartsWith(k_StartedMessage) && !condition.StartsWith(k_FinishedMessage)) + { + var msg = condition; + if (msg.StartsWith(k_Prefix)) msg = msg.Substring(k_Prefix.Length + 1); + if (currentTest != null && msg.EndsWith("(" + currentTest.name + ')')) msg = msg.Substring(0, msg.LastIndexOf('(')); + m_TestMessages += msg + "\n"; + } + switch (type) + { + case LogType.Exception: + { + var exceptionType = condition.Substring(0, condition.IndexOf(':')); + if (currentTest != null && currentTest.IsExceptionExpected(exceptionType)) + { + m_TestMessages += exceptionType + " was expected\n"; + if (currentTest.ShouldSucceedOnException()) + { + m_TestState = TestState.Success; + } + } + else + { + m_TestState = TestState.Exception; + m_Stacktrace = stacktrace; + } + } + break; + case LogType.Assert: + case LogType.Error: + m_TestState = TestState.Failure; + m_Stacktrace = stacktrace; + break; + case LogType.Log: + if (m_TestState == TestState.Running && condition.StartsWith(IntegrationTest.passMessage)) + { + m_TestState = TestState.Success; + } + if (condition.StartsWith(IntegrationTest.failMessage)) + { + m_TestState = TestState.Failure; + } + break; + } + } + + public IEnumerator StateMachine() + { + TestRunnerCallback.RunStarted(Application.platform.ToString(), m_TestComponents); + while (true) + { + if (!m_TestsProvider.AnyTestsLeft() && currentTest == null) + { + FinishTestRun(); + yield break; + } + if (currentTest == null) + { + StartNewTest(); + } + if (currentTest != null) + { + if (m_TestState == TestState.Running) + { + if(currentTest.ShouldSucceedOnAssertions()) + { + var assertionsToCheck = currentTest.gameObject.GetComponentsInChildren().Where(a => a.enabled).ToArray(); + if (assertionsToCheck.Any () && assertionsToCheck.All(a => a.checksPerformed > 0)) + { + IntegrationTest.Pass(currentTest.gameObject); + m_TestState = TestState.Success; + } + } + if (currentTest != null && Time.time > m_StartTime + currentTest.GetTimeout()) + { + m_TestState = TestState.Timeout; + } + } + + switch (m_TestState) + { + case TestState.Success: + LogMessage(k_FinishedMessage); + FinishTest(TestResult.ResultType.Success); + break; + case TestState.Failure: + LogMessage(k_FailedMessage); + FinishTest(TestResult.ResultType.Failed); + break; + case TestState.Exception: + LogMessage(k_FailedExceptionMessage); + FinishTest(TestResult.ResultType.FailedException); + break; + case TestState.Timeout: + LogMessage(k_TimeoutMessage); + FinishTest(TestResult.ResultType.Timeout); + break; + case TestState.Ignored: + LogMessage(k_IgnoredMessage); + FinishTest(TestResult.ResultType.Ignored); + break; + } + } + yield return null; + } + } + + private void LogMessage(string message) + { + if (currentTest != null) + Debug.Log(message + " (" + currentTest.Name + ")", currentTest.gameObject); + else + Debug.Log(message); + } + + private void FinishTestRun() + { + PrintResultToLog(); + TestRunnerCallback.RunFinished(m_ResultList); + LoadNextLevelOrQuit(); + } + + private void PrintResultToLog() + { + var resultString = ""; + resultString += "Passed: " + m_ResultList.Count(t => t.IsSuccess); + if (m_ResultList.Any(result => result.IsFailure)) + { + resultString += " Failed: " + m_ResultList.Count(t => t.IsFailure); + Debug.Log("Failed tests: " + string.Join(", ", m_ResultList.Where(t => t.IsFailure).Select(result => result.Name).ToArray())); + } + if (m_ResultList.Any(result => result.IsIgnored)) + { + resultString += " Ignored: " + m_ResultList.Count(t => t.IsIgnored); + Debug.Log("Ignored tests: " + string.Join(", ", + m_ResultList.Where(t => t.IsIgnored).Select(result => result.Name).ToArray())); + } + Debug.Log(resultString); + } + + private void LoadNextLevelOrQuit() + { + if (isInitializedByRunner) return; + + if (Application.loadedLevel < Application.levelCount - 1) + Application.LoadLevel(Application.loadedLevel + 1); + else + { + k_ResultRenderer.ShowResults(); + if (m_Configurator.isBatchRun && m_Configurator.sendResultsOverNetwork) + Application.Quit(); + } + } + + public void OnGUI() + { + k_ResultRenderer.Draw(); + } + + private void StartNewTest() + { + m_TestMessages = ""; + m_Stacktrace = ""; + m_TestState = TestState.Running; + + m_StartTime = Time.time; + currentTest = m_TestsProvider.GetNextTest() as TestComponent; + + var testResult = m_ResultList.Single(result => result.TestComponent == currentTest); + + if (currentTest != null && currentTest.IsExludedOnThisPlatform()) + { + m_TestState = TestState.Ignored; + Debug.Log(currentTest.gameObject.name + " is excluded on this platform"); + } + + // don't ignore test if user initiated it from the runner and it's the only test that is being run + if (currentTest != null + && (currentTest.IsIgnored() + && !(isInitializedByRunner && m_ResultList.Count == 1))) + m_TestState = TestState.Ignored; + + LogMessage(k_StartedMessage); + TestRunnerCallback.TestStarted(testResult); + } + + private void FinishTest(TestResult.ResultType result) + { + m_TestsProvider.FinishTest(currentTest); + var testResult = m_ResultList.Single(t => t.GameObject == currentTest.gameObject); + testResult.resultType = result; + testResult.duration = Time.time - m_StartTime; + testResult.messages = m_TestMessages; + testResult.stacktrace = m_Stacktrace; + TestRunnerCallback.TestFinished(testResult); + currentTest = null; + if (!testResult.IsSuccess + && testResult.Executed + && !testResult.IsIgnored) k_ResultRenderer.AddResults(Application.loadedLevelName, testResult); + } + + #region Test Runner Helpers + + public static TestRunner GetTestRunner() + { + TestRunner testRunnerComponent = null; + var testRunnerComponents = Resources.FindObjectsOfTypeAll(typeof(TestRunner)); + + if (testRunnerComponents.Count() > 1) + foreach (var t in testRunnerComponents) DestroyImmediate(((TestRunner)t).gameObject); + else if (!testRunnerComponents.Any()) + testRunnerComponent = Create().GetComponent(); + else + testRunnerComponent = testRunnerComponents.Single() as TestRunner; + + return testRunnerComponent; + } + + private static GameObject Create() + { + var runner = new GameObject("TestRunner"); + runner.AddComponent(); + Debug.Log("Created Test Runner"); + return runner; + } + + private static bool IsBatchMode() + { +#if !UNITY_METRO + const string internalEditorUtilityClassName = "UnityEditorInternal.InternalEditorUtility, UnityEditor, Version=0.0.0.0, Culture=neutral, PublicKeyToken=null"; + + var t = Type.GetType(internalEditorUtilityClassName, false); + if (t == null) return false; + + const string inBatchModeProperty = "inBatchMode"; + var prop = t.GetProperty(inBatchModeProperty); + return (bool)prop.GetValue(null, null); +#else // if !UNITY_METRO + return false; +#endif // if !UNITY_METRO + } + + #endregion + + enum TestState + { + Running, + Success, + Failure, + Exception, + Timeout, + Ignored + } + } +} diff --git a/Assets/UnityTestTools/IntegrationTestsFramework/TestRunner/TestRunner.cs.meta b/Assets/UnityTestTools/IntegrationTestsFramework/TestRunner/TestRunner.cs.meta new file mode 100644 index 00000000..5ef068e2 --- /dev/null +++ b/Assets/UnityTestTools/IntegrationTestsFramework/TestRunner/TestRunner.cs.meta @@ -0,0 +1,8 @@ +fileFormatVersion: 2 +guid: 5c3afc1c624179749bcdecf7b0224902 +MonoImporter: + serializedVersion: 2 + defaultReferences: [] + executionOrder: 0 + icon: {instanceID: 0} + userData: diff --git a/Assets/UnityTestTools/IntegrationTestsFramework/TestRunner/TestRunnerCallbackList.cs b/Assets/UnityTestTools/IntegrationTestsFramework/TestRunner/TestRunnerCallbackList.cs new file mode 100644 index 00000000..d45f3983 --- /dev/null +++ b/Assets/UnityTestTools/IntegrationTestsFramework/TestRunner/TestRunnerCallbackList.cs @@ -0,0 +1,61 @@ +using System; +using System.Collections.Generic; +using UnityEngine; + +namespace UnityTest.IntegrationTestRunner +{ + public class TestRunnerCallbackList : ITestRunnerCallback + { + private readonly List m_CallbackList = new List(); + + public void Add(ITestRunnerCallback callback) + { + m_CallbackList.Add(callback); + } + + public void Remove(ITestRunnerCallback callback) + { + m_CallbackList.Remove(callback); + } + + public void RunStarted(string platform, List testsToRun) + { + foreach (var unitTestRunnerCallback in m_CallbackList) + { + unitTestRunnerCallback.RunStarted(platform, testsToRun); + } + } + + public void RunFinished(List testResults) + { + foreach (var unitTestRunnerCallback in m_CallbackList) + { + unitTestRunnerCallback.RunFinished(testResults); + } + } + + public void TestStarted(TestResult test) + { + foreach (var unitTestRunnerCallback in m_CallbackList) + { + unitTestRunnerCallback.TestStarted(test); + } + } + + public void TestFinished(TestResult test) + { + foreach (var unitTestRunnerCallback in m_CallbackList) + { + unitTestRunnerCallback.TestFinished(test); + } + } + + public void TestRunInterrupted(List testsNotRun) + { + foreach (var unitTestRunnerCallback in m_CallbackList) + { + unitTestRunnerCallback.TestRunInterrupted(testsNotRun); + } + } + } +} diff --git a/Assets/UnityTestTools/IntegrationTestsFramework/TestRunner/TestRunnerCallbackList.cs.meta b/Assets/UnityTestTools/IntegrationTestsFramework/TestRunner/TestRunnerCallbackList.cs.meta new file mode 100644 index 00000000..c39656ab --- /dev/null +++ b/Assets/UnityTestTools/IntegrationTestsFramework/TestRunner/TestRunnerCallbackList.cs.meta @@ -0,0 +1,8 @@ +fileFormatVersion: 2 +guid: 7729da83f7c08d244b5788c870a93780 +MonoImporter: + serializedVersion: 2 + defaultReferences: [] + executionOrder: 0 + icon: {instanceID: 0} + userData: diff --git a/Assets/UnityTestTools/IntegrationTestsFramework/TestRunner/TestRunnerConfigurator.cs b/Assets/UnityTestTools/IntegrationTestsFramework/TestRunner/TestRunnerConfigurator.cs new file mode 100644 index 00000000..12041215 --- /dev/null +++ b/Assets/UnityTestTools/IntegrationTestsFramework/TestRunner/TestRunnerConfigurator.cs @@ -0,0 +1,176 @@ +#if !UNITY_METRO && !UNITY_WEBPLAYER && (UNITY_PRO_LICENSE || !(UNITY_ANDROID || UNITY_IPHONE)) +#define UTT_SOCKETS_SUPPORTED +#endif +using System; +using System.Collections.Generic; +using System.IO; +using System.Linq; +using System.Threading; +using UnityEngine; +using UnityTest.IntegrationTestRunner; +#if UTT_SOCKETS_SUPPORTED +using System.Net; +using System.Net.Sockets; +using System.Net.NetworkInformation; +#endif + +#if UNITY_EDITOR +using UnityEditorInternal; +#endif + +namespace UnityTest +{ + public class TestRunnerConfigurator + { + public static string integrationTestsNetwork = "networkconfig.txt"; + public static string batchRunFileMarker = "batchrun.txt"; + + public bool isBatchRun { get; private set; } + + public bool sendResultsOverNetwork { get; private set; } + +#if UTT_SOCKETS_SUPPORTED + private readonly List m_IPEndPointList = new List(); +#endif + + public TestRunnerConfigurator() + { + CheckForBatchMode(); + CheckForSendingResultsOverNetwork(); + } + + private void CheckForSendingResultsOverNetwork() + { +#if UTT_SOCKETS_SUPPORTED + string text; + if (Application.isEditor) + text = GetTextFromTempFile(integrationTestsNetwork); + else + text = GetTextFromTextAsset(integrationTestsNetwork); + + if (text == null) return; + + sendResultsOverNetwork = true; + + m_IPEndPointList.Clear(); + + foreach (var line in text.Split(new[] {'\n'}, StringSplitOptions.RemoveEmptyEntries)) + { + var idx = line.IndexOf(':'); + if (idx == -1) throw new Exception(line); + var ip = line.Substring(0, idx); + var port = line.Substring(idx + 1); + m_IPEndPointList.Add(new IPEndPoint(IPAddress.Parse(ip), Int32.Parse(port))); + } +#endif // if UTT_SOCKETS_SUPPORTED + } + + private static string GetTextFromTextAsset(string fileName) + { + var nameWithoutExtension = fileName.Substring(0, fileName.LastIndexOf('.')); + var resultpathFile = Resources.Load(nameWithoutExtension) as TextAsset; + return resultpathFile != null ? resultpathFile.text : null; + } + + private static string GetTextFromTempFile(string fileName) + { + string text = null; + try + { +#if UNITY_EDITOR && !UNITY_WEBPLAYER + text = File.ReadAllText(Path.Combine("Temp", fileName)); +#endif + } + catch + { + return null; + } + return text; + } + + private void CheckForBatchMode() + { +#if IMITATE_BATCH_MODE + isBatchRun = true; +#elif UNITY_EDITOR + if (Application.isEditor && InternalEditorUtility.inBatchMode) + isBatchRun = true; +#else + if (GetTextFromTextAsset(batchRunFileMarker) != null) isBatchRun = true; +#endif + } + + public static List GetAvailableNetworkIPs() + { +#if UTT_SOCKETS_SUPPORTED + if (!NetworkInterface.GetIsNetworkAvailable()) + return new List{IPAddress.Loopback.ToString()}; + + var ipList = new List(); + var allIpsList = new List(); + + foreach (var netInterface in NetworkInterface.GetAllNetworkInterfaces()) + { + if (netInterface.NetworkInterfaceType != NetworkInterfaceType.Wireless80211 && + netInterface.NetworkInterfaceType != NetworkInterfaceType.Ethernet) + continue; + + var ipAdresses = netInterface.GetIPProperties().UnicastAddresses + .Where(a => a.Address.AddressFamily == AddressFamily.InterNetwork); + allIpsList.AddRange(ipAdresses); + + if (netInterface.OperationalStatus != OperationalStatus.Up) continue; + + ipList.AddRange(ipAdresses); + } + + //On Mac 10.10 all interfaces return OperationalStatus.Unknown, thus this workaround + if(!ipList.Any()) return allIpsList.Select(i => i.Address.ToString()).ToList(); + + // sort ip list by their masks to predict which ip belongs to lan network + ipList.Sort((ip1, ip2) => + { + var mask1 = BitConverter.ToInt32(ip1.IPv4Mask.GetAddressBytes().Reverse().ToArray(), 0); + var mask2 = BitConverter.ToInt32(ip2.IPv4Mask.GetAddressBytes().Reverse().ToArray(), 0); + return mask2.CompareTo(mask1); + }); + if (ipList.Count == 0) + return new List { IPAddress.Loopback.ToString() }; + return ipList.Select(i => i.Address.ToString()).ToList(); +#else + return new List(); +#endif // if UTT_SOCKETS_SUPPORTED + } + + public ITestRunnerCallback ResolveNetworkConnection() + { +#if UTT_SOCKETS_SUPPORTED + var nrsList = m_IPEndPointList.Select(ipEndPoint => new NetworkResultSender(ipEndPoint.Address.ToString(), ipEndPoint.Port)).ToList(); + + var timeout = TimeSpan.FromSeconds(30); + DateTime startTime = DateTime.Now; + while ((DateTime.Now - startTime) < timeout) + { + foreach (var networkResultSender in nrsList) + { + try + { + if (!networkResultSender.Ping()) continue; + } + catch (Exception e) + { + Debug.LogException(e); + sendResultsOverNetwork = false; + return null; + } + return networkResultSender; + } + Thread.Sleep(500); + } + Debug.LogError("Couldn't connect to the server: " + string.Join(", ", m_IPEndPointList.Select(ipep => ipep.Address + ":" + ipep.Port).ToArray())); + sendResultsOverNetwork = false; +#endif // if UTT_SOCKETS_SUPPORTED + return null; + } + } +} diff --git a/Assets/UnityTestTools/IntegrationTestsFramework/TestRunner/TestRunnerConfigurator.cs.meta b/Assets/UnityTestTools/IntegrationTestsFramework/TestRunner/TestRunnerConfigurator.cs.meta new file mode 100644 index 00000000..42f72639 --- /dev/null +++ b/Assets/UnityTestTools/IntegrationTestsFramework/TestRunner/TestRunnerConfigurator.cs.meta @@ -0,0 +1,8 @@ +fileFormatVersion: 2 +guid: 05aae864572254e478ed2f0489cdd335 +MonoImporter: + serializedVersion: 2 + defaultReferences: [] + executionOrder: 0 + icon: {instanceID: 0} + userData: diff --git a/Assets/UnityTestTools/IntegrationTestsFramework/TestingAssets.meta b/Assets/UnityTestTools/IntegrationTestsFramework/TestingAssets.meta new file mode 100644 index 00000000..433c2954 --- /dev/null +++ b/Assets/UnityTestTools/IntegrationTestsFramework/TestingAssets.meta @@ -0,0 +1,5 @@ +fileFormatVersion: 2 +guid: 1d1ccbd729921544dbd71f7e80c405b6 +folderAsset: yes +DefaultImporter: + userData: diff --git a/Assets/UnityTestTools/IntegrationTestsFramework/TestingAssets/CallTesting.cs b/Assets/UnityTestTools/IntegrationTestsFramework/TestingAssets/CallTesting.cs new file mode 100644 index 00000000..3badc58c --- /dev/null +++ b/Assets/UnityTestTools/IntegrationTestsFramework/TestingAssets/CallTesting.cs @@ -0,0 +1,204 @@ +using System; +using System.Collections.Generic; +using UnityEngine; + +namespace UnityTest +{ + public class CallTesting : MonoBehaviour + { + public enum Functions + { + CallAfterSeconds, + CallAfterFrames, + Start, + Update, + FixedUpdate, + LateUpdate, + OnDestroy, + OnEnable, + OnDisable, + OnControllerColliderHit, + OnParticleCollision, + OnJointBreak, + OnBecameInvisible, + OnBecameVisible, + OnTriggerEnter, + OnTriggerExit, + OnTriggerStay, + OnCollisionEnter, + OnCollisionExit, + OnCollisionStay, + OnTriggerEnter2D, + OnTriggerExit2D, + OnTriggerStay2D, + OnCollisionEnter2D, + OnCollisionExit2D, + OnCollisionStay2D, + } + + public enum Method + { + Pass, + Fail + } + + public int afterFrames = 0; + public float afterSeconds = 0.0f; + public Functions callOnMethod = Functions.Start; + + public Method methodToCall; + private int m_StartFrame; + private float m_StartTime; + + private void TryToCallTesting(Functions invokingMethod) + { + if (invokingMethod == callOnMethod) + { + if (methodToCall == Method.Pass) + IntegrationTest.Pass(gameObject); + else + IntegrationTest.Fail(gameObject); + + afterFrames = 0; + afterSeconds = 0.0f; + m_StartTime = float.PositiveInfinity; + m_StartFrame = int.MinValue; + } + } + + public void Start() + { + m_StartTime = Time.time; + m_StartFrame = afterFrames; + TryToCallTesting(Functions.Start); + } + + public void Update() + { + TryToCallTesting(Functions.Update); + CallAfterSeconds(); + CallAfterFrames(); + } + + private void CallAfterFrames() + { + if (afterFrames > 0 && (m_StartFrame + afterFrames) <= Time.frameCount) + TryToCallTesting(Functions.CallAfterFrames); + } + + private void CallAfterSeconds() + { + if ((m_StartTime + afterSeconds) <= Time.time) + TryToCallTesting(Functions.CallAfterSeconds); + } + + public void OnDisable() + { + TryToCallTesting(Functions.OnDisable); + } + + public void OnEnable() + { + TryToCallTesting(Functions.OnEnable); + } + + public void OnDestroy() + { + TryToCallTesting(Functions.OnDestroy); + } + + public void FixedUpdate() + { + TryToCallTesting(Functions.FixedUpdate); + } + + public void LateUpdate() + { + TryToCallTesting(Functions.LateUpdate); + } + + public void OnControllerColliderHit() + { + TryToCallTesting(Functions.OnControllerColliderHit); + } + + public void OnParticleCollision() + { + TryToCallTesting(Functions.OnParticleCollision); + } + + public void OnJointBreak() + { + TryToCallTesting(Functions.OnJointBreak); + } + + public void OnBecameInvisible() + { + TryToCallTesting(Functions.OnBecameInvisible); + } + + public void OnBecameVisible() + { + TryToCallTesting(Functions.OnBecameVisible); + } + + public void OnTriggerEnter() + { + TryToCallTesting(Functions.OnTriggerEnter); + } + + public void OnTriggerExit() + { + TryToCallTesting(Functions.OnTriggerExit); + } + + public void OnTriggerStay() + { + TryToCallTesting(Functions.OnTriggerStay); + } + public void OnCollisionEnter() + { + TryToCallTesting(Functions.OnCollisionEnter); + } + + public void OnCollisionExit() + { + TryToCallTesting(Functions.OnCollisionExit); + } + + public void OnCollisionStay() + { + TryToCallTesting(Functions.OnCollisionStay); + } + + public void OnTriggerEnter2D() + { + TryToCallTesting(Functions.OnTriggerEnter2D); + } + + public void OnTriggerExit2D() + { + TryToCallTesting(Functions.OnTriggerExit2D); + } + + public void OnTriggerStay2D() + { + TryToCallTesting(Functions.OnTriggerStay2D); + } + + public void OnCollisionEnter2D() + { + TryToCallTesting(Functions.OnCollisionEnter2D); + } + + public void OnCollisionExit2D() + { + TryToCallTesting(Functions.OnCollisionExit2D); + } + + public void OnCollisionStay2D() + { + TryToCallTesting(Functions.OnCollisionStay2D); + } + } +} diff --git a/Assets/UnityTestTools/IntegrationTestsFramework/TestingAssets/CallTesting.cs.meta b/Assets/UnityTestTools/IntegrationTestsFramework/TestingAssets/CallTesting.cs.meta new file mode 100644 index 00000000..91cbde1c --- /dev/null +++ b/Assets/UnityTestTools/IntegrationTestsFramework/TestingAssets/CallTesting.cs.meta @@ -0,0 +1,8 @@ +fileFormatVersion: 2 +guid: 0d545b1288d5fc74d8e6c961fb67ab18 +MonoImporter: + serializedVersion: 2 + defaultReferences: [] + executionOrder: 0 + icon: {instanceID: 0} + userData: diff --git a/Assets/UnityTestTools/IntegrationTestsFramework/TestingAssets/CubeCollisionFailure.prefab b/Assets/UnityTestTools/IntegrationTestsFramework/TestingAssets/CubeCollisionFailure.prefab new file mode 100644 index 00000000..fac4d3d1 --- /dev/null +++ b/Assets/UnityTestTools/IntegrationTestsFramework/TestingAssets/CubeCollisionFailure.prefab @@ -0,0 +1,102 @@ +%YAML 1.1 +%TAG !u! tag:unity3d.com,2011: +--- !u!1 &100000 +GameObject: + m_ObjectHideFlags: 0 + m_PrefabParentObject: {fileID: 0} + m_PrefabInternal: {fileID: 100100000} + serializedVersion: 4 + m_Component: + - 4: {fileID: 400000} + - 33: {fileID: 3300000} + - 65: {fileID: 6500000} + - 23: {fileID: 2300000} + - 114: {fileID: 11400000} + m_Layer: 0 + m_Name: CubeCollisionFailure + m_TagString: Untagged + m_Icon: {fileID: 0} + m_NavMeshLayer: 0 + m_StaticEditorFlags: 0 + m_IsActive: 1 +--- !u!4 &400000 +Transform: + m_ObjectHideFlags: 1 + m_PrefabParentObject: {fileID: 0} + m_PrefabInternal: {fileID: 100100000} + m_GameObject: {fileID: 100000} + m_LocalRotation: {x: 0, y: 0, z: 0, w: 1} + m_LocalPosition: {x: -5.17369938, y: 3.99781466, z: -16.2489204} + m_LocalScale: {x: 1, y: 1, z: 1} + m_Children: [] + m_Father: {fileID: 0} + m_RootOrder: 0 +--- !u!23 &2300000 +MeshRenderer: + m_ObjectHideFlags: 1 + m_PrefabParentObject: {fileID: 0} + m_PrefabInternal: {fileID: 100100000} + m_GameObject: {fileID: 100000} + m_Enabled: 1 + m_CastShadows: 0 + m_ReceiveShadows: 0 + m_Materials: + - {fileID: 2100000, guid: 03f3b4747259a364b800508ac27e1c17, type: 2} + m_SubsetIndices: + m_StaticBatchRoot: {fileID: 0} + m_UseLightProbes: 0 + m_ReflectionProbeUsage: 1 + m_ProbeAnchor: {fileID: 0} + m_ScaleInLightmap: 1 + m_PreserveUVs: 0 + m_ImportantGI: 0 + m_AutoUVMaxDistance: .5 + m_AutoUVMaxAngle: 89 + m_LightmapParameters: {fileID: 0} + m_SortingLayerID: 0 + m_SortingOrder: 0 +--- !u!33 &3300000 +MeshFilter: + m_ObjectHideFlags: 1 + m_PrefabParentObject: {fileID: 0} + m_PrefabInternal: {fileID: 100100000} + m_GameObject: {fileID: 100000} + m_Mesh: {fileID: 10202, guid: 0000000000000000e000000000000000, type: 0} +--- !u!65 &6500000 +BoxCollider: + m_ObjectHideFlags: 1 + m_PrefabParentObject: {fileID: 0} + m_PrefabInternal: {fileID: 100100000} + m_GameObject: {fileID: 100000} + m_Material: {fileID: 0} + m_IsTrigger: 0 + m_Enabled: 1 + serializedVersion: 2 + m_Size: {x: 1, y: 1, z: 1} + m_Center: {x: 0, y: 0, z: 0} +--- !u!114 &11400000 +MonoBehaviour: + m_ObjectHideFlags: 1 + m_PrefabParentObject: {fileID: 0} + m_PrefabInternal: {fileID: 100100000} + m_GameObject: {fileID: 100000} + m_Enabled: 1 + m_EditorHideFlags: 0 + m_Script: {fileID: 11500000, guid: 0d545b1288d5fc74d8e6c961fb67ab18, type: 3} + m_Name: + m_EditorClassIdentifier: + afterFrames: 0 + afterSeconds: 0 + callOnMethod: 17 + methodToCall: 1 +--- !u!1001 &100100000 +Prefab: + m_ObjectHideFlags: 1 + serializedVersion: 2 + m_Modification: + m_TransformParent: {fileID: 0} + m_Modifications: [] + m_RemovedComponents: [] + m_ParentPrefab: {fileID: 0} + m_RootGameObject: {fileID: 100000} + m_IsPrefabParent: 1 diff --git a/Assets/UnityTestTools/IntegrationTestsFramework/TestingAssets/CubeCollisionFailure.prefab.meta b/Assets/UnityTestTools/IntegrationTestsFramework/TestingAssets/CubeCollisionFailure.prefab.meta new file mode 100644 index 00000000..777585e9 --- /dev/null +++ b/Assets/UnityTestTools/IntegrationTestsFramework/TestingAssets/CubeCollisionFailure.prefab.meta @@ -0,0 +1,4 @@ +fileFormatVersion: 2 +guid: d5fc3c3488db1e74689f1fc67c33944a +NativeFormatImporter: + userData: diff --git a/Assets/UnityTestTools/IntegrationTestsFramework/TestingAssets/CubeCollisionSuccess.prefab b/Assets/UnityTestTools/IntegrationTestsFramework/TestingAssets/CubeCollisionSuccess.prefab new file mode 100644 index 00000000..3c867741 --- /dev/null +++ b/Assets/UnityTestTools/IntegrationTestsFramework/TestingAssets/CubeCollisionSuccess.prefab @@ -0,0 +1,102 @@ +%YAML 1.1 +%TAG !u! tag:unity3d.com,2011: +--- !u!1 &100000 +GameObject: + m_ObjectHideFlags: 0 + m_PrefabParentObject: {fileID: 0} + m_PrefabInternal: {fileID: 100100000} + serializedVersion: 4 + m_Component: + - 4: {fileID: 400000} + - 33: {fileID: 3300000} + - 65: {fileID: 6500000} + - 23: {fileID: 2300000} + - 114: {fileID: 11400000} + m_Layer: 0 + m_Name: CubeCollisionSuccess + m_TagString: Untagged + m_Icon: {fileID: 0} + m_NavMeshLayer: 0 + m_StaticEditorFlags: 0 + m_IsActive: 1 +--- !u!4 &400000 +Transform: + m_ObjectHideFlags: 1 + m_PrefabParentObject: {fileID: 0} + m_PrefabInternal: {fileID: 100100000} + m_GameObject: {fileID: 100000} + m_LocalRotation: {x: 0, y: 0, z: 0, w: 1} + m_LocalPosition: {x: -5.17369938, y: 3.99781466, z: -16.2489204} + m_LocalScale: {x: 1, y: 1, z: 1} + m_Children: [] + m_Father: {fileID: 0} + m_RootOrder: 0 +--- !u!23 &2300000 +MeshRenderer: + m_ObjectHideFlags: 1 + m_PrefabParentObject: {fileID: 0} + m_PrefabInternal: {fileID: 100100000} + m_GameObject: {fileID: 100000} + m_Enabled: 1 + m_CastShadows: 0 + m_ReceiveShadows: 0 + m_Materials: + - {fileID: 2100000, guid: 43da3275cd08d41429f56675d70c58df, type: 2} + m_SubsetIndices: + m_StaticBatchRoot: {fileID: 0} + m_UseLightProbes: 0 + m_ReflectionProbeUsage: 1 + m_ProbeAnchor: {fileID: 0} + m_ScaleInLightmap: 1 + m_PreserveUVs: 0 + m_ImportantGI: 0 + m_AutoUVMaxDistance: .5 + m_AutoUVMaxAngle: 89 + m_LightmapParameters: {fileID: 0} + m_SortingLayerID: 0 + m_SortingOrder: 0 +--- !u!33 &3300000 +MeshFilter: + m_ObjectHideFlags: 1 + m_PrefabParentObject: {fileID: 0} + m_PrefabInternal: {fileID: 100100000} + m_GameObject: {fileID: 100000} + m_Mesh: {fileID: 10202, guid: 0000000000000000e000000000000000, type: 0} +--- !u!65 &6500000 +BoxCollider: + m_ObjectHideFlags: 1 + m_PrefabParentObject: {fileID: 0} + m_PrefabInternal: {fileID: 100100000} + m_GameObject: {fileID: 100000} + m_Material: {fileID: 0} + m_IsTrigger: 0 + m_Enabled: 1 + serializedVersion: 2 + m_Size: {x: 1, y: 1, z: 1} + m_Center: {x: 0, y: 0, z: 0} +--- !u!114 &11400000 +MonoBehaviour: + m_ObjectHideFlags: 1 + m_PrefabParentObject: {fileID: 0} + m_PrefabInternal: {fileID: 100100000} + m_GameObject: {fileID: 100000} + m_Enabled: 1 + m_EditorHideFlags: 0 + m_Script: {fileID: 11500000, guid: 0d545b1288d5fc74d8e6c961fb67ab18, type: 3} + m_Name: + m_EditorClassIdentifier: + afterFrames: 0 + afterSeconds: 0 + callOnMethod: 17 + methodToCall: 0 +--- !u!1001 &100100000 +Prefab: + m_ObjectHideFlags: 1 + serializedVersion: 2 + m_Modification: + m_TransformParent: {fileID: 0} + m_Modifications: [] + m_RemovedComponents: [] + m_ParentPrefab: {fileID: 0} + m_RootGameObject: {fileID: 100000} + m_IsPrefabParent: 1 diff --git a/Assets/UnityTestTools/IntegrationTestsFramework/TestingAssets/CubeCollisionSuccess.prefab.meta b/Assets/UnityTestTools/IntegrationTestsFramework/TestingAssets/CubeCollisionSuccess.prefab.meta new file mode 100644 index 00000000..73ed4742 --- /dev/null +++ b/Assets/UnityTestTools/IntegrationTestsFramework/TestingAssets/CubeCollisionSuccess.prefab.meta @@ -0,0 +1,4 @@ +fileFormatVersion: 2 +guid: 1228dff762eab21488cfefd42792c37b +NativeFormatImporter: + userData: diff --git a/Assets/UnityTestTools/IntegrationTestsFramework/TestingAssets/CubeTriggerFailure.prefab b/Assets/UnityTestTools/IntegrationTestsFramework/TestingAssets/CubeTriggerFailure.prefab new file mode 100644 index 00000000..f531ea79 --- /dev/null +++ b/Assets/UnityTestTools/IntegrationTestsFramework/TestingAssets/CubeTriggerFailure.prefab @@ -0,0 +1,102 @@ +%YAML 1.1 +%TAG !u! tag:unity3d.com,2011: +--- !u!1 &100000 +GameObject: + m_ObjectHideFlags: 0 + m_PrefabParentObject: {fileID: 0} + m_PrefabInternal: {fileID: 100100000} + serializedVersion: 4 + m_Component: + - 4: {fileID: 400000} + - 33: {fileID: 3300000} + - 65: {fileID: 6500000} + - 23: {fileID: 2300000} + - 114: {fileID: 11400000} + m_Layer: 0 + m_Name: CubeTriggerFailure + m_TagString: Untagged + m_Icon: {fileID: 0} + m_NavMeshLayer: 0 + m_StaticEditorFlags: 0 + m_IsActive: 1 +--- !u!4 &400000 +Transform: + m_ObjectHideFlags: 1 + m_PrefabParentObject: {fileID: 0} + m_PrefabInternal: {fileID: 100100000} + m_GameObject: {fileID: 100000} + m_LocalRotation: {x: 0, y: 0, z: 0, w: 1} + m_LocalPosition: {x: 0, y: 0, z: 0} + m_LocalScale: {x: 1, y: 1, z: 1} + m_Children: [] + m_Father: {fileID: 0} + m_RootOrder: 0 +--- !u!23 &2300000 +MeshRenderer: + m_ObjectHideFlags: 1 + m_PrefabParentObject: {fileID: 0} + m_PrefabInternal: {fileID: 100100000} + m_GameObject: {fileID: 100000} + m_Enabled: 1 + m_CastShadows: 0 + m_ReceiveShadows: 0 + m_Materials: + - {fileID: 2100000, guid: 03f3b4747259a364b800508ac27e1c17, type: 2} + m_SubsetIndices: + m_StaticBatchRoot: {fileID: 0} + m_UseLightProbes: 0 + m_ReflectionProbeUsage: 1 + m_ProbeAnchor: {fileID: 0} + m_ScaleInLightmap: 1 + m_PreserveUVs: 0 + m_ImportantGI: 0 + m_AutoUVMaxDistance: .5 + m_AutoUVMaxAngle: 89 + m_LightmapParameters: {fileID: 0} + m_SortingLayerID: 0 + m_SortingOrder: 0 +--- !u!33 &3300000 +MeshFilter: + m_ObjectHideFlags: 1 + m_PrefabParentObject: {fileID: 0} + m_PrefabInternal: {fileID: 100100000} + m_GameObject: {fileID: 100000} + m_Mesh: {fileID: 10202, guid: 0000000000000000e000000000000000, type: 0} +--- !u!65 &6500000 +BoxCollider: + m_ObjectHideFlags: 1 + m_PrefabParentObject: {fileID: 0} + m_PrefabInternal: {fileID: 100100000} + m_GameObject: {fileID: 100000} + m_Material: {fileID: 0} + m_IsTrigger: 1 + m_Enabled: 1 + serializedVersion: 2 + m_Size: {x: 1, y: 1, z: 1} + m_Center: {x: 0, y: 0, z: 0} +--- !u!114 &11400000 +MonoBehaviour: + m_ObjectHideFlags: 1 + m_PrefabParentObject: {fileID: 0} + m_PrefabInternal: {fileID: 100100000} + m_GameObject: {fileID: 100000} + m_Enabled: 1 + m_EditorHideFlags: 0 + m_Script: {fileID: 11500000, guid: 0d545b1288d5fc74d8e6c961fb67ab18, type: 3} + m_Name: + m_EditorClassIdentifier: + afterFrames: 0 + afterSeconds: 0 + callOnMethod: 14 + methodToCall: 1 +--- !u!1001 &100100000 +Prefab: + m_ObjectHideFlags: 1 + serializedVersion: 2 + m_Modification: + m_TransformParent: {fileID: 0} + m_Modifications: [] + m_RemovedComponents: [] + m_ParentPrefab: {fileID: 0} + m_RootGameObject: {fileID: 100000} + m_IsPrefabParent: 1 diff --git a/Assets/UnityTestTools/IntegrationTestsFramework/TestingAssets/CubeTriggerFailure.prefab.meta b/Assets/UnityTestTools/IntegrationTestsFramework/TestingAssets/CubeTriggerFailure.prefab.meta new file mode 100644 index 00000000..e04b231f --- /dev/null +++ b/Assets/UnityTestTools/IntegrationTestsFramework/TestingAssets/CubeTriggerFailure.prefab.meta @@ -0,0 +1,4 @@ +fileFormatVersion: 2 +guid: 616ddafe39e02da4081e56f7f763af3c +NativeFormatImporter: + userData: diff --git a/Assets/UnityTestTools/IntegrationTestsFramework/TestingAssets/CubeTriggerSuccess.prefab b/Assets/UnityTestTools/IntegrationTestsFramework/TestingAssets/CubeTriggerSuccess.prefab new file mode 100644 index 00000000..7bc6c481 --- /dev/null +++ b/Assets/UnityTestTools/IntegrationTestsFramework/TestingAssets/CubeTriggerSuccess.prefab @@ -0,0 +1,102 @@ +%YAML 1.1 +%TAG !u! tag:unity3d.com,2011: +--- !u!1 &100000 +GameObject: + m_ObjectHideFlags: 0 + m_PrefabParentObject: {fileID: 0} + m_PrefabInternal: {fileID: 100100000} + serializedVersion: 4 + m_Component: + - 4: {fileID: 400000} + - 33: {fileID: 3300000} + - 65: {fileID: 6500000} + - 23: {fileID: 2300000} + - 114: {fileID: 11400000} + m_Layer: 0 + m_Name: CubeTriggerSuccess + m_TagString: Untagged + m_Icon: {fileID: 0} + m_NavMeshLayer: 0 + m_StaticEditorFlags: 0 + m_IsActive: 1 +--- !u!4 &400000 +Transform: + m_ObjectHideFlags: 1 + m_PrefabParentObject: {fileID: 0} + m_PrefabInternal: {fileID: 100100000} + m_GameObject: {fileID: 100000} + m_LocalRotation: {x: 0, y: 0, z: 0, w: 1} + m_LocalPosition: {x: 0, y: 0, z: 0} + m_LocalScale: {x: 1, y: 1, z: 1} + m_Children: [] + m_Father: {fileID: 0} + m_RootOrder: 0 +--- !u!23 &2300000 +MeshRenderer: + m_ObjectHideFlags: 1 + m_PrefabParentObject: {fileID: 0} + m_PrefabInternal: {fileID: 100100000} + m_GameObject: {fileID: 100000} + m_Enabled: 1 + m_CastShadows: 0 + m_ReceiveShadows: 0 + m_Materials: + - {fileID: 2100000, guid: 43da3275cd08d41429f56675d70c58df, type: 2} + m_SubsetIndices: + m_StaticBatchRoot: {fileID: 0} + m_UseLightProbes: 0 + m_ReflectionProbeUsage: 1 + m_ProbeAnchor: {fileID: 0} + m_ScaleInLightmap: 1 + m_PreserveUVs: 0 + m_ImportantGI: 0 + m_AutoUVMaxDistance: .5 + m_AutoUVMaxAngle: 89 + m_LightmapParameters: {fileID: 0} + m_SortingLayerID: 0 + m_SortingOrder: 0 +--- !u!33 &3300000 +MeshFilter: + m_ObjectHideFlags: 1 + m_PrefabParentObject: {fileID: 0} + m_PrefabInternal: {fileID: 100100000} + m_GameObject: {fileID: 100000} + m_Mesh: {fileID: 10202, guid: 0000000000000000e000000000000000, type: 0} +--- !u!65 &6500000 +BoxCollider: + m_ObjectHideFlags: 1 + m_PrefabParentObject: {fileID: 0} + m_PrefabInternal: {fileID: 100100000} + m_GameObject: {fileID: 100000} + m_Material: {fileID: 0} + m_IsTrigger: 1 + m_Enabled: 1 + serializedVersion: 2 + m_Size: {x: 1, y: 1, z: 1} + m_Center: {x: 0, y: 0, z: 0} +--- !u!114 &11400000 +MonoBehaviour: + m_ObjectHideFlags: 1 + m_PrefabParentObject: {fileID: 0} + m_PrefabInternal: {fileID: 100100000} + m_GameObject: {fileID: 100000} + m_Enabled: 1 + m_EditorHideFlags: 0 + m_Script: {fileID: 11500000, guid: 0d545b1288d5fc74d8e6c961fb67ab18, type: 3} + m_Name: + m_EditorClassIdentifier: + afterFrames: 0 + afterSeconds: 0 + callOnMethod: 14 + methodToCall: 0 +--- !u!1001 &100100000 +Prefab: + m_ObjectHideFlags: 1 + serializedVersion: 2 + m_Modification: + m_TransformParent: {fileID: 0} + m_Modifications: [] + m_RemovedComponents: [] + m_ParentPrefab: {fileID: 0} + m_RootGameObject: {fileID: 100000} + m_IsPrefabParent: 1 diff --git a/Assets/UnityTestTools/IntegrationTestsFramework/TestingAssets/CubeTriggerSuccess.prefab.meta b/Assets/UnityTestTools/IntegrationTestsFramework/TestingAssets/CubeTriggerSuccess.prefab.meta new file mode 100644 index 00000000..d16c91a5 --- /dev/null +++ b/Assets/UnityTestTools/IntegrationTestsFramework/TestingAssets/CubeTriggerSuccess.prefab.meta @@ -0,0 +1,4 @@ +fileFormatVersion: 2 +guid: d940e636fd44be84e9b7e8da46f700ef +NativeFormatImporter: + userData: diff --git a/Assets/UnityTestTools/IntegrationTestsFramework/TestingAssets/Materials.meta b/Assets/UnityTestTools/IntegrationTestsFramework/TestingAssets/Materials.meta new file mode 100644 index 00000000..ea98c418 --- /dev/null +++ b/Assets/UnityTestTools/IntegrationTestsFramework/TestingAssets/Materials.meta @@ -0,0 +1,5 @@ +fileFormatVersion: 2 +guid: 8d55f43641ba3c14eaa1156abc0edabd +folderAsset: yes +DefaultImporter: + userData: diff --git a/Assets/UnityTestTools/IntegrationTestsFramework/TestingAssets/Materials/green.mat b/Assets/UnityTestTools/IntegrationTestsFramework/TestingAssets/Materials/green.mat new file mode 100644 index 00000000..7a2022eb --- /dev/null +++ b/Assets/UnityTestTools/IntegrationTestsFramework/TestingAssets/Materials/green.mat @@ -0,0 +1,30 @@ +%YAML 1.1 +%TAG !u! tag:unity3d.com,2011: +--- !u!21 &2100000 +Material: + serializedVersion: 6 + m_ObjectHideFlags: 0 + m_PrefabParentObject: {fileID: 0} + m_PrefabInternal: {fileID: 0} + m_Name: green + m_Shader: {fileID: 7, guid: 0000000000000000f000000000000000, type: 0} + m_ShaderKeywords: + m_LightmapFlags: 5 + m_CustomRenderQueue: -1 + stringTagMap: {} + m_SavedProperties: + serializedVersion: 2 + m_TexEnvs: + data: + first: + name: _MainTex + second: + m_Texture: {fileID: 2800000, guid: 928be703400f4eb48af2f94d55bf3f74, type: 3} + m_Scale: {x: 1, y: 1} + m_Offset: {x: 0, y: 0} + m_Floats: {} + m_Colors: + data: + first: + name: _Color + second: {r: 1, g: 1, b: 1, a: 1} diff --git a/Assets/UnityTestTools/IntegrationTestsFramework/TestingAssets/Materials/green.mat.meta b/Assets/UnityTestTools/IntegrationTestsFramework/TestingAssets/Materials/green.mat.meta new file mode 100644 index 00000000..2cabfad7 --- /dev/null +++ b/Assets/UnityTestTools/IntegrationTestsFramework/TestingAssets/Materials/green.mat.meta @@ -0,0 +1,4 @@ +fileFormatVersion: 2 +guid: 43da3275cd08d41429f56675d70c58df +NativeFormatImporter: + userData: diff --git a/Assets/UnityTestTools/IntegrationTestsFramework/TestingAssets/Materials/red.mat b/Assets/UnityTestTools/IntegrationTestsFramework/TestingAssets/Materials/red.mat new file mode 100644 index 00000000..3f06b36a --- /dev/null +++ b/Assets/UnityTestTools/IntegrationTestsFramework/TestingAssets/Materials/red.mat @@ -0,0 +1,30 @@ +%YAML 1.1 +%TAG !u! tag:unity3d.com,2011: +--- !u!21 &2100000 +Material: + serializedVersion: 6 + m_ObjectHideFlags: 0 + m_PrefabParentObject: {fileID: 0} + m_PrefabInternal: {fileID: 0} + m_Name: red + m_Shader: {fileID: 7, guid: 0000000000000000f000000000000000, type: 0} + m_ShaderKeywords: + m_LightmapFlags: 5 + m_CustomRenderQueue: -1 + stringTagMap: {} + m_SavedProperties: + serializedVersion: 2 + m_TexEnvs: + data: + first: + name: _MainTex + second: + m_Texture: {fileID: 2800000, guid: 591632297e74ba34fa4c65d1265d370a, type: 3} + m_Scale: {x: 1, y: 1} + m_Offset: {x: 0, y: 0} + m_Floats: {} + m_Colors: + data: + first: + name: _Color + second: {r: 1, g: 1, b: 1, a: 1} diff --git a/Assets/UnityTestTools/IntegrationTestsFramework/TestingAssets/Materials/red.mat.meta b/Assets/UnityTestTools/IntegrationTestsFramework/TestingAssets/Materials/red.mat.meta new file mode 100644 index 00000000..90565c1e --- /dev/null +++ b/Assets/UnityTestTools/IntegrationTestsFramework/TestingAssets/Materials/red.mat.meta @@ -0,0 +1,4 @@ +fileFormatVersion: 2 +guid: 03f3b4747259a364b800508ac27e1c17 +NativeFormatImporter: + userData: diff --git a/Assets/UnityTestTools/IntegrationTestsFramework/TestingAssets/green.png b/Assets/UnityTestTools/IntegrationTestsFramework/TestingAssets/green.png new file mode 100644 index 00000000..f4dcca21 Binary files /dev/null and b/Assets/UnityTestTools/IntegrationTestsFramework/TestingAssets/green.png differ diff --git a/Assets/UnityTestTools/IntegrationTestsFramework/TestingAssets/green.png.meta b/Assets/UnityTestTools/IntegrationTestsFramework/TestingAssets/green.png.meta new file mode 100644 index 00000000..2add2695 --- /dev/null +++ b/Assets/UnityTestTools/IntegrationTestsFramework/TestingAssets/green.png.meta @@ -0,0 +1,53 @@ +fileFormatVersion: 2 +guid: 928be703400f4eb48af2f94d55bf3f74 +TextureImporter: + serializedVersion: 2 + mipmaps: + mipMapMode: 0 + enableMipMap: 1 + linearTexture: 0 + correctGamma: 0 + fadeOut: 0 + borderMipMap: 0 + mipMapFadeDistanceStart: 1 + mipMapFadeDistanceEnd: 3 + bumpmap: + convertToNormalMap: 0 + externalNormalMap: 0 + heightScale: .25 + normalMapFilter: 0 + isReadable: 0 + grayScaleToAlpha: 0 + generateCubemap: 0 + seamlessCubemap: 0 + textureFormat: -1 + maxTextureSize: 1024 + textureSettings: + filterMode: -1 + aniso: -1 + mipBias: -1 + wrapMode: -1 + nPOTScale: 1 + lightmap: 0 + compressionQuality: 50 + spriteMode: 0 + autoDetectMinSpriteSize: 4 + gridPadding: 0 + gridOffsetX: 0 + gridOffsetY: 0 + gridSizeX: 64 + gridSizeY: 64 + spriteExtrude: 1 + alignment: 0 + spritePivot: {x: .5, y: .5} + spriteAtlasHint: 0 + spritePixelsToUnits: 100 + generateSpritePolygon: 0 + spritePolygonAlphaCutoff: 254 + spritePolygonDetail: .5 + alphaIsTransparency: 0 + textureType: -1 + buildTargetSettings: [] + spriteSheet: + spriteFrames: [] + userData: diff --git a/Assets/UnityTestTools/IntegrationTestsFramework/TestingAssets/red.png b/Assets/UnityTestTools/IntegrationTestsFramework/TestingAssets/red.png new file mode 100644 index 00000000..8b29b459 Binary files /dev/null and b/Assets/UnityTestTools/IntegrationTestsFramework/TestingAssets/red.png differ diff --git a/Assets/UnityTestTools/IntegrationTestsFramework/TestingAssets/red.png.meta b/Assets/UnityTestTools/IntegrationTestsFramework/TestingAssets/red.png.meta new file mode 100644 index 00000000..9cda8d85 --- /dev/null +++ b/Assets/UnityTestTools/IntegrationTestsFramework/TestingAssets/red.png.meta @@ -0,0 +1,53 @@ +fileFormatVersion: 2 +guid: 591632297e74ba34fa4c65d1265d370a +TextureImporter: + serializedVersion: 2 + mipmaps: + mipMapMode: 0 + enableMipMap: 1 + linearTexture: 0 + correctGamma: 0 + fadeOut: 0 + borderMipMap: 0 + mipMapFadeDistanceStart: 1 + mipMapFadeDistanceEnd: 3 + bumpmap: + convertToNormalMap: 0 + externalNormalMap: 0 + heightScale: .25 + normalMapFilter: 0 + isReadable: 0 + grayScaleToAlpha: 0 + generateCubemap: 0 + seamlessCubemap: 0 + textureFormat: -1 + maxTextureSize: 1024 + textureSettings: + filterMode: -1 + aniso: -1 + mipBias: -1 + wrapMode: -1 + nPOTScale: 1 + lightmap: 0 + compressionQuality: 50 + spriteMode: 0 + autoDetectMinSpriteSize: 4 + gridPadding: 0 + gridOffsetX: 0 + gridOffsetY: 0 + gridSizeX: 64 + gridSizeY: 64 + spriteExtrude: 1 + alignment: 0 + spritePivot: {x: .5, y: .5} + spriteAtlasHint: 0 + spritePixelsToUnits: 100 + generateSpritePolygon: 0 + spritePolygonAlphaCutoff: 254 + spritePolygonDetail: .5 + alphaIsTransparency: 0 + textureType: -1 + buildTargetSettings: [] + spriteSheet: + spriteFrames: [] + userData: diff --git a/Assets/UnityTestTools/LICENSE.txt b/Assets/UnityTestTools/LICENSE.txt new file mode 100644 index 00000000..8da6126c --- /dev/null +++ b/Assets/UnityTestTools/LICENSE.txt @@ -0,0 +1,83 @@ +This software is provided 'as-is', without any express or implied warranty. + + +THE UNITY TEST TOOLS CONTAIN THE FOLLOWING THIRD PARTY LIBRARIES: +NSubstitute Copyright (c) 2009 Anthony Egerton (nsubstitute@delfish.com) and David Tchepak (dave@davesquared.net). All rights reserved. +NUnit Portions Copyright © 2002-2009 Charlie Poole or Copyright © 2002-2004 James W. Newkirk, Michael C. Two, Alexei A. Vorontsov or Copyright © 2000-2002 Philip A. Craig +Cecil Copyright (c) 2008 - 2011, Jb Evain + + + +NSubstitute is open source software, licensed under the BSD License. The modifications made by Unity are available on github. + +Copyright (c) 2009 Anthony Egerton (nsubstitute@delfish.com) and David Tchepak (dave@davesquared.net) +All rights reserved. + +Redistribution and use in source and binary forms, with or without modification, +are permitted provided that the following conditions are met: + + * Redistributions of source code must retain the above copyright notice, + this list of conditions and the following disclaimer. + * Redistributions in binary form must reproduce the above copyright notice, + this list of conditions and the following disclaimer in the documentation + and/or other materials provided with the distribution. + * Neither the names of the copyright holders nor the names of + contributors may be used to endorse or promote products derived from this + software without specific prior written permission. + +THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND +ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED +WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE +DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE LIABLE +FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL +DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR +SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER +CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, +OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF +THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + +[ http://www.opensource.org/licenses/bsd-license.php ] + + + +NUnit is provided 'as-is', without any express or implied warranty. The modifications made by Unity are available on github. + +Copyright © 2002-2013 Charlie Poole +Copyright © 2002-2004 James W. Newkirk, Michael C. Two, Alexei A. Vorontsov +Copyright © 2000-2002 Philip A. Craig + +This software is provided 'as-is', without any express or implied warranty. In no event will the authors be held liable for any damages arising from the use of this software. + +Permission is granted to anyone to use this software for any purpose, including commercial applications, and to alter it and redistribute it freely, subject to the following restrictions: + +1. The origin of this software must not be misrepresented; you must not claim that you wrote the original software. If you use this software in a product, an acknowledgment (see the following) in the product documentation is required. + +Portions Copyright © 2002-2013 Charlie Poole or Copyright © 2002-2004 James W. Newkirk, Michael C. Two, Alexei A. Vorontsov or Copyright © 2000-2002 Philip A. Craig + +2. Altered source versions must be plainly marked as such, and must not be misrepresented as being the original software. + +3. This notice may not be removed or altered from any source distribution. + + + +Cecil is licensed under the MIT/X11. + +Copyright (c) 2008 - 2011, Jb Evain + +Permission is hereby granted, free of charge, to any person obtaining a copy +of this software and associated documentation files (the "Software"), to deal +in the Software without restriction, including without limitation the rights +to use, copy, modify, merge, publish, distribute, sublicense, and/or sell +copies of the Software, and to permit persons to whom the Software is +furnished to do so, subject to the following conditions: + +The above copyright notice and this permission notice shall be included in +all copies or substantial portions of the Software. + +THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN +THE SOFTWARE. \ No newline at end of file diff --git a/Assets/UnityTestTools/LICENSE.txt.meta b/Assets/UnityTestTools/LICENSE.txt.meta new file mode 100644 index 00000000..6a87d690 --- /dev/null +++ b/Assets/UnityTestTools/LICENSE.txt.meta @@ -0,0 +1,4 @@ +fileFormatVersion: 2 +guid: 0d5b4501bf773f349ad95ec34491dc61 +TextScriptImporter: + userData: diff --git a/Assets/UnityTestTools/UnitTesting.meta b/Assets/UnityTestTools/UnitTesting.meta new file mode 100644 index 00000000..10d2dd53 --- /dev/null +++ b/Assets/UnityTestTools/UnitTesting.meta @@ -0,0 +1,5 @@ +fileFormatVersion: 2 +guid: 9a87f1db904f1e948a2385ab9961e3aa +folderAsset: yes +DefaultImporter: + userData: diff --git a/Assets/UnityTestTools/UnitTesting/Editor.meta b/Assets/UnityTestTools/UnitTesting/Editor.meta new file mode 100644 index 00000000..802e986e --- /dev/null +++ b/Assets/UnityTestTools/UnitTesting/Editor.meta @@ -0,0 +1,4 @@ +fileFormatVersion: 2 +guid: 59b47eb3fc62eb44cb73a329a1e6b6cb +DefaultImporter: + userData: diff --git a/Assets/UnityTestTools/UnitTesting/Editor/Batch.cs b/Assets/UnityTestTools/UnitTesting/Editor/Batch.cs new file mode 100644 index 00000000..0235e891 --- /dev/null +++ b/Assets/UnityTestTools/UnitTesting/Editor/Batch.cs @@ -0,0 +1,123 @@ +using System; +using System.Collections.Generic; +using System.IO; +using System.Linq; +using UnityEditor; +using UnityEngine; +using UnityTest.UnitTestRunner; + +namespace UnityTest +{ + public static partial class Batch + { + const string k_ResultFilePathParam = "-resultFilePath="; + const string k_TestFilterParam = "-filter="; + const string k_CategoryParam = "-categories="; + const string k_DefaultResultFileName = "UnitTestResults.xml"; + + public static int returnCodeTestsOk = 0; + public static int returnCodeTestsFailed = 2; + public static int returnCodeRunError = 3; + + public static void RunUnitTests() + { + PlayerSettings.useMacAppStoreValidation = false; + var filter = GetTestFilter(); + var resultFilePath = GetParameterArgument(k_ResultFilePathParam) ?? Directory.GetCurrentDirectory(); + if (Directory.Exists(resultFilePath)) + resultFilePath = Path.Combine(resultFilePath, k_DefaultResultFileName); + EditorApplication.NewScene(); + var engine = new NUnitTestEngine(); + UnitTestResult[] results; + string[] categories; + engine.GetTests(out results, out categories); + engine.RunTests(filter, new TestRunnerEventListener(resultFilePath, results.ToList())); + } + + private static TestFilter GetTestFilter() + { + var testFilterArg = GetParameterArgumentArray(k_TestFilterParam); + var testCategoryArg = GetParameterArgumentArray(k_CategoryParam); + var filter = new TestFilter + { + names = testFilterArg, + categories = testCategoryArg + }; + return filter; + } + + private static string[] GetParameterArgumentArray(string parameterName) + { + var arg = GetParameterArgument(parameterName); + if (string.IsNullOrEmpty(arg)) return null; + return arg.Split(',').Select(s => s.Trim()).ToArray(); + } + + private static string GetParameterArgument(string parameterName) + { + foreach (var arg in Environment.GetCommandLineArgs()) + { + if (arg.ToLower().StartsWith(parameterName.ToLower())) + { + return arg.Substring(parameterName.Length); + } + } + return null; + } + + private class TestRunnerEventListener : ITestRunnerCallback + { + private readonly string m_ResultFilePath; + private readonly List m_Results; + + public TestRunnerEventListener(string resultFilePath, List resultList) + { + m_ResultFilePath = resultFilePath; + m_Results = resultList; + } + + public void TestFinished(ITestResult test) + { + m_Results.Single(r => r.Id == test.Id).Update(test, false); + } + + public void RunFinished() + { + var resultDestiantion = Application.dataPath; + if (!string.IsNullOrEmpty(m_ResultFilePath)) + resultDestiantion = m_ResultFilePath; + var fileName = Path.GetFileName(resultDestiantion); + if (!string.IsNullOrEmpty(fileName)) + resultDestiantion = resultDestiantion.Substring(0, resultDestiantion.Length - fileName.Length); + else + fileName = "UnitTestResults.xml"; +#if !UNITY_METRO + var resultWriter = new XmlResultWriter("Unit Tests", "Editor", m_Results.ToArray()); + resultWriter.WriteToFile(resultDestiantion, fileName); +#endif + var executed = m_Results.Where(result => result.Executed); + if (!executed.Any()) + { + EditorApplication.Exit(returnCodeRunError); + return; + } + var failed = executed.Where(result => !result.IsSuccess); + EditorApplication.Exit(failed.Any() ? returnCodeTestsFailed : returnCodeTestsOk); + } + + public void TestStarted(string fullName) + { + } + + public void RunStarted(string suiteName, int testCount) + { + } + + public void RunFinishedException(Exception exception) + { + EditorApplication.Exit(returnCodeRunError); + throw exception; + } + } + } +} diff --git a/Assets/UnityTestTools/UnitTesting/Editor/Batch.cs.meta b/Assets/UnityTestTools/UnitTesting/Editor/Batch.cs.meta new file mode 100644 index 00000000..4595868f --- /dev/null +++ b/Assets/UnityTestTools/UnitTesting/Editor/Batch.cs.meta @@ -0,0 +1,8 @@ +fileFormatVersion: 2 +guid: 5401885870ebec84f8e9c6ee18d79695 +MonoImporter: + serializedVersion: 2 + defaultReferences: [] + executionOrder: 0 + icon: {instanceID: 0} + userData: diff --git a/Assets/UnityTestTools/UnitTesting/Editor/NSubstitute.meta b/Assets/UnityTestTools/UnitTesting/Editor/NSubstitute.meta new file mode 100644 index 00000000..9a7cadb6 --- /dev/null +++ b/Assets/UnityTestTools/UnitTesting/Editor/NSubstitute.meta @@ -0,0 +1,5 @@ +fileFormatVersion: 2 +guid: 92b38897656771f409e9235955975754 +folderAsset: yes +DefaultImporter: + userData: diff --git a/Assets/UnityTestTools/UnitTesting/Editor/NSubstitute/NSubstitute.dll b/Assets/UnityTestTools/UnitTesting/Editor/NSubstitute/NSubstitute.dll new file mode 100644 index 00000000..ee8b155d Binary files /dev/null and b/Assets/UnityTestTools/UnitTesting/Editor/NSubstitute/NSubstitute.dll differ diff --git a/Assets/UnityTestTools/UnitTesting/Editor/NSubstitute/NSubstitute.dll.meta b/Assets/UnityTestTools/UnitTesting/Editor/NSubstitute/NSubstitute.dll.meta new file mode 100644 index 00000000..38d55f5f --- /dev/null +++ b/Assets/UnityTestTools/UnitTesting/Editor/NSubstitute/NSubstitute.dll.meta @@ -0,0 +1,7 @@ +fileFormatVersion: 2 +guid: 3c5e1afc6e0d68849ae6639aff58cfc7 +MonoAssemblyImporter: + serializedVersion: 1 + iconMap: {} + executionOrder: {} + userData: diff --git a/Assets/UnityTestTools/UnitTesting/Editor/NUnit.meta b/Assets/UnityTestTools/UnitTesting/Editor/NUnit.meta new file mode 100644 index 00000000..a94ee3ea --- /dev/null +++ b/Assets/UnityTestTools/UnitTesting/Editor/NUnit.meta @@ -0,0 +1,5 @@ +fileFormatVersion: 2 +guid: a92d914a774b29f42906161a387d79f7 +folderAsset: yes +DefaultImporter: + userData: diff --git a/Assets/UnityTestTools/UnitTesting/Editor/NUnit/Libs.meta b/Assets/UnityTestTools/UnitTesting/Editor/NUnit/Libs.meta new file mode 100644 index 00000000..96b0d0fb --- /dev/null +++ b/Assets/UnityTestTools/UnitTesting/Editor/NUnit/Libs.meta @@ -0,0 +1,5 @@ +fileFormatVersion: 2 +guid: e22ba039de7077c4aa95758ef723b803 +folderAsset: yes +DefaultImporter: + userData: diff --git a/Assets/UnityTestTools/UnitTesting/Editor/NUnit/Libs/Mono.Cecil.Mdb.dll b/Assets/UnityTestTools/UnitTesting/Editor/NUnit/Libs/Mono.Cecil.Mdb.dll new file mode 100644 index 00000000..2ddd9768 Binary files /dev/null and b/Assets/UnityTestTools/UnitTesting/Editor/NUnit/Libs/Mono.Cecil.Mdb.dll differ diff --git a/Assets/UnityTestTools/UnitTesting/Editor/NUnit/Libs/Mono.Cecil.Mdb.dll.meta b/Assets/UnityTestTools/UnitTesting/Editor/NUnit/Libs/Mono.Cecil.Mdb.dll.meta new file mode 100644 index 00000000..36a8c4fc --- /dev/null +++ b/Assets/UnityTestTools/UnitTesting/Editor/NUnit/Libs/Mono.Cecil.Mdb.dll.meta @@ -0,0 +1,7 @@ +fileFormatVersion: 2 +guid: 96c89cba541e8fa41acc13fcc8382878 +MonoAssemblyImporter: + serializedVersion: 1 + iconMap: {} + executionOrder: {} + userData: diff --git a/Assets/UnityTestTools/UnitTesting/Editor/NUnit/Libs/Mono.Cecil.dll b/Assets/UnityTestTools/UnitTesting/Editor/NUnit/Libs/Mono.Cecil.dll new file mode 100644 index 00000000..5e59e646 Binary files /dev/null and b/Assets/UnityTestTools/UnitTesting/Editor/NUnit/Libs/Mono.Cecil.dll differ diff --git a/Assets/UnityTestTools/UnitTesting/Editor/NUnit/Libs/Mono.Cecil.dll.meta b/Assets/UnityTestTools/UnitTesting/Editor/NUnit/Libs/Mono.Cecil.dll.meta new file mode 100644 index 00000000..242413a9 --- /dev/null +++ b/Assets/UnityTestTools/UnitTesting/Editor/NUnit/Libs/Mono.Cecil.dll.meta @@ -0,0 +1,7 @@ +fileFormatVersion: 2 +guid: f8f4181eb51beb043a92433b1c807529 +MonoAssemblyImporter: + serializedVersion: 1 + iconMap: {} + executionOrder: {} + userData: diff --git a/Assets/UnityTestTools/UnitTesting/Editor/NUnit/Libs/nunit.core.dll b/Assets/UnityTestTools/UnitTesting/Editor/NUnit/Libs/nunit.core.dll new file mode 100644 index 00000000..46832d4e Binary files /dev/null and b/Assets/UnityTestTools/UnitTesting/Editor/NUnit/Libs/nunit.core.dll differ diff --git a/Assets/UnityTestTools/UnitTesting/Editor/NUnit/Libs/nunit.core.dll.meta b/Assets/UnityTestTools/UnitTesting/Editor/NUnit/Libs/nunit.core.dll.meta new file mode 100644 index 00000000..75176314 --- /dev/null +++ b/Assets/UnityTestTools/UnitTesting/Editor/NUnit/Libs/nunit.core.dll.meta @@ -0,0 +1,7 @@ +fileFormatVersion: 2 +guid: e8950b8b4aa418a458a503526c8a2f65 +MonoAssemblyImporter: + serializedVersion: 1 + iconMap: {} + executionOrder: {} + userData: diff --git a/Assets/UnityTestTools/UnitTesting/Editor/NUnit/Libs/nunit.core.interfaces.dll b/Assets/UnityTestTools/UnitTesting/Editor/NUnit/Libs/nunit.core.interfaces.dll new file mode 100644 index 00000000..1f2e48a6 Binary files /dev/null and b/Assets/UnityTestTools/UnitTesting/Editor/NUnit/Libs/nunit.core.interfaces.dll differ diff --git a/Assets/UnityTestTools/UnitTesting/Editor/NUnit/Libs/nunit.core.interfaces.dll.meta b/Assets/UnityTestTools/UnitTesting/Editor/NUnit/Libs/nunit.core.interfaces.dll.meta new file mode 100644 index 00000000..b68531c9 --- /dev/null +++ b/Assets/UnityTestTools/UnitTesting/Editor/NUnit/Libs/nunit.core.interfaces.dll.meta @@ -0,0 +1,7 @@ +fileFormatVersion: 2 +guid: ef8ce5f8e3e580349ac63ac38e87ee2f +MonoAssemblyImporter: + serializedVersion: 1 + iconMap: {} + executionOrder: {} + userData: diff --git a/Assets/UnityTestTools/UnitTesting/Editor/NUnit/Libs/nunit.framework.dll b/Assets/UnityTestTools/UnitTesting/Editor/NUnit/Libs/nunit.framework.dll new file mode 100644 index 00000000..a50e5313 Binary files /dev/null and b/Assets/UnityTestTools/UnitTesting/Editor/NUnit/Libs/nunit.framework.dll differ diff --git a/Assets/UnityTestTools/UnitTesting/Editor/NUnit/Libs/nunit.framework.dll.meta b/Assets/UnityTestTools/UnitTesting/Editor/NUnit/Libs/nunit.framework.dll.meta new file mode 100644 index 00000000..dad72f0b --- /dev/null +++ b/Assets/UnityTestTools/UnitTesting/Editor/NUnit/Libs/nunit.framework.dll.meta @@ -0,0 +1,7 @@ +fileFormatVersion: 2 +guid: f54558e884607254ca91abc9038ac749 +MonoAssemblyImporter: + serializedVersion: 1 + iconMap: {} + executionOrder: {} + userData: diff --git a/Assets/UnityTestTools/UnitTesting/Editor/NUnit/Renderer.meta b/Assets/UnityTestTools/UnitTesting/Editor/NUnit/Renderer.meta new file mode 100644 index 00000000..4ad9a615 --- /dev/null +++ b/Assets/UnityTestTools/UnitTesting/Editor/NUnit/Renderer.meta @@ -0,0 +1,4 @@ +fileFormatVersion: 2 +guid: f94e120956782c5498f559719ff38f2a +DefaultImporter: + userData: diff --git a/Assets/UnityTestTools/UnitTesting/Editor/NUnit/Renderer/GroupLine.cs b/Assets/UnityTestTools/UnitTesting/Editor/NUnit/Renderer/GroupLine.cs new file mode 100644 index 00000000..058e0352 --- /dev/null +++ b/Assets/UnityTestTools/UnitTesting/Editor/NUnit/Renderer/GroupLine.cs @@ -0,0 +1,156 @@ +using System; +using System.Collections.Generic; +using System.Linq; +using NUnit.Core; +using UnityEditor; +using UnityEngine; +using Event = UnityEngine.Event; + +namespace UnityTest +{ + public class GroupLine : UnitTestRendererLine + { + public static List FoldMarkers; + + protected static GUIContent s_GUIExpandAll = new GUIContent("Expand all"); + protected static GUIContent s_GUICollapseAll = new GUIContent("Collapse all"); + private readonly List m_Children = new List(); + + public GroupLine(TestSuite suite) + : base(suite) + { + if (suite is NamespaceSuite) m_RenderedName = m_FullName; + } + + private bool Folded + { + get { return FoldMarkers.Contains(m_FullName); } + + set + { + if (value) + FoldMarkers.Add(m_FullName); + else + FoldMarkers.RemoveAll(s => s == m_FullName); + } + } + + public void AddChildren(UnitTestRendererLine[] children) + { + m_Children.AddRange(children); + } + + protected internal override void Render(int indend, RenderingOptions options) + { + if (!AnyVisibleChildren(options)) return; + base.Render(indend, options); + if (!Folded) + foreach (var child in m_Children) + child.Render(indend + 1, options); + } + + private bool AnyVisibleChildren(RenderingOptions options) + { + return m_Children.Any(l => l.IsVisible(options)); + } + + protected internal override bool IsVisible(RenderingOptions options) + { + return AnyVisibleChildren(options); + } + + protected override void DrawLine(bool isSelected, RenderingOptions options) + { + var resultIcon = GetResult().HasValue ? GuiHelper.GetIconForResult(GetResult().Value) : Icons.UnknownImg; + + var guiContent = new GUIContent(m_RenderedName, resultIcon, m_FullName); + + var rect = GUILayoutUtility.GetRect(guiContent, Styles.foldout, GUILayout.MaxHeight(16)); + + OnLeftMouseButtonClick(rect); + OnContextClick(rect); + + EditorGUI.BeginChangeCheck(); + var expanded = !EditorGUI.Foldout(rect, !Folded, guiContent, false, isSelected ? Styles.selectedFoldout : Styles.foldout); + if (EditorGUI.EndChangeCheck()) Folded = expanded; + } + + protected internal override TestResultState ? GetResult() + { + TestResultState? tempResult = null; + + foreach (var child in m_Children) + { + var childResultState = child.GetResult(); + + if (childResultState == TestResultState.Failure || childResultState == TestResultState.Error) + { + tempResult = TestResultState.Failure; + break; + } + if (childResultState == TestResultState.Success) + tempResult = TestResultState.Success; + else if (childResultState == TestResultState.Ignored) + tempResult = TestResultState.Ignored; + } + if (tempResult.HasValue) return tempResult.Value; + + return null; + } + + private void OnLeftMouseButtonClick(Rect rect) + { + if (rect.Contains(Event.current.mousePosition) && Event.current.type == EventType.mouseDown && Event.current.button == 0) + { + OnSelect(); + } + } + + private void OnContextClick(Rect rect) + { + if (rect.Contains(Event.current.mousePosition) && Event.current.type == EventType.ContextClick) + { + PrintGroupContextMenu(); + } + } + + private void PrintGroupContextMenu() + { + var multilineSelection = SelectedLines.Count() > 1; + var m = new GenericMenu(); + if (multilineSelection) + { + m.AddItem(s_GUIRunSelected, + false, + data => RunTests(SelectedLines.Select(line => line.m_Test.TestName).ToArray()), + ""); + } + if (!string.IsNullOrEmpty(m_FullName)) + { + m.AddItem(s_GUIRun, + false, + data => RunTests(new[] { m_Test.TestName }), + ""); + } + if (!multilineSelection) + { + m.AddSeparator(""); + + m.AddItem(Folded ? s_GUIExpandAll : s_GUICollapseAll, + false, + data => ExpandOrCollapseAll(Folded), + ""); + } + m.ShowAsContext(); + } + + private void ExpandOrCollapseAll(bool expand) + { + Folded = !expand; + foreach (var child in m_Children) + { + if (child is GroupLine) (child as GroupLine).ExpandOrCollapseAll(expand); + } + } + } +} diff --git a/Assets/UnityTestTools/UnitTesting/Editor/NUnit/Renderer/GroupLine.cs.meta b/Assets/UnityTestTools/UnitTesting/Editor/NUnit/Renderer/GroupLine.cs.meta new file mode 100644 index 00000000..9195f337 --- /dev/null +++ b/Assets/UnityTestTools/UnitTesting/Editor/NUnit/Renderer/GroupLine.cs.meta @@ -0,0 +1,8 @@ +fileFormatVersion: 2 +guid: 4fcef1ec40255f14d827da8b0d742334 +MonoImporter: + serializedVersion: 2 + defaultReferences: [] + executionOrder: 0 + icon: {instanceID: 0} + userData: diff --git a/Assets/UnityTestTools/UnitTesting/Editor/NUnit/Renderer/RenderingOptions.cs b/Assets/UnityTestTools/UnitTesting/Editor/NUnit/Renderer/RenderingOptions.cs new file mode 100644 index 00000000..5d16efc3 --- /dev/null +++ b/Assets/UnityTestTools/UnitTesting/Editor/NUnit/Renderer/RenderingOptions.cs @@ -0,0 +1,16 @@ +using System; +using System.Collections.Generic; +using UnityEngine; + +namespace UnityTest +{ + public class RenderingOptions + { + public string nameFilter; + public bool showSucceeded; + public bool showFailed; + public bool showIgnored; + public bool showNotRunned; + public string[] categories; + } +} diff --git a/Assets/UnityTestTools/UnitTesting/Editor/NUnit/Renderer/RenderingOptions.cs.meta b/Assets/UnityTestTools/UnitTesting/Editor/NUnit/Renderer/RenderingOptions.cs.meta new file mode 100644 index 00000000..6ecfc38c --- /dev/null +++ b/Assets/UnityTestTools/UnitTesting/Editor/NUnit/Renderer/RenderingOptions.cs.meta @@ -0,0 +1,8 @@ +fileFormatVersion: 2 +guid: 5c0aec4b4a6d1b047a98e8cc213e1a36 +MonoImporter: + serializedVersion: 2 + defaultReferences: [] + executionOrder: 0 + icon: {instanceID: 0} + userData: diff --git a/Assets/UnityTestTools/UnitTesting/Editor/NUnit/Renderer/TestLine.cs b/Assets/UnityTestTools/UnitTesting/Editor/NUnit/Renderer/TestLine.cs new file mode 100644 index 00000000..352fe8ad --- /dev/null +++ b/Assets/UnityTestTools/UnitTesting/Editor/NUnit/Renderer/TestLine.cs @@ -0,0 +1,182 @@ +using System; +using System.Collections.Generic; +using System.Linq; +using NUnit.Core; +using UnityEditor; +using UnityEngine; +using Event = UnityEngine.Event; +using System.Text; + +namespace UnityTest +{ + public class TestLine : UnitTestRendererLine, IComparable + { + public static Func GetUnitTestResult; + + protected static GUIContent s_GUIOpenInEditor = new GUIContent("Open in editor"); + private readonly string m_ResultId; + private readonly IList m_Categories; + private readonly int m_maxLineLenght = 15000; + + private GUIContent m_Content; + + public TestLine(TestMethod test, string resultId) : base(test) + { + m_RenderedName = test.Parent is ParameterizedMethodSuite ? test.TestName.Name : test.MethodName; + + if(m_RenderedName.Length > 100) + m_RenderedName = m_RenderedName.Substring(0, 100); + m_RenderedName = m_RenderedName.Replace("\n", ""); + + m_ResultId = resultId; + var c = new List(); + foreach (string category in test.Categories) + c.Add(category); + foreach (string category in test.Parent.Categories) + c.Add(category); + if (test.Parent is ParameterizedMethodSuite) + foreach (string category in test.Parent.Parent.Categories) + c.Add(category); + m_Categories = c; + m_Content = new GUIContent(m_RenderedName, null, m_FullName); + } + + public UnitTestResult result + { + get { return GetUnitTestResult(m_ResultId); } + } + + public int CompareTo(TestLine other) + { + return result.Id.CompareTo(other.result.Id); + } + + protected override void DrawLine(bool isSelected, RenderingOptions options) + { + if (!IsVisible(options)) return; + + var tempColor = GUI.color; + if (result.Executed && result.Outdated) GUI.color = new Color(1, 1, 1, 0.7f); + + var icon = result.Executed || result.IsIgnored || result.ResultState == TestResultState.NotRunnable + ? GuiHelper.GetIconForResult(result.ResultState) + : Icons.UnknownImg; + if (m_Test.RunState == RunState.Ignored) + icon = GuiHelper.GetIconForResult(TestResultState.Ignored); + + m_Content.image = icon; + + var rect = GUILayoutUtility.GetRect(m_Content, Styles.testName, GUILayout.ExpandWidth(true)); + + OnLeftMouseButtonClick(rect); + OnContextClick(rect); + + if(Event.current.type == EventType.repaint) + Styles.testName.Draw(rect, m_Content, false, false, false, isSelected); + + if (result.Outdated) GUI.color = tempColor; + } + + protected internal override TestResultState ? GetResult() + { + return result.ResultState; + } + + protected internal override bool IsVisible(RenderingOptions options) + { + if (!string.IsNullOrEmpty(options.nameFilter) && !m_FullName.ToLower().Contains(options.nameFilter.ToLower())) + return false; + if (options.categories != null && options.categories.Length > 0 && !options.categories.Any(c => m_Categories.Contains(c))) + return false; + if (!options.showIgnored && (m_Test.RunState == RunState.Ignored || (result.Executed && m_Test.RunState == RunState.Skipped))) + return false; + if (!options.showFailed && result.Executed && (result.IsFailure || result.IsError || result.IsInconclusive)) + return false; + if (!options.showNotRunned && !result.Executed && !result.IsIgnored) + return false; + if (!options.showSucceeded && result.IsSuccess) + return false; + return true; + } + + public override string GetResultText() + { + var tempTest = result; + var sb = new StringBuilder(tempTest.Name); + if (tempTest.Executed) + sb.AppendFormat(" ({0}s)", tempTest.Duration.ToString("##0.###")); + sb.AppendLine(); + + if (!string.IsNullOrEmpty(tempTest.Message)) + { + sb.AppendFormat("---\n{0}\n", tempTest.Message.Trim()); + } + if (!string.IsNullOrEmpty(tempTest.Logs)) + { + sb.AppendFormat("---\n{0}\n", tempTest.Logs.Trim()); + } + if (!tempTest.IsSuccess && !string.IsNullOrEmpty(tempTest.StackTrace)) + { + var stackTrace = StackTraceFilter.Filter(tempTest.StackTrace).Trim(); + sb.AppendFormat("---\n{0}\n", stackTrace); + } + if(sb.Length>m_maxLineLenght) + { + sb.Length = m_maxLineLenght; + sb.AppendFormat("...\n\n---MESSAGE TRUNCATED AT {0} CHARACTERS---", m_maxLineLenght); + } + return sb.ToString().Trim(); + } + + private void OnContextClick(Rect rect) + { + if (rect.Contains(Event.current.mousePosition) && Event.current.type == EventType.ContextClick) + { + Event.current.Use(); + PrintTestContextMenu(); + } + } + + private void PrintTestContextMenu() + { + var m = new GenericMenu(); + var multilineSelection = SelectedLines.Count() > 1; + if (multilineSelection) + { + m.AddItem(s_GUIRunSelected, + false, + data => RunTests(SelectedLines.Select(line => (object)line.m_Test.TestName).ToArray()), + ""); + } + if (!string.IsNullOrEmpty(m_FullName)) + { + m.AddItem(s_GUIRun, + false, + data => RunTests(new[] { (object)m_Test.TestName }), + ""); + } + if (!multilineSelection) + { + m.AddSeparator(""); + + m.AddItem(s_GUIOpenInEditor, + false, + data => GuiHelper.OpenInEditor(result, false), + ""); + } + m.ShowAsContext(); + } + + private void OnLeftMouseButtonClick(Rect rect) + { + if (rect.Contains(Event.current.mousePosition) && Event.current.type == EventType.MouseDown && Event.current.button == 0) + { + OnSelect(); + if (Event.current.clickCount == 2 && SelectedLines.Count == 1) + { + GuiHelper.OpenInEditor(result, true); + } + } + } + } +} diff --git a/Assets/UnityTestTools/UnitTesting/Editor/NUnit/Renderer/TestLine.cs.meta b/Assets/UnityTestTools/UnitTesting/Editor/NUnit/Renderer/TestLine.cs.meta new file mode 100644 index 00000000..5f6cf259 --- /dev/null +++ b/Assets/UnityTestTools/UnitTesting/Editor/NUnit/Renderer/TestLine.cs.meta @@ -0,0 +1,8 @@ +fileFormatVersion: 2 +guid: cfe0c7d95a79d374e9121633c719241e +MonoImporter: + serializedVersion: 2 + defaultReferences: [] + executionOrder: 0 + icon: {instanceID: 0} + userData: diff --git a/Assets/UnityTestTools/UnitTesting/Editor/NUnit/Renderer/UnitTestRendererLine.cs b/Assets/UnityTestTools/UnitTesting/Editor/NUnit/Renderer/UnitTestRendererLine.cs new file mode 100644 index 00000000..826711d6 --- /dev/null +++ b/Assets/UnityTestTools/UnitTesting/Editor/NUnit/Renderer/UnitTestRendererLine.cs @@ -0,0 +1,104 @@ +using System; +using System.Collections.Generic; +using System.Linq; +using NUnit.Core; +using UnityEditor; +using UnityEngine; +using Event = UnityEngine.Event; + +namespace UnityTest +{ + public abstract class UnitTestRendererLine : IComparable + { + public static Action RunTest; + public static List SelectedLines; + + protected static bool s_Refresh; + + protected static GUIContent s_GUIRunSelected = new GUIContent("Run Selected"); + protected static GUIContent s_GUIRun = new GUIContent("Run"); + protected static GUIContent s_GUITimeoutIcon = new GUIContent(Icons.StopwatchImg, "Timeout"); + + protected string m_UniqueId; + protected internal string m_FullName; + protected string m_RenderedName; + protected internal Test m_Test; + + protected UnitTestRendererLine(Test test) + { + m_FullName = test.TestName.FullName; + m_RenderedName = test.TestName.Name; + m_UniqueId = test.TestName.UniqueName; + + m_Test = test; + } + + public int CompareTo(UnitTestRendererLine other) + { + return m_UniqueId.CompareTo(other.m_UniqueId); + } + + public bool Render(RenderingOptions options) + { + s_Refresh = false; + EditorGUIUtility.SetIconSize(new Vector2(15, 15)); + Render(0, options); + EditorGUIUtility.SetIconSize(Vector2.zero); + return s_Refresh; + } + + protected internal virtual void Render(int indend, RenderingOptions options) + { + EditorGUILayout.BeginHorizontal(); + GUILayout.Space(indend * 10); + DrawLine(SelectedLines.Contains(this), options); + EditorGUILayout.EndHorizontal(); + } + + protected void OnSelect() + { + if (!Event.current.control && !Event.current.command) + { + SelectedLines.Clear(); + GUIUtility.keyboardControl = 0; + } + if ((Event.current.control || Event.current.command) && SelectedLines.Contains(this)) + SelectedLines.Remove(this); + else + SelectedLines.Add(this); + s_Refresh = true; + } + + protected abstract void DrawLine(bool isSelected, RenderingOptions options); + protected internal abstract TestResultState ? GetResult(); + protected internal abstract bool IsVisible(RenderingOptions options); + + public void RunTests(object[] testObjectsList) + { + RunTest(new TestFilter { objects = testObjectsList }); + } + + public void RunTests(string[] testList) + { + RunTest(new TestFilter {names = testList}); + } + + public void RunSelectedTests() + { + RunTest(new TestFilter { objects = SelectedLines.Select(line => line.m_Test.TestName).ToArray() }); + } + + public bool IsAnySelected + { + get + { + return SelectedLines.Count > 0; + } + } + + public virtual string GetResultText() + { + return m_RenderedName; + } + } +} diff --git a/Assets/UnityTestTools/UnitTesting/Editor/NUnit/Renderer/UnitTestRendererLine.cs.meta b/Assets/UnityTestTools/UnitTesting/Editor/NUnit/Renderer/UnitTestRendererLine.cs.meta new file mode 100644 index 00000000..6c73d201 --- /dev/null +++ b/Assets/UnityTestTools/UnitTesting/Editor/NUnit/Renderer/UnitTestRendererLine.cs.meta @@ -0,0 +1,8 @@ +fileFormatVersion: 2 +guid: fddb568bfa3ed03438d5c482ea8c6aea +MonoImporter: + serializedVersion: 2 + defaultReferences: [] + executionOrder: 0 + icon: {instanceID: 0} + userData: diff --git a/Assets/UnityTestTools/UnitTesting/Editor/TestRunner.meta b/Assets/UnityTestTools/UnitTesting/Editor/TestRunner.meta new file mode 100644 index 00000000..a253f990 --- /dev/null +++ b/Assets/UnityTestTools/UnitTesting/Editor/TestRunner.meta @@ -0,0 +1,5 @@ +fileFormatVersion: 2 +guid: 615921b0760fc0c4eaf10b7c88add37b +folderAsset: yes +DefaultImporter: + userData: diff --git a/Assets/UnityTestTools/UnitTesting/Editor/TestRunner/GuiHelper.cs b/Assets/UnityTestTools/UnitTesting/Editor/TestRunner/GuiHelper.cs new file mode 100644 index 00000000..f9c25310 --- /dev/null +++ b/Assets/UnityTestTools/UnitTesting/Editor/TestRunner/GuiHelper.cs @@ -0,0 +1,147 @@ +using System; +using System.Collections.Generic; +using System.Linq; +using System.Reflection; +using System.Text.RegularExpressions; +using Mono.Cecil; +using Mono.Cecil.Cil; +using Mono.Cecil.Mdb; +using Mono.Collections.Generic; +using UnityEditor; +using UnityEditorInternal; +using UnityEngine; + +namespace UnityTest +{ + public static class GuiHelper + { + public static Texture GetIconForResult(TestResultState resultState) + { + switch (resultState) + { + case TestResultState.Success: + return Icons.SuccessImg; + case TestResultState.Failure: + case TestResultState.Error: + return Icons.FailImg; + case TestResultState.Ignored: + case TestResultState.Skipped: + return Icons.IgnoreImg; + case TestResultState.Inconclusive: + case TestResultState.Cancelled: + case TestResultState.NotRunnable: + return Icons.InconclusiveImg; + default: + return Icons.UnknownImg; + } + } + + private static int ExtractSourceFileLine(string stackTrace) + { + int line = 0; + if (!string.IsNullOrEmpty(stackTrace)) + { + var regEx = new Regex(@".* in (?'path'.*):(?'line'\d+)"); + var matches = regEx.Matches(stackTrace); + for (int i = 0; i < matches.Count; i++) + { + line = int.Parse(matches[i].Groups["line"].Value); + if (line != 0) + break; + } + } + return line; + } + + private static string ExtractSourceFilePath(string stackTrace) + { + string path = ""; + if (!string.IsNullOrEmpty(stackTrace)) + { + var regEx = new Regex(@".* in (?'path'.*):(?'line'\d+)"); + var matches = regEx.Matches(stackTrace); + for (int i = 0; i < matches.Count; i++) + { + path = matches[i].Groups["path"].Value; + if (path != "") + break; + } + } + return path; + } + + public static void OpenInEditor(UnitTestResult test, bool openError) + { + var sourceFilePath = ExtractSourceFilePath(test.StackTrace); + var sourceFileLine = ExtractSourceFileLine(test.StackTrace); + + if (!openError || sourceFileLine == 0 || string.IsNullOrEmpty(sourceFilePath)) + { + var sp = GetSequencePointOfTest(test); + if (sp != null) + { + sourceFileLine = sp.StartLine; + sourceFilePath = sp.Document.Url; + } + } + + OpenInEditorInternal(sourceFilePath, sourceFileLine); + } + + private static SequencePoint GetSequencePointOfTest(UnitTestResult test) + { + var readerParameters = new ReaderParameters + { + ReadSymbols = true, + SymbolReaderProvider = new MdbReaderProvider(), + ReadingMode = ReadingMode.Immediate + }; + + var assemblyDefinition = AssemblyDefinition.ReadAssembly(test.Test.AssemblyPath, readerParameters); + var classModule = assemblyDefinition.MainModule.Types.Single(t => t.FullName == test.Test.FullClassName); + + Collection methods; + MethodDefinition method = null; + while (classModule.BaseType != null) + { + methods = classModule.Methods; + if (methods.Any(t => t.Name == test.Test.MethodName)) + { + method = classModule.Methods.First(t => t.Name == test.Test.MethodName); + break; + } + classModule = classModule.BaseType as TypeDefinition; + } + if (method != null) + { + var sp = method.Body.Instructions.First(i => i.SequencePoint != null).SequencePoint; + return sp; + } + return null; + } + + private static void OpenInEditorInternal(string filename, int line) + { + string assetPath = filename.Substring(Application.dataPath.Length - "Assets/".Length + 1); + var scriptAsset = AssetDatabase.LoadMainAssetAtPath(assetPath); + AssetDatabase.OpenAsset(scriptAsset, line); + } + + public static bool GetConsoleErrorPause() + { + Assembly assembly = Assembly.GetAssembly(typeof(SceneView)); + Type type = assembly.GetType("UnityEditorInternal.LogEntries"); + PropertyInfo method = type.GetProperty("consoleFlags"); + var result = (int)method.GetValue(new object(), new object[] { }); + return (result & (1 << 2)) != 0; + } + + public static void SetConsoleErrorPause(bool b) + { + Assembly assembly = Assembly.GetAssembly(typeof(SceneView)); + Type type = assembly.GetType("UnityEditorInternal.LogEntries"); + MethodInfo method = type.GetMethod("SetConsoleFlag"); + method.Invoke(new object(), new object[] { 1 << 2, b }); + } + } +} diff --git a/Assets/UnityTestTools/UnitTesting/Editor/TestRunner/GuiHelper.cs.meta b/Assets/UnityTestTools/UnitTesting/Editor/TestRunner/GuiHelper.cs.meta new file mode 100644 index 00000000..596d39f3 --- /dev/null +++ b/Assets/UnityTestTools/UnitTesting/Editor/TestRunner/GuiHelper.cs.meta @@ -0,0 +1,8 @@ +fileFormatVersion: 2 +guid: b0b95014154ef554485afc9c0316556d +MonoImporter: + serializedVersion: 2 + defaultReferences: [] + executionOrder: 0 + icon: {instanceID: 0} + userData: diff --git a/Assets/UnityTestTools/UnitTesting/Editor/TestRunner/ITestRunnerCallback.cs b/Assets/UnityTestTools/UnitTesting/Editor/TestRunner/ITestRunnerCallback.cs new file mode 100644 index 00000000..9f7b0cc8 --- /dev/null +++ b/Assets/UnityTestTools/UnitTesting/Editor/TestRunner/ITestRunnerCallback.cs @@ -0,0 +1,15 @@ +using System; +using System.Collections.Generic; +using UnityEngine; + +namespace UnityTest.UnitTestRunner +{ + public interface ITestRunnerCallback + { + void TestStarted(string fullName); + void TestFinished(ITestResult fullName); + void RunStarted(string suiteName, int testCount); + void RunFinished(); + void RunFinishedException(Exception exception); + } +} diff --git a/Assets/UnityTestTools/UnitTesting/Editor/TestRunner/ITestRunnerCallback.cs.meta b/Assets/UnityTestTools/UnitTesting/Editor/TestRunner/ITestRunnerCallback.cs.meta new file mode 100644 index 00000000..9aea576b --- /dev/null +++ b/Assets/UnityTestTools/UnitTesting/Editor/TestRunner/ITestRunnerCallback.cs.meta @@ -0,0 +1,8 @@ +fileFormatVersion: 2 +guid: 45a983e950f22034ba987c6db2a8b216 +MonoImporter: + serializedVersion: 2 + defaultReferences: [] + executionOrder: 0 + icon: {instanceID: 0} + userData: diff --git a/Assets/UnityTestTools/UnitTesting/Editor/TestRunner/IUnitTestEngine.cs b/Assets/UnityTestTools/UnitTesting/Editor/TestRunner/IUnitTestEngine.cs new file mode 100644 index 00000000..0452ba18 --- /dev/null +++ b/Assets/UnityTestTools/UnitTesting/Editor/TestRunner/IUnitTestEngine.cs @@ -0,0 +1,13 @@ +using System; +using System.Collections.Generic; +using UnityEngine; +using UnityTest.UnitTestRunner; + +namespace UnityTest +{ + public interface IUnitTestEngine + { + UnitTestRendererLine GetTests(out UnitTestResult[] results, out string[] categories); + void RunTests(TestFilter filter, ITestRunnerCallback testRunnerEventListener); + } +} diff --git a/Assets/UnityTestTools/UnitTesting/Editor/TestRunner/IUnitTestEngine.cs.meta b/Assets/UnityTestTools/UnitTesting/Editor/TestRunner/IUnitTestEngine.cs.meta new file mode 100644 index 00000000..9eba5da9 --- /dev/null +++ b/Assets/UnityTestTools/UnitTesting/Editor/TestRunner/IUnitTestEngine.cs.meta @@ -0,0 +1,8 @@ +fileFormatVersion: 2 +guid: 96615b7fd2cb32b4dbea04d84cc3f7fb +MonoImporter: + serializedVersion: 2 + defaultReferences: [] + executionOrder: 0 + icon: {instanceID: 0} + userData: diff --git a/Assets/UnityTestTools/UnitTesting/Editor/TestRunner/NUnitExtensions.cs b/Assets/UnityTestTools/UnitTesting/Editor/TestRunner/NUnitExtensions.cs new file mode 100644 index 00000000..abf963b9 --- /dev/null +++ b/Assets/UnityTestTools/UnitTesting/Editor/TestRunner/NUnitExtensions.cs @@ -0,0 +1,24 @@ +using System; +using System.Collections.Generic; +using UnityEngine; + +namespace UnityTest +{ + public static class NUnitExtensions + { + public static UnitTestResult UnitTestResult(this NUnit.Core.TestResult result, string logs) + { + return new UnitTestResult + { + Executed = result.Executed, + ResultState = (TestResultState)result.ResultState, + Message = result.Message, + Logs = logs, + StackTrace = result.StackTrace, + Duration = result.Time, + Test = new UnitTestInfo(result.Test.TestName.TestID.ToString()), + IsIgnored = (result.ResultState == NUnit.Core.ResultState.Ignored) || result.Test.RunState == NUnit.Core.RunState.Ignored + }; + } + } +} diff --git a/Assets/UnityTestTools/UnitTesting/Editor/TestRunner/NUnitExtensions.cs.meta b/Assets/UnityTestTools/UnitTesting/Editor/TestRunner/NUnitExtensions.cs.meta new file mode 100644 index 00000000..00de7908 --- /dev/null +++ b/Assets/UnityTestTools/UnitTesting/Editor/TestRunner/NUnitExtensions.cs.meta @@ -0,0 +1,8 @@ +fileFormatVersion: 2 +guid: 7df86c5f85b0f7d4096d6bc23e9a4e01 +MonoImporter: + serializedVersion: 2 + defaultReferences: [] + executionOrder: 0 + icon: {instanceID: 0} + userData: diff --git a/Assets/UnityTestTools/UnitTesting/Editor/TestRunner/NUnitTestEngine.cs b/Assets/UnityTestTools/UnitTesting/Editor/TestRunner/NUnitTestEngine.cs new file mode 100644 index 00000000..2ed658e5 --- /dev/null +++ b/Assets/UnityTestTools/UnitTesting/Editor/TestRunner/NUnitTestEngine.cs @@ -0,0 +1,211 @@ +using System; +using System.Collections.Generic; +using System.Linq; +using System.Reflection; +using System.Text; +using NUnit.Core; +using NUnit.Core.Filters; +using UnityEditor; +using UnityEngine; +using UnityTest.UnitTestRunner; + +namespace UnityTest +{ + public class NUnitTestEngine : IUnitTestEngine + { + static readonly string[] k_WhitelistedAssemblies = + { + "Assembly-CSharp-Editor", + "Assembly-Boo-Editor", + "Assembly-UnityScript-Editor" + }; + private TestSuite m_TestSuite; + + public UnitTestRendererLine GetTests(out UnitTestResult[] results, out string[] categories) + { + if (m_TestSuite == null) + { + var assemblies = GetAssembliesWithTests().Select(a => a.Location).ToList(); + TestSuite suite = PrepareTestSuite(assemblies); + m_TestSuite = suite; + } + + var resultList = new List(); + var categoryList = new HashSet(); + + UnitTestRendererLine lines = null; + if (m_TestSuite != null) + lines = ParseTestList(m_TestSuite, resultList, categoryList).Single(); + results = resultList.ToArray(); + categories = categoryList.ToArray(); + + return lines; + } + + private UnitTestRendererLine[] ParseTestList(Test test, List results, HashSet categories) + { + foreach (string category in test.Categories) + categories.Add(category); + + if (test is TestMethod) + { + var result = new UnitTestResult + { + Test = new UnitTestInfo(test as TestMethod) + }; + + results.Add(result); + return new[] { new TestLine(test as TestMethod, result.Id) }; + } + + GroupLine group = null; + if (test is TestSuite) + group = new GroupLine(test as TestSuite); + + var namespaceList = new List(new[] {group}); + + foreach (Test result in test.Tests) + { + if (result is NamespaceSuite || test is TestAssembly) + namespaceList.AddRange(ParseTestList(result, results, categories)); + else + group.AddChildren(ParseTestList(result, results, categories)); + } + + namespaceList.Sort(); + return namespaceList.ToArray(); + } + + public void RunTests(ITestRunnerCallback testRunnerEventListener) + { + RunTests(TestFilter.Empty, testRunnerEventListener); + } + + public void RunTests(TestFilter filter, ITestRunnerCallback testRunnerEventListener) + { + try + { + if (testRunnerEventListener != null) + testRunnerEventListener.RunStarted(m_TestSuite.TestName.FullName, m_TestSuite.TestCount); + + ExecuteTestSuite(m_TestSuite, testRunnerEventListener, filter); + + if (testRunnerEventListener != null) + testRunnerEventListener.RunFinished(); + } + catch (Exception e) + { + Debug.LogException(e); + if (testRunnerEventListener != null) + testRunnerEventListener.RunFinishedException(e); + } + } + + public static Assembly[] GetAssembliesWithTests() + { + var libs = new List(); + foreach (var assembly in AppDomain.CurrentDomain.GetAssemblies()) + { + if (assembly.GetReferencedAssemblies().All(a => a.Name != "nunit.framework")) + continue; + if (assembly.Location.Replace('\\', '/').StartsWith(Application.dataPath) + || k_WhitelistedAssemblies.Contains(assembly.GetName().Name)) + libs.Add(assembly); + } + return libs.ToArray(); + } + + private TestSuite PrepareTestSuite(List assemblyList) + { + CoreExtensions.Host.InitializeService(); + var testPackage = new TestPackage(PlayerSettings.productName, assemblyList); + var builder = new TestSuiteBuilder(); + TestExecutionContext.CurrentContext.TestPackage = testPackage; + TestSuite suite = builder.Build(testPackage); + return suite; + } + + private void ExecuteTestSuite(TestSuite suite, ITestRunnerCallback testRunnerEventListener, TestFilter filter) + { + EventListener eventListener; + if (testRunnerEventListener == null) + eventListener = new NullListener(); + else + eventListener = new TestRunnerEventListener(testRunnerEventListener); + + TestExecutionContext.CurrentContext.Out = new EventListenerTextWriter(eventListener, TestOutputType.Out); + TestExecutionContext.CurrentContext.Error = new EventListenerTextWriter(eventListener, TestOutputType.Error); + + suite.Run(eventListener, GetFilter(filter)); + } + + private ITestFilter GetFilter(TestFilter filter) + { + var nUnitFilter = new AndFilter(); + + if (filter.names != null && filter.names.Length > 0) + nUnitFilter.Add(new SimpleNameFilter(filter.names)); + if (filter.categories != null && filter.categories.Length > 0) + nUnitFilter.Add(new CategoryFilter(filter.categories)); + if (filter.objects != null && filter.objects.Length > 0) + nUnitFilter.Add(new OrFilter(filter.objects.Where(o => o is TestName).Select(o => new NameFilter(o as TestName)).ToArray())); + return nUnitFilter; + } + + public class TestRunnerEventListener : EventListener + { + private readonly ITestRunnerCallback m_TestRunnerEventListener; + private StringBuilder m_testLog; + + public TestRunnerEventListener(ITestRunnerCallback testRunnerEventListener) + { + m_TestRunnerEventListener = testRunnerEventListener; + } + + public void RunStarted(string name, int testCount) + { + m_TestRunnerEventListener.RunStarted(name, testCount); + } + + public void RunFinished(NUnit.Core.TestResult result) + { + m_TestRunnerEventListener.RunFinished(); + } + + public void RunFinished(Exception exception) + { + m_TestRunnerEventListener.RunFinishedException(exception); + } + + public void TestStarted(TestName testName) + { + m_testLog = new StringBuilder(); + m_TestRunnerEventListener.TestStarted(testName.FullName); + } + + public void TestFinished(NUnit.Core.TestResult result) + { + m_TestRunnerEventListener.TestFinished(result.UnitTestResult(m_testLog.ToString())); + m_testLog = null; + } + + public void SuiteStarted(TestName testName) + { + } + + public void SuiteFinished(NUnit.Core.TestResult result) + { + } + + public void UnhandledException(Exception exception) + { + } + + public void TestOutput(TestOutput testOutput) + { + if (m_testLog != null) + m_testLog.AppendLine(testOutput.Text); + } + } + } +} diff --git a/Assets/UnityTestTools/UnitTesting/Editor/TestRunner/NUnitTestEngine.cs.meta b/Assets/UnityTestTools/UnitTesting/Editor/TestRunner/NUnitTestEngine.cs.meta new file mode 100644 index 00000000..241b6f91 --- /dev/null +++ b/Assets/UnityTestTools/UnitTesting/Editor/TestRunner/NUnitTestEngine.cs.meta @@ -0,0 +1,8 @@ +fileFormatVersion: 2 +guid: f313d48559bf30145b88ef7f173685c9 +MonoImporter: + serializedVersion: 2 + defaultReferences: [] + executionOrder: 0 + icon: {instanceID: 0} + userData: diff --git a/Assets/UnityTestTools/UnitTesting/Editor/TestRunner/TestRunner.cs b/Assets/UnityTestTools/UnitTesting/Editor/TestRunner/TestRunner.cs new file mode 100644 index 00000000..c80c4995 --- /dev/null +++ b/Assets/UnityTestTools/UnitTesting/Editor/TestRunner/TestRunner.cs @@ -0,0 +1,150 @@ +using System; +using System.Collections.Generic; +using System.Linq; +using UnityEditor; +using UnityEngine; +using UnityTest.UnitTestRunner; + +namespace UnityTest +{ + public partial class UnitTestView + { + private void UpdateTestInfo(ITestResult result) + { + FindTestResult(result.Id).Update(result, false); + m_FilterSettings.UpdateCounters(m_ResultList.Cast()); + } + + private UnitTestResult FindTestResult(string resultId) + { + var idx = m_ResultList.FindIndex(testResult => testResult.Id == resultId); + if (idx == -1) + { + Debug.LogWarning("Id not found for test: " + resultId); + return null; + } + return m_ResultList.ElementAt(idx); + } + + private void RunTests() + { + var filter = new TestFilter(); + var categories = m_FilterSettings.GetSelectedCategories(); + if (categories != null && categories.Length > 0) + filter.categories = categories; + RunTests(filter); + } + + private void RunTests(TestFilter filter) + { + if (m_Settings.runTestOnANewScene) + { + if (m_Settings.autoSaveSceneBeforeRun) EditorApplication.SaveScene(); + if (!EditorApplication.SaveCurrentSceneIfUserWantsTo()) return; + } + + string currentScene = null; + int undoGroup = -1; + if (m_Settings.runTestOnANewScene) + currentScene = OpenNewScene(); + else + undoGroup = RegisterUndo(); + + StartTestRun(filter, new TestRunnerEventListener(UpdateTestInfo)); + + if (m_Settings.runTestOnANewScene) + LoadPreviousScene(currentScene); + else + PerformUndo(undoGroup); + } + + private string OpenNewScene() + { + var currentScene = EditorApplication.currentScene; + if (m_Settings.runTestOnANewScene) + EditorApplication.NewScene(); + return currentScene; + } + + private void LoadPreviousScene(string currentScene) + { + if (!string.IsNullOrEmpty(currentScene)) + EditorApplication.OpenScene(currentScene); + else + EditorApplication.NewScene(); + + if (Event.current != null) + GUIUtility.ExitGUI(); + } + + public void StartTestRun(TestFilter filter, ITestRunnerCallback eventListener) + { + var callbackList = new TestRunnerCallbackList(); + if (eventListener != null) callbackList.Add(eventListener); + k_TestEngine.RunTests(filter, callbackList); + } + + private static int RegisterUndo() + { + return Undo.GetCurrentGroup(); + } + + private static void PerformUndo(int undoGroup) + { + EditorUtility.DisplayProgressBar("Undo", "Reverting changes to the scene", 0); + var undoStartTime = DateTime.Now; + Undo.RevertAllDownToGroup(undoGroup); + if ((DateTime.Now - undoStartTime).Seconds > 1) + Debug.LogWarning("Undo after unit test run took " + (DateTime.Now - undoStartTime).Seconds + " seconds. Consider running unit tests on a new scene for better performance."); + EditorUtility.ClearProgressBar(); + } + + public class TestRunnerEventListener : ITestRunnerCallback + { + private readonly Action m_UpdateCallback; + + public TestRunnerEventListener(Action updateCallback) + { + m_UpdateCallback = updateCallback; + } + + public void TestStarted(string fullName) + { + EditorUtility.DisplayProgressBar("Unit Tests Runner", fullName, 1); + } + + public void TestFinished(ITestResult result) + { + m_UpdateCallback(result); + } + + public void RunStarted(string suiteName, int testCount) + { + } + + public void RunFinished() + { + EditorUtility.ClearProgressBar(); + } + + public void RunFinishedException(Exception exception) + { + RunFinished(); + } + } + + [MenuItem("Unity Test Tools/Unit Test Runner %#&u")] + public static void ShowWindow() + { + GetWindow(typeof(UnitTestView)).Show(); + } + } + + public class TestFilter + { + public string[] names; + public string[] categories; + public object[] objects; + public static TestFilter Empty = new TestFilter(); + } +} diff --git a/Assets/UnityTestTools/UnitTesting/Editor/TestRunner/TestRunner.cs.meta b/Assets/UnityTestTools/UnitTesting/Editor/TestRunner/TestRunner.cs.meta new file mode 100644 index 00000000..19127aa4 --- /dev/null +++ b/Assets/UnityTestTools/UnitTesting/Editor/TestRunner/TestRunner.cs.meta @@ -0,0 +1,8 @@ +fileFormatVersion: 2 +guid: fbf567afda42eec43a7dbb052d318076 +MonoImporter: + serializedVersion: 2 + defaultReferences: [] + executionOrder: 0 + icon: {instanceID: 0} + userData: diff --git a/Assets/UnityTestTools/UnitTesting/Editor/TestRunner/TestRunnerCallbackList.cs b/Assets/UnityTestTools/UnitTesting/Editor/TestRunner/TestRunnerCallbackList.cs new file mode 100644 index 00000000..dbe8ad04 --- /dev/null +++ b/Assets/UnityTestTools/UnitTesting/Editor/TestRunner/TestRunnerCallbackList.cs @@ -0,0 +1,62 @@ +using System; +using System.Collections.Generic; +using UnityEngine; + +namespace UnityTest.UnitTestRunner +{ + public class TestRunnerCallbackList : ITestRunnerCallback + { + private readonly List m_CallbackList = new List(); + + public void TestStarted(string fullName) + { + foreach (var unitTestRunnerCallback in m_CallbackList) + { + unitTestRunnerCallback.TestStarted(fullName); + } + } + + public void TestFinished(ITestResult fullName) + { + foreach (var unitTestRunnerCallback in m_CallbackList) + { + unitTestRunnerCallback.TestFinished(fullName); + } + } + + public void RunStarted(string suiteName, int testCount) + { + foreach (var unitTestRunnerCallback in m_CallbackList) + { + unitTestRunnerCallback.RunStarted(suiteName, + testCount); + } + } + + public void RunFinished() + { + foreach (var unitTestRunnerCallback in m_CallbackList) + { + unitTestRunnerCallback.RunFinished(); + } + } + + public void RunFinishedException(Exception exception) + { + foreach (var unitTestRunnerCallback in m_CallbackList) + { + unitTestRunnerCallback.RunFinishedException(exception); + } + } + + public void Add(ITestRunnerCallback callback) + { + m_CallbackList.Add(callback); + } + + public void Remove(ITestRunnerCallback callback) + { + m_CallbackList.Remove(callback); + } + } +} diff --git a/Assets/UnityTestTools/UnitTesting/Editor/TestRunner/TestRunnerCallbackList.cs.meta b/Assets/UnityTestTools/UnitTesting/Editor/TestRunner/TestRunnerCallbackList.cs.meta new file mode 100644 index 00000000..83288fbc --- /dev/null +++ b/Assets/UnityTestTools/UnitTesting/Editor/TestRunner/TestRunnerCallbackList.cs.meta @@ -0,0 +1,8 @@ +fileFormatVersion: 2 +guid: b7a6cf1b9d1273d4187ba9d5bc91fc30 +MonoImporter: + serializedVersion: 2 + defaultReferences: [] + executionOrder: 0 + icon: {instanceID: 0} + userData: diff --git a/Assets/UnityTestTools/UnitTesting/Editor/TestRunner/UnitTestInfo.cs b/Assets/UnityTestTools/UnitTesting/Editor/TestRunner/UnitTestInfo.cs new file mode 100644 index 00000000..5dad7e4b --- /dev/null +++ b/Assets/UnityTestTools/UnitTesting/Editor/TestRunner/UnitTestInfo.cs @@ -0,0 +1,104 @@ +using System; +using System.Collections.Generic; +using System.IO; +using System.Linq; +using System.Text.RegularExpressions; +using NUnit.Core; +using UnityEngine; +using Object = System.Object; + +namespace UnityTest +{ + [Serializable] + public class UnitTestInfo + { + public string ParamName { get; private set; } + public string MethodName { get; private set; } + public string FullMethodName { get; private set; } + public string ClassName { get; private set; } + public string FullClassName { get; private set; } + public string Namespace { get; private set; } + public string FullName { get; private set; } + public string[] Categories { get; private set; } + public string AssemblyPath { get; private set; } + public string Id { get; private set; } + public bool IsIgnored { get; private set; } + + public UnitTestInfo(TestMethod testMethod) + { + if (testMethod == null) + throw new ArgumentException(); + + MethodName = testMethod.MethodName; + FullMethodName = testMethod.Method.ToString(); + ClassName = testMethod.FixtureType.Name; + FullClassName = testMethod.ClassName; + Namespace = testMethod.Method.ReflectedType.Namespace; + FullName = testMethod.TestName.FullName; + ParamName = ExtractMethodCallParametersString(FullName); + Id = testMethod.TestName.TestID.ToString(); + + Categories = testMethod.Categories.Cast().ToArray(); + + AssemblyPath = GetAssemblyPath(testMethod); + + IsIgnored = (testMethod.RunState == RunState.Ignored); + } + + private string GetAssemblyPath(TestMethod testMethod) + { + var parent = testMethod as Test; + var assemblyPath = ""; + while (parent != null) + { + parent = parent.Parent; + if (!(parent is TestAssembly)) continue; + var path = (parent as TestAssembly).TestName.FullName; + if (!File.Exists(path)) continue; + assemblyPath = path; + break; + } + return assemblyPath; + } + + public UnitTestInfo(string id) + { + Id = id; + } + + public override bool Equals(Object obj) + { + if (!(obj is UnitTestInfo)) return false; + + var testInfo = (UnitTestInfo)obj; + return Id == testInfo.Id; + } + + public static bool operator ==(UnitTestInfo a, UnitTestInfo b) + { + if (((object)a == null) || ((object)b == null)) return false; + return a.Id == b.Id; + } + + public static bool operator !=(UnitTestInfo a, UnitTestInfo b) + { + return !(a == b); + } + + public override int GetHashCode() + { + return Id.GetHashCode(); + } + + static string ExtractMethodCallParametersString(string methodFullName) + { + var match = Regex.Match(methodFullName, @"\((.*)\)"); + string result = ""; + if (match.Groups[1].Success) + { + result = match.Groups[1].Captures[0].Value; + } + return result; + } + } +} diff --git a/Assets/UnityTestTools/UnitTesting/Editor/TestRunner/UnitTestInfo.cs.meta b/Assets/UnityTestTools/UnitTesting/Editor/TestRunner/UnitTestInfo.cs.meta new file mode 100644 index 00000000..9198408d --- /dev/null +++ b/Assets/UnityTestTools/UnitTesting/Editor/TestRunner/UnitTestInfo.cs.meta @@ -0,0 +1,8 @@ +fileFormatVersion: 2 +guid: 39d532431356ff74cb5a51afef8cc308 +MonoImporter: + serializedVersion: 2 + defaultReferences: [] + executionOrder: 0 + icon: {instanceID: 0} + userData: diff --git a/Assets/UnityTestTools/UnitTesting/Editor/TestRunner/UnitTestResult.cs b/Assets/UnityTestTools/UnitTesting/Editor/TestRunner/UnitTestResult.cs new file mode 100644 index 00000000..83b5123b --- /dev/null +++ b/Assets/UnityTestTools/UnitTesting/Editor/TestRunner/UnitTestResult.cs @@ -0,0 +1,61 @@ +using System; +using System.Collections.Generic; +using UnityEngine; + +namespace UnityTest +{ + [Serializable] + public class UnitTestResult : ITestResult + { + public bool Executed { get; set; } + public string Name { get { return Test.MethodName; } } + public string FullName { get { return Test.FullName; } } + public TestResultState ResultState { get; set; } + public UnitTestInfo Test { get; set; } + public string Id { get { return Test.Id; } } + public double Duration { get; set; } + public string Message { get; set; } + public string StackTrace { get; set; } + public bool IsIgnored { get; set; } + + public string Logs { get; set; } + + public bool Outdated { get; set; } + + public void Update(ITestResult source, bool outdated) + { + ResultState = source.ResultState; + Duration = source.Duration; + Message = source.Message; + Logs = source.Logs; + StackTrace = source.StackTrace; + Executed = source.Executed; + IsIgnored = source.IsIgnored || (Test != null && Test.IsIgnored); + Outdated = outdated; + } + + #region Helper methods + + public bool IsFailure + { + get { return ResultState == TestResultState.Failure; } + } + + public bool IsError + { + get { return ResultState == TestResultState.Error; } + } + + public bool IsSuccess + { + get { return ResultState == TestResultState.Success; } + } + + public bool IsInconclusive + { + get { return ResultState == TestResultState.Inconclusive; } + } + + #endregion + } +} diff --git a/Assets/UnityTestTools/UnitTesting/Editor/TestRunner/UnitTestResult.cs.meta b/Assets/UnityTestTools/UnitTesting/Editor/TestRunner/UnitTestResult.cs.meta new file mode 100644 index 00000000..03d07e0c --- /dev/null +++ b/Assets/UnityTestTools/UnitTesting/Editor/TestRunner/UnitTestResult.cs.meta @@ -0,0 +1,8 @@ +fileFormatVersion: 2 +guid: 925cf9f45ea32814da65f61c1ebd7e6f +MonoImporter: + serializedVersion: 2 + defaultReferences: [] + executionOrder: 0 + icon: {instanceID: 0} + userData: diff --git a/Assets/UnityTestTools/UnitTesting/Editor/TestRunner/UnitTestView.cs b/Assets/UnityTestTools/UnitTesting/Editor/TestRunner/UnitTestView.cs new file mode 100644 index 00000000..62e8c3b5 --- /dev/null +++ b/Assets/UnityTestTools/UnitTesting/Editor/TestRunner/UnitTestView.cs @@ -0,0 +1,227 @@ +using System; +using System.Collections.Generic; +using System.Linq; +using UnityEditor; +using UnityEngine; +using UnityEditor.Callbacks; + +namespace UnityTest +{ + [Serializable] + public partial class UnitTestView : EditorWindow, IHasCustomMenu + { + private static UnitTestView s_Instance; + private static readonly IUnitTestEngine k_TestEngine = new NUnitTestEngine(); + + [SerializeField] private List m_ResultList = new List(); + [SerializeField] private List m_FoldMarkers = new List(); + [SerializeField] private List m_SelectedLines = new List(); + UnitTestRendererLine m_TestLines; + + private TestFilterSettings m_FilterSettings; + + #region runner steering vars + private Vector2 m_TestListScroll, m_TestInfoScroll; + private float m_HorizontalSplitBarPosition = 200; + private float m_VerticalSplitBarPosition = 300; + #endregion + + private UnitTestsRunnerSettings m_Settings; + + #region GUI Contents + private readonly GUIContent m_GUIRunSelectedTestsIcon = new GUIContent("Run Selected", "Run selected tests"); + private readonly GUIContent m_GUIRunAllTestsIcon = new GUIContent("Run All", "Run all tests"); + private readonly GUIContent m_GUIRerunFailedTestsIcon = new GUIContent("Rerun Failed", "Rerun failed tests"); + private readonly GUIContent m_GUIRunOnRecompile = new GUIContent("Run on recompile", "Run all tests after recompilation"); + private readonly GUIContent m_GUIShowDetailsBelowTests = new GUIContent("Show details below tests", "Show run details below test list"); + private readonly GUIContent m_GUIRunTestsOnNewScene = new GUIContent("Run tests on a new scene", "Run tests on a new scene"); + private readonly GUIContent m_GUIAutoSaveSceneBeforeRun = new GUIContent("Autosave scene", "The runner will automatically save the current scene changes before it starts"); + #endregion + + public UnitTestView() + { + m_ResultList.Clear(); + } + + public void OnEnable() + { + titleContent = new GUIContent("Unit Tests"); + s_Instance = this; + m_Settings = ProjectSettingsBase.Load(); + m_FilterSettings = new TestFilterSettings("UnityTest.UnitTestView"); + RefreshTests(); + } + + [DidReloadScripts] + public static void OnDidReloadScripts() + { + if (s_Instance != null && s_Instance.m_Settings.runOnRecompilation) + { + s_Instance.RunTests(); + s_Instance.Repaint(); + } + } + + public void OnDestroy() + { + s_Instance = null; + } + + public void OnGUI() + { + EditorGUILayout.BeginVertical(); + + EditorGUILayout.BeginHorizontal(EditorStyles.toolbar); + + if (GUILayout.Button(m_GUIRunAllTestsIcon, EditorStyles.toolbarButton)) + { + RunTests(); + GUIUtility.ExitGUI(); + } + EditorGUI.BeginDisabledGroup(!m_TestLines.IsAnySelected); + if (GUILayout.Button(m_GUIRunSelectedTestsIcon, EditorStyles.toolbarButton)) + { + m_TestLines.RunSelectedTests(); + } + EditorGUI.EndDisabledGroup(); + if (GUILayout.Button(m_GUIRerunFailedTestsIcon, EditorStyles.toolbarButton)) + { + m_TestLines.RunTests(m_ResultList.Where(result => result.IsFailure || result.IsError).Select(l => l.FullName).ToArray()); + } + + GUILayout.FlexibleSpace(); + + m_FilterSettings.OnGUI (); + + EditorGUILayout.EndHorizontal(); + + if (m_Settings.horizontalSplit) + EditorGUILayout.BeginVertical(); + else + EditorGUILayout.BeginHorizontal(GUILayout.ExpandWidth(true)); + + RenderTestList(); + RenderTestInfo(); + + if (m_Settings.horizontalSplit) + EditorGUILayout.EndVertical(); + else + EditorGUILayout.EndHorizontal(); + + EditorGUILayout.EndVertical(); + } + + private void RenderTestList() + { + EditorGUILayout.BeginVertical(Styles.testList); + m_TestListScroll = EditorGUILayout.BeginScrollView(m_TestListScroll, + GUILayout.ExpandWidth(true), + GUILayout.MaxWidth(2000)); + if (m_TestLines != null) + { + if (m_TestLines.Render(m_FilterSettings.BuildRenderingOptions())) Repaint(); + } + EditorGUILayout.EndScrollView(); + EditorGUILayout.EndVertical(); + } + + private void RenderTestInfo() + { + var ctrlId = GUIUtility.GetControlID(FocusType.Passive); + var rect = GUILayoutUtility.GetLastRect(); + if (m_Settings.horizontalSplit) + { + rect.y = rect.height + rect.y - 1; + rect.height = 3; + } + else + { + rect.x = rect.width + rect.x - 1; + rect.width = 3; + } + + EditorGUIUtility.AddCursorRect(rect, m_Settings.horizontalSplit ? MouseCursor.ResizeVertical : MouseCursor.ResizeHorizontal); + var e = Event.current; + switch (e.type) + { + case EventType.MouseDown: + if (GUIUtility.hotControl == 0 && rect.Contains(e.mousePosition)) + GUIUtility.hotControl = ctrlId; + break; + case EventType.MouseDrag: + if (GUIUtility.hotControl == ctrlId) + { + m_HorizontalSplitBarPosition -= e.delta.y; + if (m_HorizontalSplitBarPosition < 20) m_HorizontalSplitBarPosition = 20; + m_VerticalSplitBarPosition -= e.delta.x; + if (m_VerticalSplitBarPosition < 20) m_VerticalSplitBarPosition = 20; + Repaint(); + } + + break; + case EventType.MouseUp: + if (GUIUtility.hotControl == ctrlId) + GUIUtility.hotControl = 0; + break; + } + m_TestInfoScroll = EditorGUILayout.BeginScrollView(m_TestInfoScroll, m_Settings.horizontalSplit + ? GUILayout.MinHeight(m_HorizontalSplitBarPosition) + : GUILayout.Width(m_VerticalSplitBarPosition)); + + var text = ""; + if (m_SelectedLines.Any()) + { + text = m_SelectedLines.First().GetResultText(); + } + + var resultTextSize = Styles.info.CalcSize(new GUIContent(text)); + EditorGUILayout.SelectableLabel(text, Styles.info, + GUILayout.ExpandHeight(true), + GUILayout.ExpandWidth(true), + GUILayout.MinWidth(resultTextSize.x), + GUILayout.MinHeight(resultTextSize.y)); + + EditorGUILayout.EndScrollView(); + } + + private void ToggleRunOnRecompilation() + { + m_Settings.runOnRecompilation = !m_Settings.runOnRecompilation; + } + + public void AddItemsToMenu (GenericMenu menu) + { + menu.AddItem(m_GUIRunOnRecompile, m_Settings.runOnRecompilation, ToggleRunOnRecompilation); + menu.AddItem(m_GUIRunTestsOnNewScene, m_Settings.runTestOnANewScene, m_Settings.ToggleRunTestOnANewScene); + if(!m_Settings.runTestOnANewScene) + menu.AddDisabledItem(m_GUIAutoSaveSceneBeforeRun); + else + menu.AddItem(m_GUIAutoSaveSceneBeforeRun, m_Settings.autoSaveSceneBeforeRun, m_Settings.ToggleAutoSaveSceneBeforeRun); + menu.AddItem(m_GUIShowDetailsBelowTests, m_Settings.horizontalSplit, m_Settings.ToggleHorizontalSplit); + } + + private void RefreshTests() + { + UnitTestResult[] newResults; + m_TestLines = k_TestEngine.GetTests(out newResults, out m_FilterSettings.AvailableCategories); + + foreach (var newResult in newResults) + { + var result = m_ResultList.Where(t => t.Test == newResult.Test && t.FullName == newResult.FullName).ToArray(); + if (result.Count() != 1) continue; + newResult.Update(result.Single(), true); + } + + UnitTestRendererLine.SelectedLines = m_SelectedLines; + UnitTestRendererLine.RunTest = RunTests; + GroupLine.FoldMarkers = m_FoldMarkers; + TestLine.GetUnitTestResult = FindTestResult; + + m_ResultList = new List(newResults); + + m_FilterSettings.UpdateCounters(m_ResultList.Cast()); + + Repaint(); + } + } +} diff --git a/Assets/UnityTestTools/UnitTesting/Editor/TestRunner/UnitTestView.cs.meta b/Assets/UnityTestTools/UnitTesting/Editor/TestRunner/UnitTestView.cs.meta new file mode 100644 index 00000000..55bd7001 --- /dev/null +++ b/Assets/UnityTestTools/UnitTesting/Editor/TestRunner/UnitTestView.cs.meta @@ -0,0 +1,8 @@ +fileFormatVersion: 2 +guid: ba152083ecc3cdb4a82881c6a9ae73c1 +MonoImporter: + serializedVersion: 2 + defaultReferences: [] + executionOrder: 0 + icon: {instanceID: 0} + userData: diff --git a/Assets/UnityTestTools/UnitTesting/Editor/UnitTestsRunnerSettings.cs b/Assets/UnityTestTools/UnitTesting/Editor/UnitTestsRunnerSettings.cs new file mode 100644 index 00000000..1feca66e --- /dev/null +++ b/Assets/UnityTestTools/UnitTesting/Editor/UnitTestsRunnerSettings.cs @@ -0,0 +1,30 @@ +using System; +using System.Collections.Generic; +using UnityEngine; + +namespace UnityTest +{ + + public class UnitTestsRunnerSettings : ProjectSettingsBase + { + public bool runOnRecompilation; + public bool horizontalSplit = true; + public bool autoSaveSceneBeforeRun; + public bool runTestOnANewScene; + + public void ToggleRunTestOnANewScene() { + runTestOnANewScene = !runTestOnANewScene; + Save (); + } + + public void ToggleAutoSaveSceneBeforeRun() { + autoSaveSceneBeforeRun = !autoSaveSceneBeforeRun; + Save (); + } + + public void ToggleHorizontalSplit() { + horizontalSplit = !horizontalSplit; + Save (); + } + } +} diff --git a/Assets/UnityTestTools/UnitTesting/Editor/UnitTestsRunnerSettings.cs.meta b/Assets/UnityTestTools/UnitTesting/Editor/UnitTestsRunnerSettings.cs.meta new file mode 100644 index 00000000..4e53a94d --- /dev/null +++ b/Assets/UnityTestTools/UnitTesting/Editor/UnitTestsRunnerSettings.cs.meta @@ -0,0 +1,8 @@ +fileFormatVersion: 2 +guid: 4a24a0b0a24461a4ab99853f8b145e5c +MonoImporter: + serializedVersion: 2 + defaultReferences: [] + executionOrder: 0 + icon: {instanceID: 0} + userData: diff --git a/Assets/UnityTestTools/UnitTesting/Editor/UnityUnitTest.cs b/Assets/UnityTestTools/UnitTesting/Editor/UnityUnitTest.cs new file mode 100644 index 00000000..32751ec0 --- /dev/null +++ b/Assets/UnityTestTools/UnitTesting/Editor/UnityUnitTest.cs @@ -0,0 +1,28 @@ +using System; +using System.Collections.Generic; +using NUnit.Framework; +using UnityEditor; +using UnityEngine; + +[TestFixture] +public abstract class UnityUnitTest +{ + public GameObject CreateGameObject() + { + return CreateGameObject(""); + } + + public GameObject CreateGameObject(string name) + { + var go = string.IsNullOrEmpty(name) ? new GameObject() : new GameObject(name); + Undo.RegisterCreatedObjectUndo(go, ""); + return go; + } + + public GameObject CreatePrimitive(PrimitiveType type) + { + var p = GameObject.CreatePrimitive(type); + Undo.RegisterCreatedObjectUndo(p, ""); + return p; + } +} diff --git a/Assets/UnityTestTools/UnitTesting/Editor/UnityUnitTest.cs.meta b/Assets/UnityTestTools/UnitTesting/Editor/UnityUnitTest.cs.meta new file mode 100644 index 00000000..0185d4b3 --- /dev/null +++ b/Assets/UnityTestTools/UnitTesting/Editor/UnityUnitTest.cs.meta @@ -0,0 +1,8 @@ +fileFormatVersion: 2 +guid: 3ec01611d948e574c99a1bd24650a4a9 +MonoImporter: + serializedVersion: 2 + defaultReferences: [] + executionOrder: 0 + icon: {instanceID: 0} + userData: diff --git a/Assets/UnityTestTools/changelog.txt b/Assets/UnityTestTools/changelog.txt new file mode 100644 index 00000000..6c25e1f2 --- /dev/null +++ b/Assets/UnityTestTools/changelog.txt @@ -0,0 +1,198 @@ +Version 1.5.4 + +- APIs updates + +Version 1.5.3 + +- Bug fixes + +Version 1.5.2 + +- Bug fixes +- Minor improvments + +Version 1.5.1 + +- removed redundant and not applicable options +- fixed 5.0 related bugs + +Version 1.5.0 + +- Unity 5 related compatibility changes + +Version 1.4.6 + +- Bug fixes +- Minor improvments + +Version 1.4.5 + +- Added "Pause on test failure" option for integration tests +- bugfixes and code refactorization +- fixed UI bug where test details were not refreshed is the label was focused + +Version 1.4.4 + +- Minimal supported Unity version is now 4.3 +- UI changes +- code refactoring + +Version 1.4.3 + +- Remove reference to Resources.LoadAssetAtPath from runtime code + +Version 1.4.2 + +(assertion component) +- fixed string comparer bug that prevented updating the value + +(unit tests) +- unit test runner will log to stdout now +- fixes issues with opening tests in IDEs + +(integration tests) +- transform component is now visible for integration tests components +- added better support for mac's keyboard +- fixed 'succeed on assertion' for code generated assertion + +(other) +- minor bugfixes +- general improvments + +Version 1.4.1 + +- Fixed platform compilation issues +- Fixed typos in labels +- Removed docs and left a link to online docs +- Added Unity version and target platform to result files +- Other bugfixes + +Version 1.4 + +(integration tests) +- Platform runner will send the results back to the editor via TCP now +- Added naming convention for running tests in batch mode +- It's possible to cancel the run in the editor in between the tests now +- Added message filtering for integration tests results +- Added check for RegisterLogCallback in case something else overrides it +- Error messages will now fail integration tests +- Fixed dynamic integration tests not being properly reset +- Fixed platform runner for BlackBerry platform + +(assertion component) +- fixed the component editor + +(common) +- Made settings to be saved per project +- Fixed resolving icons when there are more UnityTestTools folders in the project +- Fixed process return code when running in batch mode + +Version 1.3.2 + +- Fixed integration tests performance issues + +Version 1.3.1 + +- Updated Japanese docs + +Version 1.3 + +Fixes: +(unit tests) +- nUnit will no longer change the Environment.CurrentDirectory when running tests +- fixed issues with asserting GameObject == null +(integration tests) +- fix the issue with passing or failing test in first frame +- fixed bug where ignored tests were still run in ITF +(assertion component) +- fixed resolving properties to include derived types + +Improvements: +(unit tests) +- refactored result renderer +- reenabled Random attribute +- added Category filter +- NSubstitute updated to version 1.7.2 +- result now will be dimmed after recompilation +- running tests in background will now work without having the window focused +- all assemblies in the project referencing 'nunit.framework' will now be included in the test list +(integration tests) +- updated platform exclusion mechanism +- refactored result renderer +- the runner should work even if the runner window is not focused +- added possibility to create integration tests from code +- the runner will now always run in background (if the window is not focused) +(assertion component) +- added API for creating assertions from code +- added new example +(common) +- GUI improvements +- you no longer need to change the path to icons when moving the tools to another directory +- made test details/results resizeable and scrollable +- added character escape for generated result XML + +Version 1.2.1 +- Fixed Unit Test Batch runner + +Version 1.2 +Fixes: +- Windows Store related compilation issues +- other +Improvements: +(unit tests) +- unit test runner can run in background now without having the runner window open +- unit test batch runner can take a result file path as a parameter +- changed undo system for unit test runner and UnityUnitTest base class +- execution time in now visible in test details +- fixed a bug with tests that inherit from a base test class +(integration tests) +- added hierarchical structure for integration tests +- added Player runner to automate running integration tests on platforms +- Integration tests batch runner can take a result directory as a parameter +- Integration tests batch runner can run tests on platforms +- results are rendered in a player +(assertion component) +- changed default failure messages +- it's possible to override failure action on comparer failure +- added code stripper for assertions. +- vast performance improvement +- fixed bugs +Other: +- "Hide in hierarchy" option was removed from integration test runner +- "Focus on selection" option was removed from integration test runner +- "Hide test runner" option was removed from integration test runner +- result files for unit tests and integration tests are now not generated when running tests from the editor +- UI improvements +- removed UnityScript and Boo examples +- WP8 compatibility fixes + +Version 1.1.1 +Other: +- Documentation in Japanese was added + +Version 1.1 +Fixes: +- fixed display error that happened when unit test class inherited from another TestFixture class +- fixed false positive result when "Succeed on assertions" was checked and no assertions were present in the test +- fixed XmlResultWriter to be generate XML file compatible with XSD scheme +- XmlResultWriter result writer was rewritten to remove XML libraries dependency +- Fixed an issue with a check that should be executed once after a specified frame in OnUpdate. +- added missing file UnityUnitTest.cs +Improvements: +- Added Japanese translation of the documentation +- ErrorPause value will be reverted to previous state after test run finishes +- Assertion Component will not copy reference to a GameObject if the GameObject is the same as the component is attached to. Instead, it will set the reference to the new GameObject. +- Integration tests batch runner can now run multiple scenes +- Unit test runner will now include tests written in UnityScript and Boo +- Unit tests will not run automatically if the compilation failes +- Added scene auto-save option to the Unit Test Runner +Other: +- changed icons +- restructured project files +- moved XmlResultWriter to Common folder +- added UnityScript and Boo unit tests examples +- added more unit tests examples +- Test runners visual adjustments + +Version 1.0 +- Initial release \ No newline at end of file diff --git a/Assets/UnityTestTools/changelog.txt.meta b/Assets/UnityTestTools/changelog.txt.meta new file mode 100644 index 00000000..8c28fefa --- /dev/null +++ b/Assets/UnityTestTools/changelog.txt.meta @@ -0,0 +1,4 @@ +fileFormatVersion: 2 +guid: 29b770d9107643740b69cb98b00430aa +TextScriptImporter: + userData: