From 1954cbfc84e96974ad0f953bb34c54f521da4089 Mon Sep 17 00:00:00 2001 From: Kishan P Rao Date: Thu, 25 Jun 2026 17:03:03 +0200 Subject: [PATCH 1/4] Fixes scoped attributes test, wherein skipped legacy attributes are now processed if scoped attributes don't exist --- Runtime/Native/Windows/NativeClient.cs | 44 ++++++++++++-------------- 1 file changed, 20 insertions(+), 24 deletions(-) diff --git a/Runtime/Native/Windows/NativeClient.cs b/Runtime/Native/Windows/NativeClient.cs index 19eed48f..a08c2b21 100644 --- a/Runtime/Native/Windows/NativeClient.cs +++ b/Runtime/Native/Windows/NativeClient.cs @@ -390,34 +390,30 @@ internal static void CleanScopedAttributes() internal static IDictionary GetScopedAttributes() { - var attributesJson = PlayerPrefs.GetString(ScopedAttributeListKey); - if (!HasScopedAttributesEmpty(attributesJson)) - { - return new Dictionary(); - } - var result = new Dictionary(StringComparer.Ordinal); - ScopedAttributesContainer attributes = null; - - try - { - attributes = JsonUtility.FromJson(attributesJson); - } - catch (Exception e) + var attributesJson = PlayerPrefs.GetString(ScopedAttributeListKey); + if (HasScopedAttributesEmpty(attributesJson)) { - Debug.LogWarning($"Backtrace Failed to parse scoped attributes at read: {e.Message}"); - return result; - } + ScopedAttributesContainer attributes; - if (attributes?.Keys == null) - { - return result; - } + try + { + attributes = JsonUtility.FromJson(attributesJson); + } + catch (Exception e) + { + Debug.LogWarning($"Backtrace Failed to parse scoped attributes at read: {e.Message}"); + return result; + } - foreach (var attributeKey in attributes.Keys) - { - var value = PlayerPrefs.GetString(string.Format(ScopedAttributesPattern, attributeKey), string.Empty); - result[attributeKey] = value; + if (attributes?.Keys != null) + { + foreach (var attributeKey in attributes.Keys) + { + var value = PlayerPrefs.GetString(string.Format(ScopedAttributesPattern, attributeKey), string.Empty); + result[attributeKey] = value; + } + } } // extend scoped attributes with legacy attributes stored by Backtrace-Unity library in previous versions From 8116136101416c211a3158dd02fe82d1c61ac974 Mon Sep 17 00:00:00 2001 From: Kishan P Rao Date: Thu, 2 Jul 2026 15:43:07 +0200 Subject: [PATCH 2/4] Guarded editor tests running in Player mode --- Tests/Runtime/BacktraceStackTraceTests.cs | 9 +++++++++ .../Runtime/Client/BacktraceClientDisableTests.cs | 13 +++++++++++++ .../Native/Windows/ScopedNativeAttributesTests.cs | 14 +++++++++++++- 3 files changed, 35 insertions(+), 1 deletion(-) diff --git a/Tests/Runtime/BacktraceStackTraceTests.cs b/Tests/Runtime/BacktraceStackTraceTests.cs index c2facbdf..b5ef375c 100644 --- a/Tests/Runtime/BacktraceStackTraceTests.cs +++ b/Tests/Runtime/BacktraceStackTraceTests.cs @@ -331,6 +331,9 @@ public IEnumerator TestStackTraceCreation_ShouldUseEnvStackTraceWhenExStackTrace [Test] public void TestStackTraceCreation_AndroidException_ValidStackTraceObject() { +#if !UNITY_ANDROID && !UNITY_EDITOR + Assert.Ignore("Android stack trace parsing is only compiled for Android and the Editor."); +#endif var stackTrace = ConvertStackTraceToString(_anrStackTrace); var exception = new BacktraceUnhandledException(string.Empty, stackTrace); var backtraceStackTrace = new BacktraceStackTrace(exception); @@ -348,6 +351,9 @@ public void TestStackTraceCreation_AndroidException_ValidStackTraceObject() [Test] public void TestNativeStackTraceDetection_AndroidExceptionShouldSetFlag_NativeStackTraceIsSet() { +#if !UNITY_ANDROID && !UNITY_EDITOR + Assert.Ignore("Android stack trace parsing is only compiled for Android and the Editor."); +#endif var stackTrace = ConvertStackTraceToString(_anrStackTrace); var exception = new BacktraceUnhandledException(string.Empty, stackTrace); Assert.IsTrue(exception.NativeStackTrace); @@ -366,6 +372,9 @@ public void TestNativeStackTraceDetection_UnityExceptionShouldNotSetFlag_NativeS [Test] public void TestStackTraceCreation_AndroidMixModeCallStack_ValidStackTraceObject() { +#if !UNITY_ANDROID && !UNITY_EDITOR + Assert.Ignore("Android stack trace parsing is only compiled for Android and the Editor."); +#endif var stackTrace = ConvertStackTraceToString(_mixModeCallStack); var exception = new BacktraceUnhandledException(string.Empty, stackTrace); var backtraceStackTrace = new BacktraceStackTrace(exception); diff --git a/Tests/Runtime/Client/BacktraceClientDisableTests.cs b/Tests/Runtime/Client/BacktraceClientDisableTests.cs index df72a99c..874c250a 100644 --- a/Tests/Runtime/Client/BacktraceClientDisableTests.cs +++ b/Tests/Runtime/Client/BacktraceClientDisableTests.cs @@ -18,9 +18,19 @@ public void Setup() AfterSetup(false); } + // Skip in a Player build: DisableInEditor only disables the client in the Editor + private static void SkipWhenNotInEditor() + { + if (!Application.isEditor) + { + Assert.Ignore("DisableInEditor only applies in the Editor."); + } + } + [Test] public void TestEditorDisabling_ShouldntSendData_ShouldntSendExceptionViaSendAPI() { + SkipWhenNotInEditor(); var clientConfiguration = GetValidClientConfiguration(); BacktraceClient.Configuration = clientConfiguration; BacktraceClient.Configuration.DisableInEditor = true; @@ -42,6 +52,7 @@ public void TestEditorDisabling_ShouldntSendData_ShouldntSendExceptionViaSendAPI [Test] public void TestEditorDisabling_ShouldntSendData_ShouldntSendReportViaSendAPI() { + SkipWhenNotInEditor(); var clientConfiguration = GetValidClientConfiguration(); BacktraceClient.Configuration = clientConfiguration; BacktraceClient.Configuration.DisableInEditor = true; @@ -63,6 +74,7 @@ public void TestEditorDisabling_ShouldntSendData_ShouldntSendReportViaSendAPI() [Test] public void TestEditorDisabling_ShouldntSendData_ShouldntSendMessageViaSendAPI() { + SkipWhenNotInEditor(); var clientConfiguration = GetValidClientConfiguration(); BacktraceClient.Configuration = clientConfiguration; BacktraceClient.Configuration.DisableInEditor = true; @@ -84,6 +96,7 @@ public void TestEditorDisabling_ShouldntSendData_ShouldntSendMessageViaSendAPI() [Test] public void TestEditorDisabling_ShouldntSendData_ShouldntSendUnhandledException() { + SkipWhenNotInEditor(); var clientConfiguration = GetValidClientConfiguration(); BacktraceClient.Configuration = clientConfiguration; BacktraceClient.Configuration.DisableInEditor = true; diff --git a/Tests/Runtime/Native/Windows/ScopedNativeAttributesTests.cs b/Tests/Runtime/Native/Windows/ScopedNativeAttributesTests.cs index d2b1db67..c51d8f47 100644 --- a/Tests/Runtime/Native/Windows/ScopedNativeAttributesTests.cs +++ b/Tests/Runtime/Native/Windows/ScopedNativeAttributesTests.cs @@ -14,8 +14,20 @@ public sealed class ScopedNativeAttributesTests private const string ScopedKeyList = "backtrace-scoped-attributes"; private const string ScopedValuePattern = "bt-{0}"; - [TearDown] + // PlayerPrefs persist between runs in a Player build, so clean before and after each test + [SetUp] public void Setup() + { + CleanState(); + } + + [TearDown] + public void Cleanup() + { + CleanState(); + } + + private void CleanState() { CleanLegacyAttributes(); NativeClient.CleanScopedAttributes(); From 0bb22b896df75957dce390f94c9b7f926057db3d Mon Sep 17 00:00:00 2001 From: Kishan P Rao Date: Thu, 2 Jul 2026 16:06:29 +0200 Subject: [PATCH 3/4] Update confusing method name --- Runtime/Native/Windows/NativeClient.cs | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/Runtime/Native/Windows/NativeClient.cs b/Runtime/Native/Windows/NativeClient.cs index a08c2b21..1c4bad50 100644 --- a/Runtime/Native/Windows/NativeClient.cs +++ b/Runtime/Native/Windows/NativeClient.cs @@ -364,7 +364,7 @@ internal static void CleanScopedAttributes() // the reason behind this decision is to make sure user change in the configuration // won't leave any useless data var attributesJson = PlayerPrefs.GetString(ScopedAttributeListKey); - if (!HasScopedAttributesEmpty(attributesJson)) + if (!HasScopedAttributes(attributesJson)) { return; } @@ -392,7 +392,7 @@ internal static IDictionary GetScopedAttributes() { var result = new Dictionary(StringComparer.Ordinal); var attributesJson = PlayerPrefs.GetString(ScopedAttributeListKey); - if (HasScopedAttributesEmpty(attributesJson)) + if (HasScopedAttributes(attributesJson)) { ScopedAttributesContainer attributes; @@ -455,7 +455,7 @@ private HashSet LoadScopedKeys() { var keys = new HashSet(StringComparer.Ordinal); var attributesJson = PlayerPrefs.GetString(ScopedAttributeListKey); - if (HasScopedAttributesEmpty(attributesJson)) + if (HasScopedAttributes(attributesJson)) { try { @@ -582,7 +582,7 @@ private void AddScopedAttribute(string key, string value) PlayerPrefs.Save(); } - private static bool HasScopedAttributesEmpty(string attributesJson) + private static bool HasScopedAttributes(string attributesJson) { return !(string.IsNullOrEmpty(attributesJson) || attributesJson == "{}"); } From 8f7dd971cb32dbbf223d49540456914a7ee3b3b5 Mon Sep 17 00:00:00 2001 From: Kishan P Rao Date: Mon, 6 Jul 2026 14:52:56 +0200 Subject: [PATCH 4/4] Save PlayerPrefs once legacy attributes are processed and deleted from prefs --- Runtime/Native/Windows/NativeClient.cs | 7 +++++++ 1 file changed, 7 insertions(+) diff --git a/Runtime/Native/Windows/NativeClient.cs b/Runtime/Native/Windows/NativeClient.cs index 1c4bad50..bf13a3ba 100644 --- a/Runtime/Native/Windows/NativeClient.cs +++ b/Runtime/Native/Windows/NativeClient.cs @@ -424,6 +424,7 @@ internal static IDictionary GetScopedAttributes() { SessionKey, BacktraceMetrics.ApplicationSessionKey } }; + bool removedLegacy = false; foreach (var legacyAttribute in legacyAttributes) { string legacyAttributeValue = PlayerPrefs.GetString(legacyAttribute.Key, string.Empty); @@ -431,8 +432,14 @@ internal static IDictionary GetScopedAttributes() { PlayerPrefs.DeleteKey(legacyAttribute.Key); result[legacyAttribute.Value] = legacyAttributeValue; + removedLegacy = true; } } + + if (removedLegacy) + { + PlayerPrefs.Save(); + } return result; }