diff --git a/.gitignore b/.gitignore index 2201e38a3..7eb5eab99 100644 --- a/.gitignore +++ b/.gitignore @@ -64,6 +64,7 @@ package-dev/alias-references.txt # ignore integration test files Samples/IntegrationTest* unity.log +unity-test.log # Ignore package release test-package-release/ diff --git a/Agents.md b/Agents.md index b81241096..b8c6d3182 100644 --- a/Agents.md +++ b/Agents.md @@ -11,19 +11,28 @@ The Sentry Unity SDK provides error monitoring, performance tracing, and crash r ### Quick Commands +**IMPORTANT**: Always run `dotnet build` from the repository root. Never build specific `.csproj` files directly. + ```bash # Download prebuilt native SDKs dotnet msbuild /t:DownloadNativeSDKs src/Sentry.Unity -# Build the Unity SDK +# Build the Unity SDK (always from root, never target specific .csproj files) dotnet build -# Run all tests -./test.sh +# Run all tests (builds SDK first) +pwsh scripts/run-tests.ps1 + +# Run specific test types +pwsh scripts/run-tests.ps1 -PlayMode +pwsh scripts/run-tests.ps1 -EditMode + +# Run filtered tests +pwsh scripts/run-tests.ps1 -Filter "TestClassName" +pwsh scripts/run-tests.ps1 -PlayMode -Filter "Throttler" -# Run specific test targets -dotnet msbuild /t:UnityEditModeTest /p:Configuration=Release test/Sentry.Unity.Editor.Tests -dotnet msbuild /t:UnityPlayModeTest /p:Configuration=Release +# Skip build for faster iteration +pwsh scripts/run-tests.ps1 -SkipBuild -Filter "MyTest" # Integration testing (local) ./test/Scripts.Integration.Test/integration-test.ps1 -Platform "macOS" -UnityVersion "2021.3.45f2" @@ -249,7 +258,7 @@ Scripts involved: | macOS | Objective-C | `__Internal` | `SentryCocoaBridgeProxy.cs`, `SentryNativeCocoa.cs` | | Windows | P/Invoke | `sentry` | `SentryNativeBridge.cs`, `CFunctions.cs` | | Linux | P/Invoke | `sentry` | `SentryNativeBridge.cs`, `CFunctions.cs` | -| PlayStation | Static | `__Internal` | `CFunctions.cs` | +| PlayStation | P/Invoke | `sentry` | `SentryNativeBridge.cs`, `CFunctions.cs` | ### Native SDK Submodules @@ -482,7 +491,14 @@ Key options: ### Running All Tests ```bash -./test.sh +# Run all tests (builds SDK first) +pwsh scripts/run-tests.ps1 + +# Run with filtering +pwsh scripts/run-tests.ps1 -Filter "TestClassName" + +# Skip build for faster iteration +pwsh scripts/run-tests.ps1 -SkipBuild ``` ### Integration Test Scripts @@ -520,10 +536,20 @@ Supported platforms: `macOS`, `Windows`, `Linux`, `Android`, `iOS`, `WebGL` ### Development Workflow +**Prerequisites (first-time setup or after clean):** +```bash +# Download native SDKs - REQUIRED before building +dotnet msbuild /t:DownloadNativeSDKs src/Sentry.Unity +``` + +**Development cycle:** 1. Make changes to source code in `src/` 2. Run `dotnet build` to build and update `package-dev/` -3. Test changes using the sample project or integration tests -4. Run `pwsh scripts/repack.ps1` before creating releases +3. Run `pwsh scripts/run-tests.ps1` to build and run all tests +4. Test changes using the sample project or integration tests +5. Run `pwsh scripts/repack.ps1` before creating releases + +> **Note:** The native SDKs in `package-dev/Plugins/` are not committed to the repository. You must run `DownloadNativeSDKs` before the first build or after cleaning the repository. ### Error Handling Patterns diff --git a/Directory.Build.targets b/Directory.Build.targets index 5e471defd..733cfff87 100644 --- a/Directory.Build.targets +++ b/Directory.Build.targets @@ -398,7 +398,7 @@ Related: https://forum.unity.com/threads/6572-debugger-agent-unable-to-listen-on - + @@ -411,7 +411,7 @@ Related: https://forum.unity.com/threads/6572-debugger-agent-unable-to-listen-on - + @@ -465,104 +465,6 @@ File.WriteAllLines(PackageManifestFile, lines); - - - - - - - - - - - - - 1 ? "s" : "")}."; -Log.LogError(errorMessage); - -Success = false; - -void PrintFailedTests(XElement element) -{ - foreach (var descendant in element.Descendants()) - { - if (descendant.Name != "test-case" - || descendant.Attribute("result")?.Value != "Failed") - { - continue; - } - - if (descendant.Descendants().Any(d => d.Name == "test-case")) - { - PrintFailedTests(descendant); - } - else - { - var sb = new StringBuilder() - .Append("Test ") - .Append(descendant.Attribute("id")?.Value) - .Append(": ") - .AppendLine(descendant.Attribute("name")?.Value); - - var failure = descendant.Descendants("failure") - .Descendants("message") - .FirstOrDefault() - ?.Value; - - var stack = descendant.Descendants("failure") - .Descendants("stack-trace") - .FirstOrDefault() - ?.Value; - - sb.AppendLine(failure) - .Append("Test StackTrace: ") - .AppendLine(stack); - -// MSBuild is breaking each line as if it was an error per line and not a single error. -// So Log.LogError got replaced by Console.WriteLine for now. - Console.WriteLine(sb.ToString()); - } - } -} -]]> - - - - - - - - - - - - - - - - - - - - - - + diff --git a/package-dev/Runtime/Sentry.Unity.Android.dll.meta b/package-dev/Runtime/Sentry.Unity.Android.dll.meta index a5fde1b78..60106f4f9 100644 --- a/package-dev/Runtime/Sentry.Unity.Android.dll.meta +++ b/package-dev/Runtime/Sentry.Unity.Android.dll.meta @@ -17,7 +17,7 @@ PluginImporter: enabled: 0 settings: Exclude Android: 0 - Exclude Editor: 0 + Exclude Editor: 1 Exclude Linux64: 1 Exclude Lumin: 1 Exclude OSXUniversal: 1 @@ -40,7 +40,7 @@ PluginImporter: - first: Editor: Editor second: - enabled: 1 + enabled: 0 settings: CPU: AnyCPU DefaultValueInitialized: true diff --git a/package-dev/Runtime/Sentry.Unity.Native.dll.meta b/package-dev/Runtime/Sentry.Unity.Native.dll.meta index 6b6e91dde..0581715de 100644 --- a/package-dev/Runtime/Sentry.Unity.Native.dll.meta +++ b/package-dev/Runtime/Sentry.Unity.Native.dll.meta @@ -16,7 +16,7 @@ PluginImporter: second: enabled: 0 settings: - Exclude Android: 1 + Exclude Android: 0 Exclude Editor: 1 Exclude Linux64: 0 Exclude OSXUniversal: 1 @@ -31,7 +31,7 @@ PluginImporter: - first: Android: Android second: - enabled: 0 + enabled: 1 settings: CPU: ARMv7 - first: diff --git a/scripts/download-native-sdks.ps1 b/scripts/download-native-sdks.ps1 new file mode 100644 index 000000000..63c3b9588 --- /dev/null +++ b/scripts/download-native-sdks.ps1 @@ -0,0 +1,136 @@ +#!/usr/bin/env pwsh + +param( + [Parameter()] + [string]$RepoRoot = "$PSScriptRoot/.." +) + +Set-StrictMode -Version latest +$ErrorActionPreference = 'Stop' +$PSNativeCommandUseErrorActionPreference = $true + +$ArtifactsDestination = Join-Path $RepoRoot "package-dev/Plugins" + +# SDK definitions with their existence checks +$SDKs = @( + @{ + Name = "Windows" + Destination = Join-Path $ArtifactsDestination "Windows" + CheckFile = "Sentry/sentry.dll" + }, + @{ + Name = "Linux" + Destination = Join-Path $ArtifactsDestination "Linux" + CheckFile = "Sentry/libsentry.so" + }, + @{ + Name = "Android" + Destination = Join-Path $ArtifactsDestination "Android" + CheckDir = "Sentry~" + ExpectedFileCount = 4 + } +) + +function Test-SDKPresent { + param($SDK) + + if ($SDK.ContainsKey('CheckFile')) { + $checkPath = Join-Path $SDK.Destination $SDK.CheckFile + return Test-Path $checkPath + } + elseif ($SDK.ContainsKey('CheckDir')) { + $checkPath = Join-Path $SDK.Destination $SDK.CheckDir + if (-not (Test-Path $checkPath)) { + return $false + } + $fileCount = (Get-ChildItem -Path $checkPath -File).Count + return $fileCount -ge $SDK.ExpectedFileCount + } + return $false +} + +function Get-LatestSuccessfulRunId { + Write-Host "Fetching latest successful CI run ID..." -ForegroundColor Yellow + + $result = gh run list --branch main --workflow CI --json "conclusion,databaseId" --jq 'first(.[] | select(.conclusion == "success") | .databaseId)' + + if (-not $result -or $result -eq "null") { + Write-Error "Failed to find a successful CI run on main branch" + exit 1 + } + + Write-Host "Found run ID: $result" -ForegroundColor Green + return $result +} + +function Download-SDK { + param( + [Parameter(Mandatory)] + [string]$Name, + [Parameter(Mandatory)] + [string]$Destination, + [Parameter(Mandatory)] + [string]$RunId + ) + + Write-Host "Downloading $Name SDK..." -ForegroundColor Yellow + + # Remove existing directory if present (partial download) + if (Test-Path $Destination) { + Write-Host " Removing existing directory..." -ForegroundColor Gray + Remove-Item -Path $Destination -Recurse -Force + } + + $artifactName = "$Name-sdk" + gh run download $RunId -n $artifactName -D $Destination + + if ($LASTEXITCODE -ne 0) { + Write-Error "Failed to download $Name SDK" + exit 1 + } + + Write-Host " Downloaded $Name SDK successfully" -ForegroundColor Green +} + +# Main logic +Write-Host "Checking native SDK status..." -ForegroundColor Cyan +Write-Host "" + +$sdksToDownload = @() + +foreach ($sdk in $SDKs) { + if (Test-SDKPresent $sdk) { + Write-Host "$($sdk.Name) SDK already present, skipping download." -ForegroundColor Green + } + else { + Write-Host "$($sdk.Name) SDK not found, will download." -ForegroundColor Yellow + $sdksToDownload += $sdk + } +} + +Write-Host "" + +if ($sdksToDownload.Count -eq 0) { + Write-Host "All native SDKs are already present." -ForegroundColor Green + exit 0 +} + +# Fetch run ID only if we need to download something +$runId = Get-LatestSuccessfulRunId + +foreach ($sdk in $sdksToDownload) { + Download-SDK -Name $sdk.Name -Destination $sdk.Destination -RunId $runId +} + +Write-Host "" +Write-Host "Restoring package-dev/Plugins to latest git commit..." -ForegroundColor Yellow +Push-Location $RepoRoot +try { + git restore package-dev/Plugins +} +finally { + Pop-Location +} + +Write-Host "" +Write-Host "Native SDK download completed successfully!" -ForegroundColor Green diff --git a/scripts/report-test-results.ps1 b/scripts/report-test-results.ps1 new file mode 100644 index 000000000..0ab90c9f4 --- /dev/null +++ b/scripts/report-test-results.ps1 @@ -0,0 +1,91 @@ +<# +.SYNOPSIS + Reports Unity test results from NUnit XML file. + +.DESCRIPTION + Parses NUnit XML test results and prints a summary. Exits with code 1 if tests failed. + Designed to be called from MSBuild targets. + +.PARAMETER Path + Path to the NUnit XML test results file. + +.EXAMPLE + pwsh scripts/report-test-results.ps1 artifacts/test/playmode/results.xml +#> + +param( + [Parameter(Mandatory = $true, Position = 0)] + [string] $Path +) + +Set-StrictMode -Version latest +$ErrorActionPreference = "Stop" + +. $PSScriptRoot/test-utils.ps1 + +if (-not (Test-Path $Path)) { + Write-Host "Test results file not found at $Path" -ForegroundColor Red + exit 1 +} + +$results = Parse-TestResults $Path + +if ($null -eq $results) { + Write-Host "Failed to parse test results" -ForegroundColor Red + exit 1 +} + +if ($results.Total -eq 0) { + Write-Host "Unity test results is empty." -ForegroundColor Red + exit 1 +} + +# Print summary (matching format from original C# implementation) +$status = if ($results.Success) { "Passed" } else { "Failed" } +Write-Host "$status in $($results.Duration)s" +Write-Host (" Passed: {0,3}" -f $results.Passed) +Write-Host (" Failed: {0,3}" -f $results.Failed) +Write-Host (" Skipped: {0,3}" -f $results.Skipped) +Write-Host (" Inconclusive: {0,3}" -f $results.Inconclusive) + +# Print failed test details +if ($results.Failed -gt 0) { + Write-Host "" + + # Re-parse to get stack traces (not included in Parse-TestResults) + [xml]$xml = Get-Content $Path + $failedNodes = $xml.SelectNodes("//test-case[@result='Failed']") + + foreach ($node in $failedNodes) { + # Skip parent test-cases that contain child test-cases + if ($node.SelectNodes(".//test-case").Count -gt 0) { + continue + } + + $name = $node.GetAttribute("name") + $id = $node.GetAttribute("id") + Write-Host "Test $id`: $name" + + $message = $node.SelectSingleNode("failure/message") + if ($message) { + Write-Host $message.InnerText + } + + $stackTrace = $node.SelectSingleNode("failure/stack-trace") + if ($stackTrace) { + Write-Host "Test StackTrace:" + Write-Host $stackTrace.InnerText + } + + Write-Host "" + } + + $testWord = if ($results.Failed -gt 1) { "tests" } else { "test" } + Write-Host "Test run completed with $($results.Failed) failing $testWord." -ForegroundColor Red +} + +# Exit based on overall success (handles edge cases where result != "Passed" but failed count is 0) +if (-not $results.Success) { + exit 1 +} +exit 0 diff --git a/scripts/run-tests.ps1 b/scripts/run-tests.ps1 new file mode 100644 index 000000000..df3baffe7 --- /dev/null +++ b/scripts/run-tests.ps1 @@ -0,0 +1,215 @@ +<# +.SYNOPSIS + Runs Unity tests for the Sentry SDK for Unity. + +.DESCRIPTION + This script builds the SDK and runs PlayMode and/or EditMode tests with optional filtering. + +.PARAMETER PlayMode + Run PlayMode tests (runtime tests). + +.PARAMETER EditMode + Run EditMode tests (editor tests). + +.PARAMETER Filter + Test name filter passed to Unity's -testFilter (regex supported). + +.PARAMETER Category + Test category filter passed to Unity's -testCategory. + +.PARAMETER UnityVersion + Override Unity version (default: read from ProjectVersion.txt or $env:UNITY_VERSION). + +.PARAMETER SkipBuild + Skip the dotnet build step (for faster iteration when DLLs are current). + +.EXAMPLE + pwsh scripts/run-tests.ps1 + # Runs all tests (PlayMode + EditMode) + +.EXAMPLE + pwsh scripts/run-tests.ps1 -PlayMode -Filter "Throttler" + # Runs only PlayMode tests matching "Throttler" + +.EXAMPLE + pwsh scripts/run-tests.ps1 -SkipBuild -EditMode + # Runs EditMode tests without rebuilding +#> + +param( + [switch] $PlayMode, + [switch] $EditMode, + [string] $Filter, + [string] $Category, + [string] $UnityVersion, + [switch] $SkipBuild +) + +Set-StrictMode -Version latest +$ErrorActionPreference = "Stop" + +. $PSScriptRoot/test-utils.ps1 + +$repoRoot = Resolve-Path "$PSScriptRoot/.." +$sampleProject = "$repoRoot/samples/unity-of-bugs" + +# Default to both if neither specified +if (-not $PlayMode -and -not $EditMode) { + $PlayMode = $true + $EditMode = $true +} + +# Find Unity version +if (-not $UnityVersion) { + $UnityVersion = $env:UNITY_VERSION + if (-not $UnityVersion) { + $projectVersionFile = "$sampleProject/ProjectSettings/ProjectVersion.txt" + if (-not (Test-Path $projectVersionFile)) { + Write-Host "Error: ProjectVersion.txt not found at $projectVersionFile" -ForegroundColor Red + exit 1 + } + $content = Get-Content $projectVersionFile -Raw + $match = [regex]::Match($content, "m_EditorVersion:\s*(.+)") + if (-not $match.Success) { + Write-Host "Error: Could not parse Unity version from ProjectVersion.txt" -ForegroundColor Red + exit 1 + } + $UnityVersion = $match.Groups[1].Value.Trim() + } +} +# Find Unity path (platform-specific) +if ($IsMacOS) { + $unityPath = "/Applications/Unity/Hub/Editor/$UnityVersion/Unity.app/Contents/MacOS/Unity" +} +elseif ($IsWindows) { + $unityPath = "C:/Program Files/Unity/Hub/Editor/$UnityVersion/Editor/Unity.exe" +} +elseif ($IsLinux) { + $unityPath = "$env:HOME/Unity/Hub/Editor/$UnityVersion/Editor/Unity" +} +else { + Write-Host "Error: Unsupported platform" -ForegroundColor Red + exit 1 +} + +if (-not (Test-Path $unityPath)) { + Write-Host "Error: Unity $UnityVersion not found at: $unityPath" -ForegroundColor Red + exit 1 +} + +# Build SDK +if (-not $SkipBuild) { + Write-Host "Building SDK... " -NoNewline + $buildStart = Get-Date + & dotnet build "$repoRoot" --configuration Release --verbosity quiet 2>&1 | Out-Null + if ($LASTEXITCODE -ne 0) { + Write-Host "[FAIL]" -ForegroundColor Red + exit 1 + } + $buildTime = (Get-Date) - $buildStart + Write-Host "[OK] ($([math]::Round($buildTime.TotalSeconds, 1))s)" -ForegroundColor Green +} + +# Build test arguments +function Build-TestArgs([string] $testPlatform, [string] $resultsPath) { + $testArgs = @( + "-batchmode", "-nographics", "-runTests", + "-testPlatform", $testPlatform, + "-projectPath", $sampleProject, + "-testResults", $resultsPath + ) + if ($Filter) { $testArgs += @("-testFilter", $Filter) } + if ($Category) { $testArgs += @("-testCategory", $Category) } + return $testArgs +} + +# Run Unity quietly and return success/failure +function Run-Unity-Quiet([string] $unityExe, [string[]] $arguments) { + $logFile = "$repoRoot/unity-test.log" + $arguments += @("-logFile", $logFile) + + # Remove old log + Remove-Item $logFile -ErrorAction SilentlyContinue + + # Run Unity and wait for completion + $process = Start-Process -FilePath $unityExe -ArgumentList $arguments -PassThru + $process | Wait-Process + + return $process.ExitCode +} + +# Run tests and report results +function Run-Tests([string] $name, [string] $platform, [string] $resultsPath) { + $filterInfo = if ($Filter) { " (filter: `"$Filter`")" } else { "" } + Write-Host "Running $name tests$filterInfo... " -NoNewline + + # Ensure results directory exists + $resultsDir = Split-Path $resultsPath -Parent + if (-not (Test-Path $resultsDir)) { + New-Item -ItemType Directory -Path $resultsDir -Force | Out-Null + } + + # Remove old results + Remove-Item $resultsPath -ErrorAction SilentlyContinue + + # Build and run Unity quietly + $testArgs = Build-TestArgs $platform $resultsPath + $null = Run-Unity-Quiet $unityPath $testArgs + + # Parse and display results + $results = Parse-TestResults $resultsPath + if ($null -eq $results) { + Write-Host "[FAIL] Could not parse test results" -ForegroundColor Red + Write-Host " Check unity-test.log for details" -ForegroundColor DarkGray + return $false + } + + if ($results.Total -eq 0) { + Write-Host "[WARN] No tests found" -ForegroundColor Yellow + return $true + } + + $symbol = if ($results.Success) { "[PASS]" } else { "[FAIL]" } + $color = if ($results.Success) { "Green" } else { "Red" } + $duration = [math]::Round($results.Duration, 1) + + Write-Host "$symbol $($results.Passed) passed, $($results.Failed) failed, $($results.Inconclusive) inconclusive ($duration`s)" -ForegroundColor $color + + # Show failed test details + if ($results.Failed -gt 0) { + Write-Host "" + foreach ($test in $results.FailedTests) { + Write-Host " FAILED: $($test.Name)" -ForegroundColor Red + if ($test.Message) { + $test.Message.Trim() -split "`n" | Select-Object -First 3 | ForEach-Object { + Write-Host " $($_.Trim())" -ForegroundColor DarkGray + } + } + } + } + + return $results.Success +} + +# Run requested tests +$allPassed = $true + +if ($PlayMode) { + $result = Run-Tests "PlayMode" "PlayMode" "$repoRoot/artifacts/test/playmode/results.xml" + if ($result -ne $true) { $allPassed = $false } +} + +if ($EditMode) { + $result = Run-Tests "EditMode" "EditMode" "$repoRoot/artifacts/test/editmode/results.xml" + if ($result -ne $true) { $allPassed = $false } +} + +# Final summary +if ($allPassed) { + Write-Host "`nAll tests passed." -ForegroundColor Green + exit 0 +} +else { + Write-Host "`nTests failed." -ForegroundColor Red + exit 1 +} diff --git a/scripts/test-utils.ps1 b/scripts/test-utils.ps1 new file mode 100644 index 000000000..348dca7b0 --- /dev/null +++ b/scripts/test-utils.ps1 @@ -0,0 +1,69 @@ +# Shared test utilities for Unity test result parsing + +<# +.SYNOPSIS + Parses NUnit XML test results from Unity test runner. + +.DESCRIPTION + Reads an NUnit XML test results file and returns a hashtable with test statistics + and details about failed tests. + +.PARAMETER Path + Path to the NUnit XML test results file. + +.OUTPUTS + Hashtable with: Total, Passed, Failed, Inconclusive, Skipped, Duration, Success, FailedTests + Returns $null if the file doesn't exist or cannot be parsed. + +.EXAMPLE + $results = Parse-TestResults "artifacts/test/playmode/results.xml" + if ($results.Success) { Write-Host "All tests passed!" } +#> +function Parse-TestResults([string] $Path) { + if (-not (Test-Path $Path)) { + return $null + } + + try { + [xml]$xml = Get-Content $Path + } + catch { + Write-Host " Failed to parse XML: $_" -ForegroundColor Red + return $null + } + + $testRun = $xml.'test-run' + + if ($null -eq $testRun) { + Write-Host " Invalid test results XML" -ForegroundColor Red + return $null + } + + $result = @{ + Total = [int]$testRun.total + Passed = [int]$testRun.passed + Failed = [int]$testRun.failed + Inconclusive = [int]$testRun.inconclusive + Skipped = [int]$testRun.skipped + Duration = [double]$testRun.duration + Success = $testRun.result -eq "Passed" + FailedTests = @() + } + + # Collect failed test details + if ($result.Failed -gt 0) { + $failedNodes = $xml.SelectNodes("//test-case[@result='Failed']") + $result.FailedTests = @($failedNodes | ForEach-Object { + $msg = $null + if ($_.failure -and $_.failure.message) { + $msg = $_.failure.message.InnerText + } + @{ + Name = $_.fullname + Message = $msg + } + }) + } + + return $result +} diff --git a/src/Sentry.Unity.Android/NativeContextWriter.cs b/src/Sentry.Unity.Android/NativeContextWriter.cs index 573a8f2e6..7a4162145 100644 --- a/src/Sentry.Unity.Android/NativeContextWriter.cs +++ b/src/Sentry.Unity.Android/NativeContextWriter.cs @@ -1,4 +1,4 @@ -using CWUtil = Sentry.Unity.NativeUtils.ContextWriter; +using CWUtil = Sentry.Unity.Native.ContextWriterUtils; namespace Sentry.Unity.Android; diff --git a/src/Sentry.Unity.Android/Sentry.Unity.Android.csproj b/src/Sentry.Unity.Android/Sentry.Unity.Android.csproj index e84334f9f..a620dd65c 100644 --- a/src/Sentry.Unity.Android/Sentry.Unity.Android.csproj +++ b/src/Sentry.Unity.Android/Sentry.Unity.Android.csproj @@ -6,6 +6,7 @@ + diff --git a/src/Sentry.Unity.Android/SentryNativeAndroid.cs b/src/Sentry.Unity.Android/SentryNativeAndroid.cs index 0a04d476c..f93367a66 100644 --- a/src/Sentry.Unity.Android/SentryNativeAndroid.cs +++ b/src/Sentry.Unity.Android/SentryNativeAndroid.cs @@ -69,6 +69,7 @@ public static void Configure(SentryUnityOptions options) options.NativeContextWriter = new NativeContextWriter(SentryJava); options.ScopeObserver = new AndroidJavaScopeObserver(options, SentryJava); options.EnableScopeSync = true; + options.NativeDebugImageProvider = new Native.NativeDebugImageProvider(); options.CrashedLastRun = () => { options.DiagnosticLogger?.LogDebug("Checking for 'CrashedLastRun'"); diff --git a/src/Sentry.Unity/NativeUtils/CFunctions.cs b/src/Sentry.Unity.Native/CFunctions.cs similarity index 86% rename from src/Sentry.Unity/NativeUtils/CFunctions.cs rename to src/Sentry.Unity.Native/CFunctions.cs index 76088e02b..4a644e70c 100644 --- a/src/Sentry.Unity/NativeUtils/CFunctions.cs +++ b/src/Sentry.Unity.Native/CFunctions.cs @@ -4,10 +4,16 @@ using Sentry.Protocol; using UnityEngine; -namespace Sentry.Unity.NativeUtils; +namespace Sentry.Unity.Native; internal static class C { +#if SENTRY_NATIVE_SWITCH + private const string SentryLib = "__Internal"; +#else + private const string SentryLib = "sentry"; +#endif + internal static void SetValueIfNotNull(sentry_value_t obj, string key, string? value) { if (value is not null) @@ -40,6 +46,14 @@ internal static void SetValueIfNotNull(sentry_value_t obj, string key, double? v } } + internal static void SetValueIfNotNull(sentry_value_t obj, string key, long? value) + { + if (value.HasValue) + { + _ = sentry_value_set_by_key(obj, key, sentry_value_new_double(value.Value)); + } + } + internal static sentry_value_t? GetValueOrNul(sentry_value_t obj, string key) { var cValue = sentry_value_get_by_key(obj, key); @@ -77,78 +91,78 @@ internal static void SetValueIfNotNull(sentry_value_t obj, string key, double? v return null; } - [DllImport("sentry")] + [DllImport(SentryLib)] internal static extern sentry_value_t sentry_value_new_object(); - [DllImport("sentry")] + [DllImport(SentryLib)] internal static extern sentry_value_t sentry_value_new_null(); - [DllImport("sentry")] + [DllImport(SentryLib)] internal static extern sentry_value_t sentry_value_new_bool(int value); - [DllImport("sentry")] + [DllImport(SentryLib)] internal static extern sentry_value_t sentry_value_new_double(double value); - [DllImport("sentry")] + [DllImport(SentryLib)] internal static extern sentry_value_t sentry_value_new_int32(int value); - [DllImport("sentry")] + [DllImport(SentryLib)] internal static extern sentry_value_t sentry_value_new_string(string value); - [DllImport("sentry")] + [DllImport(SentryLib)] internal static extern sentry_value_t sentry_value_new_breadcrumb(string? type, string? message); - [DllImport("sentry")] + [DllImport(SentryLib)] internal static extern int sentry_value_set_by_key(sentry_value_t value, string k, sentry_value_t v); internal static bool IsNull(sentry_value_t value) => sentry_value_is_null(value) != 0; - [DllImport("sentry")] + [DllImport(SentryLib)] internal static extern int sentry_value_is_null(sentry_value_t value); - [DllImport("sentry")] + [DllImport(SentryLib)] internal static extern int sentry_value_as_int32(sentry_value_t value); - [DllImport("sentry")] + [DllImport(SentryLib)] internal static extern double sentry_value_as_double(sentry_value_t value); - [DllImport("sentry")] + [DllImport(SentryLib)] internal static extern IntPtr sentry_value_as_string(sentry_value_t value); - [DllImport("sentry")] + [DllImport(SentryLib)] internal static extern UIntPtr sentry_value_get_length(sentry_value_t value); - [DllImport("sentry")] + [DllImport(SentryLib)] internal static extern sentry_value_t sentry_value_get_by_index(sentry_value_t value, UIntPtr index); - [DllImport("sentry")] + [DllImport(SentryLib)] internal static extern sentry_value_t sentry_value_get_by_key(sentry_value_t value, string key); - [DllImport("sentry")] + [DllImport(SentryLib)] internal static extern void sentry_set_context(string key, sentry_value_t value); - [DllImport("sentry")] + [DllImport(SentryLib)] internal static extern void sentry_add_breadcrumb(sentry_value_t breadcrumb); - [DllImport("sentry")] + [DllImport(SentryLib)] internal static extern void sentry_set_tag(string key, string value); - [DllImport("sentry")] + [DllImport(SentryLib)] internal static extern void sentry_remove_tag(string key); - [DllImport("sentry")] + [DllImport(SentryLib)] internal static extern void sentry_set_user(sentry_value_t user); - [DllImport("sentry")] + [DllImport(SentryLib)] internal static extern void sentry_remove_user(); - [DllImport("sentry")] + [DllImport(SentryLib)] internal static extern void sentry_set_extra(string key, sentry_value_t value); - [DllImport("sentry")] + [DllImport(SentryLib)] internal static extern void sentry_remove_extra(string key); - [DllImport("sentry")] + [DllImport(SentryLib)] internal static extern void sentry_set_trace(string traceId, string parentSpanId); internal static readonly Lazy> DebugImages = new(LoadDebugImages); @@ -202,10 +216,10 @@ private static IEnumerable LoadDebugImages() // Returns a new reference to an immutable, frozen list. // The reference must be released with `sentry_value_decref`. - [DllImport("sentry")] + [DllImport(SentryLib)] private static extern sentry_value_t sentry_get_modules_list(); - [DllImport("sentry")] + [DllImport(SentryLib)] internal static extern void sentry_value_decref(sentry_value_t value); // native union sentry_value_u/t diff --git a/src/Sentry.Unity/NativeUtils/ContextWriter.cs b/src/Sentry.Unity.Native/ContextWriterUtils.cs similarity index 98% rename from src/Sentry.Unity/NativeUtils/ContextWriter.cs rename to src/Sentry.Unity.Native/ContextWriterUtils.cs index ec8fe9473..c825c3ad5 100644 --- a/src/Sentry.Unity/NativeUtils/ContextWriter.cs +++ b/src/Sentry.Unity.Native/ContextWriterUtils.cs @@ -1,6 +1,6 @@ -namespace Sentry.Unity.NativeUtils; +namespace Sentry.Unity.Native; -internal static class ContextWriter +internal static class ContextWriterUtils { internal static void WriteApp(string? AppStartTime, string? AppBuildType) { diff --git a/src/Sentry.Unity.Native/NativeContextWriter.cs b/src/Sentry.Unity.Native/NativeContextWriter.cs index 1c686abb1..fce658bc1 100644 --- a/src/Sentry.Unity.Native/NativeContextWriter.cs +++ b/src/Sentry.Unity.Native/NativeContextWriter.cs @@ -1,5 +1,3 @@ -using CWUtil = Sentry.Unity.NativeUtils.ContextWriter; - namespace Sentry.Unity.Native; internal class NativeContextWriter : ContextWriter @@ -40,11 +38,11 @@ protected override void WriteScope( string? UnityRenderingThreadingMode ) { - CWUtil.WriteApp(AppStartTime, AppBuildType); + ContextWriterUtils.WriteApp(AppStartTime, AppBuildType); - CWUtil.WriteOS(OperatingSystemRawDescription); + ContextWriterUtils.WriteOS(OperatingSystemRawDescription); - CWUtil.WriteDevice( + ContextWriterUtils.WriteDevice( DeviceProcessorCount, DeviceCpuDescription, DeviceTimezone, @@ -57,7 +55,7 @@ protected override void WriteScope( DeviceMemorySize ); - CWUtil.WriteGpu( + ContextWriterUtils.WriteGpu( GpuId, GpuName, GpuVendorName, @@ -74,7 +72,7 @@ protected override void WriteScope( GpuMultiThreadedRendering, GpuGraphicsShaderLevel); - CWUtil.WriteUnity( + ContextWriterUtils.WriteUnity( EditorVersion, UnityInstallMode, UnityTargetFrameRate, diff --git a/src/Sentry.Unity.Native/NativeDebugImageProvider.cs b/src/Sentry.Unity.Native/NativeDebugImageProvider.cs new file mode 100644 index 000000000..3c813959e --- /dev/null +++ b/src/Sentry.Unity.Native/NativeDebugImageProvider.cs @@ -0,0 +1,9 @@ +using System.Collections.Generic; +using Sentry.Protocol; + +namespace Sentry.Unity.Native; + +internal class NativeDebugImageProvider : INativeDebugImageProvider +{ + public IEnumerable GetDebugImages() => C.DebugImages.Value; +} diff --git a/src/Sentry.Unity.Native/NativeScopeObserver.cs b/src/Sentry.Unity.Native/NativeScopeObserver.cs index a5201125f..bebeae55c 100644 --- a/src/Sentry.Unity.Native/NativeScopeObserver.cs +++ b/src/Sentry.Unity.Native/NativeScopeObserver.cs @@ -1,5 +1,4 @@ using System; -using C = Sentry.Unity.NativeUtils.C; namespace Sentry.Unity.Native; diff --git a/src/Sentry.Unity.Native/Properties/AssemblyInfo.cs b/src/Sentry.Unity.Native/Properties/AssemblyInfo.cs index cbcda1aff..be1f926c8 100644 --- a/src/Sentry.Unity.Native/Properties/AssemblyInfo.cs +++ b/src/Sentry.Unity.Native/Properties/AssemblyInfo.cs @@ -1,3 +1,4 @@ using System.Runtime.CompilerServices; +[assembly: InternalsVisibleTo("Sentry.Unity.Android")] [assembly: InternalsVisibleTo("Sentry.Unity.Native.Tests")] diff --git a/src/Sentry.Unity.Native/SentryNative.cs b/src/Sentry.Unity.Native/SentryNative.cs index fb4c5aa59..455ab39cc 100644 --- a/src/Sentry.Unity.Native/SentryNative.cs +++ b/src/Sentry.Unity.Native/SentryNative.cs @@ -2,7 +2,6 @@ using Sentry.Extensibility; using Sentry.Unity.Integrations; using System.Collections.Generic; -using Sentry.Unity.NativeUtils; using UnityEngine; using UnityEngine.Analytics; @@ -60,6 +59,7 @@ internal static void Configure(SentryUnityOptions options, RuntimePlatform platf options.ScopeObserver = new NativeScopeObserver(options); options.EnableScopeSync = true; options.NativeContextWriter = new NativeContextWriter(); + options.NativeDebugImageProvider = new NativeDebugImageProvider(); options.DefaultUserId = GetInstallationId(); if (options.DefaultUserId is not null) diff --git a/src/Sentry.Unity.Native/SentryNativeBridge.cs b/src/Sentry.Unity.Native/SentryNativeBridge.cs index b73ec7203..5a999d291 100644 --- a/src/Sentry.Unity.Native/SentryNativeBridge.cs +++ b/src/Sentry.Unity.Native/SentryNativeBridge.cs @@ -14,6 +14,12 @@ namespace Sentry.Unity.Native; /// internal static class SentryNativeBridge { +#if SENTRY_NATIVE_SWITCH + private const string SentryLib = "__Internal"; +#else + private const string SentryLib = "sentry"; +#endif + public static bool Init(SentryUnityOptions options) { _useLibC = Application.platform @@ -120,40 +126,40 @@ internal static string GetCacheDirectory(SentryUnityOptions options) internal static void ReinstallBackend() => sentry_reinstall_backend(); // libsentry.so - [DllImport("sentry")] + [DllImport(SentryLib)] private static extern IntPtr sentry_options_new(); - [DllImport("sentry")] + [DllImport(SentryLib)] private static extern void sentry_options_set_dsn(IntPtr options, string dsn); - [DllImport("sentry")] + [DllImport(SentryLib)] private static extern void sentry_options_set_release(IntPtr options, string release); - [DllImport("sentry")] + [DllImport(SentryLib)] private static extern void sentry_options_set_debug(IntPtr options, int debug); - [DllImport("sentry")] + [DllImport(SentryLib)] private static extern void sentry_options_set_environment(IntPtr options, string environment); - [DllImport("sentry")] + [DllImport(SentryLib)] private static extern void sentry_options_set_sample_rate(IntPtr options, double rate); - [DllImport("sentry")] + [DllImport(SentryLib)] private static extern void sentry_options_set_database_path(IntPtr options, string path); - [DllImport("sentry")] + [DllImport(SentryLib)] private static extern void sentry_options_set_database_pathw(IntPtr options, [MarshalAs(UnmanagedType.LPWStr)] string path); - [DllImport("sentry")] + [DllImport(SentryLib)] private static extern void sentry_options_set_auto_session_tracking(IntPtr options, int debug); - [DllImport("sentry")] + [DllImport(SentryLib)] private static extern void sentry_options_set_attach_screenshot(IntPtr options, int attachScreenshot); [UnmanagedFunctionPointer(CallingConvention.Cdecl, SetLastError = true)] private delegate void sentry_logger_function_t(int level, IntPtr message, IntPtr argsAddress, IntPtr userData); - [DllImport("sentry")] + [DllImport(SentryLib)] private static extern void sentry_options_set_logger(IntPtr options, sentry_logger_function_t logger, IntPtr userData); // The logger we should forward native messages to. This is referenced by nativeLog() which in turn for. @@ -302,18 +308,18 @@ private static void WithMarshalledStruct(T structure, Action action) action(ptr); }); - [DllImport("sentry")] + [DllImport(SentryLib)] private static extern int sentry_init(IntPtr options); - [DllImport("sentry")] + [DllImport(SentryLib)] private static extern int sentry_close(); - [DllImport("sentry")] + [DllImport(SentryLib)] private static extern int sentry_get_crashed_last_run(); - [DllImport("sentry")] + [DllImport(SentryLib)] private static extern int sentry_clear_crashed_last_run(); - [DllImport("sentry")] + [DllImport(SentryLib)] private static extern void sentry_reinstall_backend(); } diff --git a/src/Sentry.Unity/INativeDebugImageProvider.cs b/src/Sentry.Unity/INativeDebugImageProvider.cs new file mode 100644 index 000000000..244251582 --- /dev/null +++ b/src/Sentry.Unity/INativeDebugImageProvider.cs @@ -0,0 +1,15 @@ +using System.Collections.Generic; +using Sentry.Protocol; + +namespace Sentry.Unity; + +/// +/// Provides debug images from the native SDK. +/// +public interface INativeDebugImageProvider +{ + /// + /// Gets the list of debug images from the native SDK. + /// + IEnumerable GetDebugImages(); +} diff --git a/src/Sentry.Unity/Il2CppEventProcessor.cs b/src/Sentry.Unity/Il2CppEventProcessor.cs index 279b0b846..44a09025b 100644 --- a/src/Sentry.Unity/Il2CppEventProcessor.cs +++ b/src/Sentry.Unity/Il2CppEventProcessor.cs @@ -5,7 +5,6 @@ using Sentry.Extensibility; using Sentry.Protocol; using Sentry.Unity.Integrations; -using Sentry.Unity.NativeUtils; using UnityEngine; using Application = UnityEngine.Application; @@ -231,7 +230,8 @@ public DebugImageInfo(DebugImage image) // Only on platforms where we actually use sentry-native. if (IsPlatformSupportedBySentryNative() && Options.IsNativeSupportEnabled()) { - var nativeDebugImages = C.DebugImages.Value; + var nativeDebugImages = Options.NativeDebugImageProvider?.GetDebugImages() + ?? Enumerable.Empty(); foreach (var image in nativeDebugImages) { if (image.ImageSize is null) diff --git a/src/Sentry.Unity/SentryUnityOptions.cs b/src/Sentry.Unity/SentryUnityOptions.cs index 155fd3716..2f41524b3 100644 --- a/src/Sentry.Unity/SentryUnityOptions.cs +++ b/src/Sentry.Unity/SentryUnityOptions.cs @@ -343,6 +343,11 @@ internal string? DefaultUserId /// internal ContextWriter? NativeContextWriter { get; set; } = null; + /// + /// Provides debug images from the native SDK for IL2CPP line number support. + /// + internal INativeDebugImageProvider? NativeDebugImageProvider { get; set; } = null; + /// /// Used to close down the native SDK /// diff --git a/test.sh b/test.sh deleted file mode 100755 index c87c907d0..000000000 --- a/test.sh +++ /dev/null @@ -1,3 +0,0 @@ -dotnet msbuild /t:UnityEditModeTest /p:Configuration=Release -dotnet msbuild /t:UnityPlayModeTest /p:Configuration=Release -dotnet msbuild /t:UnitySmokeTestStandalonePlayerIL2CPP /p:Configuration=Release diff --git a/test/Sentry.Unity.Editor.iOS.Tests/NativeOptionsTests.cs b/test/Sentry.Unity.Editor.iOS.Tests/NativeOptionsTests.cs index 814bd017b..71aecac29 100644 --- a/test/Sentry.Unity.Editor.iOS.Tests/NativeOptionsTests.cs +++ b/test/Sentry.Unity.Editor.iOS.Tests/NativeOptionsTests.cs @@ -1,54 +1,10 @@ -using System.Diagnostics; using System.IO; using NUnit.Framework; -using UnityEngine; namespace Sentry.Unity.Editor.iOS.Tests; public class NativeOptionsTests { - [Test] - public void GenerateOptions_NewSentryOptions_Compiles() - { - if (Application.platform != RuntimePlatform.OSXEditor) - { - Assert.Inconclusive("Skipping: Not on macOS"); - } - - const string testOptionsFileName = "testOptions.m"; - var nativeOptionsString = NativeOptions.Generate(new SentryUnityOptions()); - File.WriteAllText(testOptionsFileName, nativeOptionsString); - - var process = Process.Start("clang", $"-fsyntax-only {testOptionsFileName}"); - process.WaitForExit(); - - Assert.AreEqual(0, process.ExitCode); - - File.Delete(testOptionsFileName); - } - - [Test] - public void GenerateOptions_NewSentryOptionsGarbageAppended_FailsToCompile() - { - if (Application.platform != RuntimePlatform.OSXEditor) - { - Assert.Inconclusive("Skipping: Not on macOS"); - } - - const string testOptionsFileName = "testOptions.m"; - var nativeOptionsString = NativeOptions.Generate(new SentryUnityOptions()); - nativeOptionsString += "AppendedTextToFailCompilation"; - - File.WriteAllText(testOptionsFileName, nativeOptionsString); - - var process = Process.Start("clang", $"-fsyntax-only -framework Foundation {testOptionsFileName}"); - process.WaitForExit(); - - Assert.AreEqual(1, process.ExitCode); - - File.Delete(testOptionsFileName); - } - [Test] public void CreateOptionsFile_NewSentryOptions_FileCreated() {