diff --git a/.github/workflows/build.yml b/.github/workflows/build.yml index f54403af15..63af3b47ff 100644 --- a/.github/workflows/build.yml +++ b/.github/workflows/build.yml @@ -257,6 +257,7 @@ jobs: # integration tests, e.g. Directory.Build.props, nuget.config, ... sparse-checkout: | integration-test + scripts .github - name: Fetch NuGet Packages diff --git a/.github/workflows/device-tests-android.yml b/.github/workflows/device-tests-android.yml index b234d39df2..d64a8a6d6d 100644 --- a/.github/workflows/device-tests-android.yml +++ b/.github/workflows/device-tests-android.yml @@ -81,6 +81,8 @@ jobs: - name: Checkout uses: actions/checkout@v5 + with: + submodules: recursive - name: Download test app artifact uses: actions/download-artifact@v5 @@ -94,11 +96,11 @@ jobs: # Cached AVD setup per https://github.com/ReactiveCircus/android-emulator-runner/blob/main/README.md - name: Run Tests - id: first-run + id: first-test-run continue-on-error: true timeout-minutes: 40 uses: reactivecircus/android-emulator-runner@1dcd0090116d15e7c562f8db72807de5e036a4ed # Tag: v2.34.0 - with: + with: &android-emulator-test api-level: ${{ matrix.api-level }} target: ${{ env.ANDROID_EMULATOR_TARGET }} force-avd-creation: false @@ -110,10 +112,33 @@ jobs: script: pwsh scripts/device-test.ps1 android -Run -Tfm ${{ matrix.tfm }} - name: Retry Tests (if previous failed to run) - if: steps.first-run.outcome == 'failure' + if: steps.first-test-run.outcome == 'failure' timeout-minutes: 40 uses: reactivecircus/android-emulator-runner@1dcd0090116d15e7c562f8db72807de5e036a4ed # Tag: v2.34.0 + with: *android-emulator-test + + - name: Setup Environment + uses: ./.github/actions/environment + + - name: Select Java 17 + uses: actions/setup-java@dded0888837ed1f317902acf8a20df0ad188d165 # v5.0.0 + with: + distribution: ${{ runner.os == 'Windows' && runner.arch == 'ARM64' && 'microsoft' || 'temurin' }} + java-version: '17' + + - name: Checkout github-workflows + uses: actions/checkout@v5 with: + repository: getsentry/github-workflows + ref: a5e409bd5bad4c295201cdcfe862b17c50b29ab7 # v2.14.1 + path: modules/github-workflows + + - name: Run Integration Tests + id: first-integration-test-run + continue-on-error: true + timeout-minutes: 40 + uses: reactivecircus/android-emulator-runner@1dcd0090116d15e7c562f8db72807de5e036a4ed # v2.34.0 + with: &android-emulator-integration-test api-level: ${{ matrix.api-level }} target: ${{ env.ANDROID_EMULATOR_TARGET }} force-avd-creation: false @@ -122,11 +147,19 @@ jobs: disk-size: ${{ env.ANDROID_EMULATOR_DISK_SIZE }} emulator-options: ${{ env.ANDROID_EMULATOR_OPTIONS }} disable-animations: false - script: pwsh scripts/device-test.ps1 android -Run -Tfm ${{ matrix.tfm }} + script: pwsh integration-test/android.Tests.ps1 + + - name: Retry Integration Tests (if previous failed to run) + if: steps.first-integration-test-run.outcome == 'failure' + timeout-minutes: 40 + uses: reactivecircus/android-emulator-runner@1dcd0090116d15e7c562f8db72807de5e036a4ed # v2.34.0 + with: *android-emulator-integration-test - name: Upload results if: success() || failure() uses: actions/upload-artifact@v4 with: name: device-test-android-${{ matrix.api-level }}-${{ matrix.tfm }}-results - path: test_output + path: | + test_output + integration-test/mobile-app/test_output diff --git a/.github/workflows/device-tests-ios.yml b/.github/workflows/device-tests-ios.yml index 0e118745cc..2df78ed1d8 100644 --- a/.github/workflows/device-tests-ios.yml +++ b/.github/workflows/device-tests-ios.yml @@ -35,17 +35,32 @@ jobs: run: pwsh ./scripts/device-test.ps1 ios -Build - name: Run Tests - id: first-run + id: first-test-run continue-on-error: true run: pwsh scripts/device-test.ps1 ios -Run - name: Retry Tests (if previous failed to run) - if: steps.first-run.outcome == 'failure' + if: steps.first-test-run.outcome == 'failure' run: pwsh scripts/device-test.ps1 ios -Run + - name: Run Integration Tests + id: first-integration-test-run + continue-on-error: true + uses: getsentry/github-workflows/sentry-cli/integration-test/@a5e409bd5bad4c295201cdcfe862b17c50b29ab7 # v2.14.1 + with: + path: integration-test/ios.Tests.ps1 + + - name: Retry Integration Tests (if previous failed to run) + if: steps.first-integration-test-run.outcome == 'failure' + uses: getsentry/github-workflows/sentry-cli/integration-test/@a5e409bd5bad4c295201cdcfe862b17c50b29ab7 # v2.14.1 + with: + path: integration-test/ios.Tests.ps1 + - name: Upload results if: success() || failure() uses: actions/upload-artifact@v4 with: name: device-test-ios-results - path: test_output + path: | + test_output + integration-test/mobile-app/test_output diff --git a/integration-test/android.Tests.ps1 b/integration-test/android.Tests.ps1 new file mode 100644 index 0000000000..1758fd66f9 --- /dev/null +++ b/integration-test/android.Tests.ps1 @@ -0,0 +1,149 @@ +# This file contains test cases for https://pester.dev/ +Set-StrictMode -Version Latest +$ErrorActionPreference = 'Stop' +. $PSScriptRoot/pester.ps1 +. $PSScriptRoot/../scripts/device-test-utils.ps1 + +BeforeDiscovery { + # Skip Android integration tests unless an emulator has been already started + # by Android Device Tests, or manually when testing locally. This avoids + # slowing down non-Device Test CI builds further. + $script:emulator = Get-AndroidEmulatorId +} + +Describe 'MAUI app' -ForEach @( + @{ tfm = "net9.0-android35.0" } +) -Skip:(-not $script:emulator) { + BeforeAll { + . $PSScriptRoot/../scripts/device-test-utils.ps1 + Install-XHarness + + Remove-Item -Path "$PSScriptRoot/mobile-app" -Recurse -Force -ErrorAction SilentlyContinue + Copy-Item -Path "$PSScriptRoot/net9-maui" -Destination "$PSScriptRoot/mobile-app" -Recurse -Force + Push-Location $PSScriptRoot/mobile-app + + function InstallAndroidApp + { + param([string] $Dsn) + $dsn = $Dsn.Replace('http://', 'http://key@') + '/0' + + # replace {{SENTRY_DSN}} in MauiProgram.cs + (Get-Content MauiProgram.cs) ` + -replace '\{\{SENTRY_DSN\}\}', $dsn ` + | Set-Content MauiProgram.cs + + $arch = ($(uname -m) -eq 'arm64') ? 'arm64' : 'x64' + $rid = "android-$arch" + + Write-Host "::group::Build Sentry.Maui.Device.IntegrationTestApp.csproj" + dotnet build Sentry.Maui.Device.IntegrationTestApp.csproj ` + --configuration Release ` + --framework $tfm ` + --runtime $rid + | ForEach-Object { Write-Host $_ } + $LASTEXITCODE | Should -Be 0 + Write-Host '::endgroup::' + + Write-Host "::group::Install bin/Release/$tfm/$rid/io.sentry.dotnet.maui.device.integrationtestapp-Signed.apk" + xharness android install -v ` + --app bin/Release/$tfm/$rid/io.sentry.dotnet.maui.device.integrationtestapp-Signed.apk ` + --package-name io.sentry.dotnet.maui.device.integrationtestapp ` + --output-directory=test_output + | ForEach-Object { Write-Host $_ } + $LASTEXITCODE | Should -Be 0 + Write-Host '::endgroup::' + } + + function RunAndroidApp + { + param( + [string] $Dsn, + [string] $CrashType = 'None', + [string] $TestAction = 'None' + ) + + # Setup port forwarding for accessing sentry-server at 127.0.0.1:8000 from the emulator + $port = $Dsn.Split(':')[2].Split('/')[0] + xharness android adb -v -- reverse tcp:$port tcp:$port + + Write-Host "::group::Run Android app" + xharness android adb -v ` + -- shell am start -S -n io.sentry.dotnet.maui.device.integrationtestapp/.MainActivity ` + -e SENTRY_CRASH_TYPE $CrashType ` + -e SENTRY_TEST_ACTION $TestAction + | ForEach-Object { Write-Host $_ } + $LASTEXITCODE | Should -Be 0 + + do + { + Write-Host "Waiting for app..." + Start-Sleep -Seconds 1 + + $procid = (& adb shell pidof "io.sentry.dotnet.maui.device.integrationtestapp") -replace '\s', '' + $activity = (& adb shell dumpsys activity activities) -match "io\.sentry\.dotnet\.maui\.device\.integrationtestapp" + + } while ($procid -and $activity) + + xharness android adb -v -- reverse --remove tcp:$port + Write-Host '::endgroup::' + } + + function UninstallAndroidApp + { + Write-Host "::group::Uninstall io.sentry.dotnet.maui.device.integrationtestapp" + xharness android uninstall -v ` + --package-name io.sentry.dotnet.maui.device.integrationtestapp + | ForEach-Object { Write-Host $_ } + $LASTEXITCODE | Should -Be 0 + Write-Host '::endgroup::' + } + } + + AfterAll { + Pop-Location + } + + AfterEach { + UninstallAndroidApp + } + + It 'Managed crash' { + $result = Invoke-SentryServer { + param([string]$url) + InstallAndroidApp -Dsn $url + RunAndroidApp -Dsn $url -CrashType "Managed" + RunAndroidApp -Dsn $url -TestAction "Exit" + } + + $result.HasErrors() | Should -BeFalse + $result.Envelopes() | Should -AnyElementMatch "`"type`":`"System.ApplicationException`"" + $result.Envelopes() | Should -Not -AnyElementMatch "`"type`":`"SIGABRT`"" + } + + It 'Java crash' { + $result = Invoke-SentryServer { + param([string]$url) + InstallAndroidApp -Dsn $url + RunAndroidApp -Dsn $url -CrashType "Java" + RunAndroidApp -Dsn $url -TestAction "Exit" + } + + $result.HasErrors() | Should -BeFalse + $result.Envelopes() | Should -AnyElementMatch "`"type`":`"RuntimeException`"" + $result.Envelopes() | Should -Not -AnyElementMatch "`"type`":`"System.\w+Exception`"" + } + + It 'Null reference exception' { + $result = Invoke-SentryServer { + param([string]$url) + InstallAndroidApp -Dsn $url + RunAndroidApp -Dsn $url -TestAction "NullReferenceException" + RunAndroidApp -Dsn $url -TestAction "Exit" + } + + $result.HasErrors() | Should -BeFalse + $result.Envelopes() | Should -AnyElementMatch "`"type`":`"System.NullReferenceException`"" + # TODO: fix redundant RuntimeException (#3954) + { $result.Envelopes() | Should -Not -AnyElementMatch "`"type`":`"SIGSEGV`"" } | Should -Throw + } +} diff --git a/integration-test/common.ps1 b/integration-test/common.ps1 index b7215fc87e..878213396d 100644 --- a/integration-test/common.ps1 +++ b/integration-test/common.ps1 @@ -1,54 +1,4 @@ -# So that this works in VS Code testing integration. Otherwise the script is run within its directory. - -# In CI, the module is loaded automatically -if (!(Test-Path env:CI )) -{ - Import-Module $PSScriptRoot/../../github-workflows/sentry-cli/integration-test/action.psm1 -Force -} - -function ShouldAnyElementMatch ($ActualValue, [string]$ExpectedValue, [switch] $Negate, [string] $Because) -{ - <# - .SYNOPSIS - Asserts whether any item in the collection matches the expected value - .EXAMPLE - 'foo','bar','foobar' | Should -AnyElementMatch 'oob' - - This should pass because 'oob' is a substring of 'foobar'. - #> - - $filtered = $ActualValue | Where-Object { $_ -match $ExpectedValue } - [bool] $succeeded = @($filtered).Count -gt 0 - if ($Negate) { $succeeded = -not $succeeded } - - if (-not $succeeded) - { - if ($Negate) - { - $failureMessage = "Expected string '$ExpectedValue' to match no elements in collection @($($ActualValue -join ', '))$(if($Because) { " because $Because"})." - } - else - { - $failureMessage = "Expected string '$ExpectedValue' to match any element in collection @($($ActualValue -join ', '))$(if($Because) { " because $Because"})." - } - } - else - { - $failureMessage = $null - } - - return [pscustomobject]@{ - Succeeded = $succeeded - FailureMessage = $failureMessage - } -} - -BeforeDiscovery { - Add-ShouldOperator -Name AnyElementMatch ` - -InternalName 'ShouldAnyElementMatch' ` - -Test ${function:ShouldAnyElementMatch} ` - -SupportsArrayInput -} +. $PSScriptRoot/pester.ps1 AfterAll { Pop-Location diff --git a/integration-test/ios.Tests.ps1 b/integration-test/ios.Tests.ps1 new file mode 100644 index 0000000000..29673b97f1 --- /dev/null +++ b/integration-test/ios.Tests.ps1 @@ -0,0 +1,123 @@ +# This file contains test cases for https://pester.dev/ +Set-StrictMode -Version Latest +$ErrorActionPreference = 'Stop' +. $PSScriptRoot/pester.ps1 +. $PSScriptRoot/../scripts/device-test-utils.ps1 + +BeforeDiscovery { + # Skip iOS integration tests unless a simulator has already been booted by + # iOS Device Tests, or manually when testing locally. This avoids slowing + # down the macOS build job further. + $script:simulator = Get-IosSimulatorUdid -PreferredStates @('Booted') +} + +Describe 'iOS app ()' -ForEach @( + @{ tfm = "net9.0-ios18.0" } +) -Skip:(-not $IsMacOS -or -not $script:simulator) { + BeforeAll { + . $PSScriptRoot/../scripts/device-test-utils.ps1 + Install-XHarness + + Remove-Item -Path "$PSScriptRoot/mobile-app" -Recurse -Force -ErrorAction SilentlyContinue + Copy-Item -Path "$PSScriptRoot/net9-maui" -Destination "$PSScriptRoot/mobile-app" -Recurse -Force + Push-Location $PSScriptRoot/mobile-app + + $arch = ($(uname -m) -eq 'arm64') ? 'arm64' : 'x64' + $rid = "iossimulator-$arch" + $arguments = @( + "-v", + "--target=ios-simulator-64", + "--device=$simulator", + "--output-directory=test_output", + "--timeout=00:10:00" + ) + + Write-Host "::group::Build Sentry.Maui.Device.IntegrationTestApp.csproj" + dotnet build Sentry.Maui.Device.IntegrationTestApp.csproj ` + --configuration Release ` + --framework $tfm ` + --runtime $rid + | ForEach-Object { Write-Host $_ } + $LASTEXITCODE | Should -Be 0 + Write-Host '::endgroup::' + + function RunIosApp + { + param( + [string] $Dsn, + [string] $CrashType = 'None', + [string] $TestAction = 'None' + ) + $Dsn = $Dsn.Replace('http://', 'http://key@') + '/0' + Write-Host "::group::Run app (Crash=$CrashType, Action=$TestAction)" + xharness apple just-run $arguments ` + --app io.sentry.dotnet.maui.device.integrationtestapp ` + --set-env SENTRY_DSN=$Dsn ` + --set-env SENTRY_CRASH_TYPE=$CrashType ` + --set-env SENTRY_TEST_ACTION=$TestAction + | ForEach-Object { Write-Host $_ } + $LASTEXITCODE | Should -Be 0 + Write-Host '::endgroup::' + } + } + + AfterAll { + Pop-Location + } + + BeforeEach { + Write-Host "::group::Install bin/Release/$tfm/$rid/Sentry.Maui.Device.IntegrationTestApp.app" + xharness apple install $arguments ` + --app bin/Release/$tfm/$rid/Sentry.Maui.Device.IntegrationTestApp.app + | ForEach-Object { Write-Host $_ } + $LASTEXITCODE | Should -Be 0 + Write-Host '::endgroup::' + } + + AfterEach { + Write-Host "::group::Uninstall io.sentry.dotnet.maui.device.integrationtestapp" + xharness apple uninstall $arguments ` + --app io.sentry.dotnet.maui.device.integrationtestapp + | ForEach-Object { Write-Host $_ } + $LASTEXITCODE | Should -Be 0 + Write-Host '::endgroup::' + } + + It 'captures managed crash' { + $result = Invoke-SentryServer { + param([string]$url) + RunIosApp -Dsn $url -CrashType "Managed" + RunIosApp -Dsn $url -TestAction "Exit" + } + + $result.HasErrors() | Should -BeFalse + $result.Envelopes() | Should -AnyElementMatch "`"type`":`"System.ApplicationException`"" + # TODO: fix redundant SIGABRT (#3954) + { $result.Envelopes() | Should -Not -AnyElementMatch "`"type`":`"SIGABRT`"" } | Should -Throw + } + + It 'captures native crash' { + $result = Invoke-SentryServer { + param([string]$url) + RunIosApp -Dsn $url -CrashType "Native" + RunIosApp -Dsn $url -TestAction "Exit" + } + + $result.HasErrors() | Should -BeFalse + $result.Envelopes() | Should -AnyElementMatch "`"type`":`"EXC_[A-Z_]+`"" + $result.Envelopes() | Should -Not -AnyElementMatch "`"type`":`"System.\w+Exception`"" + } + + It 'captures null reference exception' { + $result = Invoke-SentryServer { + param([string]$url) + RunIosApp -Dsn $url -TestAction "NullReferenceException" + RunIosApp -Dsn $url -TestAction "Exit" + } + + $result.HasErrors() | Should -BeFalse + $result.Envelopes() | Should -AnyElementMatch "`"type`":`"System.NullReferenceException`"" + # TODO: fix redundant EXC_BAD_ACCESS (#3954) + { $result.Envelopes() | Should -Not -AnyElementMatch "`"type`":`"EXC_BAD_ACCESS`"" } | Should -Throw + } +} diff --git a/integration-test/net9-maui/App.xaml b/integration-test/net9-maui/App.xaml new file mode 100644 index 0000000000..183709a1c8 --- /dev/null +++ b/integration-test/net9-maui/App.xaml @@ -0,0 +1,14 @@ + + + + + + + + + + + diff --git a/integration-test/net9-maui/App.xaml.cs b/integration-test/net9-maui/App.xaml.cs new file mode 100644 index 0000000000..cd3a1668c9 --- /dev/null +++ b/integration-test/net9-maui/App.xaml.cs @@ -0,0 +1,14 @@ +namespace Sentry.Maui.Device.IntegrationTestApp; + +public partial class App : Application +{ + public App() + { + InitializeComponent(); + } + + protected override Window CreateWindow(IActivationState? activationState) + { + return new Window(new AppShell()); + } +} diff --git a/integration-test/net9-maui/AppShell.xaml b/integration-test/net9-maui/AppShell.xaml new file mode 100644 index 0000000000..9fc01ffd1c --- /dev/null +++ b/integration-test/net9-maui/AppShell.xaml @@ -0,0 +1,14 @@ + + + + + + diff --git a/integration-test/net9-maui/AppShell.xaml.cs b/integration-test/net9-maui/AppShell.xaml.cs new file mode 100644 index 0000000000..285aaf2ce9 --- /dev/null +++ b/integration-test/net9-maui/AppShell.xaml.cs @@ -0,0 +1,9 @@ +namespace Sentry.Maui.Device.IntegrationTestApp; + +public partial class AppShell : Shell +{ + public AppShell() + { + InitializeComponent(); + } +} diff --git a/integration-test/net9-maui/GlobalXmlns.cs b/integration-test/net9-maui/GlobalXmlns.cs new file mode 100644 index 0000000000..19ce62e506 --- /dev/null +++ b/integration-test/net9-maui/GlobalXmlns.cs @@ -0,0 +1,2 @@ +[assembly: XmlnsDefinition("http://schemas.microsoft.com/dotnet/maui/global", "Sentry.Maui.Device.IntegrationTestApp")] +[assembly: XmlnsDefinition("http://schemas.microsoft.com/dotnet/maui/global", "Sentry.Maui.Device.IntegrationTestApp.Pages")] diff --git a/integration-test/net9-maui/MainPage.xaml b/integration-test/net9-maui/MainPage.xaml new file mode 100644 index 0000000000..c539acffa3 --- /dev/null +++ b/integration-test/net9-maui/MainPage.xaml @@ -0,0 +1,29 @@ + + + + + + + + + + + diff --git a/integration-test/net9-maui/MainPage.xaml.cs b/integration-test/net9-maui/MainPage.xaml.cs new file mode 100644 index 0000000000..efa6c55e95 --- /dev/null +++ b/integration-test/net9-maui/MainPage.xaml.cs @@ -0,0 +1,47 @@ +#if ANDROID +using Android.OS; +#endif + +namespace Sentry.Maui.Device.IntegrationTestApp; + +public partial class MainPage : ContentPage +{ + public MainPage() + { + InitializeComponent(); + } + + protected override void OnAppearing() + { + base.OnAppearing(); + +#pragma warning disable CS0618 + var crashTypeEnv = System.Environment.GetEnvironmentVariable("SENTRY_CRASH_TYPE"); + if (Enum.TryParse(crashTypeEnv, ignoreCase: true, out var crashType)) + { + SentrySdk.CauseCrash(crashType); + } +#pragma warning restore CS0618 + + var testActionEnv = System.Environment.GetEnvironmentVariable("SENTRY_TEST_ACTION"); + if (testActionEnv?.Equals("NullReferenceException", StringComparison.OrdinalIgnoreCase) == true) + { + try + { + object? obj = null; + _ = obj!.ToString(); + } + catch (NullReferenceException ex) + { + SentrySdk.CaptureException(ex); + } + } + + SentrySdk.Flush(); +#if ANDROID + Process.KillProcess(Process.MyPid()); +#elif IOS + System.Environment.Exit(0); +#endif + } +} diff --git a/integration-test/net9-maui/MauiProgram.cs b/integration-test/net9-maui/MauiProgram.cs new file mode 100644 index 0000000000..826ea33dac --- /dev/null +++ b/integration-test/net9-maui/MauiProgram.cs @@ -0,0 +1,33 @@ +using Microsoft.Extensions.Logging; +using Sentry.Maui; + +namespace Sentry.Maui.Device.IntegrationTestApp; + +public static class MauiProgram +{ + public static MauiApp CreateMauiApp() + { + var builder = MauiApp.CreateBuilder(); + builder + .UseMauiApp() + .UseSentry(options => + { +#if ANDROID + options.Dsn = "{{SENTRY_DSN}}"; +#endif + options.Debug = true; + options.DiagnosticLevel = SentryLevel.Info; + }) + .ConfigureFonts(fonts => + { + fonts.AddFont("OpenSans-Regular.ttf", "OpenSansRegular"); + fonts.AddFont("OpenSans-Semibold.ttf", "OpenSansSemibold"); + }); + +#if DEBUG + builder.Logging.AddDebug(); +#endif + + return builder.Build(); + } +} diff --git a/integration-test/net9-maui/Platforms/Android/AndroidManifest.xml b/integration-test/net9-maui/Platforms/Android/AndroidManifest.xml new file mode 100644 index 0000000000..e5af392e6a --- /dev/null +++ b/integration-test/net9-maui/Platforms/Android/AndroidManifest.xml @@ -0,0 +1,6 @@ + + + + + + diff --git a/integration-test/net9-maui/Platforms/Android/MainActivity.cs b/integration-test/net9-maui/Platforms/Android/MainActivity.cs new file mode 100644 index 0000000000..fd38defeaf --- /dev/null +++ b/integration-test/net9-maui/Platforms/Android/MainActivity.cs @@ -0,0 +1,24 @@ +using Android.App; +using Android.Content; +using Android.Content.PM; +using Android.OS; + +namespace Sentry.Maui.Device.IntegrationTestApp; + +[Activity( + Name = "io.sentry.dotnet.maui.device.integrationtestapp.MainActivity", + Theme = "@style/Maui.SplashTheme", + MainLauncher = true, + LaunchMode = LaunchMode.SingleTop, + ConfigurationChanges = ConfigChanges.ScreenSize | ConfigChanges.Orientation | ConfigChanges.UiMode | ConfigChanges.ScreenLayout | ConfigChanges.SmallestScreenSize | ConfigChanges.Density +)] +public class MainActivity : MauiAppCompatActivity +{ + protected override void OnCreate(Bundle? savedInstanceState) + { + base.OnCreate(savedInstanceState); + + System.Environment.SetEnvironmentVariable("SENTRY_CRASH_TYPE", Intent?.GetStringExtra("SENTRY_CRASH_TYPE")); + System.Environment.SetEnvironmentVariable("SENTRY_TEST_ACTION", Intent?.GetStringExtra("SENTRY_TEST_ACTION")); + } +} diff --git a/integration-test/net9-maui/Platforms/Android/MainApplication.cs b/integration-test/net9-maui/Platforms/Android/MainApplication.cs new file mode 100644 index 0000000000..3361e98c0f --- /dev/null +++ b/integration-test/net9-maui/Platforms/Android/MainApplication.cs @@ -0,0 +1,15 @@ +using Android.App; +using Android.Runtime; + +namespace Sentry.Maui.Device.IntegrationTestApp; + +[Application] +public class MainApplication : MauiApplication +{ + public MainApplication(IntPtr handle, JniHandleOwnership ownership) + : base(handle, ownership) + { + } + + protected override MauiApp CreateMauiApp() => MauiProgram.CreateMauiApp(); +} diff --git a/integration-test/net9-maui/Platforms/Android/Resources/values/colors.xml b/integration-test/net9-maui/Platforms/Android/Resources/values/colors.xml new file mode 100644 index 0000000000..c04d7492ab --- /dev/null +++ b/integration-test/net9-maui/Platforms/Android/Resources/values/colors.xml @@ -0,0 +1,6 @@ + + + #512BD4 + #2B0B98 + #2B0B98 + \ No newline at end of file diff --git a/integration-test/net9-maui/Platforms/Android/Resources/xml/network_security_config.xml b/integration-test/net9-maui/Platforms/Android/Resources/xml/network_security_config.xml new file mode 100644 index 0000000000..2439f15c2c --- /dev/null +++ b/integration-test/net9-maui/Platforms/Android/Resources/xml/network_security_config.xml @@ -0,0 +1,4 @@ + + + + diff --git a/integration-test/net9-maui/Platforms/iOS/AppDelegate.cs b/integration-test/net9-maui/Platforms/iOS/AppDelegate.cs new file mode 100644 index 0000000000..24f2626f1c --- /dev/null +++ b/integration-test/net9-maui/Platforms/iOS/AppDelegate.cs @@ -0,0 +1,9 @@ +using Foundation; + +namespace Sentry.Maui.Device.IntegrationTestApp; + +[Register("AppDelegate")] +public class AppDelegate : MauiUIApplicationDelegate +{ + protected override MauiApp CreateMauiApp() => MauiProgram.CreateMauiApp(); +} diff --git a/integration-test/net9-maui/Platforms/iOS/Info.plist b/integration-test/net9-maui/Platforms/iOS/Info.plist new file mode 100644 index 0000000000..f2e00c68de --- /dev/null +++ b/integration-test/net9-maui/Platforms/iOS/Info.plist @@ -0,0 +1,32 @@ + + + + + LSRequiresIPhoneOS + + UIDeviceFamily + + 1 + 2 + + UIRequiredDeviceCapabilities + + arm64 + + UISupportedInterfaceOrientations + + UIInterfaceOrientationPortrait + UIInterfaceOrientationLandscapeLeft + UIInterfaceOrientationLandscapeRight + + UISupportedInterfaceOrientations~ipad + + UIInterfaceOrientationPortrait + UIInterfaceOrientationPortraitUpsideDown + UIInterfaceOrientationLandscapeLeft + UIInterfaceOrientationLandscapeRight + + XSAppIconAssets + Assets.xcassets/appicon.appiconset + + diff --git a/integration-test/net9-maui/Platforms/iOS/Program.cs b/integration-test/net9-maui/Platforms/iOS/Program.cs new file mode 100644 index 0000000000..72371dbe8f --- /dev/null +++ b/integration-test/net9-maui/Platforms/iOS/Program.cs @@ -0,0 +1,15 @@ +using ObjCRuntime; +using UIKit; + +namespace Sentry.Maui.Device.IntegrationTestApp; + +public class Program +{ + // This is the main entry point of the application. + static void Main(string[] args) + { + // if you want to use a different Application Delegate class from "AppDelegate" + // you can specify it here. + UIApplication.Main(args, null, typeof(AppDelegate)); + } +} diff --git a/integration-test/net9-maui/Platforms/iOS/Resources/PrivacyInfo.xcprivacy b/integration-test/net9-maui/Platforms/iOS/Resources/PrivacyInfo.xcprivacy new file mode 100644 index 0000000000..24ab3b4334 --- /dev/null +++ b/integration-test/net9-maui/Platforms/iOS/Resources/PrivacyInfo.xcprivacy @@ -0,0 +1,51 @@ + + + + + + NSPrivacyAccessedAPITypes + + + NSPrivacyAccessedAPIType + NSPrivacyAccessedAPICategoryFileTimestamp + NSPrivacyAccessedAPITypeReasons + + C617.1 + + + + NSPrivacyAccessedAPIType + NSPrivacyAccessedAPICategorySystemBootTime + NSPrivacyAccessedAPITypeReasons + + 35F9.1 + + + + NSPrivacyAccessedAPIType + NSPrivacyAccessedAPICategoryDiskSpace + NSPrivacyAccessedAPITypeReasons + + E174.1 + + + + + + diff --git a/integration-test/net9-maui/Resources/AppIcon/appicon.svg b/integration-test/net9-maui/Resources/AppIcon/appicon.svg new file mode 100644 index 0000000000..9d63b6513a --- /dev/null +++ b/integration-test/net9-maui/Resources/AppIcon/appicon.svg @@ -0,0 +1,4 @@ + + + + \ No newline at end of file diff --git a/integration-test/net9-maui/Resources/AppIcon/appiconfg.svg b/integration-test/net9-maui/Resources/AppIcon/appiconfg.svg new file mode 100644 index 0000000000..21dfb25f18 --- /dev/null +++ b/integration-test/net9-maui/Resources/AppIcon/appiconfg.svg @@ -0,0 +1,8 @@ + + + + + + + + \ No newline at end of file diff --git a/integration-test/net9-maui/Resources/Fonts/OpenSans-Regular.ttf b/integration-test/net9-maui/Resources/Fonts/OpenSans-Regular.ttf new file mode 100644 index 0000000000..29bfd35a2b Binary files /dev/null and b/integration-test/net9-maui/Resources/Fonts/OpenSans-Regular.ttf differ diff --git a/integration-test/net9-maui/Resources/Fonts/OpenSans-Semibold.ttf b/integration-test/net9-maui/Resources/Fonts/OpenSans-Semibold.ttf new file mode 100644 index 0000000000..54e7059cf3 Binary files /dev/null and b/integration-test/net9-maui/Resources/Fonts/OpenSans-Semibold.ttf differ diff --git a/integration-test/net9-maui/Resources/Images/dotnet_bot.png b/integration-test/net9-maui/Resources/Images/dotnet_bot.png new file mode 100644 index 0000000000..1d1b981ee1 Binary files /dev/null and b/integration-test/net9-maui/Resources/Images/dotnet_bot.png differ diff --git a/integration-test/net9-maui/Resources/Raw/AboutAssets.txt b/integration-test/net9-maui/Resources/Raw/AboutAssets.txt new file mode 100644 index 0000000000..6de1c15270 --- /dev/null +++ b/integration-test/net9-maui/Resources/Raw/AboutAssets.txt @@ -0,0 +1,15 @@ +Any raw assets you want to be deployed with your application can be placed in +this directory (and child directories). Deployment of the asset to your application +is automatically handled by the following `MauiAsset` Build Action within your `.csproj`. + + + +These files will be deployed with your package and will be accessible using Essentials: + + async Task LoadMauiAsset() + { + using var stream = await FileSystem.OpenAppPackageFileAsync("AboutAssets.txt"); + using var reader = new StreamReader(stream); + + var contents = reader.ReadToEnd(); + } diff --git a/integration-test/net9-maui/Resources/Splash/splash.svg b/integration-test/net9-maui/Resources/Splash/splash.svg new file mode 100644 index 0000000000..21dfb25f18 --- /dev/null +++ b/integration-test/net9-maui/Resources/Splash/splash.svg @@ -0,0 +1,8 @@ + + + + + + + + \ No newline at end of file diff --git a/integration-test/net9-maui/Resources/Styles/Colors.xaml b/integration-test/net9-maui/Resources/Styles/Colors.xaml new file mode 100644 index 0000000000..30307a5ddc --- /dev/null +++ b/integration-test/net9-maui/Resources/Styles/Colors.xaml @@ -0,0 +1,45 @@ + + + + + + + #512BD4 + #ac99ea + #242424 + #DFD8F7 + #9880e5 + #2B0B98 + + White + Black + #D600AA + #190649 + #1f1f1f + + #E1E1E1 + #C8C8C8 + #ACACAC + #919191 + #6E6E6E + #404040 + #212121 + #141414 + + + + + + + + + + + + + + + + \ No newline at end of file diff --git a/integration-test/net9-maui/Resources/Styles/Styles.xaml b/integration-test/net9-maui/Resources/Styles/Styles.xaml new file mode 100644 index 0000000000..fdb0cd763c --- /dev/null +++ b/integration-test/net9-maui/Resources/Styles/Styles.xaml @@ -0,0 +1,444 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/integration-test/net9-maui/Sentry.Maui.Device.IntegrationTestApp.csproj b/integration-test/net9-maui/Sentry.Maui.Device.IntegrationTestApp.csproj new file mode 100644 index 0000000000..81bfa8eb45 --- /dev/null +++ b/integration-test/net9-maui/Sentry.Maui.Device.IntegrationTestApp.csproj @@ -0,0 +1,60 @@ + + + + $(TargetFrameworks);net9.0-android35.0 + $(TargetFrameworks);net9.0-ios18.0 + + Exe + Sentry.Maui.Device.IntegrationTestApp + true + true + enable + enable + + + false + false + + + Sentry.Maui.Device.IntegrationTestApp + + + io.sentry.dotnet.maui.device.integrationtestapp + + + 1.0 + 1 + + 15.0 + 21.0 + 18.0 + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/integration-test/pester.ps1 b/integration-test/pester.ps1 new file mode 100644 index 0000000000..2f051baaf4 --- /dev/null +++ b/integration-test/pester.ps1 @@ -0,0 +1,56 @@ +# This file contains extensions for https://pester.dev/ + +# So that this works in VS Code testing integration. Otherwise the script is run within its directory. +# In CI, the module is loaded automatically +if (Test-Path $PSScriptRoot/../modules/github-workflows) +{ + Import-Module $PSScriptRoot/../modules/github-workflows/sentry-cli/integration-test/action.psm1 -Force +} +elseif (!(Test-Path env:CI )) +{ + Import-Module $PSScriptRoot/../../github-workflows/sentry-cli/integration-test/action.psm1 -Force +} + +function ShouldAnyElementMatch ($ActualValue, [string]$ExpectedValue, [switch] $Negate, [string] $Because) +{ + <# + .SYNOPSIS + Asserts whether any item in the collection matches the expected value + .EXAMPLE + 'foo','bar','foobar' | Should -AnyElementMatch 'oob' + + This should pass because 'oob' is a substring of 'foobar'. + #> + + $filtered = $ActualValue | Where-Object { $_ -match $ExpectedValue } + [bool] $succeeded = @($filtered).Count -gt 0 + if ($Negate) { $succeeded = -not $succeeded } + + if (-not $succeeded) + { + if ($Negate) + { + $failureMessage = "Expected string '$ExpectedValue' to match no elements in collection @($($ActualValue -join ', '))$(if($Because) { " because $Because"})." + } + else + { + $failureMessage = "Expected string '$ExpectedValue' to match any element in collection @($($ActualValue -join ', '))$(if($Because) { " because $Because"})." + } + } + else + { + $failureMessage = $null + } + + return [pscustomobject]@{ + Succeeded = $succeeded + FailureMessage = $failureMessage + } +} + +BeforeDiscovery { + Add-ShouldOperator -Name AnyElementMatch ` + -InternalName 'ShouldAnyElementMatch' ` + -Test ${function:ShouldAnyElementMatch} ` + -SupportsArrayInput +} diff --git a/scripts/ios-simulator-utils.ps1 b/scripts/device-test-utils.ps1 similarity index 76% rename from scripts/ios-simulator-utils.ps1 rename to scripts/device-test-utils.ps1 index 51f1b72fbd..d60fe8ab8e 100644 --- a/scripts/ios-simulator-utils.ps1 +++ b/scripts/device-test-utils.ps1 @@ -1,12 +1,39 @@ +function Install-XHarness { + if (!(Get-Command xharness -ErrorAction SilentlyContinue)) + { + $CI = Test-Path env:CI + Push-Location ($CI ? $env:RUNNER_TEMP : $IsWindows ? $env:TMP : $IsMacos ? $env:TMPDIR : '/tmp') + dotnet tool install Microsoft.DotNet.XHarness.CLI --global --version '10.0.0-prerelease.25466.1' ` + --add-source https://pkgs.dev.azure.com/dnceng/public/_packaging/dotnet-eng/nuget/v3/index.json + Pop-Location + } +} + +function Get-AndroidEmulatorId { + if ($env:ANDROID_SERIAL) + { + return $env:ANDROID_SERIAL + } + try + { + return & adb devices | Select-String "device$" | ForEach-Object { ($_ -split "`t")[0] } | Select-Object -First 1 + } + catch + { + return $null + } +} + function Get-IosSimulatorUdid { [CmdletBinding()] param( [string]$IosVersion = '18.5', [string[]]$PreferredDeviceTypes = @( - 'com.apple.CoreSimulator.SimDeviceType.iPhone-XS', - 'com.apple.CoreSimulator.SimDeviceType.iPhone-16', - 'com.apple.CoreSimulator.SimDeviceType.iPhone-15' - ) + 'com.apple.CoreSimulator.SimDeviceType.iPhone-XS', + 'com.apple.CoreSimulator.SimDeviceType.iPhone-16', + 'com.apple.CoreSimulator.SimDeviceType.iPhone-15' + ), + [string[]]$PreferredStates = @('Shutdown','Booted') ) try { @@ -60,7 +87,7 @@ function Get-IosSimulatorUdid { return $null } - $usable = $runtimeDevices | Where-Object { $_.isAvailable -and $_.state -in @('Shutdown','Booted') } + $usable = $runtimeDevices | Where-Object { $_.isAvailable -and $_.state -in $PreferredStates } if (-not $usable) { Write-Verbose "No available devices in runtime $runtimeKey" return $null diff --git a/scripts/device-test.ps1 b/scripts/device-test.ps1 index f9d5c30868..b42a1ecc06 100644 --- a/scripts/device-test.ps1 +++ b/scripts/device-test.ps1 @@ -12,7 +12,7 @@ param( Set-StrictMode -Version latest $ErrorActionPreference = 'Stop' -. $PSScriptRoot/ios-simulator-utils.ps1 +. $PSScriptRoot/device-test-utils.ps1 if (!$Build -and !$Run) { @@ -64,7 +64,7 @@ try '--set-env', "CI=$envValue" ) - $udid = Get-IosSimulatorUdid -IosVersion '18.5' -Verbose + $udid = Get-IosSimulatorUdid -Verbose if ($udid) { $arguments += @('--device', $udid) } else { @@ -84,14 +84,7 @@ try if ($Run) { - if (!(Get-Command xharness -ErrorAction SilentlyContinue)) - { - Push-Location ($CI ? $env:RUNNER_TEMP : $IsWindows ? $env:TMP : $IsMacos ? $env:TMPDIR : '/tmp') - dotnet tool install Microsoft.DotNet.XHarness.CLI --global --version '10.0.0-prerelease.25466.1' ` - --add-source https://pkgs.dev.azure.com/dnceng/public/_packaging/dotnet-eng/nuget/v3/index.json - Pop-Location - } - + Install-XHarness Remove-Item -Recurse -Force test_output -ErrorAction SilentlyContinue try {