diff --git a/src/Sentry.Unity/Integrations/IApplication.cs b/src/Sentry.Unity/Integrations/IApplication.cs index b172a96eb..8d75ca67b 100644 --- a/src/Sentry.Unity/Integrations/IApplication.cs +++ b/src/Sentry.Unity/Integrations/IApplication.cs @@ -15,6 +15,7 @@ internal interface IApplication string Version { get; } string PersistentDataPath { get; } bool IsMainThread { get; } + RuntimePlatform Platform { get; } } internal sealed class ApplicationAdapter : IApplication @@ -43,6 +44,8 @@ private ApplicationAdapter() public bool IsMainThread => Thread.CurrentThread.ManagedThreadId == 1; + public RuntimePlatform Platform => Application.platform; + private void OnLogMessageReceived(string condition, string stackTrace, LogType type) => LogMessageReceived?.Invoke(condition, stackTrace, type); diff --git a/src/Sentry.Unity/Integrations/SessionIntegration.cs b/src/Sentry.Unity/Integrations/SessionIntegration.cs index 010349a17..0ec43c5ae 100644 --- a/src/Sentry.Unity/Integrations/SessionIntegration.cs +++ b/src/Sentry.Unity/Integrations/SessionIntegration.cs @@ -16,8 +16,8 @@ public void Register(IHub hub, SentryOptions options) options.DiagnosticLogger?.LogDebug("Registering Session integration."); // HideFlags.HideAndDontSave hides the GameObject in the hierarchy and prevents changing of scenes from destroying it - var gameListenerObject = new GameObject("SentryListener") {hideFlags = HideFlags.HideAndDontSave}; - var gameListener = gameListenerObject.AddComponent(); + var gameListenerObject = new GameObject("SentryMonoBehaviour") {hideFlags = HideFlags.HideAndDontSave}; + var gameListener = gameListenerObject.AddComponent(); gameListener.ApplicationResuming += () => { options.DiagnosticLogger?.LogDebug("Resuming session."); diff --git a/src/Sentry.Unity/ApplicationPauseListener.cs b/src/Sentry.Unity/SentryMonoBehaviour.cs similarity index 82% rename from src/Sentry.Unity/ApplicationPauseListener.cs rename to src/Sentry.Unity/SentryMonoBehaviour.cs index 668463018..d34e94df1 100644 --- a/src/Sentry.Unity/ApplicationPauseListener.cs +++ b/src/Sentry.Unity/SentryMonoBehaviour.cs @@ -1,4 +1,5 @@ using System; +using Sentry.Unity.Integrations; using UnityEngine; namespace Sentry.Unity @@ -7,7 +8,7 @@ namespace Sentry.Unity /// A MonoBehavior used to forward application focus events to subscribers. /// [DefaultExecutionOrder(-900)] - internal class ApplicationPauseListener : MonoBehaviour + internal class SentryMonoBehaviour : MonoBehaviour { /// /// Hook to receive an event when the application gains focus. @@ -28,6 +29,17 @@ internal class ApplicationPauseListener : MonoBehaviour // Keeping internal track of running state because OnApplicationPause and OnApplicationFocus get called during startup and would fire false resume events private bool _isRunning = true; + private IApplication? _application; + internal IApplication Application + { + get + { + _application ??= ApplicationAdapter.Instance; + return _application; + } + set => _application = value; + } + /// /// To receive Leaving/Resuming events on Android. /// @@ -37,9 +49,9 @@ internal class ApplicationPauseListener : MonoBehaviour /// /// /// - private void OnApplicationPause(bool pauseStatus) + internal void OnApplicationPause(bool pauseStatus) { - if (Application.platform != RuntimePlatform.Android) + if (Application.Platform != RuntimePlatform.Android) { return; } @@ -60,10 +72,10 @@ private void OnApplicationPause(bool pauseStatus) /// To receive Leaving/Resuming events on all platforms except Android. /// /// - private void OnApplicationFocus(bool hasFocus) + internal void OnApplicationFocus(bool hasFocus) { // To avoid event duplication on Android since the pause event will be handled via OnApplicationPause - if (Application.platform == RuntimePlatform.Android) + if (Application.Platform == RuntimePlatform.Android) { return; } diff --git a/src/Sentry.Unity/SentryOptionsUtility.cs b/src/Sentry.Unity/SentryOptionsUtility.cs index a3ee3d168..60c2f1375 100644 --- a/src/Sentry.Unity/SentryOptionsUtility.cs +++ b/src/Sentry.Unity/SentryOptionsUtility.cs @@ -1,4 +1,3 @@ -using System; using Sentry.Unity.Integrations; namespace Sentry.Unity diff --git a/test/Sentry.Unity.Tests/IntegrationTests.cs b/test/Sentry.Unity.Tests/IntegrationTests.cs index c9628411c..c47fbf0fe 100644 --- a/test/Sentry.Unity.Tests/IntegrationTests.cs +++ b/test/Sentry.Unity.Tests/IntegrationTests.cs @@ -232,7 +232,7 @@ public IEnumerator Init_OptionsAreDefaulted() UnitySentryOptionsTests.AssertOptions(expectedOptions, actualOptions!); } - private static IEnumerator SetupSceneCoroutine(string sceneName) + internal static IEnumerator SetupSceneCoroutine(string sceneName) { // load scene with initialized Sentry, SceneManager.LoadSceneAsync(sceneName); SceneManager.LoadScene(sceneName); @@ -244,11 +244,12 @@ private static IEnumerator SetupSceneCoroutine(string sceneName) LogAssert.ignoreFailingMessages = true; } - private static IDisposable InitSentrySdk(Action configure) + internal static IDisposable InitSentrySdk(Action configure) { SentryUnity.Init(options => { options.Dsn = "https://94677106febe46b88b9b9ae5efd18a00@o447951.ingest.sentry.io/5439417"; + configure.Invoke(options); }); return new SentryDisposable(); diff --git a/test/Sentry.Unity.Tests/SentryMonoBehaviourTests.cs b/test/Sentry.Unity.Tests/SentryMonoBehaviourTests.cs new file mode 100644 index 000000000..315db298c --- /dev/null +++ b/test/Sentry.Unity.Tests/SentryMonoBehaviourTests.cs @@ -0,0 +1,151 @@ +using NUnit.Framework; +using Sentry.Unity.Tests.Stubs; +using UnityEngine; + +namespace Sentry.Unity.Tests +{ + public class SentryMonoBehaviourTests + { + private class Fixture + { + public SentryMonoBehaviour GetSut(RuntimePlatform platform) + { + var gameObject = new GameObject("PauseTest"); + var sentryMonoBehaviour = gameObject.AddComponent(); + sentryMonoBehaviour.Application = new TestApplication(platform: platform); + + return sentryMonoBehaviour; + } + } + + private Fixture _fixture = null!; + + [SetUp] + public void SetUp() + { + _fixture = new Fixture(); + } + + [Test] + public void OnApplicationPause_OnAndroid_ApplicationPausingTriggered() + { + var wasPausingCalled = false; + + var sut = _fixture.GetSut(RuntimePlatform.Android); + sut.ApplicationPausing += () => wasPausingCalled = true; + + sut.OnApplicationPause(true); + + Assert.IsTrue(wasPausingCalled); + } + + [Test] + public void OnApplicationPause_NotOnAndroid_ApplicationPausingNotTriggered() + { + var wasPausingCalled = false; + + var sut = _fixture.GetSut(RuntimePlatform.IPhonePlayer); + sut.ApplicationPausing += () => wasPausingCalled = true; + + sut.OnApplicationPause(true); + + Assert.IsFalse(wasPausingCalled); + } + + [Test] + public void OnApplicationPause_OnAndroid_ApplicationPausingTriggeredOnlyOnce() + { + var pauseEventTriggerCounter = 0; + + var sut = _fixture.GetSut(RuntimePlatform.Android); + sut.ApplicationPausing += () => pauseEventTriggerCounter++; + + sut.OnApplicationPause(true); + sut.OnApplicationPause(true); + + Assert.AreEqual(1, pauseEventTriggerCounter); + } + + [Test] + public void OnApplicationPause_OnAndroid_PausingIsRequiredBeforeApplicationResumingTrigger() + { + var wasPausingCalled = false; + var wasResumingCalled = false; + + var sut = _fixture.GetSut(RuntimePlatform.Android); + sut.ApplicationPausing += () => wasPausingCalled = true; + sut.ApplicationResuming += () => wasResumingCalled = true; + + sut.OnApplicationPause(false); + + Assert.IsFalse(wasResumingCalled); + + sut.OnApplicationPause(true); + sut.OnApplicationPause(false); + + Assert.IsTrue(wasPausingCalled); + Assert.IsTrue(wasResumingCalled); + } + + [Test] + public void OnApplicationFocus_OnAndroid_ApplicationPausingNotTriggered() + { + var wasPausingCalled = false; + + var sut = _fixture.GetSut(RuntimePlatform.Android); + sut.ApplicationPausing += () => wasPausingCalled = true; + + sut.OnApplicationFocus(false); + + Assert.IsFalse(wasPausingCalled); + } + + [Test] + public void OnApplicationFocus_NotOnAndroid_ApplicationPausingTriggered() + { + var wasPausingCalled = false; + + var sut = _fixture.GetSut(RuntimePlatform.IPhonePlayer); + sut.ApplicationPausing += () => wasPausingCalled = true; + + sut.OnApplicationFocus(false); + + Assert.IsTrue(wasPausingCalled); + } + + [Test] + public void OnApplicationFocus_NotOnAndroid_ApplicationPausingTriggeredOnlyOnce() + { + var pauseEventTriggerCounter = 0; + + var sut = _fixture.GetSut(RuntimePlatform.IPhonePlayer); + sut.ApplicationPausing += () => pauseEventTriggerCounter++; + + sut.OnApplicationFocus(false); + sut.OnApplicationFocus(false); + + Assert.AreEqual(1, pauseEventTriggerCounter); + } + + [Test] + public void OnApplicationFocus_NotOnAndroid_PausingIsRequiredBeforeApplicationResumingTrigger() + { + var wasPausingCalled = false; + var wasResumingCalled = false; + + var sut = _fixture.GetSut(RuntimePlatform.IPhonePlayer); + sut.ApplicationPausing += () => wasPausingCalled = true; + sut.ApplicationResuming += () => wasResumingCalled = true; + + sut.OnApplicationFocus(true); + + Assert.IsFalse(wasResumingCalled); + + sut.OnApplicationFocus(false); + sut.OnApplicationFocus(true); + + Assert.IsTrue(wasPausingCalled); + Assert.IsTrue(wasResumingCalled); + } + } +} diff --git a/test/Sentry.Unity.Tests/SessionIntegrationTests.cs b/test/Sentry.Unity.Tests/SessionIntegrationTests.cs new file mode 100644 index 000000000..9090056b8 --- /dev/null +++ b/test/Sentry.Unity.Tests/SessionIntegrationTests.cs @@ -0,0 +1,27 @@ +using System.Collections; +using NUnit.Framework; +using UnityEngine; +using UnityEngine.TestTools; + +namespace Sentry.Unity.Tests +{ + public class SessionIntegrationTests + { + [UnityTest] + public IEnumerator SessionIntegration_Init_SentryMonoBehaviourCreated() + { + yield return null; + + using var _ = IntegrationTests.InitSentrySdk(o => + { + // o.AutoSessionTracking = true; We expect this to be true by default + }); + + var sentryGameObject = GameObject.Find("SentryMonoBehaviour"); + var sentryMonoBehaviour = sentryGameObject.GetComponent(); + + Assert.IsNotNull(sentryGameObject); + Assert.IsNotNull(sentryMonoBehaviour); + } + } +} diff --git a/test/Sentry.Unity.Tests/Stubs/TestApplication.cs b/test/Sentry.Unity.Tests/Stubs/TestApplication.cs index b7195cc80..d16e33388 100644 --- a/test/Sentry.Unity.Tests/Stubs/TestApplication.cs +++ b/test/Sentry.Unity.Tests/Stubs/TestApplication.cs @@ -1,7 +1,6 @@ using System; using Sentry.Unity.Integrations; using UnityEngine; -using UnityEngine.TestTools.Constraints; namespace Sentry.Unity.Tests.Stubs { @@ -12,13 +11,16 @@ public TestApplication( string productName = "", string version = "", string persistentDataPath = "", - bool isMainThread = true) + bool isMainThread = true, + RuntimePlatform platform = RuntimePlatform.WindowsEditor + ) { IsEditor = isEditor; ProductName = productName; Version = version; PersistentDataPath = persistentDataPath; IsMainThread = isMainThread; + Platform = platform; } public event Application.LogCallback? LogMessageReceived; @@ -29,7 +31,7 @@ public TestApplication( public string Version { get; } public string PersistentDataPath { get; } public bool IsMainThread { get; } - + public RuntimePlatform Platform { get; } private void OnQuitting() => Quitting?.Invoke(); private void OnLogMessageReceived(string condition, string stacktrace, LogType type)