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()
{