diff --git a/CHANGELOG.md b/CHANGELOG.md index 977fb9797f..538272b08e 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,3 +1,49 @@ +# 1.15.0 / 2022-11-09 + +* [FEATURE] RUM: Add frustration signal 'Error Tap'. See [#1006](https://github.com/DataDog/dd-sdk-android/pull/1006) +* [FEATURE] RUM: Report frustration count on views. See [#1030](https://github.com/DataDog/dd-sdk-android/pull/1030) +* [FEATURE] RUM: Add API to enable/disable tracking of frustration signals. See [#1085](https://github.com/DataDog/dd-sdk-android/pull/1085) +* [FEATURE] RUM: Create internal API for sending technical performance metrics. See [#1083](https://github.com/DataDog/dd-sdk-android/pull/1083) +* [FEATURE] RUM: Configuration Telemetry. See [#1118](https://github.com/DataDog/dd-sdk-android/pull/1118) +* [IMPROVEMENT] Internal: Add internal DNS resolver. See [#991](https://github.com/DataDog/dd-sdk-android/pull/991) +* [IMPROVEMENT] RUM: Support sending CPU architecture as part of device info. See [#1000](https://github.com/DataDog/dd-sdk-android/pull/1000) +* [IMPROVEMENT] Internal: Add checks on intake request headers. See [#1005](https://github.com/DataDog/dd-sdk-android/pull/1005) +* [IMPROVEMENT] RUM: Enable custom application version support. See [#1020](https://github.com/DataDog/dd-sdk-android/pull/1020) +* [IMPROVEMENT] RUM: Add configuration method to disable action tracking. See [#1023](https://github.com/DataDog/dd-sdk-android/pull/1023) +* [IMPROVEMENT] Global: Minor performance optimization during serialization into JSON format. See [#1066](https://github.com/DataDog/dd-sdk-android/pull/1066) +* [IMPROVEMENT] Global: Editable additional attributes. See [#1089](https://github.com/DataDog/dd-sdk-android/pull/1089) +* [IMPROVEMENT] RUM: Add tracing sampling attribute. See [#1092](https://github.com/DataDog/dd-sdk-android/pull/1092) +* [IMPROVEMENT] RUM: Invert frame time to get js refresh rate. See [#1105](https://github.com/DataDog/dd-sdk-android/pull/1105) +* [IMPROVEMENT] Global: Target Android 13. See [#1130](https://github.com/DataDog/dd-sdk-android/pull/1130) +* [BUGFIX] Internal: Fix buttons overlap in the sample app. See [#1004](https://github.com/DataDog/dd-sdk-android/pull/1004) +* [BUGFIX] Global: Prevent crash on peekBody. See [#1080](https://github.com/DataDog/dd-sdk-android/pull/1080) +* [BUGFIX] Shutdown Kronos clock only after executors. See [#1127](https://github.com/DataDog/dd-sdk-android/pull/1127) +* [DOCS] Android and Android TV Monitoring Formatting Edit. See [#966](https://github.com/DataDog/dd-sdk-android/pull/966) +* [DOCS] Update statement about default view tracking strategy in the docs. See [#1003](https://github.com/DataDog/dd-sdk-android/pull/1003) +* [DOCS] Android Monitoring Doc Edit. See [#1010](https://github.com/DataDog/dd-sdk-android/pull/1010) +* [DOCS] Adds Secondary Doc Reviewer. See [#1011](https://github.com/DataDog/dd-sdk-android/pull/1011) +* [DOCS] Replace references to iOS classes with correct Android equivalents. See [#1012](https://github.com/DataDog/dd-sdk-android/pull/1012) +* [DOCS] Add setVitalsUpdateFrequency to the doc. See [#1015](https://github.com/DataDog/dd-sdk-android/pull/1015) +* [DOCS] Android Integrated Libraries Update. See [#1021](https://github.com/DataDog/dd-sdk-android/pull/1021) +* [DOCS] Sync Doc Changes From Master to Develop. See [#1024](https://github.com/DataDog/dd-sdk-android/pull/1024) +* [MAINTENANCE] CI: Use a gitlab template for analysis generated files check. See [#1016](https://github.com/DataDog/dd-sdk-android/pull/1016) +* [MAINTENANCE] Update Cmake to 3.22.1. See [#1032](https://github.com/DataDog/dd-sdk-android/pull/1032) +* [MAINTENANCE] Update CODEOWNERS. See [#1045](https://github.com/DataDog/dd-sdk-android/pull/1045) +* [MAINTENANCE] Update AGP to 7.3.0. See [#1060](https://github.com/DataDog/dd-sdk-android/pull/1060) +* [MAINTENANCE] CI: Use KtLint 0.45.1 and dedicated runner image. See [#1081](https://github.com/DataDog/dd-sdk-android/pull/1081) +* [MAINTENANCE] CI: Migrate ktlint CI job to shared gitlab template. See [#1084](https://github.com/DataDog/dd-sdk-android/pull/1084) +* [MAINTENANCE] Update ktlint to 0.47.1. See [#1091](https://github.com/DataDog/dd-sdk-android/pull/1091) +* [MAINTENANCE] Publish SNAPSHOT builds to sonatype on pushes to develop. See [#1093](https://github.com/DataDog/dd-sdk-android/pull/1093) +* [MAINTENANCE] Add version to top level project for nexusPublishing extension. See [#1096](https://github.com/DataDog/dd-sdk-android/pull/1096) +* [MAINTENANCE] Fix import ordering. See [#1098](https://github.com/DataDog/dd-sdk-android/pull/1098) +* [MAINTENANCE] Deprecate DatadogPlugin class and its usage. See [#1100](https://github.com/DataDog/dd-sdk-android/pull/1100) +* [MAINTENANCE] CI: Update .gitlab-ci.yml to use release image for static-analysis job. See [#1102](https://github.com/DataDog/dd-sdk-android/pull/1102) +* [MAINTENANCE] Suppress DatadogPlugin deprecation for instrumented tests. See [#1114](https://github.com/DataDog/dd-sdk-android/pull/1114) +* [MAINTENANCE] Remove Flutter from Dogfooding scripts. See [#1120](https://github.com/DataDog/dd-sdk-android/pull/1120) +* [MAINTENANCE] Fix flaky ANR detection test. See [#1123](https://github.com/DataDog/dd-sdk-android/pull/1123) +* [MAINTENANCE] Android Gradle Plugin 7.3.1. See [#1124](https://github.com/DataDog/dd-sdk-android/pull/1124) +* [MAINTENANCE] Filter out telemetry in the assertions of instrumented RUM tests. See [#1131](https://github.com/DataDog/dd-sdk-android/pull/1131) + # 1.14.1 / 2022-09-27 * [IMPROVEMENT] Global: Add CPU architecture to the collected device information. See [#1000](https://github.com/DataDog/dd-sdk-android/pull/1000) diff --git a/Dockerfile.gitlab b/Dockerfile.gitlab index fca26af169..0ecdafdabc 100644 --- a/Dockerfile.gitlab +++ b/Dockerfile.gitlab @@ -25,16 +25,11 @@ RUN set -x \ && rm -rf /var/lib/apt/lists/* ENV GRADLE_VERSION 7.5 -ENV ANDROID_COMPILE_SDK 31 -ENV ANDROID_BUILD_TOOLS 31.0.0 -ENV ANDROID_SDK_TOOLS 7583922 -ENV NDK_VERSION 22.1.7171670 +ENV ANDROID_COMPILE_SDK 33 +ENV ANDROID_BUILD_TOOLS 33.0.0 +ENV ANDROID_SDK_TOOLS 8512546 +ENV NDK_VERSION 25.1.8937393 ENV CMAKE_VERSION 3.22.1 -# Docker image is shared between branches and some older versions (1.14 and below) need to have CMAKE 3.10 -# Can be removed at some point -ENV CMAKE_VERSION_LEGACY 3.10.2.4988404 - - RUN apt update && apt install -y python3 @@ -73,7 +68,6 @@ RUN \ echo y | android-sdk-linux/cmdline-tools/latest/bin/sdkmanager "build-tools;${ANDROID_BUILD_TOOLS}" >/dev/null && \ echo y | android-sdk-linux/cmdline-tools/latest/bin/sdkmanager --install "ndk;${NDK_VERSION}" >/dev/null && \ echo y | android-sdk-linux/cmdline-tools/latest/bin/sdkmanager --install "cmake;${CMAKE_VERSION}" >/dev/null && \ - echo y | android-sdk-linux/cmdline-tools/latest/bin/sdkmanager --install "cmake;${CMAKE_VERSION_LEGACY}" >/dev/null && \ yes | android-sdk-linux/cmdline-tools/latest/bin/sdkmanager --licenses RUN set -x \ diff --git a/buildSrc/src/main/kotlin/com/datadog/gradle/Dependencies.kt b/buildSrc/src/main/kotlin/com/datadog/gradle/Dependencies.kt index da2bbcb3d1..a0a2bce420 100644 --- a/buildSrc/src/main/kotlin/com/datadog/gradle/Dependencies.kt +++ b/buildSrc/src/main/kotlin/com/datadog/gradle/Dependencies.kt @@ -11,7 +11,7 @@ object Dependencies { object Versions { // NDK - const val Ndk = "22.1.7171670" + const val Ndk = "25.1.8937393" const val CMake = "3.22.1" } diff --git a/buildSrc/src/main/kotlin/com/datadog/gradle/config/AndroidConfig.kt b/buildSrc/src/main/kotlin/com/datadog/gradle/config/AndroidConfig.kt index 38d17bf2ed..a7d6ceaefa 100644 --- a/buildSrc/src/main/kotlin/com/datadog/gradle/config/AndroidConfig.kt +++ b/buildSrc/src/main/kotlin/com/datadog/gradle/config/AndroidConfig.kt @@ -11,14 +11,14 @@ import com.datadog.gradle.utils.Version object AndroidConfig { - const val TARGET_SDK = 31 + const val TARGET_SDK = 33 const val MIN_SDK = 19 // this is temporary, until we bump min sdk. Compose requires min sdk 21. const val MIN_SDK_FOR_COMPOSE = 21 - const val BUILD_TOOLS_VERSION = "31.0.0" + const val BUILD_TOOLS_VERSION = "33.0.0" - val VERSION = Version(1, 15, 0, Version.Type.Snapshot) + val VERSION = Version(1, 16, 0, Version.Type.Snapshot) } @Suppress("UnstableApiUsage") diff --git a/buildSrc/src/main/kotlin/com/datadog/gradle/plugin/jsonschema/JsonDefinition.kt b/buildSrc/src/main/kotlin/com/datadog/gradle/plugin/jsonschema/JsonDefinition.kt index 175fd32a79..79df5f48c8 100644 --- a/buildSrc/src/main/kotlin/com/datadog/gradle/plugin/jsonschema/JsonDefinition.kt +++ b/buildSrc/src/main/kotlin/com/datadog/gradle/plugin/jsonschema/JsonDefinition.kt @@ -21,6 +21,7 @@ data class JsonDefinition( @SerializedName("items") val items: JsonDefinition?, @SerializedName("allOf") val allOf: List?, @SerializedName("oneOf") val oneOf: List?, + @SerializedName("anyOf") val anyOf: List?, @SerializedName("properties") val properties: Map?, @SerializedName("definitions") val definitions: Map?, @SerializedName("readOnly") val readOnly: Boolean?, @@ -42,6 +43,7 @@ data class JsonDefinition( items = null, allOf = null, oneOf = null, + anyOf = null, properties = null, definitions = null, readOnly = null, @@ -62,6 +64,7 @@ data class JsonDefinition( items = null, allOf = null, oneOf = null, + anyOf = null, properties = null, definitions = null, readOnly = null, diff --git a/buildSrc/src/main/kotlin/com/datadog/gradle/plugin/jsonschema/JsonSchemaReader.kt b/buildSrc/src/main/kotlin/com/datadog/gradle/plugin/jsonschema/JsonSchemaReader.kt index db0d2e72f4..69fdef4d9a 100644 --- a/buildSrc/src/main/kotlin/com/datadog/gradle/plugin/jsonschema/JsonSchemaReader.kt +++ b/buildSrc/src/main/kotlin/com/datadog/gradle/plugin/jsonschema/JsonSchemaReader.kt @@ -272,6 +272,9 @@ class JsonSchemaReader( generateTypeAllOf(typeName, definition.allOf, fromFile) } else if (!definition.oneOf.isNullOrEmpty()) { generateTypeOneOf(typeName, definition.oneOf, definition.description, fromFile) + } else if (!definition.anyOf.isNullOrEmpty()) { + // for now let's consider anyOf to be equivalent to oneOf + generateTypeOneOf(typeName, definition.anyOf, definition.description, fromFile) } else if (!definition.ref.isNullOrBlank()) { val refDefinition = findDefinitionReference(definition.ref, fromFile) if (refDefinition != null) { @@ -304,9 +307,10 @@ class JsonSchemaReader( TypeDefinition.Null(description.orEmpty()) } else if (options.size == 1) { options.first() - } else if (asArray != null && options.all { it == asArray || it == asArray.items }) { - // we're in a case with `oneOf(, Array)` + } else if (asArray != null) { + // we're (probably) in a case with `oneOf(, Array)` // because we can't make a type matching both, we simplify it to be always an array + logger.warn("Simplifying a 'oneOf' constraint to $asArray") asArray } else if (options.all { it is TypeDefinition.Class }) { TypeDefinition.OneOfClass( diff --git a/buildSrc/src/main/kotlin/com/datadog/gradle/plugin/jsonschema/JsonType.kt b/buildSrc/src/main/kotlin/com/datadog/gradle/plugin/jsonschema/JsonType.kt index c295044845..a608583f42 100644 --- a/buildSrc/src/main/kotlin/com/datadog/gradle/plugin/jsonschema/JsonType.kt +++ b/buildSrc/src/main/kotlin/com/datadog/gradle/plugin/jsonschema/JsonType.kt @@ -28,5 +28,17 @@ enum class JsonType { STRING, @SerializedName("integer") - INTEGER + INTEGER; + + fun asJsonPrimitiveType(): JsonPrimitiveType? { + return when (this) { + BOOLEAN -> JsonPrimitiveType.BOOLEAN + NUMBER -> JsonPrimitiveType.NUMBER + STRING -> JsonPrimitiveType.STRING + INTEGER -> JsonPrimitiveType.INTEGER + ARRAY, + OBJECT, + NULL -> null + } + } } diff --git a/dd-sdk-android-compose/src/main/kotlin/com/datadog/android/compose/Navigation.kt b/dd-sdk-android-compose/src/main/kotlin/com/datadog/android/compose/Navigation.kt index b1a6eaa836..b9096b79f8 100644 --- a/dd-sdk-android-compose/src/main/kotlin/com/datadog/android/compose/Navigation.kt +++ b/dd-sdk-android-compose/src/main/kotlin/com/datadog/android/compose/Navigation.kt @@ -86,6 +86,9 @@ internal class ComposeNavigationObserver( val attributes = mutableMapOf() bundle.keySet().forEach { + // TODO RUMM-2717 Bundle#get is deprecated, but there is no replacement for it. + // Issue is opened in the Google Issue Tracker. + @Suppress("DEPRECATION") attributes["$ARGUMENT_TAG.$it"] = bundle.get(it) } diff --git a/dd-sdk-android/README.md b/dd-sdk-android/README.md index bfa7310abf..289bc926fb 100644 --- a/dd-sdk-android/README.md +++ b/dd-sdk-android/README.md @@ -19,25 +19,21 @@ in Datadog. **Make sure you create a key of type `Client Token`.** ```kotlin class SampleApplication : Application() { - - override fun onCreate() { - super.onCreate() - Datadog.initialize(this, BuildConfig.DD_CLIENT_TOKEN) - } -} -``` - -### Setup for Europe - -If you're targeting our [Europe servers](https://datadoghq.eu), you can -initialize the library like this: - -```kotlin -class SampleApplication : Application() { - override fun onCreate() { super.onCreate() - Datadog.initialize(this, BuildConfig.DD_CLIENT_TOKEN, Datadog.DATADOG_EU) + val configuration = Configuration.Builder( + logsEnabled = true, + tracesEnabled = true, + crashReportsEnabled = true, + rumEnabled = true + ) + .useSite(DatadogSite.US1) // replace with the site you're targetting (e.g.: US3, EU1, …) + .trackInteractions() + .trackLongTasks(durationThreshold) + .useViewTrackingStrategy(strategy) + .build() + val credentials = Credentials(CLIENT_TOKEN, ENV_NAME, APP_VARIANT_NAME, APPLICATION_ID) + Datadog.initialize(this, credentials, configuration, trackingConsent) } } ``` diff --git a/dd-sdk-android/apiSurface b/dd-sdk-android/apiSurface index 90f5fa4278..29ff0534c1 100644 --- a/dd-sdk-android/apiSurface +++ b/dd-sdk-android/apiSurface @@ -1574,8 +1574,68 @@ class com.datadog.android.sqlite.DatadogDatabaseErrorHandler : android.database. constructor(android.database.DatabaseErrorHandler = DefaultDatabaseErrorHandler()) override fun onCorruption(android.database.sqlite.SQLiteDatabase) companion object +data class com.datadog.android.telemetry.model.TelemetryConfigurationEvent + constructor(Dd, kotlin.Long, kotlin.String, Source, kotlin.String, Application? = null, Session? = null, View? = null, Action? = null, kotlin.collections.List? = null, Telemetry) + val type: kotlin.String + fun toJson(): com.google.gson.JsonElement + companion object + fun fromJson(kotlin.String): TelemetryConfigurationEvent + class Dd + constructor() + val formatVersion: kotlin.Long + fun toJson(): com.google.gson.JsonElement + data class Application + constructor(kotlin.String) + fun toJson(): com.google.gson.JsonElement + companion object + fun fromJson(kotlin.String): Application + data class Session + constructor(kotlin.String) + fun toJson(): com.google.gson.JsonElement + companion object + fun fromJson(kotlin.String): Session + data class View + constructor(kotlin.String) + fun toJson(): com.google.gson.JsonElement + companion object + fun fromJson(kotlin.String): View + data class Action + constructor(kotlin.String) + fun toJson(): com.google.gson.JsonElement + companion object + fun fromJson(kotlin.String): Action + data class Telemetry + constructor(Configuration) + val type: kotlin.String + fun toJson(): com.google.gson.JsonElement + companion object + fun fromJson(kotlin.String): Telemetry + data class Configuration + constructor(kotlin.Long? = null, kotlin.Long? = null, kotlin.Long? = null, kotlin.Long? = null, kotlin.Long? = null, kotlin.Long? = null, kotlin.Long? = null, kotlin.Boolean? = null, kotlin.Boolean? = null, kotlin.Boolean? = null, kotlin.Boolean? = null, kotlin.Boolean? = null, kotlin.Boolean? = null, kotlin.String? = null, kotlin.Boolean? = null, kotlin.String? = null, kotlin.Boolean? = null, kotlin.Boolean? = null, kotlin.Boolean? = null, kotlin.Boolean? = null, kotlin.Boolean? = null, kotlin.collections.List? = null, kotlin.collections.List? = null, kotlin.Boolean? = null, ViewTrackingStrategy? = null, kotlin.Boolean? = null, kotlin.Long? = null, kotlin.Boolean? = null, kotlin.Boolean? = null, kotlin.Boolean? = null, kotlin.Boolean? = null, kotlin.Boolean? = null, kotlin.Boolean? = null, kotlin.Boolean? = null, kotlin.Boolean? = null, kotlin.Boolean? = null, kotlin.String? = null, kotlin.Boolean? = null, kotlin.Long? = null, kotlin.Long? = null) + fun toJson(): com.google.gson.JsonElement + companion object + fun fromJson(kotlin.String): Configuration + enum Source + constructor(kotlin.String) + - ANDROID + - IOS + - BROWSER + - FLUTTER + - REACT_NATIVE + fun toJson(): com.google.gson.JsonElement + companion object + fun fromJson(kotlin.String): Source + enum ViewTrackingStrategy + constructor(kotlin.String) + - ACTIVITYVIEWTRACKINGSTRATEGY + - FRAGMENTVIEWTRACKINGSTRATEGY + - MIXEDVIEWTRACKINGSTRATEGY + - NAVIGATIONVIEWTRACKINGSTRATEGY + fun toJson(): com.google.gson.JsonElement + companion object + fun fromJson(kotlin.String): ViewTrackingStrategy data class com.datadog.android.telemetry.model.TelemetryDebugEvent - constructor(Dd, kotlin.Long, kotlin.String, Source, kotlin.String, Application? = null, Session? = null, View? = null, Action? = null, Telemetry) + constructor(Dd, kotlin.Long, kotlin.String, Source, kotlin.String, Application? = null, Session? = null, View? = null, Action? = null, kotlin.collections.List? = null, Telemetry) val type: kotlin.String fun toJson(): com.google.gson.JsonElement companion object @@ -1606,6 +1666,7 @@ data class com.datadog.android.telemetry.model.TelemetryDebugEvent fun fromJson(kotlin.String): Action data class Telemetry constructor(kotlin.String) + val type: kotlin.String val status: kotlin.String fun toJson(): com.google.gson.JsonElement companion object @@ -1621,7 +1682,7 @@ data class com.datadog.android.telemetry.model.TelemetryDebugEvent companion object fun fromJson(kotlin.String): Source data class com.datadog.android.telemetry.model.TelemetryErrorEvent - constructor(Dd, kotlin.Long, kotlin.String, Source, kotlin.String, Application? = null, Session? = null, View? = null, Action? = null, Telemetry) + constructor(Dd, kotlin.Long, kotlin.String, Source, kotlin.String, Application? = null, Session? = null, View? = null, Action? = null, kotlin.collections.List? = null, Telemetry) val type: kotlin.String fun toJson(): com.google.gson.JsonElement companion object @@ -1652,6 +1713,7 @@ data class com.datadog.android.telemetry.model.TelemetryErrorEvent fun fromJson(kotlin.String): Action data class Telemetry constructor(kotlin.String, Error? = null) + val type: kotlin.String val status: kotlin.String fun toJson(): com.google.gson.JsonElement companion object diff --git a/dd-sdk-android/src/main/json/telemetry/_common-schema.json b/dd-sdk-android/src/main/json/telemetry/_common-schema.json index 28aa8a6d87..1bf8f0e0f7 100644 --- a/dd-sdk-android/src/main/json/telemetry/_common-schema.json +++ b/dd-sdk-android/src/main/json/telemetry/_common-schema.json @@ -1,6 +1,6 @@ { "$schema": "http://json-schema.org/draft-07/schema", - "$id": "_common-schema.json", + "$id": "telemetry/_common-schema.json", "title": "CommonTelemetryProperties", "type": "object", "description": "Schema of common properties of Telemetry events", @@ -92,6 +92,14 @@ "pattern": "^[a-f0-9]{8}-[a-f0-9]{4}-[a-f0-9]{4}-[a-f0-9]{4}-[a-f0-9]{12}$" } } + }, + "experimental_features": { + "type": "array", + "description": "Enabled experimental features", + "items": { + "type": "string" + }, + "readOnly": true } } } diff --git a/dd-sdk-android/src/main/json/telemetry/configuration-schema.json b/dd-sdk-android/src/main/json/telemetry/configuration-schema.json new file mode 100644 index 0000000000..6903b42a3d --- /dev/null +++ b/dd-sdk-android/src/main/json/telemetry/configuration-schema.json @@ -0,0 +1,242 @@ +{ + "$schema": "http://json-schema.org/draft-07/schema", + "$id": "telemetry/configuration-schema.json", + "title": "TelemetryConfigurationEvent", + "type": "object", + "description": "Schema of all properties of a telemetry configuration event", + "allOf": [ + { + "$ref": "_common-schema.json" + }, + { + "required": [ + "telemetry" + ], + "properties": { + "telemetry": { + "type": "object", + "description": "The telemetry configuration information", + "required": [ + "type", + "configuration" + ], + "properties": { + "type": { + "type": "string", + "description": "Telemetry type", + "const": "configuration" + }, + "configuration": { + "type": "object", + "description": "Configuration properties", + "properties": { + "session_sample_rate": { + "type": "integer", + "description": "The percentage of sessions tracked", + "minimum": 0, + "maximum": 100 + }, + "telemetry_sample_rate": { + "type": "integer", + "description": "The percentage of telemetry events sent", + "minimum": 0, + "maximum": 100 + }, + "telemetry_configuration_sample_rate": { + "type": "integer", + "description": "The percentage of telemetry configuration events sent after being sampled by telemetry_sample_rate", + "minimum": 0, + "maximum": 100 + }, + "trace_sample_rate": { + "type": "integer", + "description": "The percentage of requests traced", + "minimum": 0, + "maximum": 100 + }, + "premium_sample_rate": { + "type": "integer", + "description": "The percentage of sessions with Browser RUM & Session Replay pricing tracked (deprecated in favor of session_replay_sample_rate)", + "minimum": 0, + "maximum": 100 + }, + "replay_sample_rate": { + "type": "integer", + "description": "The percentage of sessions with Browser RUM & Session Replay pricing tracked (deprecated in favor of session_replay_sample_rate)", + "minimum": 0, + "maximum": 100 + }, + "session_replay_sample_rate": { + "type": "integer", + "description": "The percentage of sessions with Browser RUM & Session Replay pricing tracked", + "minimum": 0, + "maximum": 100 + }, + "use_proxy": { + "type": "boolean", + "description": "Whether a proxy configured is used" + }, + "use_before_send": { + "type": "boolean", + "description": "Whether beforeSend callback function is used" + }, + "silent_multiple_init": { + "type": "boolean", + "description": "Whether initialization fails silently if the SDK is already initialized" + }, + "track_session_across_subdomains": { + "type": "boolean", + "description": "Whether sessions across subdomains for the same site are tracked" + }, + "use_cross_site_session_cookie": { + "type": "boolean", + "description": "Whether a secure cross-site session cookie is used" + }, + "use_secure_session_cookie": { + "type": "boolean", + "description": "Whether a secure session cookie is used" + }, + "action_name_attribute": { + "type": "string", + "description": "Attribute to be used to name actions" + }, + "use_allowed_tracing_origins": { + "type": "boolean", + "description": "Whether the allowed tracing origins list is used" + }, + "default_privacy_level": { + "type": "string", + "description": "Session replay default privacy level" + }, + "use_excluded_activity_urls": { + "type": "boolean", + "description": "Whether the request origins list to ignore when computing the page activity is used" + }, + "track_frustrations": { + "type": "boolean", + "description": "Whether user frustrations are tracked" + }, + "track_views_manually": { + "type": "boolean", + "description": "Whether the RUM views creation is handled manually" + }, + "track_interactions": { + "type": "boolean", + "description": "Whether user actions are tracked" + }, + "forward_errors_to_logs": { + "type": "boolean", + "description": "Whether console.error logs, uncaught exceptions and network errors are tracked" + }, + "forward_console_logs": { + "anyOf": [ + { + "type": "array", + "items": { + "type": "string" + } + }, + { + "type": "string", + "const": "all" + } + ], + "description": "The console.* tracked" + }, + "forward_reports": { + "anyOf": [ + { + "type": "array", + "items": { + "type": "string" + } + }, + { + "type": "string", + "const": "all" + } + ], + "description": "The reports from the Reporting API tracked" + }, + "use_local_encryption": { + "type": "boolean", + "description": "Whether local encryption is used" + }, + "view_tracking_strategy": { + "type": "string", + "description": "View tracking strategy", + "enum": [ + "ActivityViewTrackingStrategy", + "FragmentViewTrackingStrategy", + "MixedViewTrackingStrategy", + "NavigationViewTrackingStrategy" + ] + }, + "track_background_events": { + "type": "boolean", + "description": "Whether RUM events are tracked when the application is in Background" + }, + "mobile_vitals_update_period": { + "type": "integer", + "description": "The period between each Mobile Vital sample (in milliseconds)" + }, + "track_errors": { + "type": "boolean", + "description": "Whether error monitoring & crash reporting is enabled for the source platform" + }, + "track_network_requests": { + "type": "boolean", + "description": "Whether automatic collection of network requests is enabled" + }, + "use_tracing": { + "type": "boolean", + "description": "Whether tracing features are enabled" + }, + "track_native_views": { + "type": "boolean", + "description": "Whether native views are tracked (for cross platform SDKs)" + }, + "track_native_errors": { + "type": "boolean", + "description": "Whether native error monitoring & crash reporting is enabled (for cross platform SDKs)" + }, + "track_native_long_tasks": { + "type": "boolean", + "description": "Whether long task tracking is performed automatically" + }, + "track_cross_platform_long_tasks": { + "type": "boolean", + "description": "Whether long task tracking is performed automatically for cross platform SDKs" + }, + "use_attach_to_existing": { + "type": "boolean", + "description": "Whether the cross-platform SDK was initialized on top of a pre-existing native SDK instance" + }, + "use_first_party_hosts": { + "type": "boolean", + "description": "Whether the client has provided a list of first party hosts" + }, + "initialization_type": { + "type": "string", + "description": "The type of initialization the SDK used, in case multiple are supported" + }, + "track_flutter_performance": { + "type": "boolean", + "description": "Whether Flutter build and raster time tracking is enabled" + }, + "batch_size": { + "type": "integer", + "description": "The window duration for batches sent by the SDK (in milliseconds)" + }, + "batch_upload_frequency": { + "type": "integer", + "description": "The upload frequency of batches (in milliseconds)" + } + } + } + } + } + } + } + ] +} diff --git a/dd-sdk-android/src/main/json/telemetry/debug-schema.json b/dd-sdk-android/src/main/json/telemetry/debug-schema.json index 2d41fe30bb..03113d60ed 100644 --- a/dd-sdk-android/src/main/json/telemetry/debug-schema.json +++ b/dd-sdk-android/src/main/json/telemetry/debug-schema.json @@ -1,6 +1,6 @@ { "$schema": "http://json-schema.org/draft-07/schema", - "$id": "debug-schema.json", + "$id": "telemetry/debug-schema.json", "title": "TelemetryDebugEvent", "type": "object", "description": "Schema of all properties of a telemetry debug event", @@ -13,9 +13,14 @@ "properties": { "telemetry": { "type": "object", - "description": "The telemetry information", + "description": "The telemetry log information", "required": ["status", "message"], "properties": { + "type": { + "type": "string", + "description": "Telemetry type", + "const": "log" + }, "status": { "type": "string", "description": "Level/severity of the log", diff --git a/dd-sdk-android/src/main/json/telemetry/error-schema.json b/dd-sdk-android/src/main/json/telemetry/error-schema.json index 9cf13a0832..cb0c6ba6d7 100644 --- a/dd-sdk-android/src/main/json/telemetry/error-schema.json +++ b/dd-sdk-android/src/main/json/telemetry/error-schema.json @@ -1,6 +1,6 @@ { "$schema": "http://json-schema.org/draft-07/schema", - "$id": "error-schema.json", + "$id": "telemetry/error-schema.json", "title": "TelemetryErrorEvent", "type": "object", "description": "Schema of all properties of a telemetry error event", @@ -13,9 +13,14 @@ "properties": { "telemetry": { "type": "object", - "description": "The telemetry information", + "description": "The telemetry log information", "required": ["status", "message"], "properties": { + "type": { + "type": "string", + "description": "Telemetry type", + "const": "log" + }, "status": { "type": "string", "description": "Level/severity of the log", diff --git a/dd-sdk-android/src/main/kotlin/com/datadog/android/Datadog.kt b/dd-sdk-android/src/main/kotlin/com/datadog/android/Datadog.kt index ac9959f4a5..78dd8c93d6 100644 --- a/dd-sdk-android/src/main/kotlin/com/datadog/android/Datadog.kt +++ b/dd-sdk-android/src/main/kotlin/com/datadog/android/Datadog.kt @@ -18,6 +18,7 @@ import com.datadog.android.core.internal.CoreFeature import com.datadog.android.core.internal.lifecycle.ProcessLifecycleCallback import com.datadog.android.core.internal.lifecycle.ProcessLifecycleMonitor import com.datadog.android.core.internal.utils.devLogger +import com.datadog.android.core.internal.utils.scheduleSafe import com.datadog.android.core.internal.utils.sdkLogger import com.datadog.android.core.internal.utils.telemetry import com.datadog.android.core.model.UserInfo @@ -26,11 +27,13 @@ import com.datadog.android.log.internal.LogsFeature import com.datadog.android.privacy.TrackingConsent import com.datadog.android.rum.GlobalRum import com.datadog.android.rum.internal.RumFeature +import com.datadog.android.rum.internal.monitor.AdvancedRumMonitor import com.datadog.android.rum.internal.monitor.DatadogRumMonitor import com.datadog.android.tracing.internal.TracingFeature import com.datadog.android.webview.internal.log.WebViewLogsFeature import com.datadog.android.webview.internal.rum.WebViewRumFeature import java.lang.IllegalArgumentException +import java.util.concurrent.TimeUnit import java.util.concurrent.atomic.AtomicBoolean /** @@ -106,25 +109,8 @@ object Datadog { initialized.set(true) - // Issue #154 (“Thread starting during runtime shutdown”) - // Make sure we stop Datadog when the Runtime shuts down - try { - val hookRunnable = Runnable { stop() } - - @Suppress("UnsafeThirdPartyFunctionCall") // NPE cannot happen here - val hook = Thread(hookRunnable, SHUTDOWN_THREAD) - @Suppress("UnsafeThirdPartyFunctionCall") // NPE cannot happen here - Runtime.getRuntime().addShutdownHook(hook) - } catch (e: IllegalStateException) { - // Most probably Runtime is already shutting down - sdkLogger.e("Unable to add shutdown hook, Runtime is already shutting down", e) - stop() - } catch (e: IllegalArgumentException) { - // can only happen if hook is already added, or already running - sdkLogger.e("Shutdown hook was rejected", e) - } catch (e: SecurityException) { - sdkLogger.e("Security Manager denied adding shutdown hook ", e) - } + setupShutdownHook() + sendConfigurationTelemetryEvent(configuration) } /** @@ -401,6 +387,42 @@ object Datadog { return (context.applicationInfo.flags and ApplicationInfo.FLAG_DEBUGGABLE) != 0 } + private fun setupShutdownHook() { + // Issue #154 (“Thread starting during runtime shutdown”) + // Make sure we stop Datadog when the Runtime shuts down + try { + val hookRunnable = Runnable { stop() } + + @Suppress("UnsafeThirdPartyFunctionCall") // NPE cannot happen here + val hook = Thread(hookRunnable, SHUTDOWN_THREAD) + @Suppress("UnsafeThirdPartyFunctionCall") // NPE cannot happen here + Runtime.getRuntime().addShutdownHook(hook) + } catch (e: IllegalStateException) { + // Most probably Runtime is already shutting down + sdkLogger.e("Unable to add shutdown hook, Runtime is already shutting down", e) + stop() + } catch (e: IllegalArgumentException) { + // can only happen if hook is already added, or already running + sdkLogger.e("Shutdown hook was rejected", e) + } catch (e: SecurityException) { + sdkLogger.e("Security Manager denied adding shutdown hook ", e) + } + } + + @Suppress("FunctionMaxLength") + private fun sendConfigurationTelemetryEvent(configuration: Configuration) { + val runnable = Runnable { + val monitor = GlobalRum.get() as? AdvancedRumMonitor + monitor?.sendConfigurationTelemetryEvent(configuration) + } + CoreFeature.uploadExecutorService.scheduleSafe( + "Configuration telemetry", + CONFIGURATION_TELEMETRY_DELAY_MS, + TimeUnit.MILLISECONDS, + runnable + ) + } + // endregion // region Constants @@ -435,5 +457,7 @@ object Datadog { internal const val DD_SDK_VERSION_TAG = "_dd.sdk_version" internal const val DD_APP_VERSION_TAG = "_dd.version" + internal val CONFIGURATION_TELEMETRY_DELAY_MS = TimeUnit.SECONDS.toMillis(5) + // endregion } diff --git a/dd-sdk-android/src/main/kotlin/com/datadog/android/DatadogInterceptor.kt b/dd-sdk-android/src/main/kotlin/com/datadog/android/DatadogInterceptor.kt index e50918e7a3..8699505198 100644 --- a/dd-sdk-android/src/main/kotlin/com/datadog/android/DatadogInterceptor.kt +++ b/dd-sdk-android/src/main/kotlin/com/datadog/android/DatadogInterceptor.kt @@ -157,6 +157,10 @@ internal constructor( localTracerFactory = { AndroidTracer.Builder().build() } ) + init { + GlobalRum.notifyInterceptorInstantiated() + } + // region Interceptor /** @inheritdoc */ diff --git a/dd-sdk-android/src/main/kotlin/com/datadog/android/core/configuration/Configuration.kt b/dd-sdk-android/src/main/kotlin/com/datadog/android/core/configuration/Configuration.kt index 38ea77c310..8eee1c916d 100644 --- a/dd-sdk-android/src/main/kotlin/com/datadog/android/core/configuration/Configuration.kt +++ b/dd-sdk-android/src/main/kotlin/com/datadog/android/core/configuration/Configuration.kt @@ -302,14 +302,18 @@ internal constructor( * Any long running operation on the main thread will appear as Long Tasks in Datadog * RUM Explorer * @param longTaskThresholdMs the threshold in milliseconds above which a task running on - * the Main thread [Looper] is considered as a long task (default 100ms) + * the Main thread [Looper] is considered as a long task (default 100ms). Setting a + * value less than or equal to 0 disables the long task tracking */ @JvmOverloads fun trackLongTasks(longTaskThresholdMs: Long = DEFAULT_LONG_TASK_THRESHOLD_MS): Builder { applyIfFeatureEnabled(PluginFeature.RUM, "trackLongTasks") { - rumConfig = rumConfig.copy( - longTaskTrackingStrategy = MainLooperLongTaskStrategy(longTaskThresholdMs) - ) + val strategy = if (longTaskThresholdMs > 0) { + MainLooperLongTaskStrategy(longTaskThresholdMs) + } else { + null + } + rumConfig = rumConfig.copy(longTaskTrackingStrategy = strategy) } return this } diff --git a/dd-sdk-android/src/main/kotlin/com/datadog/android/core/internal/CoreFeature.kt b/dd-sdk-android/src/main/kotlin/com/datadog/android/core/internal/CoreFeature.kt index 17c0405b79..58a8b10c79 100644 --- a/dd-sdk-android/src/main/kotlin/com/datadog/android/core/internal/CoreFeature.kt +++ b/dd-sdk-android/src/main/kotlin/com/datadog/android/core/internal/CoreFeature.kt @@ -8,6 +8,7 @@ package com.datadog.android.core.internal import android.app.ActivityManager import android.content.Context +import android.content.pm.PackageInfo import android.content.pm.PackageManager import android.os.Build import android.os.Process @@ -201,6 +202,11 @@ internal object CoreFeature { contextRef.clear() trackingConsentProvider.unregisterAllCallbacks() + + cleanupApplicationInfo() + cleanupProviders() + shutDownExecutors() + try { kronosClock.shutdown() } catch (ise: IllegalStateException) { @@ -209,9 +215,6 @@ internal object CoreFeature { sdkLogger.e("Trying to shut down Kronos when it is already not running", ise) } - cleanupApplicationInfo() - cleanupProviders() - shutDownExecutors() initialized.set(false) ndkCrashHandler = NoOpNdkCrashHandler() trackingConsentProvider = NoOpConsentProvider() @@ -305,14 +308,8 @@ internal object CoreFeature { private fun readApplicationInformation(appContext: Context, credentials: Credentials) { packageName = appContext.packageName - val packageInfo = try { - appContext.packageManager.getPackageInfo(packageName, 0) - } catch (e: PackageManager.NameNotFoundException) { - devLogger.e("Unable to read your application's version name", e) - null - } packageVersionProvider = DefaultAppVersionProvider( - packageInfo?.let { + getPackageInfo(appContext)?.let { // we need to use the deprecated method because getLongVersionCode method is only // available from API 28 and above @Suppress("DEPRECATION") @@ -327,6 +324,22 @@ internal object CoreFeature { contextRef = WeakReference(appContext) } + private fun getPackageInfo(appContext: Context): PackageInfo? { + return try { + with(appContext.packageManager) { + if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.TIRAMISU) { + getPackageInfo(packageName, PackageManager.PackageInfoFlags.of(0)) + } else { + @Suppress("DEPRECATION") + getPackageInfo(packageName, 0) + } + } + } catch (e: PackageManager.NameNotFoundException) { + devLogger.e("Unable to read your application's version name", e) + null + } + } + private fun readConfigurationSettings(configuration: Configuration.Core) { batchSize = configuration.batchSize uploadFrequency = configuration.uploadFrequency diff --git a/dd-sdk-android/src/main/kotlin/com/datadog/android/rum/GlobalRum.kt b/dd-sdk-android/src/main/kotlin/com/datadog/android/rum/GlobalRum.kt index b8e80b7216..a0ed1b1a45 100644 --- a/dd-sdk-android/src/main/kotlin/com/datadog/android/rum/GlobalRum.kt +++ b/dd-sdk-android/src/main/kotlin/com/datadog/android/rum/GlobalRum.kt @@ -147,6 +147,10 @@ object GlobalRum { (monitor as? AdvancedRumMonitor)?.sendWebViewEvent() } + internal fun notifyInterceptorInstantiated() { + (monitor as? AdvancedRumMonitor)?.notifyInterceptorInstantiated() + } + internal fun getRumContext(): RumContext { return activeContext.get() } diff --git a/dd-sdk-android/src/main/kotlin/com/datadog/android/rum/internal/domain/event/RumEventMapper.kt b/dd-sdk-android/src/main/kotlin/com/datadog/android/rum/internal/domain/event/RumEventMapper.kt index b7988c4898..6f269ab18e 100644 --- a/dd-sdk-android/src/main/kotlin/com/datadog/android/rum/internal/domain/event/RumEventMapper.kt +++ b/dd-sdk-android/src/main/kotlin/com/datadog/android/rum/internal/domain/event/RumEventMapper.kt @@ -19,6 +19,7 @@ import com.datadog.android.rum.model.ErrorEvent import com.datadog.android.rum.model.LongTaskEvent import com.datadog.android.rum.model.ResourceEvent import com.datadog.android.rum.model.ViewEvent +import com.datadog.android.telemetry.model.TelemetryConfigurationEvent import com.datadog.android.telemetry.model.TelemetryDebugEvent import com.datadog.android.telemetry.model.TelemetryErrorEvent import java.util.Locale @@ -54,7 +55,9 @@ internal data class RumEventMapper( } is ResourceEvent -> resourceEventMapper.map(event) is LongTaskEvent -> longTaskEventMapper.map(event) - is TelemetryDebugEvent, is TelemetryErrorEvent -> event + is TelemetryDebugEvent, + is TelemetryErrorEvent, + is TelemetryConfigurationEvent -> event else -> { sdkLogger.warningWithTelemetry( NO_EVENT_MAPPER_ASSIGNED_WARNING_MESSAGE diff --git a/dd-sdk-android/src/main/kotlin/com/datadog/android/rum/internal/domain/event/RumEventSerializer.kt b/dd-sdk-android/src/main/kotlin/com/datadog/android/rum/internal/domain/event/RumEventSerializer.kt index 126c0999e7..43a67e053e 100644 --- a/dd-sdk-android/src/main/kotlin/com/datadog/android/rum/internal/domain/event/RumEventSerializer.kt +++ b/dd-sdk-android/src/main/kotlin/com/datadog/android/rum/internal/domain/event/RumEventSerializer.kt @@ -15,6 +15,7 @@ import com.datadog.android.rum.model.ErrorEvent import com.datadog.android.rum.model.LongTaskEvent import com.datadog.android.rum.model.ResourceEvent import com.datadog.android.rum.model.ViewEvent +import com.datadog.android.telemetry.model.TelemetryConfigurationEvent import com.datadog.android.telemetry.model.TelemetryDebugEvent import com.datadog.android.telemetry.model.TelemetryErrorEvent import com.google.gson.JsonObject @@ -48,6 +49,9 @@ internal class RumEventSerializer( is TelemetryErrorEvent -> { model.toJson().toString() } + is TelemetryConfigurationEvent -> { + model.toJson().toString() + } is JsonObject -> { model.toString() } diff --git a/dd-sdk-android/src/main/kotlin/com/datadog/android/rum/internal/domain/event/RumEventSourceProvider.kt b/dd-sdk-android/src/main/kotlin/com/datadog/android/rum/internal/domain/event/RumEventSourceProvider.kt index cda4669354..9d7635ee4e 100644 --- a/dd-sdk-android/src/main/kotlin/com/datadog/android/rum/internal/domain/event/RumEventSourceProvider.kt +++ b/dd-sdk-android/src/main/kotlin/com/datadog/android/rum/internal/domain/event/RumEventSourceProvider.kt @@ -12,6 +12,7 @@ import com.datadog.android.rum.model.ErrorEvent import com.datadog.android.rum.model.LongTaskEvent import com.datadog.android.rum.model.ResourceEvent import com.datadog.android.rum.model.ViewEvent +import com.datadog.android.telemetry.model.TelemetryConfigurationEvent import com.datadog.android.telemetry.model.TelemetryDebugEvent import com.datadog.android.telemetry.model.TelemetryErrorEvent import java.util.Locale @@ -81,6 +82,15 @@ internal class RumEventSourceProvider(source: String) { } } + val telemetryConfigurationEventSource: TelemetryConfigurationEvent.Source? by lazy { + try { + TelemetryConfigurationEvent.Source.fromJson(source) + } catch (e: NoSuchElementException) { + devLogger.e(UNKNOWN_SOURCE_WARNING_MESSAGE_FORMAT.format(Locale.US, source), e) + null + } + } + companion object { internal const val UNKNOWN_SOURCE_WARNING_MESSAGE_FORMAT = "You are using an unknown " + "source %s for your events" diff --git a/dd-sdk-android/src/main/kotlin/com/datadog/android/rum/internal/domain/scope/RumRawEvent.kt b/dd-sdk-android/src/main/kotlin/com/datadog/android/rum/internal/domain/scope/RumRawEvent.kt index d336f38a76..cf0dd8305b 100644 --- a/dd-sdk-android/src/main/kotlin/com/datadog/android/rum/internal/domain/scope/RumRawEvent.kt +++ b/dd-sdk-android/src/main/kotlin/com/datadog/android/rum/internal/domain/scope/RumRawEvent.kt @@ -6,6 +6,7 @@ package com.datadog.android.rum.internal.domain.scope +import com.datadog.android.core.configuration.Configuration import com.datadog.android.rum.RumActionType import com.datadog.android.rum.RumErrorSource import com.datadog.android.rum.RumPerformanceMetric @@ -200,6 +201,7 @@ internal sealed class RumRawEvent { val message: String, val stack: String?, val kind: String?, + val configuration: Configuration?, override val eventTime: Time = Time() ) : RumRawEvent() } diff --git a/dd-sdk-android/src/main/kotlin/com/datadog/android/rum/internal/monitor/AdvancedRumMonitor.kt b/dd-sdk-android/src/main/kotlin/com/datadog/android/rum/internal/monitor/AdvancedRumMonitor.kt index 74d228fa23..66d071c8bb 100644 --- a/dd-sdk-android/src/main/kotlin/com/datadog/android/rum/internal/monitor/AdvancedRumMonitor.kt +++ b/dd-sdk-android/src/main/kotlin/com/datadog/android/rum/internal/monitor/AdvancedRumMonitor.kt @@ -6,6 +6,7 @@ package com.datadog.android.rum.internal.monitor +import com.datadog.android.core.configuration.Configuration import com.datadog.android.rum.RumErrorSource import com.datadog.android.rum.RumMonitor import com.datadog.android.rum.RumPerformanceMetric @@ -48,5 +49,10 @@ internal interface AdvancedRumMonitor : RumMonitor { fun sendErrorTelemetryEvent(message: String, stack: String?, kind: String?) + @Suppress("FunctionMaxLength") + fun sendConfigurationTelemetryEvent(configuration: Configuration) + + fun notifyInterceptorInstantiated() + fun updatePerformanceMetric(metric: RumPerformanceMetric, value: Double) } diff --git a/dd-sdk-android/src/main/kotlin/com/datadog/android/rum/internal/monitor/DatadogRumMonitor.kt b/dd-sdk-android/src/main/kotlin/com/datadog/android/rum/internal/monitor/DatadogRumMonitor.kt index d6e8535826..7d376a1ff6 100644 --- a/dd-sdk-android/src/main/kotlin/com/datadog/android/rum/internal/monitor/DatadogRumMonitor.kt +++ b/dd-sdk-android/src/main/kotlin/com/datadog/android/rum/internal/monitor/DatadogRumMonitor.kt @@ -7,6 +7,7 @@ package com.datadog.android.rum.internal.monitor import android.os.Handler +import com.datadog.android.core.configuration.Configuration import com.datadog.android.core.internal.net.FirstPartyHostDetector import com.datadog.android.core.internal.persistence.DataWriter import com.datadog.android.core.internal.system.AndroidInfoProvider @@ -337,17 +338,30 @@ internal class DatadogRumMonitor( } override fun sendDebugTelemetryEvent(message: String) { - handleEvent(RumRawEvent.SendTelemetry(TelemetryType.DEBUG, message, null, null)) + handleEvent(RumRawEvent.SendTelemetry(TelemetryType.DEBUG, message, null, null, null)) } override fun sendErrorTelemetryEvent(message: String, throwable: Throwable?) { val stack: String? = throwable?.loggableStackTrace() val kind: String? = throwable?.javaClass?.canonicalName ?: throwable?.javaClass?.simpleName - handleEvent(RumRawEvent.SendTelemetry(TelemetryType.ERROR, message, stack, kind)) + handleEvent(RumRawEvent.SendTelemetry(TelemetryType.ERROR, message, stack, kind, null)) } override fun sendErrorTelemetryEvent(message: String, stack: String?, kind: String?) { - handleEvent(RumRawEvent.SendTelemetry(TelemetryType.ERROR, message, stack, kind)) + handleEvent(RumRawEvent.SendTelemetry(TelemetryType.ERROR, message, stack, kind, null)) + } + + @Suppress("FunctionMaxLength") + override fun sendConfigurationTelemetryEvent(configuration: Configuration) { + handleEvent( + RumRawEvent.SendTelemetry(TelemetryType.CONFIGURATION, "", null, null, configuration) + ) + } + + override fun notifyInterceptorInstantiated() { + handleEvent( + RumRawEvent.SendTelemetry(TelemetryType.INTERCEPTOR_SETUP, "", null, null, null) + ) } override fun updatePerformanceMetric(metric: RumPerformanceMetric, value: Double) { diff --git a/dd-sdk-android/src/main/kotlin/com/datadog/android/rum/tracking/ActivityLifecycleTrackingStrategy.kt b/dd-sdk-android/src/main/kotlin/com/datadog/android/rum/tracking/ActivityLifecycleTrackingStrategy.kt index 07d7aab2f1..89b386a5a1 100644 --- a/dd-sdk-android/src/main/kotlin/com/datadog/android/rum/tracking/ActivityLifecycleTrackingStrategy.kt +++ b/dd-sdk-android/src/main/kotlin/com/datadog/android/rum/tracking/ActivityLifecycleTrackingStrategy.kt @@ -94,6 +94,9 @@ abstract class ActivityLifecycleTrackingStrategy : intent.extras?.let { bundle -> bundle.keySet().forEach { + // TODO RUMM-2717 Bundle#get is deprecated, but there is no replacement for it. + // Issue is opened in the Google Issue Tracker. + @Suppress("DEPRECATION") attributes["$ARGUMENT_TAG.$it"] = bundle.get(it) } } @@ -111,6 +114,9 @@ abstract class ActivityLifecycleTrackingStrategy : val attributes = mutableMapOf() bundle.keySet().forEach { + // TODO RUMM-2717 Bundle#get is deprecated, but there is no replacement for it. + // Issue is opened in the Google Issue Tracker. + @Suppress("DEPRECATION") attributes["$ARGUMENT_TAG.$it"] = bundle.get(it) } diff --git a/dd-sdk-android/src/main/kotlin/com/datadog/android/telemetry/internal/TelemetryEventHandler.kt b/dd-sdk-android/src/main/kotlin/com/datadog/android/telemetry/internal/TelemetryEventHandler.kt index d75c8f1d73..4097f02d57 100644 --- a/dd-sdk-android/src/main/kotlin/com/datadog/android/telemetry/internal/TelemetryEventHandler.kt +++ b/dd-sdk-android/src/main/kotlin/com/datadog/android/telemetry/internal/TelemetryEventHandler.kt @@ -6,6 +6,7 @@ package com.datadog.android.telemetry.internal +import com.datadog.android.core.configuration.Configuration import com.datadog.android.core.internal.persistence.DataWriter import com.datadog.android.core.internal.sampling.Sampler import com.datadog.android.core.internal.time.TimeProvider @@ -15,18 +16,27 @@ import com.datadog.android.rum.RumSessionListener import com.datadog.android.rum.internal.domain.RumContext import com.datadog.android.rum.internal.domain.event.RumEventSourceProvider import com.datadog.android.rum.internal.domain.scope.RumRawEvent +import com.datadog.android.rum.tracking.ActivityViewTrackingStrategy +import com.datadog.android.rum.tracking.FragmentViewTrackingStrategy +import com.datadog.android.rum.tracking.MixedViewTrackingStrategy +import com.datadog.android.rum.tracking.NavigationViewTrackingStrategy +import com.datadog.android.telemetry.model.TelemetryConfigurationEvent import com.datadog.android.telemetry.model.TelemetryDebugEvent import com.datadog.android.telemetry.model.TelemetryErrorEvent +import io.opentracing.util.GlobalTracer import java.util.Locale internal class TelemetryEventHandler( internal val sdkVersion: String, private val sourceProvider: RumEventSourceProvider, private val timeProvider: TimeProvider, - internal val eventSampler: Sampler + internal val eventSampler: Sampler, + internal val maxEventCountPerSession: Int = MAX_EVENTS_PER_SESSION ) : RumSessionListener { - private val seenInCurrentSession = mutableSetOf() + private var trackNetworkRequests = false + + private val seenInCurrentSession = mutableSetOf() fun handleEvent(event: RumRawEvent.SendTelemetry, writer: DataWriter) { if (!canWrite(event)) return @@ -37,7 +47,7 @@ internal class TelemetryEventHandler( val rumContext = GlobalRum.getRumContext() - val telemetryEvent: Any = when (event.type) { + val telemetryEvent: Any? = when (event.type) { TelemetryType.DEBUG -> { createDebugEvent(timestamp, rumContext, event.message) } @@ -50,9 +60,27 @@ internal class TelemetryEventHandler( event.kind ) } + TelemetryType.CONFIGURATION -> { + if (event.configuration == null) { + createErrorEvent( + timestamp, + rumContext, + "Trying to send configuration event with null config", + null, + null + ) + } else { + createConfigurationEvent(timestamp, rumContext, event.configuration) + } + } + TelemetryType.INTERCEPTOR_SETUP -> { + trackNetworkRequests = true + null + } + } + if (telemetryEvent != null) { + writer.write(telemetryEvent) } - - writer.write(telemetryEvent) } override fun onSessionStarted(sessionId: String, isDiscarded: Boolean) { @@ -71,7 +99,7 @@ internal class TelemetryEventHandler( return false } - if (seenInCurrentSession.size == MAX_EVENTS_PER_SESSION) { + if (seenInCurrentSession.size >= maxEventCountPerSession) { sdkLogger.i(MAX_EVENT_NUMBER_REACHED_MESSAGE) return false } @@ -133,12 +161,55 @@ internal class TelemetryEventHandler( ) } - private val RumRawEvent.SendTelemetry.identity: EventIdentity - get() { - return EventIdentity(message, kind) + private fun createConfigurationEvent( + timestamp: Long, + rumContext: RumContext, + configuration: Configuration? + ): TelemetryConfigurationEvent { + val coreConfig = configuration?.coreConfig + val traceConfig = configuration?.tracesConfig + val rumConfig = configuration?.rumConfig + val crashConfig = configuration?.crashReportConfig + val viewTrackingStrategy = when (rumConfig?.viewTrackingStrategy) { + is ActivityViewTrackingStrategy -> TelemetryConfigurationEvent.ViewTrackingStrategy.ACTIVITYVIEWTRACKINGSTRATEGY + is FragmentViewTrackingStrategy -> TelemetryConfigurationEvent.ViewTrackingStrategy.FRAGMENTVIEWTRACKINGSTRATEGY + is MixedViewTrackingStrategy -> TelemetryConfigurationEvent.ViewTrackingStrategy.MIXEDVIEWTRACKINGSTRATEGY + is NavigationViewTrackingStrategy -> TelemetryConfigurationEvent.ViewTrackingStrategy.NAVIGATIONVIEWTRACKINGSTRATEGY + else -> null } - - internal data class EventIdentity(val message: String, val kind: String?) + return TelemetryConfigurationEvent( + dd = TelemetryConfigurationEvent.Dd(), + date = timestamp, + service = TELEMETRY_SERVICE_NAME, + source = sourceProvider.telemetryConfigurationEventSource + ?: TelemetryConfigurationEvent.Source.ANDROID, + version = sdkVersion, + application = TelemetryConfigurationEvent.Application(rumContext.applicationId), + session = TelemetryConfigurationEvent.Session(rumContext.sessionId), + view = rumContext.viewId?.let { TelemetryConfigurationEvent.View(it) }, + action = rumContext.actionId?.let { TelemetryConfigurationEvent.Action(it) }, + experimentalFeatures = null, + telemetry = TelemetryConfigurationEvent.Telemetry( + TelemetryConfigurationEvent.Configuration( + sessionSampleRate = rumConfig?.samplingRate?.toLong(), + telemetrySampleRate = rumConfig?.telemetrySamplingRate?.toLong(), + useProxy = coreConfig?.proxy != null, + trackFrustrations = rumConfig?.trackFrustrations, + useLocalEncryption = coreConfig?.securityConfig?.localDataEncryption != null, + viewTrackingStrategy = viewTrackingStrategy, + trackBackgroundEvents = rumConfig?.backgroundEventTracking, + trackInteractions = rumConfig?.userActionTrackingStrategy != null, + trackErrors = crashConfig != null, + trackNativeLongTasks = rumConfig?.longTaskTrackingStrategy != null, + batchSize = coreConfig?.batchSize?.windowDurationMs, + batchUploadFrequency = coreConfig?.uploadFrequency?.baseStepMs, + mobileVitalsUpdatePeriod = rumConfig?.vitalsMonitorUpdateFrequency?.periodInMs, + useTracing = traceConfig != null && GlobalTracer.isRegistered(), + trackNetworkRequests = trackNetworkRequests + ) + ) + ) + } // endregion diff --git a/dd-sdk-android/src/main/kotlin/com/datadog/android/telemetry/internal/TelemetryEventId.kt b/dd-sdk-android/src/main/kotlin/com/datadog/android/telemetry/internal/TelemetryEventId.kt new file mode 100644 index 0000000000..d4387f1960 --- /dev/null +++ b/dd-sdk-android/src/main/kotlin/com/datadog/android/telemetry/internal/TelemetryEventId.kt @@ -0,0 +1,20 @@ +/* + * Unless explicitly stated otherwise all files in this repository are licensed under the Apache License Version 2.0. + * This product includes software developed at Datadog (https://www.datadoghq.com/). + * Copyright 2016-Present Datadog, Inc. + */ + +package com.datadog.android.telemetry.internal + +import com.datadog.android.rum.internal.domain.scope.RumRawEvent + +internal data class TelemetryEventId( + val type: TelemetryType, + val message: String, + val kind: String? +) + +internal val RumRawEvent.SendTelemetry.identity: TelemetryEventId + get() { + return TelemetryEventId(type, message, kind) + } diff --git a/dd-sdk-android/src/main/kotlin/com/datadog/android/telemetry/internal/TelemetryType.kt b/dd-sdk-android/src/main/kotlin/com/datadog/android/telemetry/internal/TelemetryType.kt index ef9d4520a0..b1b1d4f11a 100644 --- a/dd-sdk-android/src/main/kotlin/com/datadog/android/telemetry/internal/TelemetryType.kt +++ b/dd-sdk-android/src/main/kotlin/com/datadog/android/telemetry/internal/TelemetryType.kt @@ -8,5 +8,7 @@ package com.datadog.android.telemetry.internal internal enum class TelemetryType { DEBUG, - ERROR + ERROR, + CONFIGURATION, + INTERCEPTOR_SETUP } diff --git a/dd-sdk-android/src/test/kotlin/com/datadog/android/DatadogInterceptorTest.kt b/dd-sdk-android/src/test/kotlin/com/datadog/android/DatadogInterceptorTest.kt index 2094531ece..42a226b788 100644 --- a/dd-sdk-android/src/test/kotlin/com/datadog/android/DatadogInterceptorTest.kt +++ b/dd-sdk-android/src/test/kotlin/com/datadog/android/DatadogInterceptorTest.kt @@ -30,6 +30,7 @@ import com.nhaarman.mockitokotlin2.doReturn import com.nhaarman.mockitokotlin2.doThrow import com.nhaarman.mockitokotlin2.inOrder import com.nhaarman.mockitokotlin2.mock +import com.nhaarman.mockitokotlin2.verify import com.nhaarman.mockitokotlin2.whenever import fr.xgouchet.elmyr.Forge import fr.xgouchet.elmyr.annotation.FloatForgery @@ -119,6 +120,16 @@ internal class DatadogInterceptorTest : TracingInterceptorNotSendingSpanTest() { RumFeature.stop() } + @Test + fun `M notify monitor W init()`() { + // Given + + // When + + // Then + verify(rumMonitor.mockInstance).notifyInterceptorInstantiated() + } + @Test fun `M instantiate with default values W init() { no tracing hosts specified }`() { // When diff --git a/dd-sdk-android/src/test/kotlin/com/datadog/android/DatadogInterceptorWithoutRumTest.kt b/dd-sdk-android/src/test/kotlin/com/datadog/android/DatadogInterceptorWithoutRumTest.kt index 61df1b35b5..4be30eb472 100644 --- a/dd-sdk-android/src/test/kotlin/com/datadog/android/DatadogInterceptorWithoutRumTest.kt +++ b/dd-sdk-android/src/test/kotlin/com/datadog/android/DatadogInterceptorWithoutRumTest.kt @@ -19,6 +19,7 @@ import com.nhaarman.mockitokotlin2.any import com.nhaarman.mockitokotlin2.doReturn import com.nhaarman.mockitokotlin2.doThrow import com.nhaarman.mockitokotlin2.verify +import com.nhaarman.mockitokotlin2.verifyNoMoreInteractions import com.nhaarman.mockitokotlin2.verifyZeroInteractions import com.nhaarman.mockitokotlin2.whenever import fr.xgouchet.elmyr.annotation.Forgery @@ -92,7 +93,8 @@ internal class DatadogInterceptorWithoutRumTest : TracingInterceptorTest() { testedInterceptor.intercept(mockChain) // Then - verifyZeroInteractions(rumMonitor.mockInstance) + verify(rumMonitor.mockInstance).notifyInterceptorInstantiated() + verifyNoMoreInteractions(rumMonitor.mockInstance) verifyZeroInteractions(mockRumAttributesProvider) } @@ -107,7 +109,8 @@ internal class DatadogInterceptorWithoutRumTest : TracingInterceptorTest() { testedInterceptor.intercept(mockChain) // Then - verifyZeroInteractions(rumMonitor.mockInstance) + verify(rumMonitor.mockInstance).notifyInterceptorInstantiated() + verifyNoMoreInteractions(rumMonitor.mockInstance) verifyZeroInteractions(mockRumAttributesProvider) } @@ -125,7 +128,8 @@ internal class DatadogInterceptorWithoutRumTest : TracingInterceptorTest() { } // Then - verifyZeroInteractions(rumMonitor.mockInstance) + verify(rumMonitor.mockInstance).notifyInterceptorInstantiated() + verifyNoMoreInteractions(rumMonitor.mockInstance) verifyZeroInteractions(mockRumAttributesProvider) } diff --git a/dd-sdk-android/src/test/kotlin/com/datadog/android/core/configuration/ConfigurationBuilderTest.kt b/dd-sdk-android/src/test/kotlin/com/datadog/android/core/configuration/ConfigurationBuilderTest.kt index 5c37936eb4..18e93aabc9 100644 --- a/dd-sdk-android/src/test/kotlin/com/datadog/android/core/configuration/ConfigurationBuilderTest.kt +++ b/dd-sdk-android/src/test/kotlin/com/datadog/android/core/configuration/ConfigurationBuilderTest.kt @@ -505,6 +505,30 @@ internal class ConfigurationBuilderTest { assertThat(config.additionalConfig).isEmpty() } + @Test + fun `𝕄 build config with long tasks disabled 𝕎 trackLongTasks() and build()`( + @LongForgery(0L, 65536L) durationMs: Long + ) { + // Given + + // When + val config = testedBuilder + .trackLongTasks(-durationMs) + .build() + + // Then + assertThat(config.coreConfig).isEqualTo(Configuration.DEFAULT_CORE_CONFIG) + assertThat(config.logsConfig).isEqualTo(Configuration.DEFAULT_LOGS_CONFIG) + assertThat(config.tracesConfig).isEqualTo(Configuration.DEFAULT_TRACING_CONFIG) + assertThat(config.crashReportConfig).isEqualTo(Configuration.DEFAULT_CRASH_CONFIG) + assertThat(config.rumConfig).isEqualTo( + Configuration.DEFAULT_RUM_CONFIG.copy( + longTaskTrackingStrategy = null + ) + ) + assertThat(config.additionalConfig).isEmpty() + } + @Test fun `𝕄 build config with view strategy enabled 𝕎 useViewTrackingStrategy() and build()`() { // Given @@ -1640,14 +1664,6 @@ internal class ConfigurationBuilderTest { ) } - private fun Configuration.Builder.setSecurityConfig( - securityConfig: SecurityConfig - ): Configuration.Builder { - val method = this.javaClass.declaredMethods.find { it.name == "setSecurityConfig" } - method!!.isAccessible = true - return method.invoke(this, securityConfig) as Configuration.Builder - } - companion object { val logger = LoggerTestConfiguration() diff --git a/dd-sdk-android/src/test/kotlin/com/datadog/android/core/configuration/ConfigurationTestExt.kt b/dd-sdk-android/src/test/kotlin/com/datadog/android/core/configuration/ConfigurationTestExt.kt new file mode 100644 index 0000000000..98c296653c --- /dev/null +++ b/dd-sdk-android/src/test/kotlin/com/datadog/android/core/configuration/ConfigurationTestExt.kt @@ -0,0 +1,15 @@ +/* + * Unless explicitly stated otherwise all files in this repository are licensed under the Apache License Version 2.0. + * This product includes software developed at Datadog (https://www.datadoghq.com/). + * Copyright 2016-Present Datadog, Inc. + */ + +package com.datadog.android.core.configuration + +internal fun Configuration.Builder.setSecurityConfig( + securityConfig: SecurityConfig +): Configuration.Builder { + val method = this.javaClass.declaredMethods.find { it.name == "setSecurityConfig" } + method!!.isAccessible = true + return method.invoke(this, securityConfig) as Configuration.Builder +} diff --git a/dd-sdk-android/src/test/kotlin/com/datadog/android/core/internal/CoreFeatureTest.kt b/dd-sdk-android/src/test/kotlin/com/datadog/android/core/internal/CoreFeatureTest.kt index c03b77d771..bd074c09a7 100644 --- a/dd-sdk-android/src/test/kotlin/com/datadog/android/core/internal/CoreFeatureTest.kt +++ b/dd-sdk-android/src/test/kotlin/com/datadog/android/core/internal/CoreFeatureTest.kt @@ -253,7 +253,32 @@ internal class CoreFeatureTest { } @Test - fun `𝕄 initializes app info 𝕎 initialize()`() { + @TestTargetApi(Build.VERSION_CODES.LOLLIPOP) + fun `𝕄 initializes app info 𝕎 initialize() { LOLLIPOP }`() { + // When + CoreFeature.initialize( + appContext.mockInstance, + fakeCredentials, + fakeConfig, + fakeConsent + ) + + // Then + assertThat(CoreFeature.clientToken).isEqualTo(fakeCredentials.clientToken) + assertThat(CoreFeature.packageName).isEqualTo(appContext.fakePackageName) + assertThat(CoreFeature.packageVersionProvider.version).isEqualTo(appContext.fakeVersionName) + assertThat(CoreFeature.serviceName).isEqualTo(fakeCredentials.serviceName) + assertThat(CoreFeature.envName).isEqualTo(fakeCredentials.envName) + assertThat(CoreFeature.variant).isEqualTo(fakeCredentials.variant) + assertThat(CoreFeature.rumApplicationId).isEqualTo(fakeCredentials.rumApplicationId) + assertThat(CoreFeature.contextRef.get()).isEqualTo(appContext.mockInstance) + assertThat(CoreFeature.batchSize).isEqualTo(fakeConfig.batchSize) + assertThat(CoreFeature.uploadFrequency).isEqualTo(fakeConfig.uploadFrequency) + } + + @Test + @TestTargetApi(Build.VERSION_CODES.TIRAMISU) + fun `𝕄 initializes app info 𝕎 initialize() { TIRAMISU }`() { // When CoreFeature.initialize( appContext.mockInstance, @@ -352,8 +377,10 @@ internal class CoreFeatureTest { } @Test - fun `𝕄 initializes app info 𝕎 initialize() {unknown package name}`() { + @TestTargetApi(Build.VERSION_CODES.LOLLIPOP) + fun `𝕄 initializes app info 𝕎 initialize() {unknown package name, LOLLIPOP}`() { // Given + @Suppress("DEPRECATION") whenever(appContext.mockPackageManager.getPackageInfo(appContext.fakePackageName, 0)) .doThrow(PackageManager.NameNotFoundException()) whenever(appContext.mockInstance.getSystemService(Context.CONNECTIVITY_SERVICE)) @@ -382,6 +409,43 @@ internal class CoreFeatureTest { assertThat(CoreFeature.uploadFrequency).isEqualTo(fakeConfig.uploadFrequency) } + @Test + @TestTargetApi(Build.VERSION_CODES.TIRAMISU) + fun `𝕄 initializes app info 𝕎 initialize() {unknown package name, TIRAMISU}`() { + // Given + whenever( + appContext.mockPackageManager.getPackageInfo( + appContext.fakePackageName, + PackageManager.PackageInfoFlags.of(0) + ) + ) + .doThrow(PackageManager.NameNotFoundException()) + whenever(appContext.mockInstance.getSystemService(Context.CONNECTIVITY_SERVICE)) + .doReturn(mockConnectivityMgr) + + // When + CoreFeature.initialize( + appContext.mockInstance, + fakeCredentials.copy(rumApplicationId = null), + fakeConfig, + fakeConsent + ) + + // Then + assertThat(CoreFeature.clientToken).isEqualTo(fakeCredentials.clientToken) + assertThat(CoreFeature.packageName).isEqualTo(appContext.fakePackageName) + assertThat(CoreFeature.packageVersionProvider.version).isEqualTo( + CoreFeature.DEFAULT_APP_VERSION + ) + assertThat(CoreFeature.serviceName).isEqualTo(fakeCredentials.serviceName) + assertThat(CoreFeature.envName).isEqualTo(fakeCredentials.envName) + assertThat(CoreFeature.variant).isEqualTo(fakeCredentials.variant) + assertThat(CoreFeature.rumApplicationId).isNull() + assertThat(CoreFeature.contextRef.get()).isEqualTo(appContext.mockInstance) + assertThat(CoreFeature.batchSize).isEqualTo(fakeConfig.batchSize) + assertThat(CoreFeature.uploadFrequency).isEqualTo(fakeConfig.uploadFrequency) + } + @Test fun `𝕄 initialize okhttp with strict network policy 𝕎 initialize()`() { // When diff --git a/dd-sdk-android/src/test/kotlin/com/datadog/android/rum/GlobalRumTest.kt b/dd-sdk-android/src/test/kotlin/com/datadog/android/rum/GlobalRumTest.kt index d794aa0ee2..ac02d55754 100644 --- a/dd-sdk-android/src/test/kotlin/com/datadog/android/rum/GlobalRumTest.kt +++ b/dd-sdk-android/src/test/kotlin/com/datadog/android/rum/GlobalRumTest.kt @@ -66,6 +66,30 @@ internal class GlobalRumTest { verifyZeroInteractions(mockNoOpRumMonitor) } + @Test + fun `M delegate to monitor W notifyInterceptorInstantiated(){ AdvancedRumMonitor }`() { + // Given + GlobalRum.monitor = mockAdvancedRumMonitor + + // When + GlobalRum.notifyInterceptorInstantiated() + + // Then + verify(mockAdvancedRumMonitor).notifyInterceptorInstantiated() + } + + @Test + fun `M do nothing W notifyInterceptorInstantiated(){ NoOpMonitor }`() { + // Given + GlobalRum.monitor = mockNoOpRumMonitor + + // When + GlobalRum.notifyInterceptorInstantiated() + + // Then + verifyZeroInteractions(mockNoOpRumMonitor) + } + @Test fun `M update RUM context W updateRumContext()`() { // Given diff --git a/dd-sdk-android/src/test/kotlin/com/datadog/android/rum/internal/RumFeatureTest.kt b/dd-sdk-android/src/test/kotlin/com/datadog/android/rum/internal/RumFeatureTest.kt index 0ed6de40ae..0a54c66236 100644 --- a/dd-sdk-android/src/test/kotlin/com/datadog/android/rum/internal/RumFeatureTest.kt +++ b/dd-sdk-android/src/test/kotlin/com/datadog/android/rum/internal/RumFeatureTest.kt @@ -141,12 +141,14 @@ internal class RumFeatureTest : SdkFeatureTest() + fakeConfigurationFeature = + fakeConfigurationFeature.copy(viewTrackingStrategy = mockViewTrackingStrategy) testedFeature.initialize(appContext.mockInstance, fakeConfigurationFeature) // Then - assertThat(testedFeature.viewTrackingStrategy) - .isEqualTo(fakeConfigurationFeature.viewTrackingStrategy) - verify(fakeConfigurationFeature.viewTrackingStrategy!!).register(appContext.mockInstance) + assertThat(testedFeature.viewTrackingStrategy).isEqualTo(mockViewTrackingStrategy) + verify(mockViewTrackingStrategy).register(appContext.mockInstance) } @Test diff --git a/dd-sdk-android/src/test/kotlin/com/datadog/android/rum/internal/anr/ANRDetectorRunnableTest.kt b/dd-sdk-android/src/test/kotlin/com/datadog/android/rum/internal/anr/ANRDetectorRunnableTest.kt index 81e256e286..2b9e479ef7 100644 --- a/dd-sdk-android/src/test/kotlin/com/datadog/android/rum/internal/anr/ANRDetectorRunnableTest.kt +++ b/dd-sdk-android/src/test/kotlin/com/datadog/android/rum/internal/anr/ANRDetectorRunnableTest.kt @@ -126,7 +126,9 @@ internal class ANRDetectorRunnableTest { reset(mockHandler) lastValue.run() - Thread.sleep(TEST_ANR_TEST_DELAY_MS) + // +10 is to remove flakiness, otherwise it seems current thread can resume execution + // before AND test thread + Thread.sleep(TEST_ANR_TEST_DELAY_MS + 10) verify(mockHandler).post(capture()) lastValue.run() } diff --git a/dd-sdk-android/src/test/kotlin/com/datadog/android/rum/internal/domain/event/RumEventMapperTest.kt b/dd-sdk-android/src/test/kotlin/com/datadog/android/rum/internal/domain/event/RumEventMapperTest.kt index 32eba7a9d1..41a07a461e 100644 --- a/dd-sdk-android/src/test/kotlin/com/datadog/android/rum/internal/domain/event/RumEventMapperTest.kt +++ b/dd-sdk-android/src/test/kotlin/com/datadog/android/rum/internal/domain/event/RumEventMapperTest.kt @@ -15,6 +15,7 @@ import com.datadog.android.rum.model.ErrorEvent import com.datadog.android.rum.model.LongTaskEvent import com.datadog.android.rum.model.ResourceEvent import com.datadog.android.rum.model.ViewEvent +import com.datadog.android.telemetry.model.TelemetryConfigurationEvent import com.datadog.android.telemetry.model.TelemetryDebugEvent import com.datadog.android.telemetry.model.TelemetryErrorEvent import com.datadog.android.utils.config.GlobalRumMonitorTestConfiguration @@ -93,7 +94,7 @@ internal class RumEventMapperTest { val mappedRumEvent = testedRumEventMapper.map(fakeRumEvent) // THEN - assertThat(mappedRumEvent).isNotNull() + assertThat(mappedRumEvent).isNotNull assertThat(mappedRumEvent).isEqualTo(fakeRumEvent) } @@ -107,7 +108,7 @@ internal class RumEventMapperTest { val mappedRumEvent = testedRumEventMapper.map(fakeRumEvent) // THEN - assertThat(mappedRumEvent).isNotNull() + assertThat(mappedRumEvent).isNotNull assertThat(mappedRumEvent).isEqualTo(fakeRumEvent) } @@ -121,7 +122,7 @@ internal class RumEventMapperTest { val mappedRumEvent = testedRumEventMapper.map(fakeRumEvent) // THEN - assertThat(mappedRumEvent).isNotNull() + assertThat(mappedRumEvent).isNotNull assertThat(mappedRumEvent).isEqualTo(fakeRumEvent) } @@ -135,7 +136,7 @@ internal class RumEventMapperTest { val mappedRumEvent = testedRumEventMapper.map(fakeRumEvent) // THEN - assertThat(mappedRumEvent).isNotNull() + assertThat(mappedRumEvent).isNotNull assertThat(mappedRumEvent).isEqualTo(fakeRumEvent) } @@ -149,7 +150,7 @@ internal class RumEventMapperTest { val mappedRumEvent = testedRumEventMapper.map(fakeRumEvent) // THEN - assertThat(mappedRumEvent).isNotNull() + assertThat(mappedRumEvent).isNotNull assertThat(mappedRumEvent).isEqualTo(fakeRumEvent) } @@ -163,7 +164,7 @@ internal class RumEventMapperTest { val mappedRumEvent = testedRumEventMapper.map(fakeRumEvent) // THEN - assertThat(mappedRumEvent).isNotNull() + assertThat(mappedRumEvent).isNotNull assertThat(mappedRumEvent).isEqualTo(fakeRumEvent) } @@ -209,6 +210,18 @@ internal class RumEventMapperTest { assertThat(mappedRumEvent).isSameAs(telemetryErrorEvent) } + @Test + fun `M return the original event W map { TelemetryConfigurationEvent }`( + @Forgery telemetryConfigurationEvent: TelemetryConfigurationEvent + ) { + // WHEN + val mappedRumEvent = testedRumEventMapper.map(telemetryConfigurationEvent) + + // THEN + verifyZeroInteractions(logger.mockSdkLogHandler) + assertThat(mappedRumEvent).isSameAs(telemetryConfigurationEvent) + } + @Test fun `M use the original event W map returns null object { ViewEvent }`(forge: Forge) { // GIVEN diff --git a/dd-sdk-android/src/test/kotlin/com/datadog/android/rum/internal/domain/event/RumEventSerializerTest.kt b/dd-sdk-android/src/test/kotlin/com/datadog/android/rum/internal/domain/event/RumEventSerializerTest.kt index 613d803c29..5ffcb37614 100644 --- a/dd-sdk-android/src/test/kotlin/com/datadog/android/rum/internal/domain/event/RumEventSerializerTest.kt +++ b/dd-sdk-android/src/test/kotlin/com/datadog/android/rum/internal/domain/event/RumEventSerializerTest.kt @@ -14,6 +14,7 @@ import com.datadog.android.rum.model.ErrorEvent import com.datadog.android.rum.model.LongTaskEvent import com.datadog.android.rum.model.ResourceEvent import com.datadog.android.rum.model.ViewEvent +import com.datadog.android.telemetry.model.TelemetryConfigurationEvent import com.datadog.android.telemetry.model.TelemetryDebugEvent import com.datadog.android.telemetry.model.TelemetryErrorEvent import com.datadog.android.utils.config.LoggerTestConfiguration @@ -531,6 +532,224 @@ internal class RumEventSerializerTest { } } + @RepeatedTest(8) + fun `𝕄 serialize RUM event 𝕎 serialize() with TelemetryConfigurationEvent`( + @Forgery event: TelemetryConfigurationEvent + ) { + val serialized = testedSerializer.serialize(event) + val jsonObject = JsonParser.parseString(serialized).asJsonObject + assertThat(jsonObject) + .hasField("type", "telemetry") + .hasField("_dd") { + hasField("format_version", 2L) + } + .hasField("date", event.date) + .hasField("source", event.source.name.lowercase(Locale.US).replace('_', '-')) + .hasField("service", event.service) + .hasField("version", event.version) + .hasField("telemetry") { + hasField("configuration") { + val configuration = event.telemetry.configuration + if (configuration.sessionSampleRate != null) { + hasField("session_sample_rate", configuration.sessionSampleRate!!) + } + if (configuration.telemetrySampleRate != null) { + hasField("telemetry_sample_rate", configuration.telemetrySampleRate!!) + } + if (configuration.telemetryConfigurationSampleRate != null) { + hasField( + "telemetry_configuration_sample_rate", + configuration.telemetryConfigurationSampleRate!! + ) + } + if (configuration.traceSampleRate != null) { + hasField("trace_sample_rate", configuration.traceSampleRate!!) + } + if (configuration.premiumSampleRate != null) { + hasField("premium_sample_rate", configuration.premiumSampleRate!!) + } + if (configuration.replaySampleRate != null) { + hasField("replay_sample_rate", configuration.replaySampleRate!!) + } + if (configuration.sessionReplaySampleRate != null) { + hasField( + "session_replay_sample_rate", + configuration.sessionReplaySampleRate!! + ) + } + if (configuration.useProxy != null) { + hasField("use_proxy", configuration.useProxy!!) + } + if (configuration.useBeforeSend != null) { + hasField("use_before_send", configuration.useBeforeSend!!) + } + if (configuration.silentMultipleInit != null) { + hasField("silent_multiple_init", configuration.silentMultipleInit!!) + } + if (configuration.trackSessionAcrossSubdomains != null) { + hasField( + "track_session_across_subdomains", + configuration.trackSessionAcrossSubdomains!! + ) + } + if (configuration.useCrossSiteSessionCookie != null) { + hasField( + "use_cross_site_session_cookie", + configuration.useCrossSiteSessionCookie!! + ) + } + if (configuration.useSecureSessionCookie != null) { + hasField( + "use_secure_session_cookie", + configuration.useSecureSessionCookie!! + ) + } + if (configuration.actionNameAttribute != null) { + hasField("action_name_attribute", configuration.actionNameAttribute!!) + } + if (configuration.useAllowedTracingOrigins != null) { + hasField( + "use_allowed_tracing_origins", + configuration.useAllowedTracingOrigins!! + ) + } + if (configuration.defaultPrivacyLevel != null) { + hasField("default_privacy_level", configuration.defaultPrivacyLevel!!) + } + if (configuration.useExcludedActivityUrls != null) { + hasField( + "use_excluded_activity_urls", + configuration.useExcludedActivityUrls!! + ) + } + if (configuration.trackFrustrations != null) { + hasField("track_frustrations", configuration.trackFrustrations!!) + } + if (configuration.trackViewsManually != null) { + hasField("track_views_manually", configuration.trackViewsManually!!) + } + if (configuration.trackInteractions != null) { + hasField("track_interactions", configuration.trackInteractions!!) + } + if (configuration.forwardErrorsToLogs != null) { + hasField("forward_errors_to_logs", configuration.forwardErrorsToLogs!!) + } + if (configuration.forwardConsoleLogs != null) { + hasField("forward_console_logs", configuration.forwardConsoleLogs!!) + } + if (configuration.forwardReports != null) { + hasField("forward_reports", configuration.forwardReports!!) + } + if (configuration.useLocalEncryption != null) { + hasField("use_local_encryption", configuration.useLocalEncryption!!) + } + if (configuration.viewTrackingStrategy != null) { + hasField( + "view_tracking_strategy", + configuration.viewTrackingStrategy!!.toJson() + ) + } + if (configuration.trackBackgroundEvents != null) { + hasField("track_background_events", configuration.trackBackgroundEvents!!) + } + if (configuration.mobileVitalsUpdatePeriod != null) { + hasField( + "mobile_vitals_update_period", + configuration.mobileVitalsUpdatePeriod!! + ) + } + if (configuration.trackInteractions != null) { + hasField("track_interactions", configuration.trackInteractions!!) + } + if (configuration.trackErrors != null) { + hasField("track_errors", configuration.trackErrors!!) + } + if (configuration.trackNetworkRequests != null) { + hasField("track_network_requests", configuration.trackNetworkRequests!!) + } + if (configuration.useTracing != null) { + hasField("use_tracing", configuration.useTracing!!) + } + if (configuration.trackNativeViews != null) { + hasField("track_native_views", configuration.trackNativeViews!!) + } + if (configuration.trackNativeErrors != null) { + hasField("track_native_errors", configuration.trackNativeErrors!!) + } + if (configuration.trackNativeLongTasks != null) { + hasField("track_native_long_tasks", configuration.trackNativeLongTasks!!) + } + if (configuration.trackCrossPlatformLongTasks != null) { + hasField( + "track_cross_platform_long_tasks", + configuration.trackCrossPlatformLongTasks!! + ) + } + if (configuration.useAttachToExisting != null) { + hasField("use_attach_to_existing", configuration.useAttachToExisting!!) + } + if (configuration.useFirstPartyHosts != null) { + hasField("use_first_party_hosts", configuration.useFirstPartyHosts!!) + } + if (configuration.initializationType != null) { + hasField("initialization_type", configuration.initializationType!!) + } + if (configuration.trackFlutterPerformance != null) { + hasField( + "track_flutter_performance", + configuration.trackFlutterPerformance!! + ) + } + if (configuration.batchSize != null) { + hasField("batch_size", configuration.batchSize!!) + } + if (configuration.batchUploadFrequency != null) { + hasField("batch_upload_frequency", configuration.batchUploadFrequency!!) + } + } + } + + val application = event.application + if (application != null) { + assertThat(jsonObject) + .hasField("application") { + hasField("id", application.id) + } + } else { + assertThat(jsonObject).doesNotHaveField("application") + } + + val session = event.session + if (session != null) { + assertThat(jsonObject) + .hasField("session") { + hasField("id", session.id) + } + } else { + assertThat(jsonObject).doesNotHaveField("session") + } + + val view = event.view + if (view != null) { + assertThat(jsonObject) + .hasField("view") { + hasField("id", view.id) + } + } else { + assertThat(jsonObject).doesNotHaveField("view") + } + + val action = event.action + if (action != null) { + assertThat(jsonObject) + .hasField("action") { + hasField("id", action.id) + } + } else { + assertThat(jsonObject).doesNotHaveField("action") + } + } + @Test fun `𝕄 serialize RUM event 𝕎 serialize() with unknown event`( @Forgery unknownEvent: UserInfo diff --git a/dd-sdk-android/src/test/kotlin/com/datadog/android/rum/internal/domain/event/RumEventSourceProviderTest.kt b/dd-sdk-android/src/test/kotlin/com/datadog/android/rum/internal/domain/event/RumEventSourceProviderTest.kt index 395c861f69..3bb86fa171 100644 --- a/dd-sdk-android/src/test/kotlin/com/datadog/android/rum/internal/domain/event/RumEventSourceProviderTest.kt +++ b/dd-sdk-android/src/test/kotlin/com/datadog/android/rum/internal/domain/event/RumEventSourceProviderTest.kt @@ -12,6 +12,7 @@ import com.datadog.android.rum.model.ErrorEvent import com.datadog.android.rum.model.LongTaskEvent import com.datadog.android.rum.model.ResourceEvent import com.datadog.android.rum.model.ViewEvent +import com.datadog.android.telemetry.model.TelemetryConfigurationEvent import com.datadog.android.telemetry.model.TelemetryDebugEvent import com.datadog.android.telemetry.model.TelemetryErrorEvent import com.datadog.android.utils.config.LoggerTestConfiguration @@ -24,6 +25,7 @@ import com.nhaarman.mockitokotlin2.argThat import com.nhaarman.mockitokotlin2.eq import com.nhaarman.mockitokotlin2.verify import fr.xgouchet.elmyr.Forge +import fr.xgouchet.elmyr.annotation.Forgery import fr.xgouchet.elmyr.junit5.ForgeConfiguration import fr.xgouchet.elmyr.junit5.ForgeExtension import org.assertj.core.api.Assertions.assertThat @@ -48,8 +50,6 @@ internal class RumEventSourceProviderTest { lateinit var testedRumEventSourceProvider: RumEventSourceProvider lateinit var fakeInvalidSource: String - lateinit var fakeValidRumSource: String - lateinit var fakeValidTelemetrySource: String @BeforeEach fun `set up`(forge: Forge) { @@ -61,20 +61,20 @@ internal class RumEventSourceProviderTest { it.toJson().asString }.toSet() ) - fakeValidRumSource = forge.aValueFrom(ViewEvent.Source::class.java).toJson().asString - fakeValidTelemetrySource = forge.aValueFrom(TelemetryDebugEvent.Source::class.java).toJson().asString } // region ViewEvent @Test - fun `M resolve the ViewEvent source W viewEventSource`() { + fun `M resolve the ViewEvent source W viewEventSource`( + @Forgery source: ViewEvent.Source + ) { // Given - testedRumEventSourceProvider = RumEventSourceProvider(fakeValidRumSource) + testedRumEventSourceProvider = RumEventSourceProvider(source.toJson().asString) // Then assertThat(testedRumEventSourceProvider.viewEventSource) - .isEqualTo(ViewEvent.Source.fromJson(fakeValidRumSource)) + .isEqualTo(source) } @Test @@ -113,13 +113,15 @@ internal class RumEventSourceProviderTest { // region ActionEvent @Test - fun `M resolve the Action source W actionEventSource`() { + fun `M resolve the Action source W actionEventSource`( + @Forgery source: ActionEvent.Source + ) { // Given - testedRumEventSourceProvider = RumEventSourceProvider(fakeValidRumSource) + testedRumEventSourceProvider = RumEventSourceProvider(source.toJson().asString) // Then assertThat(testedRumEventSourceProvider.actionEventSource) - .isEqualTo(ActionEvent.Source.fromJson(fakeValidRumSource)) + .isEqualTo(source) } @Test @@ -158,13 +160,15 @@ internal class RumEventSourceProviderTest { // region ErrorEvent @Test - fun `M resolve the ErrorEvent source W errorEventSource`() { + fun `M resolve the ErrorEvent source W errorEventSource`( + @Forgery source: ErrorEvent.ErrorEventSource + ) { // Given - testedRumEventSourceProvider = RumEventSourceProvider(fakeValidRumSource) + testedRumEventSourceProvider = RumEventSourceProvider(source.toJson().asString) // Then assertThat(testedRumEventSourceProvider.errorEventSource) - .isEqualTo(ErrorEvent.ErrorEventSource.fromJson(fakeValidRumSource)) + .isEqualTo(source) } @Test @@ -203,13 +207,15 @@ internal class RumEventSourceProviderTest { // region ResourceEvent @Test - fun `M resolve the ResourceEvent source W resourceEventSource`() { + fun `M resolve the ResourceEvent source W resourceEventSource`( + @Forgery source: ResourceEvent.Source + ) { // Given - testedRumEventSourceProvider = RumEventSourceProvider(fakeValidRumSource) + testedRumEventSourceProvider = RumEventSourceProvider(source.toJson().asString) // Then assertThat(testedRumEventSourceProvider.resourceEventSource) - .isEqualTo(ResourceEvent.Source.fromJson(fakeValidRumSource)) + .isEqualTo(source) } @Test @@ -248,13 +254,15 @@ internal class RumEventSourceProviderTest { // region LongTaskEvent @Test - fun `M resolve the LongTaskEvent source W longTaskEventSource`() { + fun `M resolve the LongTaskEvent source W longTaskEventSource`( + @Forgery source: LongTaskEvent.Source + ) { // Given - testedRumEventSourceProvider = RumEventSourceProvider(fakeValidRumSource) + testedRumEventSourceProvider = RumEventSourceProvider(source.toJson().asString) // Then assertThat(testedRumEventSourceProvider.longTaskEventSource) - .isEqualTo(LongTaskEvent.Source.fromJson(fakeValidRumSource)) + .isEqualTo(source) } @Test @@ -293,13 +301,15 @@ internal class RumEventSourceProviderTest { // region TelemetryDebugEvent @Test - fun `M resolve the TelemetryDebugEvent source W telemetryDebugEventSource`() { + fun `M resolve the TelemetryDebugEvent source W telemetryDebugEventSource`( + @Forgery source: TelemetryDebugEvent.Source + ) { // Given - testedRumEventSourceProvider = RumEventSourceProvider(fakeValidTelemetrySource) + testedRumEventSourceProvider = RumEventSourceProvider(source.toJson().asString) // Then assertThat(testedRumEventSourceProvider.telemetryDebugEventSource) - .isEqualTo(TelemetryDebugEvent.Source.fromJson(fakeValidTelemetrySource)) + .isEqualTo(source) } @Test @@ -338,13 +348,15 @@ internal class RumEventSourceProviderTest { // region TelemetryErrorEvent @Test - fun `M resolve the TelemetryErrorEvent source W telemetryErrorEventSource`() { + fun `M resolve the TelemetryErrorEvent source W telemetryErrorEventSource`( + @Forgery source: TelemetryErrorEvent.Source + ) { // Given - testedRumEventSourceProvider = RumEventSourceProvider(fakeValidTelemetrySource) + testedRumEventSourceProvider = RumEventSourceProvider(source.toJson().asString) // Then assertThat(testedRumEventSourceProvider.telemetryErrorEventSource) - .isEqualTo(TelemetryErrorEvent.Source.fromJson(fakeValidTelemetrySource)) + .isEqualTo(source) } @Test @@ -380,6 +392,53 @@ internal class RumEventSourceProviderTest { // endregion + // region TelemetryConfigurationEvent + + @Test + fun `M resolve the TelemetryConfigurationEvent source W telemetryConfigurationEventSource`( + @Forgery source: TelemetryConfigurationEvent.Source + ) { + // Given + testedRumEventSourceProvider = RumEventSourceProvider(source.toJson().asString) + + // Then + assertThat(testedRumEventSourceProvider.telemetryConfigurationEventSource) + .isEqualTo(source) + } + + @Test + fun `M return null W telemetryConfigurationEventSource { unknown source }`() { + // Given + testedRumEventSourceProvider = RumEventSourceProvider(fakeInvalidSource) + + // Then + assertThat(testedRumEventSourceProvider.telemetryConfigurationEventSource).isNull() + } + + @Test + fun `M send an Error dev log W telemetryConfigurationEventSource { unknown source }`() { + // Given + testedRumEventSourceProvider = RumEventSourceProvider(fakeInvalidSource) + + // When + testedRumEventSourceProvider.telemetryConfigurationEventSource + + // Then + verify(logger.mockDevLogHandler).handleLog( + eq(Log.ERROR), + eq( + RumEventSourceProvider.UNKNOWN_SOURCE_WARNING_MESSAGE_FORMAT + .format(Locale.US, fakeInvalidSource) + ), + argThat { this is NoSuchElementException }, + eq(emptyMap()), + eq(emptySet()), + eq(null) + ) + } + + // endregion + companion object { val logger = LoggerTestConfiguration() diff --git a/dd-sdk-android/src/test/kotlin/com/datadog/android/rum/internal/monitor/DatadogRumMonitorTest.kt b/dd-sdk-android/src/test/kotlin/com/datadog/android/rum/internal/monitor/DatadogRumMonitorTest.kt index 63298bf68a..3d5b6ab04b 100644 --- a/dd-sdk-android/src/test/kotlin/com/datadog/android/rum/internal/monitor/DatadogRumMonitorTest.kt +++ b/dd-sdk-android/src/test/kotlin/com/datadog/android/rum/internal/monitor/DatadogRumMonitorTest.kt @@ -7,6 +7,7 @@ package com.datadog.android.rum.internal.monitor import android.os.Handler +import com.datadog.android.core.configuration.Configuration import com.datadog.android.core.internal.net.FirstPartyHostDetector import com.datadog.android.core.internal.persistence.DataWriter import com.datadog.android.core.internal.system.AndroidInfoProvider @@ -234,7 +235,7 @@ internal class DatadogRumMonitorTest { val event = firstValue as RumRawEvent.StartAction assertThat(event.type).isEqualTo(type) assertThat(event.name).isEqualTo(name) - assertThat(event.waitForStop).isFalse() + assertThat(event.waitForStop).isFalse assertThat(event.attributes).containsAllEntriesOf(fakeAttributes) } verifyNoMoreInteractions(mockScope, mockWriter) @@ -254,7 +255,7 @@ internal class DatadogRumMonitorTest { val event = firstValue as RumRawEvent.StartAction assertThat(event.type).isEqualTo(type) assertThat(event.name).isEqualTo(name) - assertThat(event.waitForStop).isTrue() + assertThat(event.waitForStop).isTrue assertThat(event.attributes).containsAllEntriesOf(fakeAttributes) } verifyNoMoreInteractions(mockScope, mockWriter) @@ -452,7 +453,7 @@ internal class DatadogRumMonitorTest { assertThat(event.source).isEqualTo(source) assertThat(event.throwable).isEqualTo(throwable) assertThat(event.stacktrace).isNull() - assertThat(event.isFatal).isFalse() + assertThat(event.isFatal).isFalse assertThat(event.sourceType).isEqualTo(RumErrorSourceType.ANDROID) assertThat(event.attributes).containsAllEntriesOf(fakeAttributes) } @@ -476,7 +477,7 @@ internal class DatadogRumMonitorTest { assertThat(event.source).isEqualTo(source) assertThat(event.throwable).isNull() assertThat(event.stacktrace).isEqualTo(stacktrace) - assertThat(event.isFatal).isFalse() + assertThat(event.isFatal).isFalse assertThat(event.sourceType).isEqualTo(RumErrorSourceType.ANDROID) assertThat(event.attributes).containsAllEntriesOf(fakeAttributes) } @@ -557,7 +558,7 @@ internal class DatadogRumMonitorTest { assertThat(event.message).isEqualTo(message) assertThat(event.source).isEqualTo(source) assertThat(event.throwable).isEqualTo(throwable) - assertThat(event.isFatal).isTrue() + assertThat(event.isFatal).isTrue assertThat(event.sourceType).isEqualTo(RumErrorSourceType.ANDROID) assertThat(event.attributes).isEmpty() } @@ -661,7 +662,7 @@ internal class DatadogRumMonitorTest { assertThat(event.eventTime.timestamp).isEqualTo(fakeTimestamp) assertThat(event.type).isEqualTo(type) assertThat(event.name).isEqualTo(name) - assertThat(event.waitForStop).isFalse() + assertThat(event.waitForStop).isFalse assertThat(event.attributes).containsAllEntriesOf(fakeAttributes) } verifyNoMoreInteractions(mockScope, mockWriter) @@ -684,7 +685,7 @@ internal class DatadogRumMonitorTest { assertThat(event.eventTime.timestamp).isEqualTo(fakeTimestamp) assertThat(event.type).isEqualTo(type) assertThat(event.name).isEqualTo(name) - assertThat(event.waitForStop).isTrue() + assertThat(event.waitForStop).isTrue assertThat(event.attributes).containsAllEntriesOf(fakeAttributes) } verifyNoMoreInteractions(mockScope, mockWriter) @@ -782,7 +783,7 @@ internal class DatadogRumMonitorTest { assertThat(event.source).isEqualTo(source) assertThat(event.throwable).isEqualTo(throwable) assertThat(event.stacktrace).isNull() - assertThat(event.isFatal).isFalse() + assertThat(event.isFatal).isFalse assertThat(event.sourceType).isEqualTo(RumErrorSourceType.ANDROID) assertThat(event.attributes).containsAllEntriesOf(fakeAttributes) } @@ -809,7 +810,7 @@ internal class DatadogRumMonitorTest { assertThat(event.source).isEqualTo(source) assertThat(event.throwable).isNull() assertThat(event.stacktrace).isEqualTo(stacktrace) - assertThat(event.isFatal).isFalse() + assertThat(event.isFatal).isFalse assertThat(event.sourceType).isEqualTo(RumErrorSourceType.ANDROID) assertThat(event.attributes).containsAllEntriesOf(fakeAttributes) } @@ -836,7 +837,7 @@ internal class DatadogRumMonitorTest { assertThat(event.source).isEqualTo(source) assertThat(event.throwable).isEqualTo(throwable) assertThat(event.stacktrace).isNull() - assertThat(event.isFatal).isFalse() + assertThat(event.isFatal).isFalse assertThat(event.type).isEqualTo(errorType) assertThat(event.sourceType).isEqualTo(RumErrorSourceType.ANDROID) assertThat(event.attributes).containsAllEntriesOf(fakeAttributesWithErrorType) @@ -869,7 +870,7 @@ internal class DatadogRumMonitorTest { assertThat(event.source).isEqualTo(source) assertThat(event.throwable).isNull() assertThat(event.stacktrace).isEqualTo(stacktrace) - assertThat(event.isFatal).isFalse() + assertThat(event.isFatal).isFalse assertThat(event.attributes).containsAllEntriesOf(fakeAttributesWithErrorType) assertThat(event.type).isEqualTo(errorType) assertThat(event.sourceType).isEqualTo(RumErrorSourceType.ANDROID) @@ -914,7 +915,7 @@ internal class DatadogRumMonitorTest { assertThat(event.source).isEqualTo(source) assertThat(event.throwable).isNull() assertThat(event.stacktrace).isEqualTo(stacktrace) - assertThat(event.isFatal).isFalse() + assertThat(event.isFatal).isFalse assertThat(event.attributes).containsAllEntriesOf(fakeAttributesWithErrorSourceType) assertThat(event.sourceType).isEqualTo(sourceTypeExpectations[sourceType]) } @@ -1414,11 +1415,35 @@ internal class DatadogRumMonitorTest { assertThat(lastValue.type).isEqualTo(TelemetryType.DEBUG) assertThat(lastValue.stack).isNull() assertThat(lastValue.kind).isNull() + assertThat(lastValue.configuration).isNull() } } @Test - fun `M handle error telemetry event W sendErrorTelemetryEvent()`( + fun `M handle error telemetry event W sendErrorTelemetryEvent() {stack+kind}`( + @StringForgery message: String, + @StringForgery stackTrace: String, + @StringForgery kind: String + ) { + // When + testedMonitor.sendErrorTelemetryEvent(message, stackTrace, kind) + + // Then + argumentCaptor { + verify(mockTelemetryEventHandler).handleEvent( + capture(), + eq(mockWriter) + ) + assertThat(lastValue.message).isEqualTo(message) + assertThat(lastValue.type).isEqualTo(TelemetryType.ERROR) + assertThat(lastValue.stack).isEqualTo(stackTrace) + assertThat(lastValue.kind).isEqualTo(kind) + assertThat(lastValue.configuration).isNull() + } + } + + @Test + fun `M handle error telemetry event W sendErrorTelemetryEvent() {throwable}`( @StringForgery message: String, forge: Forge ) { @@ -1436,6 +1461,47 @@ internal class DatadogRumMonitorTest { assertThat(lastValue.type).isEqualTo(TelemetryType.ERROR) assertThat(lastValue.stack).isEqualTo(throwable?.loggableStackTrace()) assertThat(lastValue.kind).isEqualTo(throwable?.javaClass?.canonicalName) + assertThat(lastValue.configuration).isNull() + } + } + + @Test + fun `M handle configuration telemetry event W sendConfigurationTelemetryEvent()`( + @Forgery configuration: Configuration + ) { + // When + testedMonitor.sendConfigurationTelemetryEvent(configuration) + + // Then + argumentCaptor { + verify(mockTelemetryEventHandler).handleEvent( + capture(), + eq(mockWriter) + ) + assertThat(lastValue.message).isEmpty() + assertThat(lastValue.type).isEqualTo(TelemetryType.CONFIGURATION) + assertThat(lastValue.stack).isNull() + assertThat(lastValue.kind).isNull() + assertThat(lastValue.configuration).isSameAs(configuration) + } + } + + @Test + fun `M handle interceptor event W notifyInterceptorInstantiated()`() { + // When + testedMonitor.notifyInterceptorInstantiated() + + // Then + argumentCaptor { + verify(mockTelemetryEventHandler).handleEvent( + capture(), + eq(mockWriter) + ) + assertThat(lastValue.message).isEmpty() + assertThat(lastValue.type).isEqualTo(TelemetryType.INTERCEPTOR_SETUP) + assertThat(lastValue.stack).isNull() + assertThat(lastValue.kind).isNull() + assertThat(lastValue.configuration).isNull() } } diff --git a/dd-sdk-android/src/test/kotlin/com/datadog/android/telemetry/assertj/TelemetryConfigurationEventAssert.kt b/dd-sdk-android/src/test/kotlin/com/datadog/android/telemetry/assertj/TelemetryConfigurationEventAssert.kt new file mode 100644 index 0000000000..6e15d76fdf --- /dev/null +++ b/dd-sdk-android/src/test/kotlin/com/datadog/android/telemetry/assertj/TelemetryConfigurationEventAssert.kt @@ -0,0 +1,313 @@ +/* + * Unless explicitly stated otherwise all files in this repository are licensed under the Apache License Version 2.0. + * This product includes software developed at Datadog (https://www.datadoghq.com/). + * Copyright 2016-Present Datadog, Inc. + */ + +package com.datadog.android.telemetry.assertj + +import com.datadog.android.telemetry.model.TelemetryConfigurationEvent +import org.assertj.core.api.AbstractObjectAssert +import org.assertj.core.api.Assertions.assertThat +import com.datadog.android.telemetry.model.TelemetryConfigurationEvent.ViewTrackingStrategy as VTS + +internal class TelemetryConfigurationEventAssert(actual: TelemetryConfigurationEvent) : + AbstractObjectAssert( + actual, + TelemetryConfigurationEventAssert::class.java + ) { + + // region Common Telemetry + + fun hasDate(expected: Long): TelemetryConfigurationEventAssert { + assertThat(actual.date) + .overridingErrorMessage( + "Expected event data to have date $expected but was ${actual.date}" + ) + .isEqualTo(expected) + return this + } + + fun hasSource(expected: TelemetryConfigurationEvent.Source): TelemetryConfigurationEventAssert { + assertThat(actual.source) + .overridingErrorMessage( + "Expected event data to have source $expected but was ${actual.source}" + ) + .isEqualTo(expected) + return this + } + + fun hasService(expected: String): TelemetryConfigurationEventAssert { + assertThat(actual.service) + .overridingErrorMessage( + "Expected event data to have service $expected but was ${actual.service}" + ) + .isEqualTo(expected) + return this + } + + fun hasVersion(expected: String): TelemetryConfigurationEventAssert { + assertThat(actual.version) + .overridingErrorMessage( + "Expected event data to have version $expected but was ${actual.version}" + ) + .isEqualTo(expected) + return this + } + + // endregion + + // region RUM Context + + fun hasApplicationId(expected: String?): TelemetryConfigurationEventAssert { + assertThat(actual.application?.id) + .overridingErrorMessage( + "Expected event data to have" + + " application.id $expected but was ${actual.application?.id}" + ) + .isEqualTo(expected) + return this + } + + fun hasSessionId(expected: String?): TelemetryConfigurationEventAssert { + assertThat(actual.session?.id) + .overridingErrorMessage( + "Expected event data to have session.id $expected but was ${actual.session?.id}" + ) + .isEqualTo(expected) + return this + } + + fun hasViewId(expected: String?): TelemetryConfigurationEventAssert { + assertThat(actual.view?.id) + .overridingErrorMessage( + "Expected event data to have view.id $expected but was ${actual.view?.id}" + ) + .isEqualTo(expected) + return this + } + + fun hasActionId(expected: String?): TelemetryConfigurationEventAssert { + assertThat(actual.action?.id) + .overridingErrorMessage( + "Expected event data to have action ID $expected but was ${actual.action?.id}" + ) + .isEqualTo(expected) + return this + } + + // endregion + + // region Native Configuration + + fun hasSessionSampleRate(expected: Long?): TelemetryConfigurationEventAssert { + assertThat(actual.telemetry.configuration.sessionSampleRate) + .overridingErrorMessage( + "Expected event data to have telemetry.configuration.sessionSampleRate $expected " + + "but was ${actual.telemetry.configuration.sessionSampleRate}" + ) + .isEqualTo(expected) + return this + } + + fun hasTelemetrySampleRate(expected: Long?): TelemetryConfigurationEventAssert { + assertThat(actual.telemetry.configuration.telemetrySampleRate) + .overridingErrorMessage( + "Expected event data to have telemetry.configuration.telemetrySampleRate $expected " + + "but was ${actual.telemetry.configuration.telemetrySampleRate}" + ) + .isEqualTo(expected) + return this + } + + fun hasUseProxy(expected: Boolean?): TelemetryConfigurationEventAssert { + assertThat(actual.telemetry.configuration.useProxy) + .overridingErrorMessage( + "Expected event data to have telemetry.configuration.useProxy $expected " + + "but was ${actual.telemetry.configuration.useProxy}" + ) + .isEqualTo(expected) + return this + } + + fun hasTrackFrustrations(expected: Boolean?): TelemetryConfigurationEventAssert { + assertThat(actual.telemetry.configuration.trackFrustrations) + .overridingErrorMessage( + "Expected event data to have telemetry.configuration.trackFrustrations $expected " + + "but was ${actual.telemetry.configuration.trackFrustrations}" + ) + .isEqualTo(expected) + return this + } + + fun hasUseLocalEncryption(expected: Boolean?): TelemetryConfigurationEventAssert { + assertThat(actual.telemetry.configuration.useLocalEncryption) + .overridingErrorMessage( + "Expected event data to have telemetry.configuration.useLocalEncryption $expected " + + "but was ${actual.telemetry.configuration.useLocalEncryption}" + ) + .isEqualTo(expected) + return this + } + + fun hasViewTrackingStrategy(expected: VTS?): TelemetryConfigurationEventAssert { + assertThat(actual.telemetry.configuration.viewTrackingStrategy) + .overridingErrorMessage( + "Expected event data to have telemetry.configuration.viewTrackingStrategy $expected " + + "but was ${actual.telemetry.configuration.viewTrackingStrategy}" + ) + .isEqualTo(expected) + return this + } + + fun hasTrackBackgroundEvents(expected: Boolean?): TelemetryConfigurationEventAssert { + assertThat(actual.telemetry.configuration.trackBackgroundEvents) + .overridingErrorMessage( + "Expected event data to have telemetry.configuration.trackBackgroundEvents $expected " + + "but was ${actual.telemetry.configuration.trackBackgroundEvents}" + ) + .isEqualTo(expected) + return this + } + + fun hasMobileVitalsUpdatePeriod(expected: Long?): TelemetryConfigurationEventAssert { + assertThat(actual.telemetry.configuration.mobileVitalsUpdatePeriod) + .overridingErrorMessage( + "Expected event data to have telemetry.configuration.mobileVitalsUpdatePeriod $expected " + + "but was ${actual.telemetry.configuration.mobileVitalsUpdatePeriod}" + ) + .isEqualTo(expected) + return this + } + + fun hasTrackInteractions(expected: Boolean?): TelemetryConfigurationEventAssert { + assertThat(actual.telemetry.configuration.trackInteractions) + .overridingErrorMessage( + "Expected event data to have telemetry.configuration.trackActions $expected " + + "but was ${actual.telemetry.configuration.trackInteractions}" + ) + .isEqualTo(expected) + return this + } + + fun hasTrackErrors(expected: Boolean?): TelemetryConfigurationEventAssert { + assertThat(actual.telemetry.configuration.trackErrors) + .overridingErrorMessage( + "Expected event data to have telemetry.configuration.trackErrors $expected " + + "but was ${actual.telemetry.configuration.trackErrors}" + ) + .isEqualTo(expected) + return this + } + + fun hasTrackNetworkRequests(expected: Boolean?): TelemetryConfigurationEventAssert { + assertThat(actual.telemetry.configuration.trackNetworkRequests) + .overridingErrorMessage( + "Expected event data to have telemetry.configuration.trackNetworkRequests $expected " + + "but was ${actual.telemetry.configuration.trackNetworkRequests}" + ) + .isEqualTo(expected) + return this + } + + fun hasTrackLongTasks(expected: Boolean?): TelemetryConfigurationEventAssert { + assertThat(actual.telemetry.configuration.trackNativeLongTasks) + .overridingErrorMessage( + "Expected event data to have telemetry.configuration.trackNativeLongTasks $expected " + + "but was ${actual.telemetry.configuration.trackNativeLongTasks}" + ) + .isEqualTo(expected) + return this + } + + fun hasUseTracing(expected: Boolean?): TelemetryConfigurationEventAssert { + assertThat(actual.telemetry.configuration.useTracing) + .overridingErrorMessage( + "Expected event data to have telemetry.configuration.useTracing $expected " + + "but was ${actual.telemetry.configuration.useTracing}" + ) + .isEqualTo(expected) + return this + } + + fun hasBatchSize(expected: Long?): TelemetryConfigurationEventAssert { + assertThat(actual.telemetry.configuration.batchSize) + .overridingErrorMessage( + "Expected event data to have telemetry.configuration.batchSize $expected " + + "but was ${actual.telemetry.configuration.batchSize}" + ) + .isEqualTo(expected) + return this + } + + fun hasBatchUploadFrequency(expected: Long?): TelemetryConfigurationEventAssert { + assertThat(actual.telemetry.configuration.batchUploadFrequency) + .overridingErrorMessage( + "Expected event data to have telemetry.configuration.batchUploadFrequency $expected " + + "but was ${actual.telemetry.configuration.batchUploadFrequency}" + ) + .isEqualTo(expected) + return this + } + + // endregion + + // region CrossPlatform Configuration + + fun hasTrackNativeViews(expected: Boolean?): TelemetryConfigurationEventAssert { + assertThat(actual.telemetry.configuration.trackNativeViews) + .overridingErrorMessage( + "Expected event data to have telemetry.configuration.trackNativeViews $expected " + + "but was ${actual.telemetry.configuration.trackNativeViews}" + ) + .isEqualTo(expected) + return this + } + + fun hasTrackNativeErrors(expected: Boolean?): TelemetryConfigurationEventAssert { + assertThat(actual.telemetry.configuration.trackNativeErrors) + .overridingErrorMessage( + "Expected event data to have telemetry.configuration.trackNativeErrors $expected " + + "but was ${actual.telemetry.configuration.trackNativeErrors}" + ) + .isEqualTo(expected) + return this + } + + fun hasUseFirstPartyHosts(expected: Boolean?): TelemetryConfigurationEventAssert { + assertThat(actual.telemetry.configuration.useFirstPartyHosts) + .overridingErrorMessage( + "Expected event data to have telemetry.configuration.useFirstPartyHosts $expected " + + "but was ${actual.telemetry.configuration.useFirstPartyHosts}" + ) + .isEqualTo(expected) + return this + } + + fun hasTrackFlutterPerformance(expected: Boolean?): TelemetryConfigurationEventAssert { + assertThat(actual.telemetry.configuration.trackFlutterPerformance) + .overridingErrorMessage( + "Expected event data to have telemetry.configuration.trackFlutterPerformance $expected " + + "but was ${actual.telemetry.configuration.trackFlutterPerformance}" + ) + .isEqualTo(expected) + return this + } + + fun hasInitializationType(expected: String?): TelemetryConfigurationEventAssert { + assertThat(actual.telemetry.configuration.initializationType) + .overridingErrorMessage( + "Expected event data to have telemetry.configuration.initializationType $expected " + + "but was ${actual.telemetry.configuration.initializationType}" + ) + .isEqualTo(expected) + return this + } + + // endregion + + companion object { + fun assertThat(actual: TelemetryConfigurationEvent) = + TelemetryConfigurationEventAssert(actual) + } +} diff --git a/dd-sdk-android/src/test/kotlin/com/datadog/android/telemetry/internal/TelemetryEventHandlerTest.kt b/dd-sdk-android/src/test/kotlin/com/datadog/android/telemetry/internal/TelemetryEventHandlerTest.kt index 2dc48e43de..6421b53c91 100644 --- a/dd-sdk-android/src/test/kotlin/com/datadog/android/telemetry/internal/TelemetryEventHandlerTest.kt +++ b/dd-sdk-android/src/test/kotlin/com/datadog/android/telemetry/internal/TelemetryEventHandlerTest.kt @@ -7,15 +7,29 @@ package com.datadog.android.telemetry.internal import android.util.Log +import com.datadog.android.core.configuration.BatchSize +import com.datadog.android.core.configuration.Configuration +import com.datadog.android.core.configuration.SecurityConfig +import com.datadog.android.core.configuration.UploadFrequency +import com.datadog.android.core.configuration.VitalsUpdateFrequency +import com.datadog.android.core.configuration.setSecurityConfig import com.datadog.android.core.internal.persistence.DataWriter import com.datadog.android.core.internal.sampling.Sampler import com.datadog.android.core.internal.time.TimeProvider import com.datadog.android.core.internal.utils.loggableStackTrace import com.datadog.android.rum.GlobalRum +import com.datadog.android.rum.internal.domain.RumContext import com.datadog.android.rum.internal.domain.event.RumEventSourceProvider import com.datadog.android.rum.internal.domain.scope.RumRawEvent -import com.datadog.android.telemetry.assertj.TelemetryDebugEventAssert -import com.datadog.android.telemetry.assertj.TelemetryErrorEventAssert +import com.datadog.android.rum.tracking.ActivityViewTrackingStrategy +import com.datadog.android.rum.tracking.FragmentViewTrackingStrategy +import com.datadog.android.rum.tracking.MixedViewTrackingStrategy +import com.datadog.android.rum.tracking.NavigationViewTrackingStrategy +import com.datadog.android.rum.tracking.ViewTrackingStrategy +import com.datadog.android.telemetry.assertj.TelemetryConfigurationEventAssert.Companion.assertThat +import com.datadog.android.telemetry.assertj.TelemetryDebugEventAssert.Companion.assertThat +import com.datadog.android.telemetry.assertj.TelemetryErrorEventAssert.Companion.assertThat +import com.datadog.android.telemetry.model.TelemetryConfigurationEvent import com.datadog.android.telemetry.model.TelemetryDebugEvent import com.datadog.android.telemetry.model.TelemetryErrorEvent import com.datadog.android.utils.config.GlobalRumMonitorTestConfiguration @@ -25,21 +39,27 @@ import com.datadog.tools.unit.annotations.TestConfigurationsProvider import com.datadog.tools.unit.extensions.TestConfigurationExtension import com.datadog.tools.unit.extensions.config.TestConfiguration import com.datadog.tools.unit.forge.aThrowable +import com.datadog.tools.unit.setStaticValue import com.nhaarman.mockitokotlin2.argumentCaptor import com.nhaarman.mockitokotlin2.atLeastOnce import com.nhaarman.mockitokotlin2.doAnswer import com.nhaarman.mockitokotlin2.doReturn +import com.nhaarman.mockitokotlin2.mock import com.nhaarman.mockitokotlin2.times import com.nhaarman.mockitokotlin2.verify import com.nhaarman.mockitokotlin2.verifyNoMoreInteractions import com.nhaarman.mockitokotlin2.verifyZeroInteractions import com.nhaarman.mockitokotlin2.whenever import fr.xgouchet.elmyr.Forge +import fr.xgouchet.elmyr.annotation.BoolForgery import fr.xgouchet.elmyr.annotation.StringForgery import fr.xgouchet.elmyr.junit5.ForgeConfiguration import fr.xgouchet.elmyr.junit5.ForgeExtension +import io.opentracing.Tracer +import io.opentracing.util.GlobalTracer import org.assertj.core.api.Assertions.assertThat import org.assertj.core.data.Percentage +import org.junit.jupiter.api.AfterEach import org.junit.jupiter.api.BeforeEach import org.junit.jupiter.api.Test import org.junit.jupiter.api.extension.ExtendWith @@ -50,6 +70,7 @@ import org.mockito.junit.jupiter.MockitoSettings import org.mockito.quality.Strictness import java.util.Locale import kotlin.reflect.jvm.jvmName +import com.datadog.android.telemetry.model.TelemetryConfigurationEvent.ViewTrackingStrategy as VTS @Extensions( ExtendWith(MockitoExtension::class), @@ -92,15 +113,22 @@ internal class TelemetryEventHandlerTest { whenever(mockSampler.sample()) doReturn true - testedTelemetryHandler = - TelemetryEventHandler( - mockSdkVersion, - mockSourceProvider, - mockTimeProvider, - mockSampler - ) + testedTelemetryHandler = TelemetryEventHandler( + mockSdkVersion, + mockSourceProvider, + mockTimeProvider, + mockSampler, + MAX_EVENTS_PER_SESSION_TEST + ) } + @AfterEach + fun `tear down`() { + GlobalTracer::class.java.setStaticValue("isRegistered", false) + } + + // region Debug Event + @Test fun `𝕄 create debug event 𝕎 handleEvent(SendTelemetry) { debug event status }`(forge: Forge) { // Given @@ -114,20 +142,14 @@ internal class TelemetryEventHandlerTest { // Then argumentCaptor { verify(mockWriter).write(capture()) - TelemetryDebugEventAssert.assertThat(lastValue).apply { - hasDate(debugRawEvent.eventTime.timestamp + fakeServerOffset) - hasSource(TelemetryDebugEvent.Source.ANDROID) - hasMessage(debugRawEvent.message) - hasService(TelemetryEventHandler.TELEMETRY_SERVICE_NAME) - hasVersion(mockSdkVersion) - hasApplicationId(rumContext.applicationId) - hasSessionId(rumContext.sessionId) - hasViewId(rumContext.viewId) - hasActionId(rumContext.actionId) - } + assertDebugEventMatchesRawEvent(lastValue, debugRawEvent, rumContext) } } + // endregion + + // region Error Event + @Test fun `𝕄 create error event 𝕎 handleEvent(SendTelemetry) { error event status }`(forge: Forge) { // Given @@ -141,22 +163,269 @@ internal class TelemetryEventHandlerTest { // Then argumentCaptor { verify(mockWriter).write(capture()) - TelemetryErrorEventAssert.assertThat(lastValue).apply { - hasDate(errorRawEvent.eventTime.timestamp + fakeServerOffset) - hasSource(TelemetryErrorEvent.Source.ANDROID) - hasMessage(errorRawEvent.message) - hasService(TelemetryEventHandler.TELEMETRY_SERVICE_NAME) - hasVersion(mockSdkVersion) - hasApplicationId(rumContext.applicationId) - hasSessionId(rumContext.sessionId) - hasViewId(rumContext.viewId) - hasActionId(rumContext.actionId) - hasErrorStack(errorRawEvent.stack) - hasErrorKind(errorRawEvent.kind) + assertErrorEventMatchesRawEvent(lastValue, errorRawEvent, rumContext) + } + } + + // endregion + + // region Configuration Event + + @Test + fun `𝕄 create config event 𝕎 handleEvent(SendTelemetry) { configuration }`(forge: Forge) { + // Given + val configRawEvent = forge.createRumRawTelemetryConfigurationEvent() + val rumContext = GlobalRum.getRumContext() + + // When + testedTelemetryHandler.handleEvent(configRawEvent, mockWriter) + + // Then + argumentCaptor { + verify(mockWriter).write(capture()) + assertConfigEventMatchesRawEvent(firstValue, configRawEvent, rumContext) + } + } + + @Test + fun `𝕄 create config event 𝕎 handleEvent(SendTelemetry) { configuration with sampling rates}`( + forge: Forge + ) { + // Given + val sessionSampleRate = forge.aLong(0L, 100L) + val telemetrySamplingRate = forge.aLong(0L, 100L) + val configuration = Configuration.Builder( + logsEnabled = true, + tracesEnabled = true, + crashReportsEnabled = true, + rumEnabled = true + ) + .sampleRumSessions(sessionSampleRate.toFloat()) + .sampleTelemetry(telemetrySamplingRate.toFloat()) + .build() + val configRawEvent = forge.createRumRawTelemetryConfigurationEvent(configuration) + + val rumContext = GlobalRum.getRumContext() + + // When + testedTelemetryHandler.handleEvent(configRawEvent, mockWriter) + + // Then + argumentCaptor { + verify(mockWriter).write(capture()) + assertConfigEventMatchesRawEvent(firstValue, configRawEvent, rumContext) + assertThat(firstValue) + .hasSessionSampleRate(sessionSampleRate) + .hasTelemetrySampleRate(telemetrySamplingRate) + } + } + + @Test + fun `𝕄 create config event 𝕎 handleEvent(SendTelemetry) { configuration with proxy}`( + forge: Forge + ) { + // Given + val useProxy = forge.aBool() + val configuration = Configuration.Builder( + logsEnabled = true, + tracesEnabled = true, + crashReportsEnabled = true, + rumEnabled = true + ).apply { + if (useProxy) { + setProxy(mock(), forge.aNullable { mock() }) + } + }.build() + val configRawEvent = forge.createRumRawTelemetryConfigurationEvent(configuration) + + val rumContext = GlobalRum.getRumContext() + + // When + testedTelemetryHandler.handleEvent(configRawEvent, mockWriter) + + // Then + argumentCaptor { + verify(mockWriter).write(capture()) + assertConfigEventMatchesRawEvent(firstValue, configRawEvent, rumContext) + assertThat(firstValue) + .hasUseProxy(useProxy) + } + } + + @Test + fun `𝕄 create config event 𝕎 handleEvent(SendTelemetry) { configuration with local encryption}`( + forge: Forge + ) { + // Given + val useLocalEncryption = forge.aBool() + val configuration = Configuration.Builder( + logsEnabled = true, + tracesEnabled = true, + crashReportsEnabled = true, + rumEnabled = true + ).apply { + if (useLocalEncryption) { + setSecurityConfig(SecurityConfig(mock())) } + }.build() + val configRawEvent = forge.createRumRawTelemetryConfigurationEvent(configuration) + + val rumContext = GlobalRum.getRumContext() + + // When + testedTelemetryHandler.handleEvent(configRawEvent, mockWriter) + + // Then + argumentCaptor { + verify(mockWriter).write(capture()) + assertConfigEventMatchesRawEvent(firstValue, configRawEvent, rumContext) + assertThat(firstValue) + .hasUseLocalEncryption(useLocalEncryption) + } + } + + @Test + fun `𝕄 create config event 𝕎 handleEvent(SendTelemetry) { configuration with auto-instrumentation }`( + forge: Forge + ) { + // Given + val vts = forge.aValueFrom(VTS::class.java) + val trackErrors = forge.aBool() + val trackFrustrations = forge.aBool() + val trackBackgroundEvents = forge.aBool() + val trackLongTasks = forge.aBool() + val configuration = Configuration.Builder( + logsEnabled = true, + tracesEnabled = true, + crashReportsEnabled = trackErrors, + rumEnabled = true + ) + .trackFrustrations(trackFrustrations) + .useViewTrackingStrategy(forge.aViewTrackingStrategy(vts)) + .trackBackgroundRumEvents(trackBackgroundEvents) + .trackLongTasks(if (trackLongTasks) forge.aPositiveLong() else forge.aNegativeLong()) + .build() + val configRawEvent = forge.createRumRawTelemetryConfigurationEvent(configuration) + val rumContext = GlobalRum.getRumContext() + + // When + testedTelemetryHandler.handleEvent(configRawEvent, mockWriter) + + // Then + argumentCaptor { + verify(mockWriter).write(capture()) + assertConfigEventMatchesRawEvent(firstValue, configRawEvent, rumContext) + assertThat(firstValue) + .hasTrackErrors(trackErrors) + .hasTrackLongTasks(trackLongTasks) + .hasTrackFrustrations(trackFrustrations) + .hasViewTrackingStrategy(vts) + .hasTrackBackgroundEvents(trackBackgroundEvents) + } + } + + @Test + fun `𝕄 create config event 𝕎 handleEvent(SendTelemetry) { configuration with rum settings }`( + forge: Forge + ) { + // Given + val vitalsUpdateFrequency = forge.aValueFrom(VitalsUpdateFrequency::class.java) + val batchSize = forge.aValueFrom(BatchSize::class.java) + val uploadFrequency = forge.aValueFrom(UploadFrequency::class.java) + val configuration = Configuration.Builder( + logsEnabled = true, + tracesEnabled = true, + crashReportsEnabled = true, + rumEnabled = true + ) + .setBatchSize(batchSize) + .setUploadFrequency(uploadFrequency) + .setVitalsUpdateFrequency(vitalsUpdateFrequency) + .build() + val configRawEvent = forge.createRumRawTelemetryConfigurationEvent(configuration) + val rumContext = GlobalRum.getRumContext() + + // When + testedTelemetryHandler.handleEvent(configRawEvent, mockWriter) + + // Then + argumentCaptor { + verify(mockWriter).write(capture()) + assertConfigEventMatchesRawEvent(firstValue, configRawEvent, rumContext) + assertThat(firstValue) + .hasBatchSize(batchSize.windowDurationMs) + .hasBatchUploadFrequency(uploadFrequency.baseStepMs) + .hasMobileVitalsUpdatePeriod(vitalsUpdateFrequency.periodInMs) + } + } + + @Test + fun `𝕄 create config event 𝕎 handleEvent(SendTelemetry) { configuration with tracing settings }`( + @BoolForgery useTracing: Boolean, + forge: Forge + ) { + // Given + val configuration = Configuration.Builder( + logsEnabled = true, + tracesEnabled = useTracing || forge.aBool(), + crashReportsEnabled = true, + rumEnabled = true + ).build() + val configRawEvent = forge.createRumRawTelemetryConfigurationEvent(configuration) + val rumContext = GlobalRum.getRumContext() + if (useTracing) { + GlobalTracer.registerIfAbsent(mock()) + } + + // When + testedTelemetryHandler.handleEvent(configRawEvent, mockWriter) + + // Then + argumentCaptor { + verify(mockWriter).write(capture()) + assertConfigEventMatchesRawEvent(firstValue, configRawEvent, rumContext) + assertThat(firstValue) + .hasUseTracing(useTracing) + } + } + + @Test + fun `𝕄 create config event 𝕎 handleEvent(SendTelemetry) { configuration with interceptor }`( + forge: Forge + ) { + // Given + val trackNetworkRequests = forge.aBool() + val configuration = Configuration.Builder( + logsEnabled = true, + tracesEnabled = true, + crashReportsEnabled = true, + rumEnabled = true + ).build() + val configRawEvent = forge.createRumRawTelemetryConfigurationEvent(configuration) + val rumContext = GlobalRum.getRumContext() + + // When + if (trackNetworkRequests) { + testedTelemetryHandler.handleEvent( + RumRawEvent.SendTelemetry(TelemetryType.INTERCEPTOR_SETUP, "", null, null, null), + mockWriter + ) + } + testedTelemetryHandler.handleEvent(configRawEvent, mockWriter) + + // Then + argumentCaptor { + verify(mockWriter).write(capture()) + assertConfigEventMatchesRawEvent(firstValue, configRawEvent, rumContext) + assertThat(firstValue) + .hasTrackNetworkRequests(trackNetworkRequests) } } + // endregion + + // region Sampling + @Test fun `𝕄 not write event 𝕎 handleEvent(SendTelemetry) { event is not sampled }`(forge: Forge) { // Given @@ -175,7 +444,6 @@ internal class TelemetryEventHandlerTest { // Given val rawEvent = forge.createRumRawTelemetryEvent() val anotherEvent = rawEvent.copy() - val rumContext = GlobalRum.getRumContext() // When @@ -188,7 +456,8 @@ internal class TelemetryEventHandlerTest { Log.INFO, TelemetryEventHandler.ALREADY_SEEN_EVENT_MESSAGE.format( Locale.US, - TelemetryEventHandler.EventIdentity( + TelemetryEventId( + rawEvent.type, rawEvent.message, rawEvent.kind ) @@ -200,32 +469,13 @@ internal class TelemetryEventHandlerTest { .write(capture()) when (val capturedValue = lastValue) { is TelemetryDebugEvent -> { - TelemetryDebugEventAssert.assertThat(capturedValue).apply { - hasDate(rawEvent.eventTime.timestamp + fakeServerOffset) - hasSource(TelemetryDebugEvent.Source.ANDROID) - hasMessage(rawEvent.message) - hasService(TelemetryEventHandler.TELEMETRY_SERVICE_NAME) - hasVersion(mockSdkVersion) - hasApplicationId(rumContext.applicationId) - hasSessionId(rumContext.sessionId) - hasViewId(rumContext.viewId) - hasActionId(rumContext.actionId) - } + assertDebugEventMatchesRawEvent(capturedValue, rawEvent, rumContext) } is TelemetryErrorEvent -> { - TelemetryErrorEventAssert.assertThat(capturedValue).apply { - hasDate(rawEvent.eventTime.timestamp + fakeServerOffset) - hasSource(TelemetryErrorEvent.Source.ANDROID) - hasMessage(rawEvent.message) - hasService(TelemetryEventHandler.TELEMETRY_SERVICE_NAME) - hasVersion(mockSdkVersion) - hasApplicationId(rumContext.applicationId) - hasSessionId(rumContext.sessionId) - hasViewId(rumContext.viewId) - hasActionId(rumContext.actionId) - hasErrorStack(rawEvent.stack) - hasErrorKind(rawEvent.kind) - } + assertErrorEventMatchesRawEvent(capturedValue, rawEvent, rumContext) + } + is TelemetryConfigurationEvent -> { + assertConfigEventMatchesRawEvent(capturedValue, rawEvent, rumContext) } else -> throw IllegalArgumentException( "Unexpected type=${lastValue::class.jvmName} of the captured value." @@ -238,14 +488,14 @@ internal class TelemetryEventHandlerTest { @Test fun `𝕄 not write events over the limit 𝕎 handleEvent(SendTelemetry)`(forge: Forge) { // Given - val extraNumber = forge.aSmallInt() val events = forge.aList( - size = TelemetryEventHandler.MAX_EVENTS_PER_SESSION + extraNumber - ) { - createRumRawTelemetryEvent() - } + size = MAX_EVENTS_PER_SESSION_TEST * 5 + ) { createRumRawTelemetryEvent() } + // remove unwanted identity collisions + .groupBy { it.identity }.map { it.value.first() } + val extraNumber = events.size - MAX_EVENTS_PER_SESSION_TEST - val expectedInvocations = TelemetryEventHandler.MAX_EVENTS_PER_SESSION + val expectedInvocations = MAX_EVENTS_PER_SESSION_TEST val rumContext = GlobalRum.getRumContext() @@ -267,32 +517,25 @@ internal class TelemetryEventHandlerTest { allValues.withIndex().forEach { when (val capturedValue = it.value) { is TelemetryDebugEvent -> { - TelemetryDebugEventAssert.assertThat(capturedValue).apply { - hasDate(events[it.index].eventTime.timestamp + fakeServerOffset) - hasSource(TelemetryDebugEvent.Source.ANDROID) - hasMessage(events[it.index].message) - hasService(TelemetryEventHandler.TELEMETRY_SERVICE_NAME) - hasVersion(mockSdkVersion) - hasApplicationId(rumContext.applicationId) - hasSessionId(rumContext.sessionId) - hasViewId(rumContext.viewId) - hasActionId(rumContext.actionId) - } + assertDebugEventMatchesRawEvent( + capturedValue, + events[it.index], + rumContext + ) } is TelemetryErrorEvent -> { - TelemetryErrorEventAssert.assertThat(capturedValue).apply { - hasDate(events[it.index].eventTime.timestamp + fakeServerOffset) - hasSource(TelemetryErrorEvent.Source.ANDROID) - hasMessage(events[it.index].message) - hasService(TelemetryEventHandler.TELEMETRY_SERVICE_NAME) - hasVersion(mockSdkVersion) - hasApplicationId(rumContext.applicationId) - hasSessionId(rumContext.sessionId) - hasViewId(rumContext.viewId) - hasActionId(rumContext.actionId) - hasErrorStack(events[it.index].stack) - hasErrorKind(events[it.index].kind) - } + assertErrorEventMatchesRawEvent( + capturedValue, + events[it.index], + rumContext + ) + } + is TelemetryConfigurationEvent -> { + assertConfigEventMatchesRawEvent( + capturedValue, + events[it.index], + rumContext + ) } else -> throw IllegalArgumentException( "Unexpected type=${lastValue::class.jvmName} of the captured value." @@ -305,24 +548,22 @@ internal class TelemetryEventHandlerTest { @Test fun `𝕄 continue writing events after new session 𝕎 handleEvent(SendTelemetry)`(forge: Forge) { // Given - val extraNumber = forge.aSmallInt() val eventsInOldSession = forge.aList( - size = TelemetryEventHandler.MAX_EVENTS_PER_SESSION + extraNumber - ) { - createRumRawTelemetryEvent() - } + size = forge.anInt(MAX_EVENTS_PER_SESSION_TEST * 2, MAX_EVENTS_PER_SESSION_TEST * 4) + ) { createRumRawTelemetryEvent() } + // remove unwanted identity collisions + .groupBy { it.identity }.map { it.value.first() } + val extraNumber = eventsInOldSession.size - MAX_EVENTS_PER_SESSION_TEST - val eventsInNewSessionNumber = forge.aTinyInt() val eventsInNewSession = forge.aList( - size = eventsInNewSessionNumber - ) { - createRumRawTelemetryEvent() - } + size = forge.anInt(1, MAX_EVENTS_PER_SESSION_TEST) + ) { createRumRawTelemetryEvent() } + // remove unwanted identity collisions + .groupBy { it.identity }.map { it.value.first() } - val expectedInvocations = TelemetryEventHandler.MAX_EVENTS_PER_SESSION + - eventsInNewSessionNumber val expectedEvents = eventsInOldSession - .take(TelemetryEventHandler.MAX_EVENTS_PER_SESSION) + eventsInNewSession + .take(MAX_EVENTS_PER_SESSION_TEST) + eventsInNewSession + val expectedInvocations = expectedEvents.size val rumContext = GlobalRum.getRumContext() @@ -330,9 +571,7 @@ internal class TelemetryEventHandlerTest { eventsInOldSession.forEach { testedTelemetryHandler.handleEvent(it, mockWriter) } - testedTelemetryHandler.onSessionStarted(forge.aString(), forge.aBool()) - eventsInNewSession.forEach { testedTelemetryHandler.handleEvent(it, mockWriter) } @@ -350,34 +589,20 @@ internal class TelemetryEventHandlerTest { allValues.withIndex().forEach { when (val capturedValue = it.value) { is TelemetryDebugEvent -> { - TelemetryDebugEventAssert.assertThat(capturedValue).apply { - hasDate(expectedEvents[it.index].eventTime.timestamp + fakeServerOffset) - hasSource(TelemetryDebugEvent.Source.ANDROID) - hasMessage(expectedEvents[it.index].message) - hasService(TelemetryEventHandler.TELEMETRY_SERVICE_NAME) - hasVersion(mockSdkVersion) - hasApplicationId(rumContext.applicationId) - hasSessionId(rumContext.sessionId) - hasViewId(rumContext.viewId) - hasActionId(rumContext.actionId) - } + assertDebugEventMatchesRawEvent( + capturedValue, + expectedEvents[it.index], + rumContext + ) } is TelemetryErrorEvent -> { - TelemetryErrorEventAssert.assertThat(capturedValue).apply { - hasDate(expectedEvents[it.index].eventTime.timestamp + fakeServerOffset) - hasSource(TelemetryErrorEvent.Source.ANDROID) - hasMessage(expectedEvents[it.index].message) - hasService(TelemetryEventHandler.TELEMETRY_SERVICE_NAME) - hasVersion(mockSdkVersion) - hasApplicationId(rumContext.applicationId) - hasSessionId(rumContext.sessionId) - hasViewId(rumContext.viewId) - hasActionId(rumContext.actionId) - hasErrorStack(expectedEvents[it.index].stack) - hasErrorKind( - expectedEvents[it.index].kind - ) - } + assertErrorEventMatchesRawEvent( + capturedValue, + expectedEvents[it.index], + rumContext + ) + } + is TelemetryConfigurationEvent -> { } else -> throw IllegalArgumentException( "Unexpected type=${lastValue::class.jvmName} of the captured value." @@ -391,37 +616,122 @@ internal class TelemetryEventHandlerTest { fun `𝕄 count the limit only after the sampling 𝕎 handleEvent(SendTelemetry)`(forge: Forge) { // Given whenever(mockSampler.sample()) doAnswer { forge.aBool() } - - val extraNumber = forge.aTinyInt() val events = forge.aList( - size = TelemetryEventHandler.MAX_EVENTS_PER_SESSION + extraNumber - ) { - createRumRawTelemetryEvent() - } - - val expectedWrites = events.size / 2 + size = MAX_EVENTS_PER_SESSION_TEST * 5 + ) { createRumRawTelemetryEvent() } + // remove unwanted identity collisions + .groupBy { it.identity }.map { it.value.first() } + .take(MAX_EVENTS_PER_SESSION_TEST) + val repeats = 10 + val expectedWrites = MAX_EVENTS_PER_SESSION_TEST * repeats / 2 // When - events.forEach { - testedTelemetryHandler.handleEvent(it, mockWriter) + repeat(repeats) { + testedTelemetryHandler.onSessionStarted(forge.aString(), false) + events.forEach { + testedTelemetryHandler.handleEvent(it, mockWriter) + } } // Then - verifyZeroInteractions(logger.mockSdkLogHandler) - argumentCaptor { verify(mockWriter, atLeastOnce()) .write(capture()) assertThat(allValues.size).isCloseTo(expectedWrites, Percentage.withPercentage(25.0)) } + verifyZeroInteractions(logger.mockSdkLogHandler) + } + + // endregion + + // region Assertions + + private fun assertDebugEventMatchesRawEvent( + actual: TelemetryDebugEvent, + rawEvent: RumRawEvent.SendTelemetry, + rumContext: RumContext + ) { + assertThat(actual) + .hasDate(rawEvent.eventTime.timestamp + fakeServerOffset) + .hasSource(TelemetryDebugEvent.Source.ANDROID) + .hasMessage(rawEvent.message) + .hasService(TelemetryEventHandler.TELEMETRY_SERVICE_NAME) + .hasVersion(mockSdkVersion) + .hasApplicationId(rumContext.applicationId) + .hasSessionId(rumContext.sessionId) + .hasViewId(rumContext.viewId) + .hasActionId(rumContext.actionId) + } + + private fun assertErrorEventMatchesRawEvent( + actual: TelemetryErrorEvent, + rawEvent: RumRawEvent.SendTelemetry, + rumContext: RumContext + ) { + assertThat(actual) + .hasDate(rawEvent.eventTime.timestamp + fakeServerOffset) + .hasSource(TelemetryErrorEvent.Source.ANDROID) + .hasMessage(rawEvent.message) + .hasService(TelemetryEventHandler.TELEMETRY_SERVICE_NAME) + .hasVersion(mockSdkVersion) + .hasApplicationId(rumContext.applicationId) + .hasSessionId(rumContext.sessionId) + .hasViewId(rumContext.viewId) + .hasActionId(rumContext.actionId) + .hasErrorStack(rawEvent.stack) + .hasErrorKind(rawEvent.kind) + } + + private fun assertConfigEventMatchesRawEvent( + actual: TelemetryConfigurationEvent, + rawEvent: RumRawEvent.SendTelemetry, + rumContext: RumContext + ) { + assertThat(actual) + .hasDate(rawEvent.eventTime.timestamp + fakeServerOffset) + .hasSource(TelemetryConfigurationEvent.Source.ANDROID) + .hasService(TelemetryEventHandler.TELEMETRY_SERVICE_NAME) + .hasVersion(mockSdkVersion) + .hasApplicationId(rumContext.applicationId) + .hasSessionId(rumContext.sessionId) + .hasViewId(rumContext.viewId) + .hasActionId(rumContext.actionId) } - // region private + // endregion + + // region Forgeries + + private fun Forge.aViewTrackingStrategy(vts: VTS): ViewTrackingStrategy { + return when (vts) { + VTS.ACTIVITYVIEWTRACKINGSTRATEGY -> ActivityViewTrackingStrategy( + trackExtras = aBool(), + componentPredicate = mock() + ) + VTS.FRAGMENTVIEWTRACKINGSTRATEGY -> FragmentViewTrackingStrategy( + trackArguments = aBool(), + supportFragmentComponentPredicate = mock(), + defaultFragmentComponentPredicate = mock() + ) + VTS.MIXEDVIEWTRACKINGSTRATEGY -> MixedViewTrackingStrategy( + trackExtras = aBool(), + componentPredicate = mock(), + supportFragmentComponentPredicate = mock(), + defaultFragmentComponentPredicate = mock() + ) + VTS.NAVIGATIONVIEWTRACKINGSTRATEGY -> NavigationViewTrackingStrategy( + navigationViewId = anInt(), + trackArguments = aBool(), + componentPredicate = mock() + ) + } + } private fun Forge.createRumRawTelemetryEvent(): RumRawEvent.SendTelemetry { return anElementFrom( createRumRawTelemetryDebugEvent(), - createRumRawTelemetryErrorEvent() + createRumRawTelemetryErrorEvent(), + createRumRawTelemetryConfigurationEvent() ) } @@ -430,6 +740,7 @@ internal class TelemetryEventHandlerTest { TelemetryType.DEBUG, aString(), null, + null, null ) } @@ -440,13 +751,29 @@ internal class TelemetryEventHandlerTest { TelemetryType.ERROR, aString(), throwable?.loggableStackTrace(), - throwable?.javaClass?.canonicalName + throwable?.javaClass?.canonicalName, + null + ) + } + + private fun Forge.createRumRawTelemetryConfigurationEvent( + configuration: Configuration? = null + ): RumRawEvent.SendTelemetry { + return RumRawEvent.SendTelemetry( + TelemetryType.CONFIGURATION, + "", + null, + null, + configuration ?: getForgery() ) } // endregion companion object { + + private const val MAX_EVENTS_PER_SESSION_TEST = 10 + val rumMonitor = GlobalRumMonitorTestConfiguration() val logger = LoggerTestConfiguration() diff --git a/dd-sdk-android/src/test/kotlin/com/datadog/android/utils/config/ApplicationContextTestConfiguration.kt b/dd-sdk-android/src/test/kotlin/com/datadog/android/utils/config/ApplicationContextTestConfiguration.kt index bddba79a86..b120050cbe 100644 --- a/dd-sdk-android/src/test/kotlin/com/datadog/android/utils/config/ApplicationContextTestConfiguration.kt +++ b/dd-sdk-android/src/test/kotlin/com/datadog/android/utils/config/ApplicationContextTestConfiguration.kt @@ -87,8 +87,14 @@ internal open class ApplicationContextTestConfiguration(klass: Clas private fun mockPackageManager() { mockPackageManager = mock() + whenever( + mockPackageManager.getPackageInfo( + fakePackageName, + PackageManager.PackageInfoFlags.of(0) + ) + ) doReturn fakePackageInfo + @Suppress("DEPRECATION") whenever(mockPackageManager.getPackageInfo(fakePackageName, 0)) doReturn fakePackageInfo - whenever(mockPackageManager.getApplicationInfo(fakePackageName, 0)) doReturn fakeAppInfo } // endregion diff --git a/dd-sdk-android/src/test/kotlin/com/datadog/android/utils/forge/ConfigurationForgeryFactory.kt b/dd-sdk-android/src/test/kotlin/com/datadog/android/utils/forge/ConfigurationForgeryFactory.kt new file mode 100644 index 0000000000..5d7ac5a74e --- /dev/null +++ b/dd-sdk-android/src/test/kotlin/com/datadog/android/utils/forge/ConfigurationForgeryFactory.kt @@ -0,0 +1,25 @@ +/* + * Unless explicitly stated otherwise all files in this repository are licensed under the Apache License Version 2.0. + * This product includes software developed at Datadog (https://www.datadoghq.com/). + * Copyright 2016-Present Datadog, Inc. + */ + +package com.datadog.android.utils.forge + +import com.datadog.android.core.configuration.Configuration +import fr.xgouchet.elmyr.Forge +import fr.xgouchet.elmyr.ForgeryFactory + +internal class ConfigurationForgeryFactory : + ForgeryFactory { + override fun getForgery(forge: Forge): Configuration { + return Configuration( + coreConfig = forge.getForgery(), + logsConfig = forge.getForgery(), + tracesConfig = forge.getForgery(), + crashReportConfig = forge.getForgery(), + rumConfig = forge.getForgery(), + additionalConfig = forge.aMap { aString() to aString() } + ) + } +} diff --git a/dd-sdk-android/src/test/kotlin/com/datadog/android/utils/forge/ConfigurationRumForgeryFactory.kt b/dd-sdk-android/src/test/kotlin/com/datadog/android/utils/forge/ConfigurationRumForgeryFactory.kt index 51ae7ea5a8..b3f04ad139 100644 --- a/dd-sdk-android/src/test/kotlin/com/datadog/android/utils/forge/ConfigurationRumForgeryFactory.kt +++ b/dd-sdk-android/src/test/kotlin/com/datadog/android/utils/forge/ConfigurationRumForgeryFactory.kt @@ -8,6 +8,10 @@ package com.datadog.android.utils.forge import com.datadog.android.core.configuration.Configuration import com.datadog.android.core.configuration.VitalsUpdateFrequency +import com.datadog.android.rum.tracking.ActivityViewTrackingStrategy +import com.datadog.android.rum.tracking.FragmentViewTrackingStrategy +import com.datadog.android.rum.tracking.MixedViewTrackingStrategy +import com.datadog.android.rum.tracking.NavigationViewTrackingStrategy import com.nhaarman.mockitokotlin2.mock import fr.xgouchet.elmyr.Forge import fr.xgouchet.elmyr.ForgeryFactory @@ -21,7 +25,14 @@ internal class ConfigurationRumForgeryFactory : samplingRate = forge.aFloat(0f, 100f), telemetrySamplingRate = forge.aFloat(0f, 100f), userActionTrackingStrategy = mock(), - viewTrackingStrategy = mock(), + viewTrackingStrategy = forge.anElementFrom( + ActivityViewTrackingStrategy(forge.aBool(), mock()), + FragmentViewTrackingStrategy(forge.aBool(), mock(), mock()), + MixedViewTrackingStrategy(forge.aBool(), mock(), mock(), mock()), + NavigationViewTrackingStrategy(forge.anInt(), forge.aBool(), mock()), + mock(), + null + ), rumEventMapper = mock(), longTaskTrackingStrategy = mock(), backgroundEventTracking = forge.aBool(), diff --git a/dd-sdk-android/src/test/kotlin/com/datadog/android/utils/forge/Configurator.kt b/dd-sdk-android/src/test/kotlin/com/datadog/android/utils/forge/Configurator.kt index a58603ee51..b9925d3a3c 100644 --- a/dd-sdk-android/src/test/kotlin/com/datadog/android/utils/forge/Configurator.kt +++ b/dd-sdk-android/src/test/kotlin/com/datadog/android/utils/forge/Configurator.kt @@ -18,6 +18,7 @@ internal class Configurator : // Datadog Core forge.addFactory(ConfigurationCoreForgeryFactory()) forge.addFactory(SecurityConfigForgeryFactory()) + forge.addFactory(ConfigurationForgeryFactory()) forge.addFactory(ConfigurationFeatureForgeryFactory()) forge.addFactory(ConfigurationLogForgeryFactory()) forge.addFactory(ConfigurationCrashReportForgeryFactory()) @@ -56,6 +57,7 @@ internal class Configurator : // Telemetry forge.addFactory(TelemetryDebugEventForgeryFactory()) forge.addFactory(TelemetryErrorEventForgeryFactory()) + forge.addFactory(TelemetryConfigurationEventForgeryFactory()) // NDK Crash forge.addFactory(NdkCrashLogForgeryFactory()) diff --git a/dd-sdk-android/src/test/kotlin/com/datadog/android/utils/forge/TelemetryConfigurationEventForgeryFactory.kt b/dd-sdk-android/src/test/kotlin/com/datadog/android/utils/forge/TelemetryConfigurationEventForgeryFactory.kt new file mode 100644 index 0000000000..587c627837 --- /dev/null +++ b/dd-sdk-android/src/test/kotlin/com/datadog/android/utils/forge/TelemetryConfigurationEventForgeryFactory.kt @@ -0,0 +1,91 @@ +/* + * Unless explicitly stated otherwise all files in this repository are licensed under the Apache License Version 2.0. + * This product includes software developed at Datadog (https://www.datadoghq.com/). + * Copyright 2016-Present Datadog, Inc. + */ + +package com.datadog.android.utils.forge + +import com.datadog.android.telemetry.model.TelemetryConfigurationEvent +import fr.xgouchet.elmyr.Forge +import fr.xgouchet.elmyr.ForgeryFactory +import fr.xgouchet.elmyr.jvm.ext.aTimestamp +import java.util.UUID + +internal class TelemetryConfigurationEventForgeryFactory : + ForgeryFactory { + + override fun getForgery(forge: Forge): TelemetryConfigurationEvent { + return TelemetryConfigurationEvent( + date = forge.aTimestamp(), + source = forge.aValueFrom(TelemetryConfigurationEvent.Source::class.java), + service = forge.anAlphabeticalString(), + version = forge.anAlphabeticalString(), + application = forge.aNullable { + TelemetryConfigurationEvent.Application( + forge.getForgery().toString() + ) + }, + session = forge.aNullable { + TelemetryConfigurationEvent.Session( + forge.getForgery().toString() + ) + }, + view = forge.aNullable { + TelemetryConfigurationEvent.View( + forge.getForgery().toString() + ) + }, + action = forge.aNullable { + TelemetryConfigurationEvent.Action( + forge.getForgery().toString() + ) + }, + dd = TelemetryConfigurationEvent.Dd(), + telemetry = TelemetryConfigurationEvent.Telemetry( + configuration = TelemetryConfigurationEvent.Configuration( + forge.aNullable { aLong() }, + forge.aNullable { aLong() }, + forge.aNullable { aLong() }, + forge.aNullable { aLong() }, + forge.aNullable { aLong() }, + forge.aNullable { aLong() }, + forge.aNullable { aLong() }, + forge.aNullable { aBool() }, + forge.aNullable { aBool() }, + forge.aNullable { aBool() }, + forge.aNullable { aBool() }, + forge.aNullable { aBool() }, + forge.aNullable { aBool() }, + forge.aNullable { aString() }, + forge.aNullable { aBool() }, + forge.aNullable { aString() }, + forge.aNullable { aBool() }, + forge.aNullable { aBool() }, + forge.aNullable { aBool() }, + forge.aNullable { aBool() }, + forge.aNullable { aBool() }, + forge.aNullable { aList { aString() } }, + forge.aNullable { aList { aString() } }, + forge.aNullable { aBool() }, + forge.aNullable { aValueFrom(TelemetryConfigurationEvent.ViewTrackingStrategy::class.java) }, + forge.aNullable { aBool() }, + forge.aNullable { aLong() }, + forge.aNullable { aBool() }, + forge.aNullable { aBool() }, + forge.aNullable { aBool() }, + forge.aNullable { aBool() }, + forge.aNullable { aBool() }, + forge.aNullable { aBool() }, + forge.aNullable { aBool() }, + forge.aNullable { aBool() }, + forge.aNullable { aBool() }, + forge.aNullable { aString() }, + forge.aNullable { aBool() }, + forge.aNullable { aLong() }, + forge.aNullable { aLong() } + ) + ) + ) + } +} diff --git a/detekt.yml b/detekt.yml index 5883a4002e..bdf35b8748 100644 --- a/detekt.yml +++ b/detekt.yml @@ -588,6 +588,7 @@ datadog: knownThrowingCalls: # region Android - "android.app.Activity.findNavController(kotlin.Int):java.lang.IllegalStateException" + - "android.content.pm.PackageManager.getPackageInfo(kotlin.String, android.content.pm.PackageManager.PackageInfoFlags):android.content.pm.PackageManager.NameNotFoundException" - "android.content.pm.PackageManager.getPackageInfo(kotlin.String, kotlin.Int):android.content.pm.PackageManager.NameNotFoundException" - "android.content.res.Resources.getResourceEntryName(kotlin.Int):android.content.res.Resources.NotFoundException" - "android.database.sqlite.SQLiteDatabase.beginTransaction():java.lang.IllegalStateException" @@ -636,6 +637,7 @@ datadog: # endregion # region Java Concurrency - "java.lang.Thread.constructor(java.lang.Runnable, kotlin.String):java.lang.NullPointerException,java.lang.SecurityException,java.lang.IllegalArgumentException" + - "java.lang.Thread.constructor(java.lang.Runnable, kotlin.String):java.lang.NullPointerException,java.lang.SecurityException,java.lang.IllegalArgumentException" - "java.lang.Thread.sleep(kotlin.Long):java.lang.IllegalArgumentException,java.lang.InterruptedException" - "java.lang.Thread.interrupt():java.lang.SecurityException" - "java.util.concurrent.BlockingQueue.drainTo(kotlin.collections.MutableCollection):java.lang.UnsupportedOperationException,java.lang.ClassCastException,java.lang.NullPointerException,java.lang.IllegalArgumentException" @@ -743,6 +745,7 @@ datadog: - "android.content.IntentFilter.constructor()" - "android.content.IntentFilter.constructor(kotlin.String)" - "android.content.pm.PackageManager.hasSystemFeature(kotlin.String)" + - "android.content.pm.PackageManager.PackageInfoFlags.of(kotlin.Long)" - "android.content.res.AssetManager.open(kotlin.String, kotlin.Int)" - "android.database.DatabaseErrorHandler.onCorruption(android.database.sqlite.SQLiteDatabase)" - "android.database.DefaultDatabaseErrorHandler.constructor()" @@ -1061,10 +1064,11 @@ datadog: - "kotlin.collections.MutableMap.remove(com.datadog.android.rum.internal.vitals.VitalListener)" - "kotlin.collections.MutableMap.remove(kotlin.String)" - "kotlin.collections.MutableSet.add(com.datadog.android.telemetry.internal.TelemetryEventHandler.EventIdentity)" + - "kotlin.collections.MutableSet.add(com.datadog.android.telemetry.internal.TelemetryEventId)" - "kotlin.collections.MutableSet.add(kotlin.String)" - "kotlin.collections.MutableSet.addAll(kotlin.collections.Collection)" - "kotlin.collections.MutableSet.clear()" - - "kotlin.collections.MutableSet.contains(com.datadog.android.telemetry.internal.TelemetryEventHandler.EventIdentity)" + - "kotlin.collections.MutableSet.contains(com.datadog.android.telemetry.internal.TelemetryEventId)" - "kotlin.collections.MutableSet.filter(kotlin.Function1)" - "kotlin.collections.MutableSet.forEach(kotlin.Function1)" - "kotlin.collections.MutableSet.joinToString(kotlin.CharSequence, kotlin.CharSequence, kotlin.CharSequence, kotlin.Int, kotlin.CharSequence, kotlin.Function1?)" @@ -1084,6 +1088,7 @@ datadog: - "kotlin.Double.rangeTo(kotlin.Double)" - "kotlin.Double.toInt()" - "kotlin.Double.toLong()" + - "kotlin.Float.toLong()" - "kotlin.Int.inv()" - "kotlin.Int.toChar()" - "kotlin.Int.toLong()" diff --git a/dogfood.py b/dogfood.py index 86bbd8fe6d..7c71ec98ad 100755 --- a/dogfood.py +++ b/dogfood.py @@ -132,7 +132,6 @@ def update_dependant(version: str, target: str, gh_token: str, dry_run: bool) -> repo.git.checkout('HEAD', b=branch_name) previous_version = generate_target_code(target, temp_dir_path, version) - update_version_table(target, temp_dir_path, version) if not repo.is_dirty(): print("Nothing to commit, all is in order-") diff --git a/gradle/libs.versions.toml b/gradle/libs.versions.toml index a7070c413a..5b781482fd 100644 --- a/gradle/libs.versions.toml +++ b/gradle/libs.versions.toml @@ -7,7 +7,7 @@ okHttp = "3.12.13" kronosNTP = "0.0.1-alpha11" # Android -androidToolsPlugin = "7.3.0" +androidToolsPlugin = "7.3.1" androidXAnnotations = "1.1.0" androidXAppCompat = "1.3.0" androidXCore = "1.3.1" diff --git a/instrumented/integration/src/androidTest/kotlin/com/datadog/android/sdk/integration/rum/RumTest.kt b/instrumented/integration/src/androidTest/kotlin/com/datadog/android/sdk/integration/rum/RumTest.kt index df0cc64564..3d7914d768 100644 --- a/instrumented/integration/src/androidTest/kotlin/com/datadog/android/sdk/integration/rum/RumTest.kt +++ b/instrumented/integration/src/androidTest/kotlin/com/datadog/android/sdk/integration/rum/RumTest.kt @@ -32,7 +32,11 @@ internal abstract class RumTest> .isNotNull .hasHeader(HeadersAssert.HEADER_CT, RuntimeConfig.CONTENT_TYPE_TEXT) if (request.textBody != null) { - sentGestureEvents += rumPayloadToJsonList(request.textBody) + val rumPayload = rumPayloadToJsonList(request.textBody).filterNot { + it.has("type") && + it.getAsJsonPrimitive("type").asString == "telemetry" + } + sentGestureEvents += rumPayload } } sentGestureEvents.verifyEventMatches(expectedEvents) diff --git a/instrumented/integration/src/androidTest/kotlin/com/datadog/android/sdk/utils/BundleExtensions.kt b/instrumented/integration/src/androidTest/kotlin/com/datadog/android/sdk/utils/BundleExtensions.kt index e96bcc9841..25c154264b 100644 --- a/instrumented/integration/src/androidTest/kotlin/com/datadog/android/sdk/utils/BundleExtensions.kt +++ b/instrumented/integration/src/androidTest/kotlin/com/datadog/android/sdk/utils/BundleExtensions.kt @@ -15,6 +15,9 @@ fun Bundle?.asMap(): Map { return keySet() .fold(mutableMapOf()) { map, key -> + // TODO RUMM-2717 Bundle#get is deprecated, but there is no replacement for it. + // Issue is opened in the Google Issue Tracker. + @Suppress("DEPRECATION") map[key] = this[key] map } diff --git a/instrumented/nightly-tests/src/androidTest/kotlin/com/datadog/android/nightly/main/NotTestableApis.kt b/instrumented/nightly-tests/src/androidTest/kotlin/com/datadog/android/nightly/main/NotTestableApis.kt index 398c211c33..6890d217b3 100644 --- a/instrumented/nightly-tests/src/androidTest/kotlin/com/datadog/android/nightly/main/NotTestableApis.kt +++ b/instrumented/nightly-tests/src/androidTest/kotlin/com/datadog/android/nightly/main/NotTestableApis.kt @@ -11,6 +11,7 @@ package com.datadog.android.nightly.main * apiMethodSignature: com.datadog.android.Datadog#fun enableRumDebugging(Boolean) * apiMethodSignature: com.datadog.android.Datadog#fun isInitialized(): Boolean * apiMethodSignature: com.datadog.android.Datadog#fun setVerbosity(Int) + * apiMethodSignature: com.datadog.android.Datadog#fun addUserExtraInfo(Map = emptyMap()) * apiMethodSignature: com.datadog.android.core.configuration.Configuration$Builder#fun sampleTelemetry(Float): Builder * apiMethodSignature: com.datadog.android.core.configuration.Configuration$Builder#fun setUploadFrequency(UploadFrequency): Builder * apiMethodSignature: com.datadog.android.core.configuration.Configuration$Builder#fun setProxy(java.net.Proxy, okhttp3.Authenticator?): Builder @@ -23,12 +24,17 @@ package com.datadog.android.nightly.main * apiMethodSignature: com.datadog.android.core.configuration.Configuration$Builder#fun useCustomTracesEndpoint(String): Builder * apiMethodSignature: com.datadog.android.core.configuration.Configuration$Builder#fun useSite(com.datadog.android.DatadogSite): Builder * apiMethodSignature: com.datadog.android.core.configuration.Configuration$Builder#fun addPlugin(com.datadog.android.plugin.DatadogPlugin, com.datadog.android.plugin.Feature): Builder + * apiMethodSignature: com.datadog.android.rum.RumMonitor$Builder#fun setSessionListener(RumSessionListener): Builder * apiMethodSignature: com.datadog.android.log.Logger#fun log(Int, String, Throwable? = null, Map = emptyMap()) * apiMethodSignature: com.datadog.android.plugin.DatadogPluginConfig#constructor(android.content.Context, String, String, com.datadog.android.privacy.TrackingConsent) * apiMethodSignature: com.datadog.android.log.Logger$Builder#fun setLogcatLogsEnabled(Boolean): Builder * apiMethodSignature: com.datadog.android._InternalProxy$_TelemetryProxy#fun debug(String) * apiMethodSignature: com.datadog.android._InternalProxy$_TelemetryProxy#fun error(String, String?, String?) * apiMethodSignature: com.datadog.android._InternalProxy$_TelemetryProxy#fun error(String, Throwable? = null) + * apiMethodSignature: com.datadog.android._InternalProxy#fun setCustomAppVersion(String) * apiMethodSignature: com.datadog.android.rum._RumInternalProxy#fun addLongTask(Long, String) * apiMethodSignature: com.datadog.android.core.configuration.Configuration$Builder#fun disableInteractionTracking(): Builder + * apiMethodSignature: com.datadog.android.rum.resource.RumResourceInputStream#constructor(java.io.InputStream, String) + * apiMethodSignature: com.datadog.android.rum.tracking.NavigationViewTrackingStrategy#fun startTracking() + * apiMethodSignature: com.datadog.android.rum.tracking.NavigationViewTrackingStrategy#fun stopTracking() */ diff --git a/instrumented/nightly-tests/src/androidTest/kotlin/com/datadog/android/nightly/rum/RumResourceTrackingE2ETests.kt b/instrumented/nightly-tests/src/androidTest/kotlin/com/datadog/android/nightly/rum/RumResourceTrackingE2ETests.kt index ba26e2871a..b65b999dd8 100644 --- a/instrumented/nightly-tests/src/androidTest/kotlin/com/datadog/android/nightly/rum/RumResourceTrackingE2ETests.kt +++ b/instrumented/nightly-tests/src/androidTest/kotlin/com/datadog/android/nightly/rum/RumResourceTrackingE2ETests.kt @@ -33,7 +33,7 @@ internal class RumResourceTrackingE2ETests { val nightlyTestRule = NightlyTestRule() /** - * apiMethodSignature: com.datadog.android.core.configuration.Configuration$Builder#fun useViewTrackingStrategy(com.datadog.android.rum.tracking.ViewTrackingStrategy): Builder + * apiMethodSignature: com.datadog.android.core.configuration.Configuration$Builder#fun useViewTrackingStrategy(com.datadog.android.rum.tracking.ViewTrackingStrategy?): Builder * apiMethodSignature: com.datadog.android.rum.tracking.ActivityViewTrackingStrategy#constructor(Boolean, ComponentPredicate = AcceptAllActivities()) * apiMethodSignature: com.datadog.android.rum.RumInterceptor#constructor(List = emptyList(), RumResourceAttributesProvider = NoOpRumResourceAttributesProvider(), Float = DEFAULT_TRACE_SAMPLING_RATE) */ @@ -57,7 +57,7 @@ internal class RumResourceTrackingE2ETests { } /** - * apiMethodSignature: com.datadog.android.core.configuration.Configuration$Builder#fun useViewTrackingStrategy(com.datadog.android.rum.tracking.ViewTrackingStrategy): Builder + * apiMethodSignature: com.datadog.android.core.configuration.Configuration$Builder#fun useViewTrackingStrategy(com.datadog.android.rum.tracking.ViewTrackingStrategy?): Builder * apiMethodSignature: com.datadog.android.rum.tracking.ActivityViewTrackingStrategy#constructor(Boolean, ComponentPredicate = AcceptAllActivities()) * apiMethodSignature: com.datadog.android.rum.RumInterceptor#constructor(List = emptyList(), RumResourceAttributesProvider = NoOpRumResourceAttributesProvider(), Float = DEFAULT_TRACE_SAMPLING_RATE) * apiMethodSignature: com.datadog.android.DatadogInterceptor#constructor(List, com.datadog.android.tracing.TracedRequestListener = NoOpTracedRequestListener(), com.datadog.android.rum.RumResourceAttributesProvider = NoOpRumResourceAttributesProvider(), Float = DEFAULT_TRACE_SAMPLING_RATE) @@ -82,7 +82,7 @@ internal class RumResourceTrackingE2ETests { } /** - * apiMethodSignature: com.datadog.android.core.configuration.Configuration$Builder#fun useViewTrackingStrategy(com.datadog.android.rum.tracking.ViewTrackingStrategy): Builder + * apiMethodSignature: com.datadog.android.core.configuration.Configuration$Builder#fun useViewTrackingStrategy(com.datadog.android.rum.tracking.ViewTrackingStrategy?): Builder * apiMethodSignature: com.datadog.android.rum.tracking.ActivityViewTrackingStrategy#constructor(Boolean, ComponentPredicate = AcceptAllActivities()) * apiMethodSignature: com.datadog.android.tracing.TracingInterceptor#constructor(TracedRequestListener = NoOpTracedRequestListener(), Float = DEFAULT_TRACE_SAMPLING_RATE) * apiMethodSignature: com.datadog.android.DatadogInterceptor#constructor(com.datadog.android.tracing.TracedRequestListener = NoOpTracedRequestListener(), com.datadog.android.rum.RumResourceAttributesProvider = NoOpRumResourceAttributesProvider(), Float = DEFAULT_TRACE_SAMPLING_RATE) @@ -114,7 +114,7 @@ internal class RumResourceTrackingE2ETests { } /** - * apiMethodSignature: com.datadog.android.core.configuration.Configuration$Builder#fun useViewTrackingStrategy(com.datadog.android.rum.tracking.ViewTrackingStrategy): Builder + * apiMethodSignature: com.datadog.android.core.configuration.Configuration$Builder#fun useViewTrackingStrategy(com.datadog.android.rum.tracking.ViewTrackingStrategy?): Builder * apiMethodSignature: com.datadog.android.rum.tracking.ActivityViewTrackingStrategy#constructor(Boolean, ComponentPredicate = AcceptAllActivities()) * apiMethodSignature: com.datadog.android.rum.RumInterceptor#constructor(List = emptyList(), RumResourceAttributesProvider = NoOpRumResourceAttributesProvider(), Float = DEFAULT_TRACE_SAMPLING_RATE) * apiMethodSignature: com.datadog.android.tracing.TracingInterceptor#constructor(List, TracedRequestListener = NoOpTracedRequestListener(), Float = DEFAULT_TRACE_SAMPLING_RATE) @@ -142,7 +142,7 @@ internal class RumResourceTrackingE2ETests { } /** - * apiMethodSignature: com.datadog.android.core.configuration.Configuration$Builder#fun useViewTrackingStrategy(com.datadog.android.rum.tracking.ViewTrackingStrategy): Builder + * apiMethodSignature: com.datadog.android.core.configuration.Configuration$Builder#fun useViewTrackingStrategy(com.datadog.android.rum.tracking.ViewTrackingStrategy?): Builder * apiMethodSignature: com.datadog.android.rum.tracking.ActivityViewTrackingStrategy#constructor(Boolean, ComponentPredicate = AcceptAllActivities()) * apiMethodSignature: com.datadog.android.rum.RumInterceptor#constructor(List = emptyList(), RumResourceAttributesProvider = NoOpRumResourceAttributesProvider(), Float = DEFAULT_TRACE_SAMPLING_RATE) * apiMethodSignature: com.datadog.android.DatadogInterceptor#constructor(List, com.datadog.android.tracing.TracedRequestListener = NoOpTracedRequestListener(), com.datadog.android.rum.RumResourceAttributesProvider = NoOpRumResourceAttributesProvider(), Float = DEFAULT_TRACE_SAMPLING_RATE) @@ -171,7 +171,7 @@ internal class RumResourceTrackingE2ETests { } /** - * apiMethodSignature: com.datadog.android.core.configuration.Configuration$Builder#fun useViewTrackingStrategy(com.datadog.android.rum.tracking.ViewTrackingStrategy): Builder + * apiMethodSignature: com.datadog.android.core.configuration.Configuration$Builder#fun useViewTrackingStrategy(com.datadog.android.rum.tracking.ViewTrackingStrategy?): Builder * apiMethodSignature: com.datadog.android.rum.tracking.ActivityViewTrackingStrategy#constructor(Boolean, ComponentPredicate = AcceptAllActivities()) * apiMethodSignature: com.datadog.android.rum.RumInterceptor#constructor(List = emptyList(), RumResourceAttributesProvider = NoOpRumResourceAttributesProvider(), Float = DEFAULT_TRACE_SAMPLING_RATE) */ diff --git a/instrumented/nightly-tests/src/androidTest/kotlin/com/datadog/android/nightly/rum/RumViewTrackingE2ETests.kt b/instrumented/nightly-tests/src/androidTest/kotlin/com/datadog/android/nightly/rum/RumViewTrackingE2ETests.kt index ebad1e5f9e..5949269d15 100644 --- a/instrumented/nightly-tests/src/androidTest/kotlin/com/datadog/android/nightly/rum/RumViewTrackingE2ETests.kt +++ b/instrumented/nightly-tests/src/androidTest/kotlin/com/datadog/android/nightly/rum/RumViewTrackingE2ETests.kt @@ -40,7 +40,7 @@ internal class RumViewTrackingE2ETests { val nightlyTestRule = NightlyTestRule() /** - * apiMethodSignature: com.datadog.android.core.configuration.Configuration$Builder#fun useViewTrackingStrategy(com.datadog.android.rum.tracking.ViewTrackingStrategy): Builder + * apiMethodSignature: com.datadog.android.core.configuration.Configuration$Builder#fun useViewTrackingStrategy(com.datadog.android.rum.tracking.ViewTrackingStrategy?): Builder * apiMethodSignature: com.datadog.android.rum.tracking.ActivityViewTrackingStrategy#constructor(Boolean, ComponentPredicate = AcceptAllActivities()) */ @Test @@ -63,7 +63,7 @@ internal class RumViewTrackingE2ETests { } /** - * apiMethodSignature: com.datadog.android.core.configuration.Configuration$Builder#fun useViewTrackingStrategy(com.datadog.android.rum.tracking.ViewTrackingStrategy): Builder + * apiMethodSignature: com.datadog.android.core.configuration.Configuration$Builder#fun useViewTrackingStrategy(com.datadog.android.rum.tracking.ViewTrackingStrategy?): Builder * apiMethodSignature: com.datadog.android.rum.tracking.ActivityViewTrackingStrategy#constructor(Boolean, ComponentPredicate = AcceptAllActivities()) */ @Test @@ -99,7 +99,7 @@ internal class RumViewTrackingE2ETests { } /** - * apiMethodSignature: com.datadog.android.core.configuration.Configuration$Builder#fun useViewTrackingStrategy(com.datadog.android.rum.tracking.ViewTrackingStrategy): Builder + * apiMethodSignature: com.datadog.android.core.configuration.Configuration$Builder#fun useViewTrackingStrategy(com.datadog.android.rum.tracking.ViewTrackingStrategy?): Builder * apiMethodSignature: com.datadog.android.rum.tracking.ActivityViewTrackingStrategy#constructor(Boolean, ComponentPredicate = AcceptAllActivities()) */ @Test @@ -135,7 +135,7 @@ internal class RumViewTrackingE2ETests { } /** - * apiMethodSignature: com.datadog.android.core.configuration.Configuration$Builder#fun useViewTrackingStrategy(com.datadog.android.rum.tracking.ViewTrackingStrategy): Builder + * apiMethodSignature: com.datadog.android.core.configuration.Configuration$Builder#fun useViewTrackingStrategy(com.datadog.android.rum.tracking.ViewTrackingStrategy?): Builder * apiMethodSignature: com.datadog.android.rum.tracking.FragmentViewTrackingStrategy#constructor(Boolean, ComponentPredicate = AcceptAllSupportFragments(), ComponentPredicate = AcceptAllDefaultFragment()) */ @Test @@ -158,7 +158,7 @@ internal class RumViewTrackingE2ETests { } /** - * apiMethodSignature: com.datadog.android.core.configuration.Configuration$Builder#fun useViewTrackingStrategy(com.datadog.android.rum.tracking.ViewTrackingStrategy): Builder + * apiMethodSignature: com.datadog.android.core.configuration.Configuration$Builder#fun useViewTrackingStrategy(com.datadog.android.rum.tracking.ViewTrackingStrategy?): Builder * apiMethodSignature: com.datadog.android.rum.tracking.FragmentViewTrackingStrategy#constructor(Boolean, ComponentPredicate = AcceptAllSupportFragments(), ComponentPredicate = AcceptAllDefaultFragment()) */ @Test @@ -194,7 +194,7 @@ internal class RumViewTrackingE2ETests { } /** - * apiMethodSignature: com.datadog.android.core.configuration.Configuration$Builder#fun useViewTrackingStrategy(com.datadog.android.rum.tracking.ViewTrackingStrategy): Builder + * apiMethodSignature: com.datadog.android.core.configuration.Configuration$Builder#fun useViewTrackingStrategy(com.datadog.android.rum.tracking.ViewTrackingStrategy?): Builder * apiMethodSignature: com.datadog.android.rum.tracking.FragmentViewTrackingStrategy#constructor(Boolean, ComponentPredicate = AcceptAllSupportFragments(), ComponentPredicate = AcceptAllDefaultFragment()) */ @Test @@ -230,7 +230,7 @@ internal class RumViewTrackingE2ETests { } /** - * apiMethodSignature: com.datadog.android.core.configuration.Configuration$Builder#fun useViewTrackingStrategy(com.datadog.android.rum.tracking.ViewTrackingStrategy): Builder + * apiMethodSignature: com.datadog.android.core.configuration.Configuration$Builder#fun useViewTrackingStrategy(com.datadog.android.rum.tracking.ViewTrackingStrategy?): Builder * apiMethodSignature: com.datadog.android.rum.tracking.NavigationViewTrackingStrategy#constructor(Int, Boolean, ComponentPredicate = AcceptAllNavDestinations()) */ @Test @@ -258,7 +258,7 @@ internal class RumViewTrackingE2ETests { } /** - * apiMethodSignature: com.datadog.android.core.configuration.Configuration$Builder#fun useViewTrackingStrategy(com.datadog.android.rum.tracking.ViewTrackingStrategy): Builder + * apiMethodSignature: com.datadog.android.core.configuration.Configuration$Builder#fun useViewTrackingStrategy(com.datadog.android.rum.tracking.ViewTrackingStrategy?): Builder * apiMethodSignature: com.datadog.android.rum.tracking.NavigationViewTrackingStrategy#constructor(Int, Boolean, ComponentPredicate = AcceptAllNavDestinations()) */ @Test @@ -295,7 +295,7 @@ internal class RumViewTrackingE2ETests { } /** - * apiMethodSignature: com.datadog.android.core.configuration.Configuration$Builder#fun useViewTrackingStrategy(com.datadog.android.rum.tracking.ViewTrackingStrategy): Builder + * apiMethodSignature: com.datadog.android.core.configuration.Configuration$Builder#fun useViewTrackingStrategy(com.datadog.android.rum.tracking.ViewTrackingStrategy?): Builder * apiMethodSignature: com.datadog.android.rum.tracking.NavigationViewTrackingStrategy#constructor(Int, Boolean, ComponentPredicate = AcceptAllNavDestinations()) */ @Test @@ -332,7 +332,7 @@ internal class RumViewTrackingE2ETests { } /** - * apiMethodSignature: com.datadog.android.core.configuration.Configuration$Builder#fun useViewTrackingStrategy(com.datadog.android.rum.tracking.ViewTrackingStrategy): Builder + * apiMethodSignature: com.datadog.android.core.configuration.Configuration$Builder#fun useViewTrackingStrategy(com.datadog.android.rum.tracking.ViewTrackingStrategy?): Builder * apiMethodSignature: com.datadog.android.rum.tracking.MixedViewTrackingStrategy#constructor(Boolean, ComponentPredicate = AcceptAllActivities(), ComponentPredicate = AcceptAllSupportFragments(), ComponentPredicate = AcceptAllDefaultFragment()) */ @Test @@ -355,7 +355,7 @@ internal class RumViewTrackingE2ETests { } /** - * apiMethodSignature: com.datadog.android.core.configuration.Configuration$Builder#fun useViewTrackingStrategy(com.datadog.android.rum.tracking.ViewTrackingStrategy): Builder + * apiMethodSignature: com.datadog.android.core.configuration.Configuration$Builder#fun useViewTrackingStrategy(com.datadog.android.rum.tracking.ViewTrackingStrategy?): Builder * apiMethodSignature: com.datadog.android.rum.tracking.MixedViewTrackingStrategy#constructor(Boolean, ComponentPredicate = AcceptAllActivities(), ComponentPredicate = AcceptAllSupportFragments(), ComponentPredicate = AcceptAllDefaultFragment()) */ @Test diff --git a/instrumented/nightly-tests/src/main/kotlin/com/datadog/android/nightly/services/CrashService.kt b/instrumented/nightly-tests/src/main/kotlin/com/datadog/android/nightly/services/CrashService.kt index 34a2fb24c9..d45e63d5b4 100644 --- a/instrumented/nightly-tests/src/main/kotlin/com/datadog/android/nightly/services/CrashService.kt +++ b/instrumented/nightly-tests/src/main/kotlin/com/datadog/android/nightly/services/CrashService.kt @@ -19,6 +19,9 @@ internal abstract class CrashService : Service() { GlobalRum.registerIfAbsent(RumMonitor.Builder().build()) extras?.let { bundle -> bundle.keySet().forEach { + // TODO RUMM-2717 Bundle#get is deprecated, but there is no replacement for it. + // Issue is opened in the Google Issue Tracker. + @Suppress("DEPRECATION") GlobalRum.addAttribute(it, bundle[it]) } } diff --git a/sample/kotlin/src/main/cpp/datadog-native-sample-lib.cpp b/sample/kotlin/src/main/cpp/datadog-native-sample-lib.cpp index 69066470da..1fd529ba4c 100644 --- a/sample/kotlin/src/main/cpp/datadog-native-sample-lib.cpp +++ b/sample/kotlin/src/main/cpp/datadog-native-sample-lib.cpp @@ -16,9 +16,12 @@ void crash_with_sigabrt_signal() { } #pragma clang diagnostic pop +#pragma clang diagnostic push +#pragma clang diagnostic ignored "-Wreturn-type" int crash_with_sigill_signal() { // do not return anything to simulate the SIGILL } +#pragma clang diagnostic pop extern "C" JNIEXPORT void JNICALL Java_com_datadog_android_sample_crash_CrashFragment_simulateNdkCrash(