diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml index e6213e347..fc1e988a1 100644 --- a/.github/workflows/ci.yml +++ b/.github/workflows/ci.yml @@ -278,20 +278,23 @@ jobs: - name: Make symbolic link for Unity (Windows) run: New-Item -ItemType SymbolicLink -Path "C:\${{ steps.env.outputs.unityVersion }}" -Target "C:\Program Files\Unity\Hub\Editor\${{ steps.env.outputs.unityVersion }}" - - name: Integration Test - Create new Project + - name: Create new Project run: ./test/Scripts.Integration.Test/integration-create-project.ps1 "${{ env.UNITY_PATH }}" - - name: Integration Test - Build Standalone Player without Sentry SDK + - name: Build Standalone Player without Sentry SDK run: ./test/Scripts.Integration.Test/integration-build-project.ps1 "${{ env.UNITY_PATH }}" - - name: Integration Test - Add Sentry to test project + - name: Add Sentry to test project run: ./test/Scripts.Integration.Test/integration-update-sentry.ps1 "${{ env.UNITY_PATH }}" - - name: Integration Test - Build Standalone Player Sentry SDK + - name: Build Standalone Player Sentry SDK run: ./test/Scripts.Integration.Test/integration-build-project.ps1 "${{ env.UNITY_PATH }}" - - name: Integration Test - Run Player - Smoke Test - run: ./test/Scripts.Integration.Test/integration-run-smoke-test.ps1 + - name: Run Player - Smoke Test + run: ./test/Scripts.Integration.Test/integration-run-smoke-test.ps1 -Smoke + + - name: Run Player - Crash Test + run: ./test/Scripts.Integration.Test/integration-run-smoke-test.ps1 -Crash android-smoke-test: needs: [build] diff --git a/Directory.Build.targets b/Directory.Build.targets index dc433e6bf..a52838994 100644 --- a/Directory.Build.targets +++ b/Directory.Build.targets @@ -47,7 +47,7 @@ Builder.BuildWindowsIl2CPPPlayer $(PlayerBuildPath)Windows/IL2CPP_Player.exe $(StandaloneBuildPath) - %USERPROFILE%\AppData\LocalLow\DefaultCompany\unity-of-bugs\ + $(USERPROFILE)/AppData/LocalLow/DefaultCompany/unity-of-bugs/ https://94677106febe46b88b9b9ae5efd18a00@o447951.ingest.sentry.io/5439417 @@ -244,17 +244,7 @@ Related: https://forum.unity.com/threads/6572-debugger-agent-unable-to-listen-on - - - - - - - - - - - + diff --git a/samples/unity-of-bugs/Assets/Scripts/SmokeTester.cs b/samples/unity-of-bugs/Assets/Scripts/SmokeTester.cs index 0535c0504..74a98dbea 100644 --- a/samples/unity-of-bugs/Assets/Scripts/SmokeTester.cs +++ b/samples/unity-of-bugs/Assets/Scripts/SmokeTester.cs @@ -3,6 +3,8 @@ #define SENTRY_NATIVE_IOS #elif UNITY_ANDROID #define SENTRY_NATIVE_ANDROID +#elif UNITY_STANDALONE_WIN && ENABLE_IL2CPP +#define SENTRY_NATIVE_WINDOWS #endif #endif @@ -10,6 +12,8 @@ using Sentry.Unity.iOS; #elif UNITY_ANDROID using Sentry.Unity.Android; +#elif SENTRY_NATIVE_WINDOWS +using Sentry.Unity.Native; #endif #if UNITY_IOS @@ -23,6 +27,7 @@ using System.Linq; using System.Net; using System.Net.Http; +using System.Runtime.InteropServices; using System.Threading; using System.Threading.Tasks; using Sentry; @@ -34,16 +39,13 @@ public class SmokeTester : MonoBehaviour { public void Start() { + string arg = null; #if SENTRY_NATIVE_ANDROID using (var unityPlayer = new AndroidJavaClass("com.unity3d.player.UnityPlayer")) using (var currentActivity = unityPlayer.GetStatic("currentActivity")) using (var intent = currentActivity.Call("getIntent")) { - var text = intent.Call ("getStringExtra", "test"); - if (text == "smoke") - { - SmokeTest(); - } + arg = intent.Call ("getStringExtra", "test"); } #elif UNITY_IOS string pListPath = Path.Combine(Application.dataPath.Substring(0, Application.dataPath.LastIndexOf('/')), "Info.plist"); @@ -53,8 +55,8 @@ public void Start() var key = "RunSentrySmokeTest"; if (rawPlist.Contains(key)) { - Debug.Log("Key " + key + " found on Info.plistm starting Smoke test."); - SmokeTest(); + arg = "smoke"; + Debug.Log("Key " + key + " found on Info.plist starting Smoke test."); } else { @@ -65,61 +67,87 @@ public void Start() var args = Environment.GetCommandLineArgs(); if (args.Length > 2 && args[1] == "--test") { - if (args[2] == "smoke") - { - SmokeTest(); - } + arg = args[2]; } #endif + if (arg == null) + { + Debug.Log($"SmokeTest not executed - no argument given"); + } + else if (arg == "smoke") + { + SmokeTest(); + } + else if (arg == "smoke-crash") + { + SmokeTestCrash(); + } + else + { + Debug.Log($"Unknown command line argument: {arg}"); + Application.Quit(-1); + } } - public static void SmokeTest() + public static void InitSentry(SentryUnityOptions options) { - var t = new TestHandler(); - try - { - Debug.Log("SMOKE TEST: Start"); - - var options = new SentryUnityOptions(); - options.Dsn = "https://key@sentry/project"; - options.Debug = true; - // TODO: Must be set explicitly for the time being. - options.RequestBodyCompressionLevel = CompressionLevelWithAuto.Auto; - options.DiagnosticLogger = new ConsoleDiagnosticLogger(SentryLevel.Debug); - options.CreateHttpClientHandler = () => t; - - var sentryUnityInfo = new SentryUnityInfo(); + options.Dsn = "http://publickey@localhost:8000/12345"; + options.Debug = true; + options.DebugOnlyInEditor = false; + // Need to set the logger explicitly so it's available already for the native .Configure() methods. + // SentryUnity.Init() would set it to the same value, but too late for us. + options.DiagnosticLogger = new UnityLogger(options); #if SENTRY_NATIVE_IOS - Debug.Log("SMOKE TEST: Configure Native iOS."); - SentryNativeIos.Configure(options); + Debug.Log("SMOKE TEST: Configure Native iOS."); + options.IosNativeSupportEnabled = true; + SentryNativeIos.Configure(options); #elif SENTRY_NATIVE_ANDROID - Debug.Log("SMOKE TEST: Configure Native Android."); - SentryNativeAndroid.Configure(options, sentryUnityInfo); + Debug.Log("SMOKE TEST: Configure Native Android."); + options.AndroidNativeSupportEnabled = true; + SentryNativeAndroid.Configure(options, new SentryUnityInfo()); #elif SENTRY_NATIVE_WINDOWS - Debug.Log("SMOKE TEST: Configure Native Windows."); - SentryNative.Configure(options); + Debug.Log("SMOKE TEST: Configure Native Windows."); + options.WindowsNativeSupportEnabled = true; + SentryNative.Configure(options); +#else + Debug.LogWarning("SMOKE TEST: Given platform is not supported for native sentry configuration"); + throw new Exception("Given platform is not supported"); #endif - Debug.Log("SMOKE TEST: SentryUnity Init."); - SentryUnity.Init(options); + Debug.Log("SMOKE TEST: SentryUnity (.net) Init."); + SentryUnity.Init(options); + Debug.Log("SMOKE TEST: SentryUnity (.net) Init OK."); + } - Debug.Log("SMOKE TEST: SentryUnity Init OK."); + public static void SmokeTest() + { + var t = new TestHandler(); + try + { + Debug.Log("SMOKE TEST: Start"); + + InitSentry(new SentryUnityOptions() { CreateHttpClientHandler = () => t }); var currentMessage = 0; t.ExpectMessage(currentMessage, "'type':'session'"); - t.ExpectMessage(currentMessage, "'init':"); - // if first message was init:false, wait for another one with init:true (this happens on windows...) - if (t.GetMessage(currentMessage).Contains("\"init\":false")) + var guid = Guid.NewGuid().ToString(); + Debug.LogError(guid); + + // Skip the session init requests (there may be multiple of othem). We can't skip them by a "positive" + // because they're also repeated with standard events (in an envelope). + Debug.Log("Skipping all non-event requests"); + for (; currentMessage < 10; currentMessage++) { - t.ExpectMessage(++currentMessage, "'type':'session'"); - t.ExpectMessage(currentMessage, "'init':true"); + if (t.CheckMessage(currentMessage, "'type':'event'")) + { + break; + } } + Debug.Log($"Done skipping non-event requests. Last one was: #{currentMessage}"); - var guid = Guid.NewGuid().ToString(); - Debug.LogError(guid); - t.ExpectMessage(++currentMessage, "'type':'event'"); + t.ExpectMessage(currentMessage, "'type':'event'"); t.ExpectMessage(currentMessage, guid); SentrySdk.CaptureMessage(guid); @@ -127,21 +155,7 @@ public static void SmokeTest() t.ExpectMessage(currentMessage, guid); var ex = new Exception("Exception & context test"); - SentrySdk.AddBreadcrumb("crumb", "bread", "error", new Dictionary() { { "foo", "bar" } }, BreadcrumbLevel.Critical); - SentrySdk.ConfigureScope((Scope scope) => - { - scope.SetExtra("extra-key", 42); - scope.AddBreadcrumb("scope-crumb"); - scope.SetTag("tag-key", "tag-value"); - scope.User = new User() - { - Username = "username", - Email = "email@example.com", - IpAddress = "::1", - Id = "user-id", - Other = new Dictionary() { { "role", "admin" } } - }; - }); + AddContext(); SentrySdk.CaptureException(ex); t.ExpectMessage(++currentMessage, "'type':'event'"); t.ExpectMessage(currentMessage, "'message':'crumb','type':'error','data':{'foo':'bar'},'category':'bread','level':'critical'}"); @@ -166,6 +180,45 @@ public static void SmokeTest() } } + public static void SmokeTestCrash() + { + Debug.Log("SMOKE TEST: Start"); + + InitSentry(new SentryUnityOptions()); + + AddContext(); + + Debug.Log("SMOKE TEST: Issuing a native crash (c++ unhandled exception)"); + throw_cpp(); + + // shouldn't execute because the previous call should have failed + Debug.Log("SMOKE TEST: FAIL - unexpected code executed..."); + Application.Quit(-1); + } + + private static void AddContext() + { + SentrySdk.AddBreadcrumb("crumb", "bread", "error", new Dictionary() { { "foo", "bar" } }, BreadcrumbLevel.Critical); + SentrySdk.ConfigureScope((Scope scope) => + { + scope.SetExtra("extra-key", 42); + scope.AddBreadcrumb("scope-crumb"); + scope.SetTag("tag-key", "tag-value"); + scope.User = new User() + { + Username = "username", + Email = "email@example.com", + IpAddress = "::1", + Id = "user-id", + Other = new Dictionary() { { "role", "admin" } } + }; + }); + } + + // CppPlugin.cpp + [DllImport("__Internal")] + private static extern void throw_cpp(); + private class TestHandler : HttpClientHandler { private List _requests = new List(); @@ -255,11 +308,13 @@ public string GetMessage(int index) } } - public void ExpectMessage(int index, String substring) + public bool CheckMessage(int index, String substring) { var message = GetMessage(index); - Expect($"HTTP Request #{index} contains \"{substring}\".", - message.Contains(substring) || message.Contains(substring.Replace("'", "\""))); + return message.Contains(substring) || message.Contains(substring.Replace("'", "\"")); } + + public void ExpectMessage(int index, String substring) => + Expect($"HTTP Request #{index} contains \"{substring}\".", CheckMessage(index, substring)); } } diff --git a/src/Sentry.Unity.Editor/ConfigurationWindow/SentryEditorWindowInstrumentation.cs b/src/Sentry.Unity.Editor/ConfigurationWindow/SentryEditorWindowInstrumentation.cs index 5266f5235..2ce8f8e43 100644 --- a/src/Sentry.Unity.Editor/ConfigurationWindow/SentryEditorWindowInstrumentation.cs +++ b/src/Sentry.Unity.Editor/ConfigurationWindow/SentryEditorWindowInstrumentation.cs @@ -19,7 +19,7 @@ private static void ConfigureOptions(Dictionary args, [CallerMem if (!EditorApplication.ExecuteMenuItem("Tools/Sentry")) { - throw new Exception("Menu item 'Tools -> Sentry' not found. Was the Sentry UPM package installed?"); + throw new Exception($"{functionName} failed: Menu item 'Tools -> Sentry' not found. Was the Sentry UPM package installed?"); } var optionsWindow = EditorWindow.GetWindow(); @@ -27,7 +27,7 @@ private static void ConfigureOptions(Dictionary args, [CallerMem if (options is null) { - throw new InvalidOperationException("SentryOptions not found"); + throw new InvalidOperationException($"{functionName} failed: SentryOptions not found"); } Debug.LogFormat("{0}: Found SentryOptions", functionName); diff --git a/src/Sentry.Unity.Native/SentryNativeBridge.cs b/src/Sentry.Unity.Native/SentryNativeBridge.cs index 47ba6502c..1942fb1fd 100644 --- a/src/Sentry.Unity.Native/SentryNativeBridge.cs +++ b/src/Sentry.Unity.Native/SentryNativeBridge.cs @@ -1,6 +1,10 @@ using System; +using System.IO; using System.Runtime.InteropServices; using Sentry.Extensibility; +using AOT; +using System.Threading; +using System.Text; namespace Sentry.Unity { @@ -29,6 +33,52 @@ public static void Init(SentryUnityOptions options) sentry_options_set_release(cOptions, options.Release); } + if (options.Environment is not null) + { + options.DiagnosticLogger?.LogDebug("Setting Environment: {0}", options.Environment); + sentry_options_set_environment(cOptions, options.Environment); + } + + options.DiagnosticLogger?.LogDebug("Setting Debug: {0}", options.Debug); + sentry_options_set_debug(cOptions, options.Debug ? 1 : 0); + + if (options.SampleRate.HasValue) + { + options.DiagnosticLogger?.LogDebug("Setting Sample Rate: {0}", options.SampleRate.Value); + sentry_options_set_sample_rate(cOptions, options.SampleRate.Value); + } + + // Disabling the native in favor of the C# layer for now + options.DiagnosticLogger?.LogDebug("Disabling native auto session tracking"); + sentry_options_set_auto_session_tracking(cOptions, 0); + + if (options.CacheDirectoryPath is not null) + { + var dir = Path.Combine(options.CacheDirectoryPath, "SentryNative"); + if (RuntimeInformation.IsOSPlatform(OSPlatform.Windows)) + { + options.DiagnosticLogger?.LogDebug("Setting CacheDirectoryPath on Windows: {0}", dir); + sentry_options_set_database_pathw(cOptions, dir); + } + else + { + options.DiagnosticLogger?.LogDebug("Setting CacheDirectoryPath: {0}", dir); + sentry_options_set_database_path(cOptions, dir); + } + } + + if (options.DiagnosticLogger is null) + { + _logger?.LogDebug("Unsetting the current native logger"); + _logger = null; + } + else + { + options.DiagnosticLogger.LogDebug($"{(_logger is null ? "Setting a" : "Replacing the")} native logger"); + _logger = options.DiagnosticLogger; + sentry_options_set_logger(cOptions, new sentry_logger_function_t(nativeLog), IntPtr.Zero); + } + sentry_init(cOptions); } @@ -42,9 +92,77 @@ public static void Init(SentryUnityOptions options) [DllImport("sentry")] private static extern void sentry_options_set_release(IntPtr options, string release); - // TODO we could set a logger for sentry-native, forwarding the logs to `options.DiagnosticLogger?` - // [DllImport("sentry")] - // private static extern void sentry_options_set_logger(IntPtr options, IntPtr logger, IntPtr userData); + [DllImport("sentry")] + private static extern void sentry_options_set_debug(IntPtr options, int debug); + + [DllImport("sentry")] + private static extern void sentry_options_set_environment(IntPtr options, string environment); + + [DllImport("sentry")] + private static extern void sentry_options_set_sample_rate(IntPtr options, double rate); + + [DllImport("sentry")] + private static extern void sentry_options_set_database_path(IntPtr options, string path); + + [DllImport("sentry")] + private static extern void sentry_options_set_database_pathw(IntPtr options, [MarshalAs(UnmanagedType.LPWStr)] string path); + + [DllImport("sentry")] + private static extern void sentry_options_set_auto_session_tracking(IntPtr options, int debug); + + [UnmanagedFunctionPointer(CallingConvention.Cdecl, SetLastError = true)] + private delegate void sentry_logger_function_t(int level, string message, IntPtr argsAddress, IntPtr userData); + + [DllImport("sentry")] + 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. + private static IDiagnosticLogger? _logger; + + // This method is called from the C library and forwards incoming messages to the currently set _logger. + [MonoPInvokeCallback(typeof(sentry_logger_function_t))] + private static void nativeLog(int cLevel, string message, IntPtr args, IntPtr userData) + { + var logger = _logger; + if (logger is null) + { + return; + } + + // see sentry.h: sentry_level_e + var level = cLevel switch + { + -1 => SentryLevel.Debug, + 0 => SentryLevel.Info, + 1 => SentryLevel.Warning, + 2 => SentryLevel.Error, + 3 => SentryLevel.Fatal, + _ => SentryLevel.Info, + }; + + if (!logger.IsEnabled(level)) + { + return; + } + + // If the message contains any "formatting" modifiers (that should be substituted by `args`), we need + // to apply the formatting. However, we cannot access C var-arg (va_list) in c# thus we pass it back to + // vsnprintf (to find out the length of the resulting buffer) & vsprintf (to actually format the message). + if (message.Contains("%")) + { + var formattedLength = vsnprintf(null, UIntPtr.Zero, message, args); + var buffer = new StringBuilder(formattedLength + 1); + vsprintf(buffer, message, args); + message = buffer.ToString(); + } + logger.Log(level, $"Native: {message}"); + } + + [DllImport("msvcrt.dll", CharSet = CharSet.Ansi, CallingConvention = CallingConvention.Cdecl)] + private static extern int vsprintf(StringBuilder buffer, string format, IntPtr args); + + [DllImport("msvcrt.dll", CharSet = CharSet.Ansi, CallingConvention = CallingConvention.Cdecl)] + private static extern int vsnprintf(string? buffer, UIntPtr bufferSize, string format, IntPtr args); [DllImport("sentry")] private static extern void sentry_init(IntPtr options); diff --git a/test/Scripts.Integration.Test/crash-test-server.py b/test/Scripts.Integration.Test/crash-test-server.py new file mode 100644 index 000000000..a65d42adf --- /dev/null +++ b/test/Scripts.Integration.Test/crash-test-server.py @@ -0,0 +1,32 @@ +#!/usr/bin/env python3 + +from http.server import BaseHTTPRequestHandler, ThreadingHTTPServer +import sys +import threading + + +class Handler(BaseHTTPRequestHandler): + def commonServe(self): + self.send_response(200, "") + self.end_headers() + sys.stdout.flush() + sys.stderr.flush() + + if (self.path == "/STOP"): + print("HTTP server stopping!") + threading.Thread(target=self.server.shutdown).start() + + def do_GET(self): + self.commonServe() + + def do_POST(self): + self.commonServe() + + +host = '127.0.0.1' +port = 8000 +uri = 'http://{}:{}'.format(host, port) +print("HTTP server listening on {}".format(uri)) +print("To stop the server, execute a GET request to {}/STOP".format(uri)) +httpd = ThreadingHTTPServer((host, port), Handler) +target = httpd.serve_forever() diff --git a/test/Scripts.Integration.Test/integration-create-project.ps1 b/test/Scripts.Integration.Test/integration-create-project.ps1 index e9136cf9e..2f4a307b9 100644 --- a/test/Scripts.Integration.Test/integration-create-project.ps1 +++ b/test/Scripts.Integration.Test/integration-create-project.ps1 @@ -76,5 +76,10 @@ Else Copy-Item -Recurse "$IntegrationScriptsPath/SentrySetup.*" -Destination "$NewProjectAssetsPath/Editor/" Write-Host " OK" + # Don't print stack traces in debug logs. See ./samples/unity-of-bugs/ProjectSettings/PresetManager.asset + $projectSettingsPath = "$NewProjectPath/ProjectSettings/ProjectSettings.asset" + (Get-Content $projectSettingsPath) -replace "m_StackTraceTypes: ?[01]+", "m_StackTraceTypes: 010000000000000000000000000000000100000001000000" | ` + Out-File $projectSettingsPath + Write-Host "`nProject created!!" } diff --git a/test/Scripts.Integration.Test/integration-run-smoke-test.ps1 b/test/Scripts.Integration.Test/integration-run-smoke-test.ps1 index e2a49024e..def8d51e9 100644 --- a/test/Scripts.Integration.Test/integration-run-smoke-test.ps1 +++ b/test/Scripts.Integration.Test/integration-run-smoke-test.ps1 @@ -1,52 +1,174 @@ -. ./test/Scripts.Integration.Test/IntegrationGlobals.ps1 +param ( + [Parameter(Position = 0)] + [string] $TestAppPath = "", + [Parameter()] + [string] $AppDataDir = "", -If ($IsMacOS) -{ - $testAppPath = "$NewProjectBuildPath/test.app/Contents/MacOS/$NewProjectName" + [Parameter()] + [switch] $Smoke, + + [Parameter()] + [switch] $Crash +) +. $PSScriptRoot/IntegrationGlobals.ps1 + +Write-Host "Given parameters:" +Write-Host " TestAppPath: $TestAppPath" +Write-Host " AppDataDir: $AppDataDir" +Write-Host " Smoke: $Smoke" +Write-Host " Crash: $Crash" + +If (!$Smoke -and !$Crash) { + Write-Error "Select one of the following tests (or both): -Smoke or -Crash" } -ElseIf ($IsWindows) -{ - $testAppPath = "$NewProjectBuildPath/test.exe" + +if ("$TestAppPath" -eq "") { + If ($IsMacOS) { + $TestAppPath = "$NewProjectBuildPath/test.app/Contents/MacOS/$NewProjectName" + } + ElseIf ($IsWindows) { + $TestAppPath = "$NewProjectBuildPath/test.exe" + if ("$AppDataDir" -eq "") { + $AppDataDir = "$env:UserProfile\AppData\LocalLow\DefaultCompany\$NewProjectName\" + } + } + ElseIf ($IsLinux) { + $TestAppPath = "$NewProjectBuildPath/test" + } + Else { + Write-Error "Unsupported build" + } } -ElseIf ($IsLinux) -{ - $testAppPath = "$NewProjectBuildPath/test" + +if ("$AppDataDir" -ne "") { + if (Test-Path $AppDataDir) { + Write-Warning "Removing AppDataDir '$AppDataDir'" + Remove-Item -Recurse $AppDataDir + } } -Else -{ - Throw "Unsupported build" +else { + Write-Warning "No AppDataDir param given - if you're running this after a previous smoke-crash test, the smoke test will fail." + Write-Warning "You can provide AppDataDir and it will be deleted before running the test." + Write-Warning 'On windows, this would normally be: -AppDataDir "$env:UserProfile\AppData\LocalLow\DefaultCompany\unity-of-bugs\"' } -$process = Start-Process -FilePath "$testAppPath" -ArgumentList "--test", "smoke" -PassThru +Set-Strictmode -Version latest -If ($null -eq $process) -{ - Throw "Process not found." -} +function RunTest([string] $type) { + Write-Host "Running $TestAppPath --test $type" + + $process = Start-Process "$TestAppPath" -ArgumentList "--test", $type -PassThru + If ($null -eq $process) { + Throw "Process not found." + } + + # Wait for the test to finish + $timedOut = $null # reset any previously set timeout + $process | Wait-Process -Timeout 60 -ErrorAction SilentlyContinue -ErrorVariable timedOut -# Wait for 1 minute (sleeps for 500ms per iteration) -$timeout = 60 * 2 -$processName = $process.Name -Write-Host -NoNewline "Waiting for $processName" + if ("$AppDataDir" -ne "") { + Write-Host "$type test: Player.log contents:" -ForegroundColor Yellow + Get-Content "$AppDataDir/Player.log" + Write-Host "================================================================================" -ForegroundColor Yellow + Write-Host "$type test: Player.log contents END" -ForegroundColor Yellow + } -While (!$process.HasExited -and $timeout -gt 0) -{ - Start-Sleep -Milliseconds 500 - Write-Host -NoNewline "." - $timeout-- + # ExitCode 200 is the status code indicating success inside SmokeTest.cs + If ($process.ExitCode -eq 200) { + Write-Host "$type test: PASSED" -ForegroundColor Green + } + ElseIf ($timedOut) { + $process | Stop-Process + Throw "Test process timed out." + } + Else { + $info = "Test process finished with status code $($process.ExitCode)." + If ($type -ne "smoke-crash") { + if ("$AppDataDir" -ne "") { + Get-Content "$AppDataDir/Player.log" + } + throw $info + } + Write-Host $info + } } -# ExitCode 200 is the status code indicating success inside SmokeTest.cs -If ($process.ExitCode -eq 200) -{ - Write-Host "`nPASSED" +function RunApiServer() { + $result = "" | Select-Object -Property process, outFile, errFile + Write-Host "Starting the HTTP server (dummy API server)" + $result.outFile = New-TemporaryFile + $result.errFile = New-TemporaryFile + + $result.process = Start-Process "python3" -ArgumentList "$PSScriptRoot/crash-test-server.py" -NoNewWindow -PassThru -RedirectStandardOutput $result.outFile -RedirectStandardError $result.errFile + + # The process shouldn't finish by itself, if it did, there was an error, so let's check that + Start-Sleep -Second 1 + if ($result.process.HasExited) { + Write-Host "Couldn't start the HTTP server" -ForegroundColor Red + Write-Host "Standard Output:" -ForegroundColor Yellow + Get-Content $result.outFile + Write-Host "Standard Error:" -ForegroundColor Yellow + Get-Content $result.errFile + Remove-Item $result.outFile + Remove-Item $result.errFile + exit 1 + } + + return $result } -ElseIf ($timeout -eq 0) -{ - Throw "Test process timed out." + +# Simple smoke test +if ($Smoke) { + RunTest "smoke" } -Else -{ - Throw "Test process failed with status code $($process.ExitCode)." + +# Native crash test +if ($Crash) { + $runs = 1 # You can increase this to run the crash test multiple times in a loop (until it fails) + for ($run = 1; $run -le $runs; $run++) { + $httpServer = RunApiServer + RunTest "smoke-crash" + + $httpServerUri = "http://localhost:8000" + $successMessage = "POST /api/12345/minidump/" + + Write-Host "Waiting for the expected message to appear in the server output logs ..." + # Wait for 1 minute (600 * 100 milliseconds) until the expected message comes in + for ($i = 0; $i -lt 600; $i++) { + $output = (Get-Content $httpServer.outFile -Raw) + (Get-Content $httpServer.errFile -Raw) + if ("$output".Contains($successMessage)) { + break + } + Start-Sleep -Milliseconds 100 + } + + # Stop the HTTP server + Write-Host "Stopping the dummy API server ... " -NoNewline + try { + (Invoke-WebRequest -URI "$httpServerUri/STOP").StatusDescription + } + catch { + Write-Host "/STOP request failed, killing the server process" + $httpServer.process | Stop-Process -Force -ErrorAction SilentlyContinue + } + $httpServer.process | Wait-Process -Timeout 10 -ErrorAction Continue + + Write-Host "Server stdout:" -ForegroundColor Yellow + Get-Content $httpServer.outFile -Raw + + Write-Host "Server stderr:" -ForegroundColor Yellow + Get-Content $httpServer.errFile -Raw + + $output = (Get-Content $httpServer.outFile -Raw) + (Get-Content $httpServer.errFile -Raw) + Remove-Item $httpServer.outFile -ErrorAction Continue + Remove-Item $httpServer.errFile -ErrorAction Continue + + if ($output.Contains($successMessage)) { + Write-Host "smoke-crash test $run/$runs : PASSED" -ForegroundColor Green + } + else { + Write-Error "smoke-crash test $run/$runs : FAILED" + } + } } diff --git a/test/Scripts.Integration.Test/integration-update-sentry.ps1 b/test/Scripts.Integration.Test/integration-update-sentry.ps1 index 7b605e37f..c846b2cce 100644 --- a/test/Scripts.Integration.Test/integration-update-sentry.ps1 +++ b/test/Scripts.Integration.Test/integration-update-sentry.ps1 @@ -4,37 +4,40 @@ param($arg) $unityPath = FormatUnityPath $arg -If (-not(Test-Path -Path "$PackageReleaseOutput")) -{ +If (-not(Test-Path -Path "$PackageReleaseOutput")) { Throw "Path $PackageReleaseOutput does not exist. Be sure to run ./test/Scripts.Integration.Test/integration-create-project." } -ClearUnityLog - -Write-Host -NoNewline "Starting Unity process:" -$UnityProcess = RunUnity $unityPath @("-batchmode", "-projectPath ", "$NewProjectPath", "-logfile", "$NewProjectLogPath", "-installSentry", "Disk") -Write-Host " OK" +function RunUnityAndExpect([string] $name, [string] $successMessage, [string] $failMessage, [string[]] $arguments) { + ClearUnityLog + + Write-Host -NoNewline "$name | Starting Unity process:" + $UnityProcess = RunUnity $unityPath $arguments + Write-Host " OK" + + WaitForLogFile 30 + + Write-Host "$name | Waiting for Unity to finish." + $stdout = SubscribeToUnityLogFile $UnityProcess $successMessage $failMessage + + Write-Host $stdout + If ($UnityProcess.ExitCode -ne 0) { + $exitCode = $UnityProcess.ExitCode + Write-Error "$name | Unity exited with code $exitCode" + } + ElseIf ($null -ne ($stdout | select-string $successMessage)) { + Write-Host "`n$name | SUCCESS" -ForegroundColor Green + } + Else { + Write-Error "$name | Unity exited without an error but the successMessage was not found in the output ('$successMessage')" + } +} -WaitForLogFile 30 -$successMessage = "Sentry Package Installation:" +RunUnityAndExpect "AddSentryPackage" "Sentry Package Installation:" "Sentry setup: FAILED" @( ` + "-batchmode", "-projectPath ", "$NewProjectPath", "-logfile", "$NewProjectLogPath", "-installSentry", "Disk") -Write-Host "Waiting for Unity to add Sentry to the project." -$stdout = SubscribeToUnityLogFile $UnityProcess $successMessage "Sentry setup: FAILED" - -Write-Host $stdout -If ($UnityProcess.ExitCode -ne 0) -{ - $exitCode = $UnityProcess.ExitCode - Throw "Unity exited with code $exitCode" -} -ElseIf ($null -ne ($stdout | select-string $successMessage)) -{ - Write-Host "`nSentry added!!" -} -Else -{ - Throw "Unity exited but failed to add the Sentry package." -} +RunUnityAndExpect "ConfigureSentryOptions" "ConfigureOptions: Sentry options Configured" "ConfigureOptions failed" @( ` + "-quit", "-batchmode", "-nographics", "-projectPath ", "$NewProjectPath", "-logfile", "$NewProjectLogPath", "-executeMethod", "Sentry.Unity.Editor.ConfigurationWindow.SentryEditorWindowInstrumentation.ConfigureOptions", "-sentryOptions.Dsn", "http://publickey@localhost:8000/12345") Write-Host -NoNewline "Updating test files " # We were previously using an empty SmokeTester to not generate Build errors. @@ -43,4 +46,6 @@ Remove-Item -Path "$NewProjectAssetsPath/Scripts/SmokeTester.cs" Remove-Item -Path "$NewProjectAssetsPath/Scripts/SmokeTester.cs.meta" Copy-Item "$PackageReleaseAssetsPath/Scripts/SmokeTester.cs" -Destination "$NewProjectAssetsPath/Scripts" Copy-Item "$PackageReleaseAssetsPath/Scripts/SmokeTester.cs.meta" -Destination "$NewProjectAssetsPath/Scripts" -Write-Host " OK" +Copy-Item "$PackageReleaseAssetsPath/Scripts/NativeSupport/CppPlugin.*" -Destination "$NewProjectAssetsPath/Scripts/" + +Write-Host " Unity configuration finished successfully" -ForegroundColor Green