diff --git a/.github/workflows/test-linux.yml b/.github/workflows/test-linux.yml index cee0fddf6..41c690603 100644 --- a/.github/workflows/test-linux.yml +++ b/.github/workflows/test-linux.yml @@ -118,8 +118,6 @@ jobs: env: EDITOR_BINARY: ${{ inputs.unreal-version == '4.27' && 'UE4Editor' || 'UnrealEditor' }} run: | - docker exec -w /workspace/checkout/sample unreal bash -c " - ls -al /workspace/checkout/sample/Plugins/sentry " docker exec -w /workspace/checkout/sample unreal /home/ue4/UnrealEngine/Engine/Build/BatchFiles/RunUAT.sh BuildCookRun \ -project=/workspace/checkout/sample/SentryPlayground.uproject \ -archivedirectory=/workspace/checkout/sample/Builds \ @@ -131,8 +129,6 @@ jobs: -prereqss \ -package \ -archive - docker exec -w /workspace/checkout/sample unreal bash -c " - cp -r '/home/gh/Library/Logs/Unreal Engine/LocalBuildLogs' Saved/Logs " docker exec -w /workspace/checkout/sample unreal /home/ue4/UnrealEngine/Engine/Binaries/Linux/"$EDITOR_BINARY" \ /workspace/checkout/sample/SentryPlayground.uproject \ -ReportExportPath=/workspace/checkout/sample/Saved/Automation \ @@ -147,17 +143,14 @@ jobs: if: ${{ always() && steps.run-tests.outcome == 'failure' }} uses: actions/upload-artifact@v4 with: - name: UE ${{ inputs.unreal-version }} sample test report + name: UE ${{ inputs.unreal-version }} sample test report (Linux) path: | checkout/sample/Saved/Automation - - name: Collect sample build info - if: contains(fromJson('["success", "failure"]'), steps.run-tests.outcome) + - name: Upload sample build + if: ${{ success() && steps.run-tests.outcome == 'success' }} uses: actions/upload-artifact@v4 with: - name: UE ${{ inputs.unreal-version }} sample build logs - path: | - checkout/sample/Saved/Logs - checkout/sample/Saved/Stats - checkout/sample/Saved/MaterialStats - checkout/sample/Saved/MaterialStatsDebug + name: UE ${{ inputs.unreal-version }} sample build (Linux) + path: checkout/sample/Builds/${{ inputs.unreal-version == '4.27' && 'LinuxNoEditor' || 'Linux' }}/ + retention-days: 1 diff --git a/.github/workflows/test-windows.yml b/.github/workflows/test-windows.yml index 89bb4a20c..82b571547 100644 --- a/.github/workflows/test-windows.yml +++ b/.github/workflows/test-windows.yml @@ -11,7 +11,7 @@ env: jobs: test: name: Test - runs-on: windows-latest + runs-on: windows-2022 steps: - name: Log in to GitHub package registry @@ -79,3 +79,20 @@ jobs: -NoPause ` -NoSplash ` -NullRHI + + - name: Collect sample test info + if: ${{ always() && steps.run-tests.outcome == 'failure' }} + uses: actions/upload-artifact@v4 + with: + name: UE ${{ inputs.unreal-version }} sample test report (Windows) + path: | + checkout/sample/Saved/Automation + + - name: Upload sample build + if: ${{ success() && steps.run-tests.outcome == 'success' }} + uses: actions/upload-artifact@v4 + with: + name: UE ${{ inputs.unreal-version }} sample build (Windows) + path: checkout/sample/Builds/${{ inputs.unreal-version == '4.27' && 'WindowsNoEditor' || 'Windows' }}/ + retention-days: 1 + diff --git a/CONTRIBUTING.md b/CONTRIBUTING.md index 18b616992..809993dc8 100644 --- a/CONTRIBUTING.md +++ b/CONTRIBUTING.md @@ -50,10 +50,9 @@ In order to run the demo level navigate to `Content Browser -> Content -> Maps` ## Modifying plugin content -All files that belong to the plugin are listed in the snapshot files: +All files that belong to the plugin are listed in the snapshot file: -- `/scripts/packaging/package-github.snapshot` (for the GitHub package) -- `/scripts/packaging/package-marketplace.snapshot` (for the Marketplace package) +- `/scripts/packaging/package.snapshot` If you add, delete or move files within the `plugin-dev` directory these snapshot files must be updated to reflect the changes. To do that, run: diff --git a/sample/Content/Blueprints/BP_BeforeBreadcrumbHandler.uasset b/sample/Content/Blueprints/BP_BeforeBreadcrumbHandler.uasset deleted file mode 100644 index 2b89d5404..000000000 Binary files a/sample/Content/Blueprints/BP_BeforeBreadcrumbHandler.uasset and /dev/null differ diff --git a/sample/Content/Misc/BP_BeforeBreadcrumbHandler.uasset b/sample/Content/Misc/BP_BeforeBreadcrumbHandler.uasset new file mode 100644 index 000000000..b81908dbc Binary files /dev/null and b/sample/Content/Misc/BP_BeforeBreadcrumbHandler.uasset differ diff --git a/sample/README.md b/sample/README.md new file mode 100644 index 000000000..57fbc72c4 --- /dev/null +++ b/sample/README.md @@ -0,0 +1,91 @@ +

+ + + +
+

+

+ +Sentry Unreal Engine SDK Sample Project +=========== + +This sample project demonstrates the capabilities of the Sentry Unreal Engine SDK and provides a comprehensive testing environment for all SDK features. + +## Getting started + +### Prerequisites + +- Unreal Engine 4.27 or newer (version configured in `SentryPlayground.uproject`) +- Platform support: Windows, macOS, Linux, Android, iOS, PlayStation 5, Xbox, Nintendo Switch +- Sentry account with a project and DSN + +### Setup + +- Clone or download the Sentry Unreal SDK repository +- Copy or symlink the Sentry plugin sources to `sample/Plugins/Sentry/` +- Open `SentryPlayground.uproject` in Unreal Engine +- Configure your Sentry DSN in `Config/DefaultEngine.ini` or through the project settings menu +- Play the `SentryDemo` level to begin testing + +## Project structure overview + +Here's a breakdown of the important sample project files and folders: + +```pwsh +📁 sample +├── 📄 SentryPlayground.uproject # Engine version configuration, supports UE 4.27 and newer +├── 📁 Source/ +│ └── 📁 SentryPlayground/ +│ ├── 📄 SentryPlaygroundGameInstance.cpp/.h # Logic for running integration tests +│ ├── 📄 SentryPlaygroundUtils.cpp/.h # Utilities for triggering different types of crashes +│ ├── 📄 CppBeforeSendHandler.cpp/.h # Example C++ implementation of `beforeSend` hook handler +│ └── 📄 SentryGCCallback.cpp/.h # Utility for capturing events during garbage collection +├── 📁 Content/ +│ ├── 📁 Maps/ +│ │ └── 📄 SentryDemo.umap # Main demo level) +│ ├── 📁 UI/ +│ │ └── 📄 W_SentryDemo.uasset # Demo UI widget for testing SDK features +│ └── 📁 Misc/ +│ ├── 📄 BP_BeforeSendHandler.uasset # Example Blueprint implementation of `beforeSend` hook handler +│ ├── 📄 BP_BeforeBreadcrumbHandler.uasset # Example Blueprint implementation of `beforeBreadcrumb` hook handler +│ └── 📄 BP_TraceSampler.uasset # Example Blueprint implementation of traces sampling function +├── 📁 Config/ +│ └── 📄 DefaultEngine.ini # Configuration file with Sentry plugin settings +└── 📁 Plugins/ # Location for Sentry SDK sources - copy or symlink here +``` + +## Demo Level + +The demo level (`SentryDemo.umap`) in the project's Content folder presents a simple UI for sending test events to Sentry. The `W_SentryDemo` Blueprint implementation demonstrates how to call the plugin API and serves as a reference. + +To run the demo level, navigate to `Content Browser -> Content -> Maps` and open the `SentryDemo` map. Click Play to launch the demo. + +## Unit Tests + +To run automation tests, several engine plugins are enabled (see `Settings -> Plugins -> Testing`). Navigate to `Windows -> Test Automation` menu and open the `Session Frontend` window. Switch to the `Automation` tab and select `Sentry` from the list of available tests. Click the `Start Tests` button to run the tests and check the results. + +## Integration Tests + +The `SentryPlaygroundGameInstance.cpp` file contains logic that parses command line input used to launch the sample game build and runs test actions accordingly. Here are example commands: + +```pwsh +# Windows - Crash capture test +SentryPlayground.exe -nullrhi -unattended -log -crash-capture -dsn="your-dsn-here" + +# Windows - Message capture test +SentryPlayground.exe -nullrhi -unattended -log -message-capture +``` + +To run integration tests, specify which test to run using the appropriate argument (e.g., `-crash-capture` or `-message-capture`). The game will close after the test is completed. Otherwise, the game will launch as usual and present the sample UI. + +Optionally, you can override the DSN for integration tests by adding `-dsn="your-dsn-here"` to the command line. When provided, this DSN will be used instead of the one configured in the project settings. + +## Example Content + +The sample project contains example Blueprint implementations of various hook handlers under `Content -> Misc`: + +- `BP_BeforeSendHandler` - Example Blueprint implementation of event filtering +- `BP_BeforeBreadcrumbHandler` - Custom breadcrumb processing logic +- `BP_TraceSampler` - Performance monitoring sampling configuration + +These can be configured for the SDK to use in `Project Settings -> Plugins -> Sentry`. \ No newline at end of file diff --git a/sample/Source/SentryPlayground/SentryPlayground.cpp b/sample/Source/SentryPlayground/SentryPlayground.cpp index d2101222e..28f969aa9 100644 --- a/sample/Source/SentryPlayground/SentryPlayground.cpp +++ b/sample/Source/SentryPlayground/SentryPlayground.cpp @@ -4,3 +4,5 @@ #include "Modules/ModuleManager.h" IMPLEMENT_PRIMARY_GAME_MODULE( FDefaultGameModuleImpl, SentryPlayground, "SentryPlayground" ); + +DEFINE_LOG_CATEGORY(LogSentrySample); diff --git a/sample/Source/SentryPlayground/SentryPlayground.h b/sample/Source/SentryPlayground/SentryPlayground.h index 37143a4de..f454875cc 100644 --- a/sample/Source/SentryPlayground/SentryPlayground.h +++ b/sample/Source/SentryPlayground/SentryPlayground.h @@ -4,3 +4,5 @@ #include "CoreMinimal.h" +DECLARE_LOG_CATEGORY_EXTERN(LogSentrySample, Verbose, All); + diff --git a/sample/Source/SentryPlayground/SentryPlaygroundGameInstance.cpp b/sample/Source/SentryPlayground/SentryPlaygroundGameInstance.cpp index 6898c0eb2..63c28023c 100644 --- a/sample/Source/SentryPlayground/SentryPlaygroundGameInstance.cpp +++ b/sample/Source/SentryPlayground/SentryPlaygroundGameInstance.cpp @@ -2,9 +2,122 @@ #include "SentryPlaygroundGameInstance.h" +#include "SentryPlayground.h" #include "SentrySubsystem.h" +#include "SentrySettings.h" +#include "SentryPlaygroundUtils.h" +#include "SentryUser.h" + +#include "Misc/CommandLine.h" +#include "Engine/Engine.h" void USentryPlaygroundGameInstance::Init() { Super::Init(); + + const TCHAR* CommandLine = FCommandLine::Get(); + + // Check for expected test parameters to decide between running integration tests + // or launching the sample app with UI for manual testing + if (FParse::Param(FCommandLine::Get(), TEXT("crash-capture")) || + FParse::Param(FCommandLine::Get(), TEXT("message-capture"))) + { + RunIntegrationTest(CommandLine); + } +} + +void USentryPlaygroundGameInstance::RunIntegrationTest(const TCHAR* CommandLine) +{ + UE_LOG(LogSentrySample, Log, TEXT("Running integration test for command: %s\n"), CommandLine); + + USentrySubsystem* SentrySubsystem = GEngine->GetEngineSubsystem(); + if (!SentrySubsystem) + { + CompleteTestWithResult(TEXT("sentry-error"), false, TEXT("Invalid Sentry subsystem")); + return; + } + + SentrySubsystem->InitializeWithSettings(FConfigureSettingsNativeDelegate::CreateLambda([=](USentrySettings* Settings) + { + // Override options set in config file if needed + FString Dsn; + if (FParse::Value(CommandLine, TEXT("dsn="), Dsn)) + { + Settings->Dsn = Dsn; + } + })); + + if (!SentrySubsystem->IsEnabled()) + { + CompleteTestWithResult(TEXT("sentry-error"), false, TEXT("Failed to initialize Sentry")); + return; + } + + SentrySubsystem->AddBreadcrumbWithParams( + TEXT("Integration test started"), TEXT("Test"), TEXT("info"), TMap(), ESentryLevel::Info); + + ConfigureTestContext(); + + SentrySubsystem->AddBreadcrumbWithParams( + TEXT("Context configuration finished"), TEXT("Test"), TEXT("info"), TMap(), ESentryLevel::Info); + + if (FParse::Param(CommandLine, TEXT("crash-capture"))) + { + RunCrashTest(); + } + else if (FParse::Param(CommandLine, TEXT("message-capture"))) + { + RunMessageTest(); + } +} + +void USentryPlaygroundGameInstance::RunCrashTest() +{ + USentrySubsystem* SentrySubsystem = GEngine->GetEngineSubsystem(); + + // Because we don't get the real crash event ID, create a fake one and set it as a tag + // This tag is then used by integration test script in CI to fetch the event + + FString EventId = FGuid::NewGuid().ToString(EGuidFormats::Digits); + + UE_LOG(LogSentrySample, Log, TEXT("EVENT_CAPTURED: %s\n"), *EventId); + + SentrySubsystem->SetTag(TEXT("test.crash_id"), EventId); + + USentryPlaygroundUtils::Terminate(ESentryAppTerminationType::NullPointer); +} + +void USentryPlaygroundGameInstance::RunMessageTest() +{ + USentrySubsystem* SentrySubsystem = GEngine->GetEngineSubsystem(); + + FString EventId = SentrySubsystem->CaptureMessage(TEXT("Integration test message")); + + UE_LOG(LogSentrySample, Log, TEXT("EVENT_CAPTURED: %s\n"), *EventId); + + CompleteTestWithResult(TEXT("message-capture"), !EventId.IsEmpty(), TEXT("Test complete")); +} + +void USentryPlaygroundGameInstance::ConfigureTestContext() +{ + USentrySubsystem* SentrySubsystem = GEngine->GetEngineSubsystem(); + + USentryUser* User = NewObject(); + User->Initialize(); + User->SetUsername(TEXT("TestUser")); + User->SetEmail(TEXT("user-mail@test.abc")); + User->SetId(TEXT("12345")); + + SentrySubsystem->SetUser(User); + + SentrySubsystem->SetTag(TEXT("test.suite"), TEXT("integration")); +} + +void USentryPlaygroundGameInstance::CompleteTestWithResult(const FString& TestName, bool Result, const FString& Message) +{ + UE_LOG(LogSentrySample, Log, TEXT("TEST_RESULT: {\"test\":\"%s\",\"success\":%s,\"message\":\"%s\"}\n"), + *TestName, Result ? TEXT("true") : TEXT("false"), *Message); + + // Close app after test is completed + FGenericPlatformMisc::RequestExit(false); } diff --git a/sample/Source/SentryPlayground/SentryPlaygroundGameInstance.h b/sample/Source/SentryPlayground/SentryPlaygroundGameInstance.h index 5381d662f..59f5672d2 100644 --- a/sample/Source/SentryPlayground/SentryPlaygroundGameInstance.h +++ b/sample/Source/SentryPlayground/SentryPlaygroundGameInstance.h @@ -4,6 +4,8 @@ #include "CoreMinimal.h" #include "Engine/GameInstance.h" +#include "SentryPlaygroundUtils.h" +#include "SentrySubsystem.h" #include "SentryPlaygroundGameInstance.generated.h" /** @@ -14,5 +16,15 @@ class SENTRYPLAYGROUND_API USentryPlaygroundGameInstance : public UGameInstance { GENERATED_BODY() +public: virtual void Init() override; + +private: + void RunIntegrationTest(const TCHAR* CommandLine); + void RunCrashTest(); + void RunMessageTest(); + + void ConfigureTestContext(); + + void CompleteTestWithResult(const FString& TestName, bool Result, const FString& Message); };