From acba3ca6561a0da6d2c78e8b0d00179ca4a7fe4e Mon Sep 17 00:00:00 2001 From: Kurtis Date: Fri, 25 Sep 2026 10:25:21 -0700 Subject: [PATCH 1/2] Update logic for speech reco exception during tests --- org.mixedrealitytoolkit.uxcore/CHANGELOG.md | 1 + .../Runtime/SeeItSayItLabelEnablerTests.cs | 35 ++++++++----------- 2 files changed, 16 insertions(+), 20 deletions(-) diff --git a/org.mixedrealitytoolkit.uxcore/CHANGELOG.md b/org.mixedrealitytoolkit.uxcore/CHANGELOG.md index f5b02e2d5..b5ac52eb0 100644 --- a/org.mixedrealitytoolkit.uxcore/CHANGELOG.md +++ b/org.mixedrealitytoolkit.uxcore/CHANGELOG.md @@ -12,6 +12,7 @@ The format is based on [Keep a Changelog](https://keepachangelog.com/en/1.1.0/). * Fixed "leaked managed shell" issue in `UGUIInputAdapter`. [PR #1096](https://github.com/MixedRealityToolkit/MixedRealityToolkit-Unity/pull/1096) * Fixed "Attribute 'SerializeField' is not valid on this declaration type. It is only valid on 'field' declarations" error on `DialogButton` in Unity 6.3. [PR #1108](https://github.com/MixedRealityToolkit/MixedRealityToolkit-Unity/pull/1108) +* Fixed `SeeItSayItLabelEnablerTests` failure when running on platforms without speech recognition support. [PR #1141](https://github.com/MixedRealityToolkit/MixedRealityToolkit-Unity/pull/1141) ## [3.3.0] - 2025-11-12 diff --git a/org.mixedrealitytoolkit.uxcore/Tests/Runtime/SeeItSayItLabelEnablerTests.cs b/org.mixedrealitytoolkit.uxcore/Tests/Runtime/SeeItSayItLabelEnablerTests.cs index f92b9b7de..d4f54030e 100644 --- a/org.mixedrealitytoolkit.uxcore/Tests/Runtime/SeeItSayItLabelEnablerTests.cs +++ b/org.mixedrealitytoolkit.uxcore/Tests/Runtime/SeeItSayItLabelEnablerTests.cs @@ -34,10 +34,7 @@ public IEnumerator TestEnableAndSetLabel() interactor.gameObject.SetActive(true); yield return null; - if (Application.isBatchMode) - { - LogAssert.Expect(LogType.Exception, new Regex("Speech recognition is not supported on this machine")); - } + ExpectSpeechRecognitionNotSupportedException(); Transform sublabel = label.transform.GetChild(0); Assert.IsTrue(label.gameObject.activeSelf, "Label is enabled"); @@ -64,10 +61,7 @@ public IEnumerator TestAutoUpdateLabel() interactor.gameObject.SetActive(true); yield return null; - if (Application.isBatchMode) - { - LogAssert.Expect(LogType.Exception, new Regex("Speech recognition is not supported on this machine")); - } + ExpectSpeechRecognitionNotSupportedException(); Transform sublabel = label.transform.GetChild(0); TMP_Text text = label.gameObject.GetComponentInChildren(true); @@ -84,10 +78,7 @@ public IEnumerator TestAutoUpdateLabel() // Wait for a frame to give Unity a change to actually destroy the object yield return null; // The speech recognition keyword change will trigger this exception at next update when speech recognition is not supported - if (Application.isBatchMode) - { - LogAssert.Expect(LogType.Exception, new Regex("Speech recognition is not supported on this machine")); - } + ExpectSpeechRecognitionNotSupportedException(); } [UnityTest] @@ -115,10 +106,7 @@ public IEnumerator TestPositionCanvasLabel() interactor.gameObject.SetActive(true); yield return null; - if (Application.isBatchMode) - { - LogAssert.Expect(LogType.Exception, new Regex("Speech recognition is not supported on this machine")); - } + ExpectSpeechRecognitionNotSupportedException(); RectTransform sublabel = label.transform.GetChild(0) as RectTransform; Assert.AreEqual(sublabel.anchoredPosition3D, new Vector3(10, -30, -10), "Label is positioned correctly"); @@ -142,10 +130,7 @@ public IEnumerator TestPositionNonCanvasLabel() interactor.gameObject.SetActive(true); yield return null; - if (Application.isBatchMode) - { - LogAssert.Expect(LogType.Exception, new Regex("Speech recognition is not supported on this machine")); - } + ExpectSpeechRecognitionNotSupportedException(); Assert.AreEqual(label.transform.localPosition, new Vector3(10f, -.504f, -.004f), "Label is positioned correctly"); #else @@ -203,6 +188,16 @@ private GameObject SetUpButton(bool allowSelectByVoice, Control control) return testButton; } + private static void ExpectSpeechRecognitionNotSupportedException() + { +#if MRTK_INPUT_PRESENT && MRTK_SPEECH_PRESENT && (UNITY_STANDALONE_WIN || UNITY_WSA || UNITY_EDITOR_WIN) + if (!UnityEngine.Windows.Speech.PhraseRecognitionSystem.isSupported) + { + LogAssert.Expect(LogType.Exception, new Regex("Speech recognition is not supported on this machine")); + } +#endif + } + private enum Control { None, From 9ce63a577a159656550ba15e03a49dfb53c68dcb Mon Sep 17 00:00:00 2001 From: Kurtis Date: Fri, 25 Sep 2026 12:33:01 -0700 Subject: [PATCH 2/2] Ensure XR is off before running playmode tests --- org.mixedrealitytoolkit.core/CHANGELOG.md | 1 + .../Tests/TestUtilities/BaseRuntimeTests.cs | 2 + .../MRTK.Core.TestUtilities.asmdef | 4 +- .../TestUtilities/PlayModeTestXRDisabler.cs | 150 ++++++++++++++++++ .../PlayModeTestXRDisabler.cs.meta | 11 ++ 5 files changed, 167 insertions(+), 1 deletion(-) create mode 100644 org.mixedrealitytoolkit.core/Tests/TestUtilities/PlayModeTestXRDisabler.cs create mode 100644 org.mixedrealitytoolkit.core/Tests/TestUtilities/PlayModeTestXRDisabler.cs.meta diff --git a/org.mixedrealitytoolkit.core/CHANGELOG.md b/org.mixedrealitytoolkit.core/CHANGELOG.md index 95f237481..1b5c53d99 100644 --- a/org.mixedrealitytoolkit.core/CHANGELOG.md +++ b/org.mixedrealitytoolkit.core/CHANGELOG.md @@ -19,6 +19,7 @@ The format is based on [Keep a Changelog](https://keepachangelog.com/en/1.1.0/). * Fixed "The type `MixedReality.Toolkit.Core MixedReality.Toolkit.Experimental.BubbleChildHoverEvents/TrickleChildHoverEvents/BubbleChildSelectEvents/TrickleChildSelectEvents` is being serialized by `[SerializeReference]`, but is missing the `[Serializable]` attribute." on Unity 6.3. [PR #1107](https://github.com/MixedRealityToolkit/MixedRealityToolkit-Unity/pull/1107) * Fixed object identity API usages and obsolete sorting warnings for Unity 6.4 and newer in `FindObjectUtility`. [PR #1136](https://github.com/MixedRealityToolkit/MixedRealityToolkit-Unity/pull/1136) +* Fixed PlayMode tests failing when XR is initialized by ensuring XR is disabled during test runs. [PR #1141](https://github.com/MixedRealityToolkit/MixedRealityToolkit-Unity/pull/1141) ## Deprecated diff --git a/org.mixedrealitytoolkit.core/Tests/TestUtilities/BaseRuntimeTests.cs b/org.mixedrealitytoolkit.core/Tests/TestUtilities/BaseRuntimeTests.cs index e136cbfe1..0ef495608 100644 --- a/org.mixedrealitytoolkit.core/Tests/TestUtilities/BaseRuntimeTests.cs +++ b/org.mixedrealitytoolkit.core/Tests/TestUtilities/BaseRuntimeTests.cs @@ -12,6 +12,8 @@ namespace MixedReality.Toolkit.Core.Tests /// /// Base class to handle typical code setup/teardown and test utilities /// + [PrebuildSetup(typeof(PlayModeTestXRDisabler))] + [PostBuildCleanup(typeof(PlayModeTestXRDisabler))] public abstract class BaseRuntimeTests { int originalTargetFrameRate = 0; diff --git a/org.mixedrealitytoolkit.core/Tests/TestUtilities/MRTK.Core.TestUtilities.asmdef b/org.mixedrealitytoolkit.core/Tests/TestUtilities/MRTK.Core.TestUtilities.asmdef index ac3cc28ee..8e7d3f29a 100644 --- a/org.mixedrealitytoolkit.core/Tests/TestUtilities/MRTK.Core.TestUtilities.asmdef +++ b/org.mixedrealitytoolkit.core/Tests/TestUtilities/MRTK.Core.TestUtilities.asmdef @@ -6,7 +6,9 @@ "Unity.InputSystem", "UnityEditor.TestRunner", "UnityEngine.TestRunner", - "Unity.asset-store-validation.Editor" + "Unity.asset-store-validation.Editor", + "Unity.XR.Management", + "Unity.XR.Management.Editor" ], "includePlatforms": [], "excludePlatforms": [], diff --git a/org.mixedrealitytoolkit.core/Tests/TestUtilities/PlayModeTestXRDisabler.cs b/org.mixedrealitytoolkit.core/Tests/TestUtilities/PlayModeTestXRDisabler.cs new file mode 100644 index 000000000..a05fdc35e --- /dev/null +++ b/org.mixedrealitytoolkit.core/Tests/TestUtilities/PlayModeTestXRDisabler.cs @@ -0,0 +1,150 @@ +// Copyright (c) Mixed Reality Toolkit Contributors +// Licensed under the BSD 3-Clause + +// Disable "missing XML comment" warning for tests. While nice to have, this documentation is not required. +#pragma warning disable CS1591 + +using System; +using UnityEngine; +using UnityEngine.TestTools; + +#if UNITY_EDITOR +using UnityEditor; +using UnityEditor.XR.Management; +using UnityEngine.XR.Management; +#endif + +namespace MixedReality.Toolkit.Core.Tests +{ + /// + /// Ensures XR initialization on start is disabled during PlayMode tests so that input simulation + /// and synthetic hands function properly without being suppressed by active OpenXR or XR displays. + /// State is maintained in memory without dirtying project settings on disk, and restored to its original value upon test completion or exit. + /// + internal class PlayModeTestXRDisabler : IPrebuildSetup, IPostBuildCleanup + { + private const string WasXRDisabledByTestKey = "MRTK_PlayModeTest_XRDisabledByTest"; + private static bool isSubscribed; + + /// + void IPrebuildSetup.Setup() + { +#if UNITY_EDITOR + SubscribePlayModeStateChanged(); + DisableXRIfEnabled(); +#endif + } + + /// + void IPostBuildCleanup.Cleanup() + { +#if UNITY_EDITOR + UnsubscribePlayModeStateChanged(); + RestoreXR(); +#endif + } + + [RuntimeInitializeOnLoadMethod(RuntimeInitializeLoadType.SubsystemRegistration)] + private static void OnSubsystemRegistration() + { +#if UNITY_EDITOR + if (SessionState.GetBool(WasXRDisabledByTestKey, false)) + { + ApplyDisableXR(); + } +#endif + } + +#if UNITY_EDITOR + private static void SubscribePlayModeStateChanged() + { + if (!isSubscribed) + { + EditorApplication.playModeStateChanged += OnPlayModeStateChanged; + isSubscribed = true; + } + } + + private static void UnsubscribePlayModeStateChanged() + { + if (isSubscribed) + { + EditorApplication.playModeStateChanged -= OnPlayModeStateChanged; + isSubscribed = false; + } + } + + private static void OnPlayModeStateChanged(PlayModeStateChange state) + { + if (state == PlayModeStateChange.EnteredEditMode) + { + UnsubscribePlayModeStateChanged(); + RestoreXR(); + } + } + + private static XRGeneralSettings GetTargetSettings() + { + try + { + BuildTargetGroup targetGroup = BuildPipeline.GetBuildTargetGroup(EditorUserBuildSettings.activeBuildTarget); + return XRGeneralSettingsPerBuildTarget.XRGeneralSettingsForBuildTarget(targetGroup); + } + catch (Exception e) + { + Debug.LogWarning($"[PlayModeTestXRDisabler] Failed to get XRGeneralSettings: {e.Message}"); + return null; + } + } + + private static void DisableXRIfEnabled() + { + XRGeneralSettings settings = GetTargetSettings(); + bool isEnabledOnTarget = settings != null && settings.InitManagerOnStart; + bool isEnabledOnInstance = XRGeneralSettings.Instance != null && XRGeneralSettings.Instance.InitManagerOnStart; + + if (isEnabledOnTarget || isEnabledOnInstance) + { + SessionState.SetBool(WasXRDisabledByTestKey, true); + ApplyDisableXR(); + } + } + + private static void ApplyDisableXR() + { + if (XRGeneralSettings.Instance != null) + { + XRGeneralSettings.Instance.InitManagerOnStart = false; + } + + XRGeneralSettings settings = GetTargetSettings(); + if (settings != null) + { + settings.InitManagerOnStart = false; + } + } + + private static void RestoreXR() + { + if (!SessionState.GetBool(WasXRDisabledByTestKey, false)) + { + return; + } + + SessionState.EraseBool(WasXRDisabledByTestKey); + + XRGeneralSettings settings = GetTargetSettings(); + if (settings != null) + { + settings.InitManagerOnStart = true; + } + + if (XRGeneralSettings.Instance != null) + { + XRGeneralSettings.Instance.InitManagerOnStart = true; + } + } +#endif + } +} +#pragma warning restore CS1591 diff --git a/org.mixedrealitytoolkit.core/Tests/TestUtilities/PlayModeTestXRDisabler.cs.meta b/org.mixedrealitytoolkit.core/Tests/TestUtilities/PlayModeTestXRDisabler.cs.meta new file mode 100644 index 000000000..3b3d4aeae --- /dev/null +++ b/org.mixedrealitytoolkit.core/Tests/TestUtilities/PlayModeTestXRDisabler.cs.meta @@ -0,0 +1,11 @@ +fileFormatVersion: 2 +guid: 270301c6dca942958f7a6c42db4652fa +MonoImporter: + externalObjects: {} + serializedVersion: 2 + defaultReferences: [] + executionOrder: 0 + icon: {instanceID: 0} + userData: + assetBundleName: + assetBundleVariant: