diff --git a/CHANGELOG.md b/CHANGELOG.md index 586cee5d23..e4e670a996 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -13,6 +13,7 @@ - Add a `transfer_timeout` option for SDK-managed HTTP transports. ([#1741](https://github.com/getsentry/sentry-native/pull/1741)) - Apple: use `os_sync_wait_on_address` for the level-triggered waitable flag in the batcher on modern macOS(14.4+) and iOS(17.4+). ([#1765](https://github.com/getsentry/sentry-native/pull/1765)) - Native/macOS: add thread names. ([#1766](https://github.com/getsentry/sentry-native/pull/1766)) +- Native/Windows: add `attach_wer_report` option for attaching `Report.wer` generated by WER. ([#1777](https://github.com/getsentry/sentry-native/pull/1777)) **Fixes**: diff --git a/examples/example.c b/examples/example.c index 25200de27c..15fb1df636 100644 --- a/examples/example.c +++ b/examples/example.c @@ -878,6 +878,9 @@ main(int argc, char **argv) sentry_options_set_crash_upload_mode( options, SENTRY_CRASH_UPLOAD_MODE_ASYNC); } + if (has_arg(argc, argv, "attach-wer-report")) { + sentry_options_set_attach_wer_report(options, true); + } // E2E test mode: generate unique test ID for event correlation char e2e_test_id[37] = { 0 }; diff --git a/include/sentry.h b/include/sentry.h index 75d5ebd88a..562dd8bda5 100644 --- a/include/sentry.h +++ b/include/sentry.h @@ -1924,6 +1924,18 @@ SENTRY_API void sentry_options_set_crash_upload_mode( SENTRY_API sentry_crash_upload_mode_t sentry_options_get_crash_upload_mode( const sentry_options_t *opts); +/** + * Enables or disables attaching WER (Windows Error Reporting) reports. This is + * disabled by default. + * + * When enabled, a `Report.wer` file generated by WER is attached to crash + * events. + * + * This setting only has an effect when using the `native` backend on Windows. + */ +SENTRY_EXPERIMENTAL_API void sentry_options_set_attach_wer_report( + sentry_options_t *opts, int val); + /** * Enables a wait for the crash report upload to be finished before shutting * down. This is disabled by default. diff --git a/src/backends/native/sentry_crash_context.h b/src/backends/native/sentry_crash_context.h index 40f43b4f18..7260107395 100644 --- a/src/backends/native/sentry_crash_context.h +++ b/src/backends/native/sentry_crash_context.h @@ -122,8 +122,9 @@ typedef enum { SENTRY_CRASH_STATE_READY = 0, SENTRY_CRASH_STATE_CRASHED = 1, SENTRY_CRASH_STATE_PROCESSING = 2, - SENTRY_CRASH_STATE_CAPTURED = 3, - SENTRY_CRASH_STATE_DONE = 4 + SENTRY_CRASH_STATE_PROCESSED = 3, + SENTRY_CRASH_STATE_CAPTURED = 4, + SENTRY_CRASH_STATE_DONE = 5 } sentry_crash_state_t; /** @@ -242,6 +243,9 @@ typedef struct { // Additional thread contexts DWORD num_threads; sentry_thread_context_windows_t threads[SENTRY_CRASH_MAX_THREADS]; + + bool wer_enabled; + char wer_report_id[64]; } sentry_crash_platform_windows_t; typedef struct { @@ -281,6 +285,7 @@ typedef struct { bool attach_screenshot; // Screenshot attachment enabled in parent process bool attach_session_replay; // Session replay attachment enabled in parent // process + bool attach_wer_report; // WER report attachment enabled in parent process uint32_t session_replay_duration; // Requested session replay duration in // ms int cache_keep; // sentry_cache_keep_t diff --git a/src/backends/native/sentry_crash_daemon.c b/src/backends/native/sentry_crash_daemon.c index 93397233f8..b5d8ee4c32 100644 --- a/src/backends/native/sentry_crash_daemon.c +++ b/src/backends/native/sentry_crash_daemon.c @@ -56,6 +56,7 @@ # include # include # include +# include # include // Forward declaration for StackWalk64-based stack unwinding (defined later) @@ -2211,6 +2212,161 @@ enumerate_threads_from_process(sentry_crash_context_t *ctx) ctx->crashed_pid); # endif // SENTRY_PLATFORM_XBOX } + +# if !defined(SENTRY_PLATFORM_XBOX) +typedef HRESULT(WINAPI *WerStoreOpen_t)(REPORT_STORE_TYPES, HREPORTSTORE *); +typedef void(WINAPI *WerStoreClose_t)(HREPORTSTORE); +typedef HRESULT(WINAPI *WerStoreGetFirstReportKey_t)(HREPORTSTORE, PCWSTR *); +typedef HRESULT(WINAPI *WerStoreGetNextReportKey_t)(HREPORTSTORE, PCWSTR *); +typedef HRESULT(WINAPI *WerStoreQueryReportMetadataV2_t)( + HREPORTSTORE, PCWSTR, PWER_REPORT_METADATA_V2); +typedef void(WINAPI *WerFreeString_t)(PCWSTR); + +static struct { + HMODULE module; + WerStoreOpen_t WerStoreOpen; + WerStoreClose_t WerStoreClose; + WerStoreGetFirstReportKey_t WerStoreGetFirstReportKey; + WerStoreGetNextReportKey_t WerStoreGetNextReportKey; + WerStoreQueryReportMetadataV2_t WerStoreQueryReportMetadataV2; + WerFreeString_t WerFreeString; +} g_wer = { NULL, NULL, NULL, NULL, NULL, NULL, NULL }; + +# define WER_FAILED ((HMODULE)(intptr_t)-1) + +static bool +resolve_wer(void) +{ + if (g_wer.module) { + return g_wer.module != WER_FAILED; + } + + g_wer.module + = LoadLibraryExW(L"wer.dll", NULL, LOAD_LIBRARY_SEARCH_SYSTEM32); + if (!g_wer.module) { + g_wer.module = WER_FAILED; + return false; + } + + g_wer.WerStoreOpen + = (WerStoreOpen_t)GetProcAddress(g_wer.module, "WerStoreOpen"); + g_wer.WerStoreClose + = (WerStoreClose_t)GetProcAddress(g_wer.module, "WerStoreClose"); + g_wer.WerStoreGetFirstReportKey + = (WerStoreGetFirstReportKey_t)GetProcAddress( + g_wer.module, "WerStoreGetFirstReportKey"); + g_wer.WerStoreGetNextReportKey = (WerStoreGetNextReportKey_t)GetProcAddress( + g_wer.module, "WerStoreGetNextReportKey"); + g_wer.WerStoreQueryReportMetadataV2 + = (WerStoreQueryReportMetadataV2_t)GetProcAddress( + g_wer.module, "WerStoreQueryReportMetadataV2"); + g_wer.WerFreeString + = (WerFreeString_t)GetProcAddress(g_wer.module, "WerFreeString"); + + if (!g_wer.WerStoreOpen || !g_wer.WerStoreClose + || !g_wer.WerStoreGetFirstReportKey || !g_wer.WerStoreGetNextReportKey + || !g_wer.WerStoreQueryReportMetadataV2 || !g_wer.WerFreeString) { + FreeLibrary(g_wer.module); + g_wer.module = WER_FAILED; + return false; + } + + return true; +} + +static sentry_path_t * +find_wer_report(sentry_uuid_t *report_id) +{ + if (!resolve_wer()) { + return NULL; + } + + sentry_path_t *report_path = NULL; + REPORT_STORE_TYPES store_types[] + = { E_STORE_USER_ARCHIVE, E_STORE_MACHINE_ARCHIVE }; + for (size_t i = 0; + !report_path && i < sizeof(store_types) / sizeof(store_types[0]); i++) { + HREPORTSTORE report_store; + if (g_wer.WerStoreOpen(store_types[i], &report_store) != S_OK) { + continue; + } + + PCWSTR report_key = NULL; + WER_REPORT_METADATA_V2 report_data = { 0 }; + + HRESULT hr = g_wer.WerStoreGetFirstReportKey(report_store, &report_key); + while (SUCCEEDED(hr) && report_key && !report_path) { + if (g_wer.WerStoreQueryReportMetadataV2( + report_store, report_key, &report_data) + == S_OK) { + if (sentry__uuid_equal_native(report_id, &report_data.ReportId) + || sentry__uuid_equal_native( + report_id, &report_data.ReportIntegratorId)) { + sentry_path_t *report_dir + = sentry__path_from_wstr(report_key); + if (report_dir) { + report_path + = sentry__path_join_str(report_dir, "Report.wer"); + sentry__path_free(report_dir); + } + } + } + g_wer.WerFreeString(report_key); + if (!report_path) { + hr = g_wer.WerStoreGetNextReportKey(report_store, &report_key); + } + } + + g_wer.WerStoreClose(report_store); + } + + return report_path; +} + +/** + * Reads a WER report, converts it to UTF-16LE -> UTF-8, and writes to the .run + * directory. + * + * TODO: use sentry__path_copy: https://github.com/getsentry/sentry/issues/91336 + */ +static bool +write_wer_report(sentry_path_t *report_path, sentry_path_t *run_folder) +{ + int rv = -1; + char *utf8 = NULL; + void *utf16 = NULL; + size_t utf16_size = 0; + + sentry_path_t *run_path = sentry__path_join_str(run_folder, "Report.wer"); + if (!run_path) { + goto cleanup; + } + + utf16 = sentry__path_read_to_buffer(report_path, &utf16_size); + if (!utf16) { + SENTRY_WARN("Failed to read WER report"); + goto cleanup; + } + + utf8 = sentry__string_from_wstr_n( + (const wchar_t *)utf16, utf16_size / sizeof(wchar_t)); + if (!utf8) { + SENTRY_WARN("Failed to convert WER report to UTF-8"); + goto cleanup; + } + + if ((rv = sentry__path_write_buffer(run_path, utf8, strlen(utf8))) != 0) { + SENTRY_WARN("Failed to write WER report"); + goto cleanup; + } + +cleanup: + sentry_free(utf8); + sentry_free(utf16); + sentry__path_free(run_path); + return rv == 0; +} +# endif // !SENTRY_PLATFORM_XBOX #endif // SENTRY_PLATFORM_WINDOWS /** @@ -2819,6 +2975,17 @@ write_envelope_with_native_stacktrace(const sentry_options_t *options, } } + // Add Report.wer if captured by WER + if (ctx->attach_wer_report && run_folder) { + sentry_path_t *report_path + = sentry__path_join_str(run_folder, "Report.wer"); + if (report_path) { + write_attachment_to_envelope( + fd, report_path->path, "Report.wer", NULL, "text/plain"); + sentry__path_free(report_path); + } + } + #if defined(SENTRY_PLATFORM_UNIX) close(fd); #elif defined(SENTRY_PLATFORM_WINDOWS) @@ -3071,6 +3238,17 @@ write_envelope_with_minidump(const sentry_options_t *options, } } + // Add Report.wer if captured by WER + if (ctx->attach_wer_report && run_folder) { + sentry_path_t *report_path + = sentry__path_join_str(run_folder, "Report.wer"); + if (report_path) { + write_attachment_to_envelope( + fd, report_path->path, "Report.wer", NULL, "text/plain"); + sentry__path_free(report_path); + } + } + #if defined(SENTRY_PLATFORM_UNIX) close(fd); #elif defined(SENTRY_PLATFORM_WINDOWS) @@ -3288,7 +3466,8 @@ sentry__process_crash(const sentry_options_t *options, sentry_crash_ipc_t *ipc) } #endif - // On Windows, capture modules and threads from the crashed process + // On Windows, capture modules, threads, and WER report for the crashed + // process #if defined(SENTRY_PLATFORM_WINDOWS) if (use_native_mode) { if (ctx->module_count == 0) { @@ -3300,6 +3479,34 @@ sentry__process_crash(const sentry_options_t *options, sentry_crash_ipc_t *ipc) enumerate_threads_from_process(ctx); } } + +# if !defined(SENTRY_PLATFORM_XBOX) + if (ctx->platform.wer_enabled && ctx->attach_wer_report && run_folder) { + SENTRY_DEBUG("Waiting for WER, allowing app process to exit"); + sentry__atomic_store(&ctx->state, SENTRY_CRASH_STATE_PROCESSED); + + int elapsed_ms = 0; + while (elapsed_ms < SENTRY_CRASH_HANDLER_WAIT_TIMEOUT_MS) { + if (!sentry__string_empty(ctx->platform.wer_report_id)) { + sentry_uuid_t report_id + = sentry_uuid_from_string(ctx->platform.wer_report_id); + sentry_path_t *wer_report_path = find_wer_report(&report_id); + if (wer_report_path) { + SENTRY_DEBUGF("Found WER report %s: %s", + ctx->platform.wer_report_id, wer_report_path->path); + if (write_wer_report(wer_report_path, run_folder)) { + sentry__path_free(wer_report_path); + break; + } + sentry__path_free(wer_report_path); + } + } + + Sleep(SENTRY_CRASH_HANDLER_POLL_INTERVAL_MS); + elapsed_ms += SENTRY_CRASH_HANDLER_POLL_INTERVAL_MS; + } + } +# endif #endif // Write envelope based on mode diff --git a/src/backends/native/sentry_crash_handler.c b/src/backends/native/sentry_crash_handler.c index 3d6658a7c9..1d72721994 100644 --- a/src/backends/native/sentry_crash_handler.c +++ b/src/backends/native/sentry_crash_handler.c @@ -1026,17 +1026,12 @@ crash_exception_filter(EXCEPTION_POINTERS *exception_info) // Wait for daemon to finish processing (keep process alive for // minidump) - bool processing_started = false; int elapsed_ms = 0; while (elapsed_ms < SENTRY_CRASH_HANDLER_WAIT_TIMEOUT_MS) { long state = sentry__atomic_fetch(&ctx->state); - if (state == SENTRY_CRASH_STATE_PROCESSING && !processing_started) { - // Daemon started processing (no logging - exception filter - // context) - processing_started = true; - } else if (state >= SENTRY_CRASH_STATE_CAPTURED) { - // Daemon captured crash data (no logging - exception filter - // context) + if (state >= SENTRY_CRASH_STATE_PROCESSED) { + // Daemon already processed crash data (no logging - exception + // filter context) break; } Sleep(SENTRY_CRASH_HANDLER_POLL_INTERVAL_MS); diff --git a/src/backends/native/sentry_wer.c b/src/backends/native/sentry_wer.c index 37542528c3..665841ac0f 100644 --- a/src/backends/native/sentry_wer.c +++ b/src/backends/native/sentry_wer.c @@ -6,29 +6,21 @@ #include #include -#ifndef STATUS_FAIL_FAST_EXCEPTION -# define STATUS_FAIL_FAST_EXCEPTION ((DWORD)0xC0000602) -#endif - -#ifndef STATUS_STACK_BUFFER_OVERRUN -# define STATUS_STACK_BUFFER_OVERRUN ((DWORD)0xC0000409) -#endif +typedef struct { + DWORD dwSize; + HANDLE hProcess; + HANDLE hThread; + EXCEPTION_RECORD exceptionRecord; + CONTEXT context; + PCWSTR pwszReportId; + BOOL bIsFatal; + DWORD dwReserved; +} WER_RUNTIME_EXCEPTION_INFORMATION_19041; static BOOL is_fatal_wer_exception(const WER_RUNTIME_EXCEPTION_INFORMATION *info) { // bIsFatal is missing in older SDKs; guard access with dwSize. - typedef struct { - DWORD dwSize; - HANDLE hProcess; - HANDLE hThread; - EXCEPTION_RECORD exceptionRecord; - CONTEXT context; - PCWSTR pwszReportId; - BOOL bIsFatal; - DWORD dwReserved; - } WER_RUNTIME_EXCEPTION_INFORMATION_19041; - if (!info || info->dwSize <= offsetof(WER_RUNTIME_EXCEPTION_INFORMATION_19041, bIsFatal)) { @@ -38,11 +30,18 @@ is_fatal_wer_exception(const WER_RUNTIME_EXCEPTION_INFORMATION *info) return ((const WER_RUNTIME_EXCEPTION_INFORMATION_19041 *)info)->bIsFatal; } -static BOOL -is_native_wer_exception(DWORD code) +static PCWSTR +get_report_id(const WER_RUNTIME_EXCEPTION_INFORMATION *info) { - return code == STATUS_FAIL_FAST_EXCEPTION - || code == STATUS_STACK_BUFFER_OVERRUN; + // pwszReportId is missing in older SDKs; guard access with dwSize. + if (!info + || info->dwSize <= offsetof( + WER_RUNTIME_EXCEPTION_INFORMATION_19041, pwszReportId)) { + return NULL; + } + + return ((const WER_RUNTIME_EXCEPTION_INFORMATION_19041 *)info) + ->pwszReportId; } static BOOL @@ -113,9 +112,7 @@ static BOOL process_wer_exception( PVOID context, const WER_RUNTIME_EXCEPTION_INFORMATION *exception_info) { - if (!exception_info || !is_fatal_wer_exception(exception_info) - || !is_native_wer_exception( - exception_info->exceptionRecord.ExceptionCode)) { + if (!exception_info || !is_fatal_wer_exception(exception_info)) { return FALSE; } @@ -132,6 +129,16 @@ process_wer_exception( } BOOL claimed = FALSE; + PCWSTR report_id = get_report_id(exception_info); + if (report_id + && WideCharToMultiByte(CP_UTF8, 0, report_id, -1, + ctx->platform.wer_report_id, + (int)sizeof(ctx->platform.wer_report_id), NULL, NULL) + <= 0) { + ctx->platform.wer_report_id[0] = '\0'; + } + + // SENTRY_CRASH_STATE_READY: hard WER crash that bypassed the crash handler if (InterlockedCompareExchange(&ctx->state, SENTRY_CRASH_STATE_PROCESSING, SENTRY_CRASH_STATE_READY) == SENTRY_CRASH_STATE_READY) { diff --git a/src/backends/sentry_backend_native.c b/src/backends/sentry_backend_native.c index 3c3e508434..3a6f531dfc 100644 --- a/src/backends/sentry_backend_native.c +++ b/src/backends/sentry_backend_native.c @@ -124,21 +124,21 @@ wer_unregister_module(void) memset(&g_wer_registration, 0, sizeof(g_wer_registration)); } -static void +static bool wer_register_module(uint64_t app_tid) { windows_version_t win_ver; if (!sentry__get_windows_version(&win_ver) || win_ver.build < 19041) { SENTRY_WARN("Native WER module not registered, because Windows " "doesn't meet version requirements (build >= 19041)."); - return; + return false; } sentry_path_t *wer_path = wer_default_path(); if (!wer_path || !sentry__path_is_file(wer_path)) { SENTRY_WARN("Native WER module not found"); sentry__path_free(wer_path); - return; + return false; } const DWORD one = 1; @@ -146,7 +146,7 @@ wer_register_module(uint64_t app_tid) if (reg_res != ERROR_SUCCESS) { SENTRY_WARN("registering native WER module in registry failed"); sentry__path_free(wer_path); - return; + return false; } g_wer_registration.version = 1; @@ -160,11 +160,12 @@ wer_register_module(uint64_t app_tid) wer_delete_registry_value(wer_path); sentry__path_free(wer_path); memset(&g_wer_registration, 0, sizeof(g_wer_registration)); - return; + return false; } SENTRY_DEBUGF("registered native WER module \"%s\"", wer_path->path); g_wer_path = wer_path; + return true; } #endif @@ -297,6 +298,7 @@ native_backend_startup( ctx->crash_reporting_mode = options->crash_reporting_mode; ctx->system_crash_reporter_enabled = options->system_crash_reporter_enabled; ctx->crash_upload_mode = options->crash_upload_mode; + ctx->attach_wer_report = options->attach_wer_report; // Pass debug logging setting to daemon ctx->debug_enabled = options->debug; @@ -526,7 +528,7 @@ native_backend_startup( } # if defined(SENTRY_PLATFORM_WINDOWS) && !defined(SENTRY_PLATFORM_XBOX) - wer_register_module(tid); + state->ipc->shmem->platform.wer_enabled = wer_register_module(tid); # endif if (sentry__crash_handler_init(state->ipc) < 0) { diff --git a/src/sentry_options.c b/src/sentry_options.c index cb5bb936a6..e280dfa909 100644 --- a/src/sentry_options.c +++ b/src/sentry_options.c @@ -99,6 +99,7 @@ sentry_options_new(void) = SENTRY_CRASH_REPORTING_MODE_NATIVE_WITH_MINIDUMP; // Default: best of // both worlds opts->crash_upload_mode = SENTRY_CRASH_UPLOAD_MODE_SYNC; + opts->attach_wer_report = false; opts->http_retry = false; opts->send_client_reports = true; opts->enable_large_attachments = false; @@ -636,6 +637,12 @@ sentry_options_get_crash_upload_mode(const sentry_options_t *opts) return (sentry_crash_upload_mode_t)opts->crash_upload_mode; } +void +sentry_options_set_attach_wer_report(sentry_options_t *opts, int val) +{ + opts->attach_wer_report = !!val; +} + void sentry_options_set_crashpad_wait_for_upload( sentry_options_t *opts, int wait_for_upload) diff --git a/src/sentry_options.h b/src/sentry_options.h index 6f64bba436..c2ad889b3b 100644 --- a/src/sentry_options.h +++ b/src/sentry_options.h @@ -103,6 +103,7 @@ struct sentry_options_s { int crash_reporting_mode; // 0=minidump, 1=native, 2=native_with_minidump // (see sentry_crash_reporting_mode_t) int crash_upload_mode; // 0=sync, 1=async (see sentry_crash_upload_mode_t) + bool attach_wer_report; #ifdef SENTRY_PLATFORM_NX void (*network_connect_func)(void); diff --git a/src/sentry_uuid.c b/src/sentry_uuid.c index 1a81ca20e4..2359f1ba04 100644 --- a/src/sentry_uuid.c +++ b/src/sentry_uuid.c @@ -37,7 +37,12 @@ sentry_uuid_from_string_n(const char *str, size_t str_len) bool is_nibble = true; char nibble = 0; - for (i = 0; i < len && pos < 16; i++) { + if (len > 1 && str[0] == '{' && str[len - 1] == '}') { + i = 1; + len--; + } + + for (; i < len && pos < 16; i++) { char c = str[i]; if (!c || c == '-') { continue; @@ -148,6 +153,12 @@ sentry__uuid_as_filename(const sentry_uuid_t *uuid, const char *suffix) return buf; } +bool +sentry__uuid_equal(const sentry_uuid_t *a, const sentry_uuid_t *b) +{ + return memcmp(a->bytes, b->bytes, 16) == 0; +} + #ifdef SENTRY_PLATFORM_WINDOWS sentry_uuid_t sentry__uuid_from_native(const GUID *guid) @@ -171,4 +182,11 @@ sentry__uuid_from_native(const GUID *guid) rv.bytes[15] = (char)guid->Data4[7]; return rv; } + +bool +sentry__uuid_equal_native(const sentry_uuid_t *uuid, const GUID *guid) +{ + sentry_uuid_t guid_uuid = sentry__uuid_from_native(guid); + return sentry__uuid_equal(uuid, &guid_uuid); +} #endif diff --git a/src/sentry_uuid.h b/src/sentry_uuid.h index 8476e81727..ef9ae6644d 100644 --- a/src/sentry_uuid.h +++ b/src/sentry_uuid.h @@ -20,11 +20,21 @@ void sentry__span_uuid_as_string(const sentry_uuid_t *uuid, char str[17]); */ char *sentry__uuid_as_filename(const sentry_uuid_t *uuid, const char *suffix); +/** + * Compares two sentry UUIDs for equality. + */ +bool sentry__uuid_equal(const sentry_uuid_t *a, const sentry_uuid_t *b); + #ifdef SENTRY_PLATFORM_WINDOWS /** * Create a new UUID from the windows-native GUID type. */ sentry_uuid_t sentry__uuid_from_native(const GUID *guid); + +/** + * Compares a sentry UUID with a windows-native GUID for equality. + */ +bool sentry__uuid_equal_native(const sentry_uuid_t *uuid, const GUID *guid); #endif #endif diff --git a/tests/test_integration_native.py b/tests/test_integration_native.py index 54180014b7..fe909b1a1d 100644 --- a/tests/test_integration_native.py +++ b/tests/test_integration_native.py @@ -105,7 +105,7 @@ def test_native_wer_crash(cmake, httpserver, crash_arg, exception_code): run_crash( tmp_path, "sentry_example", - ["log", "stdout", crash_arg], + ["log", "stdout", crash_arg, "attach-wer-report"], env=dict(os.environ, SENTRY_DSN=make_dsn(httpserver)), ) assert waiting.result @@ -114,6 +114,47 @@ def test_native_wer_crash(cmake, httpserver, crash_arg, exception_code): envelope = Envelope.deserialize(httpserver.log[0][0].get_data()) assert_native_crash(envelope, exception_code=exception_code) + has_wer_report = any( + item.headers.get("type") == "attachment" + and item.headers.get("filename") == "Report.wer" + for item in envelope.items + ) + assert has_wer_report, "Should include WER report" + + +@pytest.mark.skipif( + sys.platform != "win32", + reason="WER reports are only available on Windows", +) +@pytest.mark.with_wer +@pytest.mark.parametrize("crash_mode", ["native", "minidump", "native-with-minidump"]) +def test_native_wer_report(cmake, httpserver, crash_mode): + """Test WER report capture with native backend""" + tmp_path = cmake(["sentry_example"], {"SENTRY_BACKEND": "native"}) + + httpserver.expect_oneshot_request("/api/123456/envelope/").respond_with_data("OK") + + with httpserver.wait(timeout=10) as waiting: + run_crash( + tmp_path, + "sentry_example", + ["log", "stdout", "crash", "crash-mode", crash_mode, "attach-wer-report"], + env=dict(os.environ, SENTRY_DSN=make_dsn(httpserver)), + ) + assert waiting.result + + assert len(httpserver.log) >= 1 + envelope = Envelope.deserialize(httpserver.log[0][0].get_data()) + if "native" in crash_mode: + assert_native_crash(envelope) + + has_wer_report = any( + item.headers.get("type") == "attachment" + and item.headers.get("filename") == "Report.wer" + for item in envelope.items + ) + assert has_wer_report, "Should include WER report" + @pytest.mark.skipif(not has_oom, reason="OOM test unreliable in this environment") def test_native_oom(cmake, httpserver): diff --git a/tests/unit/test_uuid.c b/tests/unit/test_uuid.c index 2ff38ebd38..1c04efd884 100644 --- a/tests/unit/test_uuid.c +++ b/tests/unit/test_uuid.c @@ -46,3 +46,13 @@ SENTRY_TEST(internal_uuid_api) sentry__span_uuid_as_string(&span_id, sbuf); TEST_CHECK_STRING_EQUAL(sbuf, "f391fdc0bb2743b1"); } + +SENTRY_TEST(uuid_braces) +{ + sentry_uuid_t uuid + = sentry_uuid_from_string("{f391fdc0-bb27-43b1-8c0c-183bc217d42b}"); + TEST_CHECK(!sentry_uuid_is_nil(&uuid)); + char buf[37]; + sentry_uuid_as_string(&uuid, buf); + TEST_CHECK_STRING_EQUAL(buf, "f391fdc0-bb27-43b1-8c0c-183bc217d42b"); +} diff --git a/tests/unit/tests.inc b/tests/unit/tests.inc index e7ad60ab96..f26889d055 100644 --- a/tests/unit/tests.inc +++ b/tests/unit/tests.inc @@ -353,6 +353,7 @@ XX(user_feedback_is_valid) XX(user_feedback_with_null_args) XX(user_report_is_valid) XX(uuid_api) +XX(uuid_braces) XX(uuid_v4) XX(value_attribute) XX(value_bool)