diff --git a/CHANGELOG.md b/CHANGELOG.md index 6130860ee3..1da07233a0 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -4,6 +4,7 @@ ### Fixes +- Clear contexts when calling `Scope.clear()` ([#5902](https://github.com/getsentry/sentry-java/pull/5902)) - Preserve custom `Throwable` identities when R8 optimizes Android apps ([#5881](https://github.com/getsentry/sentry-java/pull/5881)) ## 8.52.0 diff --git a/sentry/api/sentry.api b/sentry/api/sentry.api index 51cffae330..43de4164a4 100644 --- a/sentry/api/sentry.api +++ b/sentry/api/sentry.api @@ -227,6 +227,7 @@ public final class io/sentry/CheckInStatus : java/lang/Enum { public final class io/sentry/CombinedContextsView : io/sentry/protocol/Contexts { public fun (Lio/sentry/protocol/Contexts;Lio/sentry/protocol/Contexts;Lio/sentry/protocol/Contexts;Lio/sentry/ScopeType;)V + public fun clear ()V public fun containsKey (Ljava/lang/Object;)Z public fun entrySet ()Ljava/util/Set; public fun get (Ljava/lang/Object;)Ljava/lang/Object; @@ -5795,6 +5796,7 @@ public class io/sentry/protocol/Contexts : io/sentry/JsonSerializable { protected final field responseLock Lio/sentry/util/AutoClosableReentrantLock; public fun ()V public fun (Lio/sentry/protocol/Contexts;)V + public fun clear ()V public fun containsKey (Ljava/lang/Object;)Z public fun entrySet ()Ljava/util/Set; public fun equals (Ljava/lang/Object;)Z diff --git a/sentry/src/main/java/io/sentry/CombinedContextsView.java b/sentry/src/main/java/io/sentry/CombinedContextsView.java index 3cb10e88b8..d9a8b14c62 100644 --- a/sentry/src/main/java/io/sentry/CombinedContextsView.java +++ b/sentry/src/main/java/io/sentry/CombinedContextsView.java @@ -293,6 +293,11 @@ public boolean containsKey(final @Nullable Object key) { return getDefaultContexts().remove(key); } + @Override + public void clear() { + getDefaultContexts().clear(); + } + @Override public @NotNull Enumeration keys() { return mergeContexts().keys(); diff --git a/sentry/src/main/java/io/sentry/Scope.java b/sentry/src/main/java/io/sentry/Scope.java index 195e5b5b05..25f36cd3f5 100644 --- a/sentry/src/main/java/io/sentry/Scope.java +++ b/sentry/src/main/java/io/sentry/Scope.java @@ -575,10 +575,15 @@ public void clear() { tags.clear(); attributes.clear(); extra.clear(); + contexts.clear(); eventProcessors.clear(); clearTransaction(); clearAttachments(); clearFeatureFlags(); + + for (final IScopeObserver observer : options.getScopeObservers()) { + observer.setContexts(contexts); + } } /** diff --git a/sentry/src/main/java/io/sentry/protocol/Contexts.java b/sentry/src/main/java/io/sentry/protocol/Contexts.java index 83a770eb0f..f6ba635383 100644 --- a/sentry/src/main/java/io/sentry/protocol/Contexts.java +++ b/sentry/src/main/java/io/sentry/protocol/Contexts.java @@ -248,6 +248,10 @@ public boolean containsKey(final @Nullable Object key) { return internalStorage.remove(key); } + public void clear() { + internalStorage.clear(); + } + public @NotNull Enumeration keys() { return internalStorage.keys(); } diff --git a/sentry/src/test/java/io/sentry/CombinedContextsViewTest.kt b/sentry/src/test/java/io/sentry/CombinedContextsViewTest.kt index 43ded1ad2a..0fb7d07c0a 100644 --- a/sentry/src/test/java/io/sentry/CombinedContextsViewTest.kt +++ b/sentry/src/test/java/io/sentry/CombinedContextsViewTest.kt @@ -605,6 +605,20 @@ class CombinedContextsViewTest { assertEquals("global", fixture.global.get("test")) } + @Test + fun `clear clears default context only`() { + val combined = fixture.getSut() + fixture.current.put("test", "current") + fixture.isolation.put("test", "isolation") + fixture.global.put("test", "global") + + combined.clear() + + assertEquals("current", fixture.current.get("test")) + assertNull(fixture.isolation.get("test")) + assertEquals("global", fixture.global.get("test")) + } + @Test fun `set null value on context does not cause exception`() { val combined = fixture.getSut() diff --git a/sentry/src/test/java/io/sentry/ScopeTest.kt b/sentry/src/test/java/io/sentry/ScopeTest.kt index 7af4f6ccdc..be6a22516c 100644 --- a/sentry/src/test/java/io/sentry/ScopeTest.kt +++ b/sentry/src/test/java/io/sentry/ScopeTest.kt @@ -292,6 +292,7 @@ class ScopeTest { scope.screen = "MainActivity" scope.setExtra("some", "extra") scope.setAttribute("some", "attribute") + scope.setContexts("some", "context") scope.addEventProcessor(eventProcessor()) scope.addAttachment(Attachment("path")) scope.addFeatureFlag("flag", true) @@ -308,6 +309,7 @@ class ScopeTest { assertEquals(0, scope.tags.size) assertEquals(0, scope.attributes.size) assertEquals(0, scope.extras.size) + assertTrue(scope.contexts.isEmpty) assertEquals(0, scope.eventProcessors.size) assertEquals(0, scope.attachments.size) assertEquals(0, scope.featureFlags!!.values.size) @@ -886,6 +888,21 @@ class ScopeTest { verify(observer).setContexts(argThat { (get("test") as Obj).stuff == 3 }) } + @Test + fun `Scope clear contexts sync scopes`() { + val observer = mock() + val options = SentryOptions().apply { addScopeObserver(observer) } + val scope = Scope(options) + + // Populate the live contexts directly so the observer is only notified by clear(). + scope.contexts.set("test", "value") + assertFalse(scope.contexts.isEmpty) + + scope.clear() + assertTrue(scope.contexts.isEmpty) + verify(observer).setContexts(argThat { isEmpty }) + } + @Test fun `Scope set propagation context sync scopes`() { val observer = mock() diff --git a/sentry/src/test/java/io/sentry/protocol/ContextsTest.kt b/sentry/src/test/java/io/sentry/protocol/ContextsTest.kt index 0035b733b4..7a3f425e99 100644 --- a/sentry/src/test/java/io/sentry/protocol/ContextsTest.kt +++ b/sentry/src/test/java/io/sentry/protocol/ContextsTest.kt @@ -7,6 +7,7 @@ import kotlin.test.assertEquals import kotlin.test.assertFalse import kotlin.test.assertNotNull import kotlin.test.assertNotSame +import kotlin.test.assertTrue class ContextsTest { @Test @@ -137,4 +138,15 @@ class ContextsTest { assertEquals(listOf("a"), contexts.keys().toList()) } + + @Test + fun `clear removes all entries from context`() { + val contexts = Contexts() + contexts.put("a", "1") + contexts.put("b", "2") + + contexts.clear() + + assertTrue(contexts.isEmpty) + } }