import from UniRx and some modified.

This commit is contained in:
Yoshifumi Kawai
2019-05-20 00:14:47 +09:00
parent d5dab7fd1a
commit 5aaeb13c5d
246 changed files with 20742 additions and 19 deletions

View File

@@ -0,0 +1,8 @@
fileFormatVersion: 2
guid: 959c1472a5d812843bedf9341e87af3b
folderAsset: yes
DefaultImporter:
externalObjects: {}
userData:
assetBundleName:
assetBundleVariant:

View File

@@ -0,0 +1,133 @@
#if UNITY_EDITOR
using System;
using UnityEngine;
namespace RuntimeUnitTestToolkit.Editor
{
// functional declarative construction like flutter.
internal interface IBuilder
{
GameObject GameObject { get; }
T GetComponent<T>();
}
internal class Builder<T> : IBuilder
where T : Component
{
public T Component1 { get; private set; }
public GameObject GameObject { get; private set; }
public Transform Transform { get { return GameObject.transform; } }
public RectTransform RectTransform { get { return GameObject.GetComponent<RectTransform>(); } }
public Action<GameObject> SetTarget
{
set
{
value(this.GameObject);
}
}
public IBuilder Child
{
set
{
value.GameObject.transform.SetParent(GameObject.transform);
}
}
public IBuilder[] Children
{
set
{
foreach (var item in value)
{
item.GameObject.transform.SetParent(GameObject.transform);
}
}
}
public Builder(string name)
{
this.GameObject = new GameObject(name);
this.Component1 = GameObject.AddComponent<T>();
}
public Builder(string name, out T referenceSelf) // out primary reference.
{
this.GameObject = new GameObject(name);
this.Component1 = GameObject.AddComponent<T>();
referenceSelf = this.Component1;
}
public TComponent GetComponent<TComponent>()
{
return this.GameObject.GetComponent<TComponent>();
}
}
internal class Builder<T1, T2> : Builder<T1>
where T1 : Component
where T2 : Component
{
public T2 Component2 { get; private set; }
public Builder(string name)
: base(name)
{
this.Component2 = GameObject.AddComponent<T2>();
}
public Builder(string name, out T1 referenceSelf)
: base(name, out referenceSelf)
{
this.Component2 = GameObject.AddComponent<T2>();
}
}
internal class Builder<T1, T2, T3> : Builder<T1, T2>
where T1 : Component
where T2 : Component
where T3 : Component
{
public T3 Component3 { get; private set; }
public Builder(string name)
: base(name)
{
this.Component3 = GameObject.AddComponent<T3>();
}
public Builder(string name, out T1 referenceSelf)
: base(name, out referenceSelf)
{
this.Component3 = GameObject.AddComponent<T3>();
}
}
internal class Builder<T1, T2, T3, T4> : Builder<T1, T2, T3>
where T1 : Component
where T2 : Component
where T3 : Component
where T4 : Component
{
public T4 Component4 { get; private set; }
public Builder(string name)
: base(name)
{
this.Component4 = GameObject.AddComponent<T4>();
}
public Builder(string name, out T1 referenceSelf)
: base(name, out referenceSelf)
{
this.Component4 = GameObject.AddComponent<T4>();
}
}
}
#endif

View File

@@ -0,0 +1,11 @@
fileFormatVersion: 2
guid: 8760bbbab905a534eb6fb7b61b736926
MonoImporter:
externalObjects: {}
serializedVersion: 2
defaultReferences: []
executionOrder: 0
icon: {instanceID: 0}
userData:
assetBundleName:
assetBundleVariant:

View File

@@ -0,0 +1,341 @@
#if UNITY_EDITOR
using UnityEditor;
// Settings MenuItems.
public static partial class UnitTestBuilder
{
[MenuItem("Test/Settings/ScriptBackend/Mono", validate = true, priority = 1)]
static bool ValidateScriptBackendMono()
{
Menu.SetChecked("Test/Settings/ScriptBackend/Mono", LoadOrGetDefaultSettings().ScriptBackend == ScriptingImplementation.Mono2x);
return true;
}
[MenuItem("Test/Settings/ScriptBackend/Mono", validate = false, priority = 1)]
static void ScriptBackendMono()
{
var settings = LoadOrGetDefaultSettings();
settings.UseCurrentScriptBackend = false;
settings.ScriptBackend = ScriptingImplementation.Mono2x;
SaveSettings(settings);
}
[MenuItem("Test/Settings/ScriptBackend/IL2CPP", validate = true, priority = 2)]
static bool ValidateScriptBackendIL2CPP()
{
Menu.SetChecked("Test/Settings/ScriptBackend/IL2CPP", LoadOrGetDefaultSettings().ScriptBackend == ScriptingImplementation.IL2CPP);
return true;
}
[MenuItem("Test/Settings/ScriptBackend/IL2CPP", validate = false, priority = 2)]
static void ScriptBackendIL2CPP()
{
var settings = LoadOrGetDefaultSettings();
settings.UseCurrentScriptBackend = false;
settings.ScriptBackend = ScriptingImplementation.IL2CPP;
SaveSettings(settings);
}
[MenuItem("Test/Settings/AutoRunPlayer", validate = true, priority = 3)]
static bool ValidateAutoRun()
{
Menu.SetChecked("Test/Settings/AutoRunPlayer", LoadOrGetDefaultSettings().AutoRunPlayer);
return true;
}
[MenuItem("Test/Settings/AutoRunPlayer", validate = false, priority = 3)]
static void AutoRun()
{
var settings = LoadOrGetDefaultSettings();
settings.AutoRunPlayer = !settings.AutoRunPlayer;
SaveSettings(settings);
}
[MenuItem("Test/Settings/Headless", validate = true, priority = 4)]
static bool ValidateHeadless()
{
Menu.SetChecked("Test/Settings/Headless", LoadOrGetDefaultSettings().Headless);
return true;
}
[MenuItem("Test/Settings/Headless", validate = false, priority = 4)]
static void Headless()
{
var settings = LoadOrGetDefaultSettings();
settings.Headless = !settings.Headless;
SaveSettings(settings);
}
[MenuItem("Test/Settings/DisableAutoClose", validate = true, priority = 5)]
static bool ValidateDisableAutoClose()
{
Menu.SetChecked("Test/Settings/DisableAutoClose", LoadOrGetDefaultSettings().DisableAutoClose);
return true;
}
[MenuItem("Test/Settings/DisableAutoClose", validate = false, priority = 5)]
static void DisableAutoClose()
{
var settings = LoadOrGetDefaultSettings();
settings.DisableAutoClose = !settings.DisableAutoClose;
SaveSettings(settings);
}
// generated
/*
*
void Main()
{
var sb = new StringBuilder();
var p = 1;
foreach (var target in Enum.GetNames(typeof(BuildTarget)))
{
var path = $"Test/Settings/BuildTarget/{target}";
var priority = p++;
var template = $@"
[MenuItem(""{path}"", validate = true, priority = {priority})]
static bool ValidateBuildTarget{target}()
{{
Menu.SetChecked(""{path}"", LoadOrGetDefaultSettings().BuildTarget == BuildTarget.{target});
return true;
}}
[MenuItem(""{path}"", validate = false, priority = {priority})]
static void BuildTarget{target}()
{{
var settings = LoadOrGetDefaultSettings();
settings.UseCurrentBuildTarget = false;
settings.BuildTarget = BuildTarget.{target};
SaveSettings(settings);
}}";
sb.AppendLine(template);
}
sb.ToString().Dump();
}
public enum BuildTarget
{
StandaloneWindows,
StandaloneWindows64,
StandaloneLinux,
StandaloneLinux64,
StandaloneOSX,
WebGL,
iOS,
Android,
WSAPlayer,
PS4,
XboxOne,
Switch,
}
*/
[MenuItem("Test/Settings/BuildTarget/StandaloneWindows", validate = true, priority = 1)]
static bool ValidateBuildTargetStandaloneWindows()
{
Menu.SetChecked("Test/Settings/BuildTarget/StandaloneWindows", LoadOrGetDefaultSettings().BuildTarget == BuildTarget.StandaloneWindows);
return true;
}
[MenuItem("Test/Settings/BuildTarget/StandaloneWindows", validate = false, priority = 1)]
static void BuildTargetStandaloneWindows()
{
var settings = LoadOrGetDefaultSettings();
settings.UseCurrentBuildTarget = false;
settings.BuildTarget = BuildTarget.StandaloneWindows;
SaveSettings(settings);
}
[MenuItem("Test/Settings/BuildTarget/StandaloneWindows64", validate = true, priority = 2)]
static bool ValidateBuildTargetStandaloneWindows64()
{
Menu.SetChecked("Test/Settings/BuildTarget/StandaloneWindows64", LoadOrGetDefaultSettings().BuildTarget == BuildTarget.StandaloneWindows64);
return true;
}
[MenuItem("Test/Settings/BuildTarget/StandaloneWindows64", validate = false, priority = 2)]
static void BuildTargetStandaloneWindows64()
{
var settings = LoadOrGetDefaultSettings();
settings.UseCurrentBuildTarget = false;
settings.BuildTarget = BuildTarget.StandaloneWindows64;
SaveSettings(settings);
}
[MenuItem("Test/Settings/BuildTarget/StandaloneLinux", validate = true, priority = 3)]
static bool ValidateBuildTargetStandaloneLinux()
{
Menu.SetChecked("Test/Settings/BuildTarget/StandaloneLinux", LoadOrGetDefaultSettings().BuildTarget == BuildTarget.StandaloneLinux);
return true;
}
[MenuItem("Test/Settings/BuildTarget/StandaloneLinux", validate = false, priority = 3)]
static void BuildTargetStandaloneLinux()
{
var settings = LoadOrGetDefaultSettings();
settings.UseCurrentBuildTarget = false;
settings.BuildTarget = BuildTarget.StandaloneLinux;
SaveSettings(settings);
}
[MenuItem("Test/Settings/BuildTarget/StandaloneLinux64", validate = true, priority = 4)]
static bool ValidateBuildTargetStandaloneLinux64()
{
Menu.SetChecked("Test/Settings/BuildTarget/StandaloneLinux64", LoadOrGetDefaultSettings().BuildTarget == BuildTarget.StandaloneLinux64);
return true;
}
[MenuItem("Test/Settings/BuildTarget/StandaloneLinux64", validate = false, priority = 4)]
static void BuildTargetStandaloneLinux64()
{
var settings = LoadOrGetDefaultSettings();
settings.UseCurrentBuildTarget = false;
settings.BuildTarget = BuildTarget.StandaloneLinux64;
SaveSettings(settings);
}
[MenuItem("Test/Settings/BuildTarget/StandaloneOSX", validate = true, priority = 5)]
static bool ValidateBuildTargetStandaloneOSX()
{
Menu.SetChecked("Test/Settings/BuildTarget/StandaloneOSX", LoadOrGetDefaultSettings().BuildTarget == BuildTarget.StandaloneOSX);
return true;
}
[MenuItem("Test/Settings/BuildTarget/StandaloneOSX", validate = false, priority = 5)]
static void BuildTargetStandaloneOSX()
{
var settings = LoadOrGetDefaultSettings();
settings.UseCurrentBuildTarget = false;
settings.BuildTarget = BuildTarget.StandaloneOSX;
SaveSettings(settings);
}
[MenuItem("Test/Settings/BuildTarget/WebGL", validate = true, priority = 6)]
static bool ValidateBuildTargetWebGL()
{
Menu.SetChecked("Test/Settings/BuildTarget/WebGL", LoadOrGetDefaultSettings().BuildTarget == BuildTarget.WebGL);
return true;
}
[MenuItem("Test/Settings/BuildTarget/WebGL", validate = false, priority = 6)]
static void BuildTargetWebGL()
{
var settings = LoadOrGetDefaultSettings();
settings.UseCurrentBuildTarget = false;
settings.BuildTarget = BuildTarget.WebGL;
SaveSettings(settings);
}
[MenuItem("Test/Settings/BuildTarget/iOS", validate = true, priority = 7)]
static bool ValidateBuildTargetiOS()
{
Menu.SetChecked("Test/Settings/BuildTarget/iOS", LoadOrGetDefaultSettings().BuildTarget == BuildTarget.iOS);
return true;
}
[MenuItem("Test/Settings/BuildTarget/iOS", validate = false, priority = 7)]
static void BuildTargetiOS()
{
var settings = LoadOrGetDefaultSettings();
settings.UseCurrentBuildTarget = false;
settings.BuildTarget = BuildTarget.iOS;
SaveSettings(settings);
}
[MenuItem("Test/Settings/BuildTarget/Android", validate = true, priority = 8)]
static bool ValidateBuildTargetAndroid()
{
Menu.SetChecked("Test/Settings/BuildTarget/Android", LoadOrGetDefaultSettings().BuildTarget == BuildTarget.Android);
return true;
}
[MenuItem("Test/Settings/BuildTarget/Android", validate = false, priority = 8)]
static void BuildTargetAndroid()
{
var settings = LoadOrGetDefaultSettings();
settings.UseCurrentBuildTarget = false;
settings.BuildTarget = BuildTarget.Android;
SaveSettings(settings);
}
[MenuItem("Test/Settings/BuildTarget/WSAPlayer", validate = true, priority = 9)]
static bool ValidateBuildTargetWSAPlayer()
{
Menu.SetChecked("Test/Settings/BuildTarget/WSAPlayer", LoadOrGetDefaultSettings().BuildTarget == BuildTarget.WSAPlayer);
return true;
}
[MenuItem("Test/Settings/BuildTarget/WSAPlayer", validate = false, priority = 9)]
static void BuildTargetWSAPlayer()
{
var settings = LoadOrGetDefaultSettings();
settings.UseCurrentBuildTarget = false;
settings.BuildTarget = BuildTarget.WSAPlayer;
SaveSettings(settings);
}
[MenuItem("Test/Settings/BuildTarget/PS4", validate = true, priority = 10)]
static bool ValidateBuildTargetPS4()
{
Menu.SetChecked("Test/Settings/BuildTarget/PS4", LoadOrGetDefaultSettings().BuildTarget == BuildTarget.PS4);
return true;
}
[MenuItem("Test/Settings/BuildTarget/PS4", validate = false, priority = 10)]
static void BuildTargetPS4()
{
var settings = LoadOrGetDefaultSettings();
settings.UseCurrentBuildTarget = false;
settings.BuildTarget = BuildTarget.PS4;
SaveSettings(settings);
}
[MenuItem("Test/Settings/BuildTarget/XboxOne", validate = true, priority = 11)]
static bool ValidateBuildTargetXboxOne()
{
Menu.SetChecked("Test/Settings/BuildTarget/XboxOne", LoadOrGetDefaultSettings().BuildTarget == BuildTarget.XboxOne);
return true;
}
[MenuItem("Test/Settings/BuildTarget/XboxOne", validate = false, priority = 11)]
static void BuildTargetXboxOne()
{
var settings = LoadOrGetDefaultSettings();
settings.UseCurrentBuildTarget = false;
settings.BuildTarget = BuildTarget.XboxOne;
SaveSettings(settings);
}
[MenuItem("Test/Settings/BuildTarget/Switch", validate = true, priority = 12)]
static bool ValidateBuildTargetSwitch()
{
Menu.SetChecked("Test/Settings/BuildTarget/Switch", LoadOrGetDefaultSettings().BuildTarget == BuildTarget.Switch);
return true;
}
[MenuItem("Test/Settings/BuildTarget/Switch", validate = false, priority = 12)]
static void BuildTargetSwitch()
{
var settings = LoadOrGetDefaultSettings();
settings.UseCurrentBuildTarget = false;
settings.BuildTarget = BuildTarget.Switch;
SaveSettings(settings);
}
}
#endif

View File

@@ -0,0 +1,11 @@
fileFormatVersion: 2
guid: 12bdad0556e999f4aa82da29415d361f
MonoImporter:
externalObjects: {}
serializedVersion: 2
defaultReferences: []
executionOrder: 0
icon: {instanceID: 0}
userData:
assetBundleName:
assetBundleVariant:

View File

@@ -0,0 +1,515 @@
#if UNITY_EDITOR
using RuntimeUnitTestToolkit;
using RuntimeUnitTestToolkit.Editor;
using System;
using UnityEditor;
using UnityEditor.Build.Reporting;
using UnityEditor.SceneManagement;
using UnityEngine;
using UnityEngine.EventSystems;
using UnityEngine.SceneManagement;
using UnityEngine.UI;
internal class RuntimeUnitTestSettings
{
public ScriptingImplementation ScriptBackend;
public bool UseCurrentScriptBackend;
public BuildTarget BuildTarget;
public bool UseCurrentBuildTarget;
public bool Headless;
public bool AutoRunPlayer;
public bool DisableAutoClose;
public RuntimeUnitTestSettings()
{
UseCurrentBuildTarget = true;
UseCurrentScriptBackend = true;
Headless = false;
AutoRunPlayer = true;
DisableAutoClose = false;
}
public override string ToString()
{
return $"{ScriptBackend} {BuildTarget} Headless:{Headless} AutoRunPlayer:{AutoRunPlayer} DisableAutoClose:{DisableAutoClose}";
}
}
// no namespace(because invoke from commandline)
public static partial class UnitTestBuilder
{
const string SettingsKeyBase = "RuntimeUnitTest.Settings.";
[MenuItem("Test/BuildUnitTest")]
public static void BuildUnitTest()
{
var settings = new RuntimeUnitTestSettings(); // default
string buildPath = null;
if (Application.isBatchMode) // from commandline
{
settings.AutoRunPlayer = false;
settings.DisableAutoClose = false;
var cmdArgs = Environment.GetCommandLineArgs();
for (int i = 0; i < cmdArgs.Length; i++)
{
if (string.Equals(cmdArgs[i].Trim('-', '/'), "ScriptBackend", StringComparison.OrdinalIgnoreCase))
{
settings.UseCurrentScriptBackend = false;
var str = cmdArgs[++i];
if (str.StartsWith("mono", StringComparison.OrdinalIgnoreCase))
{
settings.ScriptBackend = ScriptingImplementation.Mono2x;
}
else if (str.StartsWith("IL2CPP", StringComparison.OrdinalIgnoreCase))
{
settings.ScriptBackend = ScriptingImplementation.IL2CPP;
}
else
{
settings.ScriptBackend = (ScriptingImplementation)Enum.Parse(typeof(ScriptingImplementation), str, true);
}
}
else if (string.Equals(cmdArgs[i].Trim('-', '/'), "BuildTarget", StringComparison.OrdinalIgnoreCase))
{
settings.UseCurrentBuildTarget = false;
settings.BuildTarget = (BuildTarget)Enum.Parse(typeof(BuildTarget), cmdArgs[++i], true);
}
else if (string.Equals(cmdArgs[i].Trim('-', '/'), "Headless", StringComparison.OrdinalIgnoreCase))
{
settings.Headless = true;
}
else if (string.Equals(cmdArgs[i].Trim('-', '/'), "buildPath", StringComparison.OrdinalIgnoreCase))
{
buildPath = cmdArgs[++i];
}
}
}
else
{
var key = SettingsKeyBase + Application.productName;
var settingsValue = EditorPrefs.GetString(key, null);
try
{
if (!string.IsNullOrWhiteSpace(settingsValue))
{
settings = JsonUtility.FromJson<RuntimeUnitTestSettings>(settingsValue);
}
}
catch
{
UnityEngine.Debug.LogError("Fail to load RuntimeUnitTest settings");
EditorPrefs.SetString(key, null);
}
}
if (settings.UseCurrentBuildTarget)
{
settings.BuildTarget = EditorUserBuildSettings.activeBuildTarget;
}
if (settings.UseCurrentScriptBackend)
{
settings.ScriptBackend = PlayerSettings.GetScriptingBackend(ToBuildTargetGroup(settings.BuildTarget));
}
if (buildPath == null)
{
buildPath = $"bin/UnitTest/{settings.BuildTarget}_{settings.ScriptBackend}/test" + (IsWindows(settings.BuildTarget) ? ".exe" : "");
}
var originalScene = SceneManager.GetActiveScene().path;
BuildUnitTest(buildPath, settings.ScriptBackend, settings.BuildTarget, settings.Headless, settings.AutoRunPlayer, settings.DisableAutoClose);
// reopen original scene
if (!string.IsNullOrWhiteSpace(originalScene))
{
EditorSceneManager.OpenScene(originalScene, OpenSceneMode.Single);
}
else
{
EditorSceneManager.NewScene(NewSceneSetup.DefaultGameObjects);
}
}
static RuntimeUnitTestSettings LoadOrGetDefaultSettings()
{
var key = SettingsKeyBase + Application.productName;
var settingsValue = EditorPrefs.GetString(key, null);
RuntimeUnitTestSettings settings = null;
try
{
if (!string.IsNullOrWhiteSpace(settingsValue))
{
settings = JsonUtility.FromJson<RuntimeUnitTestSettings>(settingsValue);
}
}
catch
{
UnityEngine.Debug.LogError("Fail to load RuntimeUnitTest settings");
EditorPrefs.SetString(key, null);
settings = null;
}
if (settings == null)
{
// default
settings = new RuntimeUnitTestSettings
{
UseCurrentBuildTarget = true,
UseCurrentScriptBackend = true,
Headless = false,
AutoRunPlayer = true,
};
}
return settings;
}
static void SaveSettings(RuntimeUnitTestSettings settings)
{
var key = SettingsKeyBase + Application.productName;
EditorPrefs.SetString(key, JsonUtility.ToJson(settings));
}
public static void BuildUnitTest(string buildPath, ScriptingImplementation scriptBackend, BuildTarget buildTarget, bool headless, bool autoRunPlayer, bool disableAutoClose)
{
var sceneName = "Assets/TempRuntimeUnitTestScene_" + DateTimeOffset.UtcNow.ToUnixTimeSeconds();
if (disableAutoClose)
{
sceneName += "_DisableAutoClose";
}
sceneName += ".unity";
var scene = EditorSceneManager.NewScene(NewSceneSetup.EmptyScene, NewSceneMode.Single);
BuildUnitTestRunnerScene();
EditorSceneManager.MarkSceneDirty(scene);
AssetDatabase.SaveAssets();
EditorSceneManager.SaveScene(scene, sceneName, false);
try
{
Build(sceneName, buildPath, new RuntimeUnitTestSettings { ScriptBackend = scriptBackend, BuildTarget = buildTarget, Headless = headless, AutoRunPlayer = autoRunPlayer, DisableAutoClose = disableAutoClose });
}
finally
{
AssetDatabase.DeleteAsset(sceneName);
}
}
public static UnitTestRunner BuildUnitTestRunnerScene()
{
const string kStandardSpritePath = "UI/Skin/UISprite.psd";
const string kBackgroundSpritePath = "UI/Skin/Background.psd";
var uisprite = AssetDatabase.GetBuiltinExtraResource<Sprite>(kStandardSpritePath);
var background = AssetDatabase.GetBuiltinExtraResource<Sprite>(kBackgroundSpritePath);
ScrollRect buttonList;
VerticalLayoutGroup listLayout;
Scrollbar refListScrollbar;
ScrollRect logList;
Scrollbar refLogScrollbar;
Button clearButton;
Text logText;
// Flutter like coded build utility
var rootObject = new Builder<Camera>("SceneRoot")
{
Children = new IBuilder[] {
new Builder<EventSystem, StandaloneInputModule>("EventSystem"),
new Builder<Canvas, CanvasScaler, GraphicRaycaster>("Canvas") {
Component1 = { renderMode = RenderMode.ScreenSpaceOverlay },
Children = new IBuilder[] {
new Builder<HorizontalLayoutGroup, CanvasRenderer>("HorizontalSplitter") {
RectTransform = { anchorMin = new Vector2(0, 0), anchorMax = new Vector2(1, 1) },
Component1 = { childControlWidth = true, childControlHeight = true, spacing = 10 },
Children = new IBuilder[] {
new Builder<ScrollRect, CanvasRenderer>("ButtonList", out buttonList) {
RectTransform = { pivot = new Vector2(0.5f, 0.5f) },
Component1 = { horizontal =false, vertical = true, movementType = ScrollRect.MovementType.Clamped },
Children = new IBuilder[] {
new Builder<VerticalLayoutGroup, ContentSizeFitter>("ListLayoutToAttach", out listLayout) {
RectTransform = { anchorMin = new Vector2(0, 0), anchorMax = new Vector2(1, 1), pivot = new Vector2(0, 1) },
Component1 = { childControlWidth = true, childControlHeight = true, childForceExpandWidth = true, childForceExpandHeight = false, spacing = 10, padding = new RectOffset(10,20,10,10) },
Component2 = { horizontalFit = ContentSizeFitter.FitMode.Unconstrained, verticalFit = ContentSizeFitter.FitMode.PreferredSize },
SetTarget = self => { buttonList.content = self.GetComponent<RectTransform>(); },
Child = new Builder<Button, Image, LayoutElement>("ClearButton", out clearButton) {
Component2 = { sprite = uisprite, type = Image.Type.Sliced },
Component3 = { minHeight = 50 },
SetTarget = self => { self.GetComponent<Button>().targetGraphic = self.GetComponent<Graphic>(); },
Child = new Builder<Text>("ButtonText") {
RectTransform = { anchorMin = new Vector2(0, 0), anchorMax = new Vector2(1, 1), pivot = new Vector2(0.5f, 0.5f) },
Component1 = { text = "Clear", color = FromRGB(50, 50, 50), alignment = TextAnchor.MiddleCenter, fontSize = 24, lineSpacing = 1 }
}
}
},
new Builder<Scrollbar,Image>("ListScrollbar", out refListScrollbar) {
RectTransform = { anchorMin = new Vector2(1, 0), anchorMax = new Vector2(1, 1) },
Component1 = { navigation = new Navigation{ mode = Navigation.Mode.None }, direction = Scrollbar.Direction.BottomToTop, size = 1.0f },
Component2 = { sprite = background, type = Image.Type.Sliced },
SetTarget = self => { buttonList.verticalScrollbar = self.GetComponent<Scrollbar>(); },
Child = new Builder<RectTransform>("Sliding Area") {
RectTransform = { anchorMin = new Vector2(0, 0), anchorMax = new Vector2(1, 1) },
Child = new Builder<Image>("Handle") {
Component1 = { sprite = uisprite, type = Image.Type.Sliced },
SetTarget = self =>
{
refListScrollbar.targetGraphic = self.GetComponent<Graphic>();
refListScrollbar.handleRect = self.GetComponent<RectTransform>();
}
}
}
}
}
},
new Builder<ScrollRect, CanvasRenderer>("ScrollableText", out logList) {
RectTransform = { pivot = new Vector2(0.5f, 0.5f) },
Component1 = { horizontal =false, vertical = true, movementType = ScrollRect.MovementType.Elastic, elasticity = 0.1f },
Children = new IBuilder[] {
new Builder<Text, ContentSizeFitter>("Log", out logText) {
RectTransform = { anchorMin = new Vector2(0, 0), anchorMax = new Vector2(1, 1), pivot = new Vector2(0, 1) },
Component1 = { fontSize = 24, lineSpacing = 1, supportRichText = true, alignment = TextAnchor.UpperLeft, horizontalOverflow = HorizontalWrapMode.Wrap, verticalOverflow = VerticalWrapMode.Truncate },
Component2 = { horizontalFit = ContentSizeFitter.FitMode.Unconstrained, verticalFit = ContentSizeFitter.FitMode.PreferredSize },
SetTarget = self => { logList.content = self.GetComponent<RectTransform>(); }
},
new Builder<Scrollbar,Image>("LogScrollbar", out refLogScrollbar) {
RectTransform = { anchorMin = new Vector2(1, 0), anchorMax = new Vector2(1, 1) },
Component1 = { navigation = new Navigation{ mode = Navigation.Mode.None }, direction = Scrollbar.Direction.BottomToTop, size = 1.0f },
Component2 = { sprite = background, type = Image.Type.Sliced },
SetTarget = self => { logList.verticalScrollbar = self.GetComponent<Scrollbar>(); },
Child = new Builder<RectTransform>("Sliding Area2") {
RectTransform = { anchorMin = new Vector2(0, 0), anchorMax = new Vector2(1, 1) },
Child = new Builder<Image>("Handle2") {
Component1 = { sprite = uisprite, type = Image.Type.Sliced },
SetTarget = self =>
{
refLogScrollbar.targetGraphic = self.GetComponent<Graphic>();
refLogScrollbar.handleRect = self.GetComponent<RectTransform>();
}
}
}
}
}
},
}
}
}
}
}
};
// size modify after build complete:)
{
var rect = GameObject.Find("HorizontalSplitter").GetComponent<RectTransform>();
rect.offsetMin = new Vector2(0, 0);
rect.offsetMax = new Vector2(0, 0);
}
{
var rect = GameObject.Find("ListLayoutToAttach").GetComponent<RectTransform>();
rect.offsetMin = new Vector2(0, 0);
rect.offsetMax = new Vector2(0, 0);
}
{
var rect = GameObject.Find("ListScrollbar").GetComponent<RectTransform>();
rect.offsetMin = new Vector2(0, 0);
rect.offsetMax = new Vector2(0, 0);
rect.sizeDelta = new Vector2(30, 0);
}
{
var rect = GameObject.Find("ClearButton").GetComponent<RectTransform>();
rect.offsetMin = new Vector2(0, 0);
rect.offsetMax = new Vector2(0, 0);
}
{
var rect = GameObject.Find("Sliding Area").GetComponent<RectTransform>();
rect.offsetMin = new Vector2(0, 0);
rect.offsetMax = new Vector2(0, 0);
rect.sizeDelta = new Vector2(-20, -20);
}
{
var rect = GameObject.Find("Handle").GetComponent<RectTransform>();
rect.offsetMin = new Vector2(0, 0);
rect.offsetMax = new Vector2(0, 0);
rect.sizeDelta = new Vector2(20, 20);
}
{
var rect = GameObject.Find("ButtonText").GetComponent<RectTransform>();
rect.offsetMin = new Vector2(0, 0);
rect.offsetMax = new Vector2(0, 0);
}
{
var rect = GameObject.Find("Log").GetComponent<RectTransform>();
rect.offsetMin = new Vector2(15, 0);
rect.offsetMax = new Vector2(-20, 0);
}
{
var rect = GameObject.Find("LogScrollbar").GetComponent<RectTransform>();
rect.offsetMin = new Vector2(-30, 0);
rect.offsetMax = new Vector2(0, 0);
rect.sizeDelta = new Vector2(30, 0);
}
{
var rect = GameObject.Find("Sliding Area2").GetComponent<RectTransform>();
rect.offsetMin = new Vector2(0, 0);
rect.offsetMax = new Vector2(0, 0);
rect.sizeDelta = new Vector2(-20, -20);
}
{
var rect = GameObject.Find("Handle2").GetComponent<RectTransform>();
rect.offsetMin = new Vector2(0, 0);
rect.offsetMax = new Vector2(0, 0);
rect.sizeDelta = new Vector2(20, 20);
}
// add test script
var runner = rootObject.GameObject.AddComponent<UnitTestRunner>();
runner.clearButton = clearButton;
runner.list = listLayout.gameObject.GetComponent<RectTransform>();
runner.listScrollBar = refListScrollbar;
runner.logText = logText;
runner.logScrollBar = refLogScrollbar;
return runner;
}
static void Build(string sceneName, string buildPath, RuntimeUnitTestSettings settings)
{
var options = BuildOptions.BuildScriptsOnly | BuildOptions.IncludeTestAssemblies;
if (settings.AutoRunPlayer)
{
options |= BuildOptions.AutoRunPlayer;
}
if (settings.Headless)
{
options |= BuildOptions.EnableHeadlessMode;
}
var targetGroup = ToBuildTargetGroup(settings.BuildTarget);
var currentBackend = PlayerSettings.GetScriptingBackend(targetGroup);
if (currentBackend != settings.ScriptBackend)
{
UnityEngine.Debug.Log("Modify ScriptBackend to " + settings.ScriptBackend);
PlayerSettings.SetScriptingBackend(targetGroup, settings.ScriptBackend);
}
var buildOptions = new BuildPlayerOptions
{
target = settings.BuildTarget,
targetGroup = targetGroup,
options = options,
scenes = new[] { sceneName },
locationPathName = buildPath
};
UnityEngine.Debug.Log("UnitTest Build Start, " + settings.ToString());
var buildReport = BuildPipeline.BuildPlayer(buildOptions);
if (currentBackend != settings.ScriptBackend)
{
UnityEngine.Debug.Log("Restore ScriptBackend to " + currentBackend);
PlayerSettings.SetScriptingBackend(targetGroup, currentBackend);
}
if (buildReport.summary.result != BuildResult.Succeeded)
{
// Note: show error summary?
// Debug.LogError(buildReport.SummarizeErrors());
UnityEngine.Debug.LogError("UnitTest Build Failed.");
}
else
{
UnityEngine.Debug.Log("UnitTest Build Completed, binary located: " + buildOptions.locationPathName);
}
}
static Color FromRGB(int r, int g, int b)
{
return new Color(r / 255f, g / 255f, b / 255f);
}
static bool IsWindows(BuildTarget buildTarget)
{
switch (buildTarget)
{
case BuildTarget.StandaloneWindows:
case BuildTarget.StandaloneWindows64:
case BuildTarget.WSAPlayer:
return true;
default:
return false;
}
}
static BuildTargetGroup ToBuildTargetGroup(BuildTarget buildTarget)
{
#pragma warning disable CS0618
switch (buildTarget)
{
case BuildTarget.StandaloneOSX:
case (BuildTarget)3:
case BuildTarget.StandaloneOSXIntel:
case BuildTarget.StandaloneOSXIntel64:
case BuildTarget.StandaloneWindows:
case BuildTarget.StandaloneWindows64:
case BuildTarget.StandaloneLinux:
case BuildTarget.StandaloneLinux64:
case BuildTarget.StandaloneLinuxUniversal:
return BuildTargetGroup.Standalone;
case (BuildTarget)6:
case (BuildTarget)7:
case BuildTarget.WebGL:
return BuildTargetGroup.WebGL;
case BuildTarget.iOS:
return BuildTargetGroup.iOS;
case BuildTarget.PS3:
return BuildTargetGroup.PS3;
case BuildTarget.PS4:
return BuildTargetGroup.PS4;
case BuildTarget.XBOX360:
return BuildTargetGroup.XBOX360;
case BuildTarget.Android:
return BuildTargetGroup.Android;
case BuildTarget.WSAPlayer:
return BuildTargetGroup.WSA;
case BuildTarget.WP8Player:
return BuildTargetGroup.WP8;
case BuildTarget.Tizen:
return BuildTargetGroup.Tizen;
case BuildTarget.PSP2:
return BuildTargetGroup.PSP2;
case BuildTarget.PSM:
return BuildTargetGroup.PSM;
case BuildTarget.XboxOne:
return BuildTargetGroup.XboxOne;
case BuildTarget.SamsungTV:
return BuildTargetGroup.SamsungTV;
case BuildTarget.N3DS:
return BuildTargetGroup.N3DS;
case BuildTarget.WiiU:
return BuildTargetGroup.WiiU;
case BuildTarget.tvOS:
return BuildTargetGroup.tvOS;
case BuildTarget.Switch:
return BuildTargetGroup.Switch;
case BuildTarget.Lumin:
return BuildTargetGroup.Lumin;
case BuildTarget.BlackBerry:
return BuildTargetGroup.BlackBerry;
case BuildTarget.NoTarget:
default:
return BuildTargetGroup.Unknown;
}
#pragma warning restore CS0618
}
}
#endif

View File

@@ -0,0 +1,12 @@
fileFormatVersion: 2
guid: 3518da33b6245d341a0ef3670ee9268b
timeCreated: 1488689723
licenseType: Pro
MonoImporter:
serializedVersion: 2
defaultReferences: []
executionOrder: 0
icon: {instanceID: 0}
userData:
assetBundleName:
assetBundleVariant:

View File

@@ -0,0 +1,16 @@
{
"name": "RuntimeUnitTestToolkit",
"references": [
],
"optionalUnityReferences": [
"TestAssemblies"
],
"includePlatforms": [],
"excludePlatforms": [],
"allowUnsafeCode": false,
"overrideReferences": false,
"precompiledReferences": [],
"autoReferenced": true,
"defineConstraints": [],
"versionDefines": []
}

View File

@@ -0,0 +1,7 @@
fileFormatVersion: 2
guid: 14c4fea4b238088479114ba2ffe195f9
AssemblyDefinitionImporter:
externalObjects: {}
userData:
assetBundleName:
assetBundleVariant:

View File

@@ -0,0 +1,450 @@
using NUnit.Framework;
using System;
using System.Collections;
using System.Collections.Generic;
using System.Linq;
using System.Reflection;
using UnityEngine;
using UnityEngine.Events;
using UnityEngine.TestTools;
using UnityEngine.UI;
namespace RuntimeUnitTestToolkit
{
public class UnitTestRunner : MonoBehaviour
{
// object is IEnumerator or Func<IEnumerator>
Dictionary<string, List<KeyValuePair<string, object>>> tests = new Dictionary<string, List<KeyValuePair<string, object>>>();
List<Pair> additionalActionsOnFirst = new List<Pair>();
public Button clearButton;
public RectTransform list;
public Scrollbar listScrollBar;
public Text logText;
public Scrollbar logScrollBar;
readonly Color passColor = new Color(0f, 1f, 0f, 1f); // green
readonly Color failColor = new Color(1f, 0f, 0f, 1f); // red
readonly Color normalColor = new Color(1f, 1f, 1f, 1f); // white
bool allTestGreen = true;
void Start()
{
try
{
UnityEngine.Application.logMessageReceived += (a, b, c) =>
{
logText.text += "[" + c + "]" + a + "\n";
};
// register all test types
foreach (var item in GetTestTargetTypes())
{
RegisterAllMethods(item);
}
var executeAll = new List<Func<Coroutine>>();
foreach (var ___item in tests)
{
var actionList = ___item; // be careful, capture in lambda
executeAll.Add(() => StartCoroutine(RunTestInCoroutine(actionList)));
Add(actionList.Key, () => StartCoroutine(RunTestInCoroutine(actionList)));
}
var executeAllButton = Add("Run All Tests", () => StartCoroutine(ExecuteAllInCoroutine(executeAll)));
clearButton.gameObject.GetComponent<Image>().color = new Color(170 / 255f, 170 / 255f, 170 / 255f, 1);
executeAllButton.gameObject.GetComponent<Image>().color = new Color(250 / 255f, 150 / 255f, 150 / 255f, 1);
executeAllButton.transform.SetSiblingIndex(1);
additionalActionsOnFirst.Reverse();
foreach (var item in additionalActionsOnFirst)
{
var newButton = GameObject.Instantiate(clearButton);
newButton.name = item.Name;
newButton.onClick.RemoveAllListeners();
newButton.GetComponentInChildren<Text>().text = item.Name;
newButton.onClick.AddListener(item.Action);
newButton.transform.SetParent(list);
newButton.transform.SetSiblingIndex(1);
}
clearButton.onClick.AddListener(() =>
{
logText.text = "";
foreach (var btn in list.GetComponentsInChildren<Button>())
{
btn.interactable = true;
btn.GetComponent<Image>().color = normalColor;
}
executeAllButton.gameObject.GetComponent<Image>().color = new Color(250 / 255f, 150 / 255f, 150 / 255f, 1);
});
listScrollBar.value = 1;
logScrollBar.value = 1;
if (Application.isBatchMode)
{
// run immediately in player
StartCoroutine(ExecuteAllInCoroutine(executeAll));
}
}
catch (Exception ex)
{
if (Application.isBatchMode)
{
// when failed(can not start runner), quit immediately.
WriteToConsole(ex.ToString());
Application.Quit(1);
}
else
{
throw;
}
}
}
Button Add(string title, UnityAction test)
{
var newButton = GameObject.Instantiate(clearButton);
newButton.name = title;
newButton.onClick.RemoveAllListeners();
newButton.GetComponentInChildren<Text>().text = title;
newButton.onClick.AddListener(test);
newButton.transform.SetParent(list);
return newButton;
}
static IEnumerable<Type> GetTestTargetTypes()
{
foreach (var assembly in AppDomain.CurrentDomain.GetAssemblies())
{
var n = assembly.FullName;
if (n.StartsWith("UnityEngine")) continue;
if (n.StartsWith("mscorlib")) continue;
if (n.StartsWith("System")) continue;
foreach (var item in assembly.GetTypes())
{
foreach (var method in item.GetMethods())
{
var t1 = method.GetCustomAttribute<TestAttribute>(true);
if (t1 != null)
{
yield return item;
break;
}
var t2 = method.GetCustomAttribute<UnityTestAttribute>(true);
if (t2 != null)
{
yield return item;
break;
}
}
}
}
}
public void AddTest(string group, string title, Action test)
{
List<KeyValuePair<string, object>> list;
if (!tests.TryGetValue(group, out list))
{
list = new List<KeyValuePair<string, object>>();
tests[group] = list;
}
list.Add(new KeyValuePair<string, object>(title, test));
}
public void AddAsyncTest(string group, string title, Func<IEnumerator> asyncTestCoroutine)
{
List<KeyValuePair<string, object>> list;
if (!tests.TryGetValue(group, out list))
{
list = new List<KeyValuePair<string, object>>();
tests[group] = list;
}
list.Add(new KeyValuePair<string, object>(title, asyncTestCoroutine));
}
public void AddCutomAction(string name, UnityAction action)
{
additionalActionsOnFirst.Add(new Pair { Name = name, Action = action });
}
public void RegisterAllMethods<T>()
where T : new()
{
RegisterAllMethods(typeof(T));
}
public void RegisterAllMethods(Type testType)
{
try
{
var test = Activator.CreateInstance(testType);
var methods = testType.GetMethods(System.Reflection.BindingFlags.Instance | System.Reflection.BindingFlags.Public);
foreach (var item in methods)
{
try
{
var iteratorTest = item.GetCustomAttribute<UnityEngine.TestTools.UnityTestAttribute>(true);
if (iteratorTest != null)
{
if (item.GetParameters().Length == 0 && item.ReturnType == typeof(IEnumerator))
{
var factory = (Func<IEnumerator>)Delegate.CreateDelegate(typeof(Func<IEnumerator>), test, item);
AddAsyncTest(factory.Target.GetType().Name, factory.Method.Name, factory);
}
else
{
UnityEngine.Debug.Log(testType.Name + "." + item.Name + " currently does not supported in RuntumeUnitTestToolkit(multiple parameter or return type is invalid).");
}
}
var standardTest = item.GetCustomAttribute<NUnit.Framework.TestAttribute>(true);
if (standardTest != null)
{
if (item.GetParameters().Length == 0 && item.ReturnType == typeof(void))
{
var invoke = (Action)Delegate.CreateDelegate(typeof(Action), test, item);
AddTest(invoke.Target.GetType().Name, invoke.Method.Name, invoke);
}
else
{
UnityEngine.Debug.Log(testType.Name + "." + item.Name + " currently does not supported in RuntumeUnitTestToolkit(multiple parameter or return type is invalid).");
}
}
}
catch (Exception e)
{
UnityEngine.Debug.LogError(testType.Name + "." + item.Name + " failed to register method, exception: " + e.ToString());
}
}
}
catch (Exception ex)
{
Debug.LogException(ex);
}
}
System.Collections.IEnumerator ScrollLogToEndNextFrame()
{
yield return null;
yield return null;
logScrollBar.value = 0;
}
IEnumerator RunTestInCoroutine(KeyValuePair<string, List<KeyValuePair<string, object>>> actionList)
{
Button self = null;
foreach (var btn in list.GetComponentsInChildren<Button>())
{
btn.interactable = false;
if (btn.name == actionList.Key) self = btn;
}
if (self != null)
{
self.GetComponent<Image>().color = normalColor;
}
var allGreen = true;
logText.text += "<color=yellow>" + actionList.Key + "</color>\n";
WriteToConsole("Begin Test Class: " + actionList.Key);
yield return null;
var totalExecutionTime = new List<double>();
foreach (var item2 in actionList.Value)
{
// before start, cleanup
GC.Collect();
GC.WaitForPendingFinalizers();
GC.Collect();
logText.text += "<color=teal>" + item2.Key + "</color>\n";
yield return null;
var v = item2.Value;
var methodStopwatch = System.Diagnostics.Stopwatch.StartNew();
Exception exception = null;
if (v is Action)
{
try
{
((Action)v).Invoke();
}
catch (Exception ex)
{
exception = ex;
}
}
else
{
var coroutineFactory = (Func<IEnumerator>)v;
IEnumerator coroutine = null;
try
{
coroutine = coroutineFactory();
}
catch (Exception ex)
{
exception = ex;
}
if (exception == null)
{
yield return StartCoroutine(UnwrapEnumerator(coroutine, ex =>
{
exception = ex;
}));
}
}
methodStopwatch.Stop();
totalExecutionTime.Add(methodStopwatch.Elapsed.TotalMilliseconds);
if (exception == null)
{
logText.text += "OK, " + methodStopwatch.Elapsed.TotalMilliseconds.ToString("0.00") + "ms\n";
WriteToConsoleResult(item2.Key + ", " + methodStopwatch.Elapsed.TotalMilliseconds.ToString("0.00") + "ms", true);
}
else
{
// found match line...
var line = string.Join("\n", exception.StackTrace.Split('\n').Where(x => x.Contains(actionList.Key) || x.Contains(item2.Key)).ToArray());
logText.text += "<color=red>" + exception.Message + "\n" + line + "</color>\n";
WriteToConsoleResult(item2.Key + ", " + exception.Message, false);
WriteToConsole(line);
allGreen = false;
allTestGreen = false;
}
}
logText.text += "[" + actionList.Key + "]" + totalExecutionTime.Sum().ToString("0.00") + "ms\n\n";
foreach (var btn in list.GetComponentsInChildren<Button>()) btn.interactable = true;
if (self != null)
{
self.GetComponent<Image>().color = allGreen ? passColor : failColor;
}
yield return StartCoroutine(ScrollLogToEndNextFrame());
}
IEnumerator ExecuteAllInCoroutine(List<Func<Coroutine>> tests)
{
allTestGreen = true;
foreach (var item in tests)
{
yield return item();
}
if (Application.isBatchMode)
{
var scene = UnityEngine.SceneManagement.SceneManager.GetActiveScene();
bool disableAutoClose = (scene.name.Contains("DisableAutoClose"));
if (allTestGreen)
{
WriteToConsole("Test Complete Successfully");
if (!disableAutoClose)
{
Application.Quit();
}
}
else
{
WriteToConsole("Test Failed, please see [NG] log.");
if (!disableAutoClose)
{
Application.Quit(1);
}
}
}
}
IEnumerator UnwrapEnumerator(IEnumerator enumerator, Action<Exception> exceptionCallback)
{
var hasNext = true;
while (hasNext)
{
try
{
hasNext = enumerator.MoveNext();
}
catch (Exception ex)
{
exceptionCallback(ex);
hasNext = false;
}
if (hasNext)
{
// unwrap self for bug of Unity
// https://issuetracker.unity3d.com/issues/does-not-stop-coroutine-when-it-throws-exception-in-movenext-at-first-frame
var moreCoroutine = enumerator.Current as IEnumerator;
if (moreCoroutine != null)
{
yield return StartCoroutine(UnwrapEnumerator(moreCoroutine, ex =>
{
exceptionCallback(ex);
hasNext = false;
}));
}
else
{
yield return enumerator.Current;
}
}
}
}
static void WriteToConsole(string msg)
{
if (Application.isBatchMode)
{
Console.WriteLine(msg);
}
}
static void WriteToConsoleResult(string msg, bool green)
{
if (Application.isBatchMode)
{
if (!green)
{
var currentForeground = Console.ForegroundColor;
Console.ForegroundColor = ConsoleColor.Red;
Console.Write("[NG]");
Console.ForegroundColor = currentForeground;
}
else
{
var currentForeground = Console.ForegroundColor;
Console.ForegroundColor = ConsoleColor.Green;
Console.Write("[OK]");
Console.ForegroundColor = currentForeground;
}
System.Console.WriteLine(msg);
}
}
struct Pair
{
public string Name;
public UnityAction Action;
}
}
}

View File

@@ -0,0 +1,12 @@
fileFormatVersion: 2
guid: 660baed073888b8438569f57e42679b2
timeCreated: 1476793308
licenseType: Pro
MonoImporter:
serializedVersion: 2
defaultReferences: []
executionOrder: 0
icon: {instanceID: 0}
userData:
assetBundleName:
assetBundleVariant:

View File

@@ -0,0 +1,11 @@
{
"name": "jp.cysharp.runtimeunittesttoolkit",
"displayName": "RuntimeUnitTestToolkit",
"version": "2.0.0",
"unity": "2018.3",
"description": "CLI/GUI Frontend of Unity Test Runner to test on any platform.",
"keywords": ["test"],
"category": "Tests",
"dependencies": {
}
}

View File

@@ -0,0 +1,7 @@
fileFormatVersion: 2
guid: b7883c7ac5d6ea4409a229aeab14e796
TextScriptImporter:
externalObjects: {}
userData:
assetBundleName:
assetBundleVariant: