diff --git a/CMakeLists.txt b/CMakeLists.txt index e955145be..0c3d3ffaa 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -115,6 +115,9 @@ option(SENTRY_BUILD_TESTS "Build sentry-native tests" "${SENTRY_MAIN_PROJECT}") option(SENTRY_BUILD_EXAMPLES "Build sentry-native example(s)" "${SENTRY_MAIN_PROJECT}") option(SENTRY_BUILD_BENCHMARKS "Build sentry-native benchmarks" OFF) +option(SENTRY_INTEGRATION_PLATFORM + "Enable the downstream-provided platform integration" OFF) + # Platform version embedding options option(SENTRY_EMBED_INFO "Embed version information in binary" OFF) set(SENTRY_BUILD_PLATFORM "${CMAKE_SYSTEM_NAME}" CACHE STRING "Platform name for embedded version (e.g., switch, playstation, xbox)") @@ -298,6 +301,7 @@ message(STATUS "SENTRY_TRANSPORT=${SENTRY_TRANSPORT}") message(STATUS "SENTRY_BACKEND=${SENTRY_BACKEND}") message(STATUS "SENTRY_LIBRARY_TYPE=${SENTRY_LIBRARY_TYPE}") message(STATUS "SENTRY_SDK_NAME=${SENTRY_SDK_NAME}") +message(STATUS "SENTRY_INTEGRATION_PLATFORM=${SENTRY_INTEGRATION_PLATFORM}") message(STATUS "SENTRY_HANDLER_STACK_SIZE=${SENTRY_HANDLER_STACK_SIZE}") message(STATUS "SENTRY_BATCHER_BUFFER_COUNT=${SENTRY_BATCHER_BUFFER_COUNT}") if (WIN32) @@ -419,6 +423,10 @@ endif() add_subdirectory(src) +if(SENTRY_INTEGRATION_PLATFORM) + target_compile_definitions(sentry PRIVATE SENTRY_INTEGRATION_PLATFORM) +endif() + target_compile_definitions(sentry PRIVATE SENTRY_HANDLER_STACK_SIZE=${SENTRY_HANDLER_STACK_SIZE}) target_compile_definitions(sentry PRIVATE SENTRY_BATCHER_BUFFER_COUNT=${SENTRY_BATCHER_BUFFER_COUNT}) if(WIN32) @@ -1030,6 +1038,9 @@ if(SENTRY_BUILD_TESTS) add_subdirectory(tests/unit) add_subdirectory(tests/fixtures/crash_reporter) add_subdirectory(tests/fixtures/early_init) + if(SENTRY_INTEGRATION_PLATFORM AND SENTRY_MAIN_PROJECT) + add_subdirectory(tests/fixtures/test_platform) + endif() add_subdirectory(tests/fixtures/screenshot) if(WIN32 AND NOT XBOX) add_subdirectory(tests/fixtures/appx) diff --git a/src/sentry_core.c b/src/sentry_core.c index b87e36c59..6a6ef7719 100644 --- a/src/sentry_core.c +++ b/src/sentry_core.c @@ -117,8 +117,11 @@ unregister_integrations(sentry_scope_t *scope, const sentry_options_t *options) } } -#if defined(SENTRY_PLATFORM_NX) || defined(SENTRY_PLATFORM_PS) \ - || defined(SENTRY_PLATFORM_XBOX) +// TODO: remove sentry__native_init after console SDKs have been migrated to +// platform integrations +#if (defined(SENTRY_PLATFORM_NX) || defined(SENTRY_PLATFORM_PS) \ + || defined(SENTRY_PLATFORM_XBOX)) \ + && !defined(SENTRY_INTEGRATION_PLATFORM) int sentry__native_init(sentry_options_t *options) #else diff --git a/src/sentry_core.h b/src/sentry_core.h index b2ac8ae07..bfbb0fff0 100644 --- a/src/sentry_core.h +++ b/src/sentry_core.h @@ -162,8 +162,11 @@ bool sentry__should_send_transaction( sentry_value_t tx_ctx, sentry_sampling_context_t *sampling_ctx); #endif -#if defined(SENTRY_PLATFORM_NX) || defined(SENTRY_PLATFORM_PS) \ - || defined(SENTRY_PLATFORM_XBOX) +// TODO: remove sentry__native_init after console SDKs have been migrated to +// platform integrations +#if (defined(SENTRY_PLATFORM_NX) || defined(SENTRY_PLATFORM_PS) \ + || defined(SENTRY_PLATFORM_XBOX)) \ + && !defined(SENTRY_INTEGRATION_PLATFORM) int sentry__native_init(sentry_options_t *options); #endif diff --git a/src/sentry_integration.h b/src/sentry_integration.h index b02e74d9b..2bcfd52f2 100644 --- a/src/sentry_integration.h +++ b/src/sentry_integration.h @@ -14,4 +14,16 @@ typedef struct sentry_integration_s { void (*free_func)(void *data); } sentry_integration_t; +#ifdef SENTRY_INTEGRATION_PLATFORM +# ifdef __cplusplus +extern "C" { +# endif + +sentry_integration_t *sentry_integration_platform_new(void); + +# ifdef __cplusplus +} +# endif +#endif + #endif diff --git a/src/sentry_options.c b/src/sentry_options.c index 1710ca6e1..3b0cdb828 100644 --- a/src/sentry_options.c +++ b/src/sentry_options.c @@ -130,6 +130,13 @@ sentry_options_new(void) opts->http_retry = false; opts->send_client_reports = true; opts->enable_large_attachments = false; +#ifdef SENTRY_INTEGRATION_PLATFORM + if (!sentry__options_add_integration( + opts, sentry_integration_platform_new())) { + sentry_options_free(opts); + return NULL; + } +#endif #ifdef SENTRY_INTEGRATION_QT sentry__options_add_integration(opts, sentry_integration_qt_new()); #endif @@ -980,12 +987,12 @@ sentry_options_set_backend(sentry_options_t *opts, sentry_backend_t *backend) opts->backend = backend; } -void +bool sentry__options_add_integration( sentry_options_t *opts, sentry_integration_t *integration) { if (!integration) { - return; + return false; } size_t new_count = opts->num_integrations + 1; @@ -993,7 +1000,7 @@ sentry__options_add_integration( = sentry__calloc(new_count, sizeof(sentry_integration_t *)); if (!integrations) { free_integration(integration); - return; + return false; } for (size_t i = 0; i < opts->num_integrations; i++) { @@ -1003,6 +1010,7 @@ sentry__options_add_integration( sentry_free(opts->integrations); opts->integrations = integrations; opts->num_integrations = new_count; + return true; } bool diff --git a/src/sentry_options.h b/src/sentry_options.h index dcdfeec72..280016a44 100644 --- a/src/sentry_options.h +++ b/src/sentry_options.h @@ -137,8 +137,10 @@ const char *sentry__options_get_org_id(const sentry_options_t *options); * * Takes ownership of `integration`. If the integration owns `data`, it must * provide `free_func`. + * + * Returns true if the integration was added. */ -void sentry__options_add_integration( +bool sentry__options_add_integration( sentry_options_t *opts, sentry_integration_t *integration); /** diff --git a/tests/cmake.py b/tests/cmake.py index fd98a0101..8b4bdcd5f 100644 --- a/tests/cmake.py +++ b/tests/cmake.py @@ -96,6 +96,7 @@ def destroy(self): lib_name("sentry"), exe_name("sentry-crash"), exe_name("sentry_early_init"), + exe_name("sentry_test_platform"), ] cmd = [ os.environ.get("LLVM_COV", "llvm-cov"), diff --git a/tests/fixtures/test_platform/CMakeLists.txt b/tests/fixtures/test_platform/CMakeLists.txt new file mode 100644 index 000000000..89d94d443 --- /dev/null +++ b/tests/fixtures/test_platform/CMakeLists.txt @@ -0,0 +1,9 @@ +cmake_minimum_required(VERSION 3.10) +project(sentry_test_platform LANGUAGES C) + +target_compile_definitions(sentry PRIVATE + SENTRY_SDK_NAME="sentry.native.test") + +add_executable(sentry_test_platform test_platform.c) +target_include_directories(sentry_test_platform PRIVATE ${SENTRY_SOURCE_DIR}/src) +target_link_libraries(sentry_test_platform PRIVATE sentry) diff --git a/tests/fixtures/test_platform/test_platform.c b/tests/fixtures/test_platform/test_platform.c new file mode 100644 index 000000000..f9da671fc --- /dev/null +++ b/tests/fixtures/test_platform/test_platform.c @@ -0,0 +1,46 @@ +#include "sentry_alloc.h" +#include "sentry_integration.h" +#include "sentry_scope.h" + +static void +register_platform( + void *data, sentry_scope_t *scope, const sentry_options_t *options) +{ + (void)data; + (void)options; + + sentry_value_t device = sentry_value_new_object(); + sentry_value_set_by_key(device, "name", sentry_value_new_string("Test")); + sentry_value_set_by_key( + device, "model", sentry_value_new_string("test-model")); + sentry_value_set_by_key( + device, "arch", sentry_value_new_string("test-arch")); + sentry_scope_set_context(scope, "device", device); +} + +sentry_integration_t * +sentry_integration_platform_new(void) +{ + sentry_integration_t *integration = SENTRY_MAKE(sentry_integration_t); + integration->name = "test"; + integration->register_func = register_platform; + return integration; +} + +int +main(void) +{ + sentry_options_t *options = sentry_options_new(); + sentry_options_set_auto_session_tracking(options, 0); + sentry_options_set_debug(options, true); + sentry_init(options); + + sentry_set_tag("my-tag", "my-value"); + sentry_set_user(sentry_value_new_user("123", "my-user", NULL, NULL)); + + sentry_value_t event = sentry_value_new_message_event( + SENTRY_LEVEL_INFO, "my-logger", "Hello World!"); + sentry_capture_event(event); + + sentry_close(); +} diff --git a/tests/test_integration_platform.py b/tests/test_integration_platform.py new file mode 100644 index 000000000..ae4860e50 --- /dev/null +++ b/tests/test_integration_platform.py @@ -0,0 +1,63 @@ +import os + +import pytest + +from . import Envelope, SENTRY_VERSION, make_dsn, run +from .conditions import has_http + +pytestmark = pytest.mark.skipif(not has_http, reason="tests need http transport") + + +def test_platform_integration(cmake, httpserver): + cwd = cmake( + ["sentry_test_platform"], + { + "SENTRY_BACKEND": "none", + "SENTRY_BUILD_SHARED_LIBS": "OFF", + "SENTRY_INTEGRATION_PLATFORM": "ON", + }, + ) + + httpserver.expect_oneshot_request("/api/123456/envelope/").respond_with_data("OK") + + run( + cwd, + "sentry_test_platform", + [], + env=dict(os.environ, SENTRY_DSN=make_dsn(httpserver)), + ) + + assert len(httpserver.log) == 1 + envelope = Envelope.deserialize(httpserver.log[0][0].get_data()) + + (item,) = envelope.items + assert item.headers["type"] == "event" + event = item.payload.json + + # SDK + assert event["platform"] == "native" + assert event["environment"] == "production" + assert event["event_id"] + assert event["contexts"]["os"]["name"] + assert len(event["contexts"]["trace"]["trace_id"]) == 32 + assert len(event["contexts"]["trace"]["span_id"]) == 16 + assert event["sdk"]["version"] == SENTRY_VERSION + assert event["sdk"]["packages"] == [ + { + "name": "github:getsentry/sentry-native", + "version": SENTRY_VERSION, + } + ] + + # platform integration + assert event["contexts"]["device"] == { + "name": "Test", + "model": "test-model", + "arch": "test-arch", + } + assert event["sdk"]["name"] == "sentry.native.test" + assert event["sdk"]["integrations"].count("test") == 1 + + # app + assert event["tags"] == {"my-tag": "my-value"} + assert event["user"] == {"id": "123", "username": "my-user"} diff --git a/tests/unit/test_basic.c b/tests/unit/test_basic.c index b08f2551a..0c2fd735b 100644 --- a/tests/unit/test_basic.c +++ b/tests/unit/test_basic.c @@ -419,7 +419,7 @@ SENTRY_TEST(client_sdk_integrations) sentry_integration_t *integration = SENTRY_MAKE(sentry_integration_t); TEST_ASSERT(!!integration); integration->name = "custom"; - sentry__options_add_integration(options, integration); + TEST_ASSERT(sentry__options_add_integration(options, integration)); sentry_init(options); diff --git a/tests/unit/test_scope.c b/tests/unit/test_scope.c index e21e79e87..0ec6d1223 100644 --- a/tests/unit/test_scope.c +++ b/tests/unit/test_scope.c @@ -1685,13 +1685,16 @@ SENTRY_TEST(scope_observer_null) sentry_init(options); SENTRY_WITH_SCOPE_MUT (scope) { + size_t initial_count = scope->num_observers; + sentry_scope_observer_t **initial_observers = scope->observers; + TEST_CHECK(!sentry__scope_add_observer(scope, NULL)); - TEST_CHECK_INT_EQUAL(scope->num_observers, 0); - TEST_CHECK(scope->observers == NULL); + TEST_CHECK_INT_EQUAL(scope->num_observers, initial_count); + TEST_CHECK(scope->observers == initial_observers); sentry__scope_remove_observer(scope, NULL); - TEST_CHECK_INT_EQUAL(scope->num_observers, 0); - TEST_CHECK(scope->observers == NULL); + TEST_CHECK_INT_EQUAL(scope->num_observers, initial_count); + TEST_CHECK(scope->observers == initial_observers); } test_observer_data_t d = { .tags = sentry_value_new_null() }; @@ -1730,7 +1733,9 @@ SENTRY_TEST(scope_observer_multiple) observer2->data = &d2; observer2->set_tag = observe_set_tag; + size_t initial_count = 0; SENTRY_WITH_SCOPE_MUT (scope) { + initial_count = scope->num_observers; TEST_CHECK(sentry__scope_add_observer(scope, observer1)); TEST_CHECK(sentry__scope_add_observer(scope, observer2)); } @@ -1749,7 +1754,7 @@ SENTRY_TEST(scope_observer_multiple) d2.was_called = false; SENTRY_WITH_SCOPE_MUT (scope) { sentry__scope_remove_observer(scope, observer2); - TEST_CHECK_INT_EQUAL(scope->num_observers, 1); + TEST_CHECK_INT_EQUAL(scope->num_observers, initial_count + 1); TEST_CHECK(scope->observers != NULL); } @@ -1762,8 +1767,8 @@ SENTRY_TEST(scope_observer_multiple) SENTRY_WITH_SCOPE_MUT (scope) { sentry__scope_remove_observer(scope, observer1); - TEST_CHECK_INT_EQUAL(scope->num_observers, 0); - TEST_CHECK(scope->observers == NULL); + TEST_CHECK_INT_EQUAL(scope->num_observers, initial_count); + TEST_CHECK((scope->observers == NULL) == (initial_count == 0)); } sentry_value_decref(d1.tags); @@ -1797,7 +1802,9 @@ SENTRY_TEST(scope_observer_mutate) observer3->data = &d3; observer3->set_tag = observe_set_tag; + size_t initial_count; SENTRY_WITH_SCOPE_MUT (scope) { + initial_count = scope->num_observers; TEST_CHECK(sentry__scope_add_observer(scope, observer1)); TEST_CHECK(sentry__scope_add_observer(scope, observer2)); } @@ -1828,14 +1835,14 @@ SENTRY_TEST(scope_observer_mutate) SENTRY_WITH_SCOPE_MUT (scope) { TEST_CHECK(sentry__scope_add_observer(scope, observer4)); - TEST_CHECK_INT_EQUAL(scope->num_observers, 1); + TEST_CHECK_INT_EQUAL(scope->num_observers, initial_count + 1); } sentry_set_tag("self", "remove"); TEST_CHECK(d4.was_called); SENTRY_WITH_SCOPE_MUT (scope) { - TEST_CHECK_INT_EQUAL(scope->num_observers, 0); - TEST_CHECK(scope->observers == NULL); + TEST_CHECK_INT_EQUAL(scope->num_observers, initial_count); + TEST_CHECK((scope->observers == NULL) == (initial_count == 0)); } sentry_value_decref(d1.tags);