From f5eaf92657b56fd47c12d07ed68f746c59aab586 Mon Sep 17 00:00:00 2001 From: Kuniwak Date: Thu, 9 Nov 2023 15:20:59 +0900 Subject: [PATCH 1/2] Add reproducing tests environment Reproducing steps: 1. Stay keep is not playing 2. Open the scene "Error" 3. Open an inspector for "AutopilotSettingsForFacingImmediatelyError" 4. Click Run Expected Behaviour: Stop playing and report the error to Slack Actual Behaviour: Still playing and did not report to Slack --- Tests/Runtime/TestDoubles/StubClickAgent.cs | 39 ++ .../TestDoubles/StubClickAgent.cs.meta | 3 + .../TestDoubles/StubThrowingErrorOnClick.cs | 22 + .../StubThrowingErrorOnClick.cs.meta | 3 + ...lotSettingsForFacingImmediatelyError.asset | 33 ++ ...ttingsForFacingImmediatelyError.asset.meta | 8 + Tests/TestAssets/StubClickAgentForTests.asset | 16 + .../StubClickAgentForTests.asset.meta | 8 + .../StubImmediatelyErrorAgentForTests.asset | 15 + ...ubImmediatelyErrorAgentForTests.asset.meta | 8 + Tests/TestScenes/Error.unity | 522 ++++++++++++++++++ Tests/TestScenes/Error.unity.meta | 7 + 12 files changed, 684 insertions(+) create mode 100644 Tests/Runtime/TestDoubles/StubClickAgent.cs create mode 100644 Tests/Runtime/TestDoubles/StubClickAgent.cs.meta create mode 100644 Tests/Runtime/TestDoubles/StubThrowingErrorOnClick.cs create mode 100644 Tests/Runtime/TestDoubles/StubThrowingErrorOnClick.cs.meta create mode 100644 Tests/TestAssets/AutopilotSettingsForFacingImmediatelyError.asset create mode 100644 Tests/TestAssets/AutopilotSettingsForFacingImmediatelyError.asset.meta create mode 100644 Tests/TestAssets/StubClickAgentForTests.asset create mode 100644 Tests/TestAssets/StubClickAgentForTests.asset.meta create mode 100644 Tests/TestAssets/StubImmediatelyErrorAgentForTests.asset create mode 100644 Tests/TestAssets/StubImmediatelyErrorAgentForTests.asset.meta create mode 100644 Tests/TestScenes/Error.unity create mode 100644 Tests/TestScenes/Error.unity.meta diff --git a/Tests/Runtime/TestDoubles/StubClickAgent.cs b/Tests/Runtime/TestDoubles/StubClickAgent.cs new file mode 100644 index 0000000..d384def --- /dev/null +++ b/Tests/Runtime/TestDoubles/StubClickAgent.cs @@ -0,0 +1,39 @@ +// Copyright (c) 2023 DeNA Co., Ltd. +// This software is released under the MIT License. + +using System.Threading; +using Cysharp.Threading.Tasks; +using DeNA.Anjin.Agents; +using UnityEngine; +using UnityEngine.EventSystems; + +namespace DeNA.Anjin.TestDoubles +{ + public class StubClickAgent : AbstractAgent + { + [SerializeField] public string targetName; + + public override UniTask Run(CancellationToken token) + { + foreach (var obj in FindObjectsByType(FindObjectsSortMode.None)) + { + if (obj.name != targetName) + { + continue; + } + + var target = obj; + if (!target.TryGetComponent(out var handler)) + { + Debug.LogWarning( + $"{target.name} did not have any components that derived {nameof(IPointerClickHandler)}" + ); + continue; + } + + handler.OnPointerClick(new PointerEventData(EventSystem.current)); + } + return UniTask.CompletedTask; + } + } +} diff --git a/Tests/Runtime/TestDoubles/StubClickAgent.cs.meta b/Tests/Runtime/TestDoubles/StubClickAgent.cs.meta new file mode 100644 index 0000000..98a46aa --- /dev/null +++ b/Tests/Runtime/TestDoubles/StubClickAgent.cs.meta @@ -0,0 +1,3 @@ +fileFormatVersion: 2 +guid: fcc96872a9564f89be6b16a814ad0034 +timeCreated: 1699501683 \ No newline at end of file diff --git a/Tests/Runtime/TestDoubles/StubThrowingErrorOnClick.cs b/Tests/Runtime/TestDoubles/StubThrowingErrorOnClick.cs new file mode 100644 index 0000000..de3273f --- /dev/null +++ b/Tests/Runtime/TestDoubles/StubThrowingErrorOnClick.cs @@ -0,0 +1,22 @@ +// Copyright (c) 2023 DeNA Co., Ltd. +// This software is released under the MIT License. + +using System; +using System.Threading; +using UnityEngine; +using UnityEngine.EventSystems; + +namespace DeNA.Anjin.TestDoubles +{ + /// + /// A test stub throw errors when clicked + /// + [AddComponentMenu("")] + public class StubThrowingErrorOnClick : MonoBehaviour, IPointerClickHandler + { + public void OnPointerClick(PointerEventData eventData) + { + throw new Exception($"TEST on thread #{Thread.CurrentThread.ManagedThreadId}"); + } + } +} diff --git a/Tests/Runtime/TestDoubles/StubThrowingErrorOnClick.cs.meta b/Tests/Runtime/TestDoubles/StubThrowingErrorOnClick.cs.meta new file mode 100644 index 0000000..5a31fe0 --- /dev/null +++ b/Tests/Runtime/TestDoubles/StubThrowingErrorOnClick.cs.meta @@ -0,0 +1,3 @@ +fileFormatVersion: 2 +guid: 71beb16fdd0d495a9fdc4633545c8020 +timeCreated: 1699490874 \ No newline at end of file diff --git a/Tests/TestAssets/AutopilotSettingsForFacingImmediatelyError.asset b/Tests/TestAssets/AutopilotSettingsForFacingImmediatelyError.asset new file mode 100644 index 0000000..309a8e5 --- /dev/null +++ b/Tests/TestAssets/AutopilotSettingsForFacingImmediatelyError.asset @@ -0,0 +1,33 @@ +%YAML 1.1 +%TAG !u! tag:unity3d.com,2011: +--- !u!114 &11400000 +MonoBehaviour: + m_ObjectHideFlags: 0 + m_CorrespondingSourceObject: {fileID: 0} + m_PrefabInstance: {fileID: 0} + m_PrefabAsset: {fileID: 0} + m_GameObject: {fileID: 0} + m_Enabled: 1 + m_EditorHideFlags: 0 + m_Script: {fileID: 11500000, guid: c7a1731caa574ed4b9334b7d52febe27, type: 3} + m_Name: AutopilotSettingsForFacingImmediatelyError + m_EditorClassIdentifier: + description: + sceneAgentMaps: + - scenePath: Packages/com.dena.anjin/Tests/TestScenes/Error.unity + agent: {fileID: 11400000, guid: 9a5c6d18335194c1b9f8e162695d73fd, type: 2} + fallbackAgent: {fileID: 0} + observerAgent: {fileID: 0} + lifespanSec: 0 + randomSeed: + timeScale: 1 + junitReportPath: + slackToken: + slackChannels: + mentionSubTeamIDs: + addHereInSlackMessage: 0 + handleException: 1 + handleError: 1 + handleAssert: 1 + handleWarning: 0 + ignoreMessages: [] diff --git a/Tests/TestAssets/AutopilotSettingsForFacingImmediatelyError.asset.meta b/Tests/TestAssets/AutopilotSettingsForFacingImmediatelyError.asset.meta new file mode 100644 index 0000000..fc74f11 --- /dev/null +++ b/Tests/TestAssets/AutopilotSettingsForFacingImmediatelyError.asset.meta @@ -0,0 +1,8 @@ +fileFormatVersion: 2 +guid: ff270c1597231470a8566632e1a0a115 +NativeFormatImporter: + externalObjects: {} + mainObjectFileID: 11400000 + userData: + assetBundleName: + assetBundleVariant: diff --git a/Tests/TestAssets/StubClickAgentForTests.asset b/Tests/TestAssets/StubClickAgentForTests.asset new file mode 100644 index 0000000..e17b788 --- /dev/null +++ b/Tests/TestAssets/StubClickAgentForTests.asset @@ -0,0 +1,16 @@ +%YAML 1.1 +%TAG !u! tag:unity3d.com,2011: +--- !u!114 &11400000 +MonoBehaviour: + m_ObjectHideFlags: 0 + m_CorrespondingSourceObject: {fileID: 0} + m_PrefabInstance: {fileID: 0} + m_PrefabAsset: {fileID: 0} + m_GameObject: {fileID: 0} + m_Enabled: 1 + m_EditorHideFlags: 0 + m_Script: {fileID: 11500000, guid: fcc96872a9564f89be6b16a814ad0034, type: 3} + m_Name: StubClickAgentForTests + m_EditorClassIdentifier: + description: + targetName: ThrowErrorFromMainThreadOnClick diff --git a/Tests/TestAssets/StubClickAgentForTests.asset.meta b/Tests/TestAssets/StubClickAgentForTests.asset.meta new file mode 100644 index 0000000..583a07d --- /dev/null +++ b/Tests/TestAssets/StubClickAgentForTests.asset.meta @@ -0,0 +1,8 @@ +fileFormatVersion: 2 +guid: 9a5c6d18335194c1b9f8e162695d73fd +NativeFormatImporter: + externalObjects: {} + mainObjectFileID: 11400000 + userData: + assetBundleName: + assetBundleVariant: diff --git a/Tests/TestAssets/StubImmediatelyErrorAgentForTests.asset b/Tests/TestAssets/StubImmediatelyErrorAgentForTests.asset new file mode 100644 index 0000000..26b1bef --- /dev/null +++ b/Tests/TestAssets/StubImmediatelyErrorAgentForTests.asset @@ -0,0 +1,15 @@ +%YAML 1.1 +%TAG !u! tag:unity3d.com,2011: +--- !u!114 &11400000 +MonoBehaviour: + m_ObjectHideFlags: 0 + m_CorrespondingSourceObject: {fileID: 0} + m_PrefabInstance: {fileID: 0} + m_PrefabAsset: {fileID: 0} + m_GameObject: {fileID: 0} + m_Enabled: 1 + m_EditorHideFlags: 0 + m_Script: {fileID: 11500000, guid: bc82cbd7e5b6404da291e090ed1f4eba, type: 3} + m_Name: StubImmediatelyErrorAgentForTests + m_EditorClassIdentifier: + description: diff --git a/Tests/TestAssets/StubImmediatelyErrorAgentForTests.asset.meta b/Tests/TestAssets/StubImmediatelyErrorAgentForTests.asset.meta new file mode 100644 index 0000000..7ebefe6 --- /dev/null +++ b/Tests/TestAssets/StubImmediatelyErrorAgentForTests.asset.meta @@ -0,0 +1,8 @@ +fileFormatVersion: 2 +guid: 3f7edebaf584441eea0d62bb4e001beb +NativeFormatImporter: + externalObjects: {} + mainObjectFileID: 11400000 + userData: + assetBundleName: + assetBundleVariant: diff --git a/Tests/TestScenes/Error.unity b/Tests/TestScenes/Error.unity new file mode 100644 index 0000000..9f41aef --- /dev/null +++ b/Tests/TestScenes/Error.unity @@ -0,0 +1,522 @@ +%YAML 1.1 +%TAG !u! tag:unity3d.com,2011: +--- !u!29 &1 +OcclusionCullingSettings: + m_ObjectHideFlags: 0 + serializedVersion: 2 + m_OcclusionBakeSettings: + smallestOccluder: 5 + smallestHole: 0.25 + backfaceThreshold: 100 + m_SceneGUID: 00000000000000000000000000000000 + m_OcclusionCullingData: {fileID: 0} +--- !u!104 &2 +RenderSettings: + m_ObjectHideFlags: 0 + serializedVersion: 9 + m_Fog: 0 + m_FogColor: {r: 0.5, g: 0.5, b: 0.5, a: 1} + m_FogMode: 3 + m_FogDensity: 0.01 + m_LinearFogStart: 0 + m_LinearFogEnd: 300 + m_AmbientSkyColor: {r: 0.212, g: 0.227, b: 0.259, a: 1} + m_AmbientEquatorColor: {r: 0.114, g: 0.125, b: 0.133, a: 1} + m_AmbientGroundColor: {r: 0.047, g: 0.043, b: 0.035, a: 1} + m_AmbientIntensity: 1 + m_AmbientMode: 0 + m_SubtractiveShadowColor: {r: 0.42, g: 0.478, b: 0.627, a: 1} + m_SkyboxMaterial: {fileID: 10304, guid: 0000000000000000f000000000000000, type: 0} + m_HaloStrength: 0.5 + m_FlareStrength: 1 + m_FlareFadeSpeed: 3 + m_HaloTexture: {fileID: 0} + m_SpotCookie: {fileID: 10001, guid: 0000000000000000e000000000000000, type: 0} + m_DefaultReflectionMode: 0 + m_DefaultReflectionResolution: 128 + m_ReflectionBounces: 1 + m_ReflectionIntensity: 1 + m_CustomReflection: {fileID: 0} + m_Sun: {fileID: 0} + m_IndirectSpecularColor: {r: 0.44657844, g: 0.49641222, b: 0.57481676, a: 1} + m_UseRadianceAmbientProbe: 0 +--- !u!157 &3 +LightmapSettings: + m_ObjectHideFlags: 0 + serializedVersion: 12 + m_GIWorkflowMode: 1 + m_GISettings: + serializedVersion: 2 + m_BounceScale: 1 + m_IndirectOutputScale: 1 + m_AlbedoBoost: 1 + m_EnvironmentLightingMode: 0 + m_EnableBakedLightmaps: 1 + m_EnableRealtimeLightmaps: 0 + m_LightmapEditorSettings: + serializedVersion: 12 + m_Resolution: 2 + m_BakeResolution: 40 + m_AtlasSize: 1024 + m_AO: 0 + m_AOMaxDistance: 1 + m_CompAOExponent: 1 + m_CompAOExponentDirect: 0 + m_ExtractAmbientOcclusion: 0 + m_Padding: 2 + m_LightmapParameters: {fileID: 0} + m_LightmapsBakeMode: 1 + m_TextureCompression: 1 + m_FinalGather: 0 + m_FinalGatherFiltering: 1 + m_FinalGatherRayCount: 256 + m_ReflectionCompression: 2 + m_MixedBakeMode: 2 + m_BakeBackend: 1 + m_PVRSampling: 1 + m_PVRDirectSampleCount: 32 + m_PVRSampleCount: 512 + m_PVRBounces: 2 + m_PVREnvironmentSampleCount: 256 + m_PVREnvironmentReferencePointCount: 2048 + m_PVRFilteringMode: 1 + m_PVRDenoiserTypeDirect: 1 + m_PVRDenoiserTypeIndirect: 1 + m_PVRDenoiserTypeAO: 1 + m_PVRFilterTypeDirect: 0 + m_PVRFilterTypeIndirect: 0 + m_PVRFilterTypeAO: 0 + m_PVREnvironmentMIS: 1 + m_PVRCulling: 1 + m_PVRFilteringGaussRadiusDirect: 1 + m_PVRFilteringGaussRadiusIndirect: 5 + m_PVRFilteringGaussRadiusAO: 2 + m_PVRFilteringAtrousPositionSigmaDirect: 0.5 + m_PVRFilteringAtrousPositionSigmaIndirect: 2 + m_PVRFilteringAtrousPositionSigmaAO: 1 + m_ExportTrainingData: 0 + m_TrainingDataDestination: TrainingData + m_LightProbeSampleCountMultiplier: 4 + m_LightingDataAsset: {fileID: 0} + m_LightingSettings: {fileID: 0} +--- !u!196 &4 +NavMeshSettings: + serializedVersion: 2 + m_ObjectHideFlags: 0 + m_BuildSettings: + serializedVersion: 3 + agentTypeID: 0 + agentRadius: 0.5 + agentHeight: 2 + agentSlope: 45 + agentClimb: 0.4 + ledgeDropHeight: 0 + maxJumpAcrossDistance: 0 + minRegionArea: 2 + manualCellSize: 0 + cellSize: 0.16666667 + manualTileSize: 0 + tileSize: 256 + buildHeightMesh: 0 + maxJobWorkers: 0 + preserveTilesOutsideBounds: 0 + debug: + m_Flags: 0 + m_NavMeshData: {fileID: 0} +--- !u!1 &300655192 +GameObject: + m_ObjectHideFlags: 0 + m_CorrespondingSourceObject: {fileID: 0} + m_PrefabInstance: {fileID: 0} + m_PrefabAsset: {fileID: 0} + serializedVersion: 6 + m_Component: + - component: {fileID: 300655197} + - component: {fileID: 300655196} + - component: {fileID: 300655195} + - component: {fileID: 300655194} + - component: {fileID: 300655193} + m_Layer: 0 + m_Name: ThrowErrorFromMainThreadOnClick + m_TagString: Untagged + m_Icon: {fileID: 0} + m_NavMeshLayer: 0 + m_StaticEditorFlags: 0 + m_IsActive: 1 +--- !u!114 &300655193 +MonoBehaviour: + m_ObjectHideFlags: 0 + m_CorrespondingSourceObject: {fileID: 0} + m_PrefabInstance: {fileID: 0} + m_PrefabAsset: {fileID: 0} + m_GameObject: {fileID: 300655192} + m_Enabled: 1 + m_EditorHideFlags: 0 + m_Script: {fileID: 11500000, guid: 71beb16fdd0d495a9fdc4633545c8020, type: 3} + m_Name: + m_EditorClassIdentifier: +--- !u!65 &300655194 +BoxCollider: + m_ObjectHideFlags: 0 + m_CorrespondingSourceObject: {fileID: 0} + m_PrefabInstance: {fileID: 0} + m_PrefabAsset: {fileID: 0} + m_GameObject: {fileID: 300655192} + m_Material: {fileID: 0} + m_IncludeLayers: + serializedVersion: 2 + m_Bits: 0 + m_ExcludeLayers: + serializedVersion: 2 + m_Bits: 0 + m_LayerOverridePriority: 0 + m_IsTrigger: 0 + m_ProvidesContacts: 0 + m_Enabled: 1 + serializedVersion: 3 + m_Size: {x: 1, y: 1, z: 1} + m_Center: {x: 0, y: 0, z: 0} +--- !u!23 &300655195 +MeshRenderer: + m_ObjectHideFlags: 0 + m_CorrespondingSourceObject: {fileID: 0} + m_PrefabInstance: {fileID: 0} + m_PrefabAsset: {fileID: 0} + m_GameObject: {fileID: 300655192} + m_Enabled: 1 + m_CastShadows: 1 + m_ReceiveShadows: 1 + m_DynamicOccludee: 1 + m_StaticShadowCaster: 0 + m_MotionVectors: 1 + m_LightProbeUsage: 1 + m_ReflectionProbeUsage: 1 + m_RayTracingMode: 2 + m_RayTraceProcedural: 0 + m_RenderingLayerMask: 1 + m_RendererPriority: 0 + m_Materials: + - {fileID: 10303, guid: 0000000000000000f000000000000000, type: 0} + m_StaticBatchInfo: + firstSubMesh: 0 + subMeshCount: 0 + m_StaticBatchRoot: {fileID: 0} + m_ProbeAnchor: {fileID: 0} + m_LightProbeVolumeOverride: {fileID: 0} + m_ScaleInLightmap: 1 + m_ReceiveGI: 1 + m_PreserveUVs: 0 + m_IgnoreNormalsForChartDetection: 0 + m_ImportantGI: 0 + m_StitchLightmapSeams: 1 + m_SelectedEditorRenderState: 3 + m_MinimumChartSize: 4 + m_AutoUVMaxDistance: 0.5 + m_AutoUVMaxAngle: 89 + m_LightmapParameters: {fileID: 0} + m_SortingLayerID: 0 + m_SortingLayer: 0 + m_SortingOrder: 0 + m_AdditionalVertexStreams: {fileID: 0} +--- !u!33 &300655196 +MeshFilter: + m_ObjectHideFlags: 0 + m_CorrespondingSourceObject: {fileID: 0} + m_PrefabInstance: {fileID: 0} + m_PrefabAsset: {fileID: 0} + m_GameObject: {fileID: 300655192} + m_Mesh: {fileID: 10202, guid: 0000000000000000e000000000000000, type: 0} +--- !u!4 &300655197 +Transform: + m_ObjectHideFlags: 0 + m_CorrespondingSourceObject: {fileID: 0} + m_PrefabInstance: {fileID: 0} + m_PrefabAsset: {fileID: 0} + m_GameObject: {fileID: 300655192} + serializedVersion: 2 + m_LocalRotation: {x: 0, y: 0, z: 0, w: 1} + m_LocalPosition: {x: -1, y: 0, z: 0} + m_LocalScale: {x: 1, y: 1, z: 1} + m_ConstrainProportionsScale: 0 + m_Children: [] + m_Father: {fileID: 0} + m_LocalEulerAnglesHint: {x: 0, y: 0, z: 0} +--- !u!1 &352512550 +GameObject: + m_ObjectHideFlags: 0 + m_CorrespondingSourceObject: {fileID: 0} + m_PrefabInstance: {fileID: 0} + m_PrefabAsset: {fileID: 0} + serializedVersion: 6 + m_Component: + - component: {fileID: 352512553} + - component: {fileID: 352512552} + - component: {fileID: 352512551} + m_Layer: 0 + m_Name: EventSystem + m_TagString: Untagged + m_Icon: {fileID: 0} + m_NavMeshLayer: 0 + m_StaticEditorFlags: 0 + m_IsActive: 1 +--- !u!114 &352512551 +MonoBehaviour: + m_ObjectHideFlags: 0 + m_CorrespondingSourceObject: {fileID: 0} + m_PrefabInstance: {fileID: 0} + m_PrefabAsset: {fileID: 0} + m_GameObject: {fileID: 352512550} + m_Enabled: 1 + m_EditorHideFlags: 0 + m_Script: {fileID: 11500000, guid: 4f231c4fb786f3946a6b90b886c48677, type: 3} + m_Name: + m_EditorClassIdentifier: + m_SendPointerHoverToParent: 1 + m_HorizontalAxis: Horizontal + m_VerticalAxis: Vertical + m_SubmitButton: Submit + m_CancelButton: Cancel + m_InputActionsPerSecond: 10 + m_RepeatDelay: 0.5 + m_ForceModuleActive: 0 +--- !u!114 &352512552 +MonoBehaviour: + m_ObjectHideFlags: 0 + m_CorrespondingSourceObject: {fileID: 0} + m_PrefabInstance: {fileID: 0} + m_PrefabAsset: {fileID: 0} + m_GameObject: {fileID: 352512550} + m_Enabled: 1 + m_EditorHideFlags: 0 + m_Script: {fileID: 11500000, guid: 76c392e42b5098c458856cdf6ecaaaa1, type: 3} + m_Name: + m_EditorClassIdentifier: + m_FirstSelected: {fileID: 0} + m_sendNavigationEvents: 1 + m_DragThreshold: 10 +--- !u!4 &352512553 +Transform: + m_ObjectHideFlags: 0 + m_CorrespondingSourceObject: {fileID: 0} + m_PrefabInstance: {fileID: 0} + m_PrefabAsset: {fileID: 0} + m_GameObject: {fileID: 352512550} + serializedVersion: 2 + 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_ConstrainProportionsScale: 0 + m_Children: [] + m_Father: {fileID: 0} + m_LocalEulerAnglesHint: {x: 0, y: 0, z: 0} +--- !u!1 &1025330164 +GameObject: + m_ObjectHideFlags: 0 + m_CorrespondingSourceObject: {fileID: 0} + m_PrefabInstance: {fileID: 0} + m_PrefabAsset: {fileID: 0} + serializedVersion: 6 + m_Component: + - component: {fileID: 1025330167} + - component: {fileID: 1025330166} + - component: {fileID: 1025330165} + - component: {fileID: 1025330168} + m_Layer: 0 + m_Name: Main Camera + m_TagString: MainCamera + m_Icon: {fileID: 0} + m_NavMeshLayer: 0 + m_StaticEditorFlags: 0 + m_IsActive: 1 +--- !u!81 &1025330165 +AudioListener: + m_ObjectHideFlags: 0 + m_CorrespondingSourceObject: {fileID: 0} + m_PrefabInstance: {fileID: 0} + m_PrefabAsset: {fileID: 0} + m_GameObject: {fileID: 1025330164} + m_Enabled: 1 +--- !u!20 &1025330166 +Camera: + m_ObjectHideFlags: 0 + m_CorrespondingSourceObject: {fileID: 0} + m_PrefabInstance: {fileID: 0} + m_PrefabAsset: {fileID: 0} + m_GameObject: {fileID: 1025330164} + m_Enabled: 1 + serializedVersion: 2 + m_ClearFlags: 1 + m_BackGroundColor: {r: 0.19215687, g: 0.3019608, b: 0.4745098, a: 0} + m_projectionMatrixMode: 1 + m_GateFitMode: 2 + m_FOVAxisMode: 0 + m_Iso: 200 + m_ShutterSpeed: 0.005 + m_Aperture: 16 + m_FocusDistance: 10 + m_FocalLength: 50 + m_BladeCount: 5 + m_Curvature: {x: 2, y: 11} + m_BarrelClipping: 0.25 + m_Anamorphism: 0 + m_SensorSize: {x: 36, y: 24} + m_LensShift: {x: 0, y: 0} + m_NormalizedViewPortRect: + serializedVersion: 2 + x: 0 + y: 0 + width: 1 + height: 1 + near clip plane: 0.3 + far clip plane: 1000 + field of view: 60 + orthographic: 0 + orthographic size: 5 + m_Depth: -1 + m_CullingMask: + serializedVersion: 2 + m_Bits: 4294967295 + m_RenderingPath: -1 + m_TargetTexture: {fileID: 0} + m_TargetDisplay: 0 + m_TargetEye: 3 + m_HDR: 1 + m_AllowMSAA: 1 + m_AllowDynamicResolution: 0 + m_ForceIntoRT: 0 + m_OcclusionCulling: 1 + m_StereoConvergence: 10 + m_StereoSeparation: 0.022 +--- !u!4 &1025330167 +Transform: + m_ObjectHideFlags: 0 + m_CorrespondingSourceObject: {fileID: 0} + m_PrefabInstance: {fileID: 0} + m_PrefabAsset: {fileID: 0} + m_GameObject: {fileID: 1025330164} + serializedVersion: 2 + m_LocalRotation: {x: 0, y: 0, z: 0, w: 1} + m_LocalPosition: {x: 0, y: 1, z: -10} + m_LocalScale: {x: 1, y: 1, z: 1} + m_ConstrainProportionsScale: 0 + m_Children: [] + m_Father: {fileID: 0} + m_LocalEulerAnglesHint: {x: 0, y: 0, z: 0} +--- !u!114 &1025330168 +MonoBehaviour: + m_ObjectHideFlags: 0 + m_CorrespondingSourceObject: {fileID: 0} + m_PrefabInstance: {fileID: 0} + m_PrefabAsset: {fileID: 0} + m_GameObject: {fileID: 1025330164} + m_Enabled: 1 + m_EditorHideFlags: 0 + m_Script: {fileID: 11500000, guid: c49b4cc203aa6414fae5c798d1d0e7d6, type: 3} + m_Name: + m_EditorClassIdentifier: + m_EventMask: + serializedVersion: 2 + m_Bits: 4294967295 + m_MaxRayIntersections: 0 +--- !u!1 &1325883444 +GameObject: + m_ObjectHideFlags: 0 + m_CorrespondingSourceObject: {fileID: 0} + m_PrefabInstance: {fileID: 0} + m_PrefabAsset: {fileID: 0} + serializedVersion: 6 + m_Component: + - component: {fileID: 1325883446} + - component: {fileID: 1325883445} + m_Layer: 0 + m_Name: Directional Light + m_TagString: Untagged + m_Icon: {fileID: 0} + m_NavMeshLayer: 0 + m_StaticEditorFlags: 0 + m_IsActive: 1 +--- !u!108 &1325883445 +Light: + m_ObjectHideFlags: 0 + m_CorrespondingSourceObject: {fileID: 0} + m_PrefabInstance: {fileID: 0} + m_PrefabAsset: {fileID: 0} + m_GameObject: {fileID: 1325883444} + m_Enabled: 1 + serializedVersion: 10 + m_Type: 1 + m_Shape: 0 + m_Color: {r: 1, g: 0.95686275, b: 0.8392157, a: 1} + m_Intensity: 1 + m_Range: 10 + m_SpotAngle: 30 + m_InnerSpotAngle: 21.80208 + m_CookieSize: 10 + m_Shadows: + m_Type: 2 + m_Resolution: -1 + m_CustomResolution: -1 + m_Strength: 1 + m_Bias: 0.05 + m_NormalBias: 0.4 + m_NearPlane: 0.2 + m_CullingMatrixOverride: + e00: 1 + e01: 0 + e02: 0 + e03: 0 + e10: 0 + e11: 1 + e12: 0 + e13: 0 + e20: 0 + e21: 0 + e22: 1 + e23: 0 + e30: 0 + e31: 0 + e32: 0 + e33: 1 + m_UseCullingMatrixOverride: 0 + m_Cookie: {fileID: 0} + m_DrawHalo: 0 + m_Flare: {fileID: 0} + m_RenderMode: 0 + m_CullingMask: + serializedVersion: 2 + m_Bits: 4294967295 + m_RenderingLayerMask: 1 + m_Lightmapping: 4 + m_LightShadowCasterMode: 0 + m_AreaSize: {x: 1, y: 1} + m_BounceIntensity: 1 + m_ColorTemperature: 6570 + m_UseColorTemperature: 0 + m_BoundingSphereOverride: {x: 0, y: 0, z: 0, w: 0} + m_UseBoundingSphereOverride: 0 + m_UseViewFrustumForShadowCasterCull: 1 + m_ShadowRadius: 0 + m_ShadowAngle: 0 +--- !u!4 &1325883446 +Transform: + m_ObjectHideFlags: 0 + m_CorrespondingSourceObject: {fileID: 0} + m_PrefabInstance: {fileID: 0} + m_PrefabAsset: {fileID: 0} + m_GameObject: {fileID: 1325883444} + serializedVersion: 2 + m_LocalRotation: {x: 0.40821788, y: -0.23456968, z: 0.10938163, w: 0.8754261} + m_LocalPosition: {x: 0, y: 3, z: 0} + m_LocalScale: {x: 1, y: 1, z: 1} + m_ConstrainProportionsScale: 0 + m_Children: [] + m_Father: {fileID: 0} + m_LocalEulerAnglesHint: {x: 50, y: -30, z: 0} +--- !u!1660057539 &9223372036854775807 +SceneRoots: + m_ObjectHideFlags: 0 + m_Roots: + - {fileID: 1025330167} + - {fileID: 1325883446} + - {fileID: 300655197} + - {fileID: 352512553} diff --git a/Tests/TestScenes/Error.unity.meta b/Tests/TestScenes/Error.unity.meta new file mode 100644 index 0000000..145c391 --- /dev/null +++ b/Tests/TestScenes/Error.unity.meta @@ -0,0 +1,7 @@ +fileFormatVersion: 2 +guid: 9dc38f604e3d940f3a604ecdf19b423b +DefaultImporter: + externalObjects: {} + userData: + assetBundleName: + assetBundleVariant: From 9af66be9c28a83dc851e52465b4666e277af7335 Mon Sep 17 00:00:00 2001 From: Kuniwak Date: Thu, 9 Nov 2023 15:28:37 +0900 Subject: [PATCH 2/2] Fix not stopping and reporting when error occurred --- Editor/DeNA.Anjin.Editor.asmdef | 1 + Editor/UI/Settings/AutopilotSettingsEditor.cs | 7 ++-- Runtime/AgentDispatcher.cs | 15 ++++++-- Runtime/Autopilot.cs | 35 +++++++++++++++---- Runtime/Utilities/LogMessageHandler.cs | 2 +- Tests/Runtime/LauncherTest.cs | 4 +-- Tests/Runtime/TestDoubles/StubClickAgent.cs | 9 +++++ 7 files changed, 58 insertions(+), 15 deletions(-) diff --git a/Editor/DeNA.Anjin.Editor.asmdef b/Editor/DeNA.Anjin.Editor.asmdef index bf7a167..498fee2 100644 --- a/Editor/DeNA.Anjin.Editor.asmdef +++ b/Editor/DeNA.Anjin.Editor.asmdef @@ -2,6 +2,7 @@ "name": "DeNA.Anjin.Editor", "rootNamespace": "DeNA.Anjin", "references": [ + "UniTask", "DeNA.Anjin", "TestHelper.Monkey.Annotations", "TestHelper.Monkey" diff --git a/Editor/UI/Settings/AutopilotSettingsEditor.cs b/Editor/UI/Settings/AutopilotSettingsEditor.cs index 7612f15..86a127a 100644 --- a/Editor/UI/Settings/AutopilotSettingsEditor.cs +++ b/Editor/UI/Settings/AutopilotSettingsEditor.cs @@ -2,6 +2,7 @@ // This software is released under the MIT License. using System.Diagnostics.CodeAnalysis; +using Cysharp.Threading.Tasks; using DeNA.Anjin.Settings; using UnityEditor; using UnityEngine; @@ -137,7 +138,7 @@ public override void OnInspectorGUI() { if (GUILayout.Button(s_stopButton)) { - Stop(); + Stop().Forget(); } } else @@ -157,10 +158,10 @@ private static void DrawHeader(string label) } [SuppressMessage("ApiDesign", "RS0030")] - internal void Stop() + internal async UniTask Stop() { var autopilot = FindObjectOfType(); - autopilot.Terminate(Autopilot.ExitCode.Normally); + await autopilot.TerminateAsync(Autopilot.ExitCode.Normally); } internal void Launch(AutopilotState state) diff --git a/Runtime/AgentDispatcher.cs b/Runtime/AgentDispatcher.cs index 3046046..1352ae3 100644 --- a/Runtime/AgentDispatcher.cs +++ b/Runtime/AgentDispatcher.cs @@ -1,6 +1,7 @@ // Copyright (c) 2023 DeNA Co., Ltd. // This software is released under the MIT License. +using System; using DeNA.Anjin.Agents; using DeNA.Anjin.Settings; using DeNA.Anjin.Utilities; @@ -106,9 +107,17 @@ private void DispatchAgent(AbstractAgent agent) agent.Logger = _logger; agent.Random = _randomFactory.CreateRandom(); - agent.Run(token).Forget(); - - _logger.Log($"Agent {agentName} dispatched!"); + + try + { + agent.Run(token).Forget(); + _logger.Log($"Agent {agentName} dispatched!"); + } + catch (Exception e) + { + Debug.LogException(e); + _logger.Log($"Agent {agentName} dispatched but immediately threw an error!"); + } } } } diff --git a/Runtime/Autopilot.cs b/Runtime/Autopilot.cs index 7bf1ac7..c5f67cf 100644 --- a/Runtime/Autopilot.cs +++ b/Runtime/Autopilot.cs @@ -3,6 +3,8 @@ using System; using System.Collections; +using System.Threading; +using Cysharp.Threading.Tasks; using DeNA.Anjin.Reporters; using DeNA.Anjin.Settings; using DeNA.Anjin.Utilities; @@ -62,15 +64,18 @@ private void Start() _randomFactory = new RandomFactory(seed); _logger.Log($"Random seed is {seed}"); + + // NOTE: Registering logMessageReceived must be placed before DispatchByScene. + // Because some agent can throw an error immediately, so reporter can miss the error if + // registering logMessageReceived is placed after DispatchByScene. + _reporter = new SlackReporter(_settings, new SlackAPI()); + _logMessageHandler = new LogMessageHandler(_settings, _reporter); + Application.logMessageReceived += _logMessageHandler.HandleLog; _dispatcher = new AgentDispatcher(_settings, _logger, _randomFactory); _dispatcher.DispatchByScene(SceneManager.GetActiveScene()); SceneManager.activeSceneChanged += _dispatcher.DispatchByScene; - _reporter = new SlackReporter(_settings, new SlackAPI()); - _logMessageHandler = new LogMessageHandler(_settings, _reporter); - Application.logMessageReceived += _logMessageHandler.HandleLog; - if (_settings.lifespanSec > 0) { StartCoroutine(Lifespan(_settings.lifespanSec)); @@ -92,7 +97,7 @@ private void Start() private IEnumerator Lifespan(int timeoutSec) { yield return new WaitForSecondsRealtime(timeoutSec); - Terminate(ExitCode.Normally); + yield return UniTask.ToCoroutine(() => TerminateAsync(ExitCode.Normally)); } /// @@ -101,7 +106,8 @@ private IEnumerator Lifespan(int timeoutSec) /// Exit code for Unity Editor /// Log message string when terminate by the log message /// Stack trace when terminate by the log message - public void Terminate(ExitCode exitCode, string logString = null, string stackTrace = null) + /// A task awaits termination get completed + public async UniTask TerminateAsync(ExitCode exitCode, string logString = null, string stackTrace = null, CancellationToken token = default) { if (_dispatcher != null) { @@ -132,12 +138,29 @@ public void Terminate(ExitCode exitCode, string logString = null, string stackTr // Terminate when ran specified time. _logger.Log("Stop playing by autopilot"); _state.exitCode = exitCode; + // XXX: Avoid a problem that Editor stay playing despite isPlaying get assigned false. + // SEE: https://github.com/DeNA/Anjin/issues/20 + await UniTask.DelayFrame(1, cancellationToken: token); UnityEditor.EditorApplication.isPlaying = false; // Call Launcher.OnChangePlayModeState() so terminates Unity editor, when launch from CLI. #else _logger.Log($"Exit Unity-editor by autopilot, exit code={exitCode}"); Application.Quit((int)exitCode); + await UniTask.CompletedTask; #endif } + + + /// + /// Terminate autopilot + /// + /// Exit code for Unity Editor + /// Log message string when terminate by the log message + /// Stack trace when terminate by the log message + [Obsolete("Use " + nameof(TerminateAsync))] + public void Terminate(ExitCode exitCode, string logString = null, string stackTrace = null) + { + TerminateAsync(exitCode, logString, stackTrace).Forget(); + } } } diff --git a/Runtime/Utilities/LogMessageHandler.cs b/Runtime/Utilities/LogMessageHandler.cs index ae777df..6da59d9 100644 --- a/Runtime/Utilities/LogMessageHandler.cs +++ b/Runtime/Utilities/LogMessageHandler.cs @@ -44,7 +44,7 @@ public async void HandleLog(string logString, string stackTrace, LogType type) var autopilot = Object.FindObjectOfType(); if (autopilot != null) { - autopilot.Terminate(Autopilot.ExitCode.AutopilotFailed, logString, stackTrace); + await autopilot.TerminateAsync(Autopilot.ExitCode.AutopilotFailed, logString, stackTrace); } } diff --git a/Tests/Runtime/LauncherTest.cs b/Tests/Runtime/LauncherTest.cs index 68129ce..09ece5b 100644 --- a/Tests/Runtime/LauncherTest.cs +++ b/Tests/Runtime/LauncherTest.cs @@ -39,7 +39,7 @@ public async Task Launch_InPlayMode_RunAutopilot() var autopilot = Object.FindObjectOfType(); Assert.That((bool)autopilot, Is.True, "Autopilot object is alive"); - autopilot.Terminate(Autopilot.ExitCode.Normally); + await autopilot.TerminateAsync(Autopilot.ExitCode.Normally); } [Test] @@ -66,7 +66,7 @@ public async Task Stop_TerminateAutopilotAndKeepPlayMode() var state = AutopilotState.Instance; editor.Launch(state); await Task.Delay(2000); - editor.Stop(); // Note: If Autopilot stops for life before Stop, a NullReference exception is raised here. + await editor.Stop(); // Note: If Autopilot stops for life before Stop, a NullReference exception is raised here. Assert.That(state.IsRunning, Is.False, "AutopilotState is terminated"); Assert.That(EditorApplication.isPlaying, Is.True, "Keep play mode"); diff --git a/Tests/Runtime/TestDoubles/StubClickAgent.cs b/Tests/Runtime/TestDoubles/StubClickAgent.cs index d384def..e03b57e 100644 --- a/Tests/Runtime/TestDoubles/StubClickAgent.cs +++ b/Tests/Runtime/TestDoubles/StubClickAgent.cs @@ -9,10 +9,18 @@ namespace DeNA.Anjin.TestDoubles { + /// + /// A test double for agent. This agent immediately click game objects that have the name specified + /// public class StubClickAgent : AbstractAgent { + /// + /// A name of game objects to click + /// [SerializeField] public string targetName; + + /// public override UniTask Run(CancellationToken token) { foreach (var obj in FindObjectsByType(FindObjectsSortMode.None)) @@ -33,6 +41,7 @@ public override UniTask Run(CancellationToken token) handler.OnPointerClick(new PointerEventData(EventSystem.current)); } + return UniTask.CompletedTask; } }