From 65bfb995b00db5062e739033bef78c1e7af7eb4a Mon Sep 17 00:00:00 2001 From: bitsandfoxes Date: Fri, 13 Feb 2026 17:12:43 +0100 Subject: [PATCH 01/86] 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/86] 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/86] 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/86] 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/86] 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/86] 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/86] 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/86] . --- .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/86] 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/86] . --- 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/86] 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/86] 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/86] 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/86] 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/86] 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/86] 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/86] 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/86] 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/86] 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/86] 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 From fc3c734bf223be5a2f3c2f586a519610d5f97eb8 Mon Sep 17 00:00:00 2001 From: bitsandfoxes Date: Thu, 19 Feb 2026 14:13:34 +0100 Subject: [PATCH 21/86] Bumped .NET to 'main' --- src/sentry-dotnet | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/sentry-dotnet b/src/sentry-dotnet index d1f42b8c7..f201b6535 160000 --- a/src/sentry-dotnet +++ b/src/sentry-dotnet @@ -1 +1 @@ -Subproject commit d1f42b8c78b9ca36f3f73745287aaa3f3eba49b0 +Subproject commit f201b6535b1ebeb51195ff1267c57bccfb46f9d6 From ab6d14e19b5d3d40783d41ee318846239dcaa9bd Mon Sep 17 00:00:00 2001 From: bitsandfoxes Date: Thu, 19 Feb 2026 14:24:40 +0100 Subject: [PATCH 22/86] Mark crash-capture skipped tests as Skipped instead of silently passing --- test/IntegrationTest/CommonTestCases.ps1 | 9 +++++---- 1 file changed, 5 insertions(+), 4 deletions(-) diff --git a/test/IntegrationTest/CommonTestCases.ps1 b/test/IntegrationTest/CommonTestCases.ps1 index 2bb1e1fa6..003c5ebc9 100644 --- a/test/IntegrationTest/CommonTestCases.ps1 +++ b/test/IntegrationTest/CommonTestCases.ps1 @@ -54,7 +54,7 @@ $CommonTestCases = @( param($TestSetup, $TestType, $SentryEvent, $RunResult) if ($TestType -eq "crash-capture") { - # User context may not survive native crashes on all platforms + Set-ItResult -Skipped -Because "user context may not survive native crashes" return } @@ -68,7 +68,7 @@ $CommonTestCases = @( param($TestSetup, $TestType, $SentryEvent, $RunResult) if ($TestType -eq "crash-capture") { - # Breadcrumbs may not survive native crashes + Set-ItResult -Skipped -Because "breadcrumbs may not survive native crashes" return } @@ -80,6 +80,7 @@ $CommonTestCases = @( param($TestSetup, $TestType, $SentryEvent, $RunResult) if ($TestType -eq "crash-capture") { + Set-ItResult -Skipped -Because "breadcrumbs may not survive native crashes" return } @@ -99,7 +100,7 @@ $CommonTestCases = @( param($TestSetup, $TestType, $SentryEvent, $RunResult) if ($TestType -eq "crash-capture") { - # App context may not be available for native crashes + Set-ItResult -Skipped -Because "app context may not be available for native crashes" return } @@ -121,7 +122,7 @@ $CommonTestCases = @( param($TestSetup, $TestType, $SentryEvent, $RunResult) if ($TestType -eq "crash-capture") { - # Unity context may not be synchronized to NDK for native crashes + Set-ItResult -Skipped -Because "Unity context may not be synchronized to NDK for native crashes" return } From af19c5e2d40ea065da6a2b29c4be390bba4514a7 Mon Sep 17 00:00:00 2001 From: bitsandfoxes Date: Thu, 19 Feb 2026 14:51:59 +0100 Subject: [PATCH 23/86] Extending the tests of scope sync to the crash tests --- test/IntegrationTest/CommonTestCases.ps1 | 25 ------------------- .../Scripts/IntegrationTester.cs | 7 ++++-- 2 files changed, 5 insertions(+), 27 deletions(-) diff --git a/test/IntegrationTest/CommonTestCases.ps1 b/test/IntegrationTest/CommonTestCases.ps1 index 003c5ebc9..8922db30a 100644 --- a/test/IntegrationTest/CommonTestCases.ps1 +++ b/test/IntegrationTest/CommonTestCases.ps1 @@ -53,11 +53,6 @@ $CommonTestCases = @( @{ Name = "Contains user information"; TestBlock = { param($TestSetup, $TestType, $SentryEvent, $RunResult) - if ($TestType -eq "crash-capture") { - Set-ItResult -Skipped -Because "user context may not survive native crashes" - return - } - $SentryEvent.user | Should -Not -BeNullOrEmpty $SentryEvent.user.username | Should -Be "TestUser" $SentryEvent.user.email | Should -Be "user-mail@test.abc" @@ -67,11 +62,6 @@ $CommonTestCases = @( @{ Name = "Contains breadcrumbs"; TestBlock = { param($TestSetup, $TestType, $SentryEvent, $RunResult) - if ($TestType -eq "crash-capture") { - Set-ItResult -Skipped -Because "breadcrumbs may not survive native crashes" - return - } - $SentryEvent.breadcrumbs | Should -Not -BeNullOrEmpty $SentryEvent.breadcrumbs.values | Should -Not -BeNullOrEmpty } @@ -79,11 +69,6 @@ $CommonTestCases = @( @{ Name = "Contains expected breadcrumbs"; TestBlock = { param($TestSetup, $TestType, $SentryEvent, $RunResult) - if ($TestType -eq "crash-capture") { - Set-ItResult -Skipped -Because "breadcrumbs may not survive native crashes" - 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 @@ -99,11 +84,6 @@ $CommonTestCases = @( @{ Name = "Contains app context"; TestBlock = { param($TestSetup, $TestType, $SentryEvent, $RunResult) - if ($TestType -eq "crash-capture") { - Set-ItResult -Skipped -Because "app context may not be available for native crashes" - return - } - $SentryEvent.contexts.app | Should -Not -BeNullOrEmpty } } @@ -121,11 +101,6 @@ $CommonTestCases = @( @{ Name = "Contains Unity context"; TestBlock = { param($TestSetup, $TestType, $SentryEvent, $RunResult) - if ($TestType -eq "crash-capture") { - Set-ItResult -Skipped -Because "Unity context may not be synchronized to NDK for native crashes" - return - } - $SentryEvent.contexts.unity | Should -Not -BeNullOrEmpty } } diff --git a/test/Scripts.Integration.Test/Scripts/IntegrationTester.cs b/test/Scripts.Integration.Test/Scripts/IntegrationTester.cs index 14c7f65a3..f9855343a 100644 --- a/test/Scripts.Integration.Test/Scripts/IntegrationTester.cs +++ b/test/Scripts.Integration.Test/Scripts/IntegrationTester.cs @@ -23,7 +23,7 @@ public void Start() ExceptionCapture(); break; case "crash-capture": - CrashCapture(); + StartCoroutine(CrashCapture()); break; case "crash-send": CrashSend(); @@ -99,7 +99,7 @@ private static void ThrowException() throw new InvalidOperationException("Integration test exception"); } - private void CrashCapture() + private IEnumerator CrashCapture() { var crashId = Guid.NewGuid().ToString(); @@ -110,6 +110,9 @@ private void CrashCapture() scope.SetTag("test.crash_id", crashId); }); + // Wait for the scope sync to complete on platforms that use a background thread (e.g. Android JNI) + yield return new WaitForSeconds(0.5f); + Debug.Log($"EVENT_CAPTURED: {crashId}"); Debug.Log("CRASH TEST: Issuing a native crash (Abort)"); From 9588ff9d92d7da294f6d1f170d86025eb875637b Mon Sep 17 00:00:00 2001 From: bitsandfoxes Date: Thu, 19 Feb 2026 15:33:20 +0100 Subject: [PATCH 24/86] Skip app context assertion for crash-capture on Android --- test/IntegrationTest/CommonTestCases.ps1 | 5 +++++ 1 file changed, 5 insertions(+) diff --git a/test/IntegrationTest/CommonTestCases.ps1 b/test/IntegrationTest/CommonTestCases.ps1 index 8922db30a..db1245362 100644 --- a/test/IntegrationTest/CommonTestCases.ps1 +++ b/test/IntegrationTest/CommonTestCases.ps1 @@ -84,6 +84,11 @@ $CommonTestCases = @( @{ Name = "Contains app context"; TestBlock = { param($TestSetup, $TestType, $SentryEvent, $RunResult) + if ($TestType -eq "crash-capture") { + Set-ItResult -Skipped -Because "app context is not synced to sentry-native on Android" + return + } + $SentryEvent.contexts.app | Should -Not -BeNullOrEmpty } } From 0ffce53349109333bf9c1e16dcf823f2d67649e6 Mon Sep 17 00:00:00 2001 From: bitsandfoxes Date: Thu, 19 Feb 2026 16:32:06 +0100 Subject: [PATCH 25/86] Improved on the crashed second run validation --- test/IntegrationTest/Integration.Tests.ps1 | 27 ++++++++++++++++++- .../Scripts/IntegrationTester.cs | 3 +++ 2 files changed, 29 insertions(+), 1 deletion(-) diff --git a/test/IntegrationTest/Integration.Tests.ps1 b/test/IntegrationTest/Integration.Tests.ps1 index 05c1fd8c1..70c427d56 100644 --- a/test/IntegrationTest/Integration.Tests.ps1 +++ b/test/IntegrationTest/Integration.Tests.ps1 @@ -54,7 +54,18 @@ BeforeAll { Write-Host "Running crash-send to ensure crash report is sent..." $sendExtras = @("-e", "test", "crash-send") - Invoke-DeviceApp -ExecutablePath $script:AndroidComponent -Arguments $sendExtras + $sendResult = Invoke-DeviceApp -ExecutablePath $script:AndroidComponent -Arguments $sendExtras + + # Save crash-send result to JSON for debugging + $sendResult | ConvertTo-Json -Depth 5 | Out-File -FilePath (Get-OutputFilePath "crash-send-result.json") + + # Print crash-send output + Write-Host "::group::App output (crash-send)" + $sendResult.Output | ForEach-Object { Write-Host $_ } + Write-Host "::endgroup::" + + # Attach to runResult for test access + $runResult | Add-Member -NotePropertyName "CrashSendOutput" -NotePropertyValue $sendResult.Output } # Print app output so it's visible in CI logs @@ -192,5 +203,19 @@ Describe "Unity Android Integration Tests" { $exception | Should -Not -BeNullOrEmpty $exception.stacktrace | Should -Not -BeNullOrEmpty } + + It "Reports crashedLastRun as Crashed on relaunch" { + $crashedLastRunLine = $runResult.CrashSendOutput | Where-Object { + $_ -match "crashedLastRun=Crashed" + } + $crashedLastRunLine | Should -Not -BeNullOrEmpty -Because "Native SDK should report crashedLastRun=Crashed after a native crash" + } + + It "Crash-send completes flush successfully" { + $flushLine = $runResult.CrashSendOutput | Where-Object { + $_ -match "Flush complete" + } + $flushLine | Should -Not -BeNullOrEmpty -Because "crash-send should complete its flush before quitting" + } } } diff --git a/test/Scripts.Integration.Test/Scripts/IntegrationTester.cs b/test/Scripts.Integration.Test/Scripts/IntegrationTester.cs index f9855343a..94e6423b6 100644 --- a/test/Scripts.Integration.Test/Scripts/IntegrationTester.cs +++ b/test/Scripts.Integration.Test/Scripts/IntegrationTester.cs @@ -127,6 +127,9 @@ private void CrashSend() { Debug.Log("CrashSend: Initializing Sentry to flush cached crash report..."); + var lastRunState = SentrySdk.GetLastRunState(); + Debug.Log($"CrashSend: crashedLastRun={lastRunState}"); + // Sentry is already initialized by IntegrationOptionsConfiguration. // Just wait a bit for the queued crash report to be sent, then quit. StartCoroutine(WaitAndQuit()); From 8be86be3fb121c96f6a684e94f74d9c92eb4dda8 Mon Sep 17 00:00:00 2001 From: bitsandfoxes Date: Thu, 19 Feb 2026 17:05:36 +0100 Subject: [PATCH 26/86] Incremental fix --- .github/workflows/smoke-test-run-android.yml | 4 +--- 1 file changed, 1 insertion(+), 3 deletions(-) diff --git a/.github/workflows/smoke-test-run-android.yml b/.github/workflows/smoke-test-run-android.yml index 663dad69f..a3ed909dd 100644 --- a/.github/workflows/smoke-test-run-android.yml +++ b/.github/workflows/smoke-test-run-android.yml @@ -42,9 +42,6 @@ jobs: 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: @@ -92,6 +89,7 @@ jobs: -camera-back none -camera-front none -timezone US/Pacific + -no-metrics arch: x86_64 script: | adb wait-for-device From d6c50845127b37c697b5e08f12bff02c6654f43b Mon Sep 17 00:00:00 2001 From: bitsandfoxes Date: Thu, 19 Feb 2026 17:07:46 +0100 Subject: [PATCH 27/86] Activity detection instead of try-catch --- test/IntegrationTest/Integration.Tests.ps1 | 27 +++++++++------------- 1 file changed, 11 insertions(+), 16 deletions(-) diff --git a/test/IntegrationTest/Integration.Tests.ps1 b/test/IntegrationTest/Integration.Tests.ps1 index 70c427d56..efdde9bcc 100644 --- a/test/IntegrationTest/Integration.Tests.ps1 +++ b/test/IntegrationTest/Integration.Tests.ps1 @@ -18,8 +18,7 @@ $ErrorActionPreference = "Stop" BeforeAll { - $script:AndroidComponent = "io.sentry.unity.integrationtest/com.unity3d.player.UnityPlayerActivity" - $script:FallbackAndroidComponent = "io.sentry.unity.integrationtest/com.unity3d.player.UnityPlayerGameActivity" + $script:PackageName = "io.sentry.unity.integrationtest" # Run integration test action on device function Invoke-TestAction { @@ -31,20 +30,7 @@ BeforeAll { Write-Host "Running $Action..." $extras = @("-e", "test", $Action) - - 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 - } - } + $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") @@ -107,6 +93,15 @@ BeforeAll { Connect-Device -Platform "Adb" Install-DeviceApp -Path $script:TestSetup.ApkPath + + # Detect the launcher activity from the installed package + $dumpOutput = & adb shell dumpsys package $script:PackageName 2>&1 | Out-String + if ($dumpOutput -match "com.unity3d.player.UnityPlayerGameActivity") { + $script:AndroidComponent = "$($script:PackageName)/com.unity3d.player.UnityPlayerGameActivity" + } else { + $script:AndroidComponent = "$($script:PackageName)/com.unity3d.player.UnityPlayerActivity" + } + Write-Host "Detected activity: $($script:AndroidComponent)" } From bf353f6dde4b691918025398d59232b00e227f95 Mon Sep 17 00:00:00 2001 From: bitsandfoxes Date: Fri, 20 Feb 2026 09:35:10 +0100 Subject: [PATCH 28/86] Get built-time check back in --- .github/workflows/ci.yml | 3 +- .../workflows/smoke-test-build-android.yml | 33 +++++++++---------- 2 files changed, 17 insertions(+), 19 deletions(-) diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml index cc59be993..6ad0a6d57 100644 --- a/.github/workflows/ci.yml +++ b/.github/workflows/ci.yml @@ -273,8 +273,7 @@ jobs: unity-version: ${{ fromJSON(needs.create-unity-matrix.outputs.unity-matrix).unity-version }} # api-level: [30, 31, 34] api-level: [30] - # init-type: ["runtime", "buildtime"] - init-type: ["runtime"] + init-type: ["runtime", "buildtime"] 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 32b7ac7e3..dbc54a963 100644 --- a/.github/workflows/smoke-test-build-android.yml +++ b/.github/workflows/smoke-test-build-android.yml @@ -101,23 +101,22 @@ 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. - # 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: 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: Overwrite IntegrationOptionsConfiguration for build-time initialization + run: | + $optionsPath = "samples/IntegrationTest/Assets/Scripts/IntegrationOptionsConfiguration.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: Upload .apk + uses: actions/upload-artifact@ea165f8d65b6e75b540449e92b4886f43607fa02 # v4 + with: + name: testapp-android-compiled-${{ env.UNITY_VERSION }}-buildtime + path: samples/IntegrationTest/Build/*.apk + retention-days: 14 - name: Upload IntegrationTest project on failure if: ${{ failure() }} From d0e10ea6f3316718c37909b73e9a642ce89a64eb Mon Sep 17 00:00:00 2001 From: bitsandfoxes Date: Fri, 20 Feb 2026 09:57:28 +0100 Subject: [PATCH 29/86] feedback --- .github/workflows/smoke-test-run-android.yml | 2 +- test/IntegrationTest/Integration.Tests.ps1 | 3 +++ 2 files changed, 4 insertions(+), 1 deletion(-) diff --git a/.github/workflows/smoke-test-run-android.yml b/.github/workflows/smoke-test-run-android.yml index a3ed909dd..7abb585b1 100644 --- a/.github/workflows/smoke-test-run-android.yml +++ b/.github/workflows/smoke-test-run-android.yml @@ -95,7 +95,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 -CI' + pwsh -Command '$env:SENTRY_TEST_APK = "samples/IntegrationTest/Build/test.apk"; Invoke-Pester -Path test/IntegrationTest/Integration.Tests.ps1 -CI' - name: Upload test results on failure if: ${{ failure() }} diff --git a/test/IntegrationTest/Integration.Tests.ps1 b/test/IntegrationTest/Integration.Tests.ps1 index efdde9bcc..a07a897bc 100644 --- a/test/IntegrationTest/Integration.Tests.ps1 +++ b/test/IntegrationTest/Integration.Tests.ps1 @@ -115,6 +115,7 @@ Describe "Unity Android Integration Tests" { Context "Message Capture" { BeforeAll { + $script:runEvent = $null $script:runResult = Invoke-TestAction -Action "message-capture" $eventId = Get-EventIds -AppOutput $script:runResult.Output -ExpectedCount 1 @@ -140,6 +141,7 @@ Describe "Unity Android Integration Tests" { Context "Exception Capture" { BeforeAll { + $script:runEvent = $null $script:runResult = Invoke-TestAction -Action "exception-capture" $eventId = Get-EventIds -AppOutput $script:runResult.Output -ExpectedCount 1 @@ -173,6 +175,7 @@ Describe "Unity Android Integration Tests" { Context "Crash Capture" { BeforeAll { + $script:runEvent = $null $script:runResult = Invoke-TestAction -Action "crash-capture" $eventId = Get-EventIds -AppOutput $script:runResult.Output -ExpectedCount 1 From 0cc6ab41e58ad3bc21866da1b9c5dd10c24caa7c Mon Sep 17 00:00:00 2001 From: bitsandfoxes Date: Fri, 20 Feb 2026 13:18:17 +0100 Subject: [PATCH 30/86] skip 'dist' for now --- test/IntegrationTest/CommonTestCases.ps1 | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/test/IntegrationTest/CommonTestCases.ps1 b/test/IntegrationTest/CommonTestCases.ps1 index db1245362..b38fb675e 100644 --- a/test/IntegrationTest/CommonTestCases.ps1 +++ b/test/IntegrationTest/CommonTestCases.ps1 @@ -31,7 +31,7 @@ $CommonTestCases = @( } @{ Name = "Has correct dist attribute"; TestBlock = { param($TestSetup, $TestType, $SentryEvent, $RunResult) - $SentryEvent.dist | Should -Be "test-dist" + Set-ItResult -Skipped -Because "dist is not yet read from AndroidManifest by sentry-java (pending submodule update)" } } @{ Name = "Has tags"; TestBlock = { From 641f5e1b3e94310434944d26fcda0e4da6ebb3f3 Mon Sep 17 00:00:00 2001 From: Stefan Jandl Date: Fri, 20 Feb 2026 14:24:24 +0100 Subject: [PATCH 31/86] Apply suggestion from @bitsandfoxes --- 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 bb2ae779c..c3f395e67 100644 --- a/test/Scripts.Integration.Test/Editor/Builder.cs +++ b/test/Scripts.Integration.Test/Editor/Builder.cs @@ -37,7 +37,7 @@ public static void BuildIl2CPPPlayer(BuildTarget target, BuildTargetGroup group, #endif Debug.Log("Builder: Configuring code stripping level"); -#if UNITY_6000_0_OR_NEWER +#if UNITY_2022_1_OR_NEWER PlayerSettings.SetManagedStrippingLevel(NamedBuildTarget.FromBuildTargetGroup(group), ManagedStrippingLevel.High); #else PlayerSettings.SetManagedStrippingLevel(NamedBuildTarget.FromBuildTargetGroup(group), ManagedStrippingLevel.Low); From 36943c33ddd87e94f2fd95a6af6df9af58c1a883 Mon Sep 17 00:00:00 2001 From: bitsandfoxes Date: Mon, 23 Feb 2026 18:18:13 +0100 Subject: [PATCH 32/86] Migrate iOS CI to use app-runner --- .github/workflows/ci.yml | 3 +- .github/workflows/smoke-test-run-ios.yml | 38 ++- .../IntegrationTest/Integration.Tests.iOS.ps1 | 216 ++++++++++++++++++ 3 files changed, 244 insertions(+), 13 deletions(-) create mode 100644 test/IntegrationTest/Integration.Tests.iOS.ps1 diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml index 6ad0a6d57..595f891c7 100644 --- a/.github/workflows/ci.yml +++ b/.github/workflows/ci.yml @@ -303,7 +303,7 @@ jobs: init-type: ${{ matrix.init-type }} smoke-test-run-ios: - name: Run iOS ${{ matrix.unity-version }} Smoke Test + name: Run iOS ${{ matrix.unity-version }} Integration Test if: ${{ !startsWith(github.ref, 'refs/heads/release/') }} needs: [smoke-test-compile-ios, create-unity-matrix] uses: ./.github/workflows/smoke-test-run-ios.yml @@ -311,6 +311,7 @@ jobs: unity-version: ${{ matrix.unity-version }} ios-version: ${{ matrix.ios-version }} init-type: ${{ matrix.init-type }} + secrets: inherit strategy: fail-fast: false matrix: diff --git a/.github/workflows/smoke-test-run-ios.yml b/.github/workflows/smoke-test-run-ios.yml index 9465e4ded..2729914c5 100644 --- a/.github/workflows/smoke-test-run-ios.yml +++ b/.github/workflows/smoke-test-run-ios.yml @@ -1,4 +1,4 @@ -name: "SmokeTest: Run iOS" +name: "IntegrationTest: Run iOS" on: workflow_call: inputs: @@ -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: @@ -27,15 +27,22 @@ jobs: runs-on: macos-14 # Pinning to get the oldest, supported version of iOS simulator # Map the job outputs to step outputs outputs: - status: ${{ steps.smoke-test.outputs.status }} + status: ${{ steps.integration-test.outputs.status }} env: UNITY_VERSION: ${{ inputs.unity-version }} IOS_VERSION: ${{ inputs.ios-version }} INIT_TYPE: ${{ inputs.init-type }} + ARTIFACTS_PATH: samples/IntegrationTest/test-artifacts/ + SENTRY_TEST_DSN: ${{ secrets.SENTRY_TEST_DSN }} + SENTRY_AUTH_TOKEN: ${{ secrets.SENTRY_AUTH_TOKEN }} steps: - name: Checkout - uses: actions/checkout@f43a0e5ff2bd294095638e18286ca9a3d1956744 # v3 + uses: actions/checkout@11bd71901bbe5b1630ceea73d27597364c9af683 # v4 + + - name: Initialize app-runner submodule + run: git submodule update --init modules/app-runner + shell: bash - name: Download app artifact uses: actions/download-artifact@d3f86a106a0bac45b974a628896c90dbdf5c8093 # v4 @@ -53,13 +60,20 @@ jobs: with: xcode-version: '15.0' # to run iOS 17.0 we need Xcode 15.0 - - name: Run iOS Smoke Tests - id: smoke-test + - name: Run iOS Integration Tests + id: integration-test timeout-minutes: 20 run: | - $runtime = "${env:IOS_VERSION}" - If ($runtime -ne "latest") - { - $runtime = "iOS " + $runtime - } - ./Scripts/smoke-test-ios.ps1 Test "$runtime" -IsIntegrationTest \ No newline at end of file + $env:SENTRY_TEST_APP = "samples/IntegrationTest/Build/IntegrationTest.app" + $env:SENTRY_IOS_VERSION = "${{ inputs.ios-version }}" + Invoke-Pester -Path test/IntegrationTest/Integration.Tests.iOS.ps1 -CI + + - name: Upload test results on failure + if: ${{ failure() }} + uses: actions/upload-artifact@ea165f8d65b6e75b540449e92b4886f43607fa02 # v4 + with: + name: testapp-ios-logs-${{ env.IOS_VERSION }}-${{ env.UNITY_VERSION }} + path: | + ${{ env.ARTIFACTS_PATH }} + test/IntegrationTest/results/ + retention-days: 14 diff --git a/test/IntegrationTest/Integration.Tests.iOS.ps1 b/test/IntegrationTest/Integration.Tests.iOS.ps1 new file mode 100644 index 000000000..d3341503c --- /dev/null +++ b/test/IntegrationTest/Integration.Tests.iOS.ps1 @@ -0,0 +1,216 @@ +#!/usr/bin/env pwsh +# +# Integration tests for Sentry Unity SDK (iOS Simulator) +# +# Environment variables: +# SENTRY_TEST_APP: path to the test .app bundle +# SENTRY_IOS_VERSION: iOS simulator version (e.g. "17.0" or "latest") +# SENTRY_TEST_DSN: test DSN +# SENTRY_AUTH_TOKEN: authentication token for Sentry API + +Set-StrictMode -Version latest +$ErrorActionPreference = "Stop" + +# Import app-runner modules +. $PSScriptRoot/../../modules/app-runner/import-modules.ps1 + +# Import shared test cases and utility functions +. $PSScriptRoot/CommonTestCases.ps1 + + +BeforeAll { + $script:BundleId = "com.DefaultCompany.IntegrationTest" + + # Run integration test action on device + function Invoke-TestAction { + param ( + [Parameter(Mandatory=$true)] + [string]$Action + ) + + Write-Host "Running $Action..." + + $args = @("--test", $Action) + $runResult = Invoke-DeviceApp -ExecutablePath $script:BundleId -Arguments $args + + # 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") { + Write-Host "Running crash-send to ensure crash report is sent..." + + $sendArgs = @("--test", "crash-send") + $sendResult = Invoke-DeviceApp -ExecutablePath $script:BundleId -Arguments $sendArgs + + # Save crash-send result to JSON for debugging + $sendResult | ConvertTo-Json -Depth 5 | Out-File -FilePath (Get-OutputFilePath "crash-send-result.json") + + # Print crash-send output + Write-Host "::group::App output (crash-send)" + $sendResult.Output | ForEach-Object { Write-Host $_ } + Write-Host "::endgroup::" + + # Attach to runResult for test access + $runResult | Add-Member -NotePropertyName "CrashSendOutput" -NotePropertyValue $sendResult.Output + } + + # 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 + } + + # 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]@{ + AppPath = $env:SENTRY_TEST_APP + iOSVersion = $env:SENTRY_IOS_VERSION + Dsn = $env:SENTRY_TEST_DSN + AuthToken = $env:SENTRY_AUTH_TOKEN + } + + # Validate environment + if ([string]::IsNullOrEmpty($script:TestSetup.AppPath)) { + throw "SENTRY_TEST_APP environment variable is not set." + } + if (-not (Test-Path $script:TestSetup.AppPath)) { + throw "App not found at: $($script:TestSetup.AppPath)" + } + if ([string]::IsNullOrEmpty($script:TestSetup.iOSVersion)) { + throw "SENTRY_IOS_VERSION environment variable is not set." + } + 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 + + $target = $script:TestSetup.iOSVersion + Connect-Device -Platform "iOSSimulator" -Target $target + Install-DeviceApp -Path $script:TestSetup.AppPath +} + + +AfterAll { + Disconnect-SentryApi + Disconnect-Device +} + + +Describe "Unity iOS Integration Tests" { + + Context "Message Capture" { + BeforeAll { + $script:runEvent = $null + $script:runResult = Invoke-TestAction -Action "message-capture" + + $eventId = Get-EventIds -AppOutput $script:runResult.Output -ExpectedCount 1 + if ($eventId) { + Write-Host "::group::Getting event content" + $script:runEvent = Get-SentryTestEvent -EventId "$eventId" + Write-Host "::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:runEvent = $null + $script:runResult = Invoke-TestAction -Action "exception-capture" + + $eventId = Get-EventIds -AppOutput $script:runResult.Output -ExpectedCount 1 + if ($eventId) { + Write-Host "::group::Getting event content" + $script:runEvent = Get-SentryTestEvent -EventId "$eventId" + Write-Host "::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:runEvent = $null + $script:runResult = Invoke-TestAction -Action "crash-capture" + + $eventId = Get-EventIds -AppOutput $script:runResult.Output -ExpectedCount 1 + if ($eventId) { + Write-Host "::group::Getting event content" + $script:runEvent = Get-SentryTestEvent -TagName "test.crash_id" -TagValue "$eventId" -TimeoutSeconds 120 + Write-Host "::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 + } + + It "Reports crashedLastRun as Crashed on relaunch" { + $crashedLastRunLine = $runResult.CrashSendOutput | Where-Object { + $_ -match "crashedLastRun=Crashed" + } + $crashedLastRunLine | Should -Not -BeNullOrEmpty -Because "Native SDK should report crashedLastRun=Crashed after a native crash" + } + + It "Crash-send completes flush successfully" { + $flushLine = $runResult.CrashSendOutput | Where-Object { + $_ -match "Flush complete" + } + $flushLine | Should -Not -BeNullOrEmpty -Because "crash-send should complete its flush before quitting" + } + } +} From 8322cbd29e6156309af662625d43b7d4f3bdb48a Mon Sep 17 00:00:00 2001 From: bitsandfoxes Date: Mon, 23 Feb 2026 19:29:13 +0100 Subject: [PATCH 33/86] bump app-runner --- modules/app-runner | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/modules/app-runner b/modules/app-runner index 0f3a63a67..ba47ad0d5 160000 --- a/modules/app-runner +++ b/modules/app-runner @@ -1 +1 @@ -Subproject commit 0f3a63a67aeead62b004e1d1548ffe47dc630fb6 +Subproject commit ba47ad0d5ca36fcfc81e449280eb962679e9df6c From 9cf1ec5710f00600d1e35fe1e3e5efb5d9c27da9 Mon Sep 17 00:00:00 2001 From: bitsandfoxes Date: Tue, 24 Feb 2026 09:15:40 +0100 Subject: [PATCH 34/86] fixes --- .github/workflows/smoke-test-run-ios.yml | 2 +- test/IntegrationTest/Integration.Tests.iOS.ps1 | 4 ++++ 2 files changed, 5 insertions(+), 1 deletion(-) diff --git a/.github/workflows/smoke-test-run-ios.yml b/.github/workflows/smoke-test-run-ios.yml index 2729914c5..6606bf4cb 100644 --- a/.github/workflows/smoke-test-run-ios.yml +++ b/.github/workflows/smoke-test-run-ios.yml @@ -72,7 +72,7 @@ jobs: if: ${{ failure() }} uses: actions/upload-artifact@ea165f8d65b6e75b540449e92b4886f43607fa02 # v4 with: - name: testapp-ios-logs-${{ env.IOS_VERSION }}-${{ env.UNITY_VERSION }} + name: testapp-ios-logs-${{ env.IOS_VERSION }}-${{ env.UNITY_VERSION }}-${{ env.INIT_TYPE }} path: | ${{ env.ARTIFACTS_PATH }} test/IntegrationTest/results/ diff --git a/test/IntegrationTest/Integration.Tests.iOS.ps1 b/test/IntegrationTest/Integration.Tests.iOS.ps1 index d3341503c..ea42fd86f 100644 --- a/test/IntegrationTest/Integration.Tests.iOS.ps1 +++ b/test/IntegrationTest/Integration.Tests.iOS.ps1 @@ -97,6 +97,10 @@ BeforeAll { -DSN $script:TestSetup.Dsn $target = $script:TestSetup.iOSVersion + # Convert bare version numbers (e.g. "17.0") to "iOS 17.0" format expected by iOSSimulatorProvider + if ($target -match '^\d+\.\d+$') { + $target = "iOS $target" + } Connect-Device -Platform "iOSSimulator" -Target $target Install-DeviceApp -Path $script:TestSetup.AppPath } From 7f03d3f7f7d87d3bbf68665a05865727f946ee6e Mon Sep 17 00:00:00 2001 From: bitsandfoxes Date: Tue, 24 Feb 2026 10:39:17 +0100 Subject: [PATCH 35/86] Fix iOS integration test configuration --- .github/workflows/ci.yml | 1 + .github/workflows/smoke-test-build-ios.yml | 7 ++++--- 2 files changed, 5 insertions(+), 3 deletions(-) diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml index 595f891c7..6639c317d 100644 --- a/.github/workflows/ci.yml +++ b/.github/workflows/ci.yml @@ -314,6 +314,7 @@ jobs: secrets: inherit strategy: fail-fast: false + max-parallel: 2 matrix: unity-version: ${{ fromJSON(needs.create-unity-matrix.outputs.unity-matrix).unity-version }} ios-version: ["17.0", "latest"] diff --git a/.github/workflows/smoke-test-build-ios.yml b/.github/workflows/smoke-test-build-ios.yml index dc8563034..4c6209b5d 100644 --- a/.github/workflows/smoke-test-build-ios.yml +++ b/.github/workflows/smoke-test-build-ios.yml @@ -96,9 +96,10 @@ 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 "$env:BUILD_PLATFORM" -CheckSymbols + run: ./test/Scripts.Integration.Test/configure-sentry.ps1 -UnityPath "$env:UNITY_PATH" -Platform "$env:BUILD_PLATFORM" -CheckSymbols -TestMode "integration" env: BUILD_PLATFORM: ${{ matrix.build_platform }} + SENTRY_DSN: ${{ secrets.SENTRY_TEST_DSN }} - name: Build Project with runtime initialization run: ./test/Scripts.Integration.Test/build-project.ps1 -UnityPath "$env:UNITY_PATH" -Platform "$env:BUILD_PLATFORM" -CheckSymbols:$false -UnityVersion "$env:UNITY_VERSION" @@ -122,9 +123,9 @@ jobs: path: test-app-runtime.tar.gz retention-days: 14 - - name: Overwrite OptionsConfiguration for build-time initialization + - name: Overwrite IntegrationOptionsConfiguration for build-time initialization run: | - $optionsPath = "samples/IntegrationTest/Assets/Scripts/OptionsConfiguration.cs" + $optionsPath = "samples/IntegrationTest/Assets/Scripts/IntegrationOptionsConfiguration.cs" $content = Get-Content $optionsPath -Raw $content = $content -replace 'IosNativeInitializationType = NativeInitializationType.Runtime', 'IosNativeInitializationType = NativeInitializationType.BuildTime' Set-Content $optionsPath $content From 5915218055961cbf38de5b65bab30b153d6bc1b9 Mon Sep 17 00:00:00 2001 From: bitsandfoxes Date: Tue, 24 Feb 2026 13:41:06 +0100 Subject: [PATCH 36/86] Timeout and retry --- modules/app-runner | 2 +- test/IntegrationTest/Integration.Tests.iOS.ps1 | 7 ++++++- 2 files changed, 7 insertions(+), 2 deletions(-) diff --git a/modules/app-runner b/modules/app-runner index ba47ad0d5..2c26ed94c 160000 --- a/modules/app-runner +++ b/modules/app-runner @@ -1 +1 @@ -Subproject commit ba47ad0d5ca36fcfc81e449280eb962679e9df6c +Subproject commit 2c26ed94cf6d5d8e4e79937a56cba0123a1aac2c diff --git a/test/IntegrationTest/Integration.Tests.iOS.ps1 b/test/IntegrationTest/Integration.Tests.iOS.ps1 index ea42fd86f..b95c4c454 100644 --- a/test/IntegrationTest/Integration.Tests.iOS.ps1 +++ b/test/IntegrationTest/Integration.Tests.iOS.ps1 @@ -31,7 +31,12 @@ BeforeAll { Write-Host "Running $Action..." $args = @("--test", $Action) - $runResult = Invoke-DeviceApp -ExecutablePath $script:BundleId -Arguments $args + + # Use a shorter timeout for crash tests - the app should crash within seconds. + # On iOS Simulator, xcrun's --console-pty can hang after a crash, so we don't + # want to wait the full default timeout (300s). + $timeout = if ($Action -eq "crash-capture") { 30 } else { 0 } + $runResult = Invoke-DeviceApp -ExecutablePath $script:BundleId -Arguments $args -Timeout $timeout # Save result to JSON file $runResult | ConvertTo-Json -Depth 5 | Out-File -FilePath (Get-OutputFilePath "${Action}-result.json") From 0965ded2924d9dbbd130c249961f08374a68a88f Mon Sep 17 00:00:00 2001 From: bitsandfoxes Date: Wed, 25 Feb 2026 11:36:48 +0100 Subject: [PATCH 37/86] machine feedback --- .github/workflows/smoke-test-run-ios.yml | 1 + test/IntegrationTest/Integration.Tests.iOS.ps1 | 4 ++-- 2 files changed, 3 insertions(+), 2 deletions(-) diff --git a/.github/workflows/smoke-test-run-ios.yml b/.github/workflows/smoke-test-run-ios.yml index 6606bf4cb..721c4a3ec 100644 --- a/.github/workflows/smoke-test-run-ios.yml +++ b/.github/workflows/smoke-test-run-ios.yml @@ -67,6 +67,7 @@ jobs: $env:SENTRY_TEST_APP = "samples/IntegrationTest/Build/IntegrationTest.app" $env:SENTRY_IOS_VERSION = "${{ inputs.ios-version }}" Invoke-Pester -Path test/IntegrationTest/Integration.Tests.iOS.ps1 -CI + echo "status=success" >> $env:GITHUB_OUTPUT - name: Upload test results on failure if: ${{ failure() }} diff --git a/test/IntegrationTest/Integration.Tests.iOS.ps1 b/test/IntegrationTest/Integration.Tests.iOS.ps1 index b95c4c454..8b6f96483 100644 --- a/test/IntegrationTest/Integration.Tests.iOS.ps1 +++ b/test/IntegrationTest/Integration.Tests.iOS.ps1 @@ -30,13 +30,13 @@ BeforeAll { Write-Host "Running $Action..." - $args = @("--test", $Action) + $appArgs = @("--test", $Action) # Use a shorter timeout for crash tests - the app should crash within seconds. # On iOS Simulator, xcrun's --console-pty can hang after a crash, so we don't # want to wait the full default timeout (300s). $timeout = if ($Action -eq "crash-capture") { 30 } else { 0 } - $runResult = Invoke-DeviceApp -ExecutablePath $script:BundleId -Arguments $args -Timeout $timeout + $runResult = Invoke-DeviceApp -ExecutablePath $script:BundleId -Arguments $appArgs -Timeout $timeout # Save result to JSON file $runResult | ConvertTo-Json -Depth 5 | Out-File -FilePath (Get-OutputFilePath "${Action}-result.json") From 5f1848333b09949743c0c8293d33fd183dacb8a7 Mon Sep 17 00:00:00 2001 From: bitsandfoxes Date: Wed, 25 Feb 2026 13:51:23 +0100 Subject: [PATCH 38/86] timeout tweaks --- modules/app-runner | 2 +- test/IntegrationTest/Integration.Tests.iOS.ps1 | 6 +----- 2 files changed, 2 insertions(+), 6 deletions(-) diff --git a/modules/app-runner b/modules/app-runner index 2c26ed94c..bdcf2b316 160000 --- a/modules/app-runner +++ b/modules/app-runner @@ -1 +1 @@ -Subproject commit 2c26ed94cf6d5d8e4e79937a56cba0123a1aac2c +Subproject commit bdcf2b3161e644de874961e9aeb4defea861773b diff --git a/test/IntegrationTest/Integration.Tests.iOS.ps1 b/test/IntegrationTest/Integration.Tests.iOS.ps1 index 8b6f96483..04946b32d 100644 --- a/test/IntegrationTest/Integration.Tests.iOS.ps1 +++ b/test/IntegrationTest/Integration.Tests.iOS.ps1 @@ -32,11 +32,7 @@ BeforeAll { $appArgs = @("--test", $Action) - # Use a shorter timeout for crash tests - the app should crash within seconds. - # On iOS Simulator, xcrun's --console-pty can hang after a crash, so we don't - # want to wait the full default timeout (300s). - $timeout = if ($Action -eq "crash-capture") { 30 } else { 0 } - $runResult = Invoke-DeviceApp -ExecutablePath $script:BundleId -Arguments $appArgs -Timeout $timeout + $runResult = Invoke-DeviceApp -ExecutablePath $script:BundleId -Arguments $appArgs # Save result to JSON file $runResult | ConvertTo-Json -Depth 5 | Out-File -FilePath (Get-OutputFilePath "${Action}-result.json") From 5af6bdd036417d24ecd4df5cf63d10e45192ada5 Mon Sep 17 00:00:00 2001 From: bitsandfoxes Date: Wed, 25 Feb 2026 13:59:47 +0100 Subject: [PATCH 39/86] . --- modules/app-runner | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/modules/app-runner b/modules/app-runner index bdcf2b316..210bbebe2 160000 --- a/modules/app-runner +++ b/modules/app-runner @@ -1 +1 @@ -Subproject commit bdcf2b3161e644de874961e9aeb4defea861773b +Subproject commit 210bbebe2563bbec1e41b52d9221a3cea20a65c7 From 3d035ca79cade423d1c7f64abf9b32e5a3cf47d8 Mon Sep 17 00:00:00 2001 From: bitsandfoxes Date: Thu, 26 Feb 2026 13:58:45 +0100 Subject: [PATCH 40/86] bumped app-runner --- modules/app-runner | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/modules/app-runner b/modules/app-runner index 0f3a63a67..579c35760 160000 --- a/modules/app-runner +++ b/modules/app-runner @@ -1 +1 @@ -Subproject commit 0f3a63a67aeead62b004e1d1548ffe47dc630fb6 +Subproject commit 579c35760da1305913fbe5ca783f8e16021a022e From 5a96ca946642fb7c493fc7e6bca1d709ed611664 Mon Sep 17 00:00:00 2001 From: bitsandfoxes Date: Thu, 26 Feb 2026 14:09:20 +0100 Subject: [PATCH 41/86] Scope app context crash-capture skip to iOS only --- test/IntegrationTest/CommonTestCases.ps1 | 4 ++-- test/IntegrationTest/Integration.Tests.iOS.ps1 | 1 + test/IntegrationTest/Integration.Tests.ps1 | 1 + 3 files changed, 4 insertions(+), 2 deletions(-) diff --git a/test/IntegrationTest/CommonTestCases.ps1 b/test/IntegrationTest/CommonTestCases.ps1 index b38fb675e..4f3aab1ea 100644 --- a/test/IntegrationTest/CommonTestCases.ps1 +++ b/test/IntegrationTest/CommonTestCases.ps1 @@ -84,8 +84,8 @@ $CommonTestCases = @( @{ Name = "Contains app context"; TestBlock = { param($TestSetup, $TestType, $SentryEvent, $RunResult) - if ($TestType -eq "crash-capture") { - Set-ItResult -Skipped -Because "app context is not synced to sentry-native on Android" + if ($TestType -eq "crash-capture" -and $TestSetup.Platform -eq "iOS") { + Set-ItResult -Skipped -Because "app context is not synced to sentry-cocoa on iOS" return } diff --git a/test/IntegrationTest/Integration.Tests.iOS.ps1 b/test/IntegrationTest/Integration.Tests.iOS.ps1 index 04946b32d..b1d52a451 100644 --- a/test/IntegrationTest/Integration.Tests.iOS.ps1 +++ b/test/IntegrationTest/Integration.Tests.iOS.ps1 @@ -70,6 +70,7 @@ BeforeAll { # Initialize test parameters $script:TestSetup = [PSCustomObject]@{ + Platform = "iOS" AppPath = $env:SENTRY_TEST_APP iOSVersion = $env:SENTRY_IOS_VERSION Dsn = $env:SENTRY_TEST_DSN diff --git a/test/IntegrationTest/Integration.Tests.ps1 b/test/IntegrationTest/Integration.Tests.ps1 index a07a897bc..222863e09 100644 --- a/test/IntegrationTest/Integration.Tests.ps1 +++ b/test/IntegrationTest/Integration.Tests.ps1 @@ -68,6 +68,7 @@ BeforeAll { # Initialize test parameters $script:TestSetup = [PSCustomObject]@{ + Platform = "Android" ApkPath = $env:SENTRY_TEST_APK Dsn = $env:SENTRY_TEST_DSN AuthToken = $env:SENTRY_AUTH_TOKEN From 8803de832fb2cb12bffbc1c9e50819be9ebd10f6 Mon Sep 17 00:00:00 2001 From: bitsandfoxes Date: Tue, 3 Mar 2026 11:29:44 +0100 Subject: [PATCH 42/86] bumped app-runner --- modules/app-runner | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/modules/app-runner b/modules/app-runner index 579c35760..63d8c36b3 160000 --- a/modules/app-runner +++ b/modules/app-runner @@ -1 +1 @@ -Subproject commit 579c35760da1305913fbe5ca783f8e16021a022e +Subproject commit 63d8c36b34580b6b2bdac3311594d07e937773b8 From b24c02ace151bed6af0a250e2e0809d50404b86f Mon Sep 17 00:00:00 2001 From: bitsandfoxes Date: Tue, 3 Mar 2026 14:17:48 +0100 Subject: [PATCH 43/86] desktop building and running --- .github/workflows/ci.yml | 145 +++--------- .../workflows/smoke-test-build-desktop.yml | 142 ++++++++++++ .github/workflows/smoke-test-run-desktop.yml | 71 ++++++ scripts/ci-docker-windows.ps1 | 46 ++++ .../Integration.Tests.Desktop.ps1 | 212 ++++++++++++++++++ 5 files changed, 498 insertions(+), 118 deletions(-) create mode 100644 .github/workflows/smoke-test-build-desktop.yml create mode 100644 .github/workflows/smoke-test-run-desktop.yml create mode 100644 scripts/ci-docker-windows.ps1 create mode 100644 test/IntegrationTest/Integration.Tests.Desktop.ps1 diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml index 6639c317d..292514d4d 100644 --- a/.github/workflows/ci.yml +++ b/.github/workflows/ci.yml @@ -98,7 +98,7 @@ jobs: with: unity-version: ${{ matrix.unity-version }} - # A Linux, docker-based build to prepare a game ("player") for some platforms. The tests run in `smoke-test-run`. + # A Linux, docker-based build to prepare a WebGL player. The tests run in `smoke-test-run`. smoke-test-build: name: Build ${{ matrix.platform }} ${{ matrix.unity-version }} Smoke Test if: ${{ !startsWith(github.ref, 'refs/heads/release/') }} @@ -108,15 +108,11 @@ jobs: fail-fast: false matrix: unity-version: ${{ fromJSON(needs.create-unity-matrix.outputs.unity-matrix).unity-version }} - platform: ["WebGL", "Linux"] + platform: ["WebGL"] include: - platform: WebGL check_symbols: true build_platform: WebGL - - platform: Linux - image-suffix: "-il2cpp" - check_symbols: true - build_platform: Linux env: UNITY_PATH: docker exec unity unity-editor steps: @@ -338,7 +334,7 @@ jobs: fail-fast: false matrix: unity-version: ${{ fromJSON(needs.create-unity-matrix.outputs.unity-matrix).unity-version }} - platform: ["WebGL", "Linux"] + platform: ["WebGL"] steps: - name: Checkout uses: actions/checkout@v3 @@ -353,133 +349,46 @@ jobs: run: tar -xvzf test-app-runtime.tar.gz - name: Run (WebGL) - if: ${{ matrix.platform == 'WebGL' }} timeout-minutes: 10 run: | pip3 install --upgrade --user selenium urllib3 requests python3 scripts/smoke-test-webgl.py "samples/IntegrationTest/Build" - - name: Run Smoke Test (Linux) - if: ${{ matrix.platform == 'Linux' }} - run: ./test/Scripts.Integration.Test/run-smoke-test.ps1 -Smoke - - - name: Run Crash Test (Linux) - if: ${{ matrix.platform == 'Linux' }} - run: ./test/Scripts.Integration.Test/run-smoke-test.ps1 -Crash - - desktop-smoke-test: - name: Run ${{ matrix.os }} ${{ matrix.unity-version }} Smoke Test + smoke-test-build-desktop: + name: Build ${{ matrix.os }} ${{ matrix.unity-version }} Integration Test if: ${{ !startsWith(github.ref, 'refs/heads/release/') }} - needs: [smoke-test-create, create-unity-matrix, build-unity-sdk] - runs-on: ${{ matrix.os }}-latest + needs: [smoke-test-create, create-unity-matrix] + secrets: inherit strategy: fail-fast: false matrix: unity-version: ${{ fromJSON(needs.create-unity-matrix.outputs.unity-matrix).unity-version }} - os: ["windows"] - include: - - os: windows - unity-modules: "windows-il2cpp" - unity-config-path: "C:/ProgramData/Unity/config/" - # os: ["windows", "macos"] - # include: - # - os: macos - # unity-modules: mac-il2cpp - # unity-config-path: /Library/Application Support/Unity/config/ - steps: - - name: Checkout - uses: actions/checkout@f43a0e5ff2bd294095638e18286ca9a3d1956744 # v3 - - - name: Load env - id: env - run: echo "unityVersion=$(./scripts/ci-env.ps1 "$env:UNITY_SCRIPT_ARG")" >> $env:GITHUB_OUTPUT - env: - UNITY_SCRIPT_ARG: unity${{ matrix.unity-version }} - - - name: Setup Unity - uses: getsentry/setup-unity@3bdc8c022b6d30ecf2d21d12a564bfa55a54fa2e - with: - unity-version: ${{ steps.env.outputs.unityVersion }} - unity-modules: ${{ matrix.unity-modules }} - - - name: Create Unity license config - run: | - New-Item -Path "$env:UNITY_CONFIG_PATH" -ItemType Directory - Set-Content -Path "$env:UNITY_CONFIG_PATH/services-config.json" -Value "$env:UNITY_LICENSE_SERVER_CONFIG" - env: - UNITY_CONFIG_PATH: ${{ matrix.unity-config-path }} - UNITY_LICENSE_SERVER_CONFIG: ${{ secrets.UNITY_LICENSE_SERVER_CONFIG }} - - - name: Download IntegrationTest project - uses: actions/download-artifact@d3f86a106a0bac45b974a628896c90dbdf5c8093 # v4 - with: - name: smoke-test-${{ matrix.unity-version }} - - - name: Extract project archive - run: tar -xvzf test-project.tar.gz - - - name: Cache Unity Library - uses: actions/cache@d4323d4df104b026a6aa633fdb11d772146be0bf # v4.2.2 - with: - path: samples/IntegrationTest/Library - key: Library-IntegrationTest-${{ matrix.os }}-${{ matrix.unity-version }}-v1 - restore-keys: | - Library-IntegrationTest-${{ matrix.os }}-${{ matrix.unity-version }}- - Library-IntegrationTest-${{ matrix.os }}- - - - name: Restore cached build without Sentry - id: cache-build-nosentry - uses: actions/cache@v4 - with: - path: samples/IntegrationTest/Build-NoSentry - key: build-nosentry-Windows-${{ matrix.unity-version }} - - - name: Build without Sentry SDK - if: steps.cache-build-nosentry.outputs.cache-hit != 'true' - run: ./test/Scripts.Integration.Test/build-project.ps1 -UnityPath "$env:UNITY_PATH" -CheckSymbols:$false -BuildDirName "Build-NoSentry" - - - name: Download UPM package - uses: actions/download-artifact@d3f86a106a0bac45b974a628896c90dbdf5c8093 # v4 - with: - name: package-release - - - name: Extract UPM package - run: ./test/Scripts.Integration.Test/extract-package.ps1 - - - name: Add Sentry to the project - 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" -CheckSymbols - - - name: Build with Sentry SDK - run: ./test/Scripts.Integration.Test/build-project.ps1 -UnityPath "$env:UNITY_PATH" -CheckSymbols -UnityVersion "$env:UNITY_VERSION" - env: - UNITY_VERSION: ${{ matrix.unity-version }} - - - name: Compare build sizes - run: ./test/Scripts.Integration.Test/measure-build-size.ps1 -Path1 "samples/IntegrationTest/Build-NoSentry" -Path2 "samples/IntegrationTest/Build" -Platform "Windows" -UnityVersion "$env:UNITY_VERSION" - env: - UNITY_VERSION: ${{ matrix.unity-version }} - - - name: Upload build size measurement - uses: actions/upload-artifact@ea165f8d65b6e75b540449e92b4886f43607fa02 # v4 - with: - name: build-size-Windows-${{ matrix.unity-version }} - path: build-size-measurements/*.json - retention-days: 1 - - - name: Run Smoke Test - run: ./test/Scripts.Integration.Test/run-smoke-test.ps1 -Smoke + os: ["windows", "linux"] + uses: ./.github/workflows/smoke-test-build-desktop.yml + with: + unity-version: ${{ matrix.unity-version }} + os: ${{ matrix.os }} - - name: Run Crash Test - run: ./test/Scripts.Integration.Test/run-smoke-test.ps1 -Crash + smoke-test-run-desktop: + name: Run ${{ matrix.os }} ${{ matrix.unity-version }} Integration Test + if: ${{ !startsWith(github.ref, 'refs/heads/release/') }} + needs: [smoke-test-build-desktop, create-unity-matrix] + secrets: inherit + strategy: + fail-fast: false + matrix: + unity-version: ${{ fromJSON(needs.create-unity-matrix.outputs.unity-matrix).unity-version }} + os: ["windows", "linux"] + uses: ./.github/workflows/smoke-test-run-desktop.yml + with: + unity-version: ${{ matrix.unity-version }} + os: ${{ matrix.os }} build-size-summary: name: Build Size runs-on: ubuntu-latest if: ${{ !startsWith(github.ref, 'refs/heads/release/') }} - needs: [smoke-test-build, smoke-test-build-android, smoke-test-compile-ios, desktop-smoke-test] + needs: [smoke-test-build, smoke-test-build-android, smoke-test-compile-ios, smoke-test-build-desktop] steps: - name: Checkout uses: actions/checkout@v3 diff --git a/.github/workflows/smoke-test-build-desktop.yml b/.github/workflows/smoke-test-build-desktop.yml new file mode 100644 index 000000000..61bb4777f --- /dev/null +++ b/.github/workflows/smoke-test-build-desktop.yml @@ -0,0 +1,142 @@ +name: "IntegrationTest: Build Desktop" +on: + workflow_call: + inputs: + unity-version: + required: true + type: string + os: + required: true + type: string + description: '"windows" or "linux"' + +defaults: + run: + shell: pwsh + +jobs: + build: + name: ${{ inputs.os }} ${{ inputs.unity-version }} + runs-on: ${{ inputs.os == 'windows' && 'windows-latest' || 'ubuntu-latest' }} + env: + GITHUB_ACTOR: ${{ github.actor }} + UNITY_PATH: docker exec unity unity-editor + UNITY_VERSION: ${{ inputs.unity-version }} + BUILD_PLATFORM: ${{ inputs.os == 'windows' && 'Windows' || 'Linux' }} + + steps: + - name: Checkout + uses: actions/checkout@f43a0e5ff2bd294095638e18286ca9a3d1956744 # v3 + + - name: Free Disk Space (Ubuntu) + if: ${{ inputs.os == 'linux' }} + uses: jlumbroso/free-disk-space@f68fdb76e2ea636224182cfb7377ff9a1708f9b8 # v1.3.0 + with: + android: true + dotnet: false + haskell: true + large-packages: false + docker-images: false + swap-storage: true + + - name: Log into Docker + uses: docker/login-action@9780b0c442fbb1117ed29e0efdff1e18412f7567 # pinned v3 + with: + registry: ghcr.io + username: ${{ env.GITHUB_ACTOR }} + password: ${{ secrets.GITHUB_TOKEN }} + + - name: Start the Unity docker container (Linux) + if: ${{ inputs.os == 'linux' }} + run: ./scripts/ci-docker.sh "$UNITY_VERSION" "Linux-il2cpp" "$UNITY_LICENSE_SERVER_CONFIG" + shell: bash + env: + UNITY_LICENSE_SERVER_CONFIG: ${{ secrets.UNITY_LICENSE_SERVER_CONFIG }} + + - name: Start the Unity docker container (Windows) + if: ${{ inputs.os == 'windows' }} + run: ./scripts/ci-docker-windows.ps1 -UnityPrefix "$env:UNITY_VERSION" -ImageVariant "windows-il2cpp" -LicenseConfig "$env:UNITY_LICENSE_SERVER_CONFIG" + env: + UNITY_LICENSE_SERVER_CONFIG: ${{ secrets.UNITY_LICENSE_SERVER_CONFIG }} + + - name: Download IntegrationTest project + uses: actions/download-artifact@d3f86a106a0bac45b974a628896c90dbdf5c8093 # v4 + with: + name: smoke-test-${{ env.UNITY_VERSION }} + + - name: Extract project archive + run: tar -xvzf test-project.tar.gz + + - name: Cache Unity Library + uses: actions/cache@d4323d4df104b026a6aa633fdb11d772146be0bf # v4.2.2 + with: + path: samples/IntegrationTest/Library + key: Library-IntegrationTest-${{ inputs.os }}-${{ env.UNITY_VERSION }}-v1 + restore-keys: | + Library-IntegrationTest-${{ inputs.os }}-${{ env.UNITY_VERSION }}- + Library-IntegrationTest-${{ inputs.os }}- + + - name: Restore cached build without Sentry + id: cache-build-nosentry + uses: actions/cache@v4 + with: + path: samples/IntegrationTest/Build-NoSentry + key: build-nosentry-${{ env.BUILD_PLATFORM }}-${{ inputs.unity-version }} + + - name: Build without Sentry SDK + if: steps.cache-build-nosentry.outputs.cache-hit != 'true' + run: ./test/Scripts.Integration.Test/build-project.ps1 -UnityPath "$env:UNITY_PATH" -Platform "$env:BUILD_PLATFORM" -CheckSymbols:$false -BuildDirName "Build-NoSentry" + + - name: Download UPM package + uses: vaind/download-artifact@e7141b6a94ef28aa3d828b52830cfa1f406a1848 # v4-with-wait-timeout + with: + name: package-release + wait-timeout: 3600 + + - name: Extract UPM package + run: ./test/Scripts.Integration.Test/extract-package.ps1 + + - name: Add Sentry to the project + 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 "$env:BUILD_PLATFORM" -CheckSymbols -TestMode "integration" + env: + SENTRY_DSN: ${{ secrets.SENTRY_TEST_DSN }} + + - name: Build with Sentry SDK + run: ./test/Scripts.Integration.Test/build-project.ps1 -UnityPath "$env:UNITY_PATH" -Platform "$env:BUILD_PLATFORM" -CheckSymbols:$true -UnityVersion "$env:UNITY_VERSION" + + - name: Compare build sizes + run: ./test/Scripts.Integration.Test/measure-build-size.ps1 -Path1 "samples/IntegrationTest/Build-NoSentry" -Path2 "samples/IntegrationTest/Build" -Platform "$env:BUILD_PLATFORM" -UnityVersion "$env:UNITY_VERSION" + + - name: Upload build size measurement + uses: actions/upload-artifact@ea165f8d65b6e75b540449e92b4886f43607fa02 # v4 + with: + name: build-size-${{ env.BUILD_PLATFORM }}-${{ env.UNITY_VERSION }} + path: build-size-measurements/*.json + retention-days: 1 + + # We create tar explicitly because upload-artifact is slow for many files. + - name: Create archive + run: | + Remove-Item -Recurse -Force samples/IntegrationTest/Build/*_BackUpThisFolder_ButDontShipItWithYourGame -ErrorAction SilentlyContinue + tar -cvzf test-app-desktop.tar.gz samples/IntegrationTest/Build + + - name: Upload test app + uses: actions/upload-artifact@ea165f8d65b6e75b540449e92b4886f43607fa02 # v4 + with: + name: testapp-desktop-compiled-${{ env.UNITY_VERSION }}-${{ inputs.os }} + if-no-files-found: error + path: test-app-desktop.tar.gz + retention-days: 14 + + - name: Upload IntegrationTest project on failure + if: ${{ failure() }} + uses: actions/upload-artifact@ea165f8d65b6e75b540449e92b4886f43607fa02 # v4 + with: + name: failed-project-desktop-${{ inputs.os }}-${{ env.UNITY_VERSION }} + path: | + samples/IntegrationTest + !samples/IntegrationTest/Build/*_BackUpThisFolder_ButDontShipItWithYourGame + retention-days: 14 diff --git a/.github/workflows/smoke-test-run-desktop.yml b/.github/workflows/smoke-test-run-desktop.yml new file mode 100644 index 000000000..37c8866a9 --- /dev/null +++ b/.github/workflows/smoke-test-run-desktop.yml @@ -0,0 +1,71 @@ +name: "IntegrationTest: Run Desktop" +on: + workflow_call: + inputs: + unity-version: + required: true + type: string + os: + required: true + type: string + description: '"windows" or "linux"' + +defaults: + run: + shell: pwsh + +jobs: + run: + name: ${{ inputs.os }} ${{ inputs.unity-version }} + runs-on: ${{ inputs.os == 'windows' && 'windows-latest' || 'ubuntu-latest' }} + env: + SENTRY_TEST_DSN: ${{ secrets.SENTRY_TEST_DSN }} + SENTRY_AUTH_TOKEN: ${{ secrets.SENTRY_AUTH_TOKEN }} + + steps: + - name: Checkout + uses: actions/checkout@11bd71901bbe5b1630ceea73d27597364c9af683 # v4 + + - name: Initialize app-runner submodule + run: git submodule update --init modules/app-runner + shell: bash + + - name: Download test app artifact + uses: actions/download-artifact@d3f86a106a0bac45b974a628896c90dbdf5c8093 # v4 + with: + name: testapp-desktop-compiled-${{ inputs.unity-version }}-${{ inputs.os }} + + - name: Extract test app + run: tar -xvzf test-app-desktop.tar.gz + + - name: Set executable permission (Linux) + if: ${{ inputs.os == 'linux' }} + run: chmod +x samples/IntegrationTest/Build/test + shell: bash + + - name: Run Integration Tests (Windows) + if: ${{ inputs.os == 'windows' }} + timeout-minutes: 20 + run: | + $env:SENTRY_TEST_APP = "samples/IntegrationTest/Build/test.exe" + Invoke-Pester -Path test/IntegrationTest/Integration.Tests.Desktop.ps1 -CI + + - name: Run Integration Tests (Linux) + if: ${{ inputs.os == 'linux' }} + timeout-minutes: 20 + run: | + xvfb-run pwsh -Command ' + $env:SENTRY_TEST_APP = "samples/IntegrationTest/Build/test"; + $env:SENTRY_TEST_DSN = "${{ secrets.SENTRY_TEST_DSN }}"; + $env:SENTRY_AUTH_TOKEN = "${{ secrets.SENTRY_AUTH_TOKEN }}"; + Invoke-Pester -Path test/IntegrationTest/Integration.Tests.Desktop.ps1 -CI' + shell: bash + + - name: Upload test results on failure + if: ${{ failure() }} + uses: actions/upload-artifact@ea165f8d65b6e75b540449e92b4886f43607fa02 # v4 + with: + name: testapp-desktop-logs-${{ inputs.os }}-${{ inputs.unity-version }} + path: | + test/IntegrationTest/results/ + retention-days: 14 diff --git a/scripts/ci-docker-windows.ps1 b/scripts/ci-docker-windows.ps1 new file mode 100644 index 000000000..504a60b7d --- /dev/null +++ b/scripts/ci-docker-windows.ps1 @@ -0,0 +1,46 @@ +param( + [Parameter(Mandatory=$true)] + [string]$UnityPrefix, + [Parameter(Mandatory=$true)] + [string]$ImageVariant, + [Parameter(Mandatory=$true)] + [string]$LicenseConfig +) + +$ErrorActionPreference = "Stop" + +$unityVersion = & pwsh ./scripts/ci-env.ps1 "unity$UnityPrefix" +$imageVariant = $ImageVariant.ToLower() + +$container = "unity" +$image = "unityci/editor:windows-$unityVersion-$imageVariant-3" +$cwd = if ($env:GITHUB_WORKSPACE) { $env:GITHUB_WORKSPACE } else { (Get-Location).Path } + +# Check for existing container +$existing = docker ps -a --filter "name=^/$container$" --format '{{.Names}}' +if ($existing -eq $container) { + Write-Host "Removing existing container '$container'" + docker stop $container + docker rm $container +} + +Write-Host "Starting up '$image' as '$container'" + +# Format: -- +$jobName = if ($env:GITHUB_JOB) { $env:GITHUB_JOB } else { "local" } +$runId = if ($env:GITHUB_RUN_ID) { $env:GITHUB_RUN_ID } else { "0" } +$uniqueHostname = "$jobName-$imageVariant-$runId" -replace '[_\s]', '-' + +docker run -td --name $container ` + --hostname $uniqueHostname ` + -v "${cwd}:C:\sentry-unity" ` + -e UNITY_VERSION=$unityVersion ` + -e GITHUB_ACTIONS=$env:GITHUB_ACTIONS ` + --workdir "C:\sentry-unity" $image + +# Set up Unity license configuration +docker exec $container powershell -Command "New-Item -ItemType Directory -Path 'C:\ProgramData\Unity\config' -Force | Out-Null" +docker exec $container powershell -Command "Set-Content -Path 'C:\ProgramData\Unity\config\services-config.json' -Value '$LicenseConfig'" + +Write-Host "Container started successfully:" +docker ps --filter "name=^/$container$" diff --git a/test/IntegrationTest/Integration.Tests.Desktop.ps1 b/test/IntegrationTest/Integration.Tests.Desktop.ps1 new file mode 100644 index 000000000..15fdfa3c1 --- /dev/null +++ b/test/IntegrationTest/Integration.Tests.Desktop.ps1 @@ -0,0 +1,212 @@ +#!/usr/bin/env pwsh +# +# Integration tests for Sentry Unity SDK (Desktop: Windows, Linux) +# +# Environment variables: +# SENTRY_TEST_APP: path to the test executable +# SENTRY_TEST_DSN: test DSN +# SENTRY_AUTH_TOKEN: authentication token for Sentry API + +Set-StrictMode -Version latest +$ErrorActionPreference = "Stop" + +# Import app-runner modules +. $PSScriptRoot/../../modules/app-runner/import-modules.ps1 + +# Import shared test cases and utility functions +. $PSScriptRoot/CommonTestCases.ps1 + + +BeforeAll { + # Auto-detect platform + $script:PlatformName = if ($IsWindows) { "Windows" } elseif ($IsLinux) { "Linux" } else { throw "Unsupported desktop platform" } + + # Run integration test action on device + function Invoke-TestAction { + param ( + [Parameter(Mandatory=$true)] + [string]$Action + ) + + Write-Host "Running $Action..." + + $appArgs = @("--test", $Action) + + $runResult = Invoke-DeviceApp -ExecutablePath $env:SENTRY_TEST_APP -Arguments $appArgs + + # 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") { + Write-Host "Running crash-send to ensure crash report is sent..." + + $sendArgs = @("--test", "crash-send") + $sendResult = Invoke-DeviceApp -ExecutablePath $env:SENTRY_TEST_APP -Arguments $sendArgs + + # Save crash-send result to JSON for debugging + $sendResult | ConvertTo-Json -Depth 5 | Out-File -FilePath (Get-OutputFilePath "crash-send-result.json") + + # Print crash-send output + Write-Host "::group::App output (crash-send)" + $sendResult.Output | ForEach-Object { Write-Host $_ } + Write-Host "::endgroup::" + + # Attach to runResult for test access + $runResult | Add-Member -NotePropertyName "CrashSendOutput" -NotePropertyValue $sendResult.Output + } + + # 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 + } + + # 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]@{ + Platform = $script:PlatformName + AppPath = $env:SENTRY_TEST_APP + Dsn = $env:SENTRY_TEST_DSN + AuthToken = $env:SENTRY_AUTH_TOKEN + } + + # Validate environment + if ([string]::IsNullOrEmpty($script:TestSetup.AppPath)) { + throw "SENTRY_TEST_APP environment variable is not set." + } + if (-not (Test-Path $script:TestSetup.AppPath)) { + throw "App not found at: $($script:TestSetup.AppPath)" + } + 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 "Local" +} + + +AfterAll { + Disconnect-SentryApi + Disconnect-Device +} + + +Describe "Unity $($script:PlatformName) Integration Tests" { + + Context "Message Capture" { + BeforeAll { + $script:runEvent = $null + $script:runResult = Invoke-TestAction -Action "message-capture" + + $eventId = Get-EventIds -AppOutput $script:runResult.Output -ExpectedCount 1 + if ($eventId) { + Write-Host "::group::Getting event content" + $script:runEvent = Get-SentryTestEvent -EventId "$eventId" + Write-Host "::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:runEvent = $null + $script:runResult = Invoke-TestAction -Action "exception-capture" + + $eventId = Get-EventIds -AppOutput $script:runResult.Output -ExpectedCount 1 + if ($eventId) { + Write-Host "::group::Getting event content" + $script:runEvent = Get-SentryTestEvent -EventId "$eventId" + Write-Host "::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:runEvent = $null + $script:runResult = Invoke-TestAction -Action "crash-capture" + + $eventId = Get-EventIds -AppOutput $script:runResult.Output -ExpectedCount 1 + if ($eventId) { + Write-Host "::group::Getting event content" + $script:runEvent = Get-SentryTestEvent -TagName "test.crash_id" -TagValue "$eventId" -TimeoutSeconds 120 + Write-Host "::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 + } + + It "Reports crashedLastRun as Crashed on relaunch" { + $crashedLastRunLine = $runResult.CrashSendOutput | Where-Object { + $_ -match "crashedLastRun=Crashed" + } + $crashedLastRunLine | Should -Not -BeNullOrEmpty -Because "Native SDK should report crashedLastRun=Crashed after a native crash" + } + + It "Crash-send completes flush successfully" { + $flushLine = $runResult.CrashSendOutput | Where-Object { + $_ -match "Flush complete" + } + $flushLine | Should -Not -BeNullOrEmpty -Because "crash-send should complete its flush before quitting" + } + } +} From fbd0106f8260b29f893814048818ccee3438dceb Mon Sep 17 00:00:00 2001 From: bitsandfoxes Date: Tue, 3 Mar 2026 16:30:56 +0100 Subject: [PATCH 44/86] symbol upload --- .../workflows/smoke-test-build-android.yml | 1 + .../workflows/smoke-test-build-desktop.yml | 6 ++++-- .github/workflows/smoke-test-build-ios.yml | 1 + scripts/ci-docker-windows.ps1 | 1 + scripts/ci-docker.sh | 1 + .../Scripts/CliConfiguration.cs | 19 ++++++++++++++++--- 6 files changed, 24 insertions(+), 5 deletions(-) diff --git a/.github/workflows/smoke-test-build-android.yml b/.github/workflows/smoke-test-build-android.yml index dbc54a963..999d30021 100644 --- a/.github/workflows/smoke-test-build-android.yml +++ b/.github/workflows/smoke-test-build-android.yml @@ -35,6 +35,7 @@ jobs: shell: bash env: UNITY_LICENSE_SERVER_CONFIG: ${{ secrets.UNITY_LICENSE_SERVER_CONFIG }} + SENTRY_AUTH_TOKEN: ${{ secrets.SENTRY_AUTH_TOKEN }} - name: Download IntegrationTest project uses: actions/download-artifact@d3f86a106a0bac45b974a628896c90dbdf5c8093 # v4 diff --git a/.github/workflows/smoke-test-build-desktop.yml b/.github/workflows/smoke-test-build-desktop.yml index 61bb4777f..07bd8c798 100644 --- a/.github/workflows/smoke-test-build-desktop.yml +++ b/.github/workflows/smoke-test-build-desktop.yml @@ -52,12 +52,14 @@ jobs: shell: bash env: UNITY_LICENSE_SERVER_CONFIG: ${{ secrets.UNITY_LICENSE_SERVER_CONFIG }} + SENTRY_AUTH_TOKEN: ${{ secrets.SENTRY_AUTH_TOKEN }} - name: Start the Unity docker container (Windows) if: ${{ inputs.os == 'windows' }} run: ./scripts/ci-docker-windows.ps1 -UnityPrefix "$env:UNITY_VERSION" -ImageVariant "windows-il2cpp" -LicenseConfig "$env:UNITY_LICENSE_SERVER_CONFIG" env: UNITY_LICENSE_SERVER_CONFIG: ${{ secrets.UNITY_LICENSE_SERVER_CONFIG }} + SENTRY_AUTH_TOKEN: ${{ secrets.SENTRY_AUTH_TOKEN }} - name: Download IntegrationTest project uses: actions/download-artifact@d3f86a106a0bac45b974a628896c90dbdf5c8093 # v4 @@ -100,12 +102,12 @@ 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 "$env:BUILD_PLATFORM" -CheckSymbols -TestMode "integration" + run: ./test/Scripts.Integration.Test/configure-sentry.ps1 -UnityPath "$env:UNITY_PATH" -Platform "$env:BUILD_PLATFORM" -TestMode "integration" env: SENTRY_DSN: ${{ secrets.SENTRY_TEST_DSN }} - name: Build with Sentry SDK - run: ./test/Scripts.Integration.Test/build-project.ps1 -UnityPath "$env:UNITY_PATH" -Platform "$env:BUILD_PLATFORM" -CheckSymbols:$true -UnityVersion "$env:UNITY_VERSION" + run: ./test/Scripts.Integration.Test/build-project.ps1 -UnityPath "$env:UNITY_PATH" -Platform "$env:BUILD_PLATFORM" -CheckSymbols:$false -UnityVersion "$env:UNITY_VERSION" - name: Compare build sizes run: ./test/Scripts.Integration.Test/measure-build-size.ps1 -Path1 "samples/IntegrationTest/Build-NoSentry" -Path2 "samples/IntegrationTest/Build" -Platform "$env:BUILD_PLATFORM" -UnityVersion "$env:UNITY_VERSION" diff --git a/.github/workflows/smoke-test-build-ios.yml b/.github/workflows/smoke-test-build-ios.yml index 4c6209b5d..ff193d36b 100644 --- a/.github/workflows/smoke-test-build-ios.yml +++ b/.github/workflows/smoke-test-build-ios.yml @@ -41,6 +41,7 @@ jobs: shell: bash env: UNITY_LICENSE_SERVER_CONFIG: ${{ secrets.UNITY_LICENSE_SERVER_CONFIG }} + SENTRY_AUTH_TOKEN: ${{ secrets.SENTRY_AUTH_TOKEN }} - name: Download IntegrationTest project uses: actions/download-artifact@d3f86a106a0bac45b974a628896c90dbdf5c8093 # v4 diff --git a/scripts/ci-docker-windows.ps1 b/scripts/ci-docker-windows.ps1 index 504a60b7d..c78af47b2 100644 --- a/scripts/ci-docker-windows.ps1 +++ b/scripts/ci-docker-windows.ps1 @@ -36,6 +36,7 @@ docker run -td --name $container ` -v "${cwd}:C:\sentry-unity" ` -e UNITY_VERSION=$unityVersion ` -e GITHUB_ACTIONS=$env:GITHUB_ACTIONS ` + -e SENTRY_AUTH_TOKEN=$env:SENTRY_AUTH_TOKEN ` --workdir "C:\sentry-unity" $image # Set up Unity license configuration diff --git a/scripts/ci-docker.sh b/scripts/ci-docker.sh index 56b9124c7..c2b6c43d8 100755 --- a/scripts/ci-docker.sh +++ b/scripts/ci-docker.sh @@ -43,6 +43,7 @@ docker run -td --name $container \ -v /opt/microsoft/powershell/7:/opt/microsoft/powershell/7 \ -e UNITY_VERSION=$unityVersion \ -e GITHUB_ACTIONS="${GITHUB_ACTIONS}" \ + -e SENTRY_AUTH_TOKEN="${SENTRY_AUTH_TOKEN}" \ --workdir /sentry-unity $image # Generate unique machine-id to avoid any hardcoded values and license-fetch congestion diff --git a/test/Scripts.Integration.Test/Scripts/CliConfiguration.cs b/test/Scripts.Integration.Test/Scripts/CliConfiguration.cs index 95b6ed321..51720bccf 100644 --- a/test/Scripts.Integration.Test/Scripts/CliConfiguration.cs +++ b/test/Scripts.Integration.Test/Scripts/CliConfiguration.cs @@ -9,11 +9,24 @@ public override void Configure(SentryCliOptions cliOptions) { Debug.Log("Sentry: CliConfiguration::Configure() called"); - cliOptions.UploadSymbols = !string.IsNullOrEmpty(cliOptions.UrlOverride); - cliOptions.UploadSources = cliOptions.UploadSymbols; + var authToken = Environment.GetEnvironmentVariable("SENTRY_AUTH_TOKEN"); + if (!string.IsNullOrEmpty(authToken)) + { + // Upload to real Sentry using the auth token from the environment. + cliOptions.UploadSymbols = true; + cliOptions.UploadSources = true; + cliOptions.Auth = authToken; + } + else + { + // Upload to a local symbol server for verification (smoke tests). + cliOptions.UploadSymbols = !string.IsNullOrEmpty(cliOptions.UrlOverride); + cliOptions.UploadSources = cliOptions.UploadSymbols; + cliOptions.Auth = "dummy-token"; + } + cliOptions.Organization = "sentry-sdks"; cliOptions.Project = "sentry-unity"; - cliOptions.Auth = "dummy-token"; Debug.Log("Sentry: CliConfiguration::Configure() finished"); } From 0c827ab6f5c5b1c9fbfb1a26836bb16ff2d83441 Mon Sep 17 00:00:00 2001 From: bitsandfoxes Date: Tue, 3 Mar 2026 16:38:01 +0100 Subject: [PATCH 45/86] windows docker tweaks --- scripts/ci-docker-windows.ps1 | 13 +++++++++++++ test/Scripts.Integration.Test/globals.ps1 | 5 +++-- 2 files changed, 16 insertions(+), 2 deletions(-) diff --git a/scripts/ci-docker-windows.ps1 b/scripts/ci-docker-windows.ps1 index c78af47b2..048de0f2d 100644 --- a/scripts/ci-docker-windows.ps1 +++ b/scripts/ci-docker-windows.ps1 @@ -43,5 +43,18 @@ docker run -td --name $container ` docker exec $container powershell -Command "New-Item -ItemType Directory -Path 'C:\ProgramData\Unity\config' -Force | Out-Null" docker exec $container powershell -Command "Set-Content -Path 'C:\ProgramData\Unity\config\services-config.json' -Value '$LicenseConfig'" +# Create unity-editor wrapper script to match the Linux Docker image convention. +# In Linux GameCI images, /usr/bin/unity-editor wraps Unity with -batchmode. +# We replicate that here so the same UNITY_PATH env var works for both platforms. +docker exec $container powershell -Command @' +$unityExe = Get-ChildItem 'C:\Program Files\Unity' -Filter Unity.exe -Recurse -ErrorAction SilentlyContinue | + Where-Object { $_.DirectoryName -match '\\Editor$' } | + Select-Object -First 1 +if (-not $unityExe) { throw 'Unity.exe not found in the container' } +$content = "@echo off`r`n`"$($unityExe.FullName)`" -batchmode %*" +Set-Content -Path 'C:\Windows\unity-editor.cmd' -Value $content +Write-Host "Created unity-editor wrapper pointing to $($unityExe.FullName)" +'@ + Write-Host "Container started successfully:" docker ps --filter "name=^/$container$" diff --git a/test/Scripts.Integration.Test/globals.ps1 b/test/Scripts.Integration.Test/globals.ps1 index 82e7f7e4a..47c290895 100644 --- a/test/Scripts.Integration.Test/globals.ps1 +++ b/test/Scripts.Integration.Test/globals.ps1 @@ -178,8 +178,9 @@ function RunUnityCustom([string] $unityPath, [string[]] $arguments, [switch] $Re If ($unityPath.StartsWith("docker ")) { # Fix paths (they're supposed to be the current working directory in the docker container) - Write-Detail "Replacing project root ($(ProjectRoot)) in docker arguments" - $arguments = $arguments | ForEach-Object { $_.Replace("$(ProjectRoot)", "/sentry-unity") } + $containerRoot = $IsWindows ? "C:\sentry-unity" : "/sentry-unity" + Write-Detail "Replacing project root ($(ProjectRoot)) with $containerRoot in docker arguments" + $arguments = $arguments | ForEach-Object { $_.Replace("$(ProjectRoot)", $containerRoot) } } return RunUnity $unityPath $arguments -ReturnLogOutput:$ReturnLogOutput From 8713530b477e4e0fe82ade9992bcc26421c4ea1d Mon Sep 17 00:00:00 2001 From: bitsandfoxes Date: Tue, 3 Mar 2026 16:40:14 +0100 Subject: [PATCH 46/86] fix unboun variable --- scripts/ci-docker.sh | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/scripts/ci-docker.sh b/scripts/ci-docker.sh index c2b6c43d8..1cbde3566 100755 --- a/scripts/ci-docker.sh +++ b/scripts/ci-docker.sh @@ -43,7 +43,7 @@ docker run -td --name $container \ -v /opt/microsoft/powershell/7:/opt/microsoft/powershell/7 \ -e UNITY_VERSION=$unityVersion \ -e GITHUB_ACTIONS="${GITHUB_ACTIONS}" \ - -e SENTRY_AUTH_TOKEN="${SENTRY_AUTH_TOKEN}" \ + -e SENTRY_AUTH_TOKEN="${SENTRY_AUTH_TOKEN:-}" \ --workdir /sentry-unity $image # Generate unique machine-id to avoid any hardcoded values and license-fetch congestion From a69115d0498d1c04943cbee1aba03c41af77e01f Mon Sep 17 00:00:00 2001 From: bitsandfoxes Date: Tue, 3 Mar 2026 16:59:38 +0100 Subject: [PATCH 47/86] muck around with docker --- .github/workflows/smoke-test-build-desktop.yml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.github/workflows/smoke-test-build-desktop.yml b/.github/workflows/smoke-test-build-desktop.yml index 07bd8c798..d0bf1b57f 100644 --- a/.github/workflows/smoke-test-build-desktop.yml +++ b/.github/workflows/smoke-test-build-desktop.yml @@ -20,7 +20,7 @@ jobs: runs-on: ${{ inputs.os == 'windows' && 'windows-latest' || 'ubuntu-latest' }} env: GITHUB_ACTOR: ${{ github.actor }} - UNITY_PATH: docker exec unity unity-editor + UNITY_PATH: ${{ inputs.os == 'windows' && 'docker exec unity cmd /c unity-editor' || 'docker exec unity unity-editor' }} UNITY_VERSION: ${{ inputs.unity-version }} BUILD_PLATFORM: ${{ inputs.os == 'windows' && 'Windows' || 'Linux' }} From c444ba64c45d74ad14092c569b75fe74d653bbe0 Mon Sep 17 00:00:00 2001 From: bitsandfoxes Date: Tue, 3 Mar 2026 17:31:57 +0100 Subject: [PATCH 48/86] more windows docker fun --- .github/workflows/smoke-test-build-desktop.yml | 14 +++++++++++++- 1 file changed, 13 insertions(+), 1 deletion(-) diff --git a/.github/workflows/smoke-test-build-desktop.yml b/.github/workflows/smoke-test-build-desktop.yml index d0bf1b57f..1312c5bc6 100644 --- a/.github/workflows/smoke-test-build-desktop.yml +++ b/.github/workflows/smoke-test-build-desktop.yml @@ -20,7 +20,7 @@ jobs: runs-on: ${{ inputs.os == 'windows' && 'windows-latest' || 'ubuntu-latest' }} env: GITHUB_ACTOR: ${{ github.actor }} - UNITY_PATH: ${{ inputs.os == 'windows' && 'docker exec unity cmd /c unity-editor' || 'docker exec unity unity-editor' }} + UNITY_PATH: ${{ inputs.os == 'windows' && 'docker exec -w C:\sentry-unity unity cmd /c unity-editor' || 'docker exec unity unity-editor' }} UNITY_VERSION: ${{ inputs.unity-version }} BUILD_PLATFORM: ${{ inputs.os == 'windows' && 'Windows' || 'Linux' }} @@ -133,6 +133,17 @@ jobs: path: test-app-desktop.tar.gz retention-days: 14 + - name: Dump Unity log from Docker container on failure + if: ${{ failure() }} + run: | + Write-Host "=== Unity log from inside Docker container ===" + if ("${{ inputs.os }}" -eq "windows") { + docker exec unity cmd /c type C:\sentry-unity\unity.log + } else { + docker exec unity cat /sentry-unity/unity.log + } + continue-on-error: true + - name: Upload IntegrationTest project on failure if: ${{ failure() }} uses: actions/upload-artifact@ea165f8d65b6e75b540449e92b4886f43607fa02 # v4 @@ -140,5 +151,6 @@ jobs: name: failed-project-desktop-${{ inputs.os }}-${{ env.UNITY_VERSION }} path: | samples/IntegrationTest + unity.log !samples/IntegrationTest/Build/*_BackUpThisFolder_ButDontShipItWithYourGame retention-days: 14 From 7c786214106068066e6503498a11665cecc3ae6e Mon Sep 17 00:00:00 2001 From: bitsandfoxes Date: Tue, 3 Mar 2026 17:50:53 +0100 Subject: [PATCH 49/86] diagnostics --- .../workflows/smoke-test-build-desktop.yml | 27 ++++++++++++++++--- scripts/unity-utils.ps1 | 4 ++- 2 files changed, 26 insertions(+), 5 deletions(-) diff --git a/.github/workflows/smoke-test-build-desktop.yml b/.github/workflows/smoke-test-build-desktop.yml index 1312c5bc6..fd93d68b1 100644 --- a/.github/workflows/smoke-test-build-desktop.yml +++ b/.github/workflows/smoke-test-build-desktop.yml @@ -133,14 +133,33 @@ jobs: path: test-app-desktop.tar.gz retention-days: 14 - - name: Dump Unity log from Docker container on failure + - name: Docker diagnostics on failure if: ${{ failure() }} run: | - Write-Host "=== Unity log from inside Docker container ===" + Write-Host "=== Docker container status ===" + docker ps -a --filter "name=^/unity$" + if ("${{ inputs.os }}" -eq "windows") { - docker exec unity cmd /c type C:\sentry-unity\unity.log + Write-Host "`n=== Working directory ===" + docker exec unity cmd /c cd + + Write-Host "`n=== unity-editor.cmd exists? ===" + docker exec unity cmd /c "if exist C:\Windows\unity-editor.cmd (echo YES & type C:\Windows\unity-editor.cmd) else (echo NO)" + + Write-Host "`n=== Test unity-editor wrapper ===" + docker exec -w C:\sentry-unity unity cmd /c "unity-editor -quit -batchmode -nographics 2>&1" + + Write-Host "`n=== Unity log from container ===" + docker exec unity cmd /c "if exist C:\sentry-unity\unity.log (type C:\sentry-unity\unity.log) else (echo unity.log not found at C:\sentry-unity)" + + Write-Host "`n=== Check for log in default locations ===" + docker exec unity powershell -Command "Get-ChildItem -Path C:\ -Filter unity.log -Recurse -Depth 2 -ErrorAction SilentlyContinue | ForEach-Object { Write-Host $_.FullName }" } else { - docker exec unity cat /sentry-unity/unity.log + Write-Host "`n=== Working directory ===" + docker exec unity pwd + + Write-Host "`n=== Unity log from container ===" + docker exec unity cat /sentry-unity/unity.log 2>&1 || echo "unity.log not found" } continue-on-error: true diff --git a/scripts/unity-utils.ps1 b/scripts/unity-utils.ps1 index 69f77d3d4..488ab43a7 100644 --- a/scripts/unity-utils.ps1 +++ b/scripts/unity-utils.ps1 @@ -52,7 +52,9 @@ function RunUnity([string] $unityPath, [string[]] $arguments, [switch] $ReturnLo ClearUnityLog $logFilePath New-Item $logFilePath > $null - $process = Start-Process -FilePath $unityPath -ArgumentList $arguments -PassThru + # -NoNewWindow ensures stdout/stderr go to the parent console (visible in CI logs). + # Without it, Windows creates a hidden window and all Docker/process output is lost. + $process = Start-Process -FilePath $unityPath -ArgumentList $arguments -NoNewWindow -PassThru $stdout = WaitForUnityExit $logFilePath $process } From f9badd755b3acc31e7776263b60d01a848ab0b93 Mon Sep 17 00:00:00 2001 From: bitsandfoxes Date: Tue, 3 Mar 2026 18:40:47 +0100 Subject: [PATCH 50/86] docker.. --- scripts/ci-docker-windows.ps1 | 18 +++++++++--------- 1 file changed, 9 insertions(+), 9 deletions(-) diff --git a/scripts/ci-docker-windows.ps1 b/scripts/ci-docker-windows.ps1 index 048de0f2d..960537c99 100644 --- a/scripts/ci-docker-windows.ps1 +++ b/scripts/ci-docker-windows.ps1 @@ -46,15 +46,15 @@ docker exec $container powershell -Command "Set-Content -Path 'C:\ProgramData\Un # Create unity-editor wrapper script to match the Linux Docker image convention. # In Linux GameCI images, /usr/bin/unity-editor wraps Unity with -batchmode. # We replicate that here so the same UNITY_PATH env var works for both platforms. -docker exec $container powershell -Command @' -$unityExe = Get-ChildItem 'C:\Program Files\Unity' -Filter Unity.exe -Recurse -ErrorAction SilentlyContinue | - Where-Object { $_.DirectoryName -match '\\Editor$' } | - Select-Object -First 1 -if (-not $unityExe) { throw 'Unity.exe not found in the container' } -$content = "@echo off`r`n`"$($unityExe.FullName)`" -batchmode %*" -Set-Content -Path 'C:\Windows\unity-editor.cmd' -Value $content -Write-Host "Created unity-editor wrapper pointing to $($unityExe.FullName)" -'@ +# GameCI Windows images install Unity to C:\UnityEditor\\Editor\. +$unityExe = "C:\UnityEditor\$unityVersion\Editor\Unity.exe" +docker exec $container cmd /c "if not exist `"$unityExe`" exit 1" +if ($LASTEXITCODE -ne 0) { throw "Unity.exe not found at $unityExe" } + +$wrapperContent = "@echo off`r`n`"$unityExe`" -batchmode %*" +docker exec $container powershell -Command "Set-Content -Path 'C:\Windows\unity-editor.cmd' -Value '$wrapperContent'" +if ($LASTEXITCODE -ne 0) { throw "Failed to create unity-editor wrapper" } +Write-Host "Created unity-editor wrapper pointing to $unityExe" Write-Host "Container started successfully:" docker ps --filter "name=^/$container$" From bcd7dff4d035ae40ed134854d2ee7066022c67db Mon Sep 17 00:00:00 2001 From: bitsandfoxes Date: Tue, 3 Mar 2026 19:01:42 +0100 Subject: [PATCH 51/86] unity path inside the container --- scripts/ci-docker-windows.ps1 | 16 ++++++++-------- 1 file changed, 8 insertions(+), 8 deletions(-) diff --git a/scripts/ci-docker-windows.ps1 b/scripts/ci-docker-windows.ps1 index 960537c99..726b6fcf2 100644 --- a/scripts/ci-docker-windows.ps1 +++ b/scripts/ci-docker-windows.ps1 @@ -46,15 +46,15 @@ docker exec $container powershell -Command "Set-Content -Path 'C:\ProgramData\Un # Create unity-editor wrapper script to match the Linux Docker image convention. # In Linux GameCI images, /usr/bin/unity-editor wraps Unity with -batchmode. # We replicate that here so the same UNITY_PATH env var works for both platforms. -# GameCI Windows images install Unity to C:\UnityEditor\\Editor\. -$unityExe = "C:\UnityEditor\$unityVersion\Editor\Unity.exe" -docker exec $container cmd /c "if not exist `"$unityExe`" exit 1" -if ($LASTEXITCODE -ne 0) { throw "Unity.exe not found at $unityExe" } - -$wrapperContent = "@echo off`r`n`"$unityExe`" -batchmode %*" -docker exec $container powershell -Command "Set-Content -Path 'C:\Windows\unity-editor.cmd' -Value '$wrapperContent'" -if ($LASTEXITCODE -ne 0) { throw "Failed to create unity-editor wrapper" } +# GameCI Windows images set UNITY_PATH to the Unity install directory. +docker exec $container powershell -Command @' +$unityExe = Join-Path $env:UNITY_PATH 'Editor\Unity.exe' +if (-not (Test-Path $unityExe)) { throw "Unity.exe not found at $unityExe (UNITY_PATH=$env:UNITY_PATH)" } +$content = "@echo off`r`n`"$unityExe`" -batchmode %*" +Set-Content -Path 'C:\Windows\unity-editor.cmd' -Value $content Write-Host "Created unity-editor wrapper pointing to $unityExe" +'@ +if ($LASTEXITCODE -ne 0) { throw "Failed to create unity-editor wrapper (exit code: $LASTEXITCODE)" } Write-Host "Container started successfully:" docker ps --filter "name=^/$container$" From b255005958a7de3d8e56ea06cc0cae35e53ff7b8 Mon Sep 17 00:00:00 2001 From: bitsandfoxes Date: Tue, 3 Mar 2026 19:27:52 +0100 Subject: [PATCH 52/86] pin win22, nographics --- .github/workflows/smoke-test-build-desktop.yml | 2 +- scripts/ci-docker-windows.ps1 | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/.github/workflows/smoke-test-build-desktop.yml b/.github/workflows/smoke-test-build-desktop.yml index fd93d68b1..ffbcf5857 100644 --- a/.github/workflows/smoke-test-build-desktop.yml +++ b/.github/workflows/smoke-test-build-desktop.yml @@ -17,7 +17,7 @@ defaults: jobs: build: name: ${{ inputs.os }} ${{ inputs.unity-version }} - runs-on: ${{ inputs.os == 'windows' && 'windows-latest' || 'ubuntu-latest' }} + runs-on: ${{ inputs.os == 'windows' && 'windows-2022' || 'ubuntu-latest' }} env: GITHUB_ACTOR: ${{ github.actor }} UNITY_PATH: ${{ inputs.os == 'windows' && 'docker exec -w C:\sentry-unity unity cmd /c unity-editor' || 'docker exec unity unity-editor' }} diff --git a/scripts/ci-docker-windows.ps1 b/scripts/ci-docker-windows.ps1 index 726b6fcf2..fd6a0a917 100644 --- a/scripts/ci-docker-windows.ps1 +++ b/scripts/ci-docker-windows.ps1 @@ -50,7 +50,7 @@ docker exec $container powershell -Command "Set-Content -Path 'C:\ProgramData\Un docker exec $container powershell -Command @' $unityExe = Join-Path $env:UNITY_PATH 'Editor\Unity.exe' if (-not (Test-Path $unityExe)) { throw "Unity.exe not found at $unityExe (UNITY_PATH=$env:UNITY_PATH)" } -$content = "@echo off`r`n`"$unityExe`" -batchmode %*" +$content = "@echo off`r`n`"$unityExe`" -batchmode -nographics %*" Set-Content -Path 'C:\Windows\unity-editor.cmd' -Value $content Write-Host "Created unity-editor wrapper pointing to $unityExe" '@ From 86f5906d0502ff834aa1d5961f075e72537e8c02 Mon Sep 17 00:00:00 2001 From: bitsandfoxes Date: Tue, 3 Mar 2026 19:30:46 +0100 Subject: [PATCH 53/86] back to win25. unity 6.3? --- .github/workflows/smoke-test-build-desktop.yml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.github/workflows/smoke-test-build-desktop.yml b/.github/workflows/smoke-test-build-desktop.yml index ffbcf5857..fd93d68b1 100644 --- a/.github/workflows/smoke-test-build-desktop.yml +++ b/.github/workflows/smoke-test-build-desktop.yml @@ -17,7 +17,7 @@ defaults: jobs: build: name: ${{ inputs.os }} ${{ inputs.unity-version }} - runs-on: ${{ inputs.os == 'windows' && 'windows-2022' || 'ubuntu-latest' }} + runs-on: ${{ inputs.os == 'windows' && 'windows-latest' || 'ubuntu-latest' }} env: GITHUB_ACTOR: ${{ github.actor }} UNITY_PATH: ${{ inputs.os == 'windows' && 'docker exec -w C:\sentry-unity unity cmd /c unity-editor' || 'docker exec unity unity-editor' }} From a1644da4c502e7b444025dc59b62d63940874514 Mon Sep 17 00:00:00 2001 From: bitsandfoxes Date: Tue, 3 Mar 2026 19:59:37 +0100 Subject: [PATCH 54/86] nographics duplication --- scripts/ci-docker-windows.ps1 | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/scripts/ci-docker-windows.ps1 b/scripts/ci-docker-windows.ps1 index fd6a0a917..726b6fcf2 100644 --- a/scripts/ci-docker-windows.ps1 +++ b/scripts/ci-docker-windows.ps1 @@ -50,7 +50,7 @@ docker exec $container powershell -Command "Set-Content -Path 'C:\ProgramData\Un docker exec $container powershell -Command @' $unityExe = Join-Path $env:UNITY_PATH 'Editor\Unity.exe' if (-not (Test-Path $unityExe)) { throw "Unity.exe not found at $unityExe (UNITY_PATH=$env:UNITY_PATH)" } -$content = "@echo off`r`n`"$unityExe`" -batchmode -nographics %*" +$content = "@echo off`r`n`"$unityExe`" -batchmode %*" Set-Content -Path 'C:\Windows\unity-editor.cmd' -Value $content Write-Host "Created unity-editor wrapper pointing to $unityExe" '@ From c5b3d03a4ffea48ae6658d6eca55b5ebfa15c708 Mon Sep 17 00:00:00 2001 From: bitsandfoxes Date: Wed, 4 Mar 2026 09:42:43 +0100 Subject: [PATCH 55/86] handle 'nopgraphics' --- scripts/ci-docker-windows.ps1 | 2 +- scripts/unity-utils.ps1 | 6 +++--- 2 files changed, 4 insertions(+), 4 deletions(-) diff --git a/scripts/ci-docker-windows.ps1 b/scripts/ci-docker-windows.ps1 index 726b6fcf2..fd6a0a917 100644 --- a/scripts/ci-docker-windows.ps1 +++ b/scripts/ci-docker-windows.ps1 @@ -50,7 +50,7 @@ docker exec $container powershell -Command "Set-Content -Path 'C:\ProgramData\Un docker exec $container powershell -Command @' $unityExe = Join-Path $env:UNITY_PATH 'Editor\Unity.exe' if (-not (Test-Path $unityExe)) { throw "Unity.exe not found at $unityExe (UNITY_PATH=$env:UNITY_PATH)" } -$content = "@echo off`r`n`"$unityExe`" -batchmode %*" +$content = "@echo off`r`n`"$unityExe`" -batchmode -nographics %*" Set-Content -Path 'C:\Windows\unity-editor.cmd' -Value $content Write-Host "Created unity-editor wrapper pointing to $unityExe" '@ diff --git a/scripts/unity-utils.ps1 b/scripts/unity-utils.ps1 index 488ab43a7..9d3f9e65a 100644 --- a/scripts/unity-utils.ps1 +++ b/scripts/unity-utils.ps1 @@ -12,9 +12,9 @@ function RunUnity([string] $unityPath, [string[]] $arguments, [switch] $ReturnLo If ($unityPath -eq "docker") { - # Remove "-batchmode" which ends up being duplicate because the referenced unity-editor script already adds it - Write-Host "Removing argument '-batchmode' - it would be duplicate and cause a build to fail" - $arguments = $arguments | Where-Object { $_ –ne "-batchmode" } + # Remove "-batchmode" and "-nographics" which end up being duplicate because the unity-editor wrapper already adds them + Write-Host "Removing arguments '-batchmode' and '-nographics' - they would be duplicate and cause a build to fail" + $arguments = $arguments | Where-Object { $_ –ne "-batchmode" -and $_ -ne "-nographics" } Write-Host "Updated arguments: $arguments" } ElseIf ($IsLinux -and "$env:XDG_CURRENT_DESKTOP" -eq "" -and $unityPath -ne "xvfb-run") From dda6bf8e836ce66fc798212bc8f324d8d114f66c Mon Sep 17 00:00:00 2001 From: bitsandfoxes Date: Wed, 4 Mar 2026 10:37:49 +0100 Subject: [PATCH 56/86] restructured desktop --- .github/workflows/ci.yml | 46 +++++-- ...desktop.yml => smoke-test-build-linux.yml} | 74 ++++------- .../workflows/smoke-test-build-windows.yml | 123 ++++++++++++++++++ ...n-desktop.yml => smoke-test-run-linux.yml} | 27 +--- .github/workflows/smoke-test-run-windows.yml | 50 +++++++ 5 files changed, 236 insertions(+), 84 deletions(-) rename .github/workflows/{smoke-test-build-desktop.yml => smoke-test-build-linux.yml} (57%) create mode 100644 .github/workflows/smoke-test-build-windows.yml rename .github/workflows/{smoke-test-run-desktop.yml => smoke-test-run-linux.yml} (64%) create mode 100644 .github/workflows/smoke-test-run-windows.yml diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml index 292514d4d..cab518fb4 100644 --- a/.github/workflows/ci.yml +++ b/.github/workflows/ci.yml @@ -354,8 +354,8 @@ jobs: pip3 install --upgrade --user selenium urllib3 requests python3 scripts/smoke-test-webgl.py "samples/IntegrationTest/Build" - smoke-test-build-desktop: - name: Build ${{ matrix.os }} ${{ matrix.unity-version }} Integration Test + smoke-test-build-linux: + name: Build linux ${{ matrix.unity-version }} Integration Test if: ${{ !startsWith(github.ref, 'refs/heads/release/') }} needs: [smoke-test-create, create-unity-matrix] secrets: inherit @@ -363,32 +363,54 @@ jobs: fail-fast: false matrix: unity-version: ${{ fromJSON(needs.create-unity-matrix.outputs.unity-matrix).unity-version }} - os: ["windows", "linux"] - uses: ./.github/workflows/smoke-test-build-desktop.yml + uses: ./.github/workflows/smoke-test-build-linux.yml with: unity-version: ${{ matrix.unity-version }} - os: ${{ matrix.os }} - smoke-test-run-desktop: - name: Run ${{ matrix.os }} ${{ matrix.unity-version }} Integration Test + smoke-test-build-windows: + name: Build windows ${{ matrix.unity-version }} Integration Test if: ${{ !startsWith(github.ref, 'refs/heads/release/') }} - needs: [smoke-test-build-desktop, create-unity-matrix] + needs: [smoke-test-create, create-unity-matrix] + secrets: inherit + strategy: + fail-fast: false + matrix: + unity-version: ${{ fromJSON(needs.create-unity-matrix.outputs.unity-matrix).unity-version }} + uses: ./.github/workflows/smoke-test-build-windows.yml + with: + unity-version: ${{ matrix.unity-version }} + + smoke-test-run-linux: + name: Run linux ${{ matrix.unity-version }} Integration Test + if: ${{ !startsWith(github.ref, 'refs/heads/release/') }} + needs: [smoke-test-build-linux, create-unity-matrix] + secrets: inherit + strategy: + fail-fast: false + matrix: + unity-version: ${{ fromJSON(needs.create-unity-matrix.outputs.unity-matrix).unity-version }} + uses: ./.github/workflows/smoke-test-run-linux.yml + with: + unity-version: ${{ matrix.unity-version }} + + smoke-test-run-windows: + name: Run windows ${{ matrix.unity-version }} Integration Test + if: ${{ !startsWith(github.ref, 'refs/heads/release/') }} + needs: [smoke-test-build-windows, create-unity-matrix] secrets: inherit strategy: fail-fast: false matrix: unity-version: ${{ fromJSON(needs.create-unity-matrix.outputs.unity-matrix).unity-version }} - os: ["windows", "linux"] - uses: ./.github/workflows/smoke-test-run-desktop.yml + uses: ./.github/workflows/smoke-test-run-windows.yml with: unity-version: ${{ matrix.unity-version }} - os: ${{ matrix.os }} build-size-summary: name: Build Size runs-on: ubuntu-latest if: ${{ !startsWith(github.ref, 'refs/heads/release/') }} - needs: [smoke-test-build, smoke-test-build-android, smoke-test-compile-ios, smoke-test-build-desktop] + needs: [smoke-test-build, smoke-test-build-android, smoke-test-compile-ios, smoke-test-build-linux, smoke-test-build-windows] steps: - name: Checkout uses: actions/checkout@v3 diff --git a/.github/workflows/smoke-test-build-desktop.yml b/.github/workflows/smoke-test-build-linux.yml similarity index 57% rename from .github/workflows/smoke-test-build-desktop.yml rename to .github/workflows/smoke-test-build-linux.yml index fd93d68b1..7d551d68b 100644 --- a/.github/workflows/smoke-test-build-desktop.yml +++ b/.github/workflows/smoke-test-build-linux.yml @@ -1,14 +1,10 @@ -name: "IntegrationTest: Build Desktop" +name: "IntegrationTest: Build Linux" on: workflow_call: inputs: unity-version: required: true type: string - os: - required: true - type: string - description: '"windows" or "linux"' defaults: run: @@ -16,20 +12,19 @@ defaults: jobs: build: - name: ${{ inputs.os }} ${{ inputs.unity-version }} - runs-on: ${{ inputs.os == 'windows' && 'windows-latest' || 'ubuntu-latest' }} + name: linux ${{ inputs.unity-version }} + runs-on: ubuntu-latest env: GITHUB_ACTOR: ${{ github.actor }} - UNITY_PATH: ${{ inputs.os == 'windows' && 'docker exec -w C:\sentry-unity unity cmd /c unity-editor' || 'docker exec unity unity-editor' }} + UNITY_PATH: docker exec unity unity-editor UNITY_VERSION: ${{ inputs.unity-version }} - BUILD_PLATFORM: ${{ inputs.os == 'windows' && 'Windows' || 'Linux' }} + BUILD_PLATFORM: Linux steps: - name: Checkout uses: actions/checkout@f43a0e5ff2bd294095638e18286ca9a3d1956744 # v3 - - name: Free Disk Space (Ubuntu) - if: ${{ inputs.os == 'linux' }} + - name: Free Disk Space uses: jlumbroso/free-disk-space@f68fdb76e2ea636224182cfb7377ff9a1708f9b8 # v1.3.0 with: android: true @@ -46,21 +41,13 @@ jobs: username: ${{ env.GITHUB_ACTOR }} password: ${{ secrets.GITHUB_TOKEN }} - - name: Start the Unity docker container (Linux) - if: ${{ inputs.os == 'linux' }} + - name: Start the Unity docker container run: ./scripts/ci-docker.sh "$UNITY_VERSION" "Linux-il2cpp" "$UNITY_LICENSE_SERVER_CONFIG" shell: bash env: UNITY_LICENSE_SERVER_CONFIG: ${{ secrets.UNITY_LICENSE_SERVER_CONFIG }} SENTRY_AUTH_TOKEN: ${{ secrets.SENTRY_AUTH_TOKEN }} - - name: Start the Unity docker container (Windows) - if: ${{ inputs.os == 'windows' }} - run: ./scripts/ci-docker-windows.ps1 -UnityPrefix "$env:UNITY_VERSION" -ImageVariant "windows-il2cpp" -LicenseConfig "$env:UNITY_LICENSE_SERVER_CONFIG" - env: - UNITY_LICENSE_SERVER_CONFIG: ${{ secrets.UNITY_LICENSE_SERVER_CONFIG }} - SENTRY_AUTH_TOKEN: ${{ secrets.SENTRY_AUTH_TOKEN }} - - name: Download IntegrationTest project uses: actions/download-artifact@d3f86a106a0bac45b974a628896c90dbdf5c8093 # v4 with: @@ -73,21 +60,21 @@ jobs: uses: actions/cache@d4323d4df104b026a6aa633fdb11d772146be0bf # v4.2.2 with: path: samples/IntegrationTest/Library - key: Library-IntegrationTest-${{ inputs.os }}-${{ env.UNITY_VERSION }}-v1 + key: Library-IntegrationTest-linux-${{ env.UNITY_VERSION }}-v1 restore-keys: | - Library-IntegrationTest-${{ inputs.os }}-${{ env.UNITY_VERSION }}- - Library-IntegrationTest-${{ inputs.os }}- + Library-IntegrationTest-linux-${{ env.UNITY_VERSION }}- + Library-IntegrationTest-linux- - name: Restore cached build without Sentry id: cache-build-nosentry uses: actions/cache@v4 with: path: samples/IntegrationTest/Build-NoSentry - key: build-nosentry-${{ env.BUILD_PLATFORM }}-${{ inputs.unity-version }} + key: build-nosentry-Linux-${{ inputs.unity-version }} - name: Build without Sentry SDK if: steps.cache-build-nosentry.outputs.cache-hit != 'true' - run: ./test/Scripts.Integration.Test/build-project.ps1 -UnityPath "$env:UNITY_PATH" -Platform "$env:BUILD_PLATFORM" -CheckSymbols:$false -BuildDirName "Build-NoSentry" + run: ./test/Scripts.Integration.Test/build-project.ps1 -UnityPath "$env:UNITY_PATH" -Platform Linux -CheckSymbols:$false -BuildDirName "Build-NoSentry" - name: Download UPM package uses: vaind/download-artifact@e7141b6a94ef28aa3d828b52830cfa1f406a1848 # v4-with-wait-timeout @@ -102,20 +89,20 @@ 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 "$env:BUILD_PLATFORM" -TestMode "integration" + run: ./test/Scripts.Integration.Test/configure-sentry.ps1 -UnityPath "$env:UNITY_PATH" -Platform Linux -TestMode "integration" env: SENTRY_DSN: ${{ secrets.SENTRY_TEST_DSN }} - name: Build with Sentry SDK - run: ./test/Scripts.Integration.Test/build-project.ps1 -UnityPath "$env:UNITY_PATH" -Platform "$env:BUILD_PLATFORM" -CheckSymbols:$false -UnityVersion "$env:UNITY_VERSION" + run: ./test/Scripts.Integration.Test/build-project.ps1 -UnityPath "$env:UNITY_PATH" -Platform Linux -CheckSymbols:$false -UnityVersion "$env:UNITY_VERSION" - name: Compare build sizes - run: ./test/Scripts.Integration.Test/measure-build-size.ps1 -Path1 "samples/IntegrationTest/Build-NoSentry" -Path2 "samples/IntegrationTest/Build" -Platform "$env:BUILD_PLATFORM" -UnityVersion "$env:UNITY_VERSION" + run: ./test/Scripts.Integration.Test/measure-build-size.ps1 -Path1 "samples/IntegrationTest/Build-NoSentry" -Path2 "samples/IntegrationTest/Build" -Platform Linux -UnityVersion "$env:UNITY_VERSION" - name: Upload build size measurement uses: actions/upload-artifact@ea165f8d65b6e75b540449e92b4886f43607fa02 # v4 with: - name: build-size-${{ env.BUILD_PLATFORM }}-${{ env.UNITY_VERSION }} + name: build-size-Linux-${{ env.UNITY_VERSION }} path: build-size-measurements/*.json retention-days: 1 @@ -128,7 +115,7 @@ jobs: - name: Upload test app uses: actions/upload-artifact@ea165f8d65b6e75b540449e92b4886f43607fa02 # v4 with: - name: testapp-desktop-compiled-${{ env.UNITY_VERSION }}-${{ inputs.os }} + name: testapp-desktop-compiled-${{ env.UNITY_VERSION }}-linux if-no-files-found: error path: test-app-desktop.tar.gz retention-days: 14 @@ -139,35 +126,18 @@ jobs: Write-Host "=== Docker container status ===" docker ps -a --filter "name=^/unity$" - if ("${{ inputs.os }}" -eq "windows") { - Write-Host "`n=== Working directory ===" - docker exec unity cmd /c cd - - Write-Host "`n=== unity-editor.cmd exists? ===" - docker exec unity cmd /c "if exist C:\Windows\unity-editor.cmd (echo YES & type C:\Windows\unity-editor.cmd) else (echo NO)" - - Write-Host "`n=== Test unity-editor wrapper ===" - docker exec -w C:\sentry-unity unity cmd /c "unity-editor -quit -batchmode -nographics 2>&1" - - Write-Host "`n=== Unity log from container ===" - docker exec unity cmd /c "if exist C:\sentry-unity\unity.log (type C:\sentry-unity\unity.log) else (echo unity.log not found at C:\sentry-unity)" - - Write-Host "`n=== Check for log in default locations ===" - docker exec unity powershell -Command "Get-ChildItem -Path C:\ -Filter unity.log -Recurse -Depth 2 -ErrorAction SilentlyContinue | ForEach-Object { Write-Host $_.FullName }" - } else { - Write-Host "`n=== Working directory ===" - docker exec unity pwd + Write-Host "`n=== Working directory ===" + docker exec unity pwd - Write-Host "`n=== Unity log from container ===" - docker exec unity cat /sentry-unity/unity.log 2>&1 || echo "unity.log not found" - } + Write-Host "`n=== Unity log from container ===" + docker exec unity cat /sentry-unity/unity.log 2>&1 || echo "unity.log not found" continue-on-error: true - name: Upload IntegrationTest project on failure if: ${{ failure() }} uses: actions/upload-artifact@ea165f8d65b6e75b540449e92b4886f43607fa02 # v4 with: - name: failed-project-desktop-${{ inputs.os }}-${{ env.UNITY_VERSION }} + name: failed-project-desktop-linux-${{ env.UNITY_VERSION }} path: | samples/IntegrationTest unity.log diff --git a/.github/workflows/smoke-test-build-windows.yml b/.github/workflows/smoke-test-build-windows.yml new file mode 100644 index 000000000..4b97b3efd --- /dev/null +++ b/.github/workflows/smoke-test-build-windows.yml @@ -0,0 +1,123 @@ +name: "IntegrationTest: Build Windows" +on: + workflow_call: + inputs: + unity-version: + required: true + type: string + +defaults: + run: + shell: pwsh + +jobs: + build: + name: windows ${{ inputs.unity-version }} + runs-on: windows-latest + env: + UNITY_VERSION: ${{ inputs.unity-version }} + BUILD_PLATFORM: Windows + + steps: + - name: Checkout + uses: actions/checkout@f43a0e5ff2bd294095638e18286ca9a3d1956744 # v3 + + - name: Load env + id: env + run: echo "unityVersion=$(./scripts/ci-env.ps1 "unity$env:UNITY_VERSION")" >> $env:GITHUB_OUTPUT + + - name: Setup Unity + uses: getsentry/setup-unity@3bdc8c022b6d30ecf2d21d12a564bfa55a54fa2e + with: + unity-version: ${{ steps.env.outputs.unityVersion }} + unity-modules: windows-il2cpp + + - name: Create Unity license config + run: | + New-Item -Path "C:/ProgramData/Unity/config/" -ItemType Directory -Force + Set-Content -Path "C:/ProgramData/Unity/config/services-config.json" -Value "$env:UNITY_LICENSE_SERVER_CONFIG" + env: + UNITY_LICENSE_SERVER_CONFIG: ${{ secrets.UNITY_LICENSE_SERVER_CONFIG }} + + - name: Download IntegrationTest project + uses: actions/download-artifact@d3f86a106a0bac45b974a628896c90dbdf5c8093 # v4 + with: + name: smoke-test-${{ env.UNITY_VERSION }} + + - name: Extract project archive + run: tar -xvzf test-project.tar.gz + + - name: Cache Unity Library + uses: actions/cache@d4323d4df104b026a6aa633fdb11d772146be0bf # v4.2.2 + with: + path: samples/IntegrationTest/Library + key: Library-IntegrationTest-windows-${{ env.UNITY_VERSION }}-v1 + restore-keys: | + Library-IntegrationTest-windows-${{ env.UNITY_VERSION }}- + Library-IntegrationTest-windows- + + - name: Restore cached build without Sentry + id: cache-build-nosentry + uses: actions/cache@v4 + with: + path: samples/IntegrationTest/Build-NoSentry + key: build-nosentry-Windows-${{ inputs.unity-version }} + + - name: Build without Sentry SDK + if: steps.cache-build-nosentry.outputs.cache-hit != 'true' + run: ./test/Scripts.Integration.Test/build-project.ps1 -UnityPath "$env:UNITY_PATH" -Platform Windows -CheckSymbols:$false -BuildDirName "Build-NoSentry" + + - name: Download UPM package + uses: vaind/download-artifact@e7141b6a94ef28aa3d828b52830cfa1f406a1848 # v4-with-wait-timeout + with: + name: package-release + wait-timeout: 3600 + + - name: Extract UPM package + run: ./test/Scripts.Integration.Test/extract-package.ps1 + + - name: Add Sentry to the project + 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 Windows -TestMode "integration" + env: + SENTRY_DSN: ${{ secrets.SENTRY_TEST_DSN }} + + - name: Build with Sentry SDK + run: ./test/Scripts.Integration.Test/build-project.ps1 -UnityPath "$env:UNITY_PATH" -Platform Windows -CheckSymbols:$false -UnityVersion "$env:UNITY_VERSION" + + - name: Compare build sizes + run: ./test/Scripts.Integration.Test/measure-build-size.ps1 -Path1 "samples/IntegrationTest/Build-NoSentry" -Path2 "samples/IntegrationTest/Build" -Platform Windows -UnityVersion "$env:UNITY_VERSION" + + - name: Upload build size measurement + uses: actions/upload-artifact@ea165f8d65b6e75b540449e92b4886f43607fa02 # v4 + with: + name: build-size-Windows-${{ env.UNITY_VERSION }} + path: build-size-measurements/*.json + retention-days: 1 + + # We create tar explicitly because upload-artifact is slow for many files. + - name: Create archive + run: | + Remove-Item -Recurse -Force samples/IntegrationTest/Build/*_BackUpThisFolder_ButDontShipItWithYourGame -ErrorAction SilentlyContinue + tar -cvzf test-app-desktop.tar.gz samples/IntegrationTest/Build + + - name: Upload test app + uses: actions/upload-artifact@ea165f8d65b6e75b540449e92b4886f43607fa02 # v4 + with: + name: testapp-desktop-compiled-${{ env.UNITY_VERSION }}-windows + if-no-files-found: error + path: test-app-desktop.tar.gz + retention-days: 14 + + - name: Upload IntegrationTest project on failure + if: ${{ failure() }} + uses: actions/upload-artifact@ea165f8d65b6e75b540449e92b4886f43607fa02 # v4 + with: + name: failed-project-desktop-windows-${{ env.UNITY_VERSION }} + path: | + samples/IntegrationTest + unity.log + !samples/IntegrationTest/Build/*_BackUpThisFolder_ButDontShipItWithYourGame + retention-days: 14 diff --git a/.github/workflows/smoke-test-run-desktop.yml b/.github/workflows/smoke-test-run-linux.yml similarity index 64% rename from .github/workflows/smoke-test-run-desktop.yml rename to .github/workflows/smoke-test-run-linux.yml index 37c8866a9..87d728da9 100644 --- a/.github/workflows/smoke-test-run-desktop.yml +++ b/.github/workflows/smoke-test-run-linux.yml @@ -1,14 +1,10 @@ -name: "IntegrationTest: Run Desktop" +name: "IntegrationTest: Run Linux" on: workflow_call: inputs: unity-version: required: true type: string - os: - required: true - type: string - description: '"windows" or "linux"' defaults: run: @@ -16,8 +12,8 @@ defaults: jobs: run: - name: ${{ inputs.os }} ${{ inputs.unity-version }} - runs-on: ${{ inputs.os == 'windows' && 'windows-latest' || 'ubuntu-latest' }} + name: linux ${{ inputs.unity-version }} + runs-on: ubuntu-latest env: SENTRY_TEST_DSN: ${{ secrets.SENTRY_TEST_DSN }} SENTRY_AUTH_TOKEN: ${{ secrets.SENTRY_AUTH_TOKEN }} @@ -33,25 +29,16 @@ jobs: - name: Download test app artifact uses: actions/download-artifact@d3f86a106a0bac45b974a628896c90dbdf5c8093 # v4 with: - name: testapp-desktop-compiled-${{ inputs.unity-version }}-${{ inputs.os }} + name: testapp-desktop-compiled-${{ inputs.unity-version }}-linux - name: Extract test app run: tar -xvzf test-app-desktop.tar.gz - - name: Set executable permission (Linux) - if: ${{ inputs.os == 'linux' }} + - name: Set executable permission run: chmod +x samples/IntegrationTest/Build/test shell: bash - - name: Run Integration Tests (Windows) - if: ${{ inputs.os == 'windows' }} - timeout-minutes: 20 - run: | - $env:SENTRY_TEST_APP = "samples/IntegrationTest/Build/test.exe" - Invoke-Pester -Path test/IntegrationTest/Integration.Tests.Desktop.ps1 -CI - - - name: Run Integration Tests (Linux) - if: ${{ inputs.os == 'linux' }} + - name: Run Integration Tests timeout-minutes: 20 run: | xvfb-run pwsh -Command ' @@ -65,7 +52,7 @@ jobs: if: ${{ failure() }} uses: actions/upload-artifact@ea165f8d65b6e75b540449e92b4886f43607fa02 # v4 with: - name: testapp-desktop-logs-${{ inputs.os }}-${{ inputs.unity-version }} + name: testapp-desktop-logs-linux-${{ inputs.unity-version }} path: | test/IntegrationTest/results/ retention-days: 14 diff --git a/.github/workflows/smoke-test-run-windows.yml b/.github/workflows/smoke-test-run-windows.yml new file mode 100644 index 000000000..a264389e0 --- /dev/null +++ b/.github/workflows/smoke-test-run-windows.yml @@ -0,0 +1,50 @@ +name: "IntegrationTest: Run Windows" +on: + workflow_call: + inputs: + unity-version: + required: true + type: string + +defaults: + run: + shell: pwsh + +jobs: + run: + name: windows ${{ inputs.unity-version }} + runs-on: windows-latest + env: + SENTRY_TEST_DSN: ${{ secrets.SENTRY_TEST_DSN }} + SENTRY_AUTH_TOKEN: ${{ secrets.SENTRY_AUTH_TOKEN }} + + steps: + - name: Checkout + uses: actions/checkout@11bd71901bbe5b1630ceea73d27597364c9af683 # v4 + + - name: Initialize app-runner submodule + run: git submodule update --init modules/app-runner + shell: bash + + - name: Download test app artifact + uses: actions/download-artifact@d3f86a106a0bac45b974a628896c90dbdf5c8093 # v4 + with: + name: testapp-desktop-compiled-${{ inputs.unity-version }}-windows + + - name: Extract test app + run: tar -xvzf test-app-desktop.tar.gz + + - name: Run Integration Tests + timeout-minutes: 20 + run: | + $env:SENTRY_TEST_APP = "samples/IntegrationTest/Build/test.exe" + Invoke-Pester -Path test/IntegrationTest/Integration.Tests.Desktop.ps1 -CI + + - name: Upload test results on failure + if: ${{ failure() }} + uses: actions/upload-artifact@ea165f8d65b6e75b540449e92b4886f43607fa02 # v4 + with: + name: testapp-desktop-logs-windows-${{ inputs.unity-version }} + path: | + test/IntegrationTest/results/ + retention-days: 14 From 29b84a86ee5fd5be17daf5c66f2ad1d062d6c6ed Mon Sep 17 00:00:00 2001 From: bitsandfoxes Date: Wed, 4 Mar 2026 10:43:26 +0100 Subject: [PATCH 57/86] remove windows docker --- scripts/ci-docker-windows.ps1 | 60 ----------------------------------- 1 file changed, 60 deletions(-) delete mode 100644 scripts/ci-docker-windows.ps1 diff --git a/scripts/ci-docker-windows.ps1 b/scripts/ci-docker-windows.ps1 deleted file mode 100644 index fd6a0a917..000000000 --- a/scripts/ci-docker-windows.ps1 +++ /dev/null @@ -1,60 +0,0 @@ -param( - [Parameter(Mandatory=$true)] - [string]$UnityPrefix, - [Parameter(Mandatory=$true)] - [string]$ImageVariant, - [Parameter(Mandatory=$true)] - [string]$LicenseConfig -) - -$ErrorActionPreference = "Stop" - -$unityVersion = & pwsh ./scripts/ci-env.ps1 "unity$UnityPrefix" -$imageVariant = $ImageVariant.ToLower() - -$container = "unity" -$image = "unityci/editor:windows-$unityVersion-$imageVariant-3" -$cwd = if ($env:GITHUB_WORKSPACE) { $env:GITHUB_WORKSPACE } else { (Get-Location).Path } - -# Check for existing container -$existing = docker ps -a --filter "name=^/$container$" --format '{{.Names}}' -if ($existing -eq $container) { - Write-Host "Removing existing container '$container'" - docker stop $container - docker rm $container -} - -Write-Host "Starting up '$image' as '$container'" - -# Format: -- -$jobName = if ($env:GITHUB_JOB) { $env:GITHUB_JOB } else { "local" } -$runId = if ($env:GITHUB_RUN_ID) { $env:GITHUB_RUN_ID } else { "0" } -$uniqueHostname = "$jobName-$imageVariant-$runId" -replace '[_\s]', '-' - -docker run -td --name $container ` - --hostname $uniqueHostname ` - -v "${cwd}:C:\sentry-unity" ` - -e UNITY_VERSION=$unityVersion ` - -e GITHUB_ACTIONS=$env:GITHUB_ACTIONS ` - -e SENTRY_AUTH_TOKEN=$env:SENTRY_AUTH_TOKEN ` - --workdir "C:\sentry-unity" $image - -# Set up Unity license configuration -docker exec $container powershell -Command "New-Item -ItemType Directory -Path 'C:\ProgramData\Unity\config' -Force | Out-Null" -docker exec $container powershell -Command "Set-Content -Path 'C:\ProgramData\Unity\config\services-config.json' -Value '$LicenseConfig'" - -# Create unity-editor wrapper script to match the Linux Docker image convention. -# In Linux GameCI images, /usr/bin/unity-editor wraps Unity with -batchmode. -# We replicate that here so the same UNITY_PATH env var works for both platforms. -# GameCI Windows images set UNITY_PATH to the Unity install directory. -docker exec $container powershell -Command @' -$unityExe = Join-Path $env:UNITY_PATH 'Editor\Unity.exe' -if (-not (Test-Path $unityExe)) { throw "Unity.exe not found at $unityExe (UNITY_PATH=$env:UNITY_PATH)" } -$content = "@echo off`r`n`"$unityExe`" -batchmode -nographics %*" -Set-Content -Path 'C:\Windows\unity-editor.cmd' -Value $content -Write-Host "Created unity-editor wrapper pointing to $unityExe" -'@ -if ($LASTEXITCODE -ne 0) { throw "Failed to create unity-editor wrapper (exit code: $LASTEXITCODE)" } - -Write-Host "Container started successfully:" -docker ps --filter "name=^/$container$" From 336c1b6edfa253a793ea5261fc8fd2bcb2d4e514 Mon Sep 17 00:00:00 2001 From: bitsandfoxes Date: Wed, 4 Mar 2026 11:00:57 +0100 Subject: [PATCH 58/86] . --- .github/workflows/ci.yml | 8 ++++---- .github/workflows/smoke-test-build-linux.yml | 2 +- .github/workflows/smoke-test-build-windows.yml | 2 +- .github/workflows/smoke-test-run-linux.yml | 2 +- .github/workflows/smoke-test-run-windows.yml | 2 +- test/IntegrationTest/Integration.Tests.Desktop.ps1 | 8 ++------ 6 files changed, 10 insertions(+), 14 deletions(-) diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml index cab518fb4..53ad2c2e4 100644 --- a/.github/workflows/ci.yml +++ b/.github/workflows/ci.yml @@ -355,7 +355,7 @@ jobs: python3 scripts/smoke-test-webgl.py "samples/IntegrationTest/Build" smoke-test-build-linux: - name: Build linux ${{ matrix.unity-version }} Integration Test + name: Build Linux ${{ matrix.unity-version }} Integration Test if: ${{ !startsWith(github.ref, 'refs/heads/release/') }} needs: [smoke-test-create, create-unity-matrix] secrets: inherit @@ -368,7 +368,7 @@ jobs: unity-version: ${{ matrix.unity-version }} smoke-test-build-windows: - name: Build windows ${{ matrix.unity-version }} Integration Test + name: Build Windows ${{ matrix.unity-version }} Integration Test if: ${{ !startsWith(github.ref, 'refs/heads/release/') }} needs: [smoke-test-create, create-unity-matrix] secrets: inherit @@ -381,7 +381,7 @@ jobs: unity-version: ${{ matrix.unity-version }} smoke-test-run-linux: - name: Run linux ${{ matrix.unity-version }} Integration Test + name: Run Linux ${{ matrix.unity-version }} Integration Test if: ${{ !startsWith(github.ref, 'refs/heads/release/') }} needs: [smoke-test-build-linux, create-unity-matrix] secrets: inherit @@ -394,7 +394,7 @@ jobs: unity-version: ${{ matrix.unity-version }} smoke-test-run-windows: - name: Run windows ${{ matrix.unity-version }} Integration Test + name: Run Windows ${{ matrix.unity-version }} Integration Test if: ${{ !startsWith(github.ref, 'refs/heads/release/') }} needs: [smoke-test-build-windows, create-unity-matrix] secrets: inherit diff --git a/.github/workflows/smoke-test-build-linux.yml b/.github/workflows/smoke-test-build-linux.yml index 7d551d68b..39f095017 100644 --- a/.github/workflows/smoke-test-build-linux.yml +++ b/.github/workflows/smoke-test-build-linux.yml @@ -12,7 +12,7 @@ defaults: jobs: build: - name: linux ${{ inputs.unity-version }} + name: Linux ${{ inputs.unity-version }} runs-on: ubuntu-latest env: GITHUB_ACTOR: ${{ github.actor }} diff --git a/.github/workflows/smoke-test-build-windows.yml b/.github/workflows/smoke-test-build-windows.yml index 4b97b3efd..cd569b8df 100644 --- a/.github/workflows/smoke-test-build-windows.yml +++ b/.github/workflows/smoke-test-build-windows.yml @@ -12,7 +12,7 @@ defaults: jobs: build: - name: windows ${{ inputs.unity-version }} + name: Windows ${{ inputs.unity-version }} runs-on: windows-latest env: UNITY_VERSION: ${{ inputs.unity-version }} diff --git a/.github/workflows/smoke-test-run-linux.yml b/.github/workflows/smoke-test-run-linux.yml index 87d728da9..e82afa25b 100644 --- a/.github/workflows/smoke-test-run-linux.yml +++ b/.github/workflows/smoke-test-run-linux.yml @@ -12,7 +12,7 @@ defaults: jobs: run: - name: linux ${{ inputs.unity-version }} + name: Linux ${{ inputs.unity-version }} runs-on: ubuntu-latest env: SENTRY_TEST_DSN: ${{ secrets.SENTRY_TEST_DSN }} diff --git a/.github/workflows/smoke-test-run-windows.yml b/.github/workflows/smoke-test-run-windows.yml index a264389e0..db9b72665 100644 --- a/.github/workflows/smoke-test-run-windows.yml +++ b/.github/workflows/smoke-test-run-windows.yml @@ -12,7 +12,7 @@ defaults: jobs: run: - name: windows ${{ inputs.unity-version }} + name: Windows ${{ inputs.unity-version }} runs-on: windows-latest env: SENTRY_TEST_DSN: ${{ secrets.SENTRY_TEST_DSN }} diff --git a/test/IntegrationTest/Integration.Tests.Desktop.ps1 b/test/IntegrationTest/Integration.Tests.Desktop.ps1 index 15fdfa3c1..473d7d3c4 100644 --- a/test/IntegrationTest/Integration.Tests.Desktop.ps1 +++ b/test/IntegrationTest/Integration.Tests.Desktop.ps1 @@ -16,11 +16,7 @@ $ErrorActionPreference = "Stop" # Import shared test cases and utility functions . $PSScriptRoot/CommonTestCases.ps1 - BeforeAll { - # Auto-detect platform - $script:PlatformName = if ($IsWindows) { "Windows" } elseif ($IsLinux) { "Linux" } else { throw "Unsupported desktop platform" } - # Run integration test action on device function Invoke-TestAction { param ( @@ -70,7 +66,7 @@ BeforeAll { # Initialize test parameters $script:TestSetup = [PSCustomObject]@{ - Platform = $script:PlatformName + Platform = "Desktop" AppPath = $env:SENTRY_TEST_APP Dsn = $env:SENTRY_TEST_DSN AuthToken = $env:SENTRY_AUTH_TOKEN @@ -104,7 +100,7 @@ AfterAll { } -Describe "Unity $($script:PlatformName) Integration Tests" { +Describe "Unity Desktop Integration Tests" { Context "Message Capture" { BeforeAll { From 887c96bf0da38f424b27e3d5cbce970ba3ec04c2 Mon Sep 17 00:00:00 2001 From: bitsandfoxes Date: Wed, 4 Mar 2026 11:08:01 +0100 Subject: [PATCH 59/86] stoptheslop --- scripts/unity-utils.ps1 | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/scripts/unity-utils.ps1 b/scripts/unity-utils.ps1 index 9d3f9e65a..1c463fa35 100644 --- a/scripts/unity-utils.ps1 +++ b/scripts/unity-utils.ps1 @@ -14,7 +14,7 @@ function RunUnity([string] $unityPath, [string[]] $arguments, [switch] $ReturnLo { # Remove "-batchmode" and "-nographics" which end up being duplicate because the unity-editor wrapper already adds them Write-Host "Removing arguments '-batchmode' and '-nographics' - they would be duplicate and cause a build to fail" - $arguments = $arguments | Where-Object { $_ –ne "-batchmode" -and $_ -ne "-nographics" } + $arguments = $arguments | Where-Object { $_ -ne "-batchmode" -and $_ -ne "-nographics" } Write-Host "Updated arguments: $arguments" } ElseIf ($IsLinux -and "$env:XDG_CURRENT_DESKTOP" -eq "" -and $unityPath -ne "xvfb-run") From b533abff0ec8776dfefd79e806271f86e66c7d45 Mon Sep 17 00:00:00 2001 From: bitsandfoxes Date: Wed, 4 Mar 2026 11:35:42 +0100 Subject: [PATCH 60/86] routing log output to stdout --- .github/workflows/ci.yml | 2 +- test/IntegrationTest/Integration.Tests.Desktop.ps1 | 4 ++-- 2 files changed, 3 insertions(+), 3 deletions(-) diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml index 53ad2c2e4..e9c0f1526 100644 --- a/.github/workflows/ci.yml +++ b/.github/workflows/ci.yml @@ -99,7 +99,7 @@ jobs: unity-version: ${{ matrix.unity-version }} # A Linux, docker-based build to prepare a WebGL player. The tests run in `smoke-test-run`. - smoke-test-build: + smoke-test-build-webgl: name: Build ${{ matrix.platform }} ${{ matrix.unity-version }} Smoke Test if: ${{ !startsWith(github.ref, 'refs/heads/release/') }} needs: [smoke-test-create, create-unity-matrix] diff --git a/test/IntegrationTest/Integration.Tests.Desktop.ps1 b/test/IntegrationTest/Integration.Tests.Desktop.ps1 index 473d7d3c4..11b3e82e6 100644 --- a/test/IntegrationTest/Integration.Tests.Desktop.ps1 +++ b/test/IntegrationTest/Integration.Tests.Desktop.ps1 @@ -26,7 +26,7 @@ BeforeAll { Write-Host "Running $Action..." - $appArgs = @("--test", $Action) + $appArgs = @("--test", $Action, "-logFile", "-") $runResult = Invoke-DeviceApp -ExecutablePath $env:SENTRY_TEST_APP -Arguments $appArgs @@ -37,7 +37,7 @@ BeforeAll { if ($Action -eq "crash-capture") { Write-Host "Running crash-send to ensure crash report is sent..." - $sendArgs = @("--test", "crash-send") + $sendArgs = @("--test", "crash-send", "-logFile", "-") $sendResult = Invoke-DeviceApp -ExecutablePath $env:SENTRY_TEST_APP -Arguments $sendArgs # Save crash-send result to JSON for debugging From 923c1cfd0a55c6807a88f25c399011fc6f00410f Mon Sep 17 00:00:00 2001 From: bitsandfoxes Date: Wed, 4 Mar 2026 11:45:26 +0100 Subject: [PATCH 61/86] upsi --- .github/workflows/ci.yml | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml index e9c0f1526..252d8f8da 100644 --- a/.github/workflows/ci.yml +++ b/.github/workflows/ci.yml @@ -325,10 +325,10 @@ jobs: # Also make sure to match the versions available here: # - https://github.com/actions/runner-images/blob/main/images/macos/macos-14-Readme.md - smoke-test-run: + smoke-test-run-webgl: name: Run ${{ matrix.platform }} ${{ matrix.unity-version }} Smoke Test if: ${{ !startsWith(github.ref, 'refs/heads/release/') }} - needs: [smoke-test-build, create-unity-matrix] + needs: [smoke-test-build-webgl, create-unity-matrix] runs-on: ubuntu-latest strategy: fail-fast: false @@ -410,7 +410,7 @@ jobs: name: Build Size runs-on: ubuntu-latest if: ${{ !startsWith(github.ref, 'refs/heads/release/') }} - needs: [smoke-test-build, smoke-test-build-android, smoke-test-compile-ios, smoke-test-build-linux, smoke-test-build-windows] + needs: [smoke-test-build-webgl, smoke-test-build-android, smoke-test-compile-ios, smoke-test-build-linux, smoke-test-build-windows] steps: - name: Checkout uses: actions/checkout@v3 From f5ffb4173cd37c2557c3a78953728af96e41790a Mon Sep 17 00:00:00 2001 From: bitsandfoxes Date: Wed, 4 Mar 2026 12:39:22 +0100 Subject: [PATCH 62/86] run the app directly --- .github/workflows/smoke-test-run-linux.yml | 2 - .../Integration.Tests.Desktop.ps1 | 43 ++++++++++++------- 2 files changed, 28 insertions(+), 17 deletions(-) diff --git a/.github/workflows/smoke-test-run-linux.yml b/.github/workflows/smoke-test-run-linux.yml index e82afa25b..476d31666 100644 --- a/.github/workflows/smoke-test-run-linux.yml +++ b/.github/workflows/smoke-test-run-linux.yml @@ -43,8 +43,6 @@ jobs: run: | xvfb-run pwsh -Command ' $env:SENTRY_TEST_APP = "samples/IntegrationTest/Build/test"; - $env:SENTRY_TEST_DSN = "${{ secrets.SENTRY_TEST_DSN }}"; - $env:SENTRY_AUTH_TOKEN = "${{ secrets.SENTRY_AUTH_TOKEN }}"; Invoke-Pester -Path test/IntegrationTest/Integration.Tests.Desktop.ps1 -CI' shell: bash diff --git a/test/IntegrationTest/Integration.Tests.Desktop.ps1 b/test/IntegrationTest/Integration.Tests.Desktop.ps1 index 11b3e82e6..f98f39de4 100644 --- a/test/IntegrationTest/Integration.Tests.Desktop.ps1 +++ b/test/IntegrationTest/Integration.Tests.Desktop.ps1 @@ -17,7 +17,9 @@ $ErrorActionPreference = "Stop" . $PSScriptRoot/CommonTestCases.ps1 BeforeAll { - # Run integration test action on device + # Run a test app action using Start-Process with file-based logging. + # We avoid piping stdout because on Windows it kills crashpad_handler.exe + # when the process crashes, preventing native crash capture. function Invoke-TestAction { param ( [Parameter(Mandatory=$true)] @@ -26,30 +28,44 @@ BeforeAll { Write-Host "Running $Action..." - $appArgs = @("--test", $Action, "-logFile", "-") + $resultsDir = Resolve-Path "$PSScriptRoot/results/" + $logFile = Join-Path $resultsDir "$Action-player.log" + $appArgs = @("--test", $Action, "-logFile", $logFile) - $runResult = Invoke-DeviceApp -ExecutablePath $env:SENTRY_TEST_APP -Arguments $appArgs + $process = Start-Process $env:SENTRY_TEST_APP -ArgumentList $appArgs -PassThru + $process | Wait-Process -Timeout 60 -ErrorAction SilentlyContinue + + if (-not $process.HasExited) { + $process | Stop-Process -Force + } + + $output = @(Get-Content $logFile -ErrorAction SilentlyContinue) + + $runResult = @{ + Output = $output + ExitCode = $process.ExitCode + } # 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 + # For crash tests: relaunch the app to flush the cached crash report if ($Action -eq "crash-capture") { Write-Host "Running crash-send to ensure crash report is sent..." - $sendArgs = @("--test", "crash-send", "-logFile", "-") - $sendResult = Invoke-DeviceApp -ExecutablePath $env:SENTRY_TEST_APP -Arguments $sendArgs + $sendLogFile = Join-Path $resultsDir "crash-send-player.log" + $sendProcess = Start-Process $env:SENTRY_TEST_APP ` + -ArgumentList "--test", "crash-send", "-logFile", $sendLogFile ` + -PassThru + $sendProcess | Wait-Process -Timeout 60 -ErrorAction SilentlyContinue - # Save crash-send result to JSON for debugging - $sendResult | ConvertTo-Json -Depth 5 | Out-File -FilePath (Get-OutputFilePath "crash-send-result.json") + $sendOutput = @(Get-Content $sendLogFile -ErrorAction SilentlyContinue) - # Print crash-send output Write-Host "::group::App output (crash-send)" - $sendResult.Output | ForEach-Object { Write-Host $_ } + $sendOutput | ForEach-Object { Write-Host $_ } Write-Host "::endgroup::" - # Attach to runResult for test access - $runResult | Add-Member -NotePropertyName "CrashSendOutput" -NotePropertyValue $sendResult.Output + $runResult.CrashSendOutput = $sendOutput } # Print app output so it's visible in CI logs @@ -89,14 +105,11 @@ BeforeAll { Connect-SentryApi ` -ApiToken $script:TestSetup.AuthToken ` -DSN $script:TestSetup.Dsn - - Connect-Device -Platform "Local" } AfterAll { Disconnect-SentryApi - Disconnect-Device } From d741d70d6930b444cdbf360685511566e1bf65ac Mon Sep 17 00:00:00 2001 From: bitsandfoxes Date: Wed, 4 Mar 2026 13:18:42 +0100 Subject: [PATCH 63/86] crashtype --- test/Scripts.Integration.Test/Scripts/IntegrationTester.cs | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/test/Scripts.Integration.Test/Scripts/IntegrationTester.cs b/test/Scripts.Integration.Test/Scripts/IntegrationTester.cs index 94e6423b6..c03c43f77 100644 --- a/test/Scripts.Integration.Test/Scripts/IntegrationTester.cs +++ b/test/Scripts.Integration.Test/Scripts/IntegrationTester.cs @@ -114,9 +114,9 @@ private IEnumerator CrashCapture() yield return new WaitForSeconds(0.5f); Debug.Log($"EVENT_CAPTURED: {crashId}"); - Debug.Log("CRASH TEST: Issuing a native crash (Abort)"); + Debug.Log("CRASH TEST: Issuing a native crash (AccessViolation)"); - Utils.ForceCrash(ForcedCrashCategory.Abort); + Utils.ForceCrash(ForcedCrashCategory.AccessViolation); // Should not reach here Debug.LogError("CRASH TEST: FAIL - unexpected code executed after crash"); From 6dff615770e978c2b43d58e8358ca36e106738a3 Mon Sep 17 00:00:00 2001 From: bitsandfoxes Date: Wed, 4 Mar 2026 13:56:26 +0100 Subject: [PATCH 64/86] cleanup --- .../Integration.Tests.Desktop.ps1 | 43 +++++++------------ 1 file changed, 15 insertions(+), 28 deletions(-) diff --git a/test/IntegrationTest/Integration.Tests.Desktop.ps1 b/test/IntegrationTest/Integration.Tests.Desktop.ps1 index f98f39de4..11b3e82e6 100644 --- a/test/IntegrationTest/Integration.Tests.Desktop.ps1 +++ b/test/IntegrationTest/Integration.Tests.Desktop.ps1 @@ -17,9 +17,7 @@ $ErrorActionPreference = "Stop" . $PSScriptRoot/CommonTestCases.ps1 BeforeAll { - # Run a test app action using Start-Process with file-based logging. - # We avoid piping stdout because on Windows it kills crashpad_handler.exe - # when the process crashes, preventing native crash capture. + # Run integration test action on device function Invoke-TestAction { param ( [Parameter(Mandatory=$true)] @@ -28,44 +26,30 @@ BeforeAll { Write-Host "Running $Action..." - $resultsDir = Resolve-Path "$PSScriptRoot/results/" - $logFile = Join-Path $resultsDir "$Action-player.log" - $appArgs = @("--test", $Action, "-logFile", $logFile) + $appArgs = @("--test", $Action, "-logFile", "-") - $process = Start-Process $env:SENTRY_TEST_APP -ArgumentList $appArgs -PassThru - $process | Wait-Process -Timeout 60 -ErrorAction SilentlyContinue - - if (-not $process.HasExited) { - $process | Stop-Process -Force - } - - $output = @(Get-Content $logFile -ErrorAction SilentlyContinue) - - $runResult = @{ - Output = $output - ExitCode = $process.ExitCode - } + $runResult = Invoke-DeviceApp -ExecutablePath $env:SENTRY_TEST_APP -Arguments $appArgs # Save result to JSON file $runResult | ConvertTo-Json -Depth 5 | Out-File -FilePath (Get-OutputFilePath "${Action}-result.json") - # For crash tests: relaunch the app to flush the cached crash report + # 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..." - $sendLogFile = Join-Path $resultsDir "crash-send-player.log" - $sendProcess = Start-Process $env:SENTRY_TEST_APP ` - -ArgumentList "--test", "crash-send", "-logFile", $sendLogFile ` - -PassThru - $sendProcess | Wait-Process -Timeout 60 -ErrorAction SilentlyContinue + $sendArgs = @("--test", "crash-send", "-logFile", "-") + $sendResult = Invoke-DeviceApp -ExecutablePath $env:SENTRY_TEST_APP -Arguments $sendArgs - $sendOutput = @(Get-Content $sendLogFile -ErrorAction SilentlyContinue) + # Save crash-send result to JSON for debugging + $sendResult | ConvertTo-Json -Depth 5 | Out-File -FilePath (Get-OutputFilePath "crash-send-result.json") + # Print crash-send output Write-Host "::group::App output (crash-send)" - $sendOutput | ForEach-Object { Write-Host $_ } + $sendResult.Output | ForEach-Object { Write-Host $_ } Write-Host "::endgroup::" - $runResult.CrashSendOutput = $sendOutput + # Attach to runResult for test access + $runResult | Add-Member -NotePropertyName "CrashSendOutput" -NotePropertyValue $sendResult.Output } # Print app output so it's visible in CI logs @@ -105,11 +89,14 @@ BeforeAll { Connect-SentryApi ` -ApiToken $script:TestSetup.AuthToken ` -DSN $script:TestSetup.Dsn + + Connect-Device -Platform "Local" } AfterAll { Disconnect-SentryApi + Disconnect-Device } From cc11c6c1a3b26f4da2afe2fa6323d637b33e83f6 Mon Sep 17 00:00:00 2001 From: bitsandfoxes Date: Wed, 4 Mar 2026 14:06:16 +0100 Subject: [PATCH 65/86] cleanup --- scripts/unity-utils.ps1 | 10 ++++------ test/Scripts.Integration.Test/globals.ps1 | 5 ++--- 2 files changed, 6 insertions(+), 9 deletions(-) diff --git a/scripts/unity-utils.ps1 b/scripts/unity-utils.ps1 index 1c463fa35..69f77d3d4 100644 --- a/scripts/unity-utils.ps1 +++ b/scripts/unity-utils.ps1 @@ -12,9 +12,9 @@ function RunUnity([string] $unityPath, [string[]] $arguments, [switch] $ReturnLo If ($unityPath -eq "docker") { - # Remove "-batchmode" and "-nographics" which end up being duplicate because the unity-editor wrapper already adds them - Write-Host "Removing arguments '-batchmode' and '-nographics' - they would be duplicate and cause a build to fail" - $arguments = $arguments | Where-Object { $_ -ne "-batchmode" -and $_ -ne "-nographics" } + # Remove "-batchmode" which ends up being duplicate because the referenced unity-editor script already adds it + Write-Host "Removing argument '-batchmode' - it would be duplicate and cause a build to fail" + $arguments = $arguments | Where-Object { $_ –ne "-batchmode" } Write-Host "Updated arguments: $arguments" } ElseIf ($IsLinux -and "$env:XDG_CURRENT_DESKTOP" -eq "" -and $unityPath -ne "xvfb-run") @@ -52,9 +52,7 @@ function RunUnity([string] $unityPath, [string[]] $arguments, [switch] $ReturnLo ClearUnityLog $logFilePath New-Item $logFilePath > $null - # -NoNewWindow ensures stdout/stderr go to the parent console (visible in CI logs). - # Without it, Windows creates a hidden window and all Docker/process output is lost. - $process = Start-Process -FilePath $unityPath -ArgumentList $arguments -NoNewWindow -PassThru + $process = Start-Process -FilePath $unityPath -ArgumentList $arguments -PassThru $stdout = WaitForUnityExit $logFilePath $process } diff --git a/test/Scripts.Integration.Test/globals.ps1 b/test/Scripts.Integration.Test/globals.ps1 index 47c290895..82e7f7e4a 100644 --- a/test/Scripts.Integration.Test/globals.ps1 +++ b/test/Scripts.Integration.Test/globals.ps1 @@ -178,9 +178,8 @@ function RunUnityCustom([string] $unityPath, [string[]] $arguments, [switch] $Re If ($unityPath.StartsWith("docker ")) { # Fix paths (they're supposed to be the current working directory in the docker container) - $containerRoot = $IsWindows ? "C:\sentry-unity" : "/sentry-unity" - Write-Detail "Replacing project root ($(ProjectRoot)) with $containerRoot in docker arguments" - $arguments = $arguments | ForEach-Object { $_.Replace("$(ProjectRoot)", $containerRoot) } + Write-Detail "Replacing project root ($(ProjectRoot)) in docker arguments" + $arguments = $arguments | ForEach-Object { $_.Replace("$(ProjectRoot)", "/sentry-unity") } } return RunUnity $unityPath $arguments -ReturnLogOutput:$ReturnLogOutput From dca01a5bf984578b136ab2e347531e358178c6d9 Mon Sep 17 00:00:00 2001 From: bitsandfoxes Date: Wed, 4 Mar 2026 14:18:45 +0100 Subject: [PATCH 66/86] merged *-run-* for desktop --- .github/workflows/ci.yml | 6 ++- ...n-linux.yml => smoke-test-run-desktop.yml} | 25 +++++++--- .github/workflows/smoke-test-run-windows.yml | 50 ------------------- 3 files changed, 23 insertions(+), 58 deletions(-) rename .github/workflows/{smoke-test-run-linux.yml => smoke-test-run-desktop.yml} (63%) delete mode 100644 .github/workflows/smoke-test-run-windows.yml diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml index 252d8f8da..1fe00b29a 100644 --- a/.github/workflows/ci.yml +++ b/.github/workflows/ci.yml @@ -389,9 +389,10 @@ jobs: fail-fast: false matrix: unity-version: ${{ fromJSON(needs.create-unity-matrix.outputs.unity-matrix).unity-version }} - uses: ./.github/workflows/smoke-test-run-linux.yml + uses: ./.github/workflows/smoke-test-run-desktop.yml with: unity-version: ${{ matrix.unity-version }} + platform: linux smoke-test-run-windows: name: Run Windows ${{ matrix.unity-version }} Integration Test @@ -402,9 +403,10 @@ jobs: fail-fast: false matrix: unity-version: ${{ fromJSON(needs.create-unity-matrix.outputs.unity-matrix).unity-version }} - uses: ./.github/workflows/smoke-test-run-windows.yml + uses: ./.github/workflows/smoke-test-run-desktop.yml with: unity-version: ${{ matrix.unity-version }} + platform: windows build-size-summary: name: Build Size diff --git a/.github/workflows/smoke-test-run-linux.yml b/.github/workflows/smoke-test-run-desktop.yml similarity index 63% rename from .github/workflows/smoke-test-run-linux.yml rename to .github/workflows/smoke-test-run-desktop.yml index 476d31666..2049caf26 100644 --- a/.github/workflows/smoke-test-run-linux.yml +++ b/.github/workflows/smoke-test-run-desktop.yml @@ -1,10 +1,14 @@ -name: "IntegrationTest: Run Linux" +name: "IntegrationTest: Run Desktop" on: workflow_call: inputs: unity-version: required: true type: string + platform: + required: true + type: string + description: "linux or windows" defaults: run: @@ -12,8 +16,8 @@ defaults: jobs: run: - name: Linux ${{ inputs.unity-version }} - runs-on: ubuntu-latest + name: ${{ inputs.platform }} ${{ inputs.unity-version }} + runs-on: ${{ inputs.platform == 'linux' && 'ubuntu-latest' || 'windows-latest' }} env: SENTRY_TEST_DSN: ${{ secrets.SENTRY_TEST_DSN }} SENTRY_AUTH_TOKEN: ${{ secrets.SENTRY_AUTH_TOKEN }} @@ -29,16 +33,18 @@ jobs: - name: Download test app artifact uses: actions/download-artifact@d3f86a106a0bac45b974a628896c90dbdf5c8093 # v4 with: - name: testapp-desktop-compiled-${{ inputs.unity-version }}-linux + name: testapp-desktop-compiled-${{ inputs.unity-version }}-${{ inputs.platform }} - name: Extract test app run: tar -xvzf test-app-desktop.tar.gz - name: Set executable permission + if: inputs.platform == 'linux' run: chmod +x samples/IntegrationTest/Build/test shell: bash - - name: Run Integration Tests + - name: Run Integration Tests (Linux) + if: inputs.platform == 'linux' timeout-minutes: 20 run: | xvfb-run pwsh -Command ' @@ -46,11 +52,18 @@ jobs: Invoke-Pester -Path test/IntegrationTest/Integration.Tests.Desktop.ps1 -CI' shell: bash + - name: Run Integration Tests (Windows) + if: inputs.platform == 'windows' + timeout-minutes: 20 + run: | + $env:SENTRY_TEST_APP = "samples/IntegrationTest/Build/test.exe" + Invoke-Pester -Path test/IntegrationTest/Integration.Tests.Desktop.ps1 -CI + - name: Upload test results on failure if: ${{ failure() }} uses: actions/upload-artifact@ea165f8d65b6e75b540449e92b4886f43607fa02 # v4 with: - name: testapp-desktop-logs-linux-${{ inputs.unity-version }} + name: testapp-desktop-logs-${{ inputs.platform }}-${{ inputs.unity-version }} path: | test/IntegrationTest/results/ retention-days: 14 diff --git a/.github/workflows/smoke-test-run-windows.yml b/.github/workflows/smoke-test-run-windows.yml deleted file mode 100644 index db9b72665..000000000 --- a/.github/workflows/smoke-test-run-windows.yml +++ /dev/null @@ -1,50 +0,0 @@ -name: "IntegrationTest: Run Windows" -on: - workflow_call: - inputs: - unity-version: - required: true - type: string - -defaults: - run: - shell: pwsh - -jobs: - run: - name: Windows ${{ inputs.unity-version }} - runs-on: windows-latest - env: - SENTRY_TEST_DSN: ${{ secrets.SENTRY_TEST_DSN }} - SENTRY_AUTH_TOKEN: ${{ secrets.SENTRY_AUTH_TOKEN }} - - steps: - - name: Checkout - uses: actions/checkout@11bd71901bbe5b1630ceea73d27597364c9af683 # v4 - - - name: Initialize app-runner submodule - run: git submodule update --init modules/app-runner - shell: bash - - - name: Download test app artifact - uses: actions/download-artifact@d3f86a106a0bac45b974a628896c90dbdf5c8093 # v4 - with: - name: testapp-desktop-compiled-${{ inputs.unity-version }}-windows - - - name: Extract test app - run: tar -xvzf test-app-desktop.tar.gz - - - name: Run Integration Tests - timeout-minutes: 20 - run: | - $env:SENTRY_TEST_APP = "samples/IntegrationTest/Build/test.exe" - Invoke-Pester -Path test/IntegrationTest/Integration.Tests.Desktop.ps1 -CI - - - name: Upload test results on failure - if: ${{ failure() }} - uses: actions/upload-artifact@ea165f8d65b6e75b540449e92b4886f43607fa02 # v4 - with: - name: testapp-desktop-logs-windows-${{ inputs.unity-version }} - path: | - test/IntegrationTest/results/ - retention-days: 14 From d82cd1ea71fa65e90ae55e4486d8d404eb81ca5d Mon Sep 17 00:00:00 2001 From: bitsandfoxes Date: Wed, 4 Mar 2026 15:58:43 +0100 Subject: [PATCH 67/86] webgl --- .github/workflows/ci.yml | 36 +--- .github/workflows/smoke-test-run-webgl.yml | 54 ++++++ .../Integration.Tests.WebGL.ps1 | 181 ++++++++++++++++++ test/IntegrationTest/webgl-server.py | 149 ++++++++++++++ .../Scripts/IntegrationTester.cs | 10 + 5 files changed, 404 insertions(+), 26 deletions(-) create mode 100644 .github/workflows/smoke-test-run-webgl.yml create mode 100644 test/IntegrationTest/Integration.Tests.WebGL.ps1 create mode 100644 test/IntegrationTest/webgl-server.py diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml index 1fe00b29a..6ec9ffc22 100644 --- a/.github/workflows/ci.yml +++ b/.github/workflows/ci.yml @@ -189,9 +189,10 @@ 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 "$env:BUILD_PLATFORM" -CheckSymbols + run: ./test/Scripts.Integration.Test/configure-sentry.ps1 -UnityPath "$env:UNITY_PATH" -Platform "$env:BUILD_PLATFORM" -CheckSymbols -TestMode "integration" env: BUILD_PLATFORM: ${{ matrix.build_platform }} + SENTRY_DSN: ${{ secrets.SENTRY_TEST_DSN }} - name: Build Project run: ./test/Scripts.Integration.Test/build-project.ps1 -UnityPath "$env:UNITY_PATH" -Platform "$env:BUILD_PLATFORM" -CheckSymbols:$([System.Convert]::ToBoolean($env:CHECK_SYMBOLS)) -UnityVersion "$env:UNITY_VERSION" @@ -219,15 +220,14 @@ jobs: run: | # Note: remove local.properties file that contains Android SDK & NDK paths in the Unity installation. rm -rf samples/IntegrationTest/Build/*_BackUpThisFolder_ButDontShipItWithYourGame - tar -cvzf test-app-runtime.tar.gz samples/IntegrationTest/Build + tar -cvzf test-app-webgl.tar.gz samples/IntegrationTest/Build - # Upload runtime initialization build - name: Upload test app uses: actions/upload-artifact@ea165f8d65b6e75b540449e92b4886f43607fa02 # v4 with: - name: testapp-${{ matrix.platform }}-${{ matrix.unity-version }}-runtime + name: testapp-webgl-compiled-${{ matrix.unity-version }} if-no-files-found: error - path: test-app-runtime.tar.gz + path: test-app-webgl.tar.gz retention-days: 14 - name: Upload IntegrationTest project on failure @@ -326,33 +326,17 @@ jobs: # - https://github.com/actions/runner-images/blob/main/images/macos/macos-14-Readme.md smoke-test-run-webgl: - name: Run ${{ matrix.platform }} ${{ matrix.unity-version }} Smoke Test + name: Run WebGL ${{ matrix.unity-version }} Integration Test if: ${{ !startsWith(github.ref, 'refs/heads/release/') }} needs: [smoke-test-build-webgl, create-unity-matrix] - runs-on: ubuntu-latest + secrets: inherit strategy: fail-fast: false matrix: unity-version: ${{ fromJSON(needs.create-unity-matrix.outputs.unity-matrix).unity-version }} - platform: ["WebGL"] - steps: - - name: Checkout - uses: actions/checkout@v3 - - - name: Download test app artifact - uses: actions/download-artifact@d3f86a106a0bac45b974a628896c90dbdf5c8093 # v4 - id: download - with: - name: testapp-${{ matrix.platform }}-${{ matrix.unity-version }}-runtime - - - name: Extract test app - run: tar -xvzf test-app-runtime.tar.gz - - - name: Run (WebGL) - timeout-minutes: 10 - run: | - pip3 install --upgrade --user selenium urllib3 requests - python3 scripts/smoke-test-webgl.py "samples/IntegrationTest/Build" + uses: ./.github/workflows/smoke-test-run-webgl.yml + with: + unity-version: ${{ matrix.unity-version }} smoke-test-build-linux: name: Build Linux ${{ matrix.unity-version }} Integration Test diff --git a/.github/workflows/smoke-test-run-webgl.yml b/.github/workflows/smoke-test-run-webgl.yml new file mode 100644 index 000000000..441e6d61e --- /dev/null +++ b/.github/workflows/smoke-test-run-webgl.yml @@ -0,0 +1,54 @@ +name: "IntegrationTest: Run WebGL" +on: + workflow_call: + inputs: + unity-version: + required: true + type: string + +defaults: + run: + shell: pwsh + +jobs: + run: + name: WebGL ${{ inputs.unity-version }} + runs-on: ubuntu-latest + env: + SENTRY_TEST_DSN: ${{ secrets.SENTRY_TEST_DSN }} + SENTRY_AUTH_TOKEN: ${{ secrets.SENTRY_AUTH_TOKEN }} + + steps: + - name: Checkout + uses: actions/checkout@11bd71901bbe5b1630ceea73d27597364c9af683 # v4 + + - name: Initialize app-runner submodule + run: git submodule update --init modules/app-runner + shell: bash + + - name: Download test app artifact + uses: actions/download-artifact@d3f86a106a0bac45b974a628896c90dbdf5c8093 # v4 + with: + name: testapp-webgl-compiled-${{ inputs.unity-version }} + + - name: Extract test app + run: tar -xvzf test-app-webgl.tar.gz + + - name: Install Selenium + run: pip3 install --upgrade selenium + shell: bash + + - name: Run Integration Tests + timeout-minutes: 20 + run: | + $env:SENTRY_WEBGL_BUILD_PATH = "samples/IntegrationTest/Build" + Invoke-Pester -Path test/IntegrationTest/Integration.Tests.WebGL.ps1 -CI + + - name: Upload test results on failure + if: ${{ failure() }} + uses: actions/upload-artifact@ea165f8d65b6e75b540449e92b4886f43607fa02 # v4 + with: + name: testapp-webgl-logs-${{ inputs.unity-version }} + path: | + test/IntegrationTest/results/ + retention-days: 14 diff --git a/test/IntegrationTest/Integration.Tests.WebGL.ps1 b/test/IntegrationTest/Integration.Tests.WebGL.ps1 new file mode 100644 index 000000000..b1a207586 --- /dev/null +++ b/test/IntegrationTest/Integration.Tests.WebGL.ps1 @@ -0,0 +1,181 @@ +#!/usr/bin/env pwsh +# +# Integration tests for Sentry Unity SDK (WebGL) +# +# Environment variables: +# SENTRY_WEBGL_BUILD_PATH: path to the WebGL build directory +# SENTRY_TEST_DSN: test DSN +# SENTRY_AUTH_TOKEN: authentication token for Sentry API + +Set-StrictMode -Version latest +$ErrorActionPreference = "Stop" + +# Import app-runner modules +. $PSScriptRoot/../../modules/app-runner/import-modules.ps1 + +# Import shared test cases and utility functions +. $PSScriptRoot/CommonTestCases.ps1 + +BeforeAll { + # Run integration test action via WebGL (HTTP server + headless Chrome) + function Invoke-TestAction { + param ( + [Parameter(Mandatory=$true)] + [string]$Action + ) + + Write-Host "Running $Action..." + + $serverScript = Join-Path $PSScriptRoot "webgl-server.py" + $buildPath = $env:SENTRY_WEBGL_BUILD_PATH + $timeoutSeconds = 120 + + $process = Start-Process -FilePath "python3" ` + -ArgumentList @($serverScript, $buildPath, $Action, $timeoutSeconds) ` + -NoNewWindow -PassThru -RedirectStandardOutput "$PSScriptRoot/results/${Action}-stdout.txt" ` + -RedirectStandardError "$PSScriptRoot/results/${Action}-stderr.txt" + + $process | Wait-Process -Timeout ($timeoutSeconds + 30) + + $exitCode = $process.ExitCode + $stdoutContent = Get-Content "$PSScriptRoot/results/${Action}-stdout.txt" -Raw -ErrorAction SilentlyContinue + $stderrContent = Get-Content "$PSScriptRoot/results/${Action}-stderr.txt" -Raw -ErrorAction SilentlyContinue + + # Parse the JSON array of console lines from stdout + $output = @() + if ($stdoutContent) { + try { + $output = $stdoutContent | ConvertFrom-Json + } + catch { + Write-Host "Failed to parse webgl-server.py output as JSON: $_" + Write-Host "Raw stdout: $stdoutContent" + $output = @($stdoutContent) + } + } + + if ($stderrContent) { + Write-Host "::group::Server stderr ($Action)" + Write-Host $stderrContent + Write-Host "::endgroup::" + } + + $runResult = [PSCustomObject]@{ + Output = $output + ExitCode = $exitCode + } + + # Save result to JSON file + $runResult | ConvertTo-Json -Depth 5 | Out-File -FilePath (Get-OutputFilePath "${Action}-result.json") + + # Print app output so it's visible in CI logs + Write-Host "::group::Browser console output ($Action)" + $runResult.Output | ForEach-Object { Write-Host $_ } + Write-Host "::endgroup::" + + if ($exitCode -ne 0) { + Write-Warning "WebGL test action '$Action' did not complete (exit code: $exitCode)" + } + + 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]@{ + Platform = "WebGL" + BuildPath = $env:SENTRY_WEBGL_BUILD_PATH + Dsn = $env:SENTRY_TEST_DSN + AuthToken = $env:SENTRY_AUTH_TOKEN + } + + # Validate environment + if ([string]::IsNullOrEmpty($script:TestSetup.BuildPath)) { + throw "SENTRY_WEBGL_BUILD_PATH environment variable is not set." + } + if (-not (Test-Path $script:TestSetup.BuildPath)) { + throw "WebGL build not found at: $($script:TestSetup.BuildPath)" + } + 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 +} + + +AfterAll { + Disconnect-SentryApi +} + + +Describe "Unity WebGL Integration Tests" { + + Context "Message Capture" { + BeforeAll { + $script:runEvent = $null + $script:runResult = Invoke-TestAction -Action "message-capture" + + $eventId = Get-EventIds -AppOutput $script:runResult.Output -ExpectedCount 1 + if ($eventId) { + Write-Host "::group::Getting event content" + $script:runEvent = Get-SentryTestEvent -EventId "$eventId" + Write-Host "::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:runEvent = $null + $script:runResult = Invoke-TestAction -Action "exception-capture" + + $eventId = Get-EventIds -AppOutput $script:runResult.Output -ExpectedCount 1 + if ($eventId) { + Write-Host "::group::Getting event content" + $script:runEvent = Get-SentryTestEvent -EventId "$eventId" + Write-Host "::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" + } + } +} diff --git a/test/IntegrationTest/webgl-server.py b/test/IntegrationTest/webgl-server.py new file mode 100644 index 000000000..7ba887d33 --- /dev/null +++ b/test/IntegrationTest/webgl-server.py @@ -0,0 +1,149 @@ +#!/usr/bin/env python3 +""" +HTTP server for WebGL integration tests. + +Serves a Unity WebGL build with proper Brotli Content-Encoding headers, +launches headless Chrome to run the test, captures browser console output, +and waits for the INTEGRATION_TEST_COMPLETE signal. + +Usage: + python3 webgl-server.py [timeout-seconds] + +Prints captured browser console lines to stdout (one per line). +Exit code 0 if completion signal seen, 1 on timeout or error. +""" + +import json +import os +import sys +import time +from http import HTTPStatus +from http.server import SimpleHTTPRequestHandler, ThreadingHTTPServer +from threading import Thread + +from selenium import webdriver +from selenium.webdriver.chrome.options import Options + +HOST = "127.0.0.1" +PORT = 8000 + + +def create_handler(app_dir): + class Handler(SimpleHTTPRequestHandler): + def __init__(self, *args, **kwargs): + super().__init__(*args, directory=app_dir, **kwargs) + + def do_POST(self): + # Accept POST requests (Sentry envelope endpoint) and respond OK + content_length = int(self.headers.get("Content-Length", 0)) + self.rfile.read(content_length) + self.send_response(HTTPStatus.OK) + self.end_headers() + + def send_head(self): + path = self.translate_path(self.path) + if path.endswith(".br"): + try: + f = open(path, "rb") + except OSError: + self.send_error(HTTPStatus.NOT_FOUND, "File not found") + return None + ctype = self.guess_type(path[:-3]) + try: + fs = os.fstat(f.fileno()) + self.send_response(HTTPStatus.OK) + self.send_header("Content-Encoding", "br") + self.send_header("Content-type", ctype) + self.send_header("Content-Length", str(fs[6])) + self.send_header( + "Last-Modified", self.date_time_string(fs.st_mtime) + ) + self.end_headers() + return f + except Exception: + f.close() + raise + return super().send_head() + + def log_message(self, format, *args): + # Suppress request logging to keep output clean + pass + + return Handler + + +def run_test(app_dir, test_action, timeout_seconds): + # Start HTTP server + handler_class = create_handler(app_dir) + server = ThreadingHTTPServer((HOST, PORT), handler_class) + server_thread = Thread(target=server.serve_forever, daemon=True) + server_thread.start() + + # Small delay for server startup + time.sleep(0.5) + + # Launch headless Chrome + options = Options() + options.add_experimental_option("excludeSwitches", ["enable-logging"]) + options.add_argument("--headless") + options.add_argument("--no-sandbox") + options.add_argument("--disable-dev-shm-usage") + options.set_capability("goog:loggingPrefs", {"browser": "ALL"}) + + driver = webdriver.Chrome(options=options) + url = f"http://{HOST}:{PORT}?test={test_action}" + driver.get(url) + + collected_lines = [] + complete = False + start_time = time.time() + + try: + while time.time() - start_time < timeout_seconds: + for entry in driver.get_log("browser"): + msg = entry["message"] + # Chrome console messages are formatted as: "URL LINE:COL \"actual message\"" + # Extract the actual message content + quote_start = msg.find('"') + if quote_start >= 0: + msg = msg[quote_start:].strip('" ') + msg = msg.replace("\\n", "\n") + + collected_lines.append(msg) + + if "INTEGRATION_TEST_COMPLETE" in msg: + complete = True + + if complete: + # Give a brief moment for any final console messages + time.sleep(1) + for entry in driver.get_log("browser"): + msg = entry["message"] + quote_start = msg.find('"') + if quote_start >= 0: + msg = msg[quote_start:].strip('" ') + msg = msg.replace("\\n", "\n") + collected_lines.append(msg) + break + + time.sleep(0.5) + finally: + driver.quit() + server.shutdown() + + # Output collected lines as JSON array for easy parsing by PowerShell + print(json.dumps(collected_lines)) + + return 0 if complete else 1 + + +if __name__ == "__main__": + if len(sys.argv) < 3: + print(f"Usage: {sys.argv[0]} [timeout-seconds]", file=sys.stderr) + sys.exit(2) + + app_dir = sys.argv[1] + test_action = sys.argv[2] + timeout = int(sys.argv[3]) if len(sys.argv) > 3 else 60 + + sys.exit(run_test(app_dir, test_action, timeout)) diff --git a/test/Scripts.Integration.Test/Scripts/IntegrationTester.cs b/test/Scripts.Integration.Test/Scripts/IntegrationTester.cs index c03c43f77..65651ef11 100644 --- a/test/Scripts.Integration.Test/Scripts/IntegrationTester.cs +++ b/test/Scripts.Integration.Test/Scripts/IntegrationTester.cs @@ -30,7 +30,9 @@ public void Start() break; default: Debug.LogError($"IntegrationTester: Unknown command: {arg}"); +#if !UNITY_WEBGL Application.Quit(1); +#endif break; } } @@ -61,7 +63,11 @@ private void MessageCapture() var eventId = SentrySdk.CaptureMessage("Integration test message"); Debug.Log($"EVENT_CAPTURED: {eventId}"); + SentrySdk.FlushAsync(TimeSpan.FromSeconds(5)).GetAwaiter().GetResult(); + Debug.Log("INTEGRATION_TEST_COMPLETE"); +#if !UNITY_WEBGL Application.Quit(0); +#endif } private void ExceptionCapture() @@ -78,7 +84,11 @@ private void ExceptionCapture() Debug.Log($"EVENT_CAPTURED: {eventId}"); } + SentrySdk.FlushAsync(TimeSpan.FromSeconds(5)).GetAwaiter().GetResult(); + Debug.Log("INTEGRATION_TEST_COMPLETE"); +#if !UNITY_WEBGL Application.Quit(0); +#endif } // Use a deeper call stack with NoInlining to ensure Unity 2022's IL2CPP From 6ff3b4b27dd7aa4f2fe3b139347055e1e5577bb4 Mon Sep 17 00:00:00 2001 From: bitsandfoxes Date: Wed, 4 Mar 2026 16:53:09 +0100 Subject: [PATCH 68/86] os --- scripts/smoke-test-webgl.py | 216 ----------------------- test/IntegrationTest/CommonTestCases.ps1 | 5 + 2 files changed, 5 insertions(+), 216 deletions(-) delete mode 100644 scripts/smoke-test-webgl.py diff --git a/scripts/smoke-test-webgl.py b/scripts/smoke-test-webgl.py deleted file mode 100644 index c144983aa..000000000 --- a/scripts/smoke-test-webgl.py +++ /dev/null @@ -1,216 +0,0 @@ -#!/usr/bin/env python3 - -# Testing approach: -# 1. Start a web=server for pre-built WebGL app directory (index.html & co) and to collect the API requests -# 3. Run the smoke test using chromedriver -# 4. Check the messages received by the API server - -import datetime -import re -import sys -import time -import os -from http import HTTPStatus -from http.server import SimpleHTTPRequestHandler, ThreadingHTTPServer -from threading import Thread -from selenium import webdriver -from selenium.webdriver.chrome.options import Options -from threading import Thread, Lock - -host = '127.0.0.1' -port = 8000 -scriptDir = os.path.dirname(os.path.abspath(__file__)) - -if len(sys.argv) > 1: - appDir = sys.argv[1] -else: - appDir = os.path.join(scriptDir, '..', 'samples', - 'artifacts', 'builds', 'WebGL') - - -print("Using appDir:{}".format(appDir)) - -ignoreRegex = '"exception":{"values":\[{"type":"(' + '|'.join( - ['The resource [^ ]+ could not be loaded from the resource file!', 'GL.End requires material.SetPass before!']) + ')"' - - -class RequestVerifier: - __requests = [] - __testNumber = 0 - __lock = Lock() - - def Capture(self, info, body): - - # Note: this error seems to be related to *not* using https - we could probably use it by providing self-signed - # certificate when starting the http server. - # We would also have to add `options.add_argument('ignore-certificate-errors')` to the chromedriver setup. - match = re.search(ignoreRegex, body) - if match: - print( - "TEST: Skipping the received HTTP Request because it's an unrelated unity bug:\n{}".format(match.group(0))) - return - - self.__lock.acquire() - try: - print("TEST: Received HTTP Request #{} = {}\n{}".format( - len(self.__requests), info, body), flush=True) - self.__requests.append({"request": info, "body": body}) - finally: - self.__lock.release() - - def Expect(self, message, result): - self.__testNumber += 1 - info = "TEST | #{}. {}: {}".format(self.__testNumber, - message, "PASS" if result else "FAIL") - if result: - print(info, flush=True) - else: - raise Exception(info) - - def CheckMessage(self, index, substring, negate): - if len(self.__requests) <= index: - raise Exception('HTTP Request #{} not captured.'.format(index)) - - message = self.__requests[index]["body"] - contains = substring in message or substring.replace( - "'", "\"") in message - return contains if not negate else not contains - - def ExpectMessage(self, index, substring): - self.Expect("HTTP Request #{} contains \"{}\".".format( - index, substring), self.CheckMessage(index, substring, False)) - - def ExpectMessageNot(self, index, substring): - self.Expect("HTTP Request #{} doesn't contain \"{}\".".format( - index, substring), self.CheckMessage(index, substring, True)) - - -t = RequestVerifier() - - -class Handler(SimpleHTTPRequestHandler): - def __init__(self, *args, **kwargs): - super().__init__(*args, directory=appDir, **kwargs) - - def do_POST(self): - body = "" - content = self.rfile.read(int(self.headers['Content-Length'])) - parts = content.split(b'\n') - for part in parts: - try: - body += '\n' + part.decode("utf-8") - except: - body += '\n(binary chunk: {} bytes)'.format(len(part)) - t.Capture(self.requestline, body) - self.send_response(HTTPStatus.OK, '{'+'}') - self.end_headers() - - # Special handling for .br (brotli) - we must send "Content-Encoding: br" header. - # Therefore, we override `send_head()` with our custom implementation in that case - def send_head(self): - path = self.translate_path(self.path) - if path.endswith('.br'): - f = None - try: - f = open(path, 'rb') - except OSError: - self.send_error(HTTPStatus.NOT_FOUND, "File not found") - return None - - ctype = self.guess_type(path[:-3]) - try: - fs = os.fstat(f.fileno()) - self.send_response(HTTPStatus.OK) - self.send_header("Content-Encoding", 'br') - self.send_header("Content-type", ctype) - self.send_header("Content-Length", str(fs[6])) - self.send_header("Last-Modified", - self.date_time_string(fs.st_mtime)) - self.end_headers() - return f - except: - f.close() - raise - return super().send_head() - - -appServer = ThreadingHTTPServer((host, port), Handler) -appServerThread = Thread(target=appServer.serve_forever) -appServerThread.start() -time.sleep(1) - - -class TestDriver: - def __init__(self): - options = Options() - options.add_experimental_option('excludeSwitches', ['enable-logging']) - options.add_argument('--headless') - options.set_capability('goog:loggingPrefs', {'browser': 'ALL'}) - self.driver = webdriver.Chrome(options=options) - self.driver.get('http://{}:{}?test=smoke'.format(host, port)) - self.messages = [] - - def fetchMessages(self): - for entry in self.driver.get_log('browser'): - m = entry['message'] - entry['message'] = m[m.find('"'):].replace('\\n', '').strip('" ') - self.messages.append(entry) - - def hasMessage(self, message): - self.fetchMessages() - return any(message in entry['message'] for entry in self.messages) - - def dumpMessages(self): - self.fetchMessages() - for entry in self.messages: - print("CHROME: {} {}".format(datetime.datetime.fromtimestamp( - entry['timestamp']/1000).strftime('%H:%M:%S.%f'), entry['message']), flush=True) - - -def waitUntil(condition, interval=0.1, timeout=1): - start = time.time() - while not condition(): - if time.time() - start >= timeout: - raise Exception('Waiting timed out'.format(condition)) - time.sleep(interval) - - -driver = TestDriver() -try: - waitUntil(lambda: driver.hasMessage('SMOKE TEST: PASS'), timeout=10) -finally: - driver.dumpMessages() - driver.driver.quit() - appServer.shutdown() - - -# Verify received API requests - see SmokeTester.cs - this is a copy-paste with minimal syntax changes -currentMessage = 0 -t.ExpectMessage(currentMessage, "'type':'session'") -currentMessage += 1 -t.ExpectMessage(currentMessage, "'type':'event'") -t.ExpectMessage(currentMessage, "LogError(GUID)") -t.ExpectMessage(currentMessage, "'user':{'id':'") -# t.ExpectMessage( -# currentMessage, "'filename':'screenshot.jpg','attachment_type':'event.attachment'") -# t.ExpectMessageNot(currentMessage, "'length':0") -currentMessage += 1 -t.ExpectMessage(currentMessage, "'type':'event'") -t.ExpectMessage(currentMessage, "CaptureMessage(GUID)") -# t.ExpectMessage( -# currentMessage, "'filename':'screenshot.jpg','attachment_type':'event.attachment'") -# t.ExpectMessageNot(currentMessage, "'length':0") -currentMessage += 1 -t.ExpectMessage(currentMessage, "'type':'event'") -t.ExpectMessage( - currentMessage, "'message':'crumb','type':'error','data':{'foo':'bar'},'category':'bread','level':'fatal'}") -t.ExpectMessage(currentMessage, "'message':'scope-crumb'}") -t.ExpectMessage(currentMessage, "'extra':{'extra-key':42}") -t.ExpectMessage(currentMessage, "'tag-key':'tag-value'") -t.ExpectMessage( - currentMessage, "'user':{'id':'user-id','username':'username','email':'email@example.com','ip_address':'::1','other':{'role':'admin'}}") - -# t.ExpectMessage( -# currentMessage, "'filename':'screenshot.jpg','attachment_type':'event.attachment'") -# t.ExpectMessageNot(currentMessage, "'length':0") -print('TEST: PASS', flush=True) diff --git a/test/IntegrationTest/CommonTestCases.ps1 b/test/IntegrationTest/CommonTestCases.ps1 index 4f3aab1ea..3af169c25 100644 --- a/test/IntegrationTest/CommonTestCases.ps1 +++ b/test/IntegrationTest/CommonTestCases.ps1 @@ -100,6 +100,11 @@ $CommonTestCases = @( @{ Name = "Contains OS context"; TestBlock = { param($TestSetup, $TestType, $SentryEvent, $RunResult) $SentryEvent.contexts.os | Should -Not -BeNullOrEmpty + + if ($TestSetup.Platform -eq "WebGL") { + Set-ItResult -Skipped -Because "OS name is not available in the browser sandbox" + return + } $SentryEvent.contexts.os.name | Should -Not -BeNullOrEmpty } } From 3e8bf00e2234d6ac36333ad9bf897669509339c8 Mon Sep 17 00:00:00 2001 From: bitsandfoxes Date: Thu, 5 Mar 2026 13:46:59 +0100 Subject: [PATCH 69/86] review --- Directory.Build.targets | 2 +- test/Scripts.Integration.Test/integration-test.ps1 | 3 ++- 2 files changed, 3 insertions(+), 2 deletions(-) diff --git a/Directory.Build.targets b/Directory.Build.targets index 733cfff87..d2bd54fac 100644 --- a/Directory.Build.targets +++ b/Directory.Build.targets @@ -386,7 +386,7 @@ Related: https://forum.unity.com/threads/6572-debugger-agent-unable-to-listen-on - + diff --git a/test/Scripts.Integration.Test/integration-test.ps1 b/test/Scripts.Integration.Test/integration-test.ps1 index d6c1ddc5a..8d14be637 100644 --- a/test/Scripts.Integration.Test/integration-test.ps1 +++ b/test/Scripts.Integration.Test/integration-test.ps1 @@ -96,7 +96,8 @@ Else { ./scripts/smoke-test-ios.ps1 Test "latest" -IsIntegrationTest } "^WebGL$" { - python3 scripts/smoke-test-webgl.py $(GetNewProjectBuildPath) + $env:SENTRY_WEBGL_BUILD_PATH = GetNewProjectBuildPath + Invoke-Pester -Path test/IntegrationTest/Integration.Tests.WebGL.ps1 -CI } "^Switch$" { Write-PhaseSuccess "Switch build completed - no automated test execution available" From 28a6b5a4bccc9e90a6d80a4da5e91a9311627b1f Mon Sep 17 00:00:00 2001 From: bitsandfoxes Date: Thu, 5 Mar 2026 13:48:14 +0100 Subject: [PATCH 70/86] message parsing --- test/IntegrationTest/webgl-server.py | 27 +++++++++++++-------------- 1 file changed, 13 insertions(+), 14 deletions(-) diff --git a/test/IntegrationTest/webgl-server.py b/test/IntegrationTest/webgl-server.py index 7ba887d33..0b81cfbaf 100644 --- a/test/IntegrationTest/webgl-server.py +++ b/test/IntegrationTest/webgl-server.py @@ -72,6 +72,17 @@ def log_message(self, format, *args): return Handler +def parse_console_message(raw_msg): + """Extract the actual message from a Chrome console log entry. + + Chrome formats console messages as: 'URL LINE:COL "actual message"' + """ + quote_start = raw_msg.find('"') + if quote_start >= 0: + raw_msg = raw_msg[quote_start:].strip('" ') + return raw_msg.replace("\\n", "\n") + + def run_test(app_dir, test_action, timeout_seconds): # Start HTTP server handler_class = create_handler(app_dir) @@ -101,14 +112,7 @@ def run_test(app_dir, test_action, timeout_seconds): try: while time.time() - start_time < timeout_seconds: for entry in driver.get_log("browser"): - msg = entry["message"] - # Chrome console messages are formatted as: "URL LINE:COL \"actual message\"" - # Extract the actual message content - quote_start = msg.find('"') - if quote_start >= 0: - msg = msg[quote_start:].strip('" ') - msg = msg.replace("\\n", "\n") - + msg = parse_console_message(entry["message"]) collected_lines.append(msg) if "INTEGRATION_TEST_COMPLETE" in msg: @@ -118,12 +122,7 @@ def run_test(app_dir, test_action, timeout_seconds): # Give a brief moment for any final console messages time.sleep(1) for entry in driver.get_log("browser"): - msg = entry["message"] - quote_start = msg.find('"') - if quote_start >= 0: - msg = msg[quote_start:].strip('" ') - msg = msg.replace("\\n", "\n") - collected_lines.append(msg) + collected_lines.append(parse_console_message(entry["message"])) break time.sleep(0.5) From f13af9ca905ba0613a4a7d7a666c3aecab2a6f8c Mon Sep 17 00:00:00 2001 From: bitsandfoxes Date: Thu, 5 Mar 2026 13:49:00 +0100 Subject: [PATCH 71/86] log printing --- test/IntegrationTest/webgl-server.py | 6 ++++-- 1 file changed, 4 insertions(+), 2 deletions(-) diff --git a/test/IntegrationTest/webgl-server.py b/test/IntegrationTest/webgl-server.py index 0b81cfbaf..05246d464 100644 --- a/test/IntegrationTest/webgl-server.py +++ b/test/IntegrationTest/webgl-server.py @@ -130,8 +130,10 @@ def run_test(app_dir, test_action, timeout_seconds): driver.quit() server.shutdown() - # Output collected lines as JSON array for easy parsing by PowerShell - print(json.dumps(collected_lines)) + # Output collected lines as JSON array for easy parsing by PowerShell. + # This must be in the finally block so partial output is emitted even + # when the polling loop fails (e.g. Chrome crash). + print(json.dumps(collected_lines)) return 0 if complete else 1 From be0c49ab36fa55dadb8b068d57de0dee79371478 Mon Sep 17 00:00:00 2001 From: bitsandfoxes Date: Thu, 5 Mar 2026 14:17:45 +0100 Subject: [PATCH 72/86] spaces in paths --- Directory.Build.targets | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/Directory.Build.targets b/Directory.Build.targets index d2bd54fac..b1a65ad05 100644 --- a/Directory.Build.targets +++ b/Directory.Build.targets @@ -386,7 +386,7 @@ Related: https://forum.unity.com/threads/6572-debugger-agent-unable-to-listen-on - + From f38c1403300d07ba426c5c7bf3be8d8c92e49e29 Mon Sep 17 00:00:00 2001 From: bitsandfoxes Date: Thu, 5 Mar 2026 14:37:59 +0100 Subject: [PATCH 73/86] fixed webbackgroundworker --- src/Sentry.Unity/UnityWebRequestTransport.cs | 12 ++++++-- .../Scripts/IntegrationTester.cs | 29 ++++++++++++------- 2 files changed, 28 insertions(+), 13 deletions(-) diff --git a/src/Sentry.Unity/UnityWebRequestTransport.cs b/src/Sentry.Unity/UnityWebRequestTransport.cs index 467a5f92f..025884cba 100644 --- a/src/Sentry.Unity/UnityWebRequestTransport.cs +++ b/src/Sentry.Unity/UnityWebRequestTransport.cs @@ -15,6 +15,7 @@ internal class WebBackgroundWorker : IBackgroundWorker { private readonly SentryMonoBehaviour _behaviour; private readonly UnityWebRequestTransport _transport; + private int _pendingItems; public WebBackgroundWorker(SentryUnityOptions options, SentryMonoBehaviour behaviour) { @@ -24,13 +25,20 @@ public WebBackgroundWorker(SentryUnityOptions options, SentryMonoBehaviour behav public bool EnqueueEnvelope(Envelope envelope) { - _behaviour.QueueCoroutine(_transport.SendEnvelopeAsync(envelope)); + _pendingItems++; + _behaviour.QueueCoroutine(SendAndTrack(envelope)); return true; } + private IEnumerator SendAndTrack(Envelope envelope) + { + yield return _transport.SendEnvelopeAsync(envelope); + _pendingItems--; + } + public Task FlushAsync(TimeSpan timeout) => Task.CompletedTask; - public int QueuedItems { get; } + public int QueuedItems => _pendingItems; } internal class UnityWebRequestTransport : HttpTransportBase diff --git a/test/Scripts.Integration.Test/Scripts/IntegrationTester.cs b/test/Scripts.Integration.Test/Scripts/IntegrationTester.cs index 65651ef11..2c501b269 100644 --- a/test/Scripts.Integration.Test/Scripts/IntegrationTester.cs +++ b/test/Scripts.Integration.Test/Scripts/IntegrationTester.cs @@ -17,10 +17,10 @@ public void Start() switch (arg) { case "message-capture": - MessageCapture(); + StartCoroutine(MessageCapture()); break; case "exception-capture": - ExceptionCapture(); + StartCoroutine(ExceptionCapture()); break; case "crash-capture": StartCoroutine(CrashCapture()); @@ -56,21 +56,17 @@ private void AddIntegrationTestContext(string testType) SentrySdk.AddBreadcrumb("Context configuration finished"); } - private void MessageCapture() + private IEnumerator MessageCapture() { AddIntegrationTestContext("message-capture"); var eventId = SentrySdk.CaptureMessage("Integration test message"); Debug.Log($"EVENT_CAPTURED: {eventId}"); - SentrySdk.FlushAsync(TimeSpan.FromSeconds(5)).GetAwaiter().GetResult(); - Debug.Log("INTEGRATION_TEST_COMPLETE"); -#if !UNITY_WEBGL - Application.Quit(0); -#endif + yield return CompleteAndQuit(); } - private void ExceptionCapture() + private IEnumerator ExceptionCapture() { AddIntegrationTestContext("exception-capture"); @@ -84,10 +80,21 @@ private void ExceptionCapture() Debug.Log($"EVENT_CAPTURED: {eventId}"); } - SentrySdk.FlushAsync(TimeSpan.FromSeconds(5)).GetAwaiter().GetResult(); + yield return CompleteAndQuit(); + } + + private IEnumerator CompleteAndQuit() + { +#if UNITY_WEBGL + // On WebGL, envelope sends are coroutine-based and need additional frames to + // complete. Wait to avoid a race where the test harness shuts down the browser + // before the send finishes. + yield return new WaitForSeconds(3); + Debug.Log("INTEGRATION_TEST_COMPLETE"); +#else Debug.Log("INTEGRATION_TEST_COMPLETE"); -#if !UNITY_WEBGL Application.Quit(0); + yield break; #endif } From cb7d0d124f90869f23b1e0575f39c719737d9739 Mon Sep 17 00:00:00 2001 From: bitsandfoxes Date: Thu, 5 Mar 2026 15:18:48 +0100 Subject: [PATCH 74/86] trycatch --- src/Sentry.Unity/UnityWebRequestTransport.cs | 10 ++++++++-- 1 file changed, 8 insertions(+), 2 deletions(-) diff --git a/src/Sentry.Unity/UnityWebRequestTransport.cs b/src/Sentry.Unity/UnityWebRequestTransport.cs index 025884cba..c19a18d8c 100644 --- a/src/Sentry.Unity/UnityWebRequestTransport.cs +++ b/src/Sentry.Unity/UnityWebRequestTransport.cs @@ -32,8 +32,14 @@ public bool EnqueueEnvelope(Envelope envelope) private IEnumerator SendAndTrack(Envelope envelope) { - yield return _transport.SendEnvelopeAsync(envelope); - _pendingItems--; + try + { + yield return _transport.SendEnvelopeAsync(envelope); + } + finally + { + _pendingItems--; + } } public Task FlushAsync(TimeSpan timeout) => Task.CompletedTask; From ba8ca927dfbaec976a0e2fa0d28165d65a9040a0 Mon Sep 17 00:00:00 2001 From: bitsandfoxes Date: Thu, 5 Mar 2026 17:21:23 +0100 Subject: [PATCH 75/86] third merge hickup --- .github/workflows/smoke-test-run-ios.yml | 4 ---- 1 file changed, 4 deletions(-) diff --git a/.github/workflows/smoke-test-run-ios.yml b/.github/workflows/smoke-test-run-ios.yml index 12b02fe54..721c4a3ec 100644 --- a/.github/workflows/smoke-test-run-ios.yml +++ b/.github/workflows/smoke-test-run-ios.yml @@ -44,10 +44,6 @@ jobs: run: git submodule update --init modules/app-runner shell: bash - - name: Initialize app-runner submodule - run: git submodule update --init modules/app-runner - shell: bash - - name: Download app artifact uses: actions/download-artifact@d3f86a106a0bac45b974a628896c90dbdf5c8093 # v4 with: From c7b507b0893c4f74abead735cd601a55b1ce434a Mon Sep 17 00:00:00 2001 From: bitsandfoxes Date: Thu, 5 Mar 2026 17:49:38 +0100 Subject: [PATCH 76/86] cleanup --- .github/workflows/ci.yml | 2 +- .../workflows/smoke-test-build-android.yml | 2 +- .github/workflows/smoke-test-build-ios.yml | 2 +- .github/workflows/smoke-test-build-linux.yml | 2 +- .../workflows/smoke-test-build-windows.yml | 2 +- .../Scenes/SmokeTest.unity | 4 +- .../Scripts/IntegrationTester.cs | 46 +- .../Scripts/OptionsConfiguration.cs | 79 ---- .../Scripts/SmokeTester.cs | 437 ------------------ .../Scripts/SmokeTester.cs.meta | 11 - .../Scripts/TestLauncher.cs | 80 ---- .../Scripts/TestLauncher.cs.meta | 11 - .../configure-sentry.ps1 | 10 +- .../integration-test.ps1 | 11 +- .../run-smoke-test.ps1 | 202 -------- 15 files changed, 62 insertions(+), 839 deletions(-) delete mode 100644 test/Scripts.Integration.Test/Scripts/OptionsConfiguration.cs delete mode 100644 test/Scripts.Integration.Test/Scripts/SmokeTester.cs delete mode 100644 test/Scripts.Integration.Test/Scripts/SmokeTester.cs.meta delete mode 100644 test/Scripts.Integration.Test/Scripts/TestLauncher.cs delete mode 100644 test/Scripts.Integration.Test/Scripts/TestLauncher.cs.meta delete mode 100644 test/Scripts.Integration.Test/run-smoke-test.ps1 diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml index 6ec9ffc22..d2d7016f2 100644 --- a/.github/workflows/ci.yml +++ b/.github/workflows/ci.yml @@ -189,7 +189,7 @@ 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 "$env:BUILD_PLATFORM" -CheckSymbols -TestMode "integration" + run: ./test/Scripts.Integration.Test/configure-sentry.ps1 -UnityPath "$env:UNITY_PATH" -Platform "$env:BUILD_PLATFORM" -CheckSymbols env: BUILD_PLATFORM: ${{ matrix.build_platform }} SENTRY_DSN: ${{ secrets.SENTRY_TEST_DSN }} diff --git a/.github/workflows/smoke-test-build-android.yml b/.github/workflows/smoke-test-build-android.yml index 999d30021..5e0d7ef85 100644 --- a/.github/workflows/smoke-test-build-android.yml +++ b/.github/workflows/smoke-test-build-android.yml @@ -78,7 +78,7 @@ 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 -TestMode "integration" + run: ./test/Scripts.Integration.Test/configure-sentry.ps1 -UnityPath "$env:UNITY_PATH" -Platform "Android" -CheckSymbols env: SENTRY_DSN: ${{ secrets.SENTRY_TEST_DSN }} diff --git a/.github/workflows/smoke-test-build-ios.yml b/.github/workflows/smoke-test-build-ios.yml index ff193d36b..a3992dde4 100644 --- a/.github/workflows/smoke-test-build-ios.yml +++ b/.github/workflows/smoke-test-build-ios.yml @@ -97,7 +97,7 @@ 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 "$env:BUILD_PLATFORM" -CheckSymbols -TestMode "integration" + run: ./test/Scripts.Integration.Test/configure-sentry.ps1 -UnityPath "$env:UNITY_PATH" -Platform "$env:BUILD_PLATFORM" -CheckSymbols env: BUILD_PLATFORM: ${{ matrix.build_platform }} SENTRY_DSN: ${{ secrets.SENTRY_TEST_DSN }} diff --git a/.github/workflows/smoke-test-build-linux.yml b/.github/workflows/smoke-test-build-linux.yml index 39f095017..8512596bc 100644 --- a/.github/workflows/smoke-test-build-linux.yml +++ b/.github/workflows/smoke-test-build-linux.yml @@ -89,7 +89,7 @@ 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 Linux -TestMode "integration" + run: ./test/Scripts.Integration.Test/configure-sentry.ps1 -UnityPath "$env:UNITY_PATH" -Platform Linux env: SENTRY_DSN: ${{ secrets.SENTRY_TEST_DSN }} diff --git a/.github/workflows/smoke-test-build-windows.yml b/.github/workflows/smoke-test-build-windows.yml index cd569b8df..b65f38389 100644 --- a/.github/workflows/smoke-test-build-windows.yml +++ b/.github/workflows/smoke-test-build-windows.yml @@ -80,7 +80,7 @@ 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 Windows -TestMode "integration" + run: ./test/Scripts.Integration.Test/configure-sentry.ps1 -UnityPath "$env:UNITY_PATH" -Platform Windows env: SENTRY_DSN: ${{ secrets.SENTRY_TEST_DSN }} diff --git a/test/Scripts.Integration.Test/Scenes/SmokeTest.unity b/test/Scripts.Integration.Test/Scenes/SmokeTest.unity index 86e603ce6..2806c2b8e 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: a1b2c3d4e5f6a7b8c9d0e1f2a3b4c5d6, type: 3} + m_Script: {fileID: 11500000, guid: b2c3d4e5f6a7b8c9d0e1f2a3b4c5d6e7, type: 3} m_Name: m_EditorClassIdentifier: --- !u!1 &1263241751 @@ -543,7 +543,7 @@ MonoBehaviour: m_HorizontalOverflow: 0 m_VerticalOverflow: 0 m_LineSpacing: 1 - m_Text: Smoke Test + m_Text: Integration Test --- !u!222 &1482558922 CanvasRenderer: m_ObjectHideFlags: 0 diff --git a/test/Scripts.Integration.Test/Scripts/IntegrationTester.cs b/test/Scripts.Integration.Test/Scripts/IntegrationTester.cs index 2c501b269..04466da32 100644 --- a/test/Scripts.Integration.Test/Scripts/IntegrationTester.cs +++ b/test/Scripts.Integration.Test/Scripts/IntegrationTester.cs @@ -2,16 +2,30 @@ using System.Collections; using System.Collections.Generic; using System.Runtime.CompilerServices; +using System.Runtime.InteropServices; using Sentry; using Sentry.Unity; using UnityEngine; using UnityEngine.Diagnostics; +#if UNITY_WEBGL +using System.Web; +#endif + public class IntegrationTester : MonoBehaviour { + private void Awake() + { + Debug.Log("IntegrationTester, awake!"); + Application.quitting += () => + { + Debug.Log("IntegrationTester is quitting."); + }; + } + public void Start() { - var arg = TestLauncher.GetTestArg(); + var arg = GetTestArg(); Debug.Log($"IntegrationTester arg: '{arg}'"); switch (arg) @@ -37,6 +51,36 @@ public void Start() } } +#if UNITY_IOS && !UNITY_EDITOR + // .NET `Environment.GetCommandLineArgs()` doesn't seem to work on iOS so we get the test arg in Objective-C + [DllImport("__Internal", EntryPoint="getTestArgObjectiveC")] + private static extern string GetTestArg(); +#else + private 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 + private void AddIntegrationTestContext(string testType) { SentrySdk.AddBreadcrumb("Integration test started"); diff --git a/test/Scripts.Integration.Test/Scripts/OptionsConfiguration.cs b/test/Scripts.Integration.Test/Scripts/OptionsConfiguration.cs deleted file mode 100644 index 42c9dd652..000000000 --- a/test/Scripts.Integration.Test/Scripts/OptionsConfiguration.cs +++ /dev/null @@ -1,79 +0,0 @@ -using System.Collections.Generic; -using Sentry; -using Sentry.Unity; -using UnityEngine; - -public class OptionsConfiguration : SentryOptionsConfiguration -{ - public override void Configure(SentryUnityOptions options) - { - Debug.Log("Sentry: OptionsConfig::Configure() called"); - - string host; - -#if UNITY_6000_0 && UNITY_EDITOR - // Workaround for an issue specific to Unity 6.0 where in CI, `UNITY_ANDROID` would resolve to `false` during the build - if (UnityEditor.EditorUserBuildSettings.activeBuildTarget == UnityEditor.BuildTarget.Android) - { - host = "10.0.2.2"; - } - else if (UnityEditor.EditorUserBuildSettings.activeBuildTarget == UnityEditor.BuildTarget.WebGL) - { - host = "127.0.0.1"; - } - else - { - host = "localhost"; - } -#else - -#if UNITY_ANDROID - host = "10.0.2.2"; -#elif UNITY_WEBGL - host = "127.0.0.1"; -#else - host = "localhost"; -#endif -#endif - - options.Dsn = $"http://publickey@{host}:8000/12345"; - - Debug.LogFormat("Sentry: Setting options.Dsn = {0}", options.Dsn); - - options.AttachScreenshot = true; - options.Il2CppLineNumberSupportEnabled = true; - options.Debug = true; - options.DiagnosticLevel = SentryLevel.Debug; - options.TracesSampleRate = 1.0d; - options.PerformanceAutoInstrumentationEnabled = true; - - options.CreateHttpMessageHandler = () => SmokeTester.t; - SmokeTester.CrashedLastRun = () => - { - if (options.CrashedLastRun != null) - { - return options.CrashedLastRun() ? 1 : 0; - } - return -2; - }; - - // Filtering the SmokeTester logs from the breadcrumbs here - options.AddBreadcrumbsForLogType = new Dictionary - { - { LogType.Error, true}, - { LogType.Assert, true}, - { LogType.Warning, true}, - { LogType.Log, false}, // No breadcrumbs for Debug.Log - { LogType.Exception, true}, - }; - - // If an ANR triggers while the smoke test runs, the test would fail because we expect exact order of events. - options.DisableAnrIntegration(); - - // These options will get overwritten by CI. This allows us to create artifacts for both initialization types. - options.AndroidNativeInitializationType = NativeInitializationType.Runtime; - options.IosNativeInitializationType = NativeInitializationType.Runtime; - - Debug.Log("Sentry: OptionsConfig::Configure() finished"); - } -} diff --git a/test/Scripts.Integration.Test/Scripts/SmokeTester.cs b/test/Scripts.Integration.Test/Scripts/SmokeTester.cs deleted file mode 100644 index fb88633e6..000000000 --- a/test/Scripts.Integration.Test/Scripts/SmokeTester.cs +++ /dev/null @@ -1,437 +0,0 @@ -using System; -using System.Collections; -using System.Collections.Generic; -using System.Collections.Concurrent; -using System.Linq; -using System.Net; -using System.Net.Http; -using System.Runtime.InteropServices; -using System.Threading; -using System.Threading.Tasks; -using Sentry; -using Sentry.Unity; -using UnityEngine; -using UnityEngine.Diagnostics; -using Debug = UnityEngine.Debug; - -#if UNITY_WEBGL -using System.Web; -#endif - -public class SmokeTester : MonoBehaviour -{ - private void Awake() - { - Debug.Log("SmokeTester, awake!"); - Application.quitting += () => - { - // The smoke-test-android.ps1 reads this from console to reliably detect when the tests have finished running. - Debug.Log("SmokeTester is quitting."); - }; - } - - public void Start() - { - Debug.Log("SmokeTester starting"); - - var arg = GetTestArg(); - Debug.Log($"SmokeTester arg: '{arg}'"); - - if (arg == "smoke") - { - // Running this as coroutine as we need to skip some frames to wait for screenshot capture - StartCoroutine(SmokeTestCoroutine()); - } - else if (arg == "hasnt-crashed") - { - HasntCrashedTest(); - } - else if (arg == "crash") - { - CrashTest(); - } - else if (arg == "has-crashed") - { - HasCrashedTest(); - } - else if (arg != null) - { - Debug.Log($"Unknown command line argument: {arg}"); - Application.Quit(-1); - } - } - -#if UNITY_IOS && !UNITY_EDITOR - // .NET `Environment.GetCommandLineArgs()` doesn't seem to work on iOS so we get the test arg in Objective-C - [DllImport("__Internal", EntryPoint="getTestArgObjectiveC")] - private static extern string GetTestArg(); -#else - private 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 - - internal static TestHandler t = new TestHandler(); - - internal static Func CrashedLastRun = () => -1; - - private IEnumerator SmokeTestCoroutine() - { - t.Start("SMOKE"); - -#if !UNITY_EDITOR - var crashed = CrashedLastRun(); - -#if UNITY_WEBGL - // On WebGL CrashedLastRun returns `Unknown` - t.Expect($"options.CrashedLastRun ({crashed}) == unknown (-2)", crashed == -2); -#else - t.Expect($"options.CrashedLastRun ({crashed}) == false (0)", crashed == 0); -#endif -#endif - var currentMessage = 0; - t.ExpectMessage(currentMessage, "'type':'session'"); - - // Skip the session init requests (there may be multiple of them). We can't skip them by a "positive" - // because they're also repeated with standard events (in an envelope). - Debug.Log("Skipping all session requests"); - for (; currentMessage < 10; currentMessage++) - { - if (t.CheckMessage(currentMessage, "'type':'transaction'")) - { - break; - } - } - Debug.Log($"Done skipping session requests. Last one was: #{currentMessage}"); - - t.ExpectMessage(currentMessage, "'type':'transaction"); - t.ExpectMessage(currentMessage, "'op':'app.start'"); // startup transaction - t.ExpectMessageNot(currentMessage, "'length':0"); - - var guid = Guid.NewGuid().ToString(); - Debug.LogError($"LogError(GUID)={guid}"); - - // Wait for screenshot capture to complete - yield return null; - - currentMessage++; // The error event - - t.ExpectMessage(currentMessage, "'type':'event'"); - t.ExpectMessage(currentMessage, $"LogError(GUID)={guid}"); - // Contexts - t.ExpectMessage(currentMessage, "'type':'app',"); - t.ExpectMessage(currentMessage, "'type':'device',"); - t.ExpectMessage(currentMessage, "'type':'gpu',"); - t.ExpectMessage(currentMessage, "'type':'os',"); - t.ExpectMessage(currentMessage, "'type':'runtime',"); - t.ExpectMessage(currentMessage, "'type':'unity',"); - t.ExpectMessage(currentMessage, "'active_scene_name':'"); // active scene name - // User - t.ExpectMessage(currentMessage, "'user':{'id':'"); // non-null automatic ID - t.ExpectMessageNot(currentMessage, "'length':0"); - - currentMessage++; // The screenshot envelope - - t.ExpectMessage(currentMessage, "'filename':'screenshot.jpg','attachment_type':'event.attachment'"); - t.ExpectMessageNot(currentMessage, "'length':0"); - - SentrySdk.CaptureMessage($"CaptureMessage(GUID)={guid}"); - - // Wait for screenshot capture to complete - yield return null; - - currentMessage++; // The message event - - t.ExpectMessage(currentMessage, "'type':'event'"); - t.ExpectMessage(currentMessage, $"CaptureMessage(GUID)={guid}"); - t.ExpectMessageNot(currentMessage, "'length':0"); - - currentMessage++; // The screenshot envelope - - t.ExpectMessage(currentMessage, "'filename':'screenshot.jpg','attachment_type':'event.attachment'"); - t.ExpectMessageNot(currentMessage, "'length':0"); - - var ex = new Exception("Exception & context test"); - AddContext(); - SentrySdk.CaptureException(ex); - - // Wait for screenshot capture to complete - yield return null; - - currentMessage++; // The exception event - - t.ExpectMessage(currentMessage, "'type':'event'"); - t.ExpectMessage(currentMessage, "'message':'crumb','type':'error','data':{'foo':'bar'},'category':'bread','level':'fatal'}"); - t.ExpectMessage(currentMessage, "'message':'scope-crumb'}"); - t.ExpectMessage(currentMessage, "'extra':{'extra-key':42}"); - t.ExpectMessage(currentMessage, "'tag-key':'tag-value'"); - t.ExpectMessage(currentMessage, "'user':{'id':'user-id','username':'username','email':'email@example.com','ip_address':'::1','other':{'role':'admin'}}"); - t.ExpectMessageNot(currentMessage, "'length':0"); - - currentMessage++; // The screenshot envelope - - t.ExpectMessage(currentMessage, "'filename':'screenshot.jpg','attachment_type':'event.attachment'"); - t.ExpectMessageNot(currentMessage, "'length':0"); - - ex = new Exception("Exception & removed context test"); - RemoveContext(); - SentrySdk.CaptureException(ex); - - // Wait for screenshot capture to complete - yield return null; - - currentMessage++; // The exception event - - t.ExpectMessage(currentMessage, "'type':'event'"); - t.ExpectMessageNot(currentMessage, "'extra':{'extra-key':42}"); - t.ExpectMessageNot(currentMessage, "'tag-key':'tag-value'"); - t.ExpectMessageNot(currentMessage, "user-id"); - t.ExpectMessageNot(currentMessage, "'length':0"); - - currentMessage++; // The screenshot envelope - - t.ExpectMessage(currentMessage, "'filename':'screenshot.jpg','attachment_type':'event.attachment'"); - t.ExpectMessageNot(currentMessage, "'length':0"); - -#if !UNITY_WEBGL - // Test screenshot capture from background thread - var backgroundThreadGuid = Guid.NewGuid().ToString(); - var backgroundThreadTask = Task.Run(() => - { - Debug.Log($"Background thread: Capturing exception with GUID={backgroundThreadGuid}"); - Debug.LogError($"BackgroundThreadException(GUID)={backgroundThreadGuid}"); - }); - backgroundThreadTask.Wait(); - - // Wait for screenshot capture to complete - yield return null; - // The capture coroutine gets queued up to be started in the next Update(), wait for that - yield return null; - - currentMessage++; // The background thread exception event - - t.ExpectMessage(currentMessage, "'type':'event'"); - t.ExpectMessage(currentMessage, $"BackgroundThreadException(GUID)={backgroundThreadGuid}"); - t.ExpectMessageNot(currentMessage, "'length':0"); - - currentMessage++; // The screenshot envelope from background thread - - t.ExpectMessage(currentMessage, "'filename':'screenshot.jpg','attachment_type':'event.attachment'"); - t.ExpectMessageNot(currentMessage, "'length':0"); -#endif - Debug.Log("Finished checking messages."); - - t.Pass(); - } - - public static void CrashTest() - { - t.Start("CRASH"); - - AddContext(); - - Debug.Log("CRASH TEST: Issuing a native crash (c++ unhandled exception)"); - Utils.ForceCrash(ForcedCrashCategory.FatalError); - - // shouldn't execute because the previous call should have failed - Debug.Log("CRASH TEST: FAIL - unexpected code executed..."); - Application.Quit(-1); - } - - public static void HasntCrashedTest() - { - t.Start("HASNT-CRASHED"); - var crashed = CrashedLastRun(); - t.Expect($"options.CrashedLastRun ({crashed}) == false (0)", crashed == 0); - - var lastRunState = SentrySdk.GetLastRunState(); - t.Expect($"SentrySdk.GetLastRunState() ({lastRunState}) is 'DidNotCrash'", lastRunState == SentrySdk.CrashedLastRun.DidNotCrash); - - t.Pass(); - } - - public static void HasCrashedTest() - { - t.Start("HAS-CRASHED"); - var crashed = CrashedLastRun(); - t.Expect($"options.CrashedLastRun ({crashed}) == true (1)", crashed == 1); - - var lastRunState = SentrySdk.GetLastRunState(); - t.Expect($"SentrySdk.GetLastRunState() ({lastRunState}) is 'Crashed'", lastRunState == SentrySdk.CrashedLastRun.Crashed); - - t.Pass(); - } - - private static void AddContext() - { - SentrySdk.AddBreadcrumb("crumb", "bread", "error", new Dictionary() { { "foo", "bar" } }, BreadcrumbLevel.Fatal); - SentrySdk.ConfigureScope((Scope scope) => - { - scope.SetExtra("extra-key", 42); - scope.AddBreadcrumb("scope-crumb"); - scope.SetTag("tag-key", "tag-value"); - scope.User = new SentryUser() - { - Username = "username", - Email = "email@example.com", - IpAddress = "::1", - Id = "user-id", - Other = new Dictionary() { { "role", "admin" } } - }; - }); - } - - private static void RemoveContext() - { - SentrySdk.ConfigureScope((Scope scope) => - { - scope.SetExtra("extra-key", null); - scope.UnsetTag("tag-key"); - scope.User = new SentryUser - { - Username = null, - Email = null, - IpAddress = null, - Id = null, - }; - }); - } - - // CppPlugin.cpp - [DllImport("__Internal", CallingConvention = CallingConvention.Cdecl)] - private static extern void throw_cpp(); - - internal class TestHandler : HttpClientHandler - { - private string _name; - private ConcurrentQueue _requests = new ConcurrentQueue(); - private AutoResetEvent _requestReceived = new AutoResetEvent(false); - - private readonly TimeSpan _receiveTimeout = TimeSpan.FromSeconds(10); - - private int _testNumber = 0; - public int ExitCode = 0; - - public void Start(string testName) - { - _name = testName; - Debug.Log($"{_name} TEST: start"); - } - - protected override Task SendAsync(HttpRequestMessage request, CancellationToken cancellationToken) - { - Receive(request); - return Task.FromResult(new HttpResponseMessage(HttpStatusCode.OK)); - } - - private void Receive(HttpRequestMessage message) - { - var msgText = message.Content.ReadAsStringAsync().Result; - Debug.Log($"{_name} TEST: Intercepted HTTP Request #{_requests.Count} = {msgText}"); - _requests.Enqueue(msgText); - _requestReceived.Set(); - } - - public void Exit(int code) - { - if (ExitCode != 0) - { - Debug.Log($"{_name} TEST: Ignoring spurious Exit({code}). Application is already exiting with code {ExitCode}"); - } - else - { -#if !UNITY_EDITOR - ExitCode = code; - Application.Quit(code); - // Application.Quit doesn't actually terminate immediately so exit the context at least... - throw new Exception($"Quitting with exit code {code}"); -#endif - } - } - - public void Pass() - { - if (ExitCode == 0) - { - // On Android we'll grep logcat for this string instead of relying on exit code: - Debug.Log($"{_name} TEST: PASS"); - - // Exit Code 200 to avoid false positive from a graceful exit unrelated to this test run - ExitCode = 200; - -#if !UNITY_WEBGL // We don't quit on WebGL because outgoing HTTP requests (in coroutines) would be cancelled. - Application.Quit(ExitCode); -#endif - } - } - - public void Expect(string message, bool result) - { - _testNumber++; - Debug.Log($"{_name} TEST | {_testNumber}. {message}: {(result ? "PASS" : "FAIL")}"); - if (!result) - { - // run-smoke-test.ps1 expects this as failure confirmation - Debug.Log($"{_name} TEST: FAIL"); - Exit(_testNumber); - } - } - - public string GetMessage(int index) - { - while (true) - { - if (_requests.Count > index) - { - break; - } - if (!_requestReceived.WaitOne(_receiveTimeout)) - { - Debug.Log($"{_name} TEST: Failed while waiting for an HTTP request #{index} to come in."); - Exit(_testNumber); - } - } - - return _requests.ElementAt(index); - } - - public bool CheckMessage(int index, string substring, bool negate = false) - { -#if UNITY_WEBGL - // Note: we cannot use the standard checks on WebGL - it would get stuck here because of the lack of multi-threading. - // The verification is done in the python script used for WebGL smoke test - smoke-test-webgl.py - return true; -#else - var message = GetMessage(index); - var contains = message.Contains(substring) || message.Contains(substring.Replace("'", "\"")); - return negate ? !contains : contains; -#endif - } - - public void ExpectMessage(int index, string substring) => - Expect($"HTTP Request #{index} contains \"{substring}\".", CheckMessage(index, substring)); - - public void ExpectMessageNot(int index, string substring) => - Expect($"HTTP Request #{index} doesn't contain \"{substring}\".", CheckMessage(index, substring, negate: true)); - } -} diff --git a/test/Scripts.Integration.Test/Scripts/SmokeTester.cs.meta b/test/Scripts.Integration.Test/Scripts/SmokeTester.cs.meta deleted file mode 100644 index 108e607c2..000000000 --- a/test/Scripts.Integration.Test/Scripts/SmokeTester.cs.meta +++ /dev/null @@ -1,11 +0,0 @@ -fileFormatVersion: 2 -guid: 6b8ba3d687233471198b184bbcb4fdbd -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 deleted file mode 100644 index e98281705..000000000 --- a/test/Scripts.Integration.Test/Scripts/TestLauncher.cs +++ /dev/null @@ -1,80 +0,0 @@ -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")] - internal 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 deleted file mode 100644 index 69999fcd4..000000000 --- a/test/Scripts.Integration.Test/Scripts/TestLauncher.cs.meta +++ /dev/null @@ -1,11 +0,0 @@ -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 daa9d4bb1..a257891b3 100644 --- a/test/Scripts.Integration.Test/configure-sentry.ps1 +++ b/test/Scripts.Integration.Test/configure-sentry.ps1 @@ -1,9 +1,7 @@ param( [string] $UnityPath, [string] $Platform = "", - [Switch] $CheckSymbols, - [ValidateSet("smoke", "integration")] - [string] $TestMode = "smoke" + [Switch] $CheckSymbols ) if (-not $Global:NewProjectPathCache) @@ -15,14 +13,12 @@ if (-not $Global:NewProjectPathCache) $UnityPath = FormatUnityPath $UnityPath -Write-Log "Configuring Sentry options (TestMode: $TestMode)..." - -$optionsScript = if ($TestMode -eq "integration") { "IntegrationOptionsConfiguration" } else { "OptionsConfiguration" } +Write-Log "Configuring Sentry options..." $unityArgs = @( ` "-quit", "-batchmode", "-nographics", "-disable-assembly-updater", "-projectPath ", $(GetNewProjectPath), ` "-executeMethod", "Sentry.Unity.Editor.ConfigurationWindow.SentryEditorWindowInstrumentation.ConfigureOptions", ` - "-optionsScript", $optionsScript, ` + "-optionsScript", "IntegrationOptionsConfiguration", ` "-cliOptionsScript", "CliConfiguration", ` "-cliOptions.UrlOverride", ($CheckSymbols ? (SymbolServerUrlFor $UnityPath $Platform) : "") ) diff --git a/test/Scripts.Integration.Test/integration-test.ps1 b/test/Scripts.Integration.Test/integration-test.ps1 index 8d14be637..50b1544b9 100644 --- a/test/Scripts.Integration.Test/integration-test.ps1 +++ b/test/Scripts.Integration.Test/integration-test.ps1 @@ -64,7 +64,7 @@ Else { Write-Log "No NativeSDKPath provided (native features disabled)" -ForegroundColor Yellow } -# Support rebuilding the integration test project. I.e. if you make changes to the SmokeTester.cs during +# Support rebuilding the integration test project. If ($Rebuild -or -not(Test-Path -Path $(GetNewProjectBuildPath))) { Write-PhaseHeader "Building Project" @@ -87,13 +87,16 @@ Else { Switch -Regex ($Platform) { "^(Windows|MacOS|Linux)$" { - ./test/Scripts.Integration.Test/run-smoke-test.ps1 -Smoke -Crash + $env:SENTRY_TEST_APP = GetNewProjectBuildPath + Invoke-Pester -Path test/IntegrationTest/Integration.Tests.Desktop.ps1 -CI } "^(Android)$" { - ./scripts/smoke-test-android.ps1 + $env:SENTRY_TEST_APK = "$(GetNewProjectBuildPath)/test.apk" + Invoke-Pester -Path test/IntegrationTest/Integration.Tests.ps1 -CI } "^iOS$" { - ./scripts/smoke-test-ios.ps1 Test "latest" -IsIntegrationTest + $env:SENTRY_TEST_APP = "$(GetNewProjectBuildPath)/IntegrationTest.app" + Invoke-Pester -Path test/IntegrationTest/Integration.Tests.iOS.ps1 -CI } "^WebGL$" { $env:SENTRY_WEBGL_BUILD_PATH = GetNewProjectBuildPath diff --git a/test/Scripts.Integration.Test/run-smoke-test.ps1 b/test/Scripts.Integration.Test/run-smoke-test.ps1 deleted file mode 100644 index 59c3c36ad..000000000 --- a/test/Scripts.Integration.Test/run-smoke-test.ps1 +++ /dev/null @@ -1,202 +0,0 @@ -param ( - [Parameter(Position = 0)] - [string] $TestAppPath = "", - - [Parameter()] - [string] $AppDataDir = "", - - [Parameter()] - [switch] $Smoke, - - [Parameter()] - [switch] $Crash, - - [Parameter()] - [int] $MaxRetries = 3 -) - -if (-not $Global:NewProjectPathCache) -{ - . $PSScriptRoot/globals.ps1 -} - -. $PSScriptRoot/common.ps1 - -Write-Log "Given parameters:" -Write-Log " TestAppPath: $TestAppPath" -Write-Log " AppDataDir: $AppDataDir" -Write-Log " Smoke: $Smoke" -Write-Log " Crash: $Crash" -Write-Log " MaxRetries: $MaxRetries" - -If (!$Smoke -and !$Crash) -{ - Write-Error "Select one of the following tests (or both): -Smoke or -Crash" -} - -if ("$TestAppPath" -eq "") -{ - If ($IsMacOS) - { - $TestAppPath = "$(GetNewProjectBuildPath)/test.app/Contents/MacOS/$(GetNewProjectName)" - if ("$AppDataDir" -eq "") - { - $AppDataDir = "$env:HOME/Library/Logs/DefaultCompany/$(GetNewProjectName)/" - } - } - ElseIf ($IsWindows) - { - $TestAppPath = "$(GetNewProjectBuildPath)/test.exe" - if ("$AppDataDir" -eq "") - { - $AppDataDir = "$env:UserProfile\AppData\LocalLow\DefaultCompany\$(GetNewProjectName)\" - } - } - ElseIf ($IsLinux) - { - $TestAppPath = "$(GetNewProjectBuildPath)/test" - chmod +x $TestAppPath - if ("$AppDataDir" -eq "") - { - $AppDataDir = "$env:HOME/.config/unity3d/DefaultCompany/$(GetNewProjectName)/" - } - } - Else - { - Write-Error "Unsupported build" - } -} - -Write-Log "Resolved parameters:" -Write-Log " TestAppPath: $TestAppPath" -Write-Log " AppDataDir: $AppDataDir" - -function RunTest([string] $type) -{ - Write-Host "::group::Test: '$type'" - try - { - if ($IsLinux -and "$env:XDG_CURRENT_DESKTOP" -eq "" -and (Get-Command "xvfb-run" -ErrorAction SilentlyContinue)) - { - Write-Log "Running xvfb-run -ae /dev/stdout $TestAppPath --test $type" - $process = Start-Process "xvfb-run" -ArgumentList "-ae", "/dev/stdout", "$TestAppPath", "--test", $type -PassThru - } - else - { - Write-Log "Running $TestAppPath --test $type" - $process = Start-Process "$TestAppPath" -ArgumentList "--test", $type -PassThru - } - - If ($null -eq $process) - { - Throw "Process not found." - } - - # Wait for the test to finish - $timedOut = $null # reset any previously set timeout - $process | Wait-Process -Timeout 60 -ErrorAction SilentlyContinue -ErrorVariable timedOut - - $appLog = "" - if ("$AppDataDir" -ne "") - { - Write-Log "$type test: Player.log contents:" -ForegroundColor Yellow - $appLog = Get-Content "$AppDataDir/Player.log" - $appLog - Write-Log "================================================================================" -ForegroundColor Yellow - 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. - ElseIf ($appLog | Select-String "SmokeTester is quitting.") - { - Write-Log "$type test: PASSED" -ForegroundColor Green - } - ElseIf ($timedOut) - { - $process | Stop-Process -Force - Throw "Test process timed out." - } - Else - { - $info = "Test process finished with status code $($process.ExitCode). No completion marker found in Player.log" - If ($type -ne "crash") - { - throw $info - } - Write-Log $info - } - } - finally - { - if ($null -ne $process -and !$process.HasExited) - { - Write-Warning "Process still running - forcing termination." - $process | Stop-Process -Force - } - Write-Host "::endgroup::" - } -} - -for ($attempt = 1; $attempt -le $MaxRetries; $attempt++) -{ - Write-Log "Test suite attempt $attempt/$MaxRetries" - - if ("$AppDataDir" -ne "" -and (Test-Path $AppDataDir)) - { - Write-Warning "Removing AppDataDir '$AppDataDir'" - Remove-Item -Force -Recurse $AppDataDir - } - - try - { - if ($Smoke) - { - RunTest "smoke" - RunTest "hasnt-crashed" - } - - if ($Crash) - { - # Note: macOS & Linux apps post the crash on the next app launch so we must run both as part of the "CrashTestWithServer" - # Windows posts the crash immediately because the handler runs as a standalone process. - if ($IsMacOS -or $IsLinux) - { - $expectedFragment = $IsMacOS ? '1f8b08000000000000' : '7b2264736e223a2268' - CrashTestWithServer -SuccessString "POST /api/12345/envelope/ HTTP/1.1`" 200 -b'$expectedFragment" -CrashTestCallback { - RunTest "crash" "CRASH TEST: Issuing a native crash" - RunTest "has-crashed" - } - } - else - { - CrashTestWithServer -SuccessString "POST /api/12345/minidump/" -CrashTestCallback { RunTest "crash" } - RunTest "has-crashed" - } - } - - Write-Log "All tests passed on attempt $attempt/$MaxRetries" - return - } - catch - { - Write-Log "Test suite attempt $attempt failed: $_" - if ($attempt -lt $MaxRetries) - { - Write-Log "Will retry..." - continue - } - throw - } -} From 9696f43ee177213f7735278b6451453f7fcc30c9 Mon Sep 17 00:00:00 2001 From: bitsandfoxes Date: Thu, 5 Mar 2026 18:05:13 +0100 Subject: [PATCH 77/86] more cleanup --- .github/workflows/ci.yml | 8 +- .../workflows/smoke-test-build-android.yml | 8 +- .github/workflows/smoke-test-build-ios.yml | 8 +- .github/workflows/smoke-test-build-linux.yml | 4 +- .../workflows/smoke-test-build-windows.yml | 4 +- .github/workflows/smoke-test-compile-ios.yml | 4 +- scripts/compile-xcode-project.ps1 | 36 ++ scripts/smoke-test-android.ps1 | 427 ------------------ scripts/smoke-test-ios.ps1 | 330 -------------- .../Scripts/CliConfiguration.cs | 18 +- .../build-project.ps1 | 11 +- test/Scripts.Integration.Test/common.ps1 | 310 ------------- .../configure-sentry.ps1 | 6 +- .../crash-test-server.py | 49 -- .../integration-test.ps1 | 9 +- .../symbol-upload-server.py | 136 ------ 16 files changed, 63 insertions(+), 1305 deletions(-) create mode 100644 scripts/compile-xcode-project.ps1 delete mode 100644 scripts/smoke-test-android.ps1 delete mode 100644 scripts/smoke-test-ios.ps1 delete mode 100644 test/Scripts.Integration.Test/crash-test-server.py delete mode 100644 test/Scripts.Integration.Test/symbol-upload-server.py diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml index d2d7016f2..172d1aa10 100644 --- a/.github/workflows/ci.yml +++ b/.github/workflows/ci.yml @@ -111,7 +111,6 @@ jobs: platform: ["WebGL"] include: - platform: WebGL - check_symbols: true build_platform: WebGL env: UNITY_PATH: docker exec unity unity-editor @@ -172,7 +171,7 @@ jobs: - name: Build without Sentry SDK if: steps.cache-build-nosentry.outputs.cache-hit != 'true' - run: ./test/Scripts.Integration.Test/build-project.ps1 -UnityPath "$env:UNITY_PATH" -Platform "$env:BUILD_PLATFORM" -CheckSymbols:$false -BuildDirName "Build-NoSentry" + run: ./test/Scripts.Integration.Test/build-project.ps1 -UnityPath "$env:UNITY_PATH" -Platform "$env:BUILD_PLATFORM" -BuildDirName "Build-NoSentry" env: BUILD_PLATFORM: ${{ matrix.build_platform }} @@ -189,16 +188,15 @@ 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 "$env:BUILD_PLATFORM" -CheckSymbols + run: ./test/Scripts.Integration.Test/configure-sentry.ps1 -UnityPath "$env:UNITY_PATH" -Platform "$env:BUILD_PLATFORM" env: BUILD_PLATFORM: ${{ matrix.build_platform }} SENTRY_DSN: ${{ secrets.SENTRY_TEST_DSN }} - name: Build Project - run: ./test/Scripts.Integration.Test/build-project.ps1 -UnityPath "$env:UNITY_PATH" -Platform "$env:BUILD_PLATFORM" -CheckSymbols:$([System.Convert]::ToBoolean($env:CHECK_SYMBOLS)) -UnityVersion "$env:UNITY_VERSION" + run: ./test/Scripts.Integration.Test/build-project.ps1 -UnityPath "$env:UNITY_PATH" -Platform "$env:BUILD_PLATFORM" -UnityVersion "$env:UNITY_VERSION" env: BUILD_PLATFORM: ${{ matrix.build_platform }} - CHECK_SYMBOLS: ${{ matrix.check_symbols }} UNITY_VERSION: ${{ matrix.unity-version }} - name: Compare build sizes diff --git a/.github/workflows/smoke-test-build-android.yml b/.github/workflows/smoke-test-build-android.yml index 5e0d7ef85..1422b1b65 100644 --- a/.github/workflows/smoke-test-build-android.yml +++ b/.github/workflows/smoke-test-build-android.yml @@ -63,7 +63,7 @@ jobs: - name: Build without Sentry SDK if: steps.cache-build-nosentry.outputs.cache-hit != 'true' - run: ./test/Scripts.Integration.Test/build-project.ps1 -UnityPath "$env:UNITY_PATH" -Platform "Android" -CheckSymbols:$false -BuildDirName "Build-NoSentry" + run: ./test/Scripts.Integration.Test/build-project.ps1 -UnityPath "$env:UNITY_PATH" -Platform "Android" -BuildDirName "Build-NoSentry" - name: Download UPM package uses: vaind/download-artifact@e7141b6a94ef28aa3d828b52830cfa1f406a1848 # v4-with-wait-timeout @@ -78,12 +78,12 @@ 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" 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" + run: ./test/Scripts.Integration.Test/build-project.ps1 -UnityPath "$env:UNITY_PATH" -Platform "Android" -UnityVersion "$env:UNITY_VERSION" - name: Compare build sizes (Runtime) run: ./test/Scripts.Integration.Test/measure-build-size.ps1 -Path1 "samples/IntegrationTest/Build-NoSentry" -Path2 "samples/IntegrationTest/Build" -Platform "Android" -UnityVersion "$env:UNITY_VERSION" @@ -110,7 +110,7 @@ jobs: 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" + run: ./test/Scripts.Integration.Test/build-project.ps1 -UnityPath "$env:UNITY_PATH" -Platform "Android" -UnityVersion "$env:UNITY_VERSION" - name: Upload .apk uses: actions/upload-artifact@ea165f8d65b6e75b540449e92b4886f43607fa02 # v4 diff --git a/.github/workflows/smoke-test-build-ios.yml b/.github/workflows/smoke-test-build-ios.yml index a3992dde4..5f620758d 100644 --- a/.github/workflows/smoke-test-build-ios.yml +++ b/.github/workflows/smoke-test-build-ios.yml @@ -69,7 +69,7 @@ jobs: - name: Build without Sentry SDK if: steps.cache-build-nosentry.outputs.cache-hit != 'true' - run: ./test/Scripts.Integration.Test/build-project.ps1 -UnityPath "$env:UNITY_PATH" -Platform "iOS" -CheckSymbols:$false -BuildDirName "Build-NoSentry" + run: ./test/Scripts.Integration.Test/build-project.ps1 -UnityPath "$env:UNITY_PATH" -Platform "iOS" -BuildDirName "Build-NoSentry" - name: Create archive for build without Sentry shell: bash @@ -97,13 +97,13 @@ 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 "$env:BUILD_PLATFORM" -CheckSymbols + run: ./test/Scripts.Integration.Test/configure-sentry.ps1 -UnityPath "$env:UNITY_PATH" -Platform "$env:BUILD_PLATFORM" env: BUILD_PLATFORM: ${{ matrix.build_platform }} SENTRY_DSN: ${{ secrets.SENTRY_TEST_DSN }} - name: Build Project with runtime initialization - run: ./test/Scripts.Integration.Test/build-project.ps1 -UnityPath "$env:UNITY_PATH" -Platform "$env:BUILD_PLATFORM" -CheckSymbols:$false -UnityVersion "$env:UNITY_VERSION" + run: ./test/Scripts.Integration.Test/build-project.ps1 -UnityPath "$env:UNITY_PATH" -Platform "$env:BUILD_PLATFORM" -UnityVersion "$env:UNITY_VERSION" env: BUILD_PLATFORM: ${{ matrix.build_platform }} @@ -132,7 +132,7 @@ jobs: Set-Content $optionsPath $content - name: Build Project with build-time initialization - run: ./test/Scripts.Integration.Test/build-project.ps1 -UnityPath "$env:UNITY_PATH" -Platform "$env:BUILD_PLATFORM" -CheckSymbols:$false -UnityVersion "$env:UNITY_VERSION" + run: ./test/Scripts.Integration.Test/build-project.ps1 -UnityPath "$env:UNITY_PATH" -Platform "$env:BUILD_PLATFORM" -UnityVersion "$env:UNITY_VERSION" env: BUILD_PLATFORM: ${{ matrix.build_platform }} diff --git a/.github/workflows/smoke-test-build-linux.yml b/.github/workflows/smoke-test-build-linux.yml index 8512596bc..8930e626d 100644 --- a/.github/workflows/smoke-test-build-linux.yml +++ b/.github/workflows/smoke-test-build-linux.yml @@ -74,7 +74,7 @@ jobs: - name: Build without Sentry SDK if: steps.cache-build-nosentry.outputs.cache-hit != 'true' - run: ./test/Scripts.Integration.Test/build-project.ps1 -UnityPath "$env:UNITY_PATH" -Platform Linux -CheckSymbols:$false -BuildDirName "Build-NoSentry" + run: ./test/Scripts.Integration.Test/build-project.ps1 -UnityPath "$env:UNITY_PATH" -Platform Linux -BuildDirName "Build-NoSentry" - name: Download UPM package uses: vaind/download-artifact@e7141b6a94ef28aa3d828b52830cfa1f406a1848 # v4-with-wait-timeout @@ -94,7 +94,7 @@ jobs: SENTRY_DSN: ${{ secrets.SENTRY_TEST_DSN }} - name: Build with Sentry SDK - run: ./test/Scripts.Integration.Test/build-project.ps1 -UnityPath "$env:UNITY_PATH" -Platform Linux -CheckSymbols:$false -UnityVersion "$env:UNITY_VERSION" + run: ./test/Scripts.Integration.Test/build-project.ps1 -UnityPath "$env:UNITY_PATH" -Platform Linux -UnityVersion "$env:UNITY_VERSION" - name: Compare build sizes run: ./test/Scripts.Integration.Test/measure-build-size.ps1 -Path1 "samples/IntegrationTest/Build-NoSentry" -Path2 "samples/IntegrationTest/Build" -Platform Linux -UnityVersion "$env:UNITY_VERSION" diff --git a/.github/workflows/smoke-test-build-windows.yml b/.github/workflows/smoke-test-build-windows.yml index b65f38389..4b64a425f 100644 --- a/.github/workflows/smoke-test-build-windows.yml +++ b/.github/workflows/smoke-test-build-windows.yml @@ -65,7 +65,7 @@ jobs: - name: Build without Sentry SDK if: steps.cache-build-nosentry.outputs.cache-hit != 'true' - run: ./test/Scripts.Integration.Test/build-project.ps1 -UnityPath "$env:UNITY_PATH" -Platform Windows -CheckSymbols:$false -BuildDirName "Build-NoSentry" + run: ./test/Scripts.Integration.Test/build-project.ps1 -UnityPath "$env:UNITY_PATH" -Platform Windows -BuildDirName "Build-NoSentry" - name: Download UPM package uses: vaind/download-artifact@e7141b6a94ef28aa3d828b52830cfa1f406a1848 # v4-with-wait-timeout @@ -85,7 +85,7 @@ jobs: SENTRY_DSN: ${{ secrets.SENTRY_TEST_DSN }} - name: Build with Sentry SDK - run: ./test/Scripts.Integration.Test/build-project.ps1 -UnityPath "$env:UNITY_PATH" -Platform Windows -CheckSymbols:$false -UnityVersion "$env:UNITY_VERSION" + run: ./test/Scripts.Integration.Test/build-project.ps1 -UnityPath "$env:UNITY_PATH" -Platform Windows -UnityVersion "$env:UNITY_VERSION" - name: Compare build sizes run: ./test/Scripts.Integration.Test/measure-build-size.ps1 -Path1 "samples/IntegrationTest/Build-NoSentry" -Path2 "samples/IntegrationTest/Build" -Platform Windows -UnityVersion "$env:UNITY_VERSION" diff --git a/.github/workflows/smoke-test-compile-ios.yml b/.github/workflows/smoke-test-compile-ios.yml index ba31718c9..640aa0590 100644 --- a/.github/workflows/smoke-test-compile-ios.yml +++ b/.github/workflows/smoke-test-compile-ios.yml @@ -47,7 +47,7 @@ jobs: - name: Compile Xcode project without Sentry if: ${{ inputs.init-type == 'runtime' && steps.cache-compiled-nosentry.outputs.cache-hit != 'true' }} - run: ./scripts/smoke-test-ios.ps1 Build -UnityVersion "${env:UNITY_VERSION}" -iOSMinVersion "17.0" + run: ./scripts/compile-xcode-project.ps1 -iOSMinVersion "17.0" timeout-minutes: 20 - name: Save compiled app without Sentry @@ -66,7 +66,7 @@ jobs: run: tar -xvzf "test-app-$env:INIT_TYPE.tar.gz" - name: iOS smoke test - run: ./scripts/smoke-test-ios.ps1 Build -IsIntegrationTest -UnityVersion "${env:UNITY_VERSION}" -iOSMinVersion "17.0" + run: ./scripts/compile-xcode-project.ps1 -iOSMinVersion "17.0" timeout-minutes: 20 - name: Upload integration-test project on failure diff --git a/scripts/compile-xcode-project.ps1 b/scripts/compile-xcode-project.ps1 new file mode 100644 index 000000000..c07cfe054 --- /dev/null +++ b/scripts/compile-xcode-project.ps1 @@ -0,0 +1,36 @@ +param ( + [string] $iOSMinVersion = "" +) + +. $PSScriptRoot/../test/Scripts.Integration.Test/common.ps1 + +$ProjectName = "Unity-iPhone" +$XcodeArtifactPath = "samples/IntegrationTest/Build" +$ArchivePath = "$XcodeArtifactPath/archive" + +MakeExecutable "$XcodeArtifactPath/MapFileParser.sh" +MakeExecutable "$XcodeArtifactPath/sentry-cli-Darwin-universal" + +If (-not $IsMacOS) +{ + Write-Log "This script should only be run on a MacOS." -ForegroundColor Yellow +} + +Write-Host "::group::Building iOS project" +try +{ + xcodebuild ` + -project "$XcodeArtifactPath/$ProjectName.xcodeproj" ` + -scheme "Unity-iPhone" ` + -configuration "Release" ` + -sdk "iphonesimulator" ` + -destination "platform=iOS Simulator,OS=$iOSMinVersion" ` + -destination "platform=iOS Simulator,OS=latest" ` + -parallel-testing-enabled YES ` + -derivedDataPath "$ArchivePath/$ProjectName" ` + | Write-Host +} +finally +{ + Write-Host "::endgroup::" +} diff --git a/scripts/smoke-test-android.ps1 b/scripts/smoke-test-android.ps1 deleted file mode 100644 index 260ac2988..000000000 --- a/scripts/smoke-test-android.ps1 +++ /dev/null @@ -1,427 +0,0 @@ -param ( - [Parameter(Position = 0)] - [string] $Action = "", - [Switch] $IsIntegrationTest, - [Switch] $WarnIfFlaky, - [string] $UnityVersion = "" -) - -if (-not $Global:NewProjectPathCache) -{ - . $PSScriptRoot/../test/Scripts.Integration.Test/globals.ps1 -} - -. $PSScriptRoot/../test/Scripts.Integration.Test/common.ps1 - -# GITHUB_WORKSPACE is the root folder where the project is stored. -Write-Host "#####################################################" -Write-Host "# ___ __ __ ___ _ _____ _____ ___ ____ _____ #" -Write-Host "# / __| \/ |/ _ \| |/ / __|_ _| __|/ __|_ _| #" -Write-Host "# \__ \ |\/| | (_) | ' <| _| | | | _| \__ \ | | #" -Write-Host "# |___/_| |_|\___/|_|\_\___| |_| |___|___/ |_| #" -Write-Host "# #" -Write-Host "#####################################################" - -$BuildDir = $(GetNewProjectBuildPath) -$ApkFileName = "test.apk" -$ProcessName = "io.sentry.unity.integrationtest" -$TestActivityName = $null # Detected after install via dumpsys - -$_ArtifactsPath = (Test-Path env:ARTIFACTS_PATH) ? $env:ARTIFACTS_PATH : (Join-Path $BuildDir "../test-artifacts/" $(Get-Date -Format "HHmmss")) - -function ArtifactsPath -{ - if (-not (Test-Path $_ArtifactsPath)) - { - New-Item $_ArtifactsPath -ItemType Directory | Out-Null - } - $_ArtifactsPath.Replace('\', '/') -} - -function TakeScreenshot([string] $deviceId) -{ - $file = "/data/local/tmp/screen$(Get-Date -Format "HHmmss").png" - adb -s $deviceId shell "screencap -p $file" - adb -s $deviceId pull $file (ArtifactsPath) - adb -s $deviceId shell "rm $file" -} - -function GetDeviceUiLog([string] $deviceId, [string] $deviceApi) -{ - if ($deviceApi -eq "21") - { - $dumpFile = "/data/local/tmp/window_dump.xml" - adb -s $deviceId exec-out uiautomator dump $dumpFile - adb -s $deviceId shell cat $dumpFile - } - else - { - adb -s $deviceId exec-out uiautomator dump /dev/tty - } -} - -function LogCat([string] $deviceId, [string] $appPID) -{ - if ([string]::IsNullOrEmpty($appPID)) - { - adb -s $device logcat -d - } - elseif ($deviceApi -eq "21") - { - adb -s $device shell "logcat -d | grep -E '\( *$appPID\)'" - } - else - { - adb -s $device logcat -d --pid=$appPID - } -} - -function PidOf([string] $deviceId, [string] $processName) -{ - $startTime = Get-Date - $timeout = New-TimeSpan -Seconds 60 - - while ((Get-Date) - $startTime -lt $timeout) - { - if ($deviceApi -eq "21") - { - # `pidof` doesn't exist - take second column from the `ps` output for the given process instead. - $processId = (adb -s $deviceId shell "ps | grep '$processName'") -Split " +" | Select-Object -Skip 1 -First 1 - } - else - { - $processId = adb -s $deviceId shell pidof $processName - } - - if (-not [string]::IsNullOrWhiteSpace($processId)) - { - return $processId - } - - Write-Log "Process '$processName' not found, retrying in 2 seconds..." - Start-Sleep -Seconds 2 - } - - Write-Log "Could not find PID for process '$processName' after 60 seconds" -ForegroundColor Red - return $null -} - -function OnError([string] $deviceId, [string] $deviceApi, [string] $appPID) -{ - Write-Log "Dumping logs for $device" - Write-Host "::group::logcat" - LogCat $deviceId $appPID - Write-Host "::endgroup::" - LogCat $deviceId $null | Out-File "$(ArtifactsPath)/logcat.txt" - Write-Host "::group::UI XML Log" - GetDeviceUiLog $device $deviceApi - Write-Host "::endgroup::" - TakeScreenshot $device -} - -# Filter device list -$RawAdbDeviceList = adb devices - -$DeviceList = @() -foreach ($device in $RawAdbDeviceList) -{ - If ($device.EndsWith("device")) - { - $DeviceList += $device.Replace("device", '').Trim() - } -} - -$DeviceCount = $DeviceList.Count -If ($DeviceCount -eq 0) -{ - Write-Error "It seems like no devices were found $RawAdbDeviceList" - exit 1 -} -Else -{ - Write-Log "Found $DeviceCount devices: $DeviceList" -} - -# Check if APK was built. -If (-not (Test-Path -Path "$BuildDir/$ApkFileName" )) -{ - Write-Error "Expected APK on $BuildDir/$ApkFileName but it was not found." - exit 1 -} - - -### START TEST - -# Pick the first device available -$device = $DeviceList[0] -adb -s $device logcat -c - -$deviceApi = "$(adb -s $device shell getprop ro.build.version.sdk)".Trim() -$deviceSdk = "$(adb -s $device shell getprop ro.build.version.release)".Trim() -Write-Log "`nChecking device $device with SDK '$deviceSdk' and API '$deviceApi'" - -# Uninstall previous installation -$stdout = adb -s $device shell "pm list packages -f" -if ($null -ne ($stdout | Select-String $ProcessName)) -{ - Write-Log "Uninstalling previous $ProcessName." - $stdout = adb -s $device uninstall $ProcessName -} - -# Move device to home screen -$stdout = adb -s $device shell input keyevent KEYCODE_HOME - -# Install the test app -$adbInstallRetry = 5 -do -{ - Write-Log "Installing test app" - $stdout = (adb -s $device install -r $BuildDir/$ApkFileName 2>&1) - - if ($stdout.Contains("Broken pipe")) - { - Write-Warning "Failed to comunicate with the Device, retrying..." - Start-Sleep 3 - $adbInstallRetry-- - } -} while ($adbInstallRetry -gt 1 -and $stdout.Contains("Broken pipe")) - -# Validate the installation -If ($stdout -contains "Success") -{ - Write-Log "Successfully installed APK" -} -else -{ - OnError $device $deviceApi - Write-Error "Failed to install APK: $stdout." - return 1 -} - -# Detect the launcher activity from the installed package -$dumpOutput = adb -s $device shell dumpsys package $ProcessName 2>&1 | Out-String -if ($dumpOutput -match "com.unity3d.player.UnityPlayerGameActivity") { - $TestActivityName = "$ProcessName/com.unity3d.player.UnityPlayerGameActivity" -} else { - $TestActivityName = "$ProcessName/com.unity3d.player.UnityPlayerActivity" -} -Write-Log "Detected activity: $TestActivityName" - -function ProcessNewLogs([array]$newLogs, [ref]$lastLogCount, [array]$logCache) { - if ($newLogs) { - $currentLogs = @($newLogs) # Force array creation even for single line - if ($currentLogs.Count -gt $lastLogCount.Value) { - $newLines = $currentLogs[$lastLogCount.Value..($currentLogs.Count-1)] - $lastLogCount.Value = $currentLogs.Count - - if ($newLines) { - $logCache += $newLines - } - } - } - return $logCache -} - -function RunTest([string] $Name, [string] $SuccessString, [string] $FailureString) -{ - Write-Host "::group::Test: '$name'" - - Write-Log "Clearing logcat from '$device'" - adb -s $device logcat -c - - # Force-stop any existing app instances to ensure clean state. - # This is especially important after crash tests where zombie processes may linger. - Write-Log "Force-stopping any existing '$ProcessName' instances" - adb -s $device shell am force-stop $ProcessName - Start-Sleep -Milliseconds 500 - - Write-Log "Setting configuration" - - # Mark the full-screen notification as acknowledged - adb -s $device shell "settings put secure immersive_mode_confirmations confirmed" - adb -s $device shell "input keyevent KEYCODE_HOME" - - Write-Log "Starting app '$TestActivityName'" - - # Start the adb command as a background job so we can wait for it to finish with a timeout - $job = Start-Job -ScriptBlock { - param($device, $activityName, $Name) - & adb -s $device shell am start -n $activityName -e test $Name -W 2>&1 - } -ArgumentList $device, $TestActivityName, $Name - - # Wait for the job to complete or to timeout - $completed = Wait-Job $job -Timeout 60 - if ($null -eq $completed) { - Stop-Job $job - Remove-Job $job -Force - Write-Log "Activity start timed out after 60 seconds" - return $false - } - - Receive-Job $job | Out-Null - Remove-Job $job - - Write-Log "Activity started successfully" - - $appPID = PidOf $device $ProcessName - if ($null -eq $appPID) - { - Write-Host "::endgroup::" - Write-Log "Retrieving process ID failed. Skipping test." -ForegroundColor Red - return $false - } - - Write-Log "Retrieved ID for '$ProcessName': $appPID" - - Write-Log "Waiting for tests to run..." - - $processFinished = $false - $logCache = @() - $startTime = Get-Date - $timeout = New-TimeSpan -Seconds 500 - $lastLogCount = 0 - - # Wait for the tests to run and the game process to complete - while ((Get-Date) - $startTime -lt $timeout) - { - $newLogs = adb -s $device logcat -d --pid=$appPID - $logCache = ProcessNewLogs -newLogs $newLogs -lastLogCount ([ref]$lastLogCount) -logCache $logCache - - # The SmokeTester logs "SmokeTester is quitting." in OnApplicationQuit() to reliably inform when tests finish running. - # For crash tests, we're checking for `sentry-native` logging "crash has been captured" to reliably inform when tests finished running. - if (($newLogs | Select-String "SmokeTester is quitting.") -or ($newLogs | Select-String "crash has been captured")) - { - Write-Log "Process finished marker detected. Finish waiting for tests to run." - $processFinished = $true - break - } - - Start-Sleep -Seconds 1 - } - - if ($processFinished) - { - Write-Log "'$Name' test finished running." - } - else - { - Write-Log "'$Name' tests timed out. See logcat for more details." - } - - Write-Host "::endgroup::" - - # Fetch the latest logs from the device - $logCache = ProcessNewLogs -newLogs $newLogs -lastLogCount ([ref]$lastLogCount) -logCache $logCache - - Write-Host "::group::logcat" - $logCache | ForEach-Object { Write-Host $_ } - Write-Host "::endgroup::" - - $lineWithSuccess = $logCache | Select-String $SuccessString - $lineWithFailure = $logCache | Select-String $FailureString - - if ($null -ne $lineWithSuccess) - { - Write-Log "'$Name' test passed." -ForegroundColor Green - return $true - } - elseif ($null -ne $lineWithFailure) - { - Write-Log "'$Name' test failed. See logcat for more details." -ForegroundColor Red - return $false - } - - Write-Log "'$Name' test execution failed." -ForegroundColor Red - return $false -} - -function RunTestWithRetry([string] $Name, [string] $SuccessString, [string] $FailureString, [int] $MaxRetries = 3) -{ - for ($retryCount = 0; $retryCount -lt $MaxRetries; $retryCount++) - { - if ($retryCount -gt 0) - { - Write-Log "Retry attempt $retryCount for test '$Name'" - Start-Sleep -Seconds 2 # Brief pause between retries - } - - Write-Log "Running test attempt $($retryCount + 1)/$MaxRetries" - $result = RunTest -Name $Name -SuccessString $SuccessString -FailureString $FailureString - - if ($result) - { - Write-Log "'$Name' test passed on attempt $($retryCount + 1)." -ForegroundColor Green - return $true - } - - if ($retryCount + 1 -lt $MaxRetries) - { - Write-Log "'$Name' test failed. Retrying..." -ForegroundColor Yellow - continue - } - - Write-Log "'$Name' test failed after $MaxRetries attempts." -ForegroundColor Red - return $false - } - - return $false -} - -$results = @{ - smokeTestPassed = $false - hasntCrashedTestPassed = $false - crashTestPassed = $false - hasCrashTestPassed = $false -} - -$results.smoketestPassed = RunTestWithRetry -Name "smoke" -SuccessString "SMOKE TEST: PASS" -FailureString "SMOKE TEST: FAIL" -MaxRetries 3 -$results.hasntCrashedTestPassed = RunTestWithRetry -Name "hasnt-crashed" -SuccessString "HASNT-CRASHED TEST: PASS" -FailureString "HASNT-CRASHED TEST: FAIL" -MaxRetries 3 - -try -{ - CrashTestWithServer -SuccessString "POST /api/12345/envelope/ HTTP/1.1`" 200 -b'1f8b08000000000000" -CrashTestCallback { - $results.crashTestPassed = RunTest -Name "crash" -SuccessString "CRASH TEST: Issuing a native crash" -FailureString "CRASH TEST: FAIL" - $results.hasCrashTestPassed = RunTest -Name "has-crashed" -SuccessString "HAS-CRASHED TEST: PASS" -FailureString "HAS-CRASHED TEST: FAIL" - } -} -catch -{ - Write-Log "Caught exception: $_" - Write-Log $_.ScriptStackTrace - OnError $device $deviceApi - exit 1 -} - -$failed = $false - -if (-not $results.smoketestPassed) -{ - Write-Log "Smoke test failed" - $failed = $true -} - -if (-not $results.hasntCrashedTestPassed) -{ - Write-Log "HasntCrashed test failed" - $failed = $true -} - -if (-not $results.crashTestPassed) -{ - Write-Log "Crash test failed" - $failed = $true -} - -if (-not $results.hasCrashTestPassed) -{ - Write-Log "HasCrashed test failed" - $failed = $true -} - -if ($failed) -{ - exit 1 -} - -Write-Log "All tests passed" -ForegroundColor Green -exit 0 diff --git a/scripts/smoke-test-ios.ps1 b/scripts/smoke-test-ios.ps1 deleted file mode 100644 index 9fb992984..000000000 --- a/scripts/smoke-test-ios.ps1 +++ /dev/null @@ -1,330 +0,0 @@ -param ( - [string] $Action, - [string] $SelectedRuntime, - [Int32] $DevicesToRun = 1, - [Switch] $IsIntegrationTest, - [string] $UnityVersion = "", - [string] $iOSMinVersion = "" -) -# $Action: 'Build' for build only -# 'Test' for Smoke test only -# null for building and testing -# $SelectedRuntime: the runtime to be run, -# 'latest' for runing the test with the latest runtime -# 'iOS ' to run on the specified runtime ex: iOS 12.4 -# $DevicesToRun: the amount of devices to run -# '0' or empty will run on 1 device, otherwise on the specified amount. - -. $PSScriptRoot/../test/Scripts.Integration.Test/common.ps1 - -Write-Log "Args received Action=$Action, SelectedRuntime=$SelectedRuntime, IsIntegrationTest=$IsIntegrationTest" - -$ProjectName = "Unity-iPhone" -$XcodeArtifactPath = "samples/IntegrationTest/Build" -$ArchivePath = "$XcodeArtifactPath/archive" -$AppPath = "$XcodeArtifactPath/IntegrationTest.app" -$AppName = "com.DefaultCompany.IntegrationTest" - -Class AppleDevice -{ - [String]$Name - [String]$UUID - [boolean]$TestFailed - [boolean]$TestSkipped - - Parse([String]$unparsedDevice) - { - # Example of unparsed device: - # iPhone 11 (D3762152-4648-4734-A409-15855F84587A) (Shutdown) - $unparsedDevice = $unparsedDevice.Trim() - $result = [regex]::Match($unparsedDevice, "(?.+) \((?[0-9A-Fa-f\-]{36})") - if ($result.Success -eq $False) - { - Throw "$unparsedDevice is not a valid iOS device" - } - $this.Name = $result.Groups["model"].Value - $this.UUID = $result.Groups["uuid"].Value - } -} - -function Build() -{ - MakeExecutable "$XcodeArtifactPath/MapFileParser.sh" - MakeExecutable "$XcodeArtifactPath/sentry-cli-Darwin-universal" - - $buildCallback = { - - Write-Host "::group::Building iOS project" - try - { - xcodebuild ` - -project "$XcodeArtifactPath/$ProjectName.xcodeproj" ` - -scheme "Unity-iPhone" ` - -configuration "Release" ` - -sdk "iphonesimulator" ` - -destination "platform=iOS Simulator,OS=$iOSMinVersion" ` - -destination "platform=iOS Simulator,OS=latest" ` - -parallel-testing-enabled YES ` - -derivedDataPath "$ArchivePath/$ProjectName" ` - | Write-Host - } - finally - { - Write-Host "::endgroup::" - } - } - - if ($IsIntegrationTest) - { - $symbolServerOutput = RunWithSymbolServer -Callback $buildCallback - CheckSymbolServerOutput 'IOS' $symbolServerOutput $UnityVersion - } - else - { - $buildCallback.Invoke() - } -} - -function Test -{ - Write-Log "Retrieving list of available simulators" -ForegroundColor Green - $deviceListRaw = xcrun simctl list devices - Write-Host "::group::Available simulators:" - $deviceListRaw | Write-Host - Write-Host "::endgroup::" - - [AppleDevice[]]$deviceList = @() - - Write-Log "Picking simulator based on selected runtime" -ForegroundColor Green - - # Find the index of the selected runtime - $runtimeIndex = ($deviceListRaw | Select-String "-- $SelectedRuntime --").LineNumber - If ($null -eq $runtimeIndex) - { - $deviceListRaw | Write-Host - throw " Runtime (-- $SelectedRuntime --) not found" - } - - foreach ($device in $deviceListRaw[$runtimeIndex..($deviceListRaw.Count - 1)]) - { - If ($device.StartsWith("--")) - { - # Reached at the end of the iOS list - break - } - $dev = [AppleDevice]::new() - $dev.Parse($device) - $deviceList += $dev - } - - $deviceCount = $DeviceList.Count - - Write-Host "::group::Found $deviceCount devices on version $SelectedRuntime" -ForegroundColor Green - ForEach ($device in $deviceList) - { - Write-Host "$($device.Name) - $($device.UUID)" - } - Write-Host "::endgroup::" - - $devicesRan = 0 - ForEach ($device in $deviceList) - { - If ($devicesRan -ge $DevicesToRun) - { - # Write-Log "Skipping Simulator $($device.Name) UUID $($device.UUID)" -ForegroundColor Green - $device.TestSkipped = $true - continue - } - $devicesRan++ - Write-Log "Starting Simulator $($device.Name) UUID $($device.UUID)" -ForegroundColor Green - xcrun simctl boot $($device.UUID) - Write-Log -NoNewline "Installing Smoke Test on $($device.Name): " - If (!(Test-Path $AppPath)) - { - Write-Error "App doesn't exist at the expected path $AppPath. Did you forget to run Build first?" - } - xcrun simctl install $($device.UUID) "$AppPath" - Write-Log "OK" -ForegroundColor Green - - function RunTest([string] $Name, [string] $SuccessString, [int] $TimeoutSeconds = 180) - { - Write-Log "Test: '$Name'" - Write-Log "Launching '$Name' test on '$($device.Name)'" -ForegroundColor Green - - # Use Start-Process with output redirection and timeout to prevent hanging - # when the app crashes (which is expected for crash tests) - $outFile = New-TemporaryFile - $errFile = New-TemporaryFile - $consoleOut = @() - - try - { - $process = Start-Process "xcrun" ` - -ArgumentList "simctl", "launch", "--console-pty", $device.UUID, $AppName, "--test", $Name ` - -NoNewWindow -PassThru ` - -RedirectStandardOutput $outFile ` - -RedirectStandardError $errFile - - $timedOut = $null - $process | Wait-Process -Timeout $TimeoutSeconds -ErrorAction SilentlyContinue -ErrorVariable timedOut - - if ($timedOut) - { - Write-Log "Test '$Name' timed out after $TimeoutSeconds seconds - stopping process" -ForegroundColor Yellow - $process | Stop-Process -Force -ErrorAction SilentlyContinue - } - - # Read captured output - $consoleOut = @(Get-Content $outFile -ErrorAction SilentlyContinue) + ` - @(Get-Content $errFile -ErrorAction SilentlyContinue) - } - finally - { - Remove-Item $outFile -ErrorAction SilentlyContinue - Remove-Item $errFile -ErrorAction SilentlyContinue - } - - if ("$SuccessString" -eq "") - { - $SuccessString = "$($Name.ToUpper()) TEST: PASS" - } - - Write-Host "::group::$($device.Name) Console Output" - foreach ($consoleLine in $consoleOut) - { - Write-Host $consoleLine - } - Write-Host "::endgroup::" - - Write-Log -NoNewline "'$Name' test STATUS: " - $stdout = $consoleOut | Select-String $SuccessString - If ($null -ne $stdout) - { - Write-Log "PASSED" -ForegroundColor Green - } - Else - { - $device.TestFailed = $True - Write-Log "FAILED" -ForegroundColor Red - throw "Test '$Name' failed - success string '$SuccessString' not found in output" - } - } - - function RunTestSuiteWithRetry([int] $MaxRetries = 3) - { - for ($attempt = 1; $attempt -le $MaxRetries; $attempt++) - { - Write-Log "Test suite attempt $attempt/$MaxRetries" -ForegroundColor Cyan - - if ($attempt -gt 1) - { - # Reset simulator state between retries - Write-Log "Reinstalling app to reset state..." -ForegroundColor Yellow - xcrun simctl uninstall $($device.UUID) $AppName 2>$null - Start-Sleep -Seconds 2 - xcrun simctl install $($device.UUID) "$AppPath" - Start-Sleep -Seconds 2 - } - - $device.TestFailed = $false - - try - { - RunTest "smoke" - RunTest "hasnt-crashed" - - # Note: mobile apps post the crash on the second app launch, so we must run both as part of the "CrashTestWithServer" - CrashTestWithServer -SuccessString "POST /api/12345/envelope/ HTTP/1.1`" 200 -b'1f8b08000000000000" -CrashTestCallback { - RunTest "crash" "CRASH TEST: Issuing a native crash" - RunTest "has-crashed" - } - - Write-Log "All tests passed on attempt $attempt/$MaxRetries" -ForegroundColor Green - return # Success! - } - catch - { - Write-Log "Test suite attempt $attempt failed: $_" -ForegroundColor Yellow - - if ($attempt -lt $MaxRetries) - { - Write-Log "Will retry..." -ForegroundColor Yellow - continue - } - - # Final attempt failed - throw - } - } - } - - RunTestSuiteWithRetry -MaxRetries 3 - - Write-Log -NoNewline "Removing Smoke Test from $($device.Name): " - xcrun simctl uninstall $($device.UUID) $AppName - Write-Log "OK" -ForegroundColor Green - - Write-Log -NoNewline "Requesting shutdown for $($device.Name): " - # Do not wait for the Simulator to close and continue testing the other simulators. - Start-Process xcrun -ArgumentList "simctl shutdown `"$($device.UUID)`"" - Write-Log "OK" -ForegroundColor Green - } - - $testFailed = $false - Write-Log "Test result" - foreach ($device in $deviceList) - { - Write-Log -NoNewline "$($device.Name) UUID $($device.UUID): " - If ($device.TestFailed) - { - Write-Log "FAILED" -ForegroundColor Red - $testFailed = $true - } - ElseIf ($device.TestSkipped) - { - Write-Log "SKIPPED" -ForegroundColor Gray - } - Else - { - Write-Log "PASSED" -ForegroundColor Green - } - } - Write-Log "End of test." - - If ($testFailed -eq $true) - { - Throw "One or more tests failed." - } -} - -function LatestRuntime -{ - $runtimes = xcrun simctl list runtimes iOS - $lastRuntime = $runtimes | Select-Object -Last 1 - $result = [regex]::Match($lastRuntime, "(?iOS [0-9.]+)") - if ($result.Success -eq $False) - { - Throw "Last runtime was not found, result: $result" - } - $lastRuntimeParsed = $result.Groups["runtime"].Value - Write-Log "Using latest runtime = $lastRuntimeParsed" - return $lastRuntimeParsed -} - -# MAIN -If (-not $IsMacOS) -{ - Write-Log "This script should only be run on a MacOS." -ForegroundColor Yellow -} -If ($null -eq $action -Or $action -eq "Build") -{ - Build -} -If ($null -eq $action -Or $action -eq "Test") -{ - If ($SelectedRuntime -eq "latest" -Or $null -eq $SelectedRuntime) - { - $SelectedRuntime = LatestRuntime - } - Test -} diff --git a/test/Scripts.Integration.Test/Scripts/CliConfiguration.cs b/test/Scripts.Integration.Test/Scripts/CliConfiguration.cs index 51720bccf..94ebe9e17 100644 --- a/test/Scripts.Integration.Test/Scripts/CliConfiguration.cs +++ b/test/Scripts.Integration.Test/Scripts/CliConfiguration.cs @@ -1,7 +1,6 @@ using System; using Sentry.Unity; using UnityEngine; -using UnityEngine.Assertions; public class CliConfiguration : SentryCliOptionsConfiguration { @@ -10,20 +9,9 @@ public override void Configure(SentryCliOptions cliOptions) Debug.Log("Sentry: CliConfiguration::Configure() called"); var authToken = Environment.GetEnvironmentVariable("SENTRY_AUTH_TOKEN"); - if (!string.IsNullOrEmpty(authToken)) - { - // Upload to real Sentry using the auth token from the environment. - cliOptions.UploadSymbols = true; - cliOptions.UploadSources = true; - cliOptions.Auth = authToken; - } - else - { - // Upload to a local symbol server for verification (smoke tests). - cliOptions.UploadSymbols = !string.IsNullOrEmpty(cliOptions.UrlOverride); - cliOptions.UploadSources = cliOptions.UploadSymbols; - cliOptions.Auth = "dummy-token"; - } + cliOptions.UploadSymbols = !string.IsNullOrEmpty(authToken); + cliOptions.UploadSources = cliOptions.UploadSymbols; + cliOptions.Auth = authToken; cliOptions.Organization = "sentry-sdks"; cliOptions.Project = "sentry-unity"; diff --git a/test/Scripts.Integration.Test/build-project.ps1 b/test/Scripts.Integration.Test/build-project.ps1 index b87f98ec2..35a287637 100644 --- a/test/Scripts.Integration.Test/build-project.ps1 +++ b/test/Scripts.Integration.Test/build-project.ps1 @@ -2,7 +2,6 @@ param( [string] $UnityPath, [string] $Platform = "", [string] $UnityVersion = "", - [Switch] $CheckSymbols, [string] $BuildDirName = "Build" ) @@ -22,15 +21,7 @@ Write-Log "Build method: $buildMethod" Write-Detail "Output path: $outputPath" $unityArgs = @("-batchmode", "-projectPath ", "$(GetNewProjectPath)", "-executeMethod", $buildMethod , "-buildPath", $outputPath, "-quit") -if ($CheckSymbols) -{ - $symbolServerOutput = RunWithSymbolServer -Callback { RunUnityCustom $unityPath $unityArgs } - CheckSymbolServerOutput $buildMethod $symbolServerOutput $UnityVersion -} -else -{ - RunUnityCustom $unityPath $unityArgs -} +RunUnityCustom $unityPath $unityArgs if ($Platform -eq "Android-Export") { diff --git a/test/Scripts.Integration.Test/common.ps1 b/test/Scripts.Integration.Test/common.ps1 index df147d90c..a0f899306 100644 --- a/test/Scripts.Integration.Test/common.ps1 +++ b/test/Scripts.Integration.Test/common.ps1 @@ -73,316 +73,6 @@ function Write-PhaseFailed Write-Host "$timestamp | [FAILED] $Message" -ForegroundColor Red } -function RunApiServer([string] $ServerScript, [string] $Uri) -{ - if ([string]::IsNullOrEmpty($Uri)) - { - $Uri = SymbolServerUrlFor ((Test-Path variable:UnityPath) ? $UnityPath : '') - } - - $result = "" | Select-Object -Property process, outFile, errFile, stop, output, dispose - Write-Log "Starting the $ServerScript on $Uri" - $result.outFile = New-TemporaryFile - $result.errFile = New-TemporaryFile - - $result.process = Start-Process "python3" -ArgumentList @("$PSScriptRoot/$ServerScript.py", $Uri) ` - -NoNewWindow -PassThru -RedirectStandardOutput $result.outFile -RedirectStandardError $result.errFile - - $result.output = { "$(Get-Content $result.outFile -Raw)`n$(Get-Content $result.errFile -Raw)" }.GetNewClosure() - - $result.dispose = { - $result.stop.Invoke() - - Write-Host "::group:: Server stdout" -ForegroundColor Yellow - $stdout = Get-Content $result.outFile -Raw - Write-Host $stdout - Write-Host "::endgroup::" - - Write-Host "::group:: Server stderr" -ForegroundColor Yellow - $stderr = Get-Content $result.errFile -Raw - Write-Host $stderr - Write-Host "::endgroup::" - - Remove-Item $result.outFile -ErrorAction Continue - Remove-Item $result.errFile -ErrorAction Continue - return "$stdout`n$stderr" - }.GetNewClosure() - - # Capture Write-Log function so it can be used inside the closure (GetNewClosure captures variables but not functions) - $writeLog = ${function:Write-Log} - $result.stop = { - # Stop the HTTP server - & $writeLog "Stopping the $ServerScript ... " -NoNewline - try - { - Write-Host (Invoke-WebRequest -Uri "$Uri/STOP").StatusDescription - } - catch - { - & $writeLog "/STOP request failed: $_ - killing the server process instead" - $result.process | Stop-Process -Force -ErrorAction SilentlyContinue - } - $result.process | Wait-Process -Timeout 10 -ErrorAction Continue - $result.stop = {} - }.GetNewClosure() - - # The process shouldn't finish by itself, if it did, there was an error, so let's check that - Start-Sleep -Second 1 - if ($result.process.HasExited) - { - Write-Log "Couldn't start the $ServerScript" -ForegroundColor Red - Write-Log "Standard Output:" -ForegroundColor Yellow - Get-Content $result.outFile - Write-Log "Standard Error:" -ForegroundColor Yellow - Get-Content $result.errFile - Remove-Item $result.outFile - Remove-Item $result.errFile - exit 1 - } - - return $result -} - -function CrashTestWithServer([ScriptBlock] $CrashTestCallback, [string] $SuccessString) -{ - if ("$SuccessString" -eq "") - { - throw "SuccessString cannot be empty" - } - - Write-Log "Running crash test with server" -ForegroundColor Yellow - - # Retry on server communication issues (success string not found in output). - # Callback exceptions are re-thrown immediately for the caller to handle. - if ($null -eq $env:CI) - { - $runs = 1 - $timeout = 5 - } - else - { - $runs = 5 - $timeout = 30 - } - - for ($run = 1; $run -le $runs; $run++) - { - if ($run -ne 1) - { - Write-Log "Sleeping for $run seconds before the next retry..." - Start-Sleep -Seconds $run - } - - # start the server - $httpServer = RunApiServer "crash-test-server" - - # run the test - callback exceptions are re-thrown immediately for the caller to handle - # (e.g., suite-level retry with app reinstall). Only server issues trigger internal retry. - try - { - $CrashTestCallback.Invoke() - } - catch - { - $httpServer.stop.Invoke() - throw - } - - # evaluate the result - for ($i = $timeout; $i -gt 0; $i--) - { - Write-Log "Waiting for the expected message to appear in the server output logs; $i seconds remaining..." - if ("$($httpServer.output.Invoke())".Contains($SuccessString)) - { - break - } - Start-Sleep -Milliseconds 1000 - } - - $output = $httpServer.dispose.Invoke() - Write-Log "Looking for the SuccessString ($SuccessString) in the server output..." - if ("$output".Contains($SuccessString)) - { - Write-Log "crash test $run/$runs : PASSED" -ForegroundColor Green - break - } - Write-Log "SuccessString ($SuccessString) not found..." -ForegroundColor Red - if ($run -eq $runs) - { - throw "crash test $run/$runs : FAILED" - } - else - { - Write-Warning "crash test $run/$runs : FAILED, retrying" - } - } -} - -function SymbolServerUrlFor([string] $UnityPath, [string] $Platform = "") -{ - # Note: Android and iOS have special handling - even though the project is exported while running in docker, - # the actual build and thus the symbol-server test runs later, on a different machine, outside docker. - # Therefore, we return localhost regardless of building in docker. - (!$UnityPath.StartsWith("docker ") -or ($Platform -eq "iOS") -or ($Platform -eq "Android-Export")) ` - ? 'http://localhost:8000' : 'http://172.17.0.1:8000' -} - -function RunWithSymbolServer([ScriptBlock] $Callback) -{ - # start the server - $httpServer = RunApiServer "symbol-upload-server" - - # run the test - try - { - $Callback.Invoke() - } - finally - { - $httpServer.stop.Invoke() - } - - return $httpServer.dispose.Invoke() -} - -function CheckSymbolServerOutput([string] $buildMethod, [string] $symbolServerOutput, [string] $unityVersion) -{ - Write-Log "Checking symbol server output" -ForegroundColor Yellow - - # Server stats contains: - # filename - # count = the number of occurrences of the same file name during upload, - # chunks = the total number of chunks over all occurrences of a file. - # We don't check the number of chunks because it depends on the file size. - $expectedFiles = @() - $unity_6000_3_OrHigher = $unityVersion -match "6000\.[3-9]" - - # Currently we only test symbol upload with sources, but we want to keep the values below to also test without in the future. - # We can have up to 4 different types of files grouped under one name: - # * the executable itself - # * the corresponding debug file - # * the sources if requested - # * the resolved il2cpp line mapping file - # For Platforms that pack two different architectures (x64 and arm64 for example) - # into one file, these numbers are doubled. - $withSources = $true - If ($buildMethod.contains('Mac')) - { - $expectedFiles = @( - "GameAssembly.dylib: count=$($withSources ? 8 : 6)", - 'IntegrationTest: count=2', - 'Sentry.dylib: count=2', - "Sentry.dylib.dSYM: count=$($withSources ? 4 : 2)", - 'UnityPlayer.dylib: count=2' - ) - } - ElseIf ($buildMethod.contains('Windows')) - { - $expectedFiles = @( - 'GameAssembly.dll: count=1', - "GameAssembly.pdb: count=$($withSources ? 3 : 2)", - 'sentry.dll: count=1', - "sentry.pdb: count=$($withSources ? 2 : 1)", - 'test.exe: count=1', - 'UnityPlayer.dll: count=1' - ) - } - ElseIf ($buildMethod.contains('Linux')) - { - $expectedFiles = @( - 'GameAssembly.so: count=1', - 'UnityPlayer.so: count=1', - 'UnityPlayer_s.debug: count=1', - "libsentry.dbg.so: count=$($withSources ? 2 : 1)", - 'test: count=1', - 'test_s.debug: count=1' - ) - } - ElseIf ($buildMethod.contains('Android')) - { - $expectedFiles = @( - 'libil2cpp.so: count=1', - 'libmain.so: count=1', - 'libsentry-android.so: count=1', - 'libsentry.so: count=1', - 'libunity.so: count=1', - 'libunity.sym.so: count=1' - ) - } - ElseIf ($buildMethod.contains('IOS')) - { - if ($unity_6000_3_OrHigher) - { - # Unity 6.3+ removed libiPhone-lib.dylib and Sentry is arm64-only - $expectedFiles = @( - "IntegrationTest: count=$($withSources ? 3 : 2)", - 'Sentry: count=4', - "UnityFramework: count=$($withSources ? 5 : 4)" - ) - } - else - { - # Unity 2021 - 6000.2 - $expectedFiles = @( - "IntegrationTest: count=$($withSources ? 3 : 2)", - 'Sentry: count=8', - "UnityFramework: count=$($withSources ? 5 : 4)", - 'libiPhone-lib.dylib: count=1' - ) - } - } - ElseIf ($buildMethod.contains('WebGL')) - { - Write-Log 'No symbols are uploaded for WebGL - nothing to test.' -ForegroundColor Yellow - return - } - ElseIf ($buildMethod.contains('Switch')) - { - $expectedFiles = @( - 'GameAssembly.nss: count=1' - ) - } - Else - { - Throw "Cannot CheckSymbolServerOutput() for an unknown buildMethod: '$buildMethod'" - } - - Write-Log 'Verifying debug symbol upload...' - $successful = $true - :nextExpectedFile foreach ($file in $expectedFiles) - { - $alternatives = ($file -is [array]) ? $file : @($file) - foreach ($file in $alternatives) - { - # It's enough if a single symbol alternative is found - if ($symbolServerOutput -match " $([Regex]::Escape($file))\b") - { - Write-Log " $file - OK" - continue nextExpectedFile - } - } - # Note: control only gets here if none of the alternatives match... - $fileWithoutCount = $file.Substring(0, $file.Length - 1) - $filePattern = [Regex]::new('(?<=' + "$([Regex]::Escape($fileWithoutCount))" + ')[\w]+') - $actualCount = $filePattern.Matches($symbolServerOutput) - - if ("$actualCount" -eq "") - { - $successful = $false - } - - Write-Log " $alternatives - MISSING `n Server received '$actualCount' instead." -ForegroundColor Red - } - if ($successful) - { - Write-Log 'All expected debug symbols have been uploaded' -ForegroundColor Green - } - else - { - exit 1 - } -} - function RunUnityAndExpect([string] $UnityPath, [string] $name, [string] $successMessage, [string[]] $arguments) { $stdout = RunUnityCustom $UnityPath $arguments -ReturnLogOutput diff --git a/test/Scripts.Integration.Test/configure-sentry.ps1 b/test/Scripts.Integration.Test/configure-sentry.ps1 index a257891b3..21c04aa26 100644 --- a/test/Scripts.Integration.Test/configure-sentry.ps1 +++ b/test/Scripts.Integration.Test/configure-sentry.ps1 @@ -1,7 +1,6 @@ param( [string] $UnityPath, - [string] $Platform = "", - [Switch] $CheckSymbols + [string] $Platform = "" ) if (-not $Global:NewProjectPathCache) @@ -19,8 +18,7 @@ $unityArgs = @( ` "-quit", "-batchmode", "-nographics", "-disable-assembly-updater", "-projectPath ", $(GetNewProjectPath), ` "-executeMethod", "Sentry.Unity.Editor.ConfigurationWindow.SentryEditorWindowInstrumentation.ConfigureOptions", ` "-optionsScript", "IntegrationOptionsConfiguration", ` - "-cliOptionsScript", "CliConfiguration", ` - "-cliOptions.UrlOverride", ($CheckSymbols ? (SymbolServerUrlFor $UnityPath $Platform) : "") ) + "-cliOptionsScript", "CliConfiguration" ) if ($env:SENTRY_DSN) { $unityArgs += @("-dsn", $env:SENTRY_DSN) diff --git a/test/Scripts.Integration.Test/crash-test-server.py b/test/Scripts.Integration.Test/crash-test-server.py deleted file mode 100644 index 08cd2589f..000000000 --- a/test/Scripts.Integration.Test/crash-test-server.py +++ /dev/null @@ -1,49 +0,0 @@ -#!/usr/bin/env python3 - -from http import HTTPStatus -from http.server import BaseHTTPRequestHandler, ThreadingHTTPServer -from urllib.parse import urlparse -import sys -import threading -import binascii - - -class Handler(BaseHTTPRequestHandler): - def commonServe(self): - self.send_response(200, "") - self.end_headers() - sys.stdout.flush() - sys.stderr.flush() - - if (self.path == "/STOP"): - print("HTTP server stopping!") - threading.Thread(target=self.server.shutdown).start() - - def do_GET(self): - self.commonServe() - - def do_POST(self): - self.commonServe() - - # override - def log_request(self, code='-', size='-'): - if isinstance(code, HTTPStatus): - code = code.value - body = "" - if self.command == "POST" and 'Content-Length' in self.headers: - content_length = int(self.headers['Content-Length']) - content = self.rfile.read(content_length) - try: - body = content.decode("utf-8") - except: - body = binascii.hexlify(bytearray(content)) - body = body[0:min(1000, len(body))] - self.log_message('"%s" %s %s%s', - self.requestline, str(code), str(size), body) - - -uri = urlparse(sys.argv[1] if len(sys.argv) > 1 else 'http://127.0.0.1:8000') -print("HTTP server listening on {}".format(uri.geturl())) -print("To stop the server, execute a GET request to {}/STOP".format(uri.geturl())) -httpd = ThreadingHTTPServer((uri.hostname, uri.port), Handler) -target = httpd.serve_forever() diff --git a/test/Scripts.Integration.Test/integration-test.ps1 b/test/Scripts.Integration.Test/integration-test.ps1 index 50b1544b9..37c4a4783 100644 --- a/test/Scripts.Integration.Test/integration-test.ps1 +++ b/test/Scripts.Integration.Test/integration-test.ps1 @@ -11,8 +11,7 @@ param( [string] $NativeSDKPath, [switch] $Recreate, [switch] $Rebuild, - [switch] $SkipTests, - [switch] $CheckSymbols + [switch] $SkipTests ) if (-not $Global:NewProjectPathCache) { @@ -47,7 +46,7 @@ If (-not(Test-Path -Path "$(GetNewProjectPath)")) { Write-PhaseSuccess "Sentry added" Write-PhaseHeader "Configuring Sentry" - ./test/Scripts.Integration.Test/configure-sentry.ps1 "$UnityPath" -Platform $Platform -CheckSymbols:$CheckSymbols + ./test/Scripts.Integration.Test/configure-sentry.ps1 "$UnityPath" -Platform $Platform Write-PhaseSuccess "Sentry configured" } @@ -71,10 +70,10 @@ If ($Rebuild -or -not(Test-Path -Path $(GetNewProjectBuildPath))) { If ("iOS" -eq $Platform) { # We're exporting an Xcode project and building that in a separate step. ./test/Scripts.Integration.Test/build-project.ps1 -UnityPath "$UnityPath" -UnityVersion $UnityVersion -Platform $Platform - & "./scripts/smoke-test-ios.ps1" Build -IsIntegrationTest -UnityVersion $UnityVersion + & "./scripts/compile-xcode-project.ps1" } Else { - ./test/Scripts.Integration.Test/build-project.ps1 -UnityPath "$UnityPath" -CheckSymbols:$CheckSymbols -UnityVersion $UnityVersion -Platform $Platform + ./test/Scripts.Integration.Test/build-project.ps1 -UnityPath "$UnityPath" -UnityVersion $UnityVersion -Platform $Platform } Write-PhaseSuccess "Project built" } diff --git a/test/Scripts.Integration.Test/symbol-upload-server.py b/test/Scripts.Integration.Test/symbol-upload-server.py deleted file mode 100644 index 04545d120..000000000 --- a/test/Scripts.Integration.Test/symbol-upload-server.py +++ /dev/null @@ -1,136 +0,0 @@ -#!/usr/bin/env python3 - -from http import HTTPStatus -from http.server import BaseHTTPRequestHandler, ThreadingHTTPServer -from urllib.parse import urlparse -from threading import Thread, Lock -import sys -import threading -import binascii -import json - -apiOrg = 'sentry-sdks' -apiProject = 'sentry-unity' -uri = urlparse(sys.argv[1] if len(sys.argv) > 1 else 'http://127.0.0.1:8000') -uploads = {} -lock = Lock() - - -def registerUpload(name: str, chunks: int): - lock.acquire() - try: - if name not in uploads: - uploads[name] = \ - {'count': 1, 'chunks': chunks} - else: - uploads[name]['count'] += 1 - uploads[name]['chunks'] += chunks - finally: - lock.release() - - -class Handler(BaseHTTPRequestHandler): - body = None - - def do_GET(self): - self.start_response(HTTPStatus.OK) - - if self.path == "/STOP": - print("HTTP server stopping!") - threading.Thread(target=self.server.shutdown).start() - - if self.isApi('api/0/organizations/{}/chunk-upload/'.format(apiOrg)): - self.writeJSON('{"url":"' + uri.geturl() + self.path + '",' - '"chunkSize":8388608,"chunksPerRequest":64,"maxFileSize":2147483648,' - '"maxRequestSize":33554432,"concurrency":1,"hashAlgorithm":"sha1","compression":["gzip"],' - '"accept":["debug_files","release_files","pdbs","sources","bcsymbolmaps"]}') - else: - self.end_headers() - - self.flushLogs() - - def do_POST(self): - self.start_response(HTTPStatus.OK) - - if self.isApi('api/0/projects/{}/{}/files/difs/assemble/'.format(apiOrg, apiProject)): - # Request body example: - # { - # "9a01653a...":{"name":"UnityPlayer.dylib","debug_id":"eb4a7644-...","chunks":["f84d3907945cdf41b33da8245747f4d05e6ffcb4", ...]}, - # "4185e454...":{"name":"UnityPlayer.dylib","debug_id":"86d95b40-...","chunks":[...]} - # } - # Response body to let the CLI know we have the symbols already (we don't need to test the actual upload): - # { - # "9a01653a...":{"state":"ok","missingChunks":[]}, - # "4185e454...":{"state":"ok","missingChunks":[]} - # } - jsonRequest = json.loads(self.body) - jsonResponse = '{' - for key, value in jsonRequest.items(): - jsonResponse += '"{}"'.format(key) - jsonResponse += ':{"state":"ok","missingChunks":[]},' - registerUpload(value['name'], len(value['chunks'])) - jsonResponse = jsonResponse.rstrip(',') + '}' - self.writeJSON(jsonResponse) - elif self.isApi('api/0/projects/{}/{}/files/dsyms/'.format(apiOrg, apiProject)): - self.writeJSON('[]') - elif self.isApi('api/0/projects/{}/{}/reprocessing/'.format(apiOrg, apiProject)): - self.writeJSON('{}') - else: - self.end_headers() - - self.flushLogs() - - def start_response(self, code): - self.body = None - self.log_request(code) - self.send_response_only(code) - - def log_request(self, code=None, size=None): - if isinstance(code, HTTPStatus): - code = code.value - body = self.body = self.requestBody() - if body: - body = self.body[0:min(1000, len(body))] - self.log_message('"%s" %s %s%s', - self.requestline, str(code), "({} bytes)".format(size) if size else '', body) - - # Note: this may only be called once during a single request - can't `.read()` the same stream again. - def requestBody(self): - if self.command == "POST" and 'Content-Length' in self.headers: - length = int(self.headers['Content-Length']) - content = self.rfile.read(length) - try: - return content.decode("utf-8") - except: - return binascii.hexlify(bytearray(content)) - return None - - def isApi(self, api: str): - if self.path.strip('/') == api.strip('/'): - self.log_message("Matched API endpoint {}".format(api)) - return True - return False - - def writeJSON(self, string: str): - self.send_header("Content-type", "application/json") - self.end_headers() - self.wfile.write(str.encode(string)) - - def flushLogs(self): - sys.stdout.flush() - sys.stderr.flush() - - -print("HTTP server listening on {}".format(uri.geturl())) -print("To stop the server, execute a GET request to {}/STOP".format(uri.geturl())) - -try: - httpd = ThreadingHTTPServer((uri.hostname, uri.port), Handler) - target = httpd.serve_forever() -except KeyboardInterrupt: - pass -finally: - print('Upload stats:') - for k in sorted(uploads): - v = uploads[k] - print(' {}: count={} chunks={}'.format(k, v['count'], v['chunks'])) From 9c00c6cfedb5b134a39c24d3ae65ef5517958d76 Mon Sep 17 00:00:00 2001 From: bitsandfoxes Date: Thu, 5 Mar 2026 18:52:19 +0100 Subject: [PATCH 78/86] Trigger CI From 747d68b150a28e32c0d4788f442174af4063529a Mon Sep 17 00:00:00 2001 From: bitsandfoxes Date: Fri, 6 Mar 2026 14:25:06 +0100 Subject: [PATCH 79/86] . --- .github/workflows/ci.yml | 1 - .github/workflows/smoke-test-run-ios.yml | 2 +- 2 files changed, 1 insertion(+), 2 deletions(-) diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml index 6ec9ffc22..cc031f721 100644 --- a/.github/workflows/ci.yml +++ b/.github/workflows/ci.yml @@ -310,7 +310,6 @@ jobs: secrets: inherit strategy: fail-fast: false - max-parallel: 2 matrix: unity-version: ${{ fromJSON(needs.create-unity-matrix.outputs.unity-matrix).unity-version }} ios-version: ["17.0", "latest"] diff --git a/.github/workflows/smoke-test-run-ios.yml b/.github/workflows/smoke-test-run-ios.yml index 721c4a3ec..7d53b345c 100644 --- a/.github/workflows/smoke-test-run-ios.yml +++ b/.github/workflows/smoke-test-run-ios.yml @@ -38,7 +38,7 @@ jobs: steps: - name: Checkout - uses: actions/checkout@11bd71901bbe5b1630ceea73d27597364c9af683 # v4 + uses: actions/checkout@f43a0e5ff2bd294095638e18286ca9a3d1956744 # v3 - name: Initialize app-runner submodule run: git submodule update --init modules/app-runner From 95f4094b065267a231b92c13f3d6d0de645c4059 Mon Sep 17 00:00:00 2001 From: bitsandfoxes Date: Tue, 10 Mar 2026 20:18:10 -0700 Subject: [PATCH 80/86] cleanup --- .github/workflows/ci.yml | 88 +++++++++---------- ...ild-android.yml => test-build-android.yml} | 4 +- ...-test-build-ios.yml => test-build-ios.yml} | 4 +- ...t-build-linux.yml => test-build-linux.yml} | 4 +- ...ild-windows.yml => test-build-windows.yml} | 4 +- ...t-compile-ios.yml => test-compile-ios.yml} | 4 +- ...{smoke-test-create.yml => test-create.yml} | 4 +- ...t-run-android.yml => test-run-android.yml} | 2 +- ...t-run-desktop.yml => test-run-desktop.yml} | 2 +- ...moke-test-run-ios.yml => test-run-ios.yml} | 2 +- ...-test-run-webgl.yml => test-run-webgl.yml} | 2 +- CLAUDE.md | 17 ++-- CONTRIBUTING.md | 2 +- Directory.Build.targets | 12 --- .../SentryEditorWindowInstrumentation.cs | 6 -- .../Editor/Builder.cs | 10 +-- .../Scenes/{SmokeTest.unity => Test.unity} | 0 test/Scripts.Integration.Test/common.ps1 | 2 +- 18 files changed, 74 insertions(+), 95 deletions(-) rename .github/workflows/{smoke-test-build-android.yml => test-build-android.yml} (98%) rename .github/workflows/{smoke-test-build-ios.yml => test-build-ios.yml} (98%) rename .github/workflows/{smoke-test-build-linux.yml => test-build-linux.yml} (98%) rename .github/workflows/{smoke-test-build-windows.yml => test-build-windows.yml} (98%) rename .github/workflows/{smoke-test-compile-ios.yml => test-compile-ios.yml} (98%) rename .github/workflows/{smoke-test-create.yml => test-create.yml} (96%) rename .github/workflows/{smoke-test-run-android.yml => test-run-android.yml} (99%) rename .github/workflows/{smoke-test-run-desktop.yml => test-run-desktop.yml} (98%) rename .github/workflows/{smoke-test-run-ios.yml => test-run-ios.yml} (98%) rename .github/workflows/{smoke-test-run-webgl.yml => test-run-webgl.yml} (97%) rename test/Scripts.Integration.Test/Scenes/{SmokeTest.unity => Test.unity} (100%) diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml index 97b2dc638..a3a625f43 100644 --- a/.github/workflows/ci.yml +++ b/.github/workflows/ci.yml @@ -85,24 +85,24 @@ jobs: # ./test/Scripts.Tests/test-pack-contents.ps1 accept run: ./test/Scripts.Tests/test-pack-contents.ps1 - # This produces the `samples/IntegrationTest` as `smoke-test-${{ matrix.unity-version }}`. - smoke-test-create: - name: Create ${{ matrix.unity-version }} Smoke Test Project + # This produces the `samples/IntegrationTest` as `test-${{ matrix.unity-version }}`. + test-create: + name: Create ${{ matrix.unity-version }} Test Project if: ${{ !startsWith(github.ref, 'refs/heads/release/') }} needs: [create-unity-matrix] secrets: inherit strategy: fail-fast: false matrix: ${{ fromJSON(needs.create-unity-matrix.outputs.unity-matrix) }} - uses: ./.github/workflows/smoke-test-create.yml + uses: ./.github/workflows/test-create.yml with: unity-version: ${{ matrix.unity-version }} - - # A Linux, docker-based build to prepare a WebGL player. The tests run in `smoke-test-run`. - smoke-test-build-webgl: - name: Build ${{ matrix.platform }} ${{ matrix.unity-version }} Smoke Test + + # A Linux, docker-based build to prepare a WebGL player. The tests run in `test-run-webgl`. + test-build-webgl: + name: Build ${{ matrix.platform }} ${{ matrix.unity-version }} Test if: ${{ !startsWith(github.ref, 'refs/heads/release/') }} - needs: [smoke-test-create, create-unity-matrix] + needs: [test-create, create-unity-matrix] runs-on: ubuntu-latest strategy: fail-fast: false @@ -148,7 +148,7 @@ jobs: - name: Download IntegrationTest project uses: actions/download-artifact@d3f86a106a0bac45b974a628896c90dbdf5c8093 # v4 with: - name: smoke-test-${{ matrix.unity-version }} + name: test-${{ matrix.unity-version }} - name: Extract project archive run: tar -xvzf test-project.tar.gz @@ -239,24 +239,24 @@ jobs: # Lower retention period - we only need this to retry CI. retention-days: 14 - smoke-test-build-android: - name: Build Android ${{ matrix.unity-version }} Smoke Test + test-build-android: + name: Build Android ${{ matrix.unity-version }} Test if: ${{ !startsWith(github.ref, 'refs/heads/release/') }} - needs: [smoke-test-create, create-unity-matrix] + needs: [test-create, create-unity-matrix] secrets: inherit strategy: fail-fast: false matrix: ${{ fromJSON(needs.create-unity-matrix.outputs.unity-matrix) }} - uses: ./.github/workflows/smoke-test-build-android.yml + uses: ./.github/workflows/test-build-android.yml with: unity-version: ${{ matrix.unity-version }} - smoke-test-run-android: + test-run-android: name: Run Android ${{ matrix.unity-version }} Integration Test if: ${{ !startsWith(github.ref, 'refs/heads/release/') }} - needs: [smoke-test-build-android, create-unity-matrix] + needs: [test-build-android, create-unity-matrix] secrets: inherit - uses: ./.github/workflows/smoke-test-run-android.yml + uses: ./.github/workflows/test-run-android.yml with: unity-version: ${{ matrix.unity-version }} api-level: ${{ matrix.api-level }} @@ -269,38 +269,38 @@ jobs: api-level: [30] init-type: ["runtime", "buildtime"] - smoke-test-build-ios: - name: Build iOS ${{ matrix.unity-version }} Smoke Test + test-build-ios: + name: Build iOS ${{ matrix.unity-version }} Test if: ${{ !startsWith(github.ref, 'refs/heads/release/') }} - needs: [smoke-test-create, create-unity-matrix] + needs: [test-create, create-unity-matrix] secrets: inherit strategy: fail-fast: false matrix: ${{ fromJSON(needs.create-unity-matrix.outputs.unity-matrix) }} - uses: ./.github/workflows/smoke-test-build-ios.yml + uses: ./.github/workflows/test-build-ios.yml with: unity-version: ${{ matrix.unity-version }} - smoke-test-compile-ios: - name: Compile iOS ${{ matrix.unity-version }} Smoke Test + test-compile-ios: + name: Compile iOS ${{ matrix.unity-version }} Test if: ${{ !startsWith(github.ref, 'refs/heads/release/') }} - needs: [smoke-test-build-ios, create-unity-matrix] + needs: [test-build-ios, create-unity-matrix] secrets: inherit strategy: fail-fast: false matrix: unity-version: ${{ fromJSON(needs.create-unity-matrix.outputs.unity-matrix).unity-version }} init-type: ["runtime", "buildtime"] - uses: ./.github/workflows/smoke-test-compile-ios.yml + uses: ./.github/workflows/test-compile-ios.yml with: unity-version: ${{ matrix.unity-version }} init-type: ${{ matrix.init-type }} - smoke-test-run-ios: + test-run-ios: name: Run iOS ${{ matrix.unity-version }} Integration Test if: ${{ !startsWith(github.ref, 'refs/heads/release/') }} - needs: [smoke-test-compile-ios, create-unity-matrix] - uses: ./.github/workflows/smoke-test-run-ios.yml + needs: [test-compile-ios, create-unity-matrix] + uses: ./.github/workflows/test-run-ios.yml with: unity-version: ${{ matrix.unity-version }} ios-version: ${{ matrix.ios-version }} @@ -322,69 +322,69 @@ jobs: # Also make sure to match the versions available here: # - https://github.com/actions/runner-images/blob/main/images/macos/macos-14-Readme.md - smoke-test-run-webgl: + test-run-webgl: name: Run WebGL ${{ matrix.unity-version }} Integration Test if: ${{ !startsWith(github.ref, 'refs/heads/release/') }} - needs: [smoke-test-build-webgl, create-unity-matrix] + needs: [test-build-webgl, create-unity-matrix] secrets: inherit strategy: fail-fast: false matrix: unity-version: ${{ fromJSON(needs.create-unity-matrix.outputs.unity-matrix).unity-version }} - uses: ./.github/workflows/smoke-test-run-webgl.yml + uses: ./.github/workflows/test-run-webgl.yml with: unity-version: ${{ matrix.unity-version }} - smoke-test-build-linux: + test-build-linux: name: Build Linux ${{ matrix.unity-version }} Integration Test if: ${{ !startsWith(github.ref, 'refs/heads/release/') }} - needs: [smoke-test-create, create-unity-matrix] + needs: [test-create, create-unity-matrix] secrets: inherit strategy: fail-fast: false matrix: unity-version: ${{ fromJSON(needs.create-unity-matrix.outputs.unity-matrix).unity-version }} - uses: ./.github/workflows/smoke-test-build-linux.yml + uses: ./.github/workflows/test-build-linux.yml with: unity-version: ${{ matrix.unity-version }} - smoke-test-build-windows: + test-build-windows: name: Build Windows ${{ matrix.unity-version }} Integration Test if: ${{ !startsWith(github.ref, 'refs/heads/release/') }} - needs: [smoke-test-create, create-unity-matrix] + needs: [test-create, create-unity-matrix] secrets: inherit strategy: fail-fast: false matrix: unity-version: ${{ fromJSON(needs.create-unity-matrix.outputs.unity-matrix).unity-version }} - uses: ./.github/workflows/smoke-test-build-windows.yml + uses: ./.github/workflows/test-build-windows.yml with: unity-version: ${{ matrix.unity-version }} - smoke-test-run-linux: + test-run-linux: name: Run Linux ${{ matrix.unity-version }} Integration Test if: ${{ !startsWith(github.ref, 'refs/heads/release/') }} - needs: [smoke-test-build-linux, create-unity-matrix] + needs: [test-build-linux, create-unity-matrix] secrets: inherit strategy: fail-fast: false matrix: unity-version: ${{ fromJSON(needs.create-unity-matrix.outputs.unity-matrix).unity-version }} - uses: ./.github/workflows/smoke-test-run-desktop.yml + uses: ./.github/workflows/test-run-desktop.yml with: unity-version: ${{ matrix.unity-version }} platform: linux - smoke-test-run-windows: + test-run-windows: name: Run Windows ${{ matrix.unity-version }} Integration Test if: ${{ !startsWith(github.ref, 'refs/heads/release/') }} - needs: [smoke-test-build-windows, create-unity-matrix] + needs: [test-build-windows, create-unity-matrix] secrets: inherit strategy: fail-fast: false matrix: unity-version: ${{ fromJSON(needs.create-unity-matrix.outputs.unity-matrix).unity-version }} - uses: ./.github/workflows/smoke-test-run-desktop.yml + uses: ./.github/workflows/test-run-desktop.yml with: unity-version: ${{ matrix.unity-version }} platform: windows @@ -393,7 +393,7 @@ jobs: name: Build Size runs-on: ubuntu-latest if: ${{ !startsWith(github.ref, 'refs/heads/release/') }} - needs: [smoke-test-build-webgl, smoke-test-build-android, smoke-test-compile-ios, smoke-test-build-linux, smoke-test-build-windows] + needs: [test-build-webgl, test-build-android, test-compile-ios, test-build-linux, test-build-windows] steps: - name: Checkout uses: actions/checkout@v3 diff --git a/.github/workflows/smoke-test-build-android.yml b/.github/workflows/test-build-android.yml similarity index 98% rename from .github/workflows/smoke-test-build-android.yml rename to .github/workflows/test-build-android.yml index 1422b1b65..6ccde026e 100644 --- a/.github/workflows/smoke-test-build-android.yml +++ b/.github/workflows/test-build-android.yml @@ -1,4 +1,4 @@ -name: "SmokeTest: Build Android" +name: "Test: Build Android" on: workflow_call: inputs: @@ -40,7 +40,7 @@ jobs: - name: Download IntegrationTest project uses: actions/download-artifact@d3f86a106a0bac45b974a628896c90dbdf5c8093 # v4 with: - name: smoke-test-${{ env.UNITY_VERSION }} + name: test-${{ env.UNITY_VERSION }} - name: Extract project archive run: tar -xvzf test-project.tar.gz diff --git a/.github/workflows/smoke-test-build-ios.yml b/.github/workflows/test-build-ios.yml similarity index 98% rename from .github/workflows/smoke-test-build-ios.yml rename to .github/workflows/test-build-ios.yml index 5f620758d..b4baaec74 100644 --- a/.github/workflows/smoke-test-build-ios.yml +++ b/.github/workflows/test-build-ios.yml @@ -1,4 +1,4 @@ -name: "SmokeTest: Build iOS" +name: "Test: Build iOS" on: workflow_call: inputs: @@ -46,7 +46,7 @@ jobs: - name: Download IntegrationTest project uses: actions/download-artifact@d3f86a106a0bac45b974a628896c90dbdf5c8093 # v4 with: - name: smoke-test-${{ env.UNITY_VERSION }} + name: test-${{ env.UNITY_VERSION }} - name: Extract project archive run: tar -xvzf test-project.tar.gz diff --git a/.github/workflows/smoke-test-build-linux.yml b/.github/workflows/test-build-linux.yml similarity index 98% rename from .github/workflows/smoke-test-build-linux.yml rename to .github/workflows/test-build-linux.yml index 8930e626d..0b8f034f0 100644 --- a/.github/workflows/smoke-test-build-linux.yml +++ b/.github/workflows/test-build-linux.yml @@ -1,4 +1,4 @@ -name: "IntegrationTest: Build Linux" +name: "Test: Build Linux" on: workflow_call: inputs: @@ -51,7 +51,7 @@ jobs: - name: Download IntegrationTest project uses: actions/download-artifact@d3f86a106a0bac45b974a628896c90dbdf5c8093 # v4 with: - name: smoke-test-${{ env.UNITY_VERSION }} + name: test-${{ env.UNITY_VERSION }} - name: Extract project archive run: tar -xvzf test-project.tar.gz diff --git a/.github/workflows/smoke-test-build-windows.yml b/.github/workflows/test-build-windows.yml similarity index 98% rename from .github/workflows/smoke-test-build-windows.yml rename to .github/workflows/test-build-windows.yml index 4b64a425f..2e40baf13 100644 --- a/.github/workflows/smoke-test-build-windows.yml +++ b/.github/workflows/test-build-windows.yml @@ -1,4 +1,4 @@ -name: "IntegrationTest: Build Windows" +name: "Test: Build Windows" on: workflow_call: inputs: @@ -42,7 +42,7 @@ jobs: - name: Download IntegrationTest project uses: actions/download-artifact@d3f86a106a0bac45b974a628896c90dbdf5c8093 # v4 with: - name: smoke-test-${{ env.UNITY_VERSION }} + name: test-${{ env.UNITY_VERSION }} - name: Extract project archive run: tar -xvzf test-project.tar.gz diff --git a/.github/workflows/smoke-test-compile-ios.yml b/.github/workflows/test-compile-ios.yml similarity index 98% rename from .github/workflows/smoke-test-compile-ios.yml rename to .github/workflows/test-compile-ios.yml index 640aa0590..770492304 100644 --- a/.github/workflows/smoke-test-compile-ios.yml +++ b/.github/workflows/test-compile-ios.yml @@ -1,4 +1,4 @@ -name: "SmokeTest: Compile iOS" +name: "Test: Compile iOS" on: workflow_call: inputs: @@ -65,7 +65,7 @@ jobs: - name: Extract app project run: tar -xvzf "test-app-$env:INIT_TYPE.tar.gz" - - name: iOS smoke test + - name: iOS test run: ./scripts/compile-xcode-project.ps1 -iOSMinVersion "17.0" timeout-minutes: 20 diff --git a/.github/workflows/smoke-test-create.yml b/.github/workflows/test-create.yml similarity index 96% rename from .github/workflows/smoke-test-create.yml rename to .github/workflows/test-create.yml index c73550f55..8e7ba861c 100644 --- a/.github/workflows/smoke-test-create.yml +++ b/.github/workflows/test-create.yml @@ -1,4 +1,4 @@ -name: "SmokeTest: Create Project" +name: "Test: Create Project" on: workflow_call: inputs: @@ -57,7 +57,7 @@ jobs: - name: Upload project uses: actions/upload-artifact@ea165f8d65b6e75b540449e92b4886f43607fa02 # v4 with: - name: smoke-test-${{ env.UNITY_VERSION }} + name: test-${{ env.UNITY_VERSION }} if-no-files-found: error path: test-project.tar.gz # Lower retention period - we only need this to retry CI. diff --git a/.github/workflows/smoke-test-run-android.yml b/.github/workflows/test-run-android.yml similarity index 99% rename from .github/workflows/smoke-test-run-android.yml rename to .github/workflows/test-run-android.yml index 7abb585b1..b73168f32 100644 --- a/.github/workflows/smoke-test-run-android.yml +++ b/.github/workflows/test-run-android.yml @@ -1,4 +1,4 @@ -name: "SmokeTest: Run Android" +name: "Test: Run Android" on: workflow_call: inputs: diff --git a/.github/workflows/smoke-test-run-desktop.yml b/.github/workflows/test-run-desktop.yml similarity index 98% rename from .github/workflows/smoke-test-run-desktop.yml rename to .github/workflows/test-run-desktop.yml index 2049caf26..283312350 100644 --- a/.github/workflows/smoke-test-run-desktop.yml +++ b/.github/workflows/test-run-desktop.yml @@ -1,4 +1,4 @@ -name: "IntegrationTest: Run Desktop" +name: "Test: Run Desktop" on: workflow_call: inputs: diff --git a/.github/workflows/smoke-test-run-ios.yml b/.github/workflows/test-run-ios.yml similarity index 98% rename from .github/workflows/smoke-test-run-ios.yml rename to .github/workflows/test-run-ios.yml index 7d53b345c..e3e320c4f 100644 --- a/.github/workflows/smoke-test-run-ios.yml +++ b/.github/workflows/test-run-ios.yml @@ -1,4 +1,4 @@ -name: "IntegrationTest: Run iOS" +name: "Test: Run iOS" on: workflow_call: inputs: diff --git a/.github/workflows/smoke-test-run-webgl.yml b/.github/workflows/test-run-webgl.yml similarity index 97% rename from .github/workflows/smoke-test-run-webgl.yml rename to .github/workflows/test-run-webgl.yml index 441e6d61e..5afc4fc9c 100644 --- a/.github/workflows/smoke-test-run-webgl.yml +++ b/.github/workflows/test-run-webgl.yml @@ -1,4 +1,4 @@ -name: "IntegrationTest: Run WebGL" +name: "Test: Run WebGL" on: workflow_call: inputs: diff --git a/CLAUDE.md b/CLAUDE.md index 910fc3276..2b4e0201d 100644 --- a/CLAUDE.md +++ b/CLAUDE.md @@ -113,12 +113,12 @@ The CI system uses modular, reusable workflows in `.github/workflows/`: | `ci.yml` | Main pipeline - triggers on push/PR | | `build.yml` | Reusable build workflow | | `sdk.yml` | Native SDK builds (Android, Linux, Windows, Cocoa) | -| `smoke-test-create.yml` | Creates integration test projects | -| `smoke-test-build-android.yml` | Builds Android test apps | -| `smoke-test-run-android.yml` | Runs Android tests on emulator | -| `smoke-test-build-ios.yml` | Builds iOS test apps | -| `smoke-test-compile-ios.yml` | Compiles iOS Xcode projects | -| `smoke-test-run-ios.yml` | Runs iOS tests on simulator | +| `test-create.yml` | Creates integration test projects | +| `test-build-android.yml` | Builds Android test apps | +| `test-run-android.yml` | Runs Android tests on emulator | +| `test-build-ios.yml` | Builds iOS test apps | +| `test-compile-ios.yml` | Compiles iOS Xcode projects | +| `test-run-ios.yml` | Runs iOS tests on simulator | | `release.yml` | Manual release preparation | | `update-deps.yml` | Scheduled dependency updates (daily) | | `create-unity-matrix.yml` | Generates test matrix | @@ -160,7 +160,6 @@ Key targets defined in `Directory.Build.targets`: | `BuildCocoaSDK` | Downloads iOS/macOS SDKs from releases | | `UnityEditModeTest` | Runs edit-mode unit tests | | `UnityPlayModeTest` | Runs play-mode tests | -| `UnitySmokeTestStandalonePlayerIL2CPP` | Runs smoke tests | ### Artifact Caching @@ -177,7 +176,7 @@ Key targets defined in `Directory.Build.targets`: 3. Validate UPM package contents 4. Create integration test projects 5. Build for WebGL, Linux, Android, iOS, Windows -6. Run smoke tests and crash tests +6. Run integration tests and crash tests 7. Measure build sizes **On Main Branch:** @@ -506,7 +505,6 @@ Key options: | Edit Mode | `dotnet msbuild /t:UnityEditModeTest` | `test/Sentry.Unity.Tests/` | | Play Mode | `dotnet msbuild /t:UnityPlayModeTest` | `test/Sentry.Unity.Tests/` | | Editor Tests | `dotnet msbuild /t:UnityEditModeTest` | `test/Sentry.Unity.Editor.Tests/` | -| Smoke Tests | `dotnet msbuild /t:UnitySmokeTestStandalonePlayerIL2CPP` | Integration tests | | Integration | `integration-test.ps1` | `test/Scripts.Integration.Test/` | ### Running All Tests @@ -532,7 +530,6 @@ Located in `test/Scripts.Integration.Test/`: | `add-sentry.ps1` | Adds Sentry package to project | | `configure-sentry.ps1` | Configures Sentry in test project | | `build-project.ps1` | Builds for target platform | -| `run-smoke-test.ps1` | Executes smoke and crash tests | | `measure-build-size.ps1` | Compares build size with/without SDK | | `integration-test.ps1` | Full local integration test | diff --git a/CONTRIBUTING.md b/CONTRIBUTING.md index 143910a2c..33d881249 100644 --- a/CONTRIBUTING.md +++ b/CONTRIBUTING.md @@ -99,7 +99,7 @@ dotnet msbuild /t:"UnityPlayModeTest;UnityEditModeTest" /p:Configuration=Release Or use the TestRunner window inside the Unity Editor. -### Integration and Smoke Tests +### Integration Tests Run integration tests locally using the same scripts as CI: diff --git a/Directory.Build.targets b/Directory.Build.targets index b1a65ad05..772f0fe96 100644 --- a/Directory.Build.targets +++ b/Directory.Build.targets @@ -346,13 +346,6 @@ Related: https://forum.unity.com/threads/6572-debugger-agent-unable-to-listen-on - - - - - - - @@ -384,11 +377,6 @@ Related: https://forum.unity.com/threads/6572-debugger-agent-unable-to-listen-on - - - - - diff --git a/src/Sentry.Unity.Editor/ConfigurationWindow/SentryEditorWindowInstrumentation.cs b/src/Sentry.Unity.Editor/ConfigurationWindow/SentryEditorWindowInstrumentation.cs index b64677096..2a2d52f16 100644 --- a/src/Sentry.Unity.Editor/ConfigurationWindow/SentryEditorWindowInstrumentation.cs +++ b/src/Sentry.Unity.Editor/ConfigurationWindow/SentryEditorWindowInstrumentation.cs @@ -38,12 +38,6 @@ private static void ConfigureOptions(Dictionary args, [CallerMem OptionsConfigurationItem.SetScript(value); } - if (args.TryGetValue("cliOptions.UrlOverride", out value)) - { - Debug.LogFormat("{0}: Configuring symbol-upload UrlOverride to {1}", functionName, value); - cliOptions.UrlOverride = value; - } - if (args.TryGetValue("cliOptionsScript", out value)) { Debug.LogFormat("{0}: Configuring CliOptions Script to {1}", functionName, value); diff --git a/test/Scripts.Integration.Test/Editor/Builder.cs b/test/Scripts.Integration.Test/Editor/Builder.cs index bb2ae779c..1e776efab 100644 --- a/test/Scripts.Integration.Test/Editor/Builder.cs +++ b/test/Scripts.Integration.Test/Editor/Builder.cs @@ -53,7 +53,7 @@ public static void BuildIl2CPPPlayer(BuildTarget target, BuildTargetGroup group, }; Debug.Log("Builder: Disabling optimizations to reduce build time"); - // TODO Linux fails with `free(): invalid pointer` in the smoke-test, after everthing seems to have shut down. + // TODO Linux fails with `free(): invalid pointer` in the test, after everything seems to have shut down. if (target != BuildTarget.StandaloneLinux64) { PlayerSettings.SetIl2CppCompilerConfiguration(NamedBuildTarget.FromBuildTargetGroup(group), Il2CppCompilerConfiguration.Debug); @@ -84,11 +84,11 @@ public static void BuildIl2CPPPlayer(BuildTarget target, BuildTargetGroup group, #endif } - Debug.Log("Builder: Checking for SmokeTest scene"); - if (File.Exists("Assets/Scenes/SmokeTest.unity")) + Debug.Log("Builder: Checking for Test scene"); + if (File.Exists("Assets/Scenes/Test.unity")) { - Debug.Log("Builder: Adding SmokeTest.unity to scenes"); - buildPlayerOptions.scenes = new[] { "Assets/Scenes/SmokeTest.unity" }; + Debug.Log("Builder: Adding Test.unity to scenes"); + buildPlayerOptions.scenes = new[] { "Assets/Scenes/Test.unity" }; } Debug.Log("Builder: Starting build"); diff --git a/test/Scripts.Integration.Test/Scenes/SmokeTest.unity b/test/Scripts.Integration.Test/Scenes/Test.unity similarity index 100% rename from test/Scripts.Integration.Test/Scenes/SmokeTest.unity rename to test/Scripts.Integration.Test/Scenes/Test.unity diff --git a/test/Scripts.Integration.Test/common.ps1 b/test/Scripts.Integration.Test/common.ps1 index a0f899306..d9865ef9d 100644 --- a/test/Scripts.Integration.Test/common.ps1 +++ b/test/Scripts.Integration.Test/common.ps1 @@ -1,4 +1,4 @@ -# Note: this is currently used by integration test scripts as well as "smoke-test-*.ps1" scripts. +# Note: this is currently used by integration test scripts. # If/when those are merged to some extent, maybe this file could be merged into `globals.ps1`. Set-StrictMode -Version latest From 7b7d187607237322c787bb6be8b15afb6c807556 Mon Sep 17 00:00:00 2001 From: bitsandfoxes Date: Tue, 17 Mar 2026 08:48:27 +0100 Subject: [PATCH 81/86] merged integration test scripts --- .github/workflows/test-run-android.yml | 2 +- .github/workflows/test-run-desktop.yml | 6 +- .github/workflows/test-run-ios.yml | 3 +- .github/workflows/test-run-webgl.yml | 5 +- .../Integration.Tests.Desktop.ps1 | 208 -------------- .../Integration.Tests.WebGL.ps1 | 181 ------------ .../IntegrationTest/Integration.Tests.iOS.ps1 | 222 --------------- test/IntegrationTest/Integration.Tests.ps1 | 260 +++++++++++++----- 8 files changed, 200 insertions(+), 687 deletions(-) delete mode 100644 test/IntegrationTest/Integration.Tests.Desktop.ps1 delete mode 100644 test/IntegrationTest/Integration.Tests.WebGL.ps1 delete mode 100644 test/IntegrationTest/Integration.Tests.iOS.ps1 diff --git a/.github/workflows/test-run-android.yml b/.github/workflows/test-run-android.yml index b73168f32..6f86b1216 100644 --- a/.github/workflows/test-run-android.yml +++ b/.github/workflows/test-run-android.yml @@ -95,7 +95,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 -Path test/IntegrationTest/Integration.Tests.ps1 -CI' + pwsh -Command '$env:SENTRY_TEST_PLATFORM = "Android"; $env:SENTRY_TEST_APP = "samples/IntegrationTest/Build/test.apk"; Invoke-Pester -Path test/IntegrationTest/Integration.Tests.ps1 -CI' - name: Upload test results on failure if: ${{ failure() }} diff --git a/.github/workflows/test-run-desktop.yml b/.github/workflows/test-run-desktop.yml index 283312350..a9da553d2 100644 --- a/.github/workflows/test-run-desktop.yml +++ b/.github/workflows/test-run-desktop.yml @@ -48,16 +48,18 @@ jobs: timeout-minutes: 20 run: | xvfb-run pwsh -Command ' + $env:SENTRY_TEST_PLATFORM = "Desktop"; $env:SENTRY_TEST_APP = "samples/IntegrationTest/Build/test"; - Invoke-Pester -Path test/IntegrationTest/Integration.Tests.Desktop.ps1 -CI' + Invoke-Pester -Path test/IntegrationTest/Integration.Tests.ps1 -CI' shell: bash - name: Run Integration Tests (Windows) if: inputs.platform == 'windows' timeout-minutes: 20 run: | + $env:SENTRY_TEST_PLATFORM = "Desktop" $env:SENTRY_TEST_APP = "samples/IntegrationTest/Build/test.exe" - Invoke-Pester -Path test/IntegrationTest/Integration.Tests.Desktop.ps1 -CI + Invoke-Pester -Path test/IntegrationTest/Integration.Tests.ps1 -CI - name: Upload test results on failure if: ${{ failure() }} diff --git a/.github/workflows/test-run-ios.yml b/.github/workflows/test-run-ios.yml index e3e320c4f..83d639d9d 100644 --- a/.github/workflows/test-run-ios.yml +++ b/.github/workflows/test-run-ios.yml @@ -64,9 +64,10 @@ jobs: id: integration-test timeout-minutes: 20 run: | + $env:SENTRY_TEST_PLATFORM = "iOS" $env:SENTRY_TEST_APP = "samples/IntegrationTest/Build/IntegrationTest.app" $env:SENTRY_IOS_VERSION = "${{ inputs.ios-version }}" - Invoke-Pester -Path test/IntegrationTest/Integration.Tests.iOS.ps1 -CI + Invoke-Pester -Path test/IntegrationTest/Integration.Tests.ps1 -CI echo "status=success" >> $env:GITHUB_OUTPUT - name: Upload test results on failure diff --git a/.github/workflows/test-run-webgl.yml b/.github/workflows/test-run-webgl.yml index 5afc4fc9c..78d82e806 100644 --- a/.github/workflows/test-run-webgl.yml +++ b/.github/workflows/test-run-webgl.yml @@ -41,8 +41,9 @@ jobs: - name: Run Integration Tests timeout-minutes: 20 run: | - $env:SENTRY_WEBGL_BUILD_PATH = "samples/IntegrationTest/Build" - Invoke-Pester -Path test/IntegrationTest/Integration.Tests.WebGL.ps1 -CI + $env:SENTRY_TEST_PLATFORM = "WebGL" + $env:SENTRY_TEST_APP = "samples/IntegrationTest/Build" + Invoke-Pester -Path test/IntegrationTest/Integration.Tests.ps1 -CI - name: Upload test results on failure if: ${{ failure() }} diff --git a/test/IntegrationTest/Integration.Tests.Desktop.ps1 b/test/IntegrationTest/Integration.Tests.Desktop.ps1 deleted file mode 100644 index 11b3e82e6..000000000 --- a/test/IntegrationTest/Integration.Tests.Desktop.ps1 +++ /dev/null @@ -1,208 +0,0 @@ -#!/usr/bin/env pwsh -# -# Integration tests for Sentry Unity SDK (Desktop: Windows, Linux) -# -# Environment variables: -# SENTRY_TEST_APP: path to the test executable -# SENTRY_TEST_DSN: test DSN -# SENTRY_AUTH_TOKEN: authentication token for Sentry API - -Set-StrictMode -Version latest -$ErrorActionPreference = "Stop" - -# Import app-runner modules -. $PSScriptRoot/../../modules/app-runner/import-modules.ps1 - -# Import shared test cases and utility functions -. $PSScriptRoot/CommonTestCases.ps1 - -BeforeAll { - # Run integration test action on device - function Invoke-TestAction { - param ( - [Parameter(Mandatory=$true)] - [string]$Action - ) - - Write-Host "Running $Action..." - - $appArgs = @("--test", $Action, "-logFile", "-") - - $runResult = Invoke-DeviceApp -ExecutablePath $env:SENTRY_TEST_APP -Arguments $appArgs - - # 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") { - Write-Host "Running crash-send to ensure crash report is sent..." - - $sendArgs = @("--test", "crash-send", "-logFile", "-") - $sendResult = Invoke-DeviceApp -ExecutablePath $env:SENTRY_TEST_APP -Arguments $sendArgs - - # Save crash-send result to JSON for debugging - $sendResult | ConvertTo-Json -Depth 5 | Out-File -FilePath (Get-OutputFilePath "crash-send-result.json") - - # Print crash-send output - Write-Host "::group::App output (crash-send)" - $sendResult.Output | ForEach-Object { Write-Host $_ } - Write-Host "::endgroup::" - - # Attach to runResult for test access - $runResult | Add-Member -NotePropertyName "CrashSendOutput" -NotePropertyValue $sendResult.Output - } - - # 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 - } - - # 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]@{ - Platform = "Desktop" - AppPath = $env:SENTRY_TEST_APP - Dsn = $env:SENTRY_TEST_DSN - AuthToken = $env:SENTRY_AUTH_TOKEN - } - - # Validate environment - if ([string]::IsNullOrEmpty($script:TestSetup.AppPath)) { - throw "SENTRY_TEST_APP environment variable is not set." - } - if (-not (Test-Path $script:TestSetup.AppPath)) { - throw "App not found at: $($script:TestSetup.AppPath)" - } - 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 "Local" -} - - -AfterAll { - Disconnect-SentryApi - Disconnect-Device -} - - -Describe "Unity Desktop Integration Tests" { - - Context "Message Capture" { - BeforeAll { - $script:runEvent = $null - $script:runResult = Invoke-TestAction -Action "message-capture" - - $eventId = Get-EventIds -AppOutput $script:runResult.Output -ExpectedCount 1 - if ($eventId) { - Write-Host "::group::Getting event content" - $script:runEvent = Get-SentryTestEvent -EventId "$eventId" - Write-Host "::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:runEvent = $null - $script:runResult = Invoke-TestAction -Action "exception-capture" - - $eventId = Get-EventIds -AppOutput $script:runResult.Output -ExpectedCount 1 - if ($eventId) { - Write-Host "::group::Getting event content" - $script:runEvent = Get-SentryTestEvent -EventId "$eventId" - Write-Host "::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:runEvent = $null - $script:runResult = Invoke-TestAction -Action "crash-capture" - - $eventId = Get-EventIds -AppOutput $script:runResult.Output -ExpectedCount 1 - if ($eventId) { - Write-Host "::group::Getting event content" - $script:runEvent = Get-SentryTestEvent -TagName "test.crash_id" -TagValue "$eventId" -TimeoutSeconds 120 - Write-Host "::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 - } - - It "Reports crashedLastRun as Crashed on relaunch" { - $crashedLastRunLine = $runResult.CrashSendOutput | Where-Object { - $_ -match "crashedLastRun=Crashed" - } - $crashedLastRunLine | Should -Not -BeNullOrEmpty -Because "Native SDK should report crashedLastRun=Crashed after a native crash" - } - - It "Crash-send completes flush successfully" { - $flushLine = $runResult.CrashSendOutput | Where-Object { - $_ -match "Flush complete" - } - $flushLine | Should -Not -BeNullOrEmpty -Because "crash-send should complete its flush before quitting" - } - } -} diff --git a/test/IntegrationTest/Integration.Tests.WebGL.ps1 b/test/IntegrationTest/Integration.Tests.WebGL.ps1 deleted file mode 100644 index b1a207586..000000000 --- a/test/IntegrationTest/Integration.Tests.WebGL.ps1 +++ /dev/null @@ -1,181 +0,0 @@ -#!/usr/bin/env pwsh -# -# Integration tests for Sentry Unity SDK (WebGL) -# -# Environment variables: -# SENTRY_WEBGL_BUILD_PATH: path to the WebGL build directory -# SENTRY_TEST_DSN: test DSN -# SENTRY_AUTH_TOKEN: authentication token for Sentry API - -Set-StrictMode -Version latest -$ErrorActionPreference = "Stop" - -# Import app-runner modules -. $PSScriptRoot/../../modules/app-runner/import-modules.ps1 - -# Import shared test cases and utility functions -. $PSScriptRoot/CommonTestCases.ps1 - -BeforeAll { - # Run integration test action via WebGL (HTTP server + headless Chrome) - function Invoke-TestAction { - param ( - [Parameter(Mandatory=$true)] - [string]$Action - ) - - Write-Host "Running $Action..." - - $serverScript = Join-Path $PSScriptRoot "webgl-server.py" - $buildPath = $env:SENTRY_WEBGL_BUILD_PATH - $timeoutSeconds = 120 - - $process = Start-Process -FilePath "python3" ` - -ArgumentList @($serverScript, $buildPath, $Action, $timeoutSeconds) ` - -NoNewWindow -PassThru -RedirectStandardOutput "$PSScriptRoot/results/${Action}-stdout.txt" ` - -RedirectStandardError "$PSScriptRoot/results/${Action}-stderr.txt" - - $process | Wait-Process -Timeout ($timeoutSeconds + 30) - - $exitCode = $process.ExitCode - $stdoutContent = Get-Content "$PSScriptRoot/results/${Action}-stdout.txt" -Raw -ErrorAction SilentlyContinue - $stderrContent = Get-Content "$PSScriptRoot/results/${Action}-stderr.txt" -Raw -ErrorAction SilentlyContinue - - # Parse the JSON array of console lines from stdout - $output = @() - if ($stdoutContent) { - try { - $output = $stdoutContent | ConvertFrom-Json - } - catch { - Write-Host "Failed to parse webgl-server.py output as JSON: $_" - Write-Host "Raw stdout: $stdoutContent" - $output = @($stdoutContent) - } - } - - if ($stderrContent) { - Write-Host "::group::Server stderr ($Action)" - Write-Host $stderrContent - Write-Host "::endgroup::" - } - - $runResult = [PSCustomObject]@{ - Output = $output - ExitCode = $exitCode - } - - # Save result to JSON file - $runResult | ConvertTo-Json -Depth 5 | Out-File -FilePath (Get-OutputFilePath "${Action}-result.json") - - # Print app output so it's visible in CI logs - Write-Host "::group::Browser console output ($Action)" - $runResult.Output | ForEach-Object { Write-Host $_ } - Write-Host "::endgroup::" - - if ($exitCode -ne 0) { - Write-Warning "WebGL test action '$Action' did not complete (exit code: $exitCode)" - } - - 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]@{ - Platform = "WebGL" - BuildPath = $env:SENTRY_WEBGL_BUILD_PATH - Dsn = $env:SENTRY_TEST_DSN - AuthToken = $env:SENTRY_AUTH_TOKEN - } - - # Validate environment - if ([string]::IsNullOrEmpty($script:TestSetup.BuildPath)) { - throw "SENTRY_WEBGL_BUILD_PATH environment variable is not set." - } - if (-not (Test-Path $script:TestSetup.BuildPath)) { - throw "WebGL build not found at: $($script:TestSetup.BuildPath)" - } - 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 -} - - -AfterAll { - Disconnect-SentryApi -} - - -Describe "Unity WebGL Integration Tests" { - - Context "Message Capture" { - BeforeAll { - $script:runEvent = $null - $script:runResult = Invoke-TestAction -Action "message-capture" - - $eventId = Get-EventIds -AppOutput $script:runResult.Output -ExpectedCount 1 - if ($eventId) { - Write-Host "::group::Getting event content" - $script:runEvent = Get-SentryTestEvent -EventId "$eventId" - Write-Host "::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:runEvent = $null - $script:runResult = Invoke-TestAction -Action "exception-capture" - - $eventId = Get-EventIds -AppOutput $script:runResult.Output -ExpectedCount 1 - if ($eventId) { - Write-Host "::group::Getting event content" - $script:runEvent = Get-SentryTestEvent -EventId "$eventId" - Write-Host "::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" - } - } -} diff --git a/test/IntegrationTest/Integration.Tests.iOS.ps1 b/test/IntegrationTest/Integration.Tests.iOS.ps1 deleted file mode 100644 index b1d52a451..000000000 --- a/test/IntegrationTest/Integration.Tests.iOS.ps1 +++ /dev/null @@ -1,222 +0,0 @@ -#!/usr/bin/env pwsh -# -# Integration tests for Sentry Unity SDK (iOS Simulator) -# -# Environment variables: -# SENTRY_TEST_APP: path to the test .app bundle -# SENTRY_IOS_VERSION: iOS simulator version (e.g. "17.0" or "latest") -# SENTRY_TEST_DSN: test DSN -# SENTRY_AUTH_TOKEN: authentication token for Sentry API - -Set-StrictMode -Version latest -$ErrorActionPreference = "Stop" - -# Import app-runner modules -. $PSScriptRoot/../../modules/app-runner/import-modules.ps1 - -# Import shared test cases and utility functions -. $PSScriptRoot/CommonTestCases.ps1 - - -BeforeAll { - $script:BundleId = "com.DefaultCompany.IntegrationTest" - - # Run integration test action on device - function Invoke-TestAction { - param ( - [Parameter(Mandatory=$true)] - [string]$Action - ) - - Write-Host "Running $Action..." - - $appArgs = @("--test", $Action) - - $runResult = Invoke-DeviceApp -ExecutablePath $script:BundleId -Arguments $appArgs - - # 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") { - Write-Host "Running crash-send to ensure crash report is sent..." - - $sendArgs = @("--test", "crash-send") - $sendResult = Invoke-DeviceApp -ExecutablePath $script:BundleId -Arguments $sendArgs - - # Save crash-send result to JSON for debugging - $sendResult | ConvertTo-Json -Depth 5 | Out-File -FilePath (Get-OutputFilePath "crash-send-result.json") - - # Print crash-send output - Write-Host "::group::App output (crash-send)" - $sendResult.Output | ForEach-Object { Write-Host $_ } - Write-Host "::endgroup::" - - # Attach to runResult for test access - $runResult | Add-Member -NotePropertyName "CrashSendOutput" -NotePropertyValue $sendResult.Output - } - - # 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 - } - - # 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]@{ - Platform = "iOS" - AppPath = $env:SENTRY_TEST_APP - iOSVersion = $env:SENTRY_IOS_VERSION - Dsn = $env:SENTRY_TEST_DSN - AuthToken = $env:SENTRY_AUTH_TOKEN - } - - # Validate environment - if ([string]::IsNullOrEmpty($script:TestSetup.AppPath)) { - throw "SENTRY_TEST_APP environment variable is not set." - } - if (-not (Test-Path $script:TestSetup.AppPath)) { - throw "App not found at: $($script:TestSetup.AppPath)" - } - if ([string]::IsNullOrEmpty($script:TestSetup.iOSVersion)) { - throw "SENTRY_IOS_VERSION environment variable is not set." - } - 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 - - $target = $script:TestSetup.iOSVersion - # Convert bare version numbers (e.g. "17.0") to "iOS 17.0" format expected by iOSSimulatorProvider - if ($target -match '^\d+\.\d+$') { - $target = "iOS $target" - } - Connect-Device -Platform "iOSSimulator" -Target $target - Install-DeviceApp -Path $script:TestSetup.AppPath -} - - -AfterAll { - Disconnect-SentryApi - Disconnect-Device -} - - -Describe "Unity iOS Integration Tests" { - - Context "Message Capture" { - BeforeAll { - $script:runEvent = $null - $script:runResult = Invoke-TestAction -Action "message-capture" - - $eventId = Get-EventIds -AppOutput $script:runResult.Output -ExpectedCount 1 - if ($eventId) { - Write-Host "::group::Getting event content" - $script:runEvent = Get-SentryTestEvent -EventId "$eventId" - Write-Host "::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:runEvent = $null - $script:runResult = Invoke-TestAction -Action "exception-capture" - - $eventId = Get-EventIds -AppOutput $script:runResult.Output -ExpectedCount 1 - if ($eventId) { - Write-Host "::group::Getting event content" - $script:runEvent = Get-SentryTestEvent -EventId "$eventId" - Write-Host "::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:runEvent = $null - $script:runResult = Invoke-TestAction -Action "crash-capture" - - $eventId = Get-EventIds -AppOutput $script:runResult.Output -ExpectedCount 1 - if ($eventId) { - Write-Host "::group::Getting event content" - $script:runEvent = Get-SentryTestEvent -TagName "test.crash_id" -TagValue "$eventId" -TimeoutSeconds 120 - Write-Host "::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 - } - - It "Reports crashedLastRun as Crashed on relaunch" { - $crashedLastRunLine = $runResult.CrashSendOutput | Where-Object { - $_ -match "crashedLastRun=Crashed" - } - $crashedLastRunLine | Should -Not -BeNullOrEmpty -Because "Native SDK should report crashedLastRun=Crashed after a native crash" - } - - It "Crash-send completes flush successfully" { - $flushLine = $runResult.CrashSendOutput | Where-Object { - $_ -match "Flush complete" - } - $flushLine | Should -Not -BeNullOrEmpty -Because "crash-send should complete its flush before quitting" - } - } -} diff --git a/test/IntegrationTest/Integration.Tests.ps1 b/test/IntegrationTest/Integration.Tests.ps1 index 3b4d32be4..851b37e1b 100644 --- a/test/IntegrationTest/Integration.Tests.ps1 +++ b/test/IntegrationTest/Integration.Tests.ps1 @@ -1,11 +1,16 @@ #!/usr/bin/env pwsh # -# Integration tests for Sentry Unity SDK (Android) +# Integration tests for Sentry Unity SDK # # Environment variables: -# SENTRY_TEST_APK: path to the test APK file +# SENTRY_TEST_PLATFORM: target platform (Android, Desktop, iOS, WebGL) # SENTRY_TEST_DSN: test DSN # SENTRY_AUTH_TOKEN: authentication token for Sentry API +# +# SENTRY_TEST_APP: path to the test app (APK, executable, .app bundle, or WebGL build directory) +# +# Platform-specific environment variables: +# iOS: SENTRY_IOS_VERSION - iOS simulator version (e.g. "17.0" or "latest") Set-StrictMode -Version latest $ErrorActionPreference = "Stop" @@ -18,9 +23,141 @@ $ErrorActionPreference = "Stop" BeforeAll { - $script:PackageName = "io.sentry.unity.integrationtest" + $script:Platform = $env:SENTRY_TEST_PLATFORM + if ([string]::IsNullOrEmpty($script:Platform)) { + throw "SENTRY_TEST_PLATFORM environment variable is not set. Expected: Android, Desktop, iOS, or WebGL" + } + + # Validate common environment + if ([string]::IsNullOrEmpty($env:SENTRY_TEST_DSN)) { + throw "SENTRY_TEST_DSN environment variable is not set." + } + if ([string]::IsNullOrEmpty($env:SENTRY_AUTH_TOKEN)) { + throw "SENTRY_AUTH_TOKEN environment variable is not set." + } + if ([string]::IsNullOrEmpty($env:SENTRY_TEST_APP)) { + throw "SENTRY_TEST_APP environment variable is not set." + } + if (-not (Test-Path $env:SENTRY_TEST_APP)) { + throw "App not found at: $env:SENTRY_TEST_APP" + } + + # Platform-specific device setup + switch ($script:Platform) { + "Android" { + $script:PackageName = "io.sentry.unity.integrationtest" + + Connect-Device -Platform "Adb" + Install-DeviceApp -Path $env:SENTRY_TEST_APP + + # Detect the launcher activity from the installed package + $dumpOutput = & adb shell dumpsys package $script:PackageName 2>&1 | Out-String + if ($dumpOutput -match "com.unity3d.player.UnityPlayerGameActivity") { + $script:ExecutablePath = "$($script:PackageName)/com.unity3d.player.UnityPlayerGameActivity" + } else { + $script:ExecutablePath = "$($script:PackageName)/com.unity3d.player.UnityPlayerActivity" + } + Write-Host "Detected activity: $($script:ExecutablePath)" + } + "Desktop" { + $script:ExecutablePath = $env:SENTRY_TEST_APP + Connect-Device -Platform "Local" + } + "iOS" { + if ([string]::IsNullOrEmpty($env:SENTRY_IOS_VERSION)) { + throw "SENTRY_IOS_VERSION environment variable is not set." + } + + $script:ExecutablePath = "com.DefaultCompany.IntegrationTest" + + $target = $env:SENTRY_IOS_VERSION + # Convert bare version numbers (e.g. "17.0") to "iOS 17.0" format expected by iOSSimulatorProvider + if ($target -match '^\d+\.\d+$') { + $target = "iOS $target" + } + Connect-Device -Platform "iOSSimulator" -Target $target + Install-DeviceApp -Path $env:SENTRY_TEST_APP + } + "WebGL" { + } + default { + throw "Unknown platform: $($script:Platform). Expected: Android, Desktop, iOS, or WebGL" + } + } - # Run integration test action on device + # Build app arguments for a given test action + function Get-AppArguments { + param([string]$Action) + + switch ($script:Platform) { + "Android" { return @("-e", "test", $Action) } + "Desktop" { return @("--test", $Action, "-logFile", "-") } + "iOS" { return @("--test", $Action) } + } + } + + # Run a WebGL test action via headless Chrome + function Invoke-WebGLTestAction { + param ( + [Parameter(Mandatory=$true)] + [string]$Action + ) + + $serverScript = Join-Path $PSScriptRoot "webgl-server.py" + $buildPath = $env:SENTRY_TEST_APP + $timeoutSeconds = 120 + + $process = Start-Process -FilePath "python3" ` + -ArgumentList @($serverScript, $buildPath, $Action, $timeoutSeconds) ` + -NoNewWindow -PassThru -RedirectStandardOutput "$PSScriptRoot/results/${Action}-stdout.txt" ` + -RedirectStandardError "$PSScriptRoot/results/${Action}-stderr.txt" + + $process | Wait-Process -Timeout ($timeoutSeconds + 30) + + $exitCode = $process.ExitCode + $stdoutContent = Get-Content "$PSScriptRoot/results/${Action}-stdout.txt" -Raw -ErrorAction SilentlyContinue + $stderrContent = Get-Content "$PSScriptRoot/results/${Action}-stderr.txt" -Raw -ErrorAction SilentlyContinue + + # Parse the JSON array of console lines from stdout + $output = @() + if ($stdoutContent) { + try { + $output = $stdoutContent | ConvertFrom-Json + } + catch { + Write-Host "Failed to parse webgl-server.py output as JSON: $_" + Write-Host "Raw stdout: $stdoutContent" + $output = @($stdoutContent) + } + } + + if ($stderrContent) { + Write-Host "::group::Server stderr ($Action)" + Write-Host $stderrContent + Write-Host "::endgroup::" + } + + $runResult = [PSCustomObject]@{ + Output = $output + ExitCode = $exitCode + } + + # Save result to JSON file + $runResult | ConvertTo-Json -Depth 5 | Out-File -FilePath (Get-OutputFilePath "${Action}-result.json") + + # Print app output so it's visible in CI logs + Write-Host "::group::Browser console output ($Action)" + $runResult.Output | ForEach-Object { Write-Host $_ } + Write-Host "::endgroup::" + + if ($exitCode -ne 0) { + Write-Warning "WebGL test action '$Action' did not complete (exit code: $exitCode)" + } + + return $runResult + } + + # Run integration test action function Invoke-TestAction { param ( [Parameter(Mandatory=$true)] @@ -29,9 +166,12 @@ BeforeAll { Write-Host "Running $Action..." - $extras = @("-e", "test", $Action) + if ($script:Platform -eq "WebGL") { + return Invoke-WebGLTestAction -Action $Action + } - $runResult = Invoke-DeviceApp -ExecutablePath $script:AndroidComponent -Arguments $extras + $appArgs = Get-AppArguments -Action $Action + $runResult = Invoke-DeviceApp -ExecutablePath $script:ExecutablePath -Arguments $appArgs # Save result to JSON file $runResult | ConvertTo-Json -Depth 5 | Out-File -FilePath (Get-OutputFilePath "${Action}-result.json") @@ -40,8 +180,8 @@ BeforeAll { if ($Action -eq "crash-capture") { Write-Host "Running crash-send to ensure crash report is sent..." - $sendExtras = @("-e", "test", "crash-send") - $sendResult = Invoke-DeviceApp -ExecutablePath $script:AndroidComponent -Arguments $sendExtras + $sendArgs = Get-AppArguments -Action "crash-send" + $sendResult = Invoke-DeviceApp -ExecutablePath $script:ExecutablePath -Arguments $sendArgs # Save crash-send result to JSON for debugging $sendResult | ConvertTo-Json -Depth 5 | Out-File -FilePath (Get-OutputFilePath "crash-send-result.json") @@ -69,51 +209,26 @@ BeforeAll { # Initialize test parameters $script:TestSetup = [PSCustomObject]@{ - Platform = "Android" - ApkPath = $env:SENTRY_TEST_APK + Platform = $script:Platform 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 - - # Detect the launcher activity from the installed package - $dumpOutput = & adb shell dumpsys package $script:PackageName 2>&1 | Out-String - if ($dumpOutput -match "com.unity3d.player.UnityPlayerGameActivity") { - $script:AndroidComponent = "$($script:PackageName)/com.unity3d.player.UnityPlayerGameActivity" - } else { - $script:AndroidComponent = "$($script:PackageName)/com.unity3d.player.UnityPlayerActivity" - } - Write-Host "Detected activity: $($script:AndroidComponent)" } AfterAll { Disconnect-SentryApi - Disconnect-Device + if ($script:Platform -ne "WebGL") { + Disconnect-Device + } } -Describe "Unity Android Integration Tests" { +Describe "Unity $($env:SENTRY_TEST_PLATFORM) Integration Tests" { Context "Message Capture" { BeforeAll { @@ -174,48 +289,53 @@ Describe "Unity Android Integration Tests" { ($runEvent.tags | Where-Object { $_.key -eq "level" }).value | Should -Be "error" } } +} - Context "Crash Capture" { - BeforeAll { - $script:runEvent = $null - $script:runResult = Invoke-TestAction -Action "crash-capture" +if ($env:SENTRY_TEST_PLATFORM -ne "WebGL") { + Describe "Unity $($env:SENTRY_TEST_PLATFORM) Crash Tests" { - $eventId = Get-EventIds -AppOutput $script:runResult.Output -ExpectedCount 1 - if ($eventId) { - Write-Host "::group::Getting event content" - $script:runEvent = Get-SentryTestEvent -TagName "test.crash_id" -TagValue "$eventId" -TimeoutSeconds 120 - Write-Host "::endgroup::" + Context "Crash Capture" { + BeforeAll { + $script:runEvent = $null + $script:runResult = Invoke-TestAction -Action "crash-capture" + + $eventId = Get-EventIds -AppOutput $script:runResult.Output -ExpectedCount 1 + if ($eventId) { + Write-Host "::group::Getting event content" + $script:runEvent = Get-SentryTestEvent -TagName "test.crash_id" -TagValue "$eventId" -TimeoutSeconds 300 + Write-Host "::endgroup::" + } } - } - It "" -ForEach $CommonTestCases { - & $testBlock -SentryEvent $runEvent -TestType "crash-capture" -RunResult $runResult -TestSetup $script:TestSetup - } + 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 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 - } + 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 + } - It "Reports crashedLastRun as Crashed on relaunch" { - $crashedLastRunLine = $runResult.CrashSendOutput | Where-Object { - $_ -match "crashedLastRun=Crashed" + It "Reports crashedLastRun as Crashed on relaunch" { + $crashedLastRunLine = $runResult.CrashSendOutput | Where-Object { + $_ -match "crashedLastRun=Crashed" + } + $crashedLastRunLine | Should -Not -BeNullOrEmpty -Because "Native SDK should report crashedLastRun=Crashed after a native crash" } - $crashedLastRunLine | Should -Not -BeNullOrEmpty -Because "Native SDK should report crashedLastRun=Crashed after a native crash" - } - It "Crash-send completes flush successfully" { - $flushLine = $runResult.CrashSendOutput | Where-Object { - $_ -match "Flush complete" + It "Crash-send completes flush successfully" { + $flushLine = $runResult.CrashSendOutput | Where-Object { + $_ -match "Flush complete" + } + $flushLine | Should -Not -BeNullOrEmpty -Because "crash-send should complete its flush before quitting" } - $flushLine | Should -Not -BeNullOrEmpty -Because "crash-send should complete its flush before quitting" } } } From 49ba1fb0ebba89e344f668ce1068fe7ce9d9d463 Mon Sep 17 00:00:00 2001 From: bitsandfoxes Date: Tue, 17 Mar 2026 11:13:15 +0100 Subject: [PATCH 82/86] redundant --- .github/workflows/smoke-test-run-webgl.yml | 54 ---------------------- 1 file changed, 54 deletions(-) delete mode 100644 .github/workflows/smoke-test-run-webgl.yml diff --git a/.github/workflows/smoke-test-run-webgl.yml b/.github/workflows/smoke-test-run-webgl.yml deleted file mode 100644 index 441e6d61e..000000000 --- a/.github/workflows/smoke-test-run-webgl.yml +++ /dev/null @@ -1,54 +0,0 @@ -name: "IntegrationTest: Run WebGL" -on: - workflow_call: - inputs: - unity-version: - required: true - type: string - -defaults: - run: - shell: pwsh - -jobs: - run: - name: WebGL ${{ inputs.unity-version }} - runs-on: ubuntu-latest - env: - SENTRY_TEST_DSN: ${{ secrets.SENTRY_TEST_DSN }} - SENTRY_AUTH_TOKEN: ${{ secrets.SENTRY_AUTH_TOKEN }} - - steps: - - name: Checkout - uses: actions/checkout@11bd71901bbe5b1630ceea73d27597364c9af683 # v4 - - - name: Initialize app-runner submodule - run: git submodule update --init modules/app-runner - shell: bash - - - name: Download test app artifact - uses: actions/download-artifact@d3f86a106a0bac45b974a628896c90dbdf5c8093 # v4 - with: - name: testapp-webgl-compiled-${{ inputs.unity-version }} - - - name: Extract test app - run: tar -xvzf test-app-webgl.tar.gz - - - name: Install Selenium - run: pip3 install --upgrade selenium - shell: bash - - - name: Run Integration Tests - timeout-minutes: 20 - run: | - $env:SENTRY_WEBGL_BUILD_PATH = "samples/IntegrationTest/Build" - Invoke-Pester -Path test/IntegrationTest/Integration.Tests.WebGL.ps1 -CI - - - name: Upload test results on failure - if: ${{ failure() }} - uses: actions/upload-artifact@ea165f8d65b6e75b540449e92b4886f43607fa02 # v4 - with: - name: testapp-webgl-logs-${{ inputs.unity-version }} - path: | - test/IntegrationTest/results/ - retention-days: 14 From ec58299af87299f36ce609bd6731655bdb7dbf34 Mon Sep 17 00:00:00 2001 From: bitsandfoxes Date: Tue, 17 Mar 2026 12:57:27 +0100 Subject: [PATCH 83/86] more logging --- test/IntegrationTest/Integration.Tests.ps1 | 7 +++++++ 1 file changed, 7 insertions(+) diff --git a/test/IntegrationTest/Integration.Tests.ps1 b/test/IntegrationTest/Integration.Tests.ps1 index 851b37e1b..42bed7f15 100644 --- a/test/IntegrationTest/Integration.Tests.ps1 +++ b/test/IntegrationTest/Integration.Tests.ps1 @@ -299,6 +299,13 @@ if ($env:SENTRY_TEST_PLATFORM -ne "WebGL") { $script:runEvent = $null $script:runResult = Invoke-TestAction -Action "crash-capture" + # Validate crash-send completed before polling Sentry (avoids a 300s blind wait) + $flushLine = $runResult.CrashSendOutput | Where-Object { $_ -match "Flush complete" } + if (-not $flushLine) { + $crashSendOutput = ($runResult.CrashSendOutput | Out-String) + throw "crash-send did not complete flush. The crash envelope was likely not sent. Output:`n$crashSendOutput" + } + $eventId = Get-EventIds -AppOutput $script:runResult.Output -ExpectedCount 1 if ($eventId) { Write-Host "::group::Getting event content" From 585f80c9087683cdc0aff66d2b0cfcbdc9dca6c8 Mon Sep 17 00:00:00 2001 From: bitsandfoxes Date: Tue, 17 Mar 2026 13:11:58 +0100 Subject: [PATCH 84/86] . --- Directory.Build.targets | 2 +- test/IntegrationTest/Integration.Tests.ps1 | 235 +++++++++--------- .../integration-test.ps1 | 14 +- 3 files changed, 127 insertions(+), 124 deletions(-) diff --git a/Directory.Build.targets b/Directory.Build.targets index f9cad2841..f2e1472a4 100644 --- a/Directory.Build.targets +++ b/Directory.Build.targets @@ -379,7 +379,7 @@ Related: https://forum.unity.com/threads/6572-debugger-agent-unable-to-listen-on - + diff --git a/test/IntegrationTest/Integration.Tests.ps1 b/test/IntegrationTest/Integration.Tests.ps1 index 42bed7f15..7d5c9288d 100644 --- a/test/IntegrationTest/Integration.Tests.ps1 +++ b/test/IntegrationTest/Integration.Tests.ps1 @@ -21,6 +21,123 @@ $ErrorActionPreference = "Stop" # Import shared test cases and utility functions . $PSScriptRoot/CommonTestCases.ps1 +# Build app arguments for a given test action +function Get-AppArguments { + param([string]$Action) + + switch ($script:Platform) { + "Android" { return @("-e", "test", $Action) } + "Desktop" { return @("--test", $Action, "-logFile", "-") } + "iOS" { return @("--test", $Action) } + } +} + +# Run a WebGL test action via headless Chrome +function Invoke-WebGLTestAction { + param ( + [Parameter(Mandatory=$true)] + [string]$Action + ) + + $serverScript = Join-Path $PSScriptRoot "webgl-server.py" + $buildPath = $env:SENTRY_TEST_APP + $timeoutSeconds = 120 + + $process = Start-Process -FilePath "python3" ` + -ArgumentList @($serverScript, $buildPath, $Action, $timeoutSeconds) ` + -NoNewWindow -PassThru -RedirectStandardOutput "$PSScriptRoot/results/${Action}-stdout.txt" ` + -RedirectStandardError "$PSScriptRoot/results/${Action}-stderr.txt" + + $process | Wait-Process -Timeout ($timeoutSeconds + 30) + + $exitCode = $process.ExitCode + $stdoutContent = Get-Content "$PSScriptRoot/results/${Action}-stdout.txt" -Raw -ErrorAction SilentlyContinue + $stderrContent = Get-Content "$PSScriptRoot/results/${Action}-stderr.txt" -Raw -ErrorAction SilentlyContinue + + # Parse the JSON array of console lines from stdout + $output = @() + if ($stdoutContent) { + try { + $output = $stdoutContent | ConvertFrom-Json + } + catch { + Write-Host "Failed to parse webgl-server.py output as JSON: $_" + Write-Host "Raw stdout: $stdoutContent" + $output = @($stdoutContent) + } + } + + if ($stderrContent) { + Write-Host "::group::Server stderr ($Action)" + Write-Host $stderrContent + Write-Host "::endgroup::" + } + + $runResult = [PSCustomObject]@{ + Output = $output + ExitCode = $exitCode + } + + # Save result to JSON file + $runResult | ConvertTo-Json -Depth 5 | Out-File -FilePath (Get-OutputFilePath "${Action}-result.json") + + # Print app output so it's visible in CI logs + Write-Host "::group::Browser console output ($Action)" + $runResult.Output | ForEach-Object { Write-Host $_ } + Write-Host "::endgroup::" + + if ($exitCode -ne 0) { + Write-Warning "WebGL test action '$Action' did not complete (exit code: $exitCode)" + } + + return $runResult +} + +# Run integration test action +function Invoke-TestAction { + param ( + [Parameter(Mandatory=$true)] + [string]$Action + ) + + Write-Host "Running $Action..." + + if ($script:Platform -eq "WebGL") { + return Invoke-WebGLTestAction -Action $Action + } + + $appArgs = Get-AppArguments -Action $Action + $runResult = Invoke-DeviceApp -ExecutablePath $script:ExecutablePath -Arguments $appArgs + + # 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") { + Write-Host "Running crash-send to ensure crash report is sent..." + + $sendArgs = Get-AppArguments -Action "crash-send" + $sendResult = Invoke-DeviceApp -ExecutablePath $script:ExecutablePath -Arguments $sendArgs + + # Save crash-send result to JSON for debugging + $sendResult | ConvertTo-Json -Depth 5 | Out-File -FilePath (Get-OutputFilePath "crash-send-result.json") + + # Print crash-send output + Write-Host "::group::App output (crash-send)" + $sendResult.Output | ForEach-Object { Write-Host $_ } + Write-Host "::endgroup::" + + # Attach to runResult for test access + $runResult | Add-Member -NotePropertyName "CrashSendOutput" -NotePropertyValue $sendResult.Output + } + + # 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 +} BeforeAll { $script:Platform = $env:SENTRY_TEST_PLATFORM @@ -85,124 +202,6 @@ BeforeAll { } } - # Build app arguments for a given test action - function Get-AppArguments { - param([string]$Action) - - switch ($script:Platform) { - "Android" { return @("-e", "test", $Action) } - "Desktop" { return @("--test", $Action, "-logFile", "-") } - "iOS" { return @("--test", $Action) } - } - } - - # Run a WebGL test action via headless Chrome - function Invoke-WebGLTestAction { - param ( - [Parameter(Mandatory=$true)] - [string]$Action - ) - - $serverScript = Join-Path $PSScriptRoot "webgl-server.py" - $buildPath = $env:SENTRY_TEST_APP - $timeoutSeconds = 120 - - $process = Start-Process -FilePath "python3" ` - -ArgumentList @($serverScript, $buildPath, $Action, $timeoutSeconds) ` - -NoNewWindow -PassThru -RedirectStandardOutput "$PSScriptRoot/results/${Action}-stdout.txt" ` - -RedirectStandardError "$PSScriptRoot/results/${Action}-stderr.txt" - - $process | Wait-Process -Timeout ($timeoutSeconds + 30) - - $exitCode = $process.ExitCode - $stdoutContent = Get-Content "$PSScriptRoot/results/${Action}-stdout.txt" -Raw -ErrorAction SilentlyContinue - $stderrContent = Get-Content "$PSScriptRoot/results/${Action}-stderr.txt" -Raw -ErrorAction SilentlyContinue - - # Parse the JSON array of console lines from stdout - $output = @() - if ($stdoutContent) { - try { - $output = $stdoutContent | ConvertFrom-Json - } - catch { - Write-Host "Failed to parse webgl-server.py output as JSON: $_" - Write-Host "Raw stdout: $stdoutContent" - $output = @($stdoutContent) - } - } - - if ($stderrContent) { - Write-Host "::group::Server stderr ($Action)" - Write-Host $stderrContent - Write-Host "::endgroup::" - } - - $runResult = [PSCustomObject]@{ - Output = $output - ExitCode = $exitCode - } - - # Save result to JSON file - $runResult | ConvertTo-Json -Depth 5 | Out-File -FilePath (Get-OutputFilePath "${Action}-result.json") - - # Print app output so it's visible in CI logs - Write-Host "::group::Browser console output ($Action)" - $runResult.Output | ForEach-Object { Write-Host $_ } - Write-Host "::endgroup::" - - if ($exitCode -ne 0) { - Write-Warning "WebGL test action '$Action' did not complete (exit code: $exitCode)" - } - - return $runResult - } - - # Run integration test action - function Invoke-TestAction { - param ( - [Parameter(Mandatory=$true)] - [string]$Action - ) - - Write-Host "Running $Action..." - - if ($script:Platform -eq "WebGL") { - return Invoke-WebGLTestAction -Action $Action - } - - $appArgs = Get-AppArguments -Action $Action - $runResult = Invoke-DeviceApp -ExecutablePath $script:ExecutablePath -Arguments $appArgs - - # 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") { - Write-Host "Running crash-send to ensure crash report is sent..." - - $sendArgs = Get-AppArguments -Action "crash-send" - $sendResult = Invoke-DeviceApp -ExecutablePath $script:ExecutablePath -Arguments $sendArgs - - # Save crash-send result to JSON for debugging - $sendResult | ConvertTo-Json -Depth 5 | Out-File -FilePath (Get-OutputFilePath "crash-send-result.json") - - # Print crash-send output - Write-Host "::group::App output (crash-send)" - $sendResult.Output | ForEach-Object { Write-Host $_ } - Write-Host "::endgroup::" - - # Attach to runResult for test access - $runResult | Add-Member -NotePropertyName "CrashSendOutput" -NotePropertyValue $sendResult.Output - } - - # 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 - } - # Create directory for the test results New-Item -ItemType Directory -Path "$PSScriptRoot/results/" -ErrorAction Continue 2>&1 | Out-Null Set-OutputDir -Path "$PSScriptRoot/results/" diff --git a/test/Scripts.Integration.Test/integration-test.ps1 b/test/Scripts.Integration.Test/integration-test.ps1 index 37c4a4783..06a0b2716 100644 --- a/test/Scripts.Integration.Test/integration-test.ps1 +++ b/test/Scripts.Integration.Test/integration-test.ps1 @@ -86,20 +86,24 @@ Else { Switch -Regex ($Platform) { "^(Windows|MacOS|Linux)$" { + $env:SENTRY_TEST_PLATFORM = "Desktop" $env:SENTRY_TEST_APP = GetNewProjectBuildPath - Invoke-Pester -Path test/IntegrationTest/Integration.Tests.Desktop.ps1 -CI + Invoke-Pester -Path test/IntegrationTest/Integration.Tests.ps1 -CI } "^(Android)$" { - $env:SENTRY_TEST_APK = "$(GetNewProjectBuildPath)/test.apk" + $env:SENTRY_TEST_PLATFORM = "Android" + $env:SENTRY_TEST_APP = "$(GetNewProjectBuildPath)/test.apk" Invoke-Pester -Path test/IntegrationTest/Integration.Tests.ps1 -CI } "^iOS$" { + $env:SENTRY_TEST_PLATFORM = "iOS" $env:SENTRY_TEST_APP = "$(GetNewProjectBuildPath)/IntegrationTest.app" - Invoke-Pester -Path test/IntegrationTest/Integration.Tests.iOS.ps1 -CI + Invoke-Pester -Path test/IntegrationTest/Integration.Tests.ps1 -CI } "^WebGL$" { - $env:SENTRY_WEBGL_BUILD_PATH = GetNewProjectBuildPath - Invoke-Pester -Path test/IntegrationTest/Integration.Tests.WebGL.ps1 -CI + $env:SENTRY_TEST_PLATFORM = "WebGL" + $env:SENTRY_TEST_APP = GetNewProjectBuildPath + Invoke-Pester -Path test/IntegrationTest/Integration.Tests.ps1 -CI } "^Switch$" { Write-PhaseSuccess "Switch build completed - no automated test execution available" From 24ab0af81cd054347cacd62ef8c632d82c684d3a Mon Sep 17 00:00:00 2001 From: bitsandfoxes Date: Tue, 17 Mar 2026 13:51:49 +0100 Subject: [PATCH 85/86] fixed scoping issue --- Directory.Build.targets | 2 +- test/IntegrationTest/Integration.Tests.ps1 | 190 ++++++++++----------- 2 files changed, 96 insertions(+), 96 deletions(-) diff --git a/Directory.Build.targets b/Directory.Build.targets index f2e1472a4..48e37bc2b 100644 --- a/Directory.Build.targets +++ b/Directory.Build.targets @@ -379,7 +379,7 @@ Related: https://forum.unity.com/threads/6572-debugger-agent-unable-to-listen-on - + diff --git a/test/IntegrationTest/Integration.Tests.ps1 b/test/IntegrationTest/Integration.Tests.ps1 index 7d5c9288d..286df2639 100644 --- a/test/IntegrationTest/Integration.Tests.ps1 +++ b/test/IntegrationTest/Integration.Tests.ps1 @@ -21,125 +21,125 @@ $ErrorActionPreference = "Stop" # Import shared test cases and utility functions . $PSScriptRoot/CommonTestCases.ps1 -# Build app arguments for a given test action -function Get-AppArguments { - param([string]$Action) - - switch ($script:Platform) { - "Android" { return @("-e", "test", $Action) } - "Desktop" { return @("--test", $Action, "-logFile", "-") } - "iOS" { return @("--test", $Action) } +BeforeAll { + # Build app arguments for a given test action + function Get-AppArguments { + param([string]$Action) + + switch ($script:Platform) { + "Android" { return @("-e", "test", $Action) } + "Desktop" { return @("--test", $Action, "-logFile", "-") } + "iOS" { return @("--test", $Action) } + } } -} -# Run a WebGL test action via headless Chrome -function Invoke-WebGLTestAction { - param ( - [Parameter(Mandatory=$true)] - [string]$Action - ) - - $serverScript = Join-Path $PSScriptRoot "webgl-server.py" - $buildPath = $env:SENTRY_TEST_APP - $timeoutSeconds = 120 - - $process = Start-Process -FilePath "python3" ` - -ArgumentList @($serverScript, $buildPath, $Action, $timeoutSeconds) ` - -NoNewWindow -PassThru -RedirectStandardOutput "$PSScriptRoot/results/${Action}-stdout.txt" ` - -RedirectStandardError "$PSScriptRoot/results/${Action}-stderr.txt" - - $process | Wait-Process -Timeout ($timeoutSeconds + 30) - - $exitCode = $process.ExitCode - $stdoutContent = Get-Content "$PSScriptRoot/results/${Action}-stdout.txt" -Raw -ErrorAction SilentlyContinue - $stderrContent = Get-Content "$PSScriptRoot/results/${Action}-stderr.txt" -Raw -ErrorAction SilentlyContinue - - # Parse the JSON array of console lines from stdout - $output = @() - if ($stdoutContent) { - try { - $output = $stdoutContent | ConvertFrom-Json + # Run a WebGL test action via headless Chrome + function Invoke-WebGLTestAction { + param ( + [Parameter(Mandatory=$true)] + [string]$Action + ) + + $serverScript = Join-Path $PSScriptRoot "webgl-server.py" + $buildPath = $env:SENTRY_TEST_APP + $timeoutSeconds = 120 + + $process = Start-Process -FilePath "python3" ` + -ArgumentList @($serverScript, $buildPath, $Action, $timeoutSeconds) ` + -NoNewWindow -PassThru -RedirectStandardOutput "$PSScriptRoot/results/${Action}-stdout.txt" ` + -RedirectStandardError "$PSScriptRoot/results/${Action}-stderr.txt" + + $process | Wait-Process -Timeout ($timeoutSeconds + 30) + + $exitCode = $process.ExitCode + $stdoutContent = Get-Content "$PSScriptRoot/results/${Action}-stdout.txt" -Raw -ErrorAction SilentlyContinue + $stderrContent = Get-Content "$PSScriptRoot/results/${Action}-stderr.txt" -Raw -ErrorAction SilentlyContinue + + # Parse the JSON array of console lines from stdout + $output = @() + if ($stdoutContent) { + try { + $output = $stdoutContent | ConvertFrom-Json + } + catch { + Write-Host "Failed to parse webgl-server.py output as JSON: $_" + Write-Host "Raw stdout: $stdoutContent" + $output = @($stdoutContent) + } } - catch { - Write-Host "Failed to parse webgl-server.py output as JSON: $_" - Write-Host "Raw stdout: $stdoutContent" - $output = @($stdoutContent) + + if ($stderrContent) { + Write-Host "::group::Server stderr ($Action)" + Write-Host $stderrContent + Write-Host "::endgroup::" } - } - if ($stderrContent) { - Write-Host "::group::Server stderr ($Action)" - Write-Host $stderrContent - Write-Host "::endgroup::" - } + $runResult = [PSCustomObject]@{ + Output = $output + ExitCode = $exitCode + } - $runResult = [PSCustomObject]@{ - Output = $output - ExitCode = $exitCode - } + # Save result to JSON file + $runResult | ConvertTo-Json -Depth 5 | Out-File -FilePath (Get-OutputFilePath "${Action}-result.json") - # Save result to JSON file - $runResult | ConvertTo-Json -Depth 5 | Out-File -FilePath (Get-OutputFilePath "${Action}-result.json") + # Print app output so it's visible in CI logs + Write-Host "::group::Browser console output ($Action)" + $runResult.Output | ForEach-Object { Write-Host $_ } + Write-Host "::endgroup::" - # Print app output so it's visible in CI logs - Write-Host "::group::Browser console output ($Action)" - $runResult.Output | ForEach-Object { Write-Host $_ } - Write-Host "::endgroup::" + if ($exitCode -ne 0) { + Write-Warning "WebGL test action '$Action' did not complete (exit code: $exitCode)" + } - if ($exitCode -ne 0) { - Write-Warning "WebGL test action '$Action' did not complete (exit code: $exitCode)" + return $runResult } - return $runResult -} + # Run integration test action + function Invoke-TestAction { + param ( + [Parameter(Mandatory=$true)] + [string]$Action + ) -# Run integration test action -function Invoke-TestAction { - param ( - [Parameter(Mandatory=$true)] - [string]$Action - ) + Write-Host "Running $Action..." - Write-Host "Running $Action..." + if ($script:Platform -eq "WebGL") { + return Invoke-WebGLTestAction -Action $Action + } - if ($script:Platform -eq "WebGL") { - return Invoke-WebGLTestAction -Action $Action - } + $appArgs = Get-AppArguments -Action $Action + $runResult = Invoke-DeviceApp -ExecutablePath $script:ExecutablePath -Arguments $appArgs - $appArgs = Get-AppArguments -Action $Action - $runResult = Invoke-DeviceApp -ExecutablePath $script:ExecutablePath -Arguments $appArgs + # Save result to JSON file + $runResult | ConvertTo-Json -Depth 5 | Out-File -FilePath (Get-OutputFilePath "${Action}-result.json") - # 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") { + Write-Host "Running crash-send to ensure crash report is sent..." - # 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..." + $sendArgs = Get-AppArguments -Action "crash-send" + $sendResult = Invoke-DeviceApp -ExecutablePath $script:ExecutablePath -Arguments $sendArgs - $sendArgs = Get-AppArguments -Action "crash-send" - $sendResult = Invoke-DeviceApp -ExecutablePath $script:ExecutablePath -Arguments $sendArgs + # Save crash-send result to JSON for debugging + $sendResult | ConvertTo-Json -Depth 5 | Out-File -FilePath (Get-OutputFilePath "crash-send-result.json") - # Save crash-send result to JSON for debugging - $sendResult | ConvertTo-Json -Depth 5 | Out-File -FilePath (Get-OutputFilePath "crash-send-result.json") + # Print crash-send output + Write-Host "::group::App output (crash-send)" + $sendResult.Output | ForEach-Object { Write-Host $_ } + Write-Host "::endgroup::" - # Print crash-send output - Write-Host "::group::App output (crash-send)" - $sendResult.Output | ForEach-Object { Write-Host $_ } + # Attach to runResult for test access + $runResult | Add-Member -NotePropertyName "CrashSendOutput" -NotePropertyValue $sendResult.Output + } + + # 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::" - # Attach to runResult for test access - $runResult | Add-Member -NotePropertyName "CrashSendOutput" -NotePropertyValue $sendResult.Output + return $runResult } - # 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 -} - -BeforeAll { $script:Platform = $env:SENTRY_TEST_PLATFORM if ([string]::IsNullOrEmpty($script:Platform)) { throw "SENTRY_TEST_PLATFORM environment variable is not set. Expected: Android, Desktop, iOS, or WebGL" From b6a614edd47bf477e67d779c759913d49af69991 Mon Sep 17 00:00:00 2001 From: bitsandfoxes Date: Tue, 17 Mar 2026 17:21:23 +0100 Subject: [PATCH 86/86] Fix WebGL smoke test shell expansion and Wait-Process timeout handling --- Directory.Build.targets | 2 +- test/IntegrationTest/Integration.Tests.ps1 | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/Directory.Build.targets b/Directory.Build.targets index 48e37bc2b..9375f617a 100644 --- a/Directory.Build.targets +++ b/Directory.Build.targets @@ -379,7 +379,7 @@ Related: https://forum.unity.com/threads/6572-debugger-agent-unable-to-listen-on - + diff --git a/test/IntegrationTest/Integration.Tests.ps1 b/test/IntegrationTest/Integration.Tests.ps1 index 286df2639..e547dc59a 100644 --- a/test/IntegrationTest/Integration.Tests.ps1 +++ b/test/IntegrationTest/Integration.Tests.ps1 @@ -49,7 +49,7 @@ BeforeAll { -NoNewWindow -PassThru -RedirectStandardOutput "$PSScriptRoot/results/${Action}-stdout.txt" ` -RedirectStandardError "$PSScriptRoot/results/${Action}-stderr.txt" - $process | Wait-Process -Timeout ($timeoutSeconds + 30) + $process | Wait-Process -Timeout ($timeoutSeconds + 30) -ErrorAction SilentlyContinue $exitCode = $process.ExitCode $stdoutContent = Get-Content "$PSScriptRoot/results/${Action}-stdout.txt" -Raw -ErrorAction SilentlyContinue