diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml index ec151e2f9..20985bc05 100644 --- a/.github/workflows/ci.yml +++ b/.github/workflows/ci.yml @@ -161,3 +161,28 @@ jobs: uses: ./.github/workflows/test-windows.yml with: unreal-version: ${{ matrix.unreal }} + + integration-test-linux: + needs: [test-linux] + name: Linux UE ${{ matrix.unreal }} + secrets: inherit + strategy: + fail-fast: false + matrix: + unreal: ['4.27', '5.1', '5.2', '5.3', '5.4', '5.5', '5.6'] + uses: ./.github/workflows/integration-test-linux.yml + with: + unreal-version: ${{ matrix.unreal }} + + integration-test-windows: + needs: [test-windows] + name: Windows UE ${{ matrix.unreal }} + secrets: inherit + strategy: + fail-fast: false + matrix: + # Integration tests only for UE 5.2 and newer where CRC can be disabled + unreal: ['5.2', '5.3', '5.4', '5.5', '5.6'] + uses: ./.github/workflows/integration-test-windows.yml + with: + unreal-version: ${{ matrix.unreal }} diff --git a/.github/workflows/integration-test-linux.yml b/.github/workflows/integration-test-linux.yml new file mode 100644 index 000000000..66dfc856a --- /dev/null +++ b/.github/workflows/integration-test-linux.yml @@ -0,0 +1,54 @@ +on: + workflow_call: + inputs: + unreal-version: + required: true + type: string + +jobs: + integration-test: + name: Integration Test + runs-on: ubuntu-latest + + steps: + - uses: actions/checkout@v4 + with: + submodules: recursive + + - name: Download sample build + uses: actions/download-artifact@v4 + with: + name: UE ${{ inputs.unreal-version }} sample build (Linux) + path: sample-build + + - name: Set execute permissions + run: | + chmod +x ${{ github.workspace }}/sample-build/SentryPlayground.sh + chmod +x ${{ github.workspace }}/sample-build/SentryPlayground/Plugins/sentry/Binaries/Linux/crashpad_handler + + - name: Install Pester + shell: pwsh + run: | + Install-Module -Name Pester -Force -SkipPublisherCheck + + - name: Run integration tests + id: run-integration-tests + shell: pwsh + env: + SENTRY_UNREAL_TEST_DSN: ${{ secrets.SENTRY_UNREAL_TEST_DSN }} + SENTRY_AUTH_TOKEN: ${{ secrets.SENTRY_API_TOKEN }} + SENTRY_UNREAL_TEST_APP_PATH: ${{ github.workspace }}/sample-build/SentryPlayground.sh + run: | + cd integration-test + mkdir build + cmake -B build -S . + Invoke-Pester Integration.Tests.ps1 -CI + + - name: Upload integration test output + if: ${{ always() && steps.run-integration-tests.outcome == 'failure' }} + uses: actions/upload-artifact@v4 + with: + name: UE ${{ inputs.unreal-version }} integration test output (Linux) + path: | + integration-test/output/ + sample-build/SentryPlayground/Saved/Logs/ diff --git a/.github/workflows/integration-test-windows.yml b/.github/workflows/integration-test-windows.yml new file mode 100644 index 000000000..71a72ff71 --- /dev/null +++ b/.github/workflows/integration-test-windows.yml @@ -0,0 +1,50 @@ +on: + workflow_call: + inputs: + unreal-version: + required: true + type: string + +jobs: + integration-test: + name: Integration Test + runs-on: windows-2022 + + steps: + - uses: actions/checkout@v4 + with: + submodules: recursive + + # UE 5.2-5.3 require older DirectX runtime to run pre-built test app + - name: Install DirectX June 2010 Runtime + run: | + Invoke-WebRequest -Uri "https://download.microsoft.com/download/8/4/A/84A35BF1-DAFE-4AE8-82AF-AD2AE20B6B14/directx_Jun2010_redist.exe" -OutFile "$env:TEMP\directx_redist.exe" + Start-Process -FilePath "$env:TEMP\directx_redist.exe" -ArgumentList "/Q","/T:$env:TEMP\DirectX" -Wait + Start-Process -FilePath "$env:TEMP\DirectX\DXSETUP.exe" -ArgumentList "/silent" -Wait + + - name: Download sample build + uses: actions/download-artifact@v4 + with: + name: UE ${{ inputs.unreal-version }} sample build (Windows) + path: sample-build + + - name: Run integration tests + id: run-integration-tests + env: + SENTRY_UNREAL_TEST_DSN: ${{ secrets.SENTRY_UNREAL_TEST_DSN }} + SENTRY_AUTH_TOKEN: ${{ secrets.SENTRY_API_TOKEN }} + SENTRY_UNREAL_TEST_APP_PATH: ${{ github.workspace }}/sample-build/SentryPlayground.exe + run: | + cd integration-test + mkdir build + cmake -B build -S . + Invoke-Pester Integration.Tests.ps1 -CI + + - name: Upload integration test output + if: ${{ always() && steps.run-integration-tests.outcome == 'failure' }} + uses: actions/upload-artifact@v4 + with: + name: UE ${{ inputs.unreal-version }} integration test output (Windows) + path: | + integration-test/output/ + sample-build/SentryPlayground/Saved/Logs/ diff --git a/.github/workflows/test-windows.yml b/.github/workflows/test-windows.yml index 83d5f978a..3c1defdeb 100644 --- a/.github/workflows/test-windows.yml +++ b/.github/workflows/test-windows.yml @@ -106,4 +106,3 @@ jobs: name: UE ${{ inputs.unreal-version }} sample build (Windows) path: checkout/sample/Builds/${{ inputs.unreal-version == '4.27' && 'WindowsNoEditor' || 'Windows' }}/ retention-days: 1 - diff --git a/integration-test/.gitignore b/integration-test/.gitignore new file mode 100644 index 000000000..89320d7b8 --- /dev/null +++ b/integration-test/.gitignore @@ -0,0 +1,6 @@ +# CMake build artifacts +build/ +TestConfig.local.ps1 + +# Test outputs +output/ diff --git a/integration-test/CMakeLists.txt b/integration-test/CMakeLists.txt new file mode 100644 index 000000000..ec54093bc --- /dev/null +++ b/integration-test/CMakeLists.txt @@ -0,0 +1,25 @@ +cmake_minimum_required(VERSION 3.20) +project(SentryUnrealIntegrationTests NONE) + +include(FetchContent) + +# Fetch PowerShell modules for Sentry API integration +FetchContent_Declare( + app-runner + GIT_REPOSITORY https://github.com/getsentry/app-runner.git + GIT_TAG 503795f0ef0f8340fcc0f0bc5fb5437df8cff9ef +) + +FetchContent_MakeAvailable(app-runner) + +# Generate test configuration with app-runner path +set(configFile "${CMAKE_CURRENT_SOURCE_DIR}/TestConfig.local.ps1") +configure_file( + "${CMAKE_CURRENT_SOURCE_DIR}/TestConfig.ps1.in" + "${configFile}" + @ONLY +) + +message(STATUS "Integration test environment configured") +message(STATUS " app-runner path: ${app-runner_SOURCE_DIR}") +message(STATUS " Config file: ${configFile}") diff --git a/integration-test/Integration.Tests.ps1 b/integration-test/Integration.Tests.ps1 new file mode 100644 index 000000000..dfd59e9de --- /dev/null +++ b/integration-test/Integration.Tests.ps1 @@ -0,0 +1,292 @@ +# Integration tests for Sentry Unreal SDK +# Requires: +# - Pre-built SentryPlayground application +# - Environment variables: SENTRY_UNREAL_TEST_DSN, SENTRY_AUTH_TOKEN, SENTRY_UNREAL_TEST_APP_PATH + +Set-StrictMode -Version Latest +$ErrorActionPreference = 'Stop' + +function script:Invoke-SentryUnrealTestApp { + [CmdletBinding()] + param( + [Parameter(Mandatory)] + [string[]]$Arguments, + + [Parameter(Mandatory)] + [string]$TestName, + + [Parameter()] + [int]$TimeoutSeconds = 300 + ) + + # Generate timestamp and output file paths + $timestamp = Get-Date -Format 'yyyyMMdd-HHmmss' + $stdoutFile = "$script:OutputDir/$timestamp-$TestName-stdout.log" + $stderrFile = "$script:OutputDir/$timestamp-$TestName-stderr.log" + $resultFile = "$script:OutputDir/$timestamp-$TestName-result.json" + + $exitCode = -1 + + try { + $process = Start-Process -FilePath $script:AppPath -ArgumentList $Arguments ` + -PassThru -NoNewWindow ` + -RedirectStandardOutput $stdoutFile ` + -RedirectStandardError $stderrFile + + if ($process.WaitForExit($TimeoutSeconds * 1000)) { + $exitCode = $process.ExitCode + } else { + Write-Host "Process timed out after $TimeoutSeconds seconds. Killing process..." -ForegroundColor Red + $process.Kill($true) + $exitCode = -1 + } + } catch { + Write-Host "Process execution failed: $_" -ForegroundColor Red + $exitCode = -1 + } + + # Read output from files to ensure we get everything that was written + $stdout = @() + $stderr = @() + + if (Test-Path $stdoutFile) { + $stdout = Get-Content $stdoutFile -ErrorAction SilentlyContinue + } + + if (Test-Path $stderrFile) { + $stderr = Get-Content $stderrFile -ErrorAction SilentlyContinue + } + + $result = @{ + ExitCode = $exitCode + Output = $stdout + Error = $stderr + } + + # Save full output to result JSON + $result | ConvertTo-Json -Depth 10 | Out-File $resultFile + + return $result +} + +BeforeAll { + # Check if configuration file exists + $configFile = "$PSScriptRoot/TestConfig.local.ps1" + if (-not (Test-Path $configFile)) { + throw "Configuration file '$configFile' not found." + } + + # Load configuration (provides $global:AppRunnerPath) + . $configFile + + # Import app-runner modules + . "$global:AppRunnerPath/import-modules.ps1" + + # Validate environment variables + $script:DSN = $env:SENTRY_UNREAL_TEST_DSN + $script:AuthToken = $env:SENTRY_AUTH_TOKEN + $script:AppPath = $env:SENTRY_UNREAL_TEST_APP_PATH + + if (-not $script:DSN) { + throw "Environment variable SENTRY_UNREAL_TEST_DSN must be set" + } + + if (-not $script:AuthToken) { + throw "Environment variable SENTRY_AUTH_TOKEN must be set" + } + + if (-not $script:AppPath) { + throw "Environment variable SENTRY_UNREAL_TEST_APP_PATH must be set" + } + + # Connect to Sentry API + Write-Host "Connecting to Sentry API..." -ForegroundColor Yellow + Connect-SentryApi -DSN $script:DSN -ApiToken $script:AuthToken + + # Validate app path + if (-not (Test-Path $script:AppPath)) { + throw "Application not found at: $script:AppPath" + } + + # Create output directory + $script:OutputDir = "$PSScriptRoot/output" + if (-not (Test-Path $script:OutputDir)) { + New-Item -ItemType Directory -Path $script:OutputDir | Out-Null + } +} + +Describe "Sentry Unreal Integration Tests" { + + Context "Crash Capture Tests" { + BeforeAll { + $script:CrashResult = $null + $script:CrashEvent = $null + + Write-Host "Running crash capture test..." -ForegroundColor Yellow + + # Build arguments and execute application: + # -crash-capture: Triggers integration test crash scenario in the sample app + # -nullrhi: Runs without graphics rendering (headless mode) + # -unattended: Disables user prompts and interactive dialogs + # -stdout: Ensures logs are written to stdout on Linux/Unix systems + # -nosplash: Prevents splash screen and dialogs + $appArgs = @('-crash-capture', '-nullrhi', '-unattended', '-stdout', '-nosplash') + $script:CrashResult = Invoke-SentryUnrealTestApp -Arguments $appArgs -TestName 'crash' + + Write-Host "Crash test executed. Exit code: $($script:CrashResult.ExitCode)" -ForegroundColor Cyan + + # Parse event ID from output + $eventIds = Get-EventIds -AppOutput $script:CrashResult.Output -ExpectedCount 1 + + if ($eventIds -and $eventIds.Count -gt 0) { + Write-Host "Event ID captured: $($eventIds[0])" -ForegroundColor Cyan + + $crashId = $eventIds[0] + + # Fetch event from Sentry (with polling) + try { + $script:CrashEvent = Get-SentryTestEvent -TagName 'test.crash_id' -TagValue "$crashId" + Write-Host "Event fetched from Sentry successfully" -ForegroundColor Green + } catch { + Write-Host "Failed to fetch event from Sentry: $_" -ForegroundColor Red + } + } else { + Write-Host "Warning: No event ID found in output" -ForegroundColor Yellow + } + } + + It "Should have non-zero exit code" { + $script:CrashResult.ExitCode | Should -Not -Be 0 + } + + It "Should output event ID" { + $eventIds = Get-EventIds -AppOutput $script:CrashResult.Output -ExpectedCount 1 + $eventIds | Should -Not -BeNullOrEmpty + $eventIds.Count | Should -Be 1 + } + + It "Should capture crash event in Sentry" { + $script:CrashEvent | Should -Not -BeNullOrEmpty + } + + It "Should have correct event type and platform" { + $script:CrashEvent.type | Should -Be 'error' + $script:CrashEvent.platform | Should -Be 'native' + } + + It "Should have exception information" { + $script:CrashEvent.exception | Should -Not -BeNullOrEmpty + $script:CrashEvent.exception.values | Should -Not -BeNullOrEmpty + } + + It "Should have stack trace" { + $exception = $script:CrashEvent.exception.values[0] + $exception.stacktrace | Should -Not -BeNullOrEmpty + $exception.stacktrace.frames | Should -Not -BeNullOrEmpty + } + + It "Should have user context" { + $script:CrashEvent.user | Should -Not -BeNullOrEmpty + $script:CrashEvent.user.username | Should -Be 'TestUser' + $script:CrashEvent.user.email | Should -Be 'user-mail@test.abc' + $script:CrashEvent.user.id | Should -Be '12345' + } + + It "Should have integration test tag" { + $tags = $script:CrashEvent.tags + ($tags | Where-Object { $_.key -eq 'test.suite' }).value | Should -Be 'integration' + } + + It "Should have breadcrumbs" { + $script:CrashEvent.breadcrumbs | Should -Not -BeNullOrEmpty + $script:CrashEvent.breadcrumbs.values | Should -Not -BeNullOrEmpty + } + } + + Context "Message Capture Tests" { + BeforeAll { + $script:MessageResult = $null + $script:MessageEvent = $null + + Write-Host "Running message capture test..." -ForegroundColor Yellow + + # Build arguments and execute application: + # -message-capture: Triggers integration test message scenario in the sample app + # -nullrhi: Runs without graphics rendering (headless mode) + # -unattended: Disables user prompts and interactive dialogs + # -stdout: Ensures logs are written to stdout on Linux/Unix systems + # -nosplash: Prevents splash screen and dialogs + $appArgs = @('-message-capture', '-nullrhi', '-unattended', '-stdout', '-nosplash') + $script:MessageResult = Invoke-SentryUnrealTestApp -Arguments $appArgs -TestName 'message' + + Write-Host "Message test executed. Exit code: $($script:MessageResult.ExitCode)" -ForegroundColor Cyan + + # Parse event ID from output + $eventIds = Get-EventIds -AppOutput $script:MessageResult.Output -ExpectedCount 1 + + if ($eventIds -and $eventIds.Count -gt 0) { + Write-Host "Event ID captured: $($eventIds[0])" -ForegroundColor Cyan + + # Fetch event from Sentry (with polling) using sanitized helper + try { + $script:MessageEvent = Get-SentryTestEvent -EventId $eventIds[0] + Write-Host "Event fetched from Sentry successfully" -ForegroundColor Green + } catch { + Write-Host "Failed to fetch event from Sentry: $_" -ForegroundColor Red + } + } else { + Write-Host "Warning: No event ID found in output" -ForegroundColor Yellow + } + } + + It "Should exit cleanly" { + $script:MessageResult.ExitCode | Should -Be 0 + } + + It "Should output event ID" { + $eventIds = Get-EventIds -AppOutput $script:MessageResult.Output -ExpectedCount 1 + $eventIds | Should -Not -BeNullOrEmpty + $eventIds.Count | Should -Be 1 + } + + It "Should output TEST_RESULT with success" { + $testResultLine = $script:MessageResult.Output | Where-Object { $_ -match 'TEST_RESULT:' } + $testResultLine | Should -Not -BeNullOrEmpty + $testResultLine | Should -Match '"success"\s*:\s*true' + } + + It "Should capture message event in Sentry" { + $script:MessageEvent | Should -Not -BeNullOrEmpty + } + + It "Should have correct platform" { + $script:MessageEvent.platform | Should -Be 'native' + } + + It "Should have message content" { + $script:MessageEvent.message | Should -Not -BeNullOrEmpty + $script:MessageEvent.message.formatted | Should -Match 'Integration test message' + } + + It "Should have user context" { + $script:MessageEvent.user | Should -Not -BeNullOrEmpty + $script:MessageEvent.user.username | Should -Be 'TestUser' + } + + It "Should have integration test tag" { + $tags = $script:MessageEvent.tags + ($tags | Where-Object { $_.key -eq 'test.suite' }).value | Should -Be 'integration' + } + + It "Should have breadcrumbs" { + $script:MessageEvent.breadcrumbs | Should -Not -BeNullOrEmpty + $script:MessageEvent.breadcrumbs.values | Should -Not -BeNullOrEmpty + } + } +} + +AfterAll { + Write-Host "Disconnecting from Sentry API..." -ForegroundColor Yellow + Disconnect-SentryApi + Write-Host "Integration tests complete" -ForegroundColor Green +} diff --git a/integration-test/README.md b/integration-test/README.md new file mode 100644 index 000000000..589f88f91 --- /dev/null +++ b/integration-test/README.md @@ -0,0 +1,115 @@ +# Sentry Unreal Integration Tests + +This directory contains integration tests for the Sentry Unreal SDK using Pester (PowerShell testing framework). + +## Prerequisites + +- **PowerShell 7+** (Core edition) +- **CMake 3.20+** +- **Pester 5+** - Install with: `Install-Module -Name Pester -Force -SkipPublisherCheck` +- **Pre-built SentryPlayground application** (from local build or CI artifact) +- **Environment variables**: + - `SENTRY_UNREAL_TEST_DSN` - Sentry test project DSN + - `SENTRY_AUTH_TOKEN` - Sentry API authentication token + - `SENTRY_UNREAL_TEST_APP_PATH` - Path to the SentryPlayground executable + +## Setup + +### 1. Configure Integration Test Environment + +Run CMake to download required PowerShell modules (app-runner): + +```bash +cd integration-test +cmake -B build -S . +``` + +This will: +- Download `app-runner` from GitHub (contains SentryApiClient and test utilities) +- Generate `TestConfig.local.ps1` with module paths + +**Note**: CMake configuration can be run without setting environment variables. The environment variables are only required at test runtime and will be validated by the test script itself. + +### 2. Get SentryPlayground Application + +#### Option A: Download from CI Artifacts + +1. Go to [GitHub Actions](https://github.com/getsentry/sentry-unreal/actions/workflows/ci.yml) +2. Find a successful workflow run +3. Download the appropriate artifact: + - `UE X.X sample build (Windows)` for Windows testing + - `UE X.X sample build (Linux)` for Linux testing +4. Extract to a known location + +#### Option B: Build Locally + +Follow the standard Unreal Engine build process for the [sample](./sample/) project. + +## Running Tests + +Before running tests, ensure you have set the required environment variables: + +### Windows + +```powershell +# Set environment variables +$env:SENTRY_UNREAL_TEST_DSN = "https://key@org.ingest.sentry.io/project" +$env:SENTRY_AUTH_TOKEN = "sntrys_your_token_here" +$env:SENTRY_UNREAL_TEST_APP_PATH = "path/to/SentryPlayground.exe" + +# Run tests +cd integration-test +Invoke-Pester Integration.Tests.ps1 +``` + +### Linux + +```bash +# Set environment variables +export SENTRY_UNREAL_TEST_DSN="https://key@org.ingest.sentry.io/project" +export SENTRY_AUTH_TOKEN="sntrys_your_token_here" +export SENTRY_UNREAL_TEST_APP_PATH="./path/to/SentryPlayground.sh" + +# Run tests +cd integration-test +pwsh -Command "Invoke-Pester Integration.Tests.ps1" +``` + +## Test Coverage + +The integration tests cover: + +### Crash Capture Tests +- Application crashes with non-zero exit code +- Event ID is captured from output (set via `test.crash_id` tag) +- Crash event appears in Sentry +- Exception information is present +- Stack traces are captured +- User context is included +- Integration test tags are set +- Breadcrumbs are collected + +### Message Capture Tests +- Application exits cleanly (exit code 0) +- Event ID is captured from output +- TEST_RESULT indicates success +- Message event appears in Sentry +- Message content is correct +- User context is included +- Integration test tags are set +- Breadcrumbs are collected + +## Output + +Test outputs are saved to `integration-test/output/`: +- `*-crash-stdout.log` - Crash test standard output +- `*-crash-stderr.log` - Crash test standard error +- `*-crash-result.json` - Full crash test result +- `*-message-stdout.log` - Message test standard output +- `*-message-stderr.log` - Message test standard error +- `*-message-result.json` - Full message test result +- `event-*.json` - Events fetched from Sentry API + +## CI Integration + +See `.github/workflows/integration-test-windows.yml` and `.github/workflows/integration-test-linux.yml` for CI usage examples. diff --git a/integration-test/TestConfig.ps1.in b/integration-test/TestConfig.ps1.in new file mode 100644 index 000000000..84909101d --- /dev/null +++ b/integration-test/TestConfig.ps1.in @@ -0,0 +1,5 @@ +# Auto-generated test configuration +# This file is generated by CMake - do not edit manually + +# Path to the fetched app-runner repository root +$global:AppRunnerPath = "@app-runner_SOURCE_DIR@" diff --git a/sample/Config/DefaultEngine.ini b/sample/Config/DefaultEngine.ini index 66348e797..bca84ad7e 100644 --- a/sample/Config/DefaultEngine.ini +++ b/sample/Config/DefaultEngine.ini @@ -230,6 +230,7 @@ bGeneratedSYMBundle=True [/Script/Sentry.SentrySettings] CrashReporterUrl="https://o447951.ingest.sentry.io/api/6253052/unreal/93c7a68867db43539980de54f09b139a/" Dsn="https://93c7a68867db43539980de54f09b139a@o447951.ingest.sentry.io/6253052" +IncludeSources=True [/Script/MacTargetPlatform.XcodeProjectSettings] BundleIdentifier=io.sentry.unreal.sample diff --git a/sample/Source/SentryPlayground/SentryPlaygroundGameInstance.cpp b/sample/Source/SentryPlayground/SentryPlaygroundGameInstance.cpp index 63c28023c..989b52943 100644 --- a/sample/Source/SentryPlayground/SentryPlaygroundGameInstance.cpp +++ b/sample/Source/SentryPlayground/SentryPlaygroundGameInstance.cpp @@ -8,6 +8,9 @@ #include "SentryPlaygroundUtils.h" #include "SentryUser.h" +#include "CoreGlobals.h" +#include "HAL/Platform.h" +#include "Misc/EngineVersionComparison.h" #include "Misc/CommandLine.h" #include "Engine/Engine.h" @@ -28,7 +31,7 @@ void USentryPlaygroundGameInstance::Init() void USentryPlaygroundGameInstance::RunIntegrationTest(const TCHAR* CommandLine) { - UE_LOG(LogSentrySample, Log, TEXT("Running integration test for command: %s\n"), CommandLine); + UE_LOG(LogSentrySample, Display, TEXT("Running integration test for command: %s\n"), CommandLine); USentrySubsystem* SentrySubsystem = GEngine->GetEngineSubsystem(); if (!SentrySubsystem) @@ -78,9 +81,12 @@ void USentryPlaygroundGameInstance::RunCrashTest() // Because we don't get the real crash event ID, create a fake one and set it as a tag // This tag is then used by integration test script in CI to fetch the event - FString EventId = FGuid::NewGuid().ToString(EGuidFormats::Digits); + FString EventId = FGuid::NewGuid().ToString(EGuidFormats::DigitsWithHyphens); - UE_LOG(LogSentrySample, Log, TEXT("EVENT_CAPTURED: %s\n"), *EventId); + UE_LOG(LogSentrySample, Display, TEXT("EVENT_CAPTURED: %s\n"), *EventId); + + // Flush logs to ensure output is captured before crash + GLog->Flush(); SentrySubsystem->SetTag(TEXT("test.crash_id"), EventId); @@ -93,7 +99,15 @@ void USentryPlaygroundGameInstance::RunMessageTest() FString EventId = SentrySubsystem->CaptureMessage(TEXT("Integration test message")); - UE_LOG(LogSentrySample, Log, TEXT("EVENT_CAPTURED: %s\n"), *EventId); + // Workaround for duplicated log messages in UE 4.27 on Linux +#if PLATFORM_LINUX && UE_VERSION_OLDER_THAN(5, 0, 0) + UE_LOG(LogSentrySample, Log, TEXT("EVENT_CAPTURED: %s\n"), *FormatEventIdWithHyphens(EventId)); +#else + UE_LOG(LogSentrySample, Display, TEXT("EVENT_CAPTURED: %s\n"), *FormatEventIdWithHyphens(EventId)); +#endif + + // Ensure events were flushed + SentrySubsystem->Close(); CompleteTestWithResult(TEXT("message-capture"), !EventId.IsEmpty(), TEXT("Test complete")); } @@ -115,9 +129,27 @@ void USentryPlaygroundGameInstance::ConfigureTestContext() void USentryPlaygroundGameInstance::CompleteTestWithResult(const FString& TestName, bool Result, const FString& Message) { - UE_LOG(LogSentrySample, Log, TEXT("TEST_RESULT: {\"test\":\"%s\",\"success\":%s,\"message\":\"%s\"}\n"), + UE_LOG(LogSentrySample, Display, TEXT("TEST_RESULT: {\"test\":\"%s\",\"success\":%s,\"message\":\"%s\"}\n"), *TestName, Result ? TEXT("true") : TEXT("false"), *Message); + // Flush logs to ensure output is captured before exit + GLog->Flush(); + // Close app after test is completed - FGenericPlatformMisc::RequestExit(false); + FPlatformMisc::RequestExitWithStatus(true, 0); +} + +FString USentryPlaygroundGameInstance::FormatEventIdWithHyphens(const FString& EventId) +{ + if (EventId.Len() == 32 && !EventId.Contains(TEXT("-"))) + { + return FString::Printf(TEXT("%s-%s-%s-%s-%s"), + *EventId.Mid(0, 8), + *EventId.Mid(8, 4), + *EventId.Mid(12, 4), + *EventId.Mid(16, 4), + *EventId.Mid(20, 12)); + } + + return EventId; } diff --git a/sample/Source/SentryPlayground/SentryPlaygroundGameInstance.h b/sample/Source/SentryPlayground/SentryPlaygroundGameInstance.h index 59f5672d2..636baeda3 100644 --- a/sample/Source/SentryPlayground/SentryPlaygroundGameInstance.h +++ b/sample/Source/SentryPlayground/SentryPlaygroundGameInstance.h @@ -27,4 +27,7 @@ class SENTRYPLAYGROUND_API USentryPlaygroundGameInstance : public UGameInstance void ConfigureTestContext(); void CompleteTestWithResult(const FString& TestName, bool Result, const FString& Message); + + /** Converts event ID to UUID format with hyphens (XXXXXXXX-XXXX-XXXX-XXXX-XXXXXXXXXXXX) */ + static FString FormatEventIdWithHyphens(const FString& EventId); };