diff --git a/src/sentry_database.c b/src/sentry_database.c index 81646410f..32cf8ba57 100644 --- a/src/sentry_database.c +++ b/src/sentry_database.c @@ -103,6 +103,13 @@ static bool write_envelope(const sentry_path_t *path, const sentry_envelope_t *envelope) { sentry_uuid_t event_id = sentry__envelope_get_event_id(envelope); + + // Generate a random UUID for the filename if the envelope has no event_id + // this avoids collisions on NIL-UUIDs + if (sentry_uuid_is_nil(&event_id)) { + event_id = sentry_uuid_new_v4(); + } + char *envelope_filename = sentry__uuid_as_filename(&event_id, ".envelope"); if (!envelope_filename) { return false; diff --git a/tests/__init__.py b/tests/__init__.py index 36fb648e2..710405bf0 100644 --- a/tests/__init__.py +++ b/tests/__init__.py @@ -70,6 +70,21 @@ def split_log_request_cond(httpserver_log, cond): ) +def extract_request(httpserver_log, cond): + """ + Extract a request matching the condition from the httpserver log. + Returns (matching_request, remaining_log_entries) + + The remaining_log_entries preserves the original format so it can be + chained with subsequent extract_request calls. + """ + for i, entry in enumerate(httpserver_log): + if cond(entry[0].get_data()): + others = [httpserver_log[j] for j in range(len(httpserver_log)) if j != i] + return (entry[0], others) + return (None, httpserver_log) + + def run(cwd, exe, args, expect_failure=False, env=None, **kwargs): if env is None: env = dict(os.environ) diff --git a/tests/test_integration_crashpad.py b/tests/test_integration_crashpad.py index 6bec1c279..0a501dd7d 100644 --- a/tests/test_integration_crashpad.py +++ b/tests/test_integration_crashpad.py @@ -11,6 +11,7 @@ run, Envelope, split_log_request_cond, + extract_request, is_session_envelope, is_logs_envelope, is_feedback_envelope, @@ -614,6 +615,42 @@ def test_crashpad_logs_on_crash(cmake, httpserver): assert_logs(logs_envelope, 1) +@pytest.mark.skipif(not flushes_state, reason="test needs state flushing") +def test_crashpad_logs_and_session_on_crash(cmake, httpserver): + tmp_path = cmake(["sentry_example"], {"SENTRY_BACKEND": "crashpad"}) + + env = dict(os.environ, SENTRY_DSN=make_dsn(httpserver)) + httpserver.expect_oneshot_request("/api/123456/minidump/").respond_with_data("OK") + httpserver.expect_request("/api/123456/envelope/").respond_with_data("OK") + + with httpserver.wait(timeout=10) as waiting: + run( + tmp_path, + "sentry_example", + ["log", "enable-logs", "capture-log", "crash", "start-session"], + expect_failure=True, + env=env, + ) + + assert waiting.result + + run(tmp_path, "sentry_example", ["log", "no-setup"], env=env) + + # we expect 1 envelope with the log, 1 for the crash, and 1 for the session + assert len(httpserver.log) == 3 + + logs_request, remaining = extract_request(httpserver.log, is_logs_envelope) + session_request, remaining = extract_request(remaining, is_session_envelope) + multipart = remaining[0][0] # The crash/minidump + + logs_envelope = Envelope.deserialize(logs_request.get_data()) + assert logs_envelope is not None + assert_logs(logs_envelope, 1) + + session_envelope = Envelope.deserialize(session_request.get_data()) + assert session_envelope is not None + + def test_disable_backend(cmake, httpserver): tmp_path = cmake(["sentry_example"], {"SENTRY_BACKEND": "crashpad"})