diff --git a/.github/workflows/agp-matrix.yml b/.github/workflows/agp-matrix.yml index 3b850344aa..6bf9db20d5 100644 --- a/.github/workflows/agp-matrix.yml +++ b/.github/workflows/agp-matrix.yml @@ -4,25 +4,20 @@ on: push: branches: - main - - release/** pull_request: -jobs: - cancel-previous-workflow: - runs-on: ubuntu-latest - steps: - - name: Cancel Previous Runs - uses: styfle/cancel-workflow-action@01ce38bf961b4e243a6342cbade0dbc8ba3f0432 # pin@0.12.0 - with: - access_token: ${{ github.token }} +concurrency: + group: ${{ github.workflow }}-${{ github.ref }} + cancel-in-progress: true +jobs: agp-matrix-compatibility: timeout-minutes: 30 runs-on: ubuntu-latest strategy: fail-fast: false matrix: - agp: [ '8.0.0','8.1.0-alpha11' ] + agp: [ '8.0.0','8.1.4','8.2.0','8.3.0-beta01' ] integrations: [ true, false ] name: AGP Matrix Release - AGP ${{ matrix.agp }} - Integrations ${{ matrix.integrations }} @@ -34,15 +29,15 @@ jobs: - name: Checkout Repo uses: actions/checkout@v4 - - name: Setup Gradle - uses: gradle/gradle-build-action@982da8e78c05368c70dac0351bb82647a9e9a5d2 # pin@v2 - - name: Setup Java Version uses: actions/setup-java@v4 with: distribution: 'temurin' java-version: '17' + - name: Setup Gradle + uses: gradle/gradle-build-action@842c587ad8aa4c68eeba24c396e15af4c2e9f30a # pin@v2 + - name: Setup KVM shell: bash run: | @@ -58,11 +53,6 @@ jobs: - name: Make assembleUiTests run: make assembleUiTests - # We stop gradle at the end to make sure the cache folders - # don't contain any lock files and are free to be cached. - - name: Make stop - run: make stop - # We tried to use the cache action to cache gradle stuff, but it made tests slower and timeout - name: Run instrumentation tests uses: reactivecircus/android-emulator-runner@99a4aac18b4df9b3af66c4a1f04c1f23fa10c270 # pin@v2 @@ -73,6 +63,7 @@ jobs: disable-animations: true disable-spellchecker: true target: 'aosp_atd' + arch: x86 channel: canary # Necessary for ATDs script: ./gradlew sentry-android-integration-tests:sentry-uitest-android:connectedReleaseAndroidTest -DtestBuildType=release --daemon diff --git a/.github/workflows/build.yml b/.github/workflows/build.yml index 2157ccba80..041d6353a6 100644 --- a/.github/workflows/build.yml +++ b/.github/workflows/build.yml @@ -3,66 +3,43 @@ on: push: branches: - main - - release/** pull_request: +concurrency: + group: ${{ github.workflow }}-${{ github.ref }} + cancel-in-progress: true + jobs: build: - name: Build Job ${{ matrix.os }} - Java ${{ matrix.java }} - runs-on: ${{ matrix.os }} - strategy: - # we want that the matrix keeps running, default is to cancel them if it fails. - fail-fast: false - matrix: - os: [ubuntu-latest] - # Zulu Community distribution of OpenJDK - java: ['17'] + name: Build Job ubuntu-latest - Java 17 + runs-on: ubuntu-latest steps: - - name: Git checkout + - name: Checkout Repo uses: actions/checkout@v4 - - name: 'Set up Java: ${{ matrix.java }}' + - name: Setup Java Version uses: actions/setup-java@v4 with: - java-version: ${{ matrix.java }} distribution: 'temurin' + java-version: '17' - - name: Cache Gradle packages - uses: actions/cache@v3 - with: - path: | - ~/.gradle/caches - ~/.gradle/wrapper - key: ${{ runner.os }}-gradle-${{ hashFiles('**/*.gradle*', '**/gradle-wrapper.properties') }} - restore-keys: | - ${{ runner.os }}-gradle- - - # Clean, check formatting, build and do a dry release - - name: Make all - run: make all - - # We stop gradle at the end to make sure the cache folders - # don't contain any lock files and are free to be cached. - - name: Make stop - run: make stop + - name: Setup Gradle + uses: gradle/gradle-build-action@842c587ad8aa4c68eeba24c396e15af4c2e9f30a # pin@v2 - - name: Archive packages - # We need artifacts from only one the builds - if: runner.os == 'Linux' && matrix.java == '17' - uses: actions/upload-artifact@v4 - with: - name: ${{ github.sha }} - if-no-files-found: error - path: | - ./*/build/distributions/*.zip - ./sentry-opentelemetry/*/build/distributions/*.zip - ./sentry-android-ndk/build/intermediates/merged_native_libs/release/out/lib/* + - name: Run Tests with coverage and Lint + run: make preMerge - name: Upload coverage to Codecov - # We need coverage data from only one the builds - if: runner.os == 'Linux' && matrix.java == '17' uses: codecov/codecov-action@eaaf4bedf32dbdc6b720b63067d99c4d77d6047d # pin@v3 with: name: sentry-java fail_ci_if_error: false + + - name: Upload test results + if: always() + uses: actions/upload-artifact@v4 + with: + name: test-results + path: | + **/build/reports/* diff --git a/.github/workflows/codeql-analysis.yml b/.github/workflows/codeql-analysis.yml index b139b002a4..2d9a50c0df 100644 --- a/.github/workflows/codeql-analysis.yml +++ b/.github/workflows/codeql-analysis.yml @@ -9,6 +9,10 @@ on: schedule: - cron: '17 23 * * 3' +concurrency: + group: ${{ github.workflow }}-${{ github.ref }} + cancel-in-progress: true + jobs: analyze: name: Analyze @@ -20,32 +24,31 @@ jobs: language: ['cpp', 'java'] steps: - - name: Checkout repository + - name: Checkout Repo uses: actions/checkout@v4 - - name: 'Set up Java: ${{ matrix.java }}' + - name: Setup Java Version uses: actions/setup-java@v4 with: - java-version: 17 distribution: 'temurin' + java-version: '17' - - name: Cache Gradle packages - uses: actions/cache@v3 - with: - path: | - ~/.gradle/caches - ~/.gradle/wrapper - key: ${{ runner.os }}-gradle-${{ hashFiles('**/*.gradle*', '**/gradle-wrapper.properties') }} - restore-keys: | - ${{ runner.os }}-gradle- + - name: Setup Gradle + uses: gradle/gradle-build-action@842c587ad8aa4c68eeba24c396e15af4c2e9f30a # pin@v2 - name: Initialize CodeQL uses: github/codeql-action/init@cdcdbb579706841c47f7063dda365e292e5cad7a # pin@v2 with: languages: ${{ matrix.language }} - - run: | - ./gradlew assemble + - if: matrix.language == 'cpp' + name: Build Cpp + run: | + ./gradlew sentry-android-ndk:buildCMakeRelWithDebInfo + - if: matrix.language == 'java' + name: Build Java + run: | + ./gradlew buildForCodeQL - name: Perform CodeQL Analysis uses: github/codeql-action/analyze@cdcdbb579706841c47f7063dda365e292e5cad7a # pin@v2 diff --git a/.github/workflows/generate-javadocs.yml b/.github/workflows/generate-javadocs.yml index 9bf273b24e..bdf21a67ec 100644 --- a/.github/workflows/generate-javadocs.yml +++ b/.github/workflows/generate-javadocs.yml @@ -16,15 +16,8 @@ jobs: distribution: 'temurin' java-version: '17' - - name: Cache Gradle packages - uses: actions/cache@v3 - with: - path: | - ~/.gradle/caches - ~/.gradle/wrapper - key: ${{ runner.os }}-gradle-${{ hashFiles('**/*.gradle*', '**/gradle-wrapper.properties') }} - restore-keys: | - ${{ runner.os }}-gradle- + - name: Setup Gradle + uses: gradle/gradle-build-action@842c587ad8aa4c68eeba24c396e15af4c2e9f30a # pin@v2 - name: Generate Aggregate Javadocs run: | diff --git a/.github/workflows/integration-tests-benchmarks.yml b/.github/workflows/integration-tests-benchmarks.yml index 4b1e312bb3..c946337c9f 100644 --- a/.github/workflows/integration-tests-benchmarks.yml +++ b/.github/workflows/integration-tests-benchmarks.yml @@ -11,6 +11,10 @@ on: - '**/sentry-android-integration-tests/**' - '**/.github/**' +concurrency: + group: ${{ github.workflow }}-${{ github.ref }} + cancel-in-progress: true + jobs: test: name: Benchmarks @@ -30,16 +34,14 @@ jobs: java-version: '17' distribution: 'temurin' + - name: Setup Gradle + uses: gradle/gradle-build-action@842c587ad8aa4c68eeba24c396e15af4c2e9f30a # pin@v2 + # Clean, build and release a test apk, but only if we will run the benchmark - name: Make assembleBenchmarks if: env.SAUCE_USERNAME != null run: make assembleBenchmarks - # We stop gradle at the end to make sure the cache folders - # don't contain any lock files and are free to be cached. - - name: Make stop - run: make stop - - name: Run All Tests in SauceLab uses: saucelabs/saucectl-run-action@7fe025ef1fdc6f211add3751a6c7d8bba27ba9b1 # pin@v3 if: github.event_name != 'pull_request' && env.SAUCE_USERNAME != null @@ -77,6 +79,9 @@ jobs: java-version: '17' distribution: 'temurin' + - name: Setup Gradle + uses: gradle/gradle-build-action@842c587ad8aa4c68eeba24c396e15af4c2e9f30a # pin@v2 + - uses: actions/cache@v3 id: app-plain-cache with: diff --git a/.github/workflows/integration-tests-ui.yml b/.github/workflows/integration-tests-ui.yml index 9af4185bff..2143d0a7bf 100644 --- a/.github/workflows/integration-tests-ui.yml +++ b/.github/workflows/integration-tests-ui.yml @@ -3,18 +3,13 @@ on: push: branches: - main - - release/** pull_request: -jobs: - cancel-previous-workflow: - runs-on: ubuntu-latest - steps: - - name: Cancel Previous Runs - uses: styfle/cancel-workflow-action@01ce38bf961b4e243a6342cbade0dbc8ba3f0432 # pin@0.12.0 - with: - access_token: ${{ github.token }} +concurrency: + group: ${{ github.workflow }}-${{ github.ref }} + cancel-in-progress: true +jobs: test: name: Ui tests runs-on: ubuntu-latest @@ -33,16 +28,14 @@ jobs: java-version: '17' distribution: 'temurin' + - name: Setup Gradle + uses: gradle/gradle-build-action@842c587ad8aa4c68eeba24c396e15af4c2e9f30a # pin@v2 + # Clean, build and release a test apk, but only if we will run the benchmark - name: Make assembleUiTests if: env.SAUCE_USERNAME != null run: make assembleUiTests - # We stop gradle at the end to make sure the cache folders - # don't contain any lock files and are free to be cached. - - name: Make stop - run: make stop - - name: Run Tests in SauceLab uses: saucelabs/saucectl-run-action@7fe025ef1fdc6f211add3751a6c7d8bba27ba9b1 # pin@v3 env: diff --git a/.github/workflows/release-build.yml b/.github/workflows/release-build.yml new file mode 100644 index 0000000000..85aa94c583 --- /dev/null +++ b/.github/workflows/release-build.yml @@ -0,0 +1,40 @@ +name: 'Release' +on: + push: + branches: + - release/** + +concurrency: + group: ${{ github.workflow }}-${{ github.ref }} + cancel-in-progress: true + +jobs: + release: + name: Build release artifacts + runs-on: ubuntu-latest + + steps: + - name: Checkout Repo + uses: actions/checkout@v4 + + - name: Setup Java Version + uses: actions/setup-java@v4 + with: + distribution: 'temurin' + java-version: '17' + + - name: Setup Gradle + uses: gradle/gradle-build-action@842c587ad8aa4c68eeba24c396e15af4c2e9f30a # pin@v2 + + - name: Build artifacts + run: make publish + + - name: Upload artifacts + uses: actions/upload-artifact@v4 + with: + name: ${{ github.sha }} + if-no-files-found: error + path: | + ./*/build/distributions/*.zip + ./sentry-opentelemetry/*/build/distributions/*.zip + ./sentry-android-ndk/build/intermediates/merged_native_libs/release/out/lib/* diff --git a/CHANGELOG.md b/CHANGELOG.md index 502b4a63b1..64d4181c87 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -14,6 +14,7 @@ - If you are setting global job listeners please also add `SentryJobListener` - Ensure serialVersionUID of Exception classes are unique ([#3115](https://github.com/getsentry/sentry-java/pull/3115)) - Get rid of "is not eligible for getting processed by all BeanPostProcessors" warnings in Spring Boot ([#3108](https://github.com/getsentry/sentry-java/pull/3108)) +- Fix missing `release` and other fields for ANRs reported with `mechanism:AppExitInfo` ([#3074](https://github.com/getsentry/sentry-java/pull/3074)) ### Dependencies diff --git a/Makefile b/Makefile index 4ed3732751..2117e6da21 100644 --- a/Makefile +++ b/Makefile @@ -1,8 +1,10 @@ -.PHONY: all clean compile javadocs dryRelease update stop checkFormat format api assembleBenchmarkTestRelease assembleUiTestRelease createCoverageReports +.PHONY: all clean compile javadocs dryRelease update stop checkFormat format api assembleBenchmarkTestRelease assembleUiTestRelease createCoverageReports check preMerge publish all: stop clean javadocs compile createCoverageReports -assembleBenchmarks: stop clean assembleBenchmarkTestRelease -assembleUiTests: stop clean assembleUiTestRelease +assembleBenchmarks: assembleBenchmarkTestRelease +assembleUiTests: assembleUiTestRelease +preMerge: check createCoverageReports +publish: clean dryRelease # deep clean clean: @@ -18,7 +20,7 @@ javadocs: # do a dry release (like a local deploy) dryRelease: - ./gradlew aggregateJavadocs publishToMavenLocal --no-daemon --no-parallel + ./gradlew aggregateJavadocs distZip --no-build-cache # check for dependencies update update: @@ -57,3 +59,7 @@ assembleUiTestRelease: createCoverageReports: ./gradlew jacocoTestReport ./gradlew koverXmlReportRelease + +# Run tests and lint +check: + ./gradlew check diff --git a/build.gradle.kts b/build.gradle.kts index c3e6eb98eb..dd566028f4 100644 --- a/build.gradle.kts +++ b/build.gradle.kts @@ -91,6 +91,11 @@ allprojects { TestLogEvent.PASSED, TestLogEvent.FAILED ) + maxParallelForks = Runtime.getRuntime().availableProcessors() / 2 + + // Cap JVM args per test + minHeapSize = "128m" + maxHeapSize = "1g" dependsOn("cleanTest") } withType { @@ -266,6 +271,23 @@ gradle.projectsEvaluated { } } } + + tasks.create("buildForCodeQL") { + subprojects + .filter { + !it.displayName.contains("sample") && + !it.displayName.contains("integration-tests") && + !it.displayName.contains("bom") && + it.name != "sentry-opentelemetry" + } + .forEach { proj -> + if (proj.plugins.hasPlugin("com.android.library")) { + this.dependsOn(proj.tasks.findByName("compileReleaseUnitTestSources")) + } else { + this.dependsOn(proj.tasks.findByName("testClasses")) + } + } + } } // Workaround for https://youtrack.jetbrains.com/issue/IDEA-316081/Gradle-8-toolchain-error-Toolchain-from-executable-property-does-not-match-toolchain-from-javaLauncher-property-when-different diff --git a/gradle.properties b/gradle.properties index 74dc833b22..b867d8ad3e 100644 --- a/gradle.properties +++ b/gradle.properties @@ -1,6 +1,6 @@ -# Daemon’s heap size +# Daemons heap size org.gradle.jvmargs=-Xmx4g -XX:MaxMetaspaceSize=1536m -XX:+HeapDumpOnOutOfMemoryError -Dfile.encoding=UTF-8 -XX:+UseParallelGC - +org.gradle.caching=true org.gradle.parallel=true # AndroidX required by AGP >= 3.6.x diff --git a/sentry-android-core/src/test/java/io/sentry/android/core/ActivityBreadcrumbsIntegrationTest.kt b/sentry-android-core/src/test/java/io/sentry/android/core/ActivityBreadcrumbsIntegrationTest.kt index f104acebfa..10dc60e74b 100644 --- a/sentry-android-core/src/test/java/io/sentry/android/core/ActivityBreadcrumbsIntegrationTest.kt +++ b/sentry-android-core/src/test/java/io/sentry/android/core/ActivityBreadcrumbsIntegrationTest.kt @@ -3,11 +3,9 @@ package io.sentry.android.core import android.app.Activity import android.app.Application import android.os.Bundle -import androidx.test.ext.junit.runners.AndroidJUnit4 import io.sentry.Breadcrumb import io.sentry.Hub import io.sentry.SentryLevel -import org.junit.runner.RunWith import org.mockito.kotlin.any import org.mockito.kotlin.anyOrNull import org.mockito.kotlin.check @@ -18,7 +16,6 @@ import org.mockito.kotlin.whenever import kotlin.test.Test import kotlin.test.assertEquals -@RunWith(AndroidJUnit4::class) class ActivityBreadcrumbsIntegrationTest { private class Fixture { diff --git a/sentry-android-core/src/test/java/io/sentry/android/core/AnrV2IntegrationTest.kt b/sentry-android-core/src/test/java/io/sentry/android/core/AnrV2IntegrationTest.kt index 7fce1a126e..885ad22c8f 100644 --- a/sentry-android-core/src/test/java/io/sentry/android/core/AnrV2IntegrationTest.kt +++ b/sentry-android-core/src/test/java/io/sentry/android/core/AnrV2IntegrationTest.kt @@ -67,6 +67,7 @@ class AnrV2IntegrationTest { useImmediateExecutorService: Boolean = true, isAnrEnabled: Boolean = true, flushTimeoutMillis: Long = 0L, + sessionFlushTimeoutMillis: Long = 0L, lastReportedAnrTimestamp: Long? = null, lastEventId: SentryId = SentryId(), sessionTrackingEnabled: Boolean = true, @@ -81,6 +82,7 @@ class AnrV2IntegrationTest { if (useImmediateExecutorService) ImmediateExecutorService() else mock() this.isAnrEnabled = isAnrEnabled this.flushTimeoutMillis = flushTimeoutMillis + this.sessionFlushTimeoutMillis = sessionFlushTimeoutMillis this.isEnableAutoSessionTracking = sessionTrackingEnabled this.isReportHistoricalAnrs = reportHistoricalAnrs this.isAttachAnrThreadDump = attachAnrThreadDump @@ -92,7 +94,6 @@ class AnrV2IntegrationTest { lastReportedAnrFile.writeText(lastReportedAnrTimestamp.toString()) } whenever(hub.captureEvent(any(), anyOrNull())).thenReturn(lastEventId) - return AnrV2Integration(context) } @@ -306,7 +307,7 @@ class AnrV2IntegrationTest { val integration = fixture.getSut( tmpDir, lastReportedAnrTimestamp = oldTimestamp, - flushTimeoutMillis = 1000L + flushTimeoutMillis = 500L ) fixture.addAppExitInfo(timestamp = newTimestamp) @@ -314,7 +315,7 @@ class AnrV2IntegrationTest { val hint = HintUtils.getSentrySdkHint(invocation.getArgument(1)) as DiskFlushNotification thread { - Thread.sleep(500L) + Thread.sleep(200L) hint.markFlushed() } SentryId() @@ -458,12 +459,12 @@ class AnrV2IntegrationTest { val integration = fixture.getSut( tmpDir, lastReportedAnrTimestamp = oldTimestamp, - flushTimeoutMillis = 1000L + sessionFlushTimeoutMillis = 500L ) fixture.addAppExitInfo(timestamp = newTimestamp) thread { - Thread.sleep(500L) + Thread.sleep(200L) val sessionHint = HintUtils.createWithTypeCheckHint(SessionStartHint()) fixture.options.envelopeDiskCache.store( SentryEnvelope(SentryId.EMPTY_ID, null, emptyList()), @@ -487,7 +488,7 @@ class AnrV2IntegrationTest { val integration = fixture.getSut( tmpDir, lastReportedAnrTimestamp = oldTimestamp, - flushTimeoutMillis = 500L, + sessionFlushTimeoutMillis = 500L, sessionTrackingEnabled = false ) fixture.addAppExitInfo(timestamp = newTimestamp) @@ -507,7 +508,7 @@ class AnrV2IntegrationTest { val integration = fixture.getSut( tmpDir, lastReportedAnrTimestamp = oldTimestamp, - flushTimeoutMillis = 500L + sessionFlushTimeoutMillis = 500L ) fixture.addAppExitInfo(timestamp = newTimestamp) diff --git a/sentry-android-core/src/test/java/io/sentry/android/core/SentryAndroidTest.kt b/sentry-android-core/src/test/java/io/sentry/android/core/SentryAndroidTest.kt index 4c431e8bc9..bd5b3695fb 100644 --- a/sentry-android-core/src/test/java/io/sentry/android/core/SentryAndroidTest.kt +++ b/sentry-android-core/src/test/java/io/sentry/android/core/SentryAndroidTest.kt @@ -21,6 +21,7 @@ import io.sentry.SentryOptions import io.sentry.SentryOptions.BeforeSendCallback import io.sentry.Session import io.sentry.ShutdownHookIntegration +import io.sentry.SystemOutLogger import io.sentry.UncaughtExceptionHandlerIntegration import io.sentry.android.core.cache.AndroidEnvelopeCache import io.sentry.android.core.performance.AppStartMetrics @@ -356,6 +357,8 @@ class SentryAndroidTest { fixture.initSut(context) { it.dsn = "https://key@sentry.io/123" it.cacheDirPath = cacheDir + it.isDebug = true + it.setLogger(SystemOutLogger()) // beforeSend is called after event processors are applied, so we can assert here // against the enriched ANR event it.beforeSend = BeforeSendCallback { event, hint -> diff --git a/sentry-android-integration-tests/sentry-uitest-android-benchmark/src/androidTest/java/io/sentry/uitest/android/benchmark/SdkBenchmarkTest.kt b/sentry-android-integration-tests/sentry-uitest-android-benchmark/src/androidTest/java/io/sentry/uitest/android/benchmark/SdkBenchmarkTest.kt deleted file mode 100644 index 75789bec51..0000000000 --- a/sentry-android-integration-tests/sentry-uitest-android-benchmark/src/androidTest/java/io/sentry/uitest/android/benchmark/SdkBenchmarkTest.kt +++ /dev/null @@ -1,62 +0,0 @@ -package io.sentry.uitest.android.benchmark - -import android.os.Bundle -import androidx.lifecycle.Lifecycle -import androidx.test.core.app.launchActivity -import androidx.test.ext.junit.runners.AndroidJUnit4 -import io.sentry.Sentry -import io.sentry.android.core.SentryAndroid -import io.sentry.uitest.android.benchmark.util.BenchmarkOperation -import org.junit.runner.RunWith -import java.util.concurrent.TimeUnit -import kotlin.test.Test -import kotlin.test.assertTrue - -@RunWith(AndroidJUnit4::class) -class SdkBenchmarkTest : BaseBenchmarkTest() { - - @Test - fun benchmarkSdkInit() { - // We compare starting an activity with and without the sdk init, to measure its impact on startup time. - val opNoSdk = getOperation() - val opSimpleSdk = getOperation { - SentryAndroid.init(context) { - it.dsn = "https://key@host/proj" - } - } - val opNoSdk2 = getOperation() - val opPerfProfilingSdk = getOperation { - SentryAndroid.init(context) { - it.dsn = "https://key@host/proj" - it.profilesSampleRate = 1.0 - it.tracesSampleRate = 1.0 - } - } - val refreshRate = BenchmarkActivity.refreshRate ?: 60F - val simpleSdkResults = BenchmarkOperation.compare(opNoSdk, "No Sdk", opSimpleSdk, "Simple Sdk", refreshRate) - val simpleSdkResult = simpleSdkResults.getSummaryResult() - simpleSdkResult.printResults() - val perfProfilingSdkResults = BenchmarkOperation.compare(opNoSdk2, "No Sdk", opPerfProfilingSdk, "Sdk with perf and profiling", refreshRate) - val perfProfilingSdkResult = perfProfilingSdkResults.getSummaryResult() - perfProfilingSdkResult.printResults() - - assertTrue(simpleSdkResult.cpuTimeIncreaseNanos in 0..TimeUnit.MILLISECONDS.toNanos(100), "Expected ${simpleSdkResult.cpuTimeIncreaseNanos} to be in range 0 < x < 100000000") - assertTrue(perfProfilingSdkResult.cpuTimeIncreaseNanos in 0..TimeUnit.MILLISECONDS.toNanos(100), "Expected ${perfProfilingSdkResult.cpuTimeIncreaseNanos} to be in range 0 < x < 100000000") - } - - private fun getOperation(init: (() -> Unit)? = null) = BenchmarkOperation( - choreographer, - op = { - runner.runOnMainSync { - init?.invoke() - } - val benchmarkScenario = launchActivity( - activityOptions = Bundle().apply { putBoolean(BenchmarkActivity.EXTRA_SUSTAINED_PERFORMANCE_MODE, false) } - ) - benchmarkScenario.moveToState(Lifecycle.State.DESTROYED) - }, - after = { - Sentry.close() - } - ) -} diff --git a/sentry-android-integration-tests/sentry-uitest-android-benchmark/src/androidTest/java/io/sentry/uitest/android/benchmark/SentryBenchmarkTest.kt b/sentry-android-integration-tests/sentry-uitest-android-benchmark/src/androidTest/java/io/sentry/uitest/android/benchmark/SentryBenchmarkTest.kt index 6dbcb457cc..110f9214b5 100644 --- a/sentry-android-integration-tests/sentry-uitest-android-benchmark/src/androidTest/java/io/sentry/uitest/android/benchmark/SentryBenchmarkTest.kt +++ b/sentry-android-integration-tests/sentry-uitest-android-benchmark/src/androidTest/java/io/sentry/uitest/android/benchmark/SentryBenchmarkTest.kt @@ -41,7 +41,9 @@ class SentryBenchmarkTest : BaseBenchmarkTest() { val op1 = BenchmarkOperation(choreographer, op = getOperation(runner)) val op2 = BenchmarkOperation(choreographer, op = getOperation(runner)) val refreshRate = BenchmarkActivity.refreshRate ?: 60F - val comparisonResults = BenchmarkOperation.compare(op1, "Op1", op2, "Op2", refreshRate) + // since we benchmark the same operation, warmupIterations = 1 would effectively mean + // 2 warmup runs which should be enough + val comparisonResults = BenchmarkOperation.compare(op1, "Op1", op2, "Op2", refreshRate, warmupIterations = 1, measuredIterations = 10) val comparisonResult = comparisonResults.getSummaryResult() comparisonResult.printResults() diff --git a/sentry-android-integration-tests/sentry-uitest-android/src/androidTest/java/io/sentry/uitest/android/EnvelopeTests.kt b/sentry-android-integration-tests/sentry-uitest-android/src/androidTest/java/io/sentry/uitest/android/EnvelopeTests.kt index 4244d56774..dab6c1eb60 100644 --- a/sentry-android-integration-tests/sentry-uitest-android/src/androidTest/java/io/sentry/uitest/android/EnvelopeTests.kt +++ b/sentry-android-integration-tests/sentry-uitest-android/src/androidTest/java/io/sentry/uitest/android/EnvelopeTests.kt @@ -9,15 +9,18 @@ import androidx.test.espresso.IdlingRegistry import androidx.test.espresso.action.ViewActions import androidx.test.espresso.matcher.ViewMatchers import androidx.test.ext.junit.runners.AndroidJUnit4 +import io.sentry.NoOpLogger import io.sentry.ProfilingTraceData import io.sentry.Sentry import io.sentry.SentryEvent import io.sentry.android.core.AndroidLogger +import io.sentry.android.core.BuildInfoProvider import io.sentry.android.core.SentryAndroidOptions import io.sentry.assertEnvelopeProfile import io.sentry.assertEnvelopeTransaction import io.sentry.profilemeasurements.ProfileMeasurement import io.sentry.protocol.SentryTransaction +import org.junit.Assume import org.junit.Assume.assumeNotNull import org.junit.runner.RunWith import java.util.concurrent.TimeUnit @@ -174,6 +177,12 @@ class EnvelopeTests : BaseUiTest() { @Test fun checkTimedOutProfile() { + Assume.assumeFalse( + "Sometimes traceFile does not exist for profiles on emulators and it fails." + + " Although, Android Runtime is the one that manages the file, so something" + + " must be wrong with Debug.startMethodTracing", + BuildInfoProvider(NoOpLogger.getInstance()).isEmulator ?: true + ) // We increase the IdlingResources timeout to exceed the profiling timeout IdlingPolicies.setIdlingResourceTimeout(1, TimeUnit.MINUTES) initSentry(true) { options: SentryAndroidOptions -> diff --git a/sentry-android-integration-tests/sentry-uitest-android/src/main/AndroidManifest.xml b/sentry-android-integration-tests/sentry-uitest-android/src/main/AndroidManifest.xml index 5576160bb0..6bcad62e6d 100644 --- a/sentry-android-integration-tests/sentry-uitest-android/src/main/AndroidManifest.xml +++ b/sentry-android-integration-tests/sentry-uitest-android/src/main/AndroidManifest.xml @@ -7,6 +7,7 @@ diff --git a/sentry-spring-boot-jakarta/src/test/kotlin/io/sentry/spring/boot/jakarta/SentrySpanRestTemplateCustomizerTest.kt b/sentry-spring-boot-jakarta/src/test/kotlin/io/sentry/spring/boot/jakarta/SentrySpanRestTemplateCustomizerTest.kt index 2c69175fa5..db5b25de44 100644 --- a/sentry-spring-boot-jakarta/src/test/kotlin/io/sentry/spring/boot/jakarta/SentrySpanRestTemplateCustomizerTest.kt +++ b/sentry-spring-boot-jakarta/src/test/kotlin/io/sentry/spring/boot/jakarta/SentrySpanRestTemplateCustomizerTest.kt @@ -28,6 +28,7 @@ import org.springframework.http.HttpHeaders import org.springframework.http.HttpMethod import org.springframework.http.HttpStatus import org.springframework.web.client.RestTemplate +import java.time.Duration import kotlin.test.Test import kotlin.test.assertEquals import kotlin.test.assertNotNull @@ -37,7 +38,10 @@ class SentrySpanRestTemplateCustomizerTest { class Fixture { val sentryOptions = SentryOptions() val hub = mock() - val restTemplate = RestTemplateBuilder().build() + val restTemplate = RestTemplateBuilder() + .setConnectTimeout(Duration.ofSeconds(2)) + .setReadTimeout(Duration.ofSeconds(2)) + .build() var mockServer = MockWebServer() val transaction: SentryTracer internal val customizer = SentrySpanRestTemplateCustomizer(hub) diff --git a/sentry-spring-boot/src/test/kotlin/io/sentry/spring/boot/SentrySpanRestTemplateCustomizerTest.kt b/sentry-spring-boot/src/test/kotlin/io/sentry/spring/boot/SentrySpanRestTemplateCustomizerTest.kt index 21c989fb7d..0d675b6841 100644 --- a/sentry-spring-boot/src/test/kotlin/io/sentry/spring/boot/SentrySpanRestTemplateCustomizerTest.kt +++ b/sentry-spring-boot/src/test/kotlin/io/sentry/spring/boot/SentrySpanRestTemplateCustomizerTest.kt @@ -28,6 +28,7 @@ import org.springframework.http.HttpHeaders import org.springframework.http.HttpMethod import org.springframework.http.HttpStatus import org.springframework.web.client.RestTemplate +import java.time.Duration import kotlin.test.Test import kotlin.test.assertEquals import kotlin.test.assertNotNull @@ -37,7 +38,10 @@ class SentrySpanRestTemplateCustomizerTest { class Fixture { val sentryOptions = SentryOptions() val hub = mock() - val restTemplate = RestTemplateBuilder().build() + val restTemplate = RestTemplateBuilder() + .setConnectTimeout(Duration.ofSeconds(2)) + .setReadTimeout(Duration.ofSeconds(2)) + .build() var mockServer = MockWebServer() val transaction: SentryTracer internal val customizer = SentrySpanRestTemplateCustomizer(hub) diff --git a/sentry/api/sentry.api b/sentry/api/sentry.api index ae27abdbe7..5caf13e88b 100644 --- a/sentry/api/sentry.api +++ b/sentry/api/sentry.api @@ -2148,6 +2148,7 @@ public class io/sentry/SentryOptions { public fun getSentryClientName ()Ljava/lang/String; public fun getSerializer ()Lio/sentry/ISerializer; public fun getServerName ()Ljava/lang/String; + public fun getSessionFlushTimeoutMillis ()J public fun getSessionTrackingIntervalMillis ()J public fun getShutdownTimeout ()J public fun getShutdownTimeoutMillis ()J @@ -2252,6 +2253,7 @@ public class io/sentry/SentryOptions { public fun setSentryClientName (Ljava/lang/String;)V public fun setSerializer (Lio/sentry/ISerializer;)V public fun setServerName (Ljava/lang/String;)V + public fun setSessionFlushTimeoutMillis (J)V public fun setSessionTrackingIntervalMillis (J)V public fun setShutdownTimeout (J)V public fun setShutdownTimeoutMillis (J)V diff --git a/sentry/src/main/java/io/sentry/SentryOptions.java b/sentry/src/main/java/io/sentry/SentryOptions.java index 1236e79d3a..39c354973b 100644 --- a/sentry/src/main/java/io/sentry/SentryOptions.java +++ b/sentry/src/main/java/io/sentry/SentryOptions.java @@ -99,6 +99,13 @@ public class SentryOptions { */ private long flushTimeoutMillis = 15000; // 15s + /** + * Controls how many seconds to wait before flushing previous session. Sentry SDKs finalizes + * unfinished sessions from a background queue and this queue is given a certain amount to drain + * sessions. Default is 15000 = 15s + */ + private long sessionFlushTimeoutMillis = 15000; // 15s + /** * Turns debug mode on or off. If debug is enabled SDK will attempt to print out useful debugging * information if something goes wrong. Default is disabled. @@ -2216,6 +2223,16 @@ public boolean isEnableBackpressureHandling() { return enableBackpressureHandling; } + @ApiStatus.Internal + public long getSessionFlushTimeoutMillis() { + return sessionFlushTimeoutMillis; + } + + @ApiStatus.Internal + public void setSessionFlushTimeoutMillis(final long sessionFlushTimeoutMillis) { + this.sessionFlushTimeoutMillis = sessionFlushTimeoutMillis; + } + /** The BeforeSend callback */ public interface BeforeSendCallback { diff --git a/sentry/src/main/java/io/sentry/cache/EnvelopeCache.java b/sentry/src/main/java/io/sentry/cache/EnvelopeCache.java index 0b5f8ac2fb..5232d6077a 100644 --- a/sentry/src/main/java/io/sentry/cache/EnvelopeCache.java +++ b/sentry/src/main/java/io/sentry/cache/EnvelopeCache.java @@ -67,8 +67,6 @@ public class EnvelopeCache extends CacheStrategy implements IEnvelopeCache { public static final String STARTUP_CRASH_MARKER_FILE = "startup_crash"; - private static final long SESSION_FLUSH_DISK_TIMEOUT_MS = 15000; - private final CountDownLatch previousSessionLatch; private final @NotNull Map fileNameMap = new WeakHashMap<>(); @@ -431,9 +429,8 @@ public void discard(final @NotNull SentryEnvelope envelope) { /** Awaits until the previous session (if any) is flushed to its own file. */ public boolean waitPreviousSessionFlush() { try { - // use fixed timeout instead of configurable options.getFlushTimeoutMillis() to ensure there's - // enough time to flush the session to disk - return previousSessionLatch.await(SESSION_FLUSH_DISK_TIMEOUT_MS, TimeUnit.MILLISECONDS); + return previousSessionLatch.await( + options.getSessionFlushTimeoutMillis(), TimeUnit.MILLISECONDS); } catch (InterruptedException e) { Thread.currentThread().interrupt(); options.getLogger().log(DEBUG, "Timed out waiting for previous session to flush."); diff --git a/sentry/src/main/java/io/sentry/cache/PersistingOptionsObserver.java b/sentry/src/main/java/io/sentry/cache/PersistingOptionsObserver.java index 296b602534..bb1bb71572 100644 --- a/sentry/src/main/java/io/sentry/cache/PersistingOptionsObserver.java +++ b/sentry/src/main/java/io/sentry/cache/PersistingOptionsObserver.java @@ -1,7 +1,5 @@ package io.sentry.cache; -import static io.sentry.SentryLevel.ERROR; - import io.sentry.IOptionsObserver; import io.sentry.JsonDeserializer; import io.sentry.SentryOptions; @@ -25,87 +23,54 @@ public PersistingOptionsObserver(final @NotNull SentryOptions options) { this.options = options; } - @SuppressWarnings("FutureReturnValueIgnored") - private void serializeToDisk(final @NotNull Runnable task) { - try { - options - .getExecutorService() - .submit( - () -> { - try { - task.run(); - } catch (Throwable e) { - options.getLogger().log(ERROR, "Serialization task failed", e); - } - }); - } catch (Throwable e) { - options.getLogger().log(ERROR, "Serialization task could not be scheduled", e); - } - } - @Override public void setRelease(@Nullable String release) { - serializeToDisk( - () -> { - if (release == null) { - delete(RELEASE_FILENAME); - } else { - store(release, RELEASE_FILENAME); - } - }); + if (release == null) { + delete(RELEASE_FILENAME); + } else { + store(release, RELEASE_FILENAME); + } } @Override public void setProguardUuid(@Nullable String proguardUuid) { - serializeToDisk( - () -> { - if (proguardUuid == null) { - delete(PROGUARD_UUID_FILENAME); - } else { - store(proguardUuid, PROGUARD_UUID_FILENAME); - } - }); + if (proguardUuid == null) { + delete(PROGUARD_UUID_FILENAME); + } else { + store(proguardUuid, PROGUARD_UUID_FILENAME); + } } @Override public void setSdkVersion(@Nullable SdkVersion sdkVersion) { - serializeToDisk( - () -> { - if (sdkVersion == null) { - delete(SDK_VERSION_FILENAME); - } else { - store(sdkVersion, SDK_VERSION_FILENAME); - } - }); + if (sdkVersion == null) { + delete(SDK_VERSION_FILENAME); + } else { + store(sdkVersion, SDK_VERSION_FILENAME); + } } @Override public void setDist(@Nullable String dist) { - serializeToDisk( - () -> { - if (dist == null) { - delete(DIST_FILENAME); - } else { - store(dist, DIST_FILENAME); - } - }); + if (dist == null) { + delete(DIST_FILENAME); + } else { + store(dist, DIST_FILENAME); + } } @Override public void setEnvironment(@Nullable String environment) { - serializeToDisk( - () -> { - if (environment == null) { - delete(ENVIRONMENT_FILENAME); - } else { - store(environment, ENVIRONMENT_FILENAME); - } - }); + if (environment == null) { + delete(ENVIRONMENT_FILENAME); + } else { + store(environment, ENVIRONMENT_FILENAME); + } } @Override public void setTags(@NotNull Map tags) { - serializeToDisk(() -> store(tags, TAGS_FILENAME)); + store(tags, TAGS_FILENAME); } private void store(final @NotNull T entity, final @NotNull String fileName) { diff --git a/sentry/src/test/java/io/sentry/JsonSerializerTest.kt b/sentry/src/test/java/io/sentry/JsonSerializerTest.kt index 93fb64c5b0..572319e2f0 100644 --- a/sentry/src/test/java/io/sentry/JsonSerializerTest.kt +++ b/sentry/src/test/java/io/sentry/JsonSerializerTest.kt @@ -8,6 +8,7 @@ import io.sentry.protocol.SdkVersion import io.sentry.protocol.SentryId import io.sentry.protocol.SentrySpan import io.sentry.protocol.SentryTransaction +import org.junit.After import org.mockito.kotlin.any import org.mockito.kotlin.check import org.mockito.kotlin.eq @@ -62,6 +63,12 @@ class JsonSerializerTest { @BeforeTest fun before() { fixture = Fixture() + SentryIntegrationPackageStorage.getInstance().clearStorage() + } + + @After + fun teardown() { + SentryIntegrationPackageStorage.getInstance().clearStorage() } private fun serializeToString(ev: T): String { diff --git a/sentry/src/test/java/io/sentry/PreviousSessionFinalizerTest.kt b/sentry/src/test/java/io/sentry/PreviousSessionFinalizerTest.kt index 8e27662b5b..239e90905e 100644 --- a/sentry/src/test/java/io/sentry/PreviousSessionFinalizerTest.kt +++ b/sentry/src/test/java/io/sentry/PreviousSessionFinalizerTest.kt @@ -39,6 +39,7 @@ class PreviousSessionFinalizerTest { isDebug = true cacheDirPath = dir?.newFolder()?.absolutePath this.flushTimeoutMillis = flushTimeoutMillis + this.sessionFlushTimeoutMillis = flushTimeoutMillis isEnableAutoSessionTracking = sessionTrackingEnabled setEnvelopeDiskCache(EnvelopeCache.create(this)) if (!shouldAwait) { diff --git a/sentry/src/test/java/io/sentry/SentryTest.kt b/sentry/src/test/java/io/sentry/SentryTest.kt index e284cbb4f1..2100a591a1 100644 --- a/sentry/src/test/java/io/sentry/SentryTest.kt +++ b/sentry/src/test/java/io/sentry/SentryTest.kt @@ -743,6 +743,8 @@ class SentryTest { Sentry.init { it.dsn = dsn + it.isDebug = true + it.setLogger(SystemOutLogger()) it.release = "io.sentry.sample@2.0" it.cacheDirPath = tmpDir.newFolder().absolutePath diff --git a/sentry/src/test/java/io/sentry/protocol/SdkVersionSerializationTest.kt b/sentry/src/test/java/io/sentry/protocol/SdkVersionSerializationTest.kt index ee3d72add9..44d15eca66 100644 --- a/sentry/src/test/java/io/sentry/protocol/SdkVersionSerializationTest.kt +++ b/sentry/src/test/java/io/sentry/protocol/SdkVersionSerializationTest.kt @@ -6,6 +6,7 @@ import io.sentry.JsonObjectReader import io.sentry.JsonObjectWriter import io.sentry.JsonSerializable import io.sentry.SentryIntegrationPackageStorage +import org.junit.After import org.junit.Before import org.junit.Test import org.mockito.kotlin.mock @@ -35,7 +36,12 @@ class SdkVersionSerializationTest { private val fixture = Fixture() @Before - fun clearIntegrationPackageStorage() { + fun setup() { + SentryIntegrationPackageStorage.getInstance().clearStorage() + } + + @After + fun teardown() { SentryIntegrationPackageStorage.getInstance().clearStorage() } diff --git a/sentry/src/test/java/io/sentry/protocol/SentryBaseEventSerializationTest.kt b/sentry/src/test/java/io/sentry/protocol/SentryBaseEventSerializationTest.kt index 49608a8664..4bc13559da 100644 --- a/sentry/src/test/java/io/sentry/protocol/SentryBaseEventSerializationTest.kt +++ b/sentry/src/test/java/io/sentry/protocol/SentryBaseEventSerializationTest.kt @@ -6,7 +6,10 @@ import io.sentry.JsonObjectReader import io.sentry.JsonSerializable import io.sentry.ObjectWriter import io.sentry.SentryBaseEvent +import io.sentry.SentryIntegrationPackageStorage import io.sentry.vendor.gson.stream.JsonToken +import org.junit.After +import org.junit.Before import org.junit.Test import org.mockito.kotlin.mock import kotlin.test.assertEquals @@ -76,6 +79,16 @@ class SentryBaseEventSerializationTest { } private val fixture = Fixture() + @Before + fun setup() { + SentryIntegrationPackageStorage.getInstance().clearStorage() + } + + @After + fun teardown() { + SentryIntegrationPackageStorage.getInstance().clearStorage() + } + @Test fun serialize() { val expected = SerializationUtils.sanitizedFile("json/sentry_base_event.json") diff --git a/sentry/src/test/java/io/sentry/protocol/SentryEnvelopeHeaderSerializationTest.kt b/sentry/src/test/java/io/sentry/protocol/SentryEnvelopeHeaderSerializationTest.kt index 8261161095..5b9edd7299 100644 --- a/sentry/src/test/java/io/sentry/protocol/SentryEnvelopeHeaderSerializationTest.kt +++ b/sentry/src/test/java/io/sentry/protocol/SentryEnvelopeHeaderSerializationTest.kt @@ -7,7 +7,10 @@ import io.sentry.JsonObjectReader import io.sentry.JsonObjectWriter import io.sentry.JsonSerializable import io.sentry.SentryEnvelopeHeader +import io.sentry.SentryIntegrationPackageStorage import io.sentry.TraceContextSerializationTest +import org.junit.After +import org.junit.Before import org.junit.Test import org.mockito.kotlin.mock import java.io.StringReader @@ -29,6 +32,16 @@ class SentryEnvelopeHeaderSerializationTest { } private val fixture = Fixture() + @Before + fun setup() { + SentryIntegrationPackageStorage.getInstance().clearStorage() + } + + @After + fun teardown() { + SentryIntegrationPackageStorage.getInstance().clearStorage() + } + @Test fun serialize() { val expected = sanitizedFile("json/sentry_envelope_header.json") diff --git a/sentry/src/test/java/io/sentry/protocol/SentryEventSerializationTest.kt b/sentry/src/test/java/io/sentry/protocol/SentryEventSerializationTest.kt index 83a60d555b..d51ac1dc13 100644 --- a/sentry/src/test/java/io/sentry/protocol/SentryEventSerializationTest.kt +++ b/sentry/src/test/java/io/sentry/protocol/SentryEventSerializationTest.kt @@ -7,7 +7,10 @@ import io.sentry.JsonObjectReader import io.sentry.JsonObjectWriter import io.sentry.JsonSerializable import io.sentry.SentryEvent +import io.sentry.SentryIntegrationPackageStorage import io.sentry.SentryLevel +import org.junit.After +import org.junit.Before import org.junit.Test import org.mockito.kotlin.mock import java.io.StringReader @@ -42,6 +45,16 @@ class SentryEventSerializationTest { } private val fixture = Fixture() + @Before + fun setup() { + SentryIntegrationPackageStorage.getInstance().clearStorage() + } + + @After + fun teardown() { + SentryIntegrationPackageStorage.getInstance().clearStorage() + } + @Test fun serialize() { val expected = sanitizedFile("json/sentry_event.json") diff --git a/sentry/src/test/java/io/sentry/protocol/SentryTransactionSerializationTest.kt b/sentry/src/test/java/io/sentry/protocol/SentryTransactionSerializationTest.kt index bfe6a1a2e7..ed21426a57 100644 --- a/sentry/src/test/java/io/sentry/protocol/SentryTransactionSerializationTest.kt +++ b/sentry/src/test/java/io/sentry/protocol/SentryTransactionSerializationTest.kt @@ -6,6 +6,9 @@ import io.sentry.ILogger import io.sentry.JsonObjectReader import io.sentry.JsonObjectWriter import io.sentry.JsonSerializable +import io.sentry.SentryIntegrationPackageStorage +import org.junit.After +import org.junit.Before import org.junit.Test import org.mockito.kotlin.mock import java.io.StringReader @@ -35,6 +38,16 @@ class SentryTransactionSerializationTest { } private val fixture = Fixture() + @Before + fun setup() { + SentryIntegrationPackageStorage.getInstance().clearStorage() + } + + @After + fun teardown() { + SentryIntegrationPackageStorage.getInstance().clearStorage() + } + @Test fun serialize() { val expected = sanitizedFile("json/sentry_transaction.json")