From 65bfb995b00db5062e739033bef78c1e7af7eb4a Mon Sep 17 00:00:00 2001 From: bitsandfoxes Date: Fri, 13 Feb 2026 17:12:43 +0100 Subject: [PATCH 01/20] First iteration --- .github/workflows/ci.yml | 9 +- .../workflows/smoke-test-build-android.yml | 35 ++-- .github/workflows/smoke-test-run-android.yml | 34 +++- .gitmodules | 3 + test/IntegrationTest/CommonTestCases.ps1 | 131 ++++++++++++ test/IntegrationTest/Integration.Tests.ps1 | 188 ++++++++++++++++++ .../Scenes/SmokeTest.unity | 2 +- .../IntegrationOptionsConfiguration.cs | 45 +++++ .../IntegrationOptionsConfiguration.cs.meta | 11 + .../Scripts/IntegrationTester.cs | 123 ++++++++++++ .../Scripts/IntegrationTester.cs.meta | 11 + .../Scripts/TestLauncher.cs | 80 ++++++++ .../Scripts/TestLauncher.cs.meta | 11 + .../configure-sentry.ps1 | 10 +- 14 files changed, 660 insertions(+), 33 deletions(-) create mode 100644 test/IntegrationTest/CommonTestCases.ps1 create mode 100644 test/IntegrationTest/Integration.Tests.ps1 create mode 100644 test/Scripts.Integration.Test/Scripts/IntegrationOptionsConfiguration.cs create mode 100644 test/Scripts.Integration.Test/Scripts/IntegrationOptionsConfiguration.cs.meta create mode 100644 test/Scripts.Integration.Test/Scripts/IntegrationTester.cs create mode 100644 test/Scripts.Integration.Test/Scripts/IntegrationTester.cs.meta create mode 100644 test/Scripts.Integration.Test/Scripts/TestLauncher.cs create mode 100644 test/Scripts.Integration.Test/Scripts/TestLauncher.cs.meta diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml index 9d0eb74e5..cc59be993 100644 --- a/.github/workflows/ci.yml +++ b/.github/workflows/ci.yml @@ -258,9 +258,10 @@ jobs: unity-version: ${{ matrix.unity-version }} smoke-test-run-android: - name: Run Android ${{ matrix.unity-version }} Smoke Test + name: Run Android ${{ matrix.unity-version }} Integration Test if: ${{ !startsWith(github.ref, 'refs/heads/release/') }} needs: [smoke-test-build-android, create-unity-matrix] + secrets: inherit uses: ./.github/workflows/smoke-test-run-android.yml with: unity-version: ${{ matrix.unity-version }} @@ -270,8 +271,10 @@ jobs: fail-fast: false matrix: unity-version: ${{ fromJSON(needs.create-unity-matrix.outputs.unity-matrix).unity-version }} - api-level: [30, 31, 34] - init-type: ["runtime", "buildtime"] + # api-level: [30, 31, 34] + api-level: [30] + # init-type: ["runtime", "buildtime"] + init-type: ["runtime"] smoke-test-build-ios: name: Build iOS ${{ matrix.unity-version }} Smoke Test diff --git a/.github/workflows/smoke-test-build-android.yml b/.github/workflows/smoke-test-build-android.yml index a2614f06e..32b7ac7e3 100644 --- a/.github/workflows/smoke-test-build-android.yml +++ b/.github/workflows/smoke-test-build-android.yml @@ -77,7 +77,9 @@ jobs: run: ./test/Scripts.Integration.Test/add-sentry.ps1 -UnityPath "$env:UNITY_PATH" -PackagePath "test-package-release" - name: Configure Sentry - run: ./test/Scripts.Integration.Test/configure-sentry.ps1 -UnityPath "$env:UNITY_PATH" -Platform "Android" -CheckSymbols + run: ./test/Scripts.Integration.Test/configure-sentry.ps1 -UnityPath "$env:UNITY_PATH" -Platform "Android" -CheckSymbols -TestMode "integration" + env: + SENTRY_DSN: ${{ secrets.SENTRY_TEST_DSN }} - name: Export APK - Runtime Initialization run: ./test/Scripts.Integration.Test/build-project.ps1 -UnityPath "$env:UNITY_PATH" -Platform "Android" -CheckSymbols:$true -UnityVersion "$env:UNITY_VERSION" @@ -99,23 +101,24 @@ jobs: path: samples/IntegrationTest/Build/*.apk # Collect app but ignore the files that are not required for the test. retention-days: 14 # Lower retention period - we only need this to retry CI. - - name: Overwrite OptionsConfiguration for build-time initialization - run: | - $optionsPath = "samples/IntegrationTest/Assets/Scripts/OptionsConfiguration.cs" - $content = Get-Content $optionsPath -Raw - $content = $content -replace 'AndroidNativeInitializationType = NativeInitializationType.Runtime', 'AndroidNativeInitializationType = NativeInitializationType.BuildTime' - Set-Content $optionsPath $content + # TODO: Get this back in + # - name: Overwrite OptionsConfiguration for build-time initialization + # run: | + # $optionsPath = "samples/IntegrationTest/Assets/Scripts/OptionsConfiguration.cs" + # $content = Get-Content $optionsPath -Raw + # $content = $content -replace 'AndroidNativeInitializationType = NativeInitializationType.Runtime', 'AndroidNativeInitializationType = NativeInitializationType.BuildTime' + # Set-Content $optionsPath $content - - name: Export APK - Build-Time Initialization - run: ./test/Scripts.Integration.Test/build-project.ps1 -UnityPath "$env:UNITY_PATH" -Platform "Android" -CheckSymbols:$true -UnityVersion "$env:UNITY_VERSION" + # - name: Export APK - Build-Time Initialization + # run: ./test/Scripts.Integration.Test/build-project.ps1 -UnityPath "$env:UNITY_PATH" -Platform "Android" -CheckSymbols:$true -UnityVersion "$env:UNITY_VERSION" + + # - name: Upload .apk + # uses: actions/upload-artifact@ea165f8d65b6e75b540449e92b4886f43607fa02 # v4 + # with: + # name: testapp-android-compiled-${{ env.UNITY_VERSION }}-buildtime + # path: samples/IntegrationTest/Build/*.apk # Collect app but ignore the files that are not required for the test. + # retention-days: 14 # Lower retention period - we only need this to retry CI. - - name: Upload .apk - uses: actions/upload-artifact@ea165f8d65b6e75b540449e92b4886f43607fa02 # v4 - with: - name: testapp-android-compiled-${{ env.UNITY_VERSION }}-buildtime - path: samples/IntegrationTest/Build/*.apk # Collect app but ignore the files that are not required for the test. - retention-days: 14 # Lower retention period - we only need this to retry CI. - - name: Upload IntegrationTest project on failure if: ${{ failure() }} uses: actions/upload-artifact@ea165f8d65b6e75b540449e92b4886f43607fa02 # v4 diff --git a/.github/workflows/smoke-test-run-android.yml b/.github/workflows/smoke-test-run-android.yml index ddc508cc1..e7029ab43 100644 --- a/.github/workflows/smoke-test-run-android.yml +++ b/.github/workflows/smoke-test-run-android.yml @@ -14,7 +14,7 @@ on: # Map the workflow outputs to job outputs outputs: status: - description: "Smoke test status" + description: "Integration test status" value: ${{ jobs.run.outputs.status }} defaults: @@ -28,14 +28,23 @@ jobs: env: ARTIFACTS_PATH: samples/IntegrationTest/test-artifacts/ HOMEBREW_NO_INSTALL_CLEANUP: 1 + SENTRY_TEST_DSN: ${{ secrets.SENTRY_TEST_DSN }} + SENTRY_AUTH_TOKEN: ${{ secrets.SENTRY_AUTH_TOKEN }} # Map the job outputs to step outputs outputs: - status: ${{ steps.smoke-test.outputs.status }} - + status: ${{ steps.integration-test.outputs.status }} + steps: - name: Checkout uses: actions/checkout@11bd71901bbe5b1630ceea73d27597364c9af683 # v4 - + + - name: Initialize app-runner submodule + run: git submodule update --init modules/app-runner + shell: bash + + - name: Install Pester + run: Install-Module -Name Pester -Force -SkipPublisherCheck + - name: Download test app artifact uses: actions/download-artifact@d3f86a106a0bac45b974a628896c90dbdf5c8093 # v4 with: @@ -56,9 +65,9 @@ jobs: mkdir -p $HOME/.android/avd touch $HOME/.android/repositories.cfg - - name: Run Android Smoke Tests + - name: Run Android Integration Tests uses: reactivecircus/android-emulator-runner@d94c3fbe4fe6a29e4a5ba47c12fb47677c73656b # pin@v2.33.0 - id: smoke-test + id: integration-test timeout-minutes: 30 with: api-level: ${{ inputs.api-level }} @@ -81,12 +90,17 @@ jobs: adb wait-for-device adb shell input keyevent 82 adb devices -l - pwsh ./scripts/smoke-test-android.ps1 -WarnIfFlaky + pwsh -Command ' + $env:SENTRY_TEST_APK = "samples/IntegrationTest/Build/test.apk" + Invoke-Pester -Script test/IntegrationTest/Integration.Tests.ps1 -Output Detailed + ' - - name: Upload artifacts on failure + - name: Upload test results on failure if: ${{ failure() }} uses: actions/upload-artifact@ea165f8d65b6e75b540449e92b4886f43607fa02 # v4 with: name: testapp-android-logs-${{ inputs.api-level }}-${{ inputs.unity-version }} - path: ${{ env.ARTIFACTS_PATH }} - retention-days: 14 \ No newline at end of file + path: | + ${{ env.ARTIFACTS_PATH }} + test/IntegrationTest/results/ + retention-days: 14 diff --git a/.gitmodules b/.gitmodules index f761264dc..aaad81671 100644 --- a/.gitmodules +++ b/.gitmodules @@ -7,3 +7,6 @@ [submodule "modules/sentry-native"] path = modules/sentry-native url = https://github.com/getsentry/sentry-native.git +[submodule "modules/app-runner"] + path = modules/app-runner + url = https://github.com/getsentry/app-runner.git diff --git a/test/IntegrationTest/CommonTestCases.ps1 b/test/IntegrationTest/CommonTestCases.ps1 new file mode 100644 index 000000000..2bb1e1fa6 --- /dev/null +++ b/test/IntegrationTest/CommonTestCases.ps1 @@ -0,0 +1,131 @@ +# Defines a collection of reusable test cases that validate common Sentry event properties +# and behaviors across different test suites for consistent integration testing. +# +# Available parameters: +# - $TestSetup: Object containing test setup parameters +# - $TestType: String indicating the type of test being run (e.g., "crash-capture", "message-capture") +# - $SentryEvent: The Sentry event object retrieved from the REST API containing error/message details +# - $RunResult: Object containing the results of running the test application, including Output and ExitCode + +$CommonTestCases = @( + @{ Name = "Outputs event ID"; TestBlock = { + param($TestSetup, $TestType, $SentryEvent, $RunResult) + $eventId = Get-EventIds -appOutput $RunResult.Output -expectedCount 1 + $eventId | Should -Not -BeNullOrEmpty + } + } + @{ Name = "Captures event in sentry.io"; TestBlock = { + param($TestSetup, $TestType, $SentryEvent, $RunResult) + $SentryEvent | Should -Not -BeNullOrEmpty + } + } + @{ Name = "Has title"; TestBlock = { + param($TestSetup, $TestType, $SentryEvent, $RunResult) + $SentryEvent.title | Should -Not -BeNullOrEmpty + } + } + @{ Name = "Has correct release version"; TestBlock = { + param($TestSetup, $TestType, $SentryEvent, $RunResult) + $SentryEvent.release.version | Should -Be "sentry-unity-test@1.0.0" + } + } + @{ Name = "Has correct dist attribute"; TestBlock = { + param($TestSetup, $TestType, $SentryEvent, $RunResult) + $SentryEvent.dist | Should -Be "test-dist" + } + } + @{ Name = "Has tags"; TestBlock = { + param($TestSetup, $TestType, $SentryEvent, $RunResult) + $SentryEvent.tags | Should -Not -BeNullOrEmpty + } + } + @{ Name = "Has correct integration test tags"; TestBlock = { + param($TestSetup, $TestType, $SentryEvent, $RunResult) + ($SentryEvent.tags | Where-Object { $_.key -eq "test.suite" }).value | Should -Be "integration" + ($SentryEvent.tags | Where-Object { $_.key -eq "test.type" }).value | Should -Be $TestType + } + } + @{ Name = "Has correct environment tag"; TestBlock = { + param($TestSetup, $TestType, $SentryEvent, $RunResult) + ($SentryEvent.tags | Where-Object { $_.key -eq "environment" }).value | Should -Be "integration-test" + } + } + @{ Name = "Contains user information"; TestBlock = { + param($TestSetup, $TestType, $SentryEvent, $RunResult) + + if ($TestType -eq "crash-capture") { + # User context may not survive native crashes on all platforms + return + } + + $SentryEvent.user | Should -Not -BeNullOrEmpty + $SentryEvent.user.username | Should -Be "TestUser" + $SentryEvent.user.email | Should -Be "user-mail@test.abc" + $SentryEvent.user.id | Should -Be "12345" + } + } + @{ Name = "Contains breadcrumbs"; TestBlock = { + param($TestSetup, $TestType, $SentryEvent, $RunResult) + + if ($TestType -eq "crash-capture") { + # Breadcrumbs may not survive native crashes + return + } + + $SentryEvent.breadcrumbs | Should -Not -BeNullOrEmpty + $SentryEvent.breadcrumbs.values | Should -Not -BeNullOrEmpty + } + } + @{ Name = "Contains expected breadcrumbs"; TestBlock = { + param($TestSetup, $TestType, $SentryEvent, $RunResult) + + if ($TestType -eq "crash-capture") { + return + } + + $SentryEvent.breadcrumbs.values | Should -Not -BeNullOrEmpty + $SentryEvent.breadcrumbs.values | Where-Object { $_.message -eq "Integration test started" } | Should -Not -BeNullOrEmpty + $SentryEvent.breadcrumbs.values | Where-Object { $_.message -eq "Context configuration finished" } | Should -Not -BeNullOrEmpty + } + } + @{ Name = "Contains SDK information"; TestBlock = { + param($TestSetup, $TestType, $SentryEvent, $RunResult) + $SentryEvent.sdk | Should -Not -BeNullOrEmpty + $SentryEvent.sdk.name | Should -Not -BeNullOrEmpty + $SentryEvent.sdk.version | Should -Not -BeNullOrEmpty + } + } + @{ Name = "Contains app context"; TestBlock = { + param($TestSetup, $TestType, $SentryEvent, $RunResult) + + if ($TestType -eq "crash-capture") { + # App context may not be available for native crashes + return + } + + $SentryEvent.contexts.app | Should -Not -BeNullOrEmpty + } + } + @{ Name = "Contains device context"; TestBlock = { + param($TestSetup, $TestType, $SentryEvent, $RunResult) + $SentryEvent.contexts.device | Should -Not -BeNullOrEmpty + } + } + @{ Name = "Contains OS context"; TestBlock = { + param($TestSetup, $TestType, $SentryEvent, $RunResult) + $SentryEvent.contexts.os | Should -Not -BeNullOrEmpty + $SentryEvent.contexts.os.name | Should -Not -BeNullOrEmpty + } + } + @{ Name = "Contains Unity context"; TestBlock = { + param($TestSetup, $TestType, $SentryEvent, $RunResult) + + if ($TestType -eq "crash-capture") { + # Unity context may not be synchronized to NDK for native crashes + return + } + + $SentryEvent.contexts.unity | Should -Not -BeNullOrEmpty + } + } +) diff --git a/test/IntegrationTest/Integration.Tests.ps1 b/test/IntegrationTest/Integration.Tests.ps1 new file mode 100644 index 000000000..0bf2bcb24 --- /dev/null +++ b/test/IntegrationTest/Integration.Tests.ps1 @@ -0,0 +1,188 @@ +#!/usr/bin/env pwsh +# +# Integration tests for Sentry Unity SDK (Android) +# +# Environment variables: +# SENTRY_TEST_APK: path to the test APK file +# SENTRY_TEST_DSN: test DSN +# SENTRY_AUTH_TOKEN: authentication token for Sentry API + +Set-StrictMode -Version latest +$ErrorActionPreference = "Stop" +$global:DebugPreference = "Continue" + +if (-not $Global:NewProjectPathCache) +{ + . $PSScriptRoot/../Scripts.Integration.Test/globals.ps1 +} + +# Import app-runner modules +. $PSScriptRoot/../../modules/app-runner/import-modules.ps1 + +# Import shared test cases and utility functions +. $PSScriptRoot/CommonTestCases.ps1 + + +BeforeAll { + $script:AndroidComponent = "io.sentry.unity.integrationtest/com.unity3d.player.UnityPlayerActivity" + $script:FallbackAndroidComponent = "io.sentry.unity.integrationtest/com.unity3d.player.UnityPlayerGameActivity" + + # Run integration test action on device + function Invoke-TestAction { + param ( + [Parameter(Mandatory=$true)] + [string]$Action + ) + + Write-Host "Running $Action..." + + $extras = @("-e", "test", $Action) + + $runResult = Invoke-DeviceApp -ExecutablePath $script:AndroidComponent -Arguments $extras + + # Save result to JSON file + $runResult | ConvertTo-Json -Depth 5 | Out-File -FilePath (Get-OutputFilePath "${Action}-result.json") + + # Launch app again to ensure crash report is sent + if ($Action -eq "crash-capture" -or $runResult.ExitCode -ne 0) { + Write-Host "Running crash-send to ensure crash report is sent..." + Write-Log "::group::Log of crash-send" + + $sendExtras = @("-e", "test", "crash-send") + Invoke-DeviceApp -ExecutablePath $script:AndroidComponent -Arguments $sendExtras + + Write-Log "::endgroup::" + } + + return $runResult + } + + # Create directory for the test results + New-Item -ItemType Directory -Path "$PSScriptRoot/results/" -ErrorAction Continue 2>&1 | Out-Null + Set-OutputDir -Path "$PSScriptRoot/results/" + + # Initialize test parameters + $script:TestSetup = [PSCustomObject]@{ + ApkPath = $env:SENTRY_TEST_APK + Dsn = $env:SENTRY_TEST_DSN + AuthToken = $env:SENTRY_AUTH_TOKEN + } + + # Validate environment + if ([string]::IsNullOrEmpty($script:TestSetup.ApkPath)) { + throw "SENTRY_TEST_APK environment variable is not set." + } + if (-not (Test-Path $script:TestSetup.ApkPath)) { + throw "APK not found at: $($script:TestSetup.ApkPath)" + } + if ([string]::IsNullOrEmpty($script:TestSetup.Dsn)) { + throw "SENTRY_TEST_DSN environment variable is not set." + } + if ([string]::IsNullOrEmpty($script:TestSetup.AuthToken)) { + throw "SENTRY_AUTH_TOKEN environment variable is not set." + } + + Connect-SentryApi ` + -ApiToken $script:TestSetup.AuthToken ` + -DSN $script:TestSetup.Dsn + + Connect-Device -Platform "Adb" + Install-DeviceApp -Path $script:TestSetup.ApkPath +} + + +AfterAll { + Disconnect-SentryApi + Disconnect-Device +} + + +Describe "Unity Android Integration Tests" { + + Context "Message Capture" { + BeforeAll { + $script:runResult = Invoke-TestAction -Action "message-capture" + + $eventId = Get-EventIds -AppOutput $script:runResult.Output -ExpectedCount 1 + if ($eventId) { + Write-Log "::group::Getting event content" + $script:runEvent = Get-SentryTestEvent -EventId "$eventId" + Write-Log "::endgroup::" + } + } + + It "" -ForEach $CommonTestCases { + & $testBlock -SentryEvent $runEvent -TestType "message-capture" -RunResult $runResult -TestSetup $script:TestSetup + } + + It "Has message level info" { + ($runEvent.tags | Where-Object { $_.key -eq "level" }).value | Should -Be "info" + } + + It "Has message content" { + $runEvent.title | Should -Not -BeNullOrEmpty + } + } + + Context "Exception Capture" { + BeforeAll { + $script:runResult = Invoke-TestAction -Action "exception-capture" + + $eventId = Get-EventIds -AppOutput $script:runResult.Output -ExpectedCount 1 + if ($eventId) { + Write-Log "::group::Getting event content" + $script:runEvent = Get-SentryTestEvent -EventId "$eventId" + Write-Log "::endgroup::" + } + } + + It "" -ForEach $CommonTestCases { + & $testBlock -SentryEvent $runEvent -TestType "exception-capture" -RunResult $runResult -TestSetup $script:TestSetup + } + + It "Has exception information" { + $runEvent.exception | Should -Not -BeNullOrEmpty + $runEvent.exception.values | Should -Not -BeNullOrEmpty + } + + It "Has exception with stacktrace" { + $exception = $runEvent.exception.values[0] + $exception | Should -Not -BeNullOrEmpty + $exception.type | Should -Not -BeNullOrEmpty + $exception.stacktrace | Should -Not -BeNullOrEmpty + } + + It "Has error level" { + ($runEvent.tags | Where-Object { $_.key -eq "level" }).value | Should -Be "error" + } + } + + Context "Crash Capture" { + BeforeAll { + $script:runResult = Invoke-TestAction -Action "crash-capture" + + $eventId = Get-EventIds -AppOutput $script:runResult.Output -ExpectedCount 1 + if ($eventId) { + Write-Log "::group::Getting event content" + $script:runEvent = Get-SentryTestEvent -TagName "test.crash_id" -TagValue "$eventId" -TimeoutSeconds 120 + Write-Log "::endgroup::" + } + } + + It "" -ForEach $CommonTestCases { + & $testBlock -SentryEvent $runEvent -TestType "crash-capture" -RunResult $runResult -TestSetup $script:TestSetup + } + + It "Has fatal level" { + ($runEvent.tags | Where-Object { $_.key -eq "level" }).value | Should -Be "fatal" + } + + It "Has exception with stacktrace" { + $runEvent.exception | Should -Not -BeNullOrEmpty + $runEvent.exception.values | Should -Not -BeNullOrEmpty + $exception = $runEvent.exception.values[0] + $exception | Should -Not -BeNullOrEmpty + $exception.stacktrace | Should -Not -BeNullOrEmpty + } + } +} diff --git a/test/Scripts.Integration.Test/Scenes/SmokeTest.unity b/test/Scripts.Integration.Test/Scenes/SmokeTest.unity index d9c403c56..86e603ce6 100644 --- a/test/Scripts.Integration.Test/Scenes/SmokeTest.unity +++ b/test/Scripts.Integration.Test/Scenes/SmokeTest.unity @@ -379,7 +379,7 @@ MonoBehaviour: m_GameObject: {fileID: 1185210226} m_Enabled: 1 m_EditorHideFlags: 0 - m_Script: {fileID: 11500000, guid: 6b8ba3d687233471198b184bbcb4fdbd, type: 3} + m_Script: {fileID: 11500000, guid: a1b2c3d4e5f6a7b8c9d0e1f2a3b4c5d6, type: 3} m_Name: m_EditorClassIdentifier: --- !u!1 &1263241751 diff --git a/test/Scripts.Integration.Test/Scripts/IntegrationOptionsConfiguration.cs b/test/Scripts.Integration.Test/Scripts/IntegrationOptionsConfiguration.cs new file mode 100644 index 000000000..ff5d32e3b --- /dev/null +++ b/test/Scripts.Integration.Test/Scripts/IntegrationOptionsConfiguration.cs @@ -0,0 +1,45 @@ +using System.Collections.Generic; +using Sentry; +using Sentry.Unity; +using UnityEngine; + +public class IntegrationOptionsConfiguration : SentryOptionsConfiguration +{ + public override void Configure(SentryUnityOptions options) + { + Debug.Log("Sentry: IntegrationOptionsConfig::Configure() called"); + + // DSN is read from SentryOptions.asset (baked at build time via configure-sentry.ps1) + // No custom DSN override needed -- the real DSN is set in the editor configuration. + + options.Environment = "integration-test"; + options.Release = "sentry-unity-test@1.0.0"; + options.Distribution = "test-dist"; + + options.AttachScreenshot = true; + options.Debug = true; + options.DiagnosticLevel = SentryLevel.Debug; + options.TracesSampleRate = 1.0d; + + // No custom HTTP handler -- events go to real sentry.io + + // Filtering test output from breadcrumbs + options.AddBreadcrumbsForLogType = new Dictionary + { + { LogType.Error, true }, + { LogType.Assert, true }, + { LogType.Warning, true }, + { LogType.Log, false }, + { LogType.Exception, true }, + }; + + // Disable ANR to avoid test interference + options.DisableAnrIntegration(); + + // Runtime initialization for integration tests + options.AndroidNativeInitializationType = NativeInitializationType.Runtime; + options.IosNativeInitializationType = NativeInitializationType.Runtime; + + Debug.Log("Sentry: IntegrationOptionsConfig::Configure() finished"); + } +} diff --git a/test/Scripts.Integration.Test/Scripts/IntegrationOptionsConfiguration.cs.meta b/test/Scripts.Integration.Test/Scripts/IntegrationOptionsConfiguration.cs.meta new file mode 100644 index 000000000..271393dc0 --- /dev/null +++ b/test/Scripts.Integration.Test/Scripts/IntegrationOptionsConfiguration.cs.meta @@ -0,0 +1,11 @@ +fileFormatVersion: 2 +guid: c3d4e5f6a7b8c9d0e1f2a3b4c5d6e7f8 +MonoImporter: + externalObjects: {} + serializedVersion: 2 + defaultReferences: [] + executionOrder: 0 + icon: {instanceID: 0} + userData: + assetBundleName: + assetBundleVariant: diff --git a/test/Scripts.Integration.Test/Scripts/IntegrationTester.cs b/test/Scripts.Integration.Test/Scripts/IntegrationTester.cs new file mode 100644 index 000000000..b4554bbe0 --- /dev/null +++ b/test/Scripts.Integration.Test/Scripts/IntegrationTester.cs @@ -0,0 +1,123 @@ +using System; +using System.Collections; +using System.Collections.Generic; +using Sentry; +using Sentry.Unity; +using UnityEngine; +using UnityEngine.Diagnostics; + +public class IntegrationTester : MonoBehaviour +{ + public void Start() + { + var arg = TestLauncher.GetTestArg(); + Debug.Log($"IntegrationTester arg: '{arg}'"); + + switch (arg) + { + case "message-capture": + MessageCapture(); + break; + case "exception-capture": + ExceptionCapture(); + break; + case "crash-capture": + CrashCapture(); + break; + case "crash-send": + CrashSend(); + break; + default: + Debug.LogError($"IntegrationTester: Unknown command: {arg}"); + Application.Quit(1); + break; + } + } + + private void AddIntegrationTestContext(string testType) + { + SentrySdk.AddBreadcrumb("Integration test started"); + + SentrySdk.ConfigureScope(scope => + { + scope.SetTag("test.suite", "integration"); + scope.SetTag("test.type", testType); + scope.User = new SentryUser + { + Id = "12345", + Username = "TestUser", + Email = "user-mail@test.abc" + }; + }); + + SentrySdk.AddBreadcrumb("Context configuration finished"); + } + + private void MessageCapture() + { + AddIntegrationTestContext("message-capture"); + + var eventId = SentrySdk.CaptureMessage("Integration test message"); + Debug.Log($"EVENT_CAPTURED: {eventId}"); + + Application.Quit(0); + } + + private void ExceptionCapture() + { + AddIntegrationTestContext("exception-capture"); + + try + { + throw new InvalidOperationException("Integration test exception"); + } + catch (Exception ex) + { + var eventId = SentrySdk.CaptureException(ex); + Debug.Log($"EVENT_CAPTURED: {eventId}"); + } + + Application.Quit(0); + } + + private void CrashCapture() + { + var crashId = Guid.NewGuid().ToString(); + + AddIntegrationTestContext("crash-capture"); + + SentrySdk.ConfigureScope(scope => + { + scope.SetTag("test.crash_id", crashId); + }); + + Debug.Log($"EVENT_CAPTURED: {crashId}"); + Debug.Log("CRASH TEST: Issuing a native crash (FatalError)"); + + Utils.ForceCrash(ForcedCrashCategory.FatalError); + + // Should not reach here + Debug.LogError("CRASH TEST: FAIL - unexpected code executed after crash"); + Application.Quit(1); + } + + private void CrashSend() + { + Debug.Log("CrashSend: Initializing Sentry to flush cached crash report..."); + + // Sentry is already initialized by IntegrationOptionsConfiguration. + // Just wait a bit for the queued crash report to be sent, then quit. + StartCoroutine(WaitAndQuit()); + } + + private IEnumerator WaitAndQuit() + { + // Wait for the crash report to be sent + yield return new WaitForSeconds(10); + + SentrySdk.FlushAsync(TimeSpan.FromSeconds(5)).GetAwaiter().GetResult(); + + Debug.Log("CrashSend: Flush complete, quitting."); + Application.Quit(0); + } +} diff --git a/test/Scripts.Integration.Test/Scripts/IntegrationTester.cs.meta b/test/Scripts.Integration.Test/Scripts/IntegrationTester.cs.meta new file mode 100644 index 000000000..dc7b008b6 --- /dev/null +++ b/test/Scripts.Integration.Test/Scripts/IntegrationTester.cs.meta @@ -0,0 +1,11 @@ +fileFormatVersion: 2 +guid: b2c3d4e5f6a7b8c9d0e1f2a3b4c5d6e7 +MonoImporter: + externalObjects: {} + serializedVersion: 2 + defaultReferences: [] + executionOrder: 0 + icon: {instanceID: 0} + userData: + assetBundleName: + assetBundleVariant: diff --git a/test/Scripts.Integration.Test/Scripts/TestLauncher.cs b/test/Scripts.Integration.Test/Scripts/TestLauncher.cs new file mode 100644 index 000000000..caadcf721 --- /dev/null +++ b/test/Scripts.Integration.Test/Scripts/TestLauncher.cs @@ -0,0 +1,80 @@ +using System; +using System.Runtime.InteropServices; +using UnityEngine; + +#if UNITY_WEBGL +using System.Web; +#endif + +public class TestLauncher : MonoBehaviour +{ + private void Awake() + { + Debug.Log("TestLauncher, awake!"); + Application.quitting += () => + { + // Keep "SmokeTester is quitting." for backward compatibility with smoke-test-android.ps1 + // and run-smoke-test.ps1 which look for this exact string to detect test completion. + Debug.Log("SmokeTester is quitting."); + }; + } + + public void Start() + { + var arg = GetTestArg(); + Debug.Log($"TestLauncher arg: '{arg}'"); + + switch (arg) + { + // Legacy smoke test commands -> SmokeTester + case "smoke": + case "crash": + case "has-crashed": + case "hasnt-crashed": + gameObject.AddComponent(); + break; + + // Integration test commands -> IntegrationTester + case "message-capture": + case "exception-capture": + case "crash-capture": + case "crash-send": + gameObject.AddComponent(); + break; + + default: + Debug.LogError($"Unknown test command: {arg}"); + Application.Quit(1); + break; + } + } + +#if UNITY_IOS && !UNITY_EDITOR + [DllImport("__Internal", EntryPoint="getTestArgObjectiveC")] + private static extern string GetTestArg(); +#else + internal static string GetTestArg() + { + string arg = null; +#if UNITY_EDITOR +#elif UNITY_ANDROID + using (var unityPlayer = new AndroidJavaClass("com.unity3d.player.UnityPlayer")) + using (var currentActivity = unityPlayer.GetStatic("currentActivity")) + using (var intent = currentActivity.Call("getIntent")) + { + arg = intent.Call("getStringExtra", "test"); + } +#elif UNITY_WEBGL + var uri = new Uri(Application.absoluteURL); + arg = HttpUtility.ParseQueryString(uri.Query).Get("test"); +#else + var args = Environment.GetCommandLineArgs(); + if (args.Length > 2 && args[1] == "--test") + { + arg = args[2]; + } +#endif + return arg; + } +#endif +} diff --git a/test/Scripts.Integration.Test/Scripts/TestLauncher.cs.meta b/test/Scripts.Integration.Test/Scripts/TestLauncher.cs.meta new file mode 100644 index 000000000..69999fcd4 --- /dev/null +++ b/test/Scripts.Integration.Test/Scripts/TestLauncher.cs.meta @@ -0,0 +1,11 @@ +fileFormatVersion: 2 +guid: a1b2c3d4e5f6a7b8c9d0e1f2a3b4c5d6 +MonoImporter: + externalObjects: {} + serializedVersion: 2 + defaultReferences: [] + executionOrder: 0 + icon: {instanceID: 0} + userData: + assetBundleName: + assetBundleVariant: diff --git a/test/Scripts.Integration.Test/configure-sentry.ps1 b/test/Scripts.Integration.Test/configure-sentry.ps1 index 332716a22..846f08ca5 100644 --- a/test/Scripts.Integration.Test/configure-sentry.ps1 +++ b/test/Scripts.Integration.Test/configure-sentry.ps1 @@ -1,7 +1,9 @@ param( [string] $UnityPath, [string] $Platform = "", - [Switch] $CheckSymbols + [Switch] $CheckSymbols, + [ValidateSet("smoke", "integration")] + [string] $TestMode = "smoke" ) if (-not $Global:NewProjectPathCache) @@ -13,12 +15,14 @@ if (-not $Global:NewProjectPathCache) $UnityPath = FormatUnityPath $UnityPath -Write-Log "Configuring Sentry options..." +Write-Log "Configuring Sentry options (TestMode: $TestMode)..." + +$optionsScript = if ($TestMode -eq "integration") { "IntegrationOptionsConfiguration" } else { "OptionsConfiguration" } $unityArgs = @( ` "-quit", "-batchmode", "-nographics", "-disable-assembly-updater", "-projectPath ", $(GetNewProjectPath), ` "-executeMethod", "Sentry.Unity.Editor.ConfigurationWindow.SentryEditorWindowInstrumentation.ConfigureOptions", ` - "-optionsScript", "OptionsConfiguration", ` + "-optionsScript", $optionsScript, ` "-cliOptionsScript", "CliConfiguration", ` "-cliOptions.UrlOverride", ($CheckSymbols ? (SymbolServerUrlFor $UnityPath $Platform) : "") ) From d90e410d3fb11fba081910dfead67fbde143266a Mon Sep 17 00:00:00 2001 From: bitsandfoxes Date: Fri, 13 Feb 2026 18:22:31 +0100 Subject: [PATCH 02/20] Fixes --- modules/app-runner | 1 + test/Scripts.Integration.Test/Scripts/TestLauncher.cs | 2 +- 2 files changed, 2 insertions(+), 1 deletion(-) create mode 160000 modules/app-runner diff --git a/modules/app-runner b/modules/app-runner new file mode 160000 index 000000000..0f3a63a67 --- /dev/null +++ b/modules/app-runner @@ -0,0 +1 @@ +Subproject commit 0f3a63a67aeead62b004e1d1548ffe47dc630fb6 diff --git a/test/Scripts.Integration.Test/Scripts/TestLauncher.cs b/test/Scripts.Integration.Test/Scripts/TestLauncher.cs index caadcf721..e98281705 100644 --- a/test/Scripts.Integration.Test/Scripts/TestLauncher.cs +++ b/test/Scripts.Integration.Test/Scripts/TestLauncher.cs @@ -51,7 +51,7 @@ public void Start() #if UNITY_IOS && !UNITY_EDITOR [DllImport("__Internal", EntryPoint="getTestArgObjectiveC")] - private static extern string GetTestArg(); + internal static extern string GetTestArg(); #else internal static string GetTestArg() { From 323d1873c37c2c14b89258bd629982e06ee28b2f Mon Sep 17 00:00:00 2001 From: bitsandfoxes Date: Mon, 16 Feb 2026 10:59:49 +0100 Subject: [PATCH 03/20] Remove startup awake span assertion --- test/Scripts.Integration.Test/Scripts/SmokeTester.cs | 3 --- 1 file changed, 3 deletions(-) diff --git a/test/Scripts.Integration.Test/Scripts/SmokeTester.cs b/test/Scripts.Integration.Test/Scripts/SmokeTester.cs index 3657b5039..fb88633e6 100644 --- a/test/Scripts.Integration.Test/Scripts/SmokeTester.cs +++ b/test/Scripts.Integration.Test/Scripts/SmokeTester.cs @@ -126,9 +126,6 @@ private IEnumerator SmokeTestCoroutine() t.ExpectMessage(currentMessage, "'type':'transaction"); t.ExpectMessage(currentMessage, "'op':'app.start'"); // startup transaction -#if !UNITY_EDITOR - t.ExpectMessage(currentMessage, "'op':'awake','description':'Main Camera.SmokeTester'"); // auto instrumentation -#endif t.ExpectMessageNot(currentMessage, "'length':0"); var guid = Guid.NewGuid().ToString(); From 5b30b7d94ec3ba022681ec1e8422ae79b37c4b16 Mon Sep 17 00:00:00 2001 From: bitsandfoxes Date: Mon, 16 Feb 2026 11:06:37 +0100 Subject: [PATCH 04/20] Fixed desktop test validation order --- test/Scripts.Integration.Test/run-smoke-test.ps1 | 16 +++++++++++++--- 1 file changed, 13 insertions(+), 3 deletions(-) diff --git a/test/Scripts.Integration.Test/run-smoke-test.ps1 b/test/Scripts.Integration.Test/run-smoke-test.ps1 index 5942a3827..59c3c36ad 100644 --- a/test/Scripts.Integration.Test/run-smoke-test.ps1 +++ b/test/Scripts.Integration.Test/run-smoke-test.ps1 @@ -106,9 +106,20 @@ function RunTest([string] $type) Write-Log "$type test: Player.log contents END" -ForegroundColor Yellow } + # Check for test failures first - a graceful shutdown doesn't mean tests passed. + $lineWithFailure = $appLog | Select-String "$($type.ToUpper()) TEST: FAIL" + If ($lineWithFailure) + { + $info = "Test process finished with status code $($process.ExitCode). $lineWithFailure" + If ($type -ne "crash") + { + throw $info + } + Write-Log $info + } # Relying on ExitCode does not seem reliable. We're looking for the line "SmokeTester is quitting." instead to indicate # a successful shut-down. - If ($appLog | Select-String "SmokeTester is quitting.") + ElseIf ($appLog | Select-String "SmokeTester is quitting.") { Write-Log "$type test: PASSED" -ForegroundColor Green } @@ -119,8 +130,7 @@ function RunTest([string] $type) } Else { - $lineWithFailure = $appLog | Select-String "$($type.ToUpper()) TEST: FAIL" - $info = "Test process finished with status code $($process.ExitCode). $lineWithFailure" + $info = "Test process finished with status code $($process.ExitCode). No completion marker found in Player.log" If ($type -ne "crash") { throw $info From 20554a9fe96394263f13110dcdd271e679e002aa Mon Sep 17 00:00:00 2001 From: bitsandfoxes Date: Mon, 16 Feb 2026 11:09:02 +0100 Subject: [PATCH 05/20] Fixed single-line pwsh command --- .github/workflows/smoke-test-run-android.yml | 5 +---- 1 file changed, 1 insertion(+), 4 deletions(-) diff --git a/.github/workflows/smoke-test-run-android.yml b/.github/workflows/smoke-test-run-android.yml index e7029ab43..7a6805005 100644 --- a/.github/workflows/smoke-test-run-android.yml +++ b/.github/workflows/smoke-test-run-android.yml @@ -90,10 +90,7 @@ jobs: adb wait-for-device adb shell input keyevent 82 adb devices -l - pwsh -Command ' - $env:SENTRY_TEST_APK = "samples/IntegrationTest/Build/test.apk" - Invoke-Pester -Script test/IntegrationTest/Integration.Tests.ps1 -Output Detailed - ' + pwsh -Command '$env:SENTRY_TEST_APK = "samples/IntegrationTest/Build/test.apk"; Invoke-Pester -Script test/IntegrationTest/Integration.Tests.ps1 -Output Detailed' - name: Upload test results on failure if: ${{ failure() }} From b899cc5f696125df264caaaaf5140433f5dbbe29 Mon Sep 17 00:00:00 2001 From: bitsandfoxes Date: Mon, 16 Feb 2026 11:35:33 +0100 Subject: [PATCH 06/20] strictmode workaround --- test/IntegrationTest/Integration.Tests.ps1 | 5 +---- 1 file changed, 1 insertion(+), 4 deletions(-) diff --git a/test/IntegrationTest/Integration.Tests.ps1 b/test/IntegrationTest/Integration.Tests.ps1 index 0bf2bcb24..6e8a645c9 100644 --- a/test/IntegrationTest/Integration.Tests.ps1 +++ b/test/IntegrationTest/Integration.Tests.ps1 @@ -11,10 +11,7 @@ Set-StrictMode -Version latest $ErrorActionPreference = "Stop" $global:DebugPreference = "Continue" -if (-not $Global:NewProjectPathCache) -{ - . $PSScriptRoot/../Scripts.Integration.Test/globals.ps1 -} +. $PSScriptRoot/../Scripts.Integration.Test/common.ps1 # Import app-runner modules . $PSScriptRoot/../../modules/app-runner/import-modules.ps1 From 7c93e12942a4f0c792752e3867293603d96f7288 Mon Sep 17 00:00:00 2001 From: bitsandfoxes Date: Mon, 16 Feb 2026 11:59:28 +0100 Subject: [PATCH 07/20] Baking the DSN --- .../SentryEditorWindowInstrumentation.cs | 6 ++++++ .../Scripts/IntegrationOptionsConfiguration.cs | 4 ++-- test/Scripts.Integration.Test/configure-sentry.ps1 | 4 ++++ 3 files changed, 12 insertions(+), 2 deletions(-) diff --git a/src/Sentry.Unity.Editor/ConfigurationWindow/SentryEditorWindowInstrumentation.cs b/src/Sentry.Unity.Editor/ConfigurationWindow/SentryEditorWindowInstrumentation.cs index 5be965316..b64677096 100644 --- a/src/Sentry.Unity.Editor/ConfigurationWindow/SentryEditorWindowInstrumentation.cs +++ b/src/Sentry.Unity.Editor/ConfigurationWindow/SentryEditorWindowInstrumentation.cs @@ -50,6 +50,12 @@ private static void ConfigureOptions(Dictionary args, [CallerMem OptionsConfigurationItem.SetScript(value); } + if (args.TryGetValue("dsn", out value)) + { + Debug.LogFormat("{0}: Setting DSN", functionName); + options.Dsn = value; + } + optionsWindow.Close(); Debug.LogFormat("{0}: SUCCESS", functionName); } diff --git a/test/Scripts.Integration.Test/Scripts/IntegrationOptionsConfiguration.cs b/test/Scripts.Integration.Test/Scripts/IntegrationOptionsConfiguration.cs index ff5d32e3b..7f8352899 100644 --- a/test/Scripts.Integration.Test/Scripts/IntegrationOptionsConfiguration.cs +++ b/test/Scripts.Integration.Test/Scripts/IntegrationOptionsConfiguration.cs @@ -9,8 +9,8 @@ public override void Configure(SentryUnityOptions options) { Debug.Log("Sentry: IntegrationOptionsConfig::Configure() called"); - // DSN is read from SentryOptions.asset (baked at build time via configure-sentry.ps1) - // No custom DSN override needed -- the real DSN is set in the editor configuration. + // DSN is baked into SentryOptions.asset at build time by configure-sentry.ps1 + // which passes the SENTRY_DSN env var to ConfigureOptions via the -dsn argument. options.Environment = "integration-test"; options.Release = "sentry-unity-test@1.0.0"; diff --git a/test/Scripts.Integration.Test/configure-sentry.ps1 b/test/Scripts.Integration.Test/configure-sentry.ps1 index 846f08ca5..daa9d4bb1 100644 --- a/test/Scripts.Integration.Test/configure-sentry.ps1 +++ b/test/Scripts.Integration.Test/configure-sentry.ps1 @@ -26,6 +26,10 @@ $unityArgs = @( ` "-cliOptionsScript", "CliConfiguration", ` "-cliOptions.UrlOverride", ($CheckSymbols ? (SymbolServerUrlFor $UnityPath $Platform) : "") ) +if ($env:SENTRY_DSN) { + $unityArgs += @("-dsn", $env:SENTRY_DSN) +} + RunUnityAndExpect $UnityPath "ConfigureSentryOptions" "ConfigureOptions: SUCCESS" $unityArgs From 3dceec0dbae0e46ffe1e5f25cc6375467b193f7b Mon Sep 17 00:00:00 2001 From: bitsandfoxes Date: Mon, 16 Feb 2026 12:56:10 +0100 Subject: [PATCH 08/20] . --- .github/workflows/smoke-test-run-android.yml | 5 ++++- 1 file changed, 4 insertions(+), 1 deletion(-) diff --git a/.github/workflows/smoke-test-run-android.yml b/.github/workflows/smoke-test-run-android.yml index 7a6805005..987d8c2a5 100644 --- a/.github/workflows/smoke-test-run-android.yml +++ b/.github/workflows/smoke-test-run-android.yml @@ -90,7 +90,10 @@ jobs: adb wait-for-device adb shell input keyevent 82 adb devices -l - pwsh -Command '$env:SENTRY_TEST_APK = "samples/IntegrationTest/Build/test.apk"; Invoke-Pester -Script test/IntegrationTest/Integration.Tests.ps1 -Output Detailed' + # Add Android SDK build-tools to PATH so aapt2 is available + BUILD_TOOLS_DIR=$(ls -d $ANDROID_HOME/build-tools/*/ | sort -V | tail -1) + export PATH="$BUILD_TOOLS_DIR:$PATH" + pwsh -Command '$env:SENTRY_TEST_APK = "samples/IntegrationTest/Build/test.apk"; Invoke-Pester -Script test/IntegrationTest/Integration.Tests.ps1 -CI' - name: Upload test results on failure if: ${{ failure() }} From e999f6e08d7f6f53163aa3619ec99cd8128c0d97 Mon Sep 17 00:00:00 2001 From: bitsandfoxes Date: Mon, 16 Feb 2026 13:23:14 +0100 Subject: [PATCH 09/20] Fix env --- .github/workflows/smoke-test-run-android.yml | 10 +++++++--- 1 file changed, 7 insertions(+), 3 deletions(-) diff --git a/.github/workflows/smoke-test-run-android.yml b/.github/workflows/smoke-test-run-android.yml index 987d8c2a5..663dad69f 100644 --- a/.github/workflows/smoke-test-run-android.yml +++ b/.github/workflows/smoke-test-run-android.yml @@ -65,6 +65,13 @@ jobs: mkdir -p $HOME/.android/avd touch $HOME/.android/repositories.cfg + - name: Add Android build-tools to PATH + run: | + BUILD_TOOLS_DIR=$(ls -d $ANDROID_HOME/build-tools/*/ | sort -V | tail -1) + echo "Found build-tools at: $BUILD_TOOLS_DIR" + echo "$BUILD_TOOLS_DIR" >> $GITHUB_PATH + shell: bash + - name: Run Android Integration Tests uses: reactivecircus/android-emulator-runner@d94c3fbe4fe6a29e4a5ba47c12fb47677c73656b # pin@v2.33.0 id: integration-test @@ -90,9 +97,6 @@ jobs: adb wait-for-device adb shell input keyevent 82 adb devices -l - # Add Android SDK build-tools to PATH so aapt2 is available - BUILD_TOOLS_DIR=$(ls -d $ANDROID_HOME/build-tools/*/ | sort -V | tail -1) - export PATH="$BUILD_TOOLS_DIR:$PATH" pwsh -Command '$env:SENTRY_TEST_APK = "samples/IntegrationTest/Build/test.apk"; Invoke-Pester -Script test/IntegrationTest/Integration.Tests.ps1 -CI' - name: Upload test results on failure From b6fc8a2d4bbab8acf3717d525cbf88fbf1c84c6f Mon Sep 17 00:00:00 2001 From: bitsandfoxes Date: Mon, 16 Feb 2026 14:27:58 +0100 Subject: [PATCH 10/20] . --- test/IntegrationTest/Integration.Tests.ps1 | 18 ++++++++---------- 1 file changed, 8 insertions(+), 10 deletions(-) diff --git a/test/IntegrationTest/Integration.Tests.ps1 b/test/IntegrationTest/Integration.Tests.ps1 index 6e8a645c9..c2168410a 100644 --- a/test/IntegrationTest/Integration.Tests.ps1 +++ b/test/IntegrationTest/Integration.Tests.ps1 @@ -11,8 +11,6 @@ Set-StrictMode -Version latest $ErrorActionPreference = "Stop" $global:DebugPreference = "Continue" -. $PSScriptRoot/../Scripts.Integration.Test/common.ps1 - # Import app-runner modules . $PSScriptRoot/../../modules/app-runner/import-modules.ps1 @@ -43,12 +41,12 @@ BeforeAll { # Launch app again to ensure crash report is sent if ($Action -eq "crash-capture" -or $runResult.ExitCode -ne 0) { Write-Host "Running crash-send to ensure crash report is sent..." - Write-Log "::group::Log of crash-send" + Write-Host "::group::Log of crash-send" $sendExtras = @("-e", "test", "crash-send") Invoke-DeviceApp -ExecutablePath $script:AndroidComponent -Arguments $sendExtras - Write-Log "::endgroup::" + Write-Host "::endgroup::" } return $runResult @@ -102,9 +100,9 @@ Describe "Unity Android Integration Tests" { $eventId = Get-EventIds -AppOutput $script:runResult.Output -ExpectedCount 1 if ($eventId) { - Write-Log "::group::Getting event content" + Write-Host "::group::Getting event content" $script:runEvent = Get-SentryTestEvent -EventId "$eventId" - Write-Log "::endgroup::" + Write-Host "::endgroup::" } } @@ -127,9 +125,9 @@ Describe "Unity Android Integration Tests" { $eventId = Get-EventIds -AppOutput $script:runResult.Output -ExpectedCount 1 if ($eventId) { - Write-Log "::group::Getting event content" + Write-Host "::group::Getting event content" $script:runEvent = Get-SentryTestEvent -EventId "$eventId" - Write-Log "::endgroup::" + Write-Host "::endgroup::" } } @@ -160,9 +158,9 @@ Describe "Unity Android Integration Tests" { $eventId = Get-EventIds -AppOutput $script:runResult.Output -ExpectedCount 1 if ($eventId) { - Write-Log "::group::Getting event content" + Write-Host "::group::Getting event content" $script:runEvent = Get-SentryTestEvent -TagName "test.crash_id" -TagValue "$eventId" -TimeoutSeconds 120 - Write-Log "::endgroup::" + Write-Host "::endgroup::" } } From f39fb139bab27467a1c508abc7deb58b6d0cc9fc Mon Sep 17 00:00:00 2001 From: bitsandfoxes Date: Mon, 16 Feb 2026 15:05:19 +0100 Subject: [PATCH 11/20] Fix double event capture --- test/IntegrationTest/Integration.Tests.ps1 | 2 +- test/Scripts.Integration.Test/Scripts/IntegrationTester.cs | 4 ++-- 2 files changed, 3 insertions(+), 3 deletions(-) diff --git a/test/IntegrationTest/Integration.Tests.ps1 b/test/IntegrationTest/Integration.Tests.ps1 index c2168410a..d45079203 100644 --- a/test/IntegrationTest/Integration.Tests.ps1 +++ b/test/IntegrationTest/Integration.Tests.ps1 @@ -39,7 +39,7 @@ BeforeAll { $runResult | ConvertTo-Json -Depth 5 | Out-File -FilePath (Get-OutputFilePath "${Action}-result.json") # Launch app again to ensure crash report is sent - if ($Action -eq "crash-capture" -or $runResult.ExitCode -ne 0) { + if ($Action -eq "crash-capture") { Write-Host "Running crash-send to ensure crash report is sent..." Write-Host "::group::Log of crash-send" diff --git a/test/Scripts.Integration.Test/Scripts/IntegrationTester.cs b/test/Scripts.Integration.Test/Scripts/IntegrationTester.cs index b4554bbe0..fb5b30a8c 100644 --- a/test/Scripts.Integration.Test/Scripts/IntegrationTester.cs +++ b/test/Scripts.Integration.Test/Scripts/IntegrationTester.cs @@ -92,9 +92,9 @@ private void CrashCapture() }); Debug.Log($"EVENT_CAPTURED: {crashId}"); - Debug.Log("CRASH TEST: Issuing a native crash (FatalError)"); + Debug.Log("CRASH TEST: Issuing a native crash (Abort)"); - Utils.ForceCrash(ForcedCrashCategory.FatalError); + Utils.ForceCrash(ForcedCrashCategory.Abort); // Should not reach here Debug.LogError("CRASH TEST: FAIL - unexpected code executed after crash"); From 3902b6826cf9e88937dd4d2455405d54a1ca6d80 Mon Sep 17 00:00:00 2001 From: bitsandfoxes Date: Mon, 16 Feb 2026 15:08:28 +0100 Subject: [PATCH 12/20] Fixed log grouping --- test/IntegrationTest/Integration.Tests.ps1 | 3 --- 1 file changed, 3 deletions(-) diff --git a/test/IntegrationTest/Integration.Tests.ps1 b/test/IntegrationTest/Integration.Tests.ps1 index d45079203..95f53d75d 100644 --- a/test/IntegrationTest/Integration.Tests.ps1 +++ b/test/IntegrationTest/Integration.Tests.ps1 @@ -41,12 +41,9 @@ BeforeAll { # Launch app again to ensure crash report is sent if ($Action -eq "crash-capture") { Write-Host "Running crash-send to ensure crash report is sent..." - Write-Host "::group::Log of crash-send" $sendExtras = @("-e", "test", "crash-send") Invoke-DeviceApp -ExecutablePath $script:AndroidComponent -Arguments $sendExtras - - Write-Host "::endgroup::" } return $runResult From 0638d92f10c269568ba3af231e8f4f7d6342df0c Mon Sep 17 00:00:00 2001 From: bitsandfoxes Date: Mon, 16 Feb 2026 15:37:06 +0100 Subject: [PATCH 13/20] Debug log level --- test/IntegrationTest/Integration.Tests.ps1 | 1 - 1 file changed, 1 deletion(-) diff --git a/test/IntegrationTest/Integration.Tests.ps1 b/test/IntegrationTest/Integration.Tests.ps1 index 95f53d75d..70a67734c 100644 --- a/test/IntegrationTest/Integration.Tests.ps1 +++ b/test/IntegrationTest/Integration.Tests.ps1 @@ -9,7 +9,6 @@ Set-StrictMode -Version latest $ErrorActionPreference = "Stop" -$global:DebugPreference = "Continue" # Import app-runner modules . $PSScriptRoot/../../modules/app-runner/import-modules.ps1 From f05f22363505eb208e4789c76150995905a21281 Mon Sep 17 00:00:00 2001 From: bitsandfoxes Date: Mon, 16 Feb 2026 15:47:24 +0100 Subject: [PATCH 14/20] Fixing 'dist' --- src/Sentry.Unity.Android/SentryJava.cs | 1 + .../Android/AndroidManifestConfiguration.cs | 8 ++++++++ 2 files changed, 9 insertions(+) diff --git a/src/Sentry.Unity.Android/SentryJava.cs b/src/Sentry.Unity.Android/SentryJava.cs index bb888bf4a..883bdc2aa 100644 --- a/src/Sentry.Unity.Android/SentryJava.cs +++ b/src/Sentry.Unity.Android/SentryJava.cs @@ -115,6 +115,7 @@ public void Init(SentryUnityOptions options) androidOptions.Call("setDsn", options.Dsn); androidOptions.Call("setDebug", options.Debug); androidOptions.Call("setRelease", options.Release); + androidOptions.Call("setDist", options.Distribution); androidOptions.Call("setEnvironment", options.Environment); var sentryLevelClass = new AndroidJavaClass("io.sentry.SentryLevel"); diff --git a/src/Sentry.Unity.Editor/Android/AndroidManifestConfiguration.cs b/src/Sentry.Unity.Editor/Android/AndroidManifestConfiguration.cs index f524a753e..1c90cacb6 100644 --- a/src/Sentry.Unity.Editor/Android/AndroidManifestConfiguration.cs +++ b/src/Sentry.Unity.Editor/Android/AndroidManifestConfiguration.cs @@ -176,6 +176,12 @@ internal void ModifyManifest(string basePath) androidManifest.SetRelease(_options.Release); } + if (_options.Distribution is not null) + { + _logger.LogDebug("Setting Dist: {0}", _options.Distribution); + androidManifest.SetDist(_options.Distribution); + } + if (_options.Environment is not null) { _logger.LogDebug("Setting Environment: {0}", _options.Environment); @@ -469,6 +475,8 @@ internal void SetSampleRate(float sampleRate) => internal void SetRelease(string release) => SetMetaData($"{SentryPrefix}.release", release); + internal void SetDist(string dist) => SetMetaData($"{SentryPrefix}.dist", dist); + internal void SetAttachScreenshot(bool value) => SetMetaData($"{SentryPrefix}.attach-screenshot", value.ToString()); internal void SetEnvironment(string environment) => SetMetaData($"{SentryPrefix}.environment", environment); From 160f355b2613253963168d33362e65599d568c43 Mon Sep 17 00:00:00 2001 From: bitsandfoxes Date: Mon, 16 Feb 2026 16:33:36 +0100 Subject: [PATCH 15/20] fallback to activity --- test/IntegrationTest/Integration.Tests.ps1 | 14 +++++++++++++- 1 file changed, 13 insertions(+), 1 deletion(-) diff --git a/test/IntegrationTest/Integration.Tests.ps1 b/test/IntegrationTest/Integration.Tests.ps1 index 70a67734c..2e1d808a5 100644 --- a/test/IntegrationTest/Integration.Tests.ps1 +++ b/test/IntegrationTest/Integration.Tests.ps1 @@ -32,7 +32,19 @@ BeforeAll { $extras = @("-e", "test", $Action) - $runResult = Invoke-DeviceApp -ExecutablePath $script:AndroidComponent -Arguments $extras + try { + $runResult = Invoke-DeviceApp -ExecutablePath $script:AndroidComponent -Arguments $extras + } + catch { + if ($_.Exception.Message -match "Activity class .* does not exist" -or $_.Exception.Message -match "Error type 3") { + Write-Host "Activity not found, trying fallback: $($script:FallbackAndroidComponent)" + $script:AndroidComponent = $script:FallbackAndroidComponent + $runResult = Invoke-DeviceApp -ExecutablePath $script:AndroidComponent -Arguments $extras + } + else { + throw + } + } # Save result to JSON file $runResult | ConvertTo-Json -Depth 5 | Out-File -FilePath (Get-OutputFilePath "${Action}-result.json") From f5e1c10d24ab938a5cbb4adccbc88285803ac314 Mon Sep 17 00:00:00 2001 From: bitsandfoxes Date: Mon, 16 Feb 2026 17:51:29 +0100 Subject: [PATCH 16/20] Do some fake work --- .../Scripts/IntegrationTester.cs | 21 ++++++++++++++++++- 1 file changed, 20 insertions(+), 1 deletion(-) diff --git a/test/Scripts.Integration.Test/Scripts/IntegrationTester.cs b/test/Scripts.Integration.Test/Scripts/IntegrationTester.cs index fb5b30a8c..14c7f65a3 100644 --- a/test/Scripts.Integration.Test/Scripts/IntegrationTester.cs +++ b/test/Scripts.Integration.Test/Scripts/IntegrationTester.cs @@ -1,6 +1,7 @@ using System; using System.Collections; using System.Collections.Generic; +using System.Runtime.CompilerServices; using Sentry; using Sentry.Unity; using UnityEngine; @@ -69,7 +70,7 @@ private void ExceptionCapture() try { - throw new InvalidOperationException("Integration test exception"); + DoSomeWork(); } catch (Exception ex) { @@ -80,6 +81,24 @@ private void ExceptionCapture() Application.Quit(0); } + // Use a deeper call stack with NoInlining to ensure Unity 2022's IL2CPP + // produces a non-empty managed stack trace (single-method throw/catch can + // result in an empty stack trace with OptimizeSize + High stripping). + [MethodImpl(MethodImplOptions.NoInlining)] + private static void DoSomeWork() + { + if (DateTime.Now.Ticks > 0) // Always true but not optimizable + { + ThrowException(); + } + } + + [MethodImpl(MethodImplOptions.NoInlining)] + private static void ThrowException() + { + throw new InvalidOperationException("Integration test exception"); + } + private void CrashCapture() { var crashId = Guid.NewGuid().ToString(); From 10ba69c454065ceca6d15b9eeb62b25afb9fe61b Mon Sep 17 00:00:00 2001 From: bitsandfoxes Date: Tue, 17 Feb 2026 10:15:34 +0100 Subject: [PATCH 17/20] Drop stripping level for older than 6 --- test/Scripts.Integration.Test/Editor/Builder.cs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/test/Scripts.Integration.Test/Editor/Builder.cs b/test/Scripts.Integration.Test/Editor/Builder.cs index 7cc900ecc..5dab872c5 100644 --- a/test/Scripts.Integration.Test/Editor/Builder.cs +++ b/test/Scripts.Integration.Test/Editor/Builder.cs @@ -38,7 +38,7 @@ public static void BuildIl2CPPPlayer(BuildTarget target, BuildTargetGroup group, #endif Debug.Log("Builder: Configuring code stripping level"); -#if UNITY_2022_1_OR_NEWER +#if UNITY_6000_0_OR_NEWER PlayerSettings.SetManagedStrippingLevel(NamedBuildTarget.FromBuildTargetGroup(group), ManagedStrippingLevel.High); #else PlayerSettings.SetManagedStrippingLevel(NamedBuildTarget.FromBuildTargetGroup(group), ManagedStrippingLevel.Low); From c2e12566a484d4331fcffb4c026b39a2930782d8 Mon Sep 17 00:00:00 2001 From: bitsandfoxes Date: Tue, 17 Feb 2026 10:22:41 +0100 Subject: [PATCH 18/20] Fixed vulkan issues --- test/Scripts.Integration.Test/Editor/Builder.cs | 9 ++++----- 1 file changed, 4 insertions(+), 5 deletions(-) diff --git a/test/Scripts.Integration.Test/Editor/Builder.cs b/test/Scripts.Integration.Test/Editor/Builder.cs index 5dab872c5..935b8009b 100644 --- a/test/Scripts.Integration.Test/Editor/Builder.cs +++ b/test/Scripts.Integration.Test/Editor/Builder.cs @@ -137,13 +137,12 @@ public static void BuildAndroidIl2CPPPlayer() { Debug.Log("Builder: Building Android IL2CPP Player"); -#if UNITY_6000_3_OR_NEWER - // Force OpenGLES3 to avoid Vulkan emulator crashes in CI. - // The Android emulator's swiftshader Vulkan implementation has shutdown issues - // with Unity 6000.3+ that cause SIGSEGV in libvulkan_enc.so after tests complete. + // Force OpenGLES3 to avoid Vulkan issues with the Android emulator in CI. + // The emulator's swiftshader Vulkan implementation doesn't fully support Unity's + // Vulkan usage, causing "Processed some Vulkan packets without process resources + // created" warnings and SIGSEGV crashes in libvulkan_enc.so. PlayerSettings.SetUseDefaultGraphicsAPIs(BuildTarget.Android, false); PlayerSettings.SetGraphicsAPIs(BuildTarget.Android, new[] { UnityEngine.Rendering.GraphicsDeviceType.OpenGLES3 }); -#endif #if UNITY_2021_2_OR_NEWER && !UNITY_6000_0_OR_NEWER // Clean Android gradle cache to force regeneration of gradle files From a0318cfea5f2279ea95da42ccec8578368ee4545 Mon Sep 17 00:00:00 2001 From: bitsandfoxes Date: Tue, 17 Feb 2026 11:19:50 +0100 Subject: [PATCH 19/20] Code generation settings and log output --- test/IntegrationTest/Integration.Tests.ps1 | 5 +++++ test/Scripts.Integration.Test/Editor/Builder.cs | 7 +++---- 2 files changed, 8 insertions(+), 4 deletions(-) diff --git a/test/IntegrationTest/Integration.Tests.ps1 b/test/IntegrationTest/Integration.Tests.ps1 index 2e1d808a5..05c1fd8c1 100644 --- a/test/IntegrationTest/Integration.Tests.ps1 +++ b/test/IntegrationTest/Integration.Tests.ps1 @@ -57,6 +57,11 @@ BeforeAll { Invoke-DeviceApp -ExecutablePath $script:AndroidComponent -Arguments $sendExtras } + # Print app output so it's visible in CI logs + Write-Host "::group::App output ($Action)" + $runResult.Output | ForEach-Object { Write-Host $_ } + Write-Host "::endgroup::" + return $runResult } diff --git a/test/Scripts.Integration.Test/Editor/Builder.cs b/test/Scripts.Integration.Test/Editor/Builder.cs index 935b8009b..bb2ae779c 100644 --- a/test/Scripts.Integration.Test/Editor/Builder.cs +++ b/test/Scripts.Integration.Test/Editor/Builder.cs @@ -29,12 +29,11 @@ public static void BuildIl2CPPPlayer(BuildTarget target, BuildTargetGroup group, DisableUnityAudio(); DisableProgressiveLightMapper(); - // This should make IL2CCPP builds faster, see https://forum.unity.com/threads/il2cpp-build-time-improvements-seeking-feedback.1064135/ - Debug.Log("Builder: Setting IL2CPP generation to OptimizeSize"); + Debug.Log("Builder: Setting IL2CPP generation to OptimizeSpeed"); #if UNITY_2022_1_OR_NEWER - PlayerSettings.SetIl2CppCodeGeneration(NamedBuildTarget.FromBuildTargetGroup(group), UnityEditor.Build.Il2CppCodeGeneration.OptimizeSize); + PlayerSettings.SetIl2CppCodeGeneration(NamedBuildTarget.FromBuildTargetGroup(group), UnityEditor.Build.Il2CppCodeGeneration.OptimizeSpeed); #elif UNITY_2021_2_OR_NEWER - EditorUserBuildSettings.il2CppCodeGeneration = UnityEditor.Build.Il2CppCodeGeneration.OptimizeSize; + EditorUserBuildSettings.il2CppCodeGeneration = UnityEditor.Build.Il2CppCodeGeneration.OptimizeSpeed; #endif Debug.Log("Builder: Configuring code stripping level"); From aa815ff8ebb216c8fe07e07faee91769f82ac2d9 Mon Sep 17 00:00:00 2001 From: bitsandfoxes Date: Tue, 17 Feb 2026 13:03:25 +0100 Subject: [PATCH 20/20] AOT check --- src/sentry-dotnet | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/sentry-dotnet b/src/sentry-dotnet index 3a426e03f..d1f42b8c7 160000 --- a/src/sentry-dotnet +++ b/src/sentry-dotnet @@ -1 +1 @@ -Subproject commit 3a426e03f2bdd54b459ff5d2ba634d506e46e36e +Subproject commit d1f42b8c78b9ca36f3f73745287aaa3f3eba49b0