From a84706f2e148d51d0f2785411640748826862ccb Mon Sep 17 00:00:00 2001 From: chrisgregan Date: Thu, 6 Aug 2015 12:16:44 +0100 Subject: [PATCH] Added Unity Test Tools asset The test framework and tests will not be included in the published Fungus unitypackage. --- Assets/UnityTestTools.meta | 9 + Assets/UnityTestTools/Assertions.meta | 5 + .../Assertions/AssertionComponent.cs | 379 ++++++++++++ .../Assertions/AssertionComponent.cs.meta | 8 + .../Assertions/AssertionException.cs | 24 + .../Assertions/AssertionException.cs.meta | 8 + .../UnityTestTools/Assertions/Assertions.cs | 42 ++ .../Assertions/Assertions.cs.meta | 8 + .../UnityTestTools/Assertions/CheckMethod.cs | 36 ++ .../Assertions/CheckMethod.cs.meta | 8 + .../UnityTestTools/Assertions/Comparers.meta | 5 + .../Assertions/Comparers/ActionBase.cs | 121 ++++ .../Assertions/Comparers/ActionBase.cs.meta | 8 + .../Assertions/Comparers/BoolComparer.cs | 14 + .../Assertions/Comparers/BoolComparer.cs.meta | 8 + .../Assertions/Comparers/ColliderComparer.cs | 29 + .../Comparers/ColliderComparer.cs.meta | 8 + .../Assertions/Comparers/ComparerBase.cs | 145 +++++ .../Assertions/Comparers/ComparerBase.cs.meta | 8 + .../Assertions/Comparers/FloatComparer.cs | 40 ++ .../Comparers/FloatComparer.cs.meta | 8 + .../Assertions/Comparers/GeneralComparer.cs | 22 + .../Comparers/GeneralComparer.cs.meta | 8 + .../Assertions/Comparers/IntComparer.cs | 41 ++ .../Assertions/Comparers/IntComparer.cs.meta | 8 + .../Comparers/IsRenderedByCamera.cs | 31 + .../Comparers/IsRenderedByCamera.cs.meta | 8 + .../Assertions/Comparers/StringComparer.cs | 42 ++ .../Comparers/StringComparer.cs.meta | 8 + .../Assertions/Comparers/TransformComparer.cs | 26 + .../Comparers/TransformComparer.cs.meta | 8 + .../Comparers/ValueDoesNotChange.cs | 20 + .../Comparers/ValueDoesNotChange.cs.meta | 8 + .../Assertions/Comparers/Vector2Comparer.cs | 36 ++ .../Comparers/Vector2Comparer.cs.meta | 8 + .../Assertions/Comparers/Vector3Comparer.cs | 32 + .../Comparers/Vector3Comparer.cs.meta | 8 + .../Assertions/Comparers/Vector4Comparer.cs | 38 ++ .../Comparers/Vector4Comparer.cs.meta | 8 + .../Comparers/VectorComparerBase.cs | 18 + .../Comparers/VectorComparerBase.cs.meta | 8 + Assets/UnityTestTools/Assertions/Editor.meta | 5 + .../Editor/AssertionComponentEditor.cs | 212 +++++++ .../Editor/AssertionComponentEditor.cs.meta | 8 + .../Editor/AssertionExplorerWindow.cs | 194 ++++++ .../Editor/AssertionExplorerWindow.cs.meta | 8 + .../Editor/AssertionListRenderer.cs | 251 ++++++++ .../Editor/AssertionListRenderer.cs.meta | 8 + .../Assertions/Editor/AssertionStripper.cs | 25 + .../Editor/AssertionStripper.cs.meta | 8 + .../Assertions/Editor/DropDownControl.cs | 78 +++ .../Assertions/Editor/DropDownControl.cs.meta | 8 + .../Editor/GroupByComparerRenderer.cs | 20 + .../Editor/GroupByComparerRenderer.cs.meta | 8 + .../Editor/GroupByExecutionMethodRenderer.cs | 31 + .../GroupByExecutionMethodRenderer.cs.meta | 8 + .../Assertions/Editor/GroupByGORenderer.cs | 34 + .../Editor/GroupByGORenderer.cs.meta | 8 + .../Editor/GroupByNothingRenderer.cs | 20 + .../Editor/GroupByNothingRenderer.cs.meta | 8 + .../Assertions/Editor/GroupByTestsRenderer.cs | 29 + .../Editor/GroupByTestsRenderer.cs.meta | 8 + .../Assertions/Editor/PropertyPathSelector.cs | 207 +++++++ .../Editor/PropertyPathSelector.cs.meta | 8 + .../Assertions/Editor/PropertyResolver.cs | 188 ++++++ .../Editor/PropertyResolver.cs.meta | 8 + .../Assertions/InvalidPathException.cs | 14 + .../Assertions/InvalidPathException.cs.meta | 8 + .../Assertions/MemberResolver.cs | 208 +++++++ .../Assertions/MemberResolver.cs.meta | 8 + Assets/UnityTestTools/Common.meta | 5 + Assets/UnityTestTools/Common/Editor.meta | 4 + Assets/UnityTestTools/Common/Editor/Icons.cs | 57 ++ .../Common/Editor/Icons.cs.meta | 8 + .../Common/Editor/ProjectSettingsBase.cs | 39 ++ .../Common/Editor/ProjectSettingsBase.cs.meta | 8 + .../Common/Editor/ResultWriter.meta | 5 + .../Editor/ResultWriter/ResultSummarizer.cs | 173 ++++++ .../ResultWriter/ResultSummarizer.cs.meta | 8 + .../Editor/ResultWriter/StackTraceFilter.cs | 62 ++ .../ResultWriter/StackTraceFilter.cs.meta | 8 + .../Editor/ResultWriter/XmlResultWriter.cs | 303 +++++++++ .../ResultWriter/XmlResultWriter.cs.meta | 8 + Assets/UnityTestTools/Common/Editor/Styles.cs | 47 ++ .../Common/Editor/Styles.cs.meta | 8 + .../Common/Editor/TestFilterSettings.cs | 104 ++++ .../Common/Editor/TestFilterSettings.cs.meta | 8 + .../UnityTestTools/Common/Editor/icons.meta | 4 + .../Common/Editor/icons/failed.png | Bin 0 -> 1041 bytes .../Common/Editor/icons/failed.png.meta | 35 ++ .../Common/Editor/icons/ignored.png | Bin 0 -> 1041 bytes .../Common/Editor/icons/ignored.png.meta | 35 ++ .../Common/Editor/icons/inconclusive.png | Bin 0 -> 1041 bytes .../Common/Editor/icons/inconclusive.png.meta | 35 ++ .../Common/Editor/icons/normal.png | Bin 0 -> 1041 bytes .../Common/Editor/icons/normal.png.meta | 35 ++ .../Common/Editor/icons/passed.png | Bin 0 -> 1041 bytes .../Common/Editor/icons/passed.png.meta | 35 ++ .../Common/Editor/icons/stopwatch.png | Bin 0 -> 1041 bytes .../Common/Editor/icons/stopwatch.png.meta | 35 ++ Assets/UnityTestTools/Common/ITestResult.cs | 29 + .../UnityTestTools/Common/ITestResult.cs.meta | 8 + Assets/UnityTestTools/Common/Settings.meta | 9 + .../IntegrationTestsRunnerSettings.asset | 15 + .../IntegrationTestsRunnerSettings.asset.meta | 8 + .../Settings/UnitTestsRunnerSettings.asset | 17 + .../UnitTestsRunnerSettings.asset.meta | 8 + .../UnityTestTools/Common/TestResultState.cs | 46 ++ .../Common/TestResultState.cs.meta | 8 + Assets/UnityTestTools/Documentation.url | 3 + Assets/UnityTestTools/Documentation.url.meta | 4 + .../IntegrationTestsFramework.meta | 5 + .../IntegrationTestsFramework/TestRunner.meta | 5 + .../TestRunner/DTOFormatter.cs | 131 ++++ .../TestRunner/DTOFormatter.cs.meta | 8 + .../TestRunner/Editor.meta | 5 + .../TestRunner/Editor/Batch.cs | 166 +++++ .../TestRunner/Editor/Batch.cs.meta | 8 + .../IntegrationTestsHierarchyAnnotation.cs | 44 ++ ...ntegrationTestsHierarchyAnnotation.cs.meta | 8 + .../Editor/IntegrationTestsRunnerSettings.cs | 25 + .../IntegrationTestsRunnerSettings.cs.meta | 8 + .../Editor/IntegrationTestsRunnerWindow.cs | 583 ++++++++++++++++++ .../IntegrationTestsRunnerWindow.cs.meta | 8 + .../TestRunner/Editor/PlatformRunner.meta | 4 + .../PlatformRunner/NetworkResultsReceiver.cs | 262 ++++++++ .../NetworkResultsReceiver.cs.meta | 8 + .../Editor/PlatformRunner/PlatformRunner.cs | 127 ++++ .../PlatformRunner/PlatformRunner.cs.meta | 8 + .../PlatformRunnerConfiguration.cs | 77 +++ .../PlatformRunnerConfiguration.cs.meta | 8 + .../PlatformRunner/PlatformRunnerSettings.cs | 13 + .../PlatformRunnerSettings.cs.meta | 8 + .../PlatformRunnerSettingsWindow.cs | 163 +++++ .../PlatformRunnerSettingsWindow.cs.meta | 8 + .../PlayerSettingConfigurator.cs | 73 +++ .../PlayerSettingConfigurator.cs.meta | 8 + .../TestRunner/Editor/Renderer.meta | 4 + .../Renderer/IntegrationTestGroupLine.cs | 84 +++ .../Renderer/IntegrationTestGroupLine.cs.meta | 8 + .../Editor/Renderer/IntegrationTestLine.cs | 57 ++ .../Renderer/IntegrationTestLine.cs.meta | 8 + .../Renderer/IntegrationTestRendererBase.cs | 161 +++++ .../IntegrationTestRendererBase.cs.meta | 8 + .../TestRunner/Editor/TestComponentEditor.cs | 128 ++++ .../Editor/TestComponentEditor.cs.meta | 8 + .../TestRunner/ITestRunnerCallback.cs | 15 + .../TestRunner/ITestRunnerCallback.cs.meta | 8 + .../TestRunner/IntegrationTest.cs | 176 ++++++ .../TestRunner/IntegrationTest.cs.meta | 8 + .../TestRunner/IntegrationTestAttribute.cs | 24 + .../IntegrationTestAttribute.cs.meta | 8 + .../TestRunner/IntegrationTestsProvider.cs | 109 ++++ .../IntegrationTestsProvider.cs.meta | 8 + .../TestRunner/NetworkResultSender.cs | 107 ++++ .../TestRunner/NetworkResultSender.cs.meta | 8 + .../TestRunner/ResultDTO.cs | 160 +++++ .../TestRunner/ResultDTO.cs.meta | 8 + .../TestRunner/TestComponent.cs | 411 ++++++++++++ .../TestRunner/TestComponent.cs.meta | 8 + .../TestRunner/TestResult.cs | 141 +++++ .../TestRunner/TestResult.cs.meta | 8 + .../TestRunner/TestResultRenderer.cs | 83 +++ .../TestRunner/TestResultRenderer.cs.meta | 8 + .../TestRunner/TestRunner.cs | 415 +++++++++++++ .../TestRunner/TestRunner.cs.meta | 8 + .../TestRunner/TestRunnerCallbackList.cs | 61 ++ .../TestRunner/TestRunnerCallbackList.cs.meta | 8 + .../TestRunner/TestRunnerConfigurator.cs | 176 ++++++ .../TestRunner/TestRunnerConfigurator.cs.meta | 8 + .../TestingAssets.meta | 5 + .../TestingAssets/CallTesting.cs | 204 ++++++ .../TestingAssets/CallTesting.cs.meta | 8 + .../TestingAssets/CubeCollisionFailure.prefab | 102 +++ .../CubeCollisionFailure.prefab.meta | 4 + .../TestingAssets/CubeCollisionSuccess.prefab | 102 +++ .../CubeCollisionSuccess.prefab.meta | 4 + .../TestingAssets/CubeTriggerFailure.prefab | 102 +++ .../CubeTriggerFailure.prefab.meta | 4 + .../TestingAssets/CubeTriggerSuccess.prefab | 102 +++ .../CubeTriggerSuccess.prefab.meta | 4 + .../TestingAssets/Materials.meta | 5 + .../TestingAssets/Materials/green.mat | 30 + .../TestingAssets/Materials/green.mat.meta | 4 + .../TestingAssets/Materials/red.mat | 30 + .../TestingAssets/Materials/red.mat.meta | 4 + .../TestingAssets/green.png | Bin 0 -> 140 bytes .../TestingAssets/green.png.meta | 53 ++ .../TestingAssets/red.png | Bin 0 -> 139 bytes .../TestingAssets/red.png.meta | 53 ++ Assets/UnityTestTools/LICENSE.txt | 83 +++ Assets/UnityTestTools/LICENSE.txt.meta | 4 + Assets/UnityTestTools/UnitTesting.meta | 5 + Assets/UnityTestTools/UnitTesting/Editor.meta | 4 + .../UnitTesting/Editor/Batch.cs | 123 ++++ .../UnitTesting/Editor/Batch.cs.meta | 8 + .../UnitTesting/Editor/NSubstitute.meta | 5 + .../Editor/NSubstitute/NSubstitute.dll | Bin 0 -> 327680 bytes .../Editor/NSubstitute/NSubstitute.dll.meta | 7 + .../UnitTesting/Editor/NUnit.meta | 5 + .../UnitTesting/Editor/NUnit/Libs.meta | 5 + .../Editor/NUnit/Libs/Mono.Cecil.Mdb.dll | Bin 0 -> 39424 bytes .../Editor/NUnit/Libs/Mono.Cecil.Mdb.dll.meta | 7 + .../Editor/NUnit/Libs/Mono.Cecil.dll | Bin 0 -> 280576 bytes .../Editor/NUnit/Libs/Mono.Cecil.dll.meta | 7 + .../Editor/NUnit/Libs/nunit.core.dll | Bin 0 -> 139264 bytes .../Editor/NUnit/Libs/nunit.core.dll.meta | 7 + .../NUnit/Libs/nunit.core.interfaces.dll | Bin 0 -> 53248 bytes .../NUnit/Libs/nunit.core.interfaces.dll.meta | 7 + .../Editor/NUnit/Libs/nunit.framework.dll | Bin 0 -> 131072 bytes .../NUnit/Libs/nunit.framework.dll.meta | 7 + .../UnitTesting/Editor/NUnit/Renderer.meta | 4 + .../Editor/NUnit/Renderer/GroupLine.cs | 156 +++++ .../Editor/NUnit/Renderer/GroupLine.cs.meta | 8 + .../Editor/NUnit/Renderer/RenderingOptions.cs | 16 + .../NUnit/Renderer/RenderingOptions.cs.meta | 8 + .../Editor/NUnit/Renderer/TestLine.cs | 182 ++++++ .../Editor/NUnit/Renderer/TestLine.cs.meta | 8 + .../NUnit/Renderer/UnitTestRendererLine.cs | 104 ++++ .../Renderer/UnitTestRendererLine.cs.meta | 8 + .../UnitTesting/Editor/TestRunner.meta | 5 + .../Editor/TestRunner/GuiHelper.cs | 147 +++++ .../Editor/TestRunner/GuiHelper.cs.meta | 8 + .../Editor/TestRunner/ITestRunnerCallback.cs | 15 + .../TestRunner/ITestRunnerCallback.cs.meta | 8 + .../Editor/TestRunner/IUnitTestEngine.cs | 13 + .../Editor/TestRunner/IUnitTestEngine.cs.meta | 8 + .../Editor/TestRunner/NUnitExtensions.cs | 24 + .../Editor/TestRunner/NUnitExtensions.cs.meta | 8 + .../Editor/TestRunner/NUnitTestEngine.cs | 211 +++++++ .../Editor/TestRunner/NUnitTestEngine.cs.meta | 8 + .../Editor/TestRunner/TestRunner.cs | 150 +++++ .../Editor/TestRunner/TestRunner.cs.meta | 8 + .../TestRunner/TestRunnerCallbackList.cs | 62 ++ .../TestRunner/TestRunnerCallbackList.cs.meta | 8 + .../Editor/TestRunner/UnitTestInfo.cs | 104 ++++ .../Editor/TestRunner/UnitTestInfo.cs.meta | 8 + .../Editor/TestRunner/UnitTestResult.cs | 61 ++ .../Editor/TestRunner/UnitTestResult.cs.meta | 8 + .../Editor/TestRunner/UnitTestView.cs | 227 +++++++ .../Editor/TestRunner/UnitTestView.cs.meta | 8 + .../Editor/UnitTestsRunnerSettings.cs | 30 + .../Editor/UnitTestsRunnerSettings.cs.meta | 8 + .../UnitTesting/Editor/UnityUnitTest.cs | 28 + .../UnitTesting/Editor/UnityUnitTest.cs.meta | 8 + Assets/UnityTestTools/changelog.txt | 198 ++++++ Assets/UnityTestTools/changelog.txt.meta | 4 + 247 files changed, 11343 insertions(+) create mode 100644 Assets/UnityTestTools.meta create mode 100644 Assets/UnityTestTools/Assertions.meta create mode 100644 Assets/UnityTestTools/Assertions/AssertionComponent.cs create mode 100644 Assets/UnityTestTools/Assertions/AssertionComponent.cs.meta create mode 100644 Assets/UnityTestTools/Assertions/AssertionException.cs create mode 100644 Assets/UnityTestTools/Assertions/AssertionException.cs.meta create mode 100644 Assets/UnityTestTools/Assertions/Assertions.cs create mode 100644 Assets/UnityTestTools/Assertions/Assertions.cs.meta create mode 100644 Assets/UnityTestTools/Assertions/CheckMethod.cs create mode 100644 Assets/UnityTestTools/Assertions/CheckMethod.cs.meta create mode 100644 Assets/UnityTestTools/Assertions/Comparers.meta create mode 100644 Assets/UnityTestTools/Assertions/Comparers/ActionBase.cs create mode 100644 Assets/UnityTestTools/Assertions/Comparers/ActionBase.cs.meta create mode 100644 Assets/UnityTestTools/Assertions/Comparers/BoolComparer.cs create mode 100644 Assets/UnityTestTools/Assertions/Comparers/BoolComparer.cs.meta create mode 100644 Assets/UnityTestTools/Assertions/Comparers/ColliderComparer.cs create mode 100644 Assets/UnityTestTools/Assertions/Comparers/ColliderComparer.cs.meta create mode 100644 Assets/UnityTestTools/Assertions/Comparers/ComparerBase.cs create mode 100644 Assets/UnityTestTools/Assertions/Comparers/ComparerBase.cs.meta create mode 100644 Assets/UnityTestTools/Assertions/Comparers/FloatComparer.cs create mode 100644 Assets/UnityTestTools/Assertions/Comparers/FloatComparer.cs.meta create mode 100644 Assets/UnityTestTools/Assertions/Comparers/GeneralComparer.cs create mode 100644 Assets/UnityTestTools/Assertions/Comparers/GeneralComparer.cs.meta create mode 100644 Assets/UnityTestTools/Assertions/Comparers/IntComparer.cs create mode 100644 Assets/UnityTestTools/Assertions/Comparers/IntComparer.cs.meta create mode 100644 Assets/UnityTestTools/Assertions/Comparers/IsRenderedByCamera.cs create mode 100644 Assets/UnityTestTools/Assertions/Comparers/IsRenderedByCamera.cs.meta create mode 100644 Assets/UnityTestTools/Assertions/Comparers/StringComparer.cs create mode 100644 Assets/UnityTestTools/Assertions/Comparers/StringComparer.cs.meta create mode 100644 Assets/UnityTestTools/Assertions/Comparers/TransformComparer.cs create mode 100644 Assets/UnityTestTools/Assertions/Comparers/TransformComparer.cs.meta create mode 100644 Assets/UnityTestTools/Assertions/Comparers/ValueDoesNotChange.cs create mode 100644 Assets/UnityTestTools/Assertions/Comparers/ValueDoesNotChange.cs.meta create mode 100644 Assets/UnityTestTools/Assertions/Comparers/Vector2Comparer.cs create mode 100644 Assets/UnityTestTools/Assertions/Comparers/Vector2Comparer.cs.meta create mode 100644 Assets/UnityTestTools/Assertions/Comparers/Vector3Comparer.cs create mode 100644 Assets/UnityTestTools/Assertions/Comparers/Vector3Comparer.cs.meta create mode 100644 Assets/UnityTestTools/Assertions/Comparers/Vector4Comparer.cs create mode 100644 Assets/UnityTestTools/Assertions/Comparers/Vector4Comparer.cs.meta create mode 100644 Assets/UnityTestTools/Assertions/Comparers/VectorComparerBase.cs create mode 100644 Assets/UnityTestTools/Assertions/Comparers/VectorComparerBase.cs.meta create mode 100644 Assets/UnityTestTools/Assertions/Editor.meta create mode 100644 Assets/UnityTestTools/Assertions/Editor/AssertionComponentEditor.cs create mode 100644 Assets/UnityTestTools/Assertions/Editor/AssertionComponentEditor.cs.meta create mode 100644 Assets/UnityTestTools/Assertions/Editor/AssertionExplorerWindow.cs create mode 100644 Assets/UnityTestTools/Assertions/Editor/AssertionExplorerWindow.cs.meta create mode 100644 Assets/UnityTestTools/Assertions/Editor/AssertionListRenderer.cs create mode 100644 Assets/UnityTestTools/Assertions/Editor/AssertionListRenderer.cs.meta create mode 100644 Assets/UnityTestTools/Assertions/Editor/AssertionStripper.cs create mode 100644 Assets/UnityTestTools/Assertions/Editor/AssertionStripper.cs.meta create mode 100644 Assets/UnityTestTools/Assertions/Editor/DropDownControl.cs create mode 100644 Assets/UnityTestTools/Assertions/Editor/DropDownControl.cs.meta create mode 100644 Assets/UnityTestTools/Assertions/Editor/GroupByComparerRenderer.cs create mode 100644 Assets/UnityTestTools/Assertions/Editor/GroupByComparerRenderer.cs.meta create mode 100644 Assets/UnityTestTools/Assertions/Editor/GroupByExecutionMethodRenderer.cs create mode 100644 Assets/UnityTestTools/Assertions/Editor/GroupByExecutionMethodRenderer.cs.meta create mode 100644 Assets/UnityTestTools/Assertions/Editor/GroupByGORenderer.cs create mode 100644 Assets/UnityTestTools/Assertions/Editor/GroupByGORenderer.cs.meta create mode 100644 Assets/UnityTestTools/Assertions/Editor/GroupByNothingRenderer.cs create mode 100644 Assets/UnityTestTools/Assertions/Editor/GroupByNothingRenderer.cs.meta create mode 100644 Assets/UnityTestTools/Assertions/Editor/GroupByTestsRenderer.cs create mode 100644 Assets/UnityTestTools/Assertions/Editor/GroupByTestsRenderer.cs.meta create mode 100644 Assets/UnityTestTools/Assertions/Editor/PropertyPathSelector.cs create mode 100644 Assets/UnityTestTools/Assertions/Editor/PropertyPathSelector.cs.meta create mode 100644 Assets/UnityTestTools/Assertions/Editor/PropertyResolver.cs create mode 100644 Assets/UnityTestTools/Assertions/Editor/PropertyResolver.cs.meta create mode 100644 Assets/UnityTestTools/Assertions/InvalidPathException.cs create mode 100644 Assets/UnityTestTools/Assertions/InvalidPathException.cs.meta create mode 100644 Assets/UnityTestTools/Assertions/MemberResolver.cs create mode 100644 Assets/UnityTestTools/Assertions/MemberResolver.cs.meta create mode 100644 Assets/UnityTestTools/Common.meta create mode 100644 Assets/UnityTestTools/Common/Editor.meta create mode 100644 Assets/UnityTestTools/Common/Editor/Icons.cs create mode 100644 Assets/UnityTestTools/Common/Editor/Icons.cs.meta create mode 100644 Assets/UnityTestTools/Common/Editor/ProjectSettingsBase.cs create mode 100644 Assets/UnityTestTools/Common/Editor/ProjectSettingsBase.cs.meta create mode 100644 Assets/UnityTestTools/Common/Editor/ResultWriter.meta create mode 100644 Assets/UnityTestTools/Common/Editor/ResultWriter/ResultSummarizer.cs create mode 100644 Assets/UnityTestTools/Common/Editor/ResultWriter/ResultSummarizer.cs.meta create mode 100644 Assets/UnityTestTools/Common/Editor/ResultWriter/StackTraceFilter.cs create mode 100644 Assets/UnityTestTools/Common/Editor/ResultWriter/StackTraceFilter.cs.meta create mode 100644 Assets/UnityTestTools/Common/Editor/ResultWriter/XmlResultWriter.cs create mode 100644 Assets/UnityTestTools/Common/Editor/ResultWriter/XmlResultWriter.cs.meta create mode 100644 Assets/UnityTestTools/Common/Editor/Styles.cs create mode 100644 Assets/UnityTestTools/Common/Editor/Styles.cs.meta create mode 100644 Assets/UnityTestTools/Common/Editor/TestFilterSettings.cs create mode 100644 Assets/UnityTestTools/Common/Editor/TestFilterSettings.cs.meta create mode 100644 Assets/UnityTestTools/Common/Editor/icons.meta create mode 100644 Assets/UnityTestTools/Common/Editor/icons/failed.png create mode 100644 Assets/UnityTestTools/Common/Editor/icons/failed.png.meta create mode 100644 Assets/UnityTestTools/Common/Editor/icons/ignored.png create mode 100644 Assets/UnityTestTools/Common/Editor/icons/ignored.png.meta create mode 100644 Assets/UnityTestTools/Common/Editor/icons/inconclusive.png create mode 100644 Assets/UnityTestTools/Common/Editor/icons/inconclusive.png.meta create mode 100644 Assets/UnityTestTools/Common/Editor/icons/normal.png create mode 100644 Assets/UnityTestTools/Common/Editor/icons/normal.png.meta create mode 100644 Assets/UnityTestTools/Common/Editor/icons/passed.png create mode 100644 Assets/UnityTestTools/Common/Editor/icons/passed.png.meta create mode 100644 Assets/UnityTestTools/Common/Editor/icons/stopwatch.png create mode 100644 Assets/UnityTestTools/Common/Editor/icons/stopwatch.png.meta create mode 100644 Assets/UnityTestTools/Common/ITestResult.cs create mode 100644 Assets/UnityTestTools/Common/ITestResult.cs.meta create mode 100644 Assets/UnityTestTools/Common/Settings.meta create mode 100644 Assets/UnityTestTools/Common/Settings/IntegrationTestsRunnerSettings.asset create mode 100644 Assets/UnityTestTools/Common/Settings/IntegrationTestsRunnerSettings.asset.meta create mode 100644 Assets/UnityTestTools/Common/Settings/UnitTestsRunnerSettings.asset create mode 100644 Assets/UnityTestTools/Common/Settings/UnitTestsRunnerSettings.asset.meta create mode 100644 Assets/UnityTestTools/Common/TestResultState.cs create mode 100644 Assets/UnityTestTools/Common/TestResultState.cs.meta create mode 100644 Assets/UnityTestTools/Documentation.url create mode 100644 Assets/UnityTestTools/Documentation.url.meta create mode 100644 Assets/UnityTestTools/IntegrationTestsFramework.meta create mode 100644 Assets/UnityTestTools/IntegrationTestsFramework/TestRunner.meta create mode 100644 Assets/UnityTestTools/IntegrationTestsFramework/TestRunner/DTOFormatter.cs create mode 100644 Assets/UnityTestTools/IntegrationTestsFramework/TestRunner/DTOFormatter.cs.meta create mode 100644 Assets/UnityTestTools/IntegrationTestsFramework/TestRunner/Editor.meta create mode 100644 Assets/UnityTestTools/IntegrationTestsFramework/TestRunner/Editor/Batch.cs create mode 100644 Assets/UnityTestTools/IntegrationTestsFramework/TestRunner/Editor/Batch.cs.meta create mode 100644 Assets/UnityTestTools/IntegrationTestsFramework/TestRunner/Editor/IntegrationTestsHierarchyAnnotation.cs create mode 100644 Assets/UnityTestTools/IntegrationTestsFramework/TestRunner/Editor/IntegrationTestsHierarchyAnnotation.cs.meta create mode 100644 Assets/UnityTestTools/IntegrationTestsFramework/TestRunner/Editor/IntegrationTestsRunnerSettings.cs create mode 100644 Assets/UnityTestTools/IntegrationTestsFramework/TestRunner/Editor/IntegrationTestsRunnerSettings.cs.meta create mode 100644 Assets/UnityTestTools/IntegrationTestsFramework/TestRunner/Editor/IntegrationTestsRunnerWindow.cs create mode 100644 Assets/UnityTestTools/IntegrationTestsFramework/TestRunner/Editor/IntegrationTestsRunnerWindow.cs.meta create mode 100644 Assets/UnityTestTools/IntegrationTestsFramework/TestRunner/Editor/PlatformRunner.meta create mode 100644 Assets/UnityTestTools/IntegrationTestsFramework/TestRunner/Editor/PlatformRunner/NetworkResultsReceiver.cs create mode 100644 Assets/UnityTestTools/IntegrationTestsFramework/TestRunner/Editor/PlatformRunner/NetworkResultsReceiver.cs.meta create mode 100644 Assets/UnityTestTools/IntegrationTestsFramework/TestRunner/Editor/PlatformRunner/PlatformRunner.cs create mode 100644 Assets/UnityTestTools/IntegrationTestsFramework/TestRunner/Editor/PlatformRunner/PlatformRunner.cs.meta create mode 100644 Assets/UnityTestTools/IntegrationTestsFramework/TestRunner/Editor/PlatformRunner/PlatformRunnerConfiguration.cs create mode 100644 Assets/UnityTestTools/IntegrationTestsFramework/TestRunner/Editor/PlatformRunner/PlatformRunnerConfiguration.cs.meta create mode 100644 Assets/UnityTestTools/IntegrationTestsFramework/TestRunner/Editor/PlatformRunner/PlatformRunnerSettings.cs create mode 100644 Assets/UnityTestTools/IntegrationTestsFramework/TestRunner/Editor/PlatformRunner/PlatformRunnerSettings.cs.meta create mode 100644 Assets/UnityTestTools/IntegrationTestsFramework/TestRunner/Editor/PlatformRunner/PlatformRunnerSettingsWindow.cs create mode 100644 Assets/UnityTestTools/IntegrationTestsFramework/TestRunner/Editor/PlatformRunner/PlatformRunnerSettingsWindow.cs.meta create mode 100644 Assets/UnityTestTools/IntegrationTestsFramework/TestRunner/Editor/PlatformRunner/PlayerSettingConfigurator.cs create mode 100644 Assets/UnityTestTools/IntegrationTestsFramework/TestRunner/Editor/PlatformRunner/PlayerSettingConfigurator.cs.meta create mode 100644 Assets/UnityTestTools/IntegrationTestsFramework/TestRunner/Editor/Renderer.meta create mode 100644 Assets/UnityTestTools/IntegrationTestsFramework/TestRunner/Editor/Renderer/IntegrationTestGroupLine.cs create mode 100644 Assets/UnityTestTools/IntegrationTestsFramework/TestRunner/Editor/Renderer/IntegrationTestGroupLine.cs.meta create mode 100644 Assets/UnityTestTools/IntegrationTestsFramework/TestRunner/Editor/Renderer/IntegrationTestLine.cs create mode 100644 Assets/UnityTestTools/IntegrationTestsFramework/TestRunner/Editor/Renderer/IntegrationTestLine.cs.meta create mode 100644 Assets/UnityTestTools/IntegrationTestsFramework/TestRunner/Editor/Renderer/IntegrationTestRendererBase.cs create mode 100644 Assets/UnityTestTools/IntegrationTestsFramework/TestRunner/Editor/Renderer/IntegrationTestRendererBase.cs.meta create mode 100644 Assets/UnityTestTools/IntegrationTestsFramework/TestRunner/Editor/TestComponentEditor.cs create mode 100644 Assets/UnityTestTools/IntegrationTestsFramework/TestRunner/Editor/TestComponentEditor.cs.meta create mode 100644 Assets/UnityTestTools/IntegrationTestsFramework/TestRunner/ITestRunnerCallback.cs create mode 100644 Assets/UnityTestTools/IntegrationTestsFramework/TestRunner/ITestRunnerCallback.cs.meta create mode 100644 Assets/UnityTestTools/IntegrationTestsFramework/TestRunner/IntegrationTest.cs create mode 100644 Assets/UnityTestTools/IntegrationTestsFramework/TestRunner/IntegrationTest.cs.meta create mode 100644 Assets/UnityTestTools/IntegrationTestsFramework/TestRunner/IntegrationTestAttribute.cs create mode 100644 Assets/UnityTestTools/IntegrationTestsFramework/TestRunner/IntegrationTestAttribute.cs.meta create mode 100644 Assets/UnityTestTools/IntegrationTestsFramework/TestRunner/IntegrationTestsProvider.cs create mode 100644 Assets/UnityTestTools/IntegrationTestsFramework/TestRunner/IntegrationTestsProvider.cs.meta create mode 100644 Assets/UnityTestTools/IntegrationTestsFramework/TestRunner/NetworkResultSender.cs create mode 100644 Assets/UnityTestTools/IntegrationTestsFramework/TestRunner/NetworkResultSender.cs.meta create mode 100644 Assets/UnityTestTools/IntegrationTestsFramework/TestRunner/ResultDTO.cs create mode 100644 Assets/UnityTestTools/IntegrationTestsFramework/TestRunner/ResultDTO.cs.meta create mode 100644 Assets/UnityTestTools/IntegrationTestsFramework/TestRunner/TestComponent.cs create mode 100644 Assets/UnityTestTools/IntegrationTestsFramework/TestRunner/TestComponent.cs.meta create mode 100644 Assets/UnityTestTools/IntegrationTestsFramework/TestRunner/TestResult.cs create mode 100644 Assets/UnityTestTools/IntegrationTestsFramework/TestRunner/TestResult.cs.meta create mode 100644 Assets/UnityTestTools/IntegrationTestsFramework/TestRunner/TestResultRenderer.cs create mode 100644 Assets/UnityTestTools/IntegrationTestsFramework/TestRunner/TestResultRenderer.cs.meta create mode 100644 Assets/UnityTestTools/IntegrationTestsFramework/TestRunner/TestRunner.cs create mode 100644 Assets/UnityTestTools/IntegrationTestsFramework/TestRunner/TestRunner.cs.meta create mode 100644 Assets/UnityTestTools/IntegrationTestsFramework/TestRunner/TestRunnerCallbackList.cs create mode 100644 Assets/UnityTestTools/IntegrationTestsFramework/TestRunner/TestRunnerCallbackList.cs.meta create mode 100644 Assets/UnityTestTools/IntegrationTestsFramework/TestRunner/TestRunnerConfigurator.cs create mode 100644 Assets/UnityTestTools/IntegrationTestsFramework/TestRunner/TestRunnerConfigurator.cs.meta create mode 100644 Assets/UnityTestTools/IntegrationTestsFramework/TestingAssets.meta create mode 100644 Assets/UnityTestTools/IntegrationTestsFramework/TestingAssets/CallTesting.cs create mode 100644 Assets/UnityTestTools/IntegrationTestsFramework/TestingAssets/CallTesting.cs.meta create mode 100644 Assets/UnityTestTools/IntegrationTestsFramework/TestingAssets/CubeCollisionFailure.prefab create mode 100644 Assets/UnityTestTools/IntegrationTestsFramework/TestingAssets/CubeCollisionFailure.prefab.meta create mode 100644 Assets/UnityTestTools/IntegrationTestsFramework/TestingAssets/CubeCollisionSuccess.prefab create mode 100644 Assets/UnityTestTools/IntegrationTestsFramework/TestingAssets/CubeCollisionSuccess.prefab.meta create mode 100644 Assets/UnityTestTools/IntegrationTestsFramework/TestingAssets/CubeTriggerFailure.prefab create mode 100644 Assets/UnityTestTools/IntegrationTestsFramework/TestingAssets/CubeTriggerFailure.prefab.meta create mode 100644 Assets/UnityTestTools/IntegrationTestsFramework/TestingAssets/CubeTriggerSuccess.prefab create mode 100644 Assets/UnityTestTools/IntegrationTestsFramework/TestingAssets/CubeTriggerSuccess.prefab.meta create mode 100644 Assets/UnityTestTools/IntegrationTestsFramework/TestingAssets/Materials.meta create mode 100644 Assets/UnityTestTools/IntegrationTestsFramework/TestingAssets/Materials/green.mat create mode 100644 Assets/UnityTestTools/IntegrationTestsFramework/TestingAssets/Materials/green.mat.meta create mode 100644 Assets/UnityTestTools/IntegrationTestsFramework/TestingAssets/Materials/red.mat create mode 100644 Assets/UnityTestTools/IntegrationTestsFramework/TestingAssets/Materials/red.mat.meta create mode 100644 Assets/UnityTestTools/IntegrationTestsFramework/TestingAssets/green.png create mode 100644 Assets/UnityTestTools/IntegrationTestsFramework/TestingAssets/green.png.meta create mode 100644 Assets/UnityTestTools/IntegrationTestsFramework/TestingAssets/red.png create mode 100644 Assets/UnityTestTools/IntegrationTestsFramework/TestingAssets/red.png.meta create mode 100644 Assets/UnityTestTools/LICENSE.txt create mode 100644 Assets/UnityTestTools/LICENSE.txt.meta create mode 100644 Assets/UnityTestTools/UnitTesting.meta create mode 100644 Assets/UnityTestTools/UnitTesting/Editor.meta create mode 100644 Assets/UnityTestTools/UnitTesting/Editor/Batch.cs create mode 100644 Assets/UnityTestTools/UnitTesting/Editor/Batch.cs.meta create mode 100644 Assets/UnityTestTools/UnitTesting/Editor/NSubstitute.meta create mode 100644 Assets/UnityTestTools/UnitTesting/Editor/NSubstitute/NSubstitute.dll create mode 100644 Assets/UnityTestTools/UnitTesting/Editor/NSubstitute/NSubstitute.dll.meta create mode 100644 Assets/UnityTestTools/UnitTesting/Editor/NUnit.meta create mode 100644 Assets/UnityTestTools/UnitTesting/Editor/NUnit/Libs.meta create mode 100644 Assets/UnityTestTools/UnitTesting/Editor/NUnit/Libs/Mono.Cecil.Mdb.dll create mode 100644 Assets/UnityTestTools/UnitTesting/Editor/NUnit/Libs/Mono.Cecil.Mdb.dll.meta create mode 100644 Assets/UnityTestTools/UnitTesting/Editor/NUnit/Libs/Mono.Cecil.dll create mode 100644 Assets/UnityTestTools/UnitTesting/Editor/NUnit/Libs/Mono.Cecil.dll.meta create mode 100644 Assets/UnityTestTools/UnitTesting/Editor/NUnit/Libs/nunit.core.dll create mode 100644 Assets/UnityTestTools/UnitTesting/Editor/NUnit/Libs/nunit.core.dll.meta create mode 100644 Assets/UnityTestTools/UnitTesting/Editor/NUnit/Libs/nunit.core.interfaces.dll create mode 100644 Assets/UnityTestTools/UnitTesting/Editor/NUnit/Libs/nunit.core.interfaces.dll.meta create mode 100644 Assets/UnityTestTools/UnitTesting/Editor/NUnit/Libs/nunit.framework.dll create mode 100644 Assets/UnityTestTools/UnitTesting/Editor/NUnit/Libs/nunit.framework.dll.meta create mode 100644 Assets/UnityTestTools/UnitTesting/Editor/NUnit/Renderer.meta create mode 100644 Assets/UnityTestTools/UnitTesting/Editor/NUnit/Renderer/GroupLine.cs create mode 100644 Assets/UnityTestTools/UnitTesting/Editor/NUnit/Renderer/GroupLine.cs.meta create mode 100644 Assets/UnityTestTools/UnitTesting/Editor/NUnit/Renderer/RenderingOptions.cs create mode 100644 Assets/UnityTestTools/UnitTesting/Editor/NUnit/Renderer/RenderingOptions.cs.meta create mode 100644 Assets/UnityTestTools/UnitTesting/Editor/NUnit/Renderer/TestLine.cs create mode 100644 Assets/UnityTestTools/UnitTesting/Editor/NUnit/Renderer/TestLine.cs.meta create mode 100644 Assets/UnityTestTools/UnitTesting/Editor/NUnit/Renderer/UnitTestRendererLine.cs create mode 100644 Assets/UnityTestTools/UnitTesting/Editor/NUnit/Renderer/UnitTestRendererLine.cs.meta create mode 100644 Assets/UnityTestTools/UnitTesting/Editor/TestRunner.meta create mode 100644 Assets/UnityTestTools/UnitTesting/Editor/TestRunner/GuiHelper.cs create mode 100644 Assets/UnityTestTools/UnitTesting/Editor/TestRunner/GuiHelper.cs.meta create mode 100644 Assets/UnityTestTools/UnitTesting/Editor/TestRunner/ITestRunnerCallback.cs create mode 100644 Assets/UnityTestTools/UnitTesting/Editor/TestRunner/ITestRunnerCallback.cs.meta create mode 100644 Assets/UnityTestTools/UnitTesting/Editor/TestRunner/IUnitTestEngine.cs create mode 100644 Assets/UnityTestTools/UnitTesting/Editor/TestRunner/IUnitTestEngine.cs.meta create mode 100644 Assets/UnityTestTools/UnitTesting/Editor/TestRunner/NUnitExtensions.cs create mode 100644 Assets/UnityTestTools/UnitTesting/Editor/TestRunner/NUnitExtensions.cs.meta create mode 100644 Assets/UnityTestTools/UnitTesting/Editor/TestRunner/NUnitTestEngine.cs create mode 100644 Assets/UnityTestTools/UnitTesting/Editor/TestRunner/NUnitTestEngine.cs.meta create mode 100644 Assets/UnityTestTools/UnitTesting/Editor/TestRunner/TestRunner.cs create mode 100644 Assets/UnityTestTools/UnitTesting/Editor/TestRunner/TestRunner.cs.meta create mode 100644 Assets/UnityTestTools/UnitTesting/Editor/TestRunner/TestRunnerCallbackList.cs create mode 100644 Assets/UnityTestTools/UnitTesting/Editor/TestRunner/TestRunnerCallbackList.cs.meta create mode 100644 Assets/UnityTestTools/UnitTesting/Editor/TestRunner/UnitTestInfo.cs create mode 100644 Assets/UnityTestTools/UnitTesting/Editor/TestRunner/UnitTestInfo.cs.meta create mode 100644 Assets/UnityTestTools/UnitTesting/Editor/TestRunner/UnitTestResult.cs create mode 100644 Assets/UnityTestTools/UnitTesting/Editor/TestRunner/UnitTestResult.cs.meta create mode 100644 Assets/UnityTestTools/UnitTesting/Editor/TestRunner/UnitTestView.cs create mode 100644 Assets/UnityTestTools/UnitTesting/Editor/TestRunner/UnitTestView.cs.meta create mode 100644 Assets/UnityTestTools/UnitTesting/Editor/UnitTestsRunnerSettings.cs create mode 100644 Assets/UnityTestTools/UnitTesting/Editor/UnitTestsRunnerSettings.cs.meta create mode 100644 Assets/UnityTestTools/UnitTesting/Editor/UnityUnitTest.cs create mode 100644 Assets/UnityTestTools/UnitTesting/Editor/UnityUnitTest.cs.meta create mode 100644 Assets/UnityTestTools/changelog.txt create mode 100644 Assets/UnityTestTools/changelog.txt.meta 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 0000000000000000000000000000000000000000..7c0aba4c39aadc1b13bce95c85bbd61e2a0aadda GIT binary patch literal 1041 zcmds#KQBXJ6o*e+X{j_FXb4|`fy8X8+@`%1)gTc~B#ptsAi-#{nX8E)Qj=JWHlv{o z5|YMb@+pwi^P|1Fja!?=xq04m-t*_a=RNlx6bsozd?apW2{-2~pp&9E$~xVs=26kL z*{owtJ++&Q3ZA~L+{(6D|9}YD@#!$m*p54wiM@nV$)VA`)$v`k$eHV;m&Q_|N@Vkc zjDIDMW+kY@TMOAG|0TozP1`1^id}l7Jb_C-6Fg?sL1~Ls)paSGi1L8g)&IIyr0I?8|y}`l3LFT}L z1ABmMOaN5GgeeLU0m=yi@l7E10Eu8zxD6<2gv|h;_2ftq=McoIkwo93B7aZsAX1zdmZ^E_P~Lo`GgCo|{#0je05;L|y= x{~(Obb4=I-i4_}(nu!gY0Z>P?cpd=1K&=!{=T1BqYE=Z{db;|#taD0e0suQ^D8c{$ literal 0 HcmV?d00001 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 0000000000000000000000000000000000000000..6a04f7951e1c8cd444038d7c122a43defb042d0d GIT binary patch literal 1041 zcmeAS@N?(olHy`uVBq!ia0vp^{28_{{~`K zAT9>l+6ScJ0DFL0GMoXbD1xd2X*>_alc0PespAZDfv)%j#KuJGpp!D6bafYA#;O+} O>pWfkT-G@yGywqZQ$$Mu literal 0 HcmV?d00001 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 0000000000000000000000000000000000000000..ac5721c5f3acb01b587e0beebfd8818bd3444be3 GIT binary patch literal 1041 zcmdUuJxD@f6ox-D%oKA7Bbr(QTO^u_8Y-^XO%kaVqbO3Sp(yB&mIfiHR*5Jg9DCRsM4cYDijOI%J*UE(wuQOdln zYwdk*mnmi*|6<2~lAbdC+Uwqcw%;)v{RZ}OV)Fn1 literal 0 HcmV?d00001 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 0000000000000000000000000000000000000000..f4dcca21896885fb30c3a4b531f1fce4aaa573bb GIT binary patch literal 140 zcmeAS@N?(olHy`uVBq!ia0vp^0wB!61|;P_|4#%`jKx9jP7LeL$-D$|SkfJR9T^xl z_H+M9WCij$3p^r=85sBufiR<}hF1enP|?%HF~q_@`OYt&Z}!Zr4V+$+dEIy=Oc_{! eWCgQ?1cO^3)Abdn4$1))GkCiCxvX 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 0000000000000000000000000000000000000000..ee8b155d7fce38d44ebd8fcaedb8c52e194c7eb5 GIT binary patch literal 327680 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 Gb*L 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

>JutcluU}C9Q;`=N4(7=E;iM)N|S@ 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%0D|oKv`vX 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 SFbEg~3<3rLgTVg-f&Tzf%r6E2 literal 0 HcmV?d00001 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 0000000000000000000000000000000000000000..2ddd97685300a00101c45fd79c53ac449a317d8a GIT binary patch literal 39424 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 literal 0 HcmV?d00001 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 0000000000000000000000000000000000000000..5e59e6469ce71529a12d351e662a3cedb5aa54c0 GIT binary patch literal 280576 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

y3ooU$^u%Fd(%T6Eo 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 B?F#?^ literal 0 HcmV?d00001 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 0000000000000000000000000000000000000000..1f2e48a67627ff338cb0d991c1584ae7aaa8d1b7 GIT binary patch literal 53248 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 PEpX5R2QBd5V}bt*w%2z> literal 0 HcmV?d00001 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 0000000000000000000000000000000000000000..a50e5313cc100036d18b6f00340aa7ba0aa25a90 GIT binary patch literal 131072 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 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: