Skip to content

Commit d156b68

Browse files
authored
Merge branch 'main' into feat/e2e-console-app-samples
2 parents 59e4f2b + 0d667ec commit d156b68

File tree

14 files changed

+312
-6
lines changed

14 files changed

+312
-6
lines changed

.cursor/rules/coding.mdc

Lines changed: 53 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,53 @@
1+
---
2+
alwaysApply: true
3+
description: Cursor Coding Rules
4+
---
5+
6+
# Contributing Rules for Agents
7+
8+
## Overview
9+
10+
sentry-java is the Java and Android SDK for Sentry. This repository contains the source code and examples for SDK usage.
11+
12+
## Tech Stack
13+
14+
- **Language**: Java and Kotlin
15+
- **Build Framework**: Gradle
16+
17+
## Key Commands
18+
19+
```bash
20+
# Format code and regenerate .api files
21+
./gradlew spotlessApply apiDump
22+
23+
# Run all tests and linter
24+
./gradlew check
25+
26+
# Run unit tests for a specific file
27+
./gradle ':<module>:testDebugUnitTest' --tests="*<file name>*" --info
28+
```
29+
30+
## Contributing Guidelines
31+
32+
1. Follow existing code style and language
33+
2. Do not modify the API files (e.g. sentry.api) manually, instead run `./gradlew apiDump` to regenerate them
34+
3. Write comprehensive tests
35+
4. New features should always be opt-in by default, extend `SentryOptions` or similar Option classes with getters and setters to enable/disable a new feature
36+
5. Consider backwards compatibility
37+
38+
## Coding rules
39+
40+
1. First think through the problem, read the codebase for relevant files, and propose a plan
41+
2. Before you begin working, check in with me and I will verify the plan
42+
3. Then, begin working on the todo items, marking them as complete as you go
43+
4. Please do not describe every step of the way and just give me a high level explanation of what changes you made
44+
5. Make every task and code change you do as simple as possible. We want to avoid making any massive or complex changes. Every change should impact as little code as possible. Everything is about simplicity.
45+
6. Once you're done, format the code and regenerate the .api files using the following command `./gradlew spotlessApply apiDump`
46+
7. As a last step, git stage the relevant files and propose (but not execute) a single git commit command (e.g. `git commit -m "<git commit message>"`)
47+
48+
49+
## Useful Resources
50+
51+
- Main SDK documentation: https://develop.sentry.dev/sdk/overview/
52+
- Internal contributing guide: https://docs.sentry.io/internal/contributing/
53+
- Git commit messages conventions: https://develop.sentry.dev/engineering-practices/commit-messages/

CHANGELOG.md

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -2,9 +2,15 @@
22

33
## Unreleased
44

5+
### Features
6+
7+
- Add deadlineTimeout option ([#4555](https://github.com/getsentry/sentry-java/pull/4555))
8+
59
### Fixes
610

711
- Allow multiple UncaughtExceptionHandlerIntegrations to be active at the same time ([#4462](https://github.com/getsentry/sentry-java/pull/4462))
12+
- Prevent repeated scroll target determination during a single scroll gesture ([#4557](https://github.com/getsentry/sentry-java/pull/4557))
13+
- This should reduce the number of ANRs seen in `SentryGestureListener`
814

915
## 8.17.0
1016

sentry-android-core/src/main/java/io/sentry/android/core/ActivityLifecycleIntegration.java

Lines changed: 5 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -189,8 +189,12 @@ private void startTracing(final @NotNull Activity activity) {
189189
}
190190

191191
final TransactionOptions transactionOptions = new TransactionOptions();
192+
193+
// Set deadline timeout based on configured option
194+
final long deadlineTimeoutMillis = options.getDeadlineTimeout();
195+
// No deadline when zero or negative value is set
192196
transactionOptions.setDeadlineTimeout(
193-
TransactionOptions.DEFAULT_DEADLINE_TIMEOUT_AUTO_TRANSACTION);
197+
deadlineTimeoutMillis <= 0 ? null : deadlineTimeoutMillis);
194198

195199
if (options.isEnableActivityLifecycleTracingAutoFinish()) {
196200
transactionOptions.setIdleTimeout(options.getIdleTimeout());

sentry-android-core/src/main/java/io/sentry/android/core/ManifestMetadataReader.java

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -127,6 +127,8 @@ final class ManifestMetadataReader {
127127
static final String ENABLE_AUTO_TRACE_ID_GENERATION =
128128
"io.sentry.traces.enable-auto-id-generation";
129129

130+
static final String DEADLINE_TIMEOUT = "io.sentry.traces.deadline-timeout";
131+
130132
static final String FEEDBACK_NAME_REQUIRED = "io.sentry.feedback.is-name-required";
131133

132134
static final String FEEDBACK_SHOW_NAME = "io.sentry.feedback.show-name";
@@ -446,6 +448,9 @@ static void applyMetadata(
446448
ENABLE_AUTO_TRACE_ID_GENERATION,
447449
options.isEnableAutoTraceIdGeneration()));
448450

451+
options.setDeadlineTimeout(
452+
readLong(metadata, logger, DEADLINE_TIMEOUT, options.getDeadlineTimeout()));
453+
449454
if (options.getSessionReplay().getSessionSampleRate() == null) {
450455
final double sessionSampleRate =
451456
readDouble(metadata, logger, REPLAYS_SESSION_SAMPLE_RATE);

sentry-android-core/src/main/java/io/sentry/android/core/internal/gestures/SentryGestureListener.java

Lines changed: 7 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -139,6 +139,7 @@ public boolean onScroll(
139139
options
140140
.getLogger()
141141
.log(SentryLevel.DEBUG, "Unable to find scroll target. No breadcrumb captured.");
142+
scrollState.type = GestureType.Scroll;
142143
return false;
143144
} else {
144145
options
@@ -249,8 +250,13 @@ private void startTracing(final @NotNull UiElement target, final @NotNull Gestur
249250

250251
final TransactionOptions transactionOptions = new TransactionOptions();
251252
transactionOptions.setWaitForChildren(true);
253+
254+
// Set deadline timeout based on configured option
255+
final long deadlineTimeoutMillis = options.getDeadlineTimeout();
256+
// No deadline when zero or negative value is set
252257
transactionOptions.setDeadlineTimeout(
253-
TransactionOptions.DEFAULT_DEADLINE_TIMEOUT_AUTO_TRANSACTION);
258+
deadlineTimeoutMillis <= 0 ? null : deadlineTimeoutMillis);
259+
254260
transactionOptions.setIdleTimeout(options.getIdleTimeout());
255261
transactionOptions.setTrimEnd(true);
256262
transactionOptions.setOrigin(TRACE_ORIGIN + "." + target.getOrigin());

sentry-android-core/src/test/java/io/sentry/android/core/ActivityLifecycleIntegrationTest.kt

Lines changed: 74 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -225,6 +225,80 @@ class ActivityLifecycleIntegrationTest {
225225
)
226226
}
227227

228+
@Test
229+
fun `Activity transaction uses custom deadline timeout when autoTransactionDeadlineTimeoutMillis is set to positive value`() {
230+
val sut = fixture.getSut()
231+
fixture.options.tracesSampleRate = 1.0
232+
fixture.options.deadlineTimeout = 60000L // 60 seconds
233+
234+
sut.register(fixture.scopes, fixture.options)
235+
sut.onActivityCreated(mock(), fixture.bundle)
236+
237+
verify(fixture.scopes)
238+
.startTransaction(
239+
any<TransactionContext>(),
240+
check<TransactionOptions> { transactionOptions ->
241+
assertEquals(60000L, transactionOptions.deadlineTimeout)
242+
},
243+
)
244+
}
245+
246+
@Test
247+
fun `Activity transaction uses no deadline timeout when autoTransactionDeadlineTimeoutMillis is set to zero`() {
248+
val sut = fixture.getSut()
249+
fixture.options.tracesSampleRate = 1.0
250+
fixture.options.deadlineTimeout = 0L // No deadline
251+
252+
sut.register(fixture.scopes, fixture.options)
253+
sut.onActivityCreated(mock(), fixture.bundle)
254+
255+
verify(fixture.scopes)
256+
.startTransaction(
257+
any<TransactionContext>(),
258+
check<TransactionOptions> { transactionOptions ->
259+
assertNull(transactionOptions.deadlineTimeout)
260+
},
261+
)
262+
}
263+
264+
@Test
265+
fun `Activity transaction uses no deadline timeout when autoTransactionDeadlineTimeoutMillis is set to negative value`() {
266+
val sut = fixture.getSut()
267+
fixture.options.tracesSampleRate = 1.0
268+
fixture.options.deadlineTimeout = -1L // No deadline
269+
270+
sut.register(fixture.scopes, fixture.options)
271+
sut.onActivityCreated(mock(), fixture.bundle)
272+
273+
verify(fixture.scopes)
274+
.startTransaction(
275+
any<TransactionContext>(),
276+
check<TransactionOptions> { transactionOptions ->
277+
assertNull(transactionOptions.deadlineTimeout)
278+
},
279+
)
280+
}
281+
282+
@Test
283+
fun `Activity transaction uses default deadline timeout when autoTransactionDeadlineTimeoutMillis is default`() {
284+
val sut = fixture.getSut()
285+
fixture.options.tracesSampleRate = 1.0
286+
287+
sut.register(fixture.scopes, fixture.options)
288+
sut.onActivityCreated(mock(), fixture.bundle)
289+
290+
verify(fixture.scopes)
291+
.startTransaction(
292+
any<TransactionContext>(),
293+
check<TransactionOptions> { transactionOptions ->
294+
assertEquals(
295+
TransactionOptions.DEFAULT_DEADLINE_TIMEOUT_AUTO_TRANSACTION,
296+
transactionOptions.deadlineTimeout,
297+
)
298+
},
299+
)
300+
}
301+
228302
@Test
229303
fun `Activity gets added to ActivityFramesTracker during transaction creation`() {
230304
val sut = fixture.getSut()

sentry-android-core/src/test/java/io/sentry/android/core/ManifestMetadataReaderTest.kt

Lines changed: 30 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -9,6 +9,7 @@ import io.sentry.ILogger
99
import io.sentry.ProfileLifecycle
1010
import io.sentry.SentryLevel
1111
import io.sentry.SentryReplayOptions
12+
import io.sentry.TransactionOptions
1213
import kotlin.test.BeforeTest
1314
import kotlin.test.Test
1415
import kotlin.test.assertEquals
@@ -1091,6 +1092,35 @@ class ManifestMetadataReaderTest {
10911092
assertEquals(expectedIdleTimeout.toLong(), fixture.options.idleTimeout)
10921093
}
10931094

1095+
@Test
1096+
fun `applyMetadata reads autoTransactionDeadlineTimeoutMillis from metadata`() {
1097+
// Arrange
1098+
val expectedTimeout = 60000
1099+
val bundle = bundleOf(ManifestMetadataReader.DEADLINE_TIMEOUT to expectedTimeout)
1100+
val context = fixture.getContext(metaData = bundle)
1101+
1102+
// Act
1103+
ManifestMetadataReader.applyMetadata(context, fixture.options, fixture.buildInfoProvider)
1104+
1105+
// Assert
1106+
assertEquals(expectedTimeout.toLong(), fixture.options.deadlineTimeout)
1107+
}
1108+
1109+
@Test
1110+
fun `applyMetadata reads autoTransactionDeadlineTimeoutMillis from metadata and keep default value if not found`() {
1111+
// Arrange
1112+
val context = fixture.getContext()
1113+
1114+
// Act
1115+
ManifestMetadataReader.applyMetadata(context, fixture.options, fixture.buildInfoProvider)
1116+
1117+
// Assert
1118+
assertEquals(
1119+
TransactionOptions.DEFAULT_DEADLINE_TIMEOUT_AUTO_TRANSACTION,
1120+
fixture.options.deadlineTimeout,
1121+
)
1122+
}
1123+
10941124
@Test
10951125
fun `applyMetadata without specifying idleTimeout, stays default`() {
10961126
// Arrange

sentry-android-core/src/test/java/io/sentry/android/core/internal/gestures/SentryGestureListenerScrollTest.kt

Lines changed: 20 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -11,11 +11,13 @@ import android.widget.AbsListView
1111
import android.widget.ListAdapter
1212
import androidx.core.view.ScrollingView
1313
import io.sentry.Breadcrumb
14+
import io.sentry.ILogger
1415
import io.sentry.IScope
1516
import io.sentry.IScopes
1617
import io.sentry.PropagationContext
1718
import io.sentry.Scope
1819
import io.sentry.ScopeCallback
20+
import io.sentry.SentryLevel
1921
import io.sentry.SentryLevel.INFO
2022
import io.sentry.android.core.SentryAndroidOptions
2123
import kotlin.test.Test
@@ -28,6 +30,7 @@ import org.mockito.kotlin.doAnswer
2830
import org.mockito.kotlin.inOrder
2931
import org.mockito.kotlin.mock
3032
import org.mockito.kotlin.never
33+
import org.mockito.kotlin.times
3134
import org.mockito.kotlin.verify
3235
import org.mockito.kotlin.verifyNoMoreInteractions
3336
import org.mockito.kotlin.whenever
@@ -56,7 +59,7 @@ class SentryGestureListenerScrollTest {
5659
val directions = setOf("up", "down", "left", "right")
5760

5861
internal inline fun <reified T : View> getSut(
59-
resourceName: String = "test_scroll_view",
62+
resourceName: String? = "test_scroll_view",
6063
touchWithinBounds: Boolean = true,
6164
direction: String = "",
6265
): SentryGestureListener {
@@ -229,6 +232,22 @@ class SentryGestureListenerScrollTest {
229232
verify(fixture.scope).propagationContext = any()
230233
}
231234

235+
@Test
236+
fun `logs error message only once per gesture when no scroll target is found`() {
237+
val logger = mock<ILogger>()
238+
fixture.options.setLogger(logger)
239+
fixture.options.isDebug = true
240+
val sut = fixture.getSut<ScrollableListView>(resourceName = null)
241+
242+
sut.onDown(fixture.firstEvent)
243+
fixture.eventsInBetween.forEach { sut.onScroll(fixture.firstEvent, it, 10.0f, 0f) }
244+
sut.onUp(fixture.endEvent)
245+
246+
// Verify that the error message is logged only once during the entire gesture
247+
verify(logger, times(1))
248+
.log(SentryLevel.DEBUG, "Unable to find scroll target. No breadcrumb captured.")
249+
}
250+
232251
internal class ScrollableView : View(mock()), ScrollingView {
233252
override fun computeVerticalScrollOffset(): Int = 0
234253

sentry-android-core/src/test/java/io/sentry/android/core/internal/gestures/ViewHelpers.kt

Lines changed: 7 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -67,6 +67,11 @@ internal inline fun <reified T : View> mockView(
6767
return mockView
6868
}
6969

70-
internal fun Resources.mockForTarget(target: View, expectedResourceName: String) {
71-
whenever(getResourceEntryName(target.id)).thenReturn(expectedResourceName)
70+
internal fun Resources.mockForTarget(target: View, expectedResourceName: String?) {
71+
if (expectedResourceName == null) {
72+
whenever(getResourceEntryName(target.id))
73+
.thenThrow(Resources.NotFoundException("res not found"))
74+
} else {
75+
whenever(getResourceEntryName(target.id)).thenReturn(expectedResourceName)
76+
}
7277
}

sentry-android-navigation/src/main/java/io/sentry/android/navigation/SentryNavigationListener.kt

Lines changed: 6 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -131,7 +131,12 @@ constructor(
131131
TransactionOptions().also {
132132
it.isWaitForChildren = true
133133
it.idleTimeout = scopes.options.idleTimeout
134-
it.deadlineTimeout = TransactionOptions.DEFAULT_DEADLINE_TIMEOUT_AUTO_TRANSACTION
134+
135+
// Set deadline timeout based on configured option
136+
val deadlineTimeoutMillis = scopes.options.deadlineTimeout
137+
// No deadline when zero or negative value is set
138+
it.deadlineTimeout = if (deadlineTimeoutMillis <= 0) null else deadlineTimeoutMillis
139+
135140
it.isTrimEnd = true
136141
}
137142

0 commit comments

Comments
 (0)