From 8505f103284785496afa0274717541feaa07df62 Mon Sep 17 00:00:00 2001 From: Maciek Grzybowski Date: Tue, 31 May 2022 16:00:51 +0200 Subject: [PATCH 1/5] RUMM-2169 Make `LoggingFeature` not depend on core configuration and use SDK context instead. --- .../Datadog/Core/FeaturesConfiguration.swift | 2 - .../CrashReporting/CrashReporter.swift | 5 ++- Sources/Datadog/Datadog.swift | 3 +- Sources/Datadog/DatadogCore/DatadogCore.swift | 13 +++--- .../DatadogInternal/DatadogV1Context.swift | 31 ++++++++++++++ .../DatadogV1CoreProtocol.swift | 7 ++++ ...CrashReportingWithLoggingIntegration.swift | 23 +++++------ .../TracingWithLoggingIntegration.swift | 9 ++++- .../WKUserContentController+Datadog.swift | 26 +++++++++--- Sources/Datadog/Logger.swift | 40 ++++++++++--------- Sources/Datadog/Logging/LoggingFeature.swift | 16 +------- Sources/Datadog/Tracer.swift | 24 ++++++++--- Tests/DatadogTests/Datadog/LoggerTests.swift | 21 ++++++---- .../Datadog/Mocks/DatadogCoreMock.swift | 25 ++++++++++++ 14 files changed, 169 insertions(+), 76 deletions(-) diff --git a/Sources/Datadog/Core/FeaturesConfiguration.swift b/Sources/Datadog/Core/FeaturesConfiguration.swift index 2c48d6569d..e48242eb53 100644 --- a/Sources/Datadog/Core/FeaturesConfiguration.swift +++ b/Sources/Datadog/Core/FeaturesConfiguration.swift @@ -27,7 +27,6 @@ internal struct FeaturesConfiguration { } struct Logging { - let common: Common let uploadURL: URL let logEventMapper: LogEventMapper? } @@ -171,7 +170,6 @@ extension FeaturesConfiguration { if configuration.loggingEnabled { logging = Logging( - common: common, uploadURL: try ifValid(endpointURLString: logsEndpoint.url), logEventMapper: configuration.logEventMapper ) diff --git a/Sources/Datadog/CrashReporting/CrashReporter.swift b/Sources/Datadog/CrashReporting/CrashReporter.swift index f3bd9ac49e..2fc03ac7cf 100644 --- a/Sources/Datadog/CrashReporting/CrashReporter.swift +++ b/Sources/Datadog/CrashReporting/CrashReporter.swift @@ -25,7 +25,8 @@ internal class CrashReporter { convenience init?( crashReportingFeature: CrashReportingFeature, loggingFeature: LoggingFeature?, - rumFeature: RUMFeature? + rumFeature: RUMFeature?, + context: DatadogV1Context ) { let loggingOrRUMIntegration: CrashReportingIntegration? @@ -33,7 +34,7 @@ internal class CrashReporter { if let rumFeature = rumFeature { loggingOrRUMIntegration = CrashReportingWithRUMIntegration(rumFeature: rumFeature) } else if let loggingFeature = loggingFeature { - loggingOrRUMIntegration = CrashReportingWithLoggingIntegration(loggingFeature: loggingFeature) + loggingOrRUMIntegration = CrashReportingWithLoggingIntegration(loggingFeature: loggingFeature, context: context) } else { loggingOrRUMIntegration = nil } diff --git a/Sources/Datadog/Datadog.swift b/Sources/Datadog/Datadog.swift index 6d2f3a7d2e..06102942e7 100644 --- a/Sources/Datadog/Datadog.swift +++ b/Sources/Datadog/Datadog.swift @@ -308,7 +308,8 @@ public class Datadog { Global.crashReporter = CrashReporter( crashReportingFeature: crashReportingFeature, loggingFeature: logging, - rumFeature: rum + rumFeature: rum, + context: core.v1Context ) Global.crashReporter?.sendCrashReportIfFound() diff --git a/Sources/Datadog/DatadogCore/DatadogCore.swift b/Sources/Datadog/DatadogCore/DatadogCore.swift index 3825ee7bf6..131831021c 100644 --- a/Sources/Datadog/DatadogCore/DatadogCore.swift +++ b/Sources/Datadog/DatadogCore/DatadogCore.swift @@ -52,6 +52,9 @@ internal final class DatadogCore { private var v1Features: [String: Any] = [:] + /// The SDK Context for V1. + internal let v1Context: DatadogV1Context + /// Creates a core instance. /// /// - Parameters: @@ -66,6 +69,7 @@ internal final class DatadogCore { self.rootDirectory = rootDirectory self.configuration = configuration self.dependencies = dependencies + self.v1Context = DatadogV1Context(configuration: configuration, dependencies: dependencies) } /// Sets current user information. @@ -127,11 +131,6 @@ extension DatadogCore: DatadogV1CoreProtocol { telemetry: telemetry ) - let v1Context = DatadogV1Context( - configuration: configuration, - dependencies: dependencies - ) - let upload = FeatureUpload( featureName: uploadConfiguration.featureName, storage: storage, @@ -158,6 +157,10 @@ extension DatadogCore: DatadogV1CoreProtocol { let key = String(describing: T.self) return v1Features[key] as? T } + + var context: DatadogV1Context? { + return v1Context + } } internal protocol V1Feature { diff --git a/Sources/Datadog/DatadogInternal/DatadogV1Context.swift b/Sources/Datadog/DatadogInternal/DatadogV1Context.swift index a9cf85075d..06445ba247 100644 --- a/Sources/Datadog/DatadogInternal/DatadogV1Context.swift +++ b/Sources/Datadog/DatadogInternal/DatadogV1Context.swift @@ -12,6 +12,9 @@ import Foundation /// - different values for V1 context need to be read either from common configuration or through shared dependencies (providers); /// - V1 context is not asynchronous, and some providers block their threads for getting their value. /// +/// `DatadogV1Context` can be safely captured during component initialization. It will never change during component's lifespan, meaning that: +/// - exposed static configuration won't change; +/// - bundled provider references won't change (although the value they provide will be different over time). internal struct DatadogV1Context { private let configuration: CoreConfiguration private let dependencies: CoreDependencies @@ -20,7 +23,12 @@ internal struct DatadogV1Context { self.configuration = configuration self.dependencies = dependencies } +} + +// MARK: - Configuration +/// This extension bundles different parts of the SDK core configuration. +internal extension DatadogV1Context { // MARK: - Datadog Specific /// The client token allowing for data uploads to [Datadog Site](https://docs.datadoghq.com/getting_started/site/). @@ -51,6 +59,29 @@ internal struct DatadogV1Context { /// The name of the application, read from `Info.plist` (`CFBundleExecutable`). var applicationName: String { configuration.applicationName } + /// The bundle identifier, read from `Info.plist` (`CFBundleIdentifier`). + var applicationBundleIdentifier: String { configuration.applicationBundleIdentifier } +} + +// MARK: - Providers + +/// This extension bundles different providers managed by the SDK core. +internal extension DatadogV1Context { /// Current device information. var mobileDevice: MobileDevice { dependencies.mobileDevice } + + /// Time provider. + var dateProvider: DateProvider { dependencies.dateProvider } + + /// NTP time correction provider. + var dateCorrector: DateCorrectorType { dependencies.dateCorrector } + + /// Network information provider. + var networkConnectionInfoProvider: NetworkConnectionInfoProviderType { dependencies.networkConnectionInfoProvider } + + /// Carrier information provider. + var carrierInfoProvider: CarrierInfoProviderType { dependencies.carrierInfoProvider } + + /// User information provider. + var userInfoProvider: UserInfoProvider { dependencies.userInfoProvider } } diff --git a/Sources/Datadog/DatadogInternal/DatadogV1CoreProtocol.swift b/Sources/Datadog/DatadogInternal/DatadogV1CoreProtocol.swift index f7d230b614..861f9c08fe 100644 --- a/Sources/Datadog/DatadogInternal/DatadogV1CoreProtocol.swift +++ b/Sources/Datadog/DatadogInternal/DatadogV1CoreProtocol.swift @@ -31,6 +31,9 @@ internal protocol DatadogV1CoreProtocol: DatadogCoreProtocol { /// - type: The feature instance type. /// - Returns: The feature if any. func feature(_ type: T.Type) -> T? + + /// The SDK context created upon core initialization or `nil` if SDK was not yet initialized. + var context: DatadogV1Context? { get } } extension NOOPDatadogCore: DatadogV1CoreProtocol { @@ -43,4 +46,8 @@ extension NOOPDatadogCore: DatadogV1CoreProtocol { func feature(_ type: T.Type) -> T? { return nil } + + var context: DatadogV1Context? { + return nil + } } diff --git a/Sources/Datadog/FeaturesIntegration/CrashReporting/CrashReportingWithLoggingIntegration.swift b/Sources/Datadog/FeaturesIntegration/CrashReporting/CrashReportingWithLoggingIntegration.swift index 92476196a0..dd9acee651 100644 --- a/Sources/Datadog/FeaturesIntegration/CrashReporting/CrashReportingWithLoggingIntegration.swift +++ b/Sources/Datadog/FeaturesIntegration/CrashReporting/CrashReportingWithLoggingIntegration.swift @@ -19,10 +19,9 @@ internal struct CrashReportingWithLoggingIntegration: CrashReportingIntegration private let dateProvider: DateProvider private let dateCorrector: DateCorrectorType - /// Global configuration set for the SDK (service name, environment, application version, ...) - private let configuration: FeaturesConfiguration.Common + private let context: DatadogV1Context - init(loggingFeature: LoggingFeature) { + init(loggingFeature: LoggingFeature, context: DatadogV1Context) { self.init( logOutput: LogFileOutput( fileWriter: loggingFeature.storage.arbitraryAuthorizedWriter, @@ -30,9 +29,9 @@ internal struct CrashReportingWithLoggingIntegration: CrashReportingIntegration // issue additional RUM Errors for crash reports. Those are send through `CrashReportingWithRUMIntegration`. rumErrorsIntegration: nil ), - dateProvider: loggingFeature.dateProvider, - dateCorrector: loggingFeature.dateCorrector, - configuration: loggingFeature.configuration.common + dateProvider: context.dateProvider, + dateCorrector: context.dateCorrector, + context: context ) } @@ -40,12 +39,12 @@ internal struct CrashReportingWithLoggingIntegration: CrashReportingIntegration logOutput: LogOutput, dateProvider: DateProvider, dateCorrector: DateCorrectorType, - configuration: FeaturesConfiguration.Common + context: DatadogV1Context ) { self.logOutput = logOutput self.dateProvider = dateProvider self.dateCorrector = dateCorrector - self.configuration = configuration + self.context = context } func send(crashReport: DDCrashReport, with crashContext: CrashContext) { @@ -85,12 +84,12 @@ internal struct CrashReportingWithLoggingIntegration: CrashReportingIntegration message: crashReport.message, stack: crashReport.stack ), - serviceName: configuration.serviceName, - environment: configuration.environment, + serviceName: context.service, + environment: context.env, loggerName: Constants.loggerName, - loggerVersion: configuration.sdkVersion, + loggerVersion: context.sdkVersion, threadName: nil, - applicationVersion: configuration.applicationVersion, + applicationVersion: context.version, userInfo: .init( id: user?.id, name: user?.name, diff --git a/Sources/Datadog/FeaturesIntegration/TracingWithLoggingIntegration.swift b/Sources/Datadog/FeaturesIntegration/TracingWithLoggingIntegration.swift index a86d2082ed..23d721a1be 100644 --- a/Sources/Datadog/FeaturesIntegration/TracingWithLoggingIntegration.swift +++ b/Sources/Datadog/FeaturesIntegration/TracingWithLoggingIntegration.swift @@ -23,7 +23,12 @@ internal struct TracingWithLoggingIntegration { /// Actual `LogOutput` bridged to `LoggingFeature`. let loggingOutput: LogOutput - init(tracingFeature: TracingFeature, tracerConfiguration: Tracer.Configuration, loggingFeature: LoggingFeature) { + init( + tracingFeature: TracingFeature, + tracerConfiguration: Tracer.Configuration, + loggingFeature: LoggingFeature, + context: DatadogV1Context + ) { self.init( logBuilder: LogEventBuilder( sdkVersion: tracingFeature.configuration.common.sdkVersion, @@ -34,7 +39,7 @@ internal struct TracingWithLoggingIntegration { userInfoProvider: tracingFeature.userInfoProvider, networkConnectionInfoProvider: tracerConfiguration.sendNetworkInfo ? tracingFeature.networkConnectionInfoProvider : nil, carrierInfoProvider: tracerConfiguration.sendNetworkInfo ? tracingFeature.carrierInfoProvider : nil, - dateCorrector: loggingFeature.dateCorrector, + dateCorrector: context.dateCorrector, logEventMapper: loggingFeature.configuration.logEventMapper ), loggingOutput: LogFileOutput( diff --git a/Sources/Datadog/FeaturesIntegration/WebView/WKUserContentController+Datadog.swift b/Sources/Datadog/FeaturesIntegration/WebView/WKUserContentController+Datadog.swift index 445e266af2..48f35418e4 100644 --- a/Sources/Datadog/FeaturesIntegration/WebView/WKUserContentController+Datadog.swift +++ b/Sources/Datadog/FeaturesIntegration/WebView/WKUserContentController+Datadog.swift @@ -25,11 +25,26 @@ public extension WKUserContentController { /// /// - Parameter hosts: a list of hosts instrumented with Browser SDK to capture Datadog events from func trackDatadogEvents(in hosts: Set, sdk core: DatadogCoreProtocol = defaultDatadogCore) { + do { + try trackDatadogEventsOrThrow(in: hosts, sdk: core) + } catch { + consolePrint("\(error)") + } + } + + private func trackDatadogEventsOrThrow(in hosts: Set, sdk core: DatadogCoreProtocol) throws { + guard let context = core.v1.context else { + throw ProgrammerError( + description: "`Datadog.initialize()` must be called prior to `trackDatadogEvents(in:)`." + ) + } + addDatadogMessageHandler( allowedWebViewHosts: hosts, hostsSanitizer: HostsSanitizer(), loggingFeature: core.v1.feature(LoggingFeature.self), - rumFeature: core.v1.feature(RUMFeature.self) + rumFeature: core.v1.feature(RUMFeature.self), + context: context ) } @@ -53,7 +68,8 @@ public extension WKUserContentController { allowedWebViewHosts: Set, hostsSanitizer: HostsSanitizing, loggingFeature: LoggingFeature?, - rumFeature: RUMFeature? + rumFeature: RUMFeature?, + context: DatadogV1Context ) { guard !isTracking else { userLogger.warn("`trackDatadogEvents(in:)` was called more than once for the same WebView. Second call will be ignored. Make sure you call it only once.") @@ -68,10 +84,10 @@ public extension WKUserContentController { if let loggingFeature = loggingFeature { logEventConsumer = DefaultWebLogEventConsumer( userLogsWriter: loggingFeature.storage.writer, - dateCorrector: loggingFeature.dateCorrector, + dateCorrector: context.dateCorrector, rumContextProvider: globalRUMMonitor?.contextProvider, - applicationVersion: loggingFeature.configuration.common.applicationVersion, - environment: loggingFeature.configuration.common.environment + applicationVersion: context.version, + environment: context.env ) } diff --git a/Sources/Datadog/Logger.swift b/Sources/Datadog/Logger.swift index 1fef9ecadf..f417923e89 100644 --- a/Sources/Datadog/Logger.swift +++ b/Sources/Datadog/Logger.swift @@ -399,15 +399,19 @@ public class Logger { } private func buildOrThrow(in core: DatadogCoreProtocol) throws -> Logger { + guard let context = core.v1.context else { + throw ProgrammerError( + description: "`Datadog.initialize()` must be called prior to `Logger.builder.build()`." + ) + } + guard let loggingFeature = core.v1.feature(LoggingFeature.self) else { throw ProgrammerError( - description: Datadog.isInitialized - ? "`Logger.builder.build()` produces a non-functional logger, as the logging feature is disabled." - : "`Datadog.initialize()` must be called prior to `Logger.builder.build()`." + description: "`Logger.builder.build()` produces a non-functional logger, as the logging feature is disabled." ) } - let (logBuilder, logOutput) = resolveLogBuilderAndOutput(for: loggingFeature) ?? (nil, nil) + let (logBuilder, logOutput) = resolveLogBuilderAndOutput(for: loggingFeature, context: context) ?? (nil, nil) // RUMM-2133 Note: strong feature coupling while migrating to v2. // In v2 active span will be provided in context from feature scope. @@ -417,24 +421,24 @@ public class Logger { return Logger( logBuilder: logBuilder, logOutput: logOutput, - dateProvider: loggingFeature.dateProvider, - identifier: resolveLoggerName(for: loggingFeature), + dateProvider: context.dateProvider, + identifier: resolveLoggerName(with: context), rumContextIntegration: (rumEnabled && bundleWithRUM) ? LoggingWithRUMContextIntegration() : nil, activeSpanIntegration: (tracingEnabled && bundleWithTrace) ? LoggingWithActiveSpanIntegration() : nil ) } - private func resolveLogBuilderAndOutput(for loggingFeature: LoggingFeature) -> (LogEventBuilder, LogOutput)? { + private func resolveLogBuilderAndOutput(for loggingFeature: LoggingFeature, context: DatadogV1Context) -> (LogEventBuilder, LogOutput)? { let logBuilder = LogEventBuilder( - sdkVersion: loggingFeature.configuration.common.sdkVersion, - applicationVersion: loggingFeature.configuration.common.applicationVersion, - environment: loggingFeature.configuration.common.environment, - serviceName: serviceName ?? loggingFeature.configuration.common.serviceName, - loggerName: resolveLoggerName(for: loggingFeature), - userInfoProvider: loggingFeature.userInfoProvider, - networkConnectionInfoProvider: sendNetworkInfo ? loggingFeature.networkConnectionInfoProvider : nil, - carrierInfoProvider: sendNetworkInfo ? loggingFeature.carrierInfoProvider : nil, - dateCorrector: loggingFeature.dateCorrector, + sdkVersion: context.sdkVersion, + applicationVersion: context.version, + environment: context.env, + serviceName: serviceName ?? context.service, + loggerName: resolveLoggerName(with: context), + userInfoProvider: context.userInfoProvider, + networkConnectionInfoProvider: sendNetworkInfo ? context.networkConnectionInfoProvider : nil, + carrierInfoProvider: sendNetworkInfo ? context.carrierInfoProvider : nil, + dateCorrector: context.dateCorrector, logEventMapper: loggingFeature.configuration.logEventMapper ) @@ -470,8 +474,8 @@ public class Logger { } } - private func resolveLoggerName(for loggingFeature: LoggingFeature) -> String { - return loggerName ?? loggingFeature.configuration.common.applicationBundleIdentifier + private func resolveLoggerName(with context: DatadogV1Context) -> String { + return loggerName ?? context.applicationBundleIdentifier } } } diff --git a/Sources/Datadog/Logging/LoggingFeature.swift b/Sources/Datadog/Logging/LoggingFeature.swift index b633ebc266..88b65f5f9b 100644 --- a/Sources/Datadog/Logging/LoggingFeature.swift +++ b/Sources/Datadog/Logging/LoggingFeature.swift @@ -15,14 +15,6 @@ internal final class LoggingFeature: V1FeatureInitializable { let configuration: Configuration - // MARK: - Dependencies - - let dateProvider: DateProvider - let dateCorrector: DateCorrectorType - let userInfoProvider: UserInfoProvider - let networkConnectionInfoProvider: NetworkConnectionInfoProviderType - let carrierInfoProvider: CarrierInfoProviderType - // MARK: - Components /// Log files storage. @@ -36,19 +28,13 @@ internal final class LoggingFeature: V1FeatureInitializable { storage: FeatureStorage, upload: FeatureUpload, configuration: Configuration, + /// TODO: RUMM-2169 Remove `commonDependencies` from `V1FeatureInitializable` interface when all Features are migrated to use `DatadogV1Context`: commonDependencies: FeaturesCommonDependencies, telemetry: Telemetry? ) { // Configuration self.configuration = configuration - // Bundle dependencies - self.dateProvider = commonDependencies.dateProvider - self.dateCorrector = commonDependencies.dateCorrector - self.userInfoProvider = commonDependencies.userInfoProvider - self.networkConnectionInfoProvider = commonDependencies.networkConnectionInfoProvider - self.carrierInfoProvider = commonDependencies.carrierInfoProvider - // Initialize stacks self.storage = storage self.upload = upload diff --git a/Sources/Datadog/Tracer.swift b/Sources/Datadog/Tracer.swift index d5831f9756..3c7a1c72ff 100644 --- a/Sources/Datadog/Tracer.swift +++ b/Sources/Datadog/Tracer.swift @@ -71,6 +71,11 @@ public class Tracer: OTTracer { /// - configuration: the tracer configuration obtained using `Tracer.Configuration()`. public static func initialize(configuration: Configuration, in core: DatadogCoreProtocol = defaultDatadogCore) -> OTTracer { do { + guard let context = core.v1.context else { + throw ProgrammerError( + description: "`Datadog.initialize()` must be called prior to `Tracer.initialize()`." + ) + } if Global.sharedTracer is Tracer { throw ProgrammerError( description: """ @@ -80,16 +85,15 @@ public class Tracer: OTTracer { } guard let tracingFeature = core.v1.feature(TracingFeature.self) else { throw ProgrammerError( - description: Datadog.isInitialized - ? "`Tracer.initialize(configuration:)` produces a non-functional tracer, as the tracing feature is disabled." - : "`Datadog.initialize()` must be called prior to `Tracer.initialize()`." + description: "`Tracer.initialize(configuration:)` produces a non-functional tracer, as the tracing feature is disabled." ) } return DDTracer( tracingFeature: tracingFeature, tracerConfiguration: configuration, rumEnabled: core.v1.feature(RUMFeature.self) != nil, - loggingFeature: core.v1.feature(LoggingFeature.self) + loggingFeature: core.v1.feature(LoggingFeature.self), + context: context ) } catch { consolePrint("\(error)") @@ -101,7 +105,8 @@ public class Tracer: OTTracer { tracingFeature: TracingFeature, tracerConfiguration: Configuration, rumEnabled: Bool, - loggingFeature: LoggingFeature? + loggingFeature: LoggingFeature?, + context: DatadogV1Context ) { self.init( spanBuilder: SpanEventBuilder( @@ -125,7 +130,14 @@ public class Tracer: OTTracer { tracingUUIDGenerator: tracingFeature.tracingUUIDGenerator, globalTags: tracerConfiguration.globalTags, rumContextIntegration: (rumEnabled && tracerConfiguration.bundleWithRUM) ? TracingWithRUMContextIntegration() : nil, - loggingIntegration: loggingFeature.map { TracingWithLoggingIntegration(tracingFeature: tracingFeature, tracerConfiguration: tracerConfiguration, loggingFeature: $0) } + loggingIntegration: loggingFeature.map { + TracingWithLoggingIntegration( + tracingFeature: tracingFeature, + tracerConfiguration: tracerConfiguration, + loggingFeature: $0, + context: context + ) + } ) } diff --git a/Tests/DatadogTests/Datadog/LoggerTests.swift b/Tests/DatadogTests/Datadog/LoggerTests.swift index 1124ec8290..42e1aa6832 100644 --- a/Tests/DatadogTests/Datadog/LoggerTests.swift +++ b/Tests/DatadogTests/Datadog/LoggerTests.swift @@ -25,17 +25,20 @@ class LoggerTests: XCTestCase { // MARK: - Customizing Logger func testSendingLogWithDefaultLogger() throws { - let feature: LoggingFeature = .mockByRecordingLogMatchers( - directory: temporaryDirectory, - configuration: .mockWith( - common: .mockWith( + let core = DatadogCoreMock( + v1Context: .mockWith( + configuration: .mockWith( applicationVersion: "1.0.0", applicationBundleIdentifier: "com.datadoghq.ios-sdk", serviceName: "default-service-name", environment: "tests", sdkVersion: "1.2.3" ) - ), + ) + ) + + let feature: LoggingFeature = .mockByRecordingLogMatchers( + directory: temporaryDirectory, dependencies: .mockWith( dateProvider: RelativeDateProvider(using: .mockDecember15th2019At10AMUTC()) ) @@ -430,11 +433,13 @@ class LoggerTests: XCTestCase { // MARK: - Sending tags func testSendingTags() throws { + let core = DatadogCoreMock(v1Context: .mockWith(configuration: .mockWith(environment: "tests"))) + defer { core.flush() } + let feature: LoggingFeature = .mockByRecordingLogMatchers( directory: temporaryDirectory, - configuration: .mockWith(common: .mockWith(environment: "tests")) + configuration: .mockAny() ) - defer { feature.deinitialize() } core.register(feature: feature) let logger = Logger.builder.build(in: core) @@ -800,7 +805,7 @@ class LoggerTests: XCTestCase { XCTAssertFalse(Datadog.isInitialized) // when - let logger = Logger.builder.build(in: core) + let logger = Logger.builder.build() // then XCTAssertEqual( diff --git a/Tests/DatadogTests/Datadog/Mocks/DatadogCoreMock.swift b/Tests/DatadogTests/Datadog/Mocks/DatadogCoreMock.swift index 2ea71fa821..c138030e6f 100644 --- a/Tests/DatadogTests/Datadog/Mocks/DatadogCoreMock.swift +++ b/Tests/DatadogTests/Datadog/Mocks/DatadogCoreMock.swift @@ -9,6 +9,11 @@ import Foundation internal final class DatadogCoreMock: Flushable { private var v1Features: [String: Any] = [:] + private var v1Context: DatadogV1Context + + init(v1Context: DatadogV1Context = .mockAny()) { + self.v1Context = v1Context + } /// Flush resgistered features. /// @@ -40,6 +45,10 @@ extension DatadogCoreMock: DatadogV1CoreProtocol { let key = String(describing: T.self) return v1Features[key] as? T } + + var context: Any { + return v1Context + } } /// `Flushable` object resets its state on flush. @@ -80,3 +89,19 @@ extension URLSessionAutoInstrumentation: Flushable { deinitialize() } } + +extension DatadogV1Context: AnyMockable { + static func mockAny() -> DatadogV1Context { + return mockWith() + } + + static func mockWith( + configuration: CoreConfiguration = .mockAny(), + dependencies: CoreDependencies = .mockAny() + ) -> DatadogV1Context { + return DatadogV1Context( + configuration: configuration, + dependencies: dependencies + ) + } +} From c05214a694e33645a4549276ffd1c61c74a408bc Mon Sep 17 00:00:00 2001 From: Maciek Grzybowski Date: Wed, 1 Jun 2022 13:37:18 +0200 Subject: [PATCH 2/5] RUMM-2169 Update tests after making `LoggingFeature` not depend on core configuration --- .../CrashReporting/CrashReporterTests.swift | 3 +- Tests/DatadogTests/Datadog/DatadogTests.swift | 7 +- ...ReportingWithLoggingIntegrationTests.swift | 4 +- .../Datadog/LoggerBuilderTests.swift | 60 +-- Tests/DatadogTests/Datadog/LoggerTests.swift | 366 ++++++++++-------- .../Datadog/Logging/LoggingFeatureTests.swift | 51 ++- .../Datadog/Mocks/CoreMocks.swift | 2 - .../Datadog/Mocks/DatadogCoreMock.swift | 6 +- .../Datadog/Mocks/LoggingFeatureMocks.swift | 41 +- ...WKUserContentController+DatadogTests.swift | 37 +- .../Datadog/RUMMonitorTests.swift | 3 +- Tests/DatadogTests/Datadog/TracerTests.swift | 49 ++- .../DatadogObjc/DDDatadogTests.swift | 5 +- .../DatadogObjc/DDLoggerTests.swift | 7 +- .../DatadogObjc/DDTracerTests.swift | 57 ++- 15 files changed, 382 insertions(+), 316 deletions(-) diff --git a/Tests/DatadogTests/Datadog/CrashReporting/CrashReporterTests.swift b/Tests/DatadogTests/Datadog/CrashReporting/CrashReporterTests.swift index 3948bd31f2..cbf3b9bf75 100644 --- a/Tests/DatadogTests/Datadog/CrashReporting/CrashReporterTests.swift +++ b/Tests/DatadogTests/Datadog/CrashReporting/CrashReporterTests.swift @@ -214,7 +214,8 @@ class CrashReporterTests: XCTestCase { let crashReporter = CrashReporter( crashReportingFeature: .mockNoOp(), loggingFeature: nil, - rumFeature: nil + rumFeature: nil, + context: .mockAny() ) // Then diff --git a/Tests/DatadogTests/Datadog/DatadogTests.swift b/Tests/DatadogTests/Datadog/DatadogTests.swift index 6686668071..fca29a1f8c 100644 --- a/Tests/DatadogTests/Datadog/DatadogTests.swift +++ b/Tests/DatadogTests/Datadog/DatadogTests.swift @@ -287,7 +287,7 @@ class DatadogTests: XCTestCase { } } - func testSupplyingDebugLaunchArgument_itOverridesUserSettings() { + func testSupplyingDebugLaunchArgument_itOverridesUserSettings() throws { let mockProcessInfo = ProcessInfoMock( arguments: [Datadog.LaunchArguments.Debug] ) @@ -312,11 +312,10 @@ class DatadogTests: XCTestCase { bundleType: .iOSApp ) - let core = defaultDatadogCore - let logging = core.v1.feature(LoggingFeature.self) + let core = try XCTUnwrap(defaultDatadogCore as? DatadogCore) let tracing = core.v1.feature(TracingFeature.self) let rum = core.v1.feature(RUMFeature.self) - XCTAssertEqual(logging?.configuration.common.performance, expectedPerformancePreset) + XCTAssertEqual(core.configuration.performance, expectedPerformancePreset) XCTAssertEqual(rum?.configuration.sessionSampler.samplingRate, 100) XCTAssertEqual(tracing?.configuration.common.performance, expectedPerformancePreset) XCTAssertEqual(Datadog.verbosityLevel, .debug) diff --git a/Tests/DatadogTests/Datadog/FeaturesIntegration/CrashReporting/CrashReportingWithLoggingIntegrationTests.swift b/Tests/DatadogTests/Datadog/FeaturesIntegration/CrashReporting/CrashReportingWithLoggingIntegrationTests.swift index 6320daaf5f..d6e414ce00 100644 --- a/Tests/DatadogTests/Datadog/FeaturesIntegration/CrashReporting/CrashReportingWithLoggingIntegrationTests.swift +++ b/Tests/DatadogTests/Datadog/FeaturesIntegration/CrashReporting/CrashReportingWithLoggingIntegrationTests.swift @@ -24,7 +24,7 @@ class CrashReportingWithLoggingIntegrationTests: XCTestCase { logOutput: logOutput, dateProvider: RelativeDateProvider(using: .mockDecember15th2019At10AMUTC()), dateCorrector: DateCorrectorMock(), - configuration: .mockAny() + context: .mockAny() ) integration.send(crashReport: crashReport, with: crashContext) @@ -82,7 +82,7 @@ class CrashReportingWithLoggingIntegrationTests: XCTestCase { logOutput: logOutput, dateProvider: RelativeDateProvider(using: .mockRandomInThePast()), dateCorrector: DateCorrectorMock(correctionOffset: dateCorrectionOffset), - configuration: configuration + context: .mockWith(configuration: configuration) ) integration.send(crashReport: crashReport, with: crashContext) diff --git a/Tests/DatadogTests/Datadog/LoggerBuilderTests.swift b/Tests/DatadogTests/Datadog/LoggerBuilderTests.swift index a0dff92c99..eb34ebae54 100644 --- a/Tests/DatadogTests/Datadog/LoggerBuilderTests.swift +++ b/Tests/DatadogTests/Datadog/LoggerBuilderTests.swift @@ -8,31 +8,29 @@ import XCTest @testable import Datadog class LoggerBuilderTests: XCTestCase { - let core = DatadogCoreMock() - + private let userInfoProvider: UserInfoProvider = .mockAny() private let networkConnectionInfoProvider: NetworkConnectionInfoProviderMock = .mockAny() private let carrierInfoProvider: CarrierInfoProviderMock = .mockAny() - - override func setUp() { - super.setUp() - temporaryDirectory.create() - - let feature: LoggingFeature = .mockByRecordingLogMatchers( - directory: temporaryDirectory, + private lazy var core = DatadogCoreMock( + v1Context: .mockWith( configuration: .mockWith( - common: .mockWith( - applicationVersion: "1.2.3", - applicationBundleIdentifier: "com.datadog.unit-tests", - serviceName: "service-name", - environment: "tests" - ) + applicationVersion: "1.2.3", + applicationBundleIdentifier: "com.datadog.unit-tests", + serviceName: "service-name", + environment: "tests" ), dependencies: .mockWith( + userInfoProvider: userInfoProvider, networkConnectionInfoProvider: networkConnectionInfoProvider, carrierInfoProvider: carrierInfoProvider ) ) + ) + override func setUp() { + super.setUp() + temporaryDirectory.create() + let feature: LoggingFeature = .mockNoOp() core.register(feature: feature) } @@ -47,22 +45,14 @@ class LoggerBuilderTests: XCTestCase { XCTAssertNil(logger.rumContextIntegration) XCTAssertNil(logger.activeSpanIntegration) + XCTAssertTrue(logger.logOutput is LogFileOutput) - let feature = try XCTUnwrap(core.v1.feature(LoggingFeature.self)) - XCTAssertTrue( - logger.logOutput is LogFileOutput, - "When Logging feature is enabled the Logger should use `LogFileOutput`." - ) - let logBuilder = try XCTUnwrap( - logger.logBuilder, - "When Logging feature is enabled the Logger should use `LogBuilder`." - ) - + let logBuilder = try XCTUnwrap(logger.logBuilder) XCTAssertEqual(logBuilder.applicationVersion, "1.2.3") XCTAssertEqual(logBuilder.serviceName, "service-name") XCTAssertEqual(logBuilder.environment, "tests") XCTAssertEqual(logBuilder.loggerName, "com.datadog.unit-tests") - XCTAssertTrue(logBuilder.userInfoProvider === feature.userInfoProvider) + XCTAssertTrue(logBuilder.userInfoProvider === userInfoProvider) XCTAssertNil(logBuilder.networkConnectionInfoProvider) XCTAssertNil(logBuilder.carrierInfoProvider) } @@ -106,24 +96,16 @@ class LoggerBuilderTests: XCTestCase { XCTAssertNil(logger.rumContextIntegration) XCTAssertNil(logger.activeSpanIntegration) + XCTAssertTrue(logger.logOutput is LogFileOutput) - let feature = try XCTUnwrap(core.v1.feature(LoggingFeature.self)) - XCTAssertTrue( - logger.logOutput is LogFileOutput, - "When Logging feature is enabled the Logger should use `LogFileOutput`." - ) - let logBuilder = try XCTUnwrap( - logger.logBuilder, - "When Logging feature is enabled the Logger should use `LogBuilder`." - ) - + let logBuilder = try XCTUnwrap(logger.logBuilder) XCTAssertEqual(logBuilder.applicationVersion, "1.2.3") XCTAssertEqual(logBuilder.serviceName, "custom-service-name") XCTAssertEqual(logBuilder.environment, "tests") XCTAssertEqual(logBuilder.loggerName, "custom-logger-name") - XCTAssertTrue(logBuilder.userInfoProvider === feature.userInfoProvider) - XCTAssertTrue(logBuilder.networkConnectionInfoProvider as AnyObject === feature.networkConnectionInfoProvider as AnyObject) - XCTAssertTrue(logBuilder.carrierInfoProvider as AnyObject === feature.carrierInfoProvider as AnyObject) + XCTAssertTrue(logBuilder.userInfoProvider === userInfoProvider) + XCTAssertTrue(logBuilder.networkConnectionInfoProvider as AnyObject === networkConnectionInfoProvider as AnyObject) + XCTAssertTrue(logBuilder.carrierInfoProvider as AnyObject === carrierInfoProvider as AnyObject) } func testUsingDifferentOutputs() throws { diff --git a/Tests/DatadogTests/Datadog/LoggerTests.swift b/Tests/DatadogTests/Datadog/LoggerTests.swift index 42e1aa6832..6bfe20114c 100644 --- a/Tests/DatadogTests/Datadog/LoggerTests.swift +++ b/Tests/DatadogTests/Datadog/LoggerTests.swift @@ -9,15 +9,12 @@ import XCTest // swiftlint:disable multiline_arguments_brackets class LoggerTests: XCTestCase { - let core = DatadogCoreMock() - override func setUp() { super.setUp() temporaryDirectory.create() } override func tearDown() { - core.flush() temporaryDirectory.delete() super.tearDown() } @@ -33,18 +30,15 @@ class LoggerTests: XCTestCase { serviceName: "default-service-name", environment: "tests", sdkVersion: "1.2.3" + ), + dependencies: .mockWith( + dateProvider: RelativeDateProvider(using: .mockDecember15th2019At10AMUTC()) ) ) ) + defer { core.flush() } - let feature: LoggingFeature = .mockByRecordingLogMatchers( - directory: temporaryDirectory, - dependencies: .mockWith( - dateProvider: RelativeDateProvider(using: .mockDecember15th2019At10AMUTC()) - ) - ) - defer { feature.deinitialize() } - + let feature: LoggingFeature = .mockByRecordingLogMatchers(directory: temporaryDirectory) core.register(feature: feature) let logger = Logger.builder.build(in: core) @@ -67,8 +61,10 @@ class LoggerTests: XCTestCase { } func testSendingLogWithCustomizedLogger() throws { + let core = DatadogCoreMock(v1Context: .mockAny()) + defer { core.flush() } + let feature: LoggingFeature = .mockByRecordingLogMatchers(directory: temporaryDirectory) - defer { feature.deinitialize() } core.register(feature: feature) let logger = Logger.builder @@ -99,13 +95,16 @@ class LoggerTests: XCTestCase { // MARK: - Sending Customized Logs func testSendingLogsWithDifferentDates() throws { - let feature: LoggingFeature = .mockByRecordingLogMatchers( - directory: temporaryDirectory, - dependencies: .mockWith( - dateProvider: RelativeDateProvider(startingFrom: .mockDecember15th2019At10AMUTC(), advancingBySeconds: 1) + let core = DatadogCoreMock( + v1Context: .mockWith( + dependencies: .mockWith( + dateProvider: RelativeDateProvider(startingFrom: .mockDecember15th2019At10AMUTC(), advancingBySeconds: 1) + ) ) ) - defer { feature.deinitialize() } + defer { core.flush() } + + let feature: LoggingFeature = .mockByRecordingLogMatchers(directory: temporaryDirectory) core.register(feature: feature) let logger = Logger.builder.build(in: core) @@ -120,8 +119,10 @@ class LoggerTests: XCTestCase { } func testSendingLogsWithDifferentLevels() throws { + let core = DatadogCoreMock(v1Context: .mockAny()) + defer { core.flush() } + let feature: LoggingFeature = .mockByRecordingLogMatchers(directory: temporaryDirectory) - defer { feature.deinitialize() } core.register(feature: feature) let logger = Logger.builder.build(in: core) @@ -144,8 +145,10 @@ class LoggerTests: XCTestCase { // MARK: - Logging an error func testLoggingError() throws { + let core = DatadogCoreMock(v1Context: .mockAny()) + defer { core.flush() } + let feature: LoggingFeature = .mockByRecordingLogMatchers(directory: temporaryDirectory) - defer { feature.deinitialize() } core.register(feature: feature) struct TestError: Error { @@ -172,33 +175,29 @@ class LoggerTests: XCTestCase { // MARK: - Sending user info func testSendingUserInfo() throws { - let core = DatadogCore( - rootDirectory: temporaryDirectory.create(), - configuration: .mockAny(), - dependencies: .mockWith( - consentProvider: ConsentProvider(initialConsent: .granted), - userInfoProvider: UserInfoProvider() - ) - ) - defer { temporaryDirectory.delete() } + let userInfoProvider = UserInfoProvider() - let feature: LoggingFeature = .mockByRecordingLogMatchers( - directory: temporaryDirectory, - dependencies: .mockWith( - userInfoProvider: core.dependencies.userInfoProvider + let core = DatadogCoreMock( + v1Context: .mockWith( + dependencies: .mockWith( + userInfoProvider: userInfoProvider + ) ) ) - defer { feature.deinitialize() } + defer { core.flush() } + let feature: LoggingFeature = .mockByRecordingLogMatchers(directory: temporaryDirectory) core.register(feature: feature) let logger = Logger.builder.build(in: core) + + userInfoProvider.value = .empty logger.debug("message with no user info") - core.setUserInfo(id: "abc-123", name: "Foo") + userInfoProvider.value = UserInfo(id: "abc-123", name: "Foo", email: nil, extraInfo: [:]) logger.debug("message with user `id` and `name`") - core.setUserInfo( + userInfoProvider.value = UserInfo( id: "abc-123", name: "Foo", email: "foo@example.com", @@ -210,7 +209,7 @@ class LoggerTests: XCTestCase { ) logger.debug("message with user `id`, `name`, `email` and `extraInfo`") - core.setUserInfo(id: nil, name: nil, email: nil) + userInfoProvider.value = .empty logger.debug("message with no user info") let logMatchers = try feature.waitAndReturnLogMatchers(count: 4) @@ -236,13 +235,17 @@ class LoggerTests: XCTestCase { func testSendingCarrierInfoWhenEnteringAndLeavingCellularServiceRange() throws { let carrierInfoProvider = CarrierInfoProviderMock(carrierInfo: nil) - let feature: LoggingFeature = .mockByRecordingLogMatchers( - directory: temporaryDirectory, - dependencies: .mockWith( - carrierInfoProvider: carrierInfoProvider + + let core = DatadogCoreMock( + v1Context: .mockWith( + dependencies: .mockWith( + carrierInfoProvider: carrierInfoProvider + ) ) ) - defer { feature.deinitialize() } + defer { core.flush() } + + let feature: LoggingFeature = .mockByRecordingLogMatchers(directory: temporaryDirectory) core.register(feature: feature) let logger = Logger.builder @@ -278,13 +281,17 @@ class LoggerTests: XCTestCase { func testSendingNetworkConnectionInfoWhenReachabilityChanges() throws { let networkConnectionInfoProvider = NetworkConnectionInfoProviderMock.mockAny() - let feature: LoggingFeature = .mockByRecordingLogMatchers( - directory: temporaryDirectory, - dependencies: .mockWith( - networkConnectionInfoProvider: networkConnectionInfoProvider + + let core = DatadogCoreMock( + v1Context: .mockWith( + dependencies: .mockWith( + networkConnectionInfoProvider: networkConnectionInfoProvider + ) ) ) - defer { feature.deinitialize() } + defer { core.flush() } + + let feature: LoggingFeature = .mockByRecordingLogMatchers(directory: temporaryDirectory) core.register(feature: feature) let logger = Logger.builder @@ -338,8 +345,10 @@ class LoggerTests: XCTestCase { // MARK: - Sending attributes func testSendingLoggerAttributesOfDifferentEncodableValues() throws { + let core = DatadogCoreMock(v1Context: .mockAny()) + defer { core.flush() } + let feature: LoggingFeature = .mockByRecordingLogMatchers(directory: temporaryDirectory) - defer { feature.deinitialize() } core.register(feature: feature) let logger = Logger.builder.build(in: core) @@ -403,8 +412,10 @@ class LoggerTests: XCTestCase { } func testSendingMessageAttributes() throws { + let core = DatadogCoreMock(v1Context: .mockAny()) + defer { core.flush() } + let feature: LoggingFeature = .mockByRecordingLogMatchers(directory: temporaryDirectory) - defer { feature.deinitialize() } core.register(feature: feature) let logger = Logger.builder.build(in: core) @@ -433,13 +444,14 @@ class LoggerTests: XCTestCase { // MARK: - Sending tags func testSendingTags() throws { - let core = DatadogCoreMock(v1Context: .mockWith(configuration: .mockWith(environment: "tests"))) + let core = DatadogCoreMock( + v1Context: .mockWith( + configuration: .mockWith(environment: "tests") + ) + ) defer { core.flush() } - let feature: LoggingFeature = .mockByRecordingLogMatchers( - directory: temporaryDirectory, - configuration: .mockAny() - ) + let feature: LoggingFeature = .mockByRecordingLogMatchers(directory: temporaryDirectory) core.register(feature: feature) let logger = Logger.builder.build(in: core) @@ -470,56 +482,61 @@ class LoggerTests: XCTestCase { logMatchers[1].assertTags(equal: ["tag1", "tag2:abcd", "env:tests"]) logMatchers[2].assertTags(equal: ["env:tests"]) } - - // MARK: - Sending logs with different network and battery conditions - - func testGivenBadBatteryConditions_itDoesNotTryToSendLogs() throws { - let server = ServerMock(delivery: .success(response: .mockResponseWith(statusCode: 200))) - let feature: LoggingFeature = .mockWith( - directory: temporaryDirectory, - dependencies: .mockWith( - mobileDevice: .mockWith( - currentBatteryStatus: { () -> MobileDevice.BatteryStatus in - .mockWith(state: .charging, level: 0.05, isLowPowerModeEnabled: true) - } - ) - ) - ) - defer { feature.deinitialize() } - core.register(feature: feature) - - let logger = Logger.builder.build(in: core) - logger.debug("message") - - server.waitAndAssertNoRequestsSent() - } - - func testGivenNoNetworkConnection_itDoesNotTryToSendLogs() throws { - let server = ServerMock(delivery: .success(response: .mockResponseWith(statusCode: 200))) - let feature: LoggingFeature = .mockWith( - directory: temporaryDirectory, - dependencies: .mockWith( - networkConnectionInfoProvider: NetworkConnectionInfoProviderMock.mockWith( - networkConnectionInfo: .mockWith(reachability: .no) - ) - ) - ) - defer { feature.deinitialize() } - core.register(feature: feature) - - let logger = Logger.builder.build(in: core) - logger.debug("message") - - server.waitAndAssertNoRequestsSent() - } +// +// // MARK: - Sending logs with different network and battery conditions +// +// func testGivenBadBatteryConditions_itDoesNotTryToSendLogs() throws { +// let server = ServerMock(delivery: .success(response: .mockResponseWith(statusCode: 200))) +// +// let core = DatadogCoreMock( +// v1Context: .mockWith( +// dependencies: .mockWith( +// mobileDevice: .mockWith( +// currentBatteryStatus: { () -> MobileDevice.BatteryStatus in +// .mockWith(state: .charging, level: 0.05, isLowPowerModeEnabled: true) +// } +// ) +// ) +// ) +// ) +// defer { core.flush() } +// +// let feature: LoggingFeature = .mockWith(directory: temporaryDirectory) +// defer { feature.deinitialize() } +// core.register(feature: feature) +// +// let logger = Logger.builder.build(in: core) +// logger.debug("message") +// +// server.waitAndAssertNoRequestsSent() +// } + +// func testGivenNoNetworkConnection_itDoesNotTryToSendLogs() throws { +// let server = ServerMock(delivery: .success(response: .mockResponseWith(statusCode: 200))) +// let feature: LoggingFeature = .mockWith( +// directory: temporaryDirectory, +// dependencies: .mockWith( +// networkConnectionInfoProvider: NetworkConnectionInfoProviderMock.mockWith( +// networkConnectionInfo: .mockWith(reachability: .no) +// ) +// ) +// ) +// defer { feature.deinitialize() } +// core.register(feature: feature) +// +// let logger = Logger.builder.build(in: core) +// logger.debug("message") +// +// server.waitAndAssertNoRequestsSent() +// } // MARK: - Integration With RUM Feature func testGivenBundlingWithRUMEnabledAndRUMMonitorRegistered_whenSendingLog_itContainsCurrentRUMContext() throws { - let logging: LoggingFeature = .mockByRecordingLogMatchers( - directory: temporaryDirectory, - configuration: .mockWith(common: .mockWith(environment: "tests")) - ) + let core = DatadogCoreMock(v1Context: .mockAny()) + defer { core.flush() } + + let logging: LoggingFeature = .mockByRecordingLogMatchers(directory: temporaryDirectory) core.register(feature: logging) let rum: RUMFeature = .mockNoOp() @@ -556,10 +573,10 @@ class LoggerTests: XCTestCase { } func testGivenBundlingWithRUMEnabledButRUMMonitorNotRegistered_whenSendingLog_itPrintsWarning() throws { - let logging: LoggingFeature = .mockByRecordingLogMatchers( - directory: temporaryDirectory, - configuration: .mockWith(common: .mockWith(environment: "tests")) - ) + let core = DatadogCoreMock(v1Context: .mockAny()) + defer { core.flush() } + + let logging: LoggingFeature = .mockByRecordingLogMatchers(directory: temporaryDirectory) core.register(feature: logging) let rum: RUMFeature = .mockNoOp() @@ -593,8 +610,8 @@ class LoggerTests: XCTestCase { } func testWhenSendingErrorOrCriticalLogs_itCreatesRUMErrorForCurrentView() throws { - temporaryFeatureDirectories.create() - defer { temporaryFeatureDirectories.delete() } + let core = DatadogCoreMock(v1Context: .mockAny()) + defer { core.flush() } let logging: LoggingFeature = .mockNoOp() core.register(feature: logging) @@ -640,9 +657,13 @@ class LoggerTests: XCTestCase { // MARK: - Integration With Active Span func testGivenBundlingWithTraceEnabledAndTracerRegistered_whenSendingLog_itContainsActiveSpanAttributes() throws { + let core = DatadogCoreMock(v1Context: .mockAny()) + defer { core.flush() } + let logging: LoggingFeature = .mockByRecordingLogMatchers(directory: temporaryDirectory) - let tracing: TracingFeature = .mockNoOp() core.register(feature: logging) + + let tracing: TracingFeature = .mockNoOp() core.register(feature: tracing) // given @@ -671,9 +692,13 @@ class LoggerTests: XCTestCase { } func testGivenBundlingWithTraceEnabledButTracerNotRegistered_whenSendingLog_itPrintsWarning() throws { - let feature: LoggingFeature = .mockByRecordingLogMatchers(directory: temporaryDirectory) + let core = DatadogCoreMock(v1Context: .mockAny()) + defer { core.flush() } + + let logging: LoggingFeature = .mockByRecordingLogMatchers(directory: temporaryDirectory) + core.register(feature: logging) + let tracing: TracingFeature = .mockNoOp() - core.register(feature: feature) core.register(feature: tracing) let previousUserLogger = userLogger @@ -696,7 +721,7 @@ class LoggerTests: XCTestCase { .contains("Tracing feature is enabled, but no `Tracer` is registered. The Tracing integration with Logging will not work.") ) - let logMatcher = try feature.waitAndReturnLogMatchers(count: 1)[0] + let logMatcher = try logging.waitAndReturnLogMatchers(count: 1)[0] logMatcher.assertNoValue(forKeyPath: LoggingWithActiveSpanIntegration.Attributes.traceID) logMatcher.assertNoValue(forKeyPath: LoggingWithActiveSpanIntegration.Attributes.spanID) } @@ -707,16 +732,18 @@ class LoggerTests: XCTestCase { // Given let deviceTime: Date = .mockDecember15th2019At10AMUTC() let serverTimeDifference = TimeInterval.random(in: -5..<5).rounded() // few seconds difference - - // When - let feature: LoggingFeature = .mockByRecordingLogMatchers( - directory: temporaryDirectory, - dependencies: .mockWith( - dateProvider: RelativeDateProvider(using: deviceTime), - dateCorrector: DateCorrectorMock(correctionOffset: serverTimeDifference) + let core = DatadogCoreMock( + v1Context: .mockWith( + dependencies: .mockWith( + dateProvider: RelativeDateProvider(using: deviceTime), + dateCorrector: DateCorrectorMock(correctionOffset: serverTimeDifference) + ) ) ) - defer { feature.deinitialize() } + defer { core.flush() } + + // When + let feature: LoggingFeature = .mockByRecordingLogMatchers(directory: temporaryDirectory) core.register(feature: feature) let logger = Logger.builder.build(in: core) @@ -728,50 +755,58 @@ class LoggerTests: XCTestCase { logDate == deviceTime.addingTimeInterval(serverTimeDifference) } } - - // MARK: - Tracking Consent - - func testWhenChangingConsentValues_itUploadsOnlyAuthorizedLogs() throws { - let consentProvider = ConsentProvider(initialConsent: .pending) - - // Given - let feature: LoggingFeature = .mockByRecordingLogMatchers( - directory: temporaryDirectory, - dependencies: .mockWith(consentProvider: consentProvider) - ) - defer { feature.deinitialize() } - core.register(feature: feature) - - let logger = Logger.builder.build(in: core) - - // When - logger.info("message in `.pending` consent changed to `.granted`") - consentProvider.changeConsent(to: .granted) - logger.info("message in `.granted` consent") - consentProvider.changeConsent(to: .notGranted) - logger.info("message in `.notGranted` consent") - consentProvider.changeConsent(to: .granted) - logger.info("another message in `.granted` consent") - - // Then - let logMatchers = try feature.waitAndReturnLogMatchers(count: 3) - logMatchers[0].assertMessage(equals: "message in `.pending` consent changed to `.granted`") - logMatchers[1].assertMessage(equals: "message in `.granted` consent") - logMatchers[2].assertMessage(equals: "another message in `.granted` consent") - } +// +// // MARK: - Tracking Consent +// +// func testWhenChangingConsentValues_itUploadsOnlyAuthorizedLogs() throws { +// let consentProvider = ConsentProvider(initialConsent: .pending) +// +// // Given +// let core = DatadogCoreMock( +// v1Context: .mockWith( +// dependencies: .mockWith( +// consentProvider: consentProvider +// ) +// ) +// ) +// defer { core.flush() } +// +// let logging: LoggingFeature = .mockByRecordingLogMatchers(directory: temporaryDirectory) +// core.register(feature: logging) +// +// let logger = Logger.builder.build(in: core) +// +// // When +// logger.info("message in `.pending` consent changed to `.granted`") +// consentProvider.changeConsent(to: .granted) +// logger.info("message in `.granted` consent") +// consentProvider.changeConsent(to: .notGranted) +// logger.info("message in `.notGranted` consent") +// consentProvider.changeConsent(to: .granted) +// logger.info("another message in `.granted` consent") +// +// // Then +// let logMatchers = try logging.waitAndReturnLogMatchers(count: 3) +// logMatchers[0].assertMessage(equals: "message in `.pending` consent changed to `.granted`") +// logMatchers[1].assertMessage(equals: "message in `.granted` consent") +// logMatchers[2].assertMessage(equals: "another message in `.granted` consent") +// } // MARK: - Thread safety func testRandomlyCallingDifferentAPIsConcurrentlyDoesNotCrash() { - let server = ServerMock(delivery: .success(response: .mockResponseWith(statusCode: 200))) - let feature: LoggingFeature = .mockNoOp() - defer { feature.deinitialize() } - core.register(feature: feature) + struct NoOpLogOutput: LogOutput { + func write(log: LogEvent) {} + } - let logger = Logger.builder - .sendLogsToDatadog(false) - .printLogsToConsole(false) - .build(in: core) + let logger = Logger( + logBuilder: .mockAny(), + logOutput: NoOpLogOutput(), + dateProvider: SystemDateProvider(), + identifier: .mockAny(), + rumContextIntegration: nil, + activeSpanIntegration: nil + ) DispatchQueue.concurrentPerform(iterations: 900) { iteration in let modulo = iteration % 3 @@ -790,8 +825,6 @@ class LoggerTests: XCTestCase { break } } - - server.waitAndAssertNoRequestsSent() } // MARK: - Usage @@ -802,10 +835,10 @@ class LoggerTests: XCTestCase { defer { consolePrint = { print($0) } } // given - XCTAssertFalse(Datadog.isInitialized) + let core = DatadogCoreMock(v1Context: nil) // when - let logger = Logger.builder.build() + let logger = Logger.builder.build(in: core) // then XCTAssertEqual( @@ -822,16 +855,11 @@ class LoggerTests: XCTestCase { defer { consolePrint = { print($0) } } // given - Datadog.initialize( - appContext: .mockAny(), - trackingConsent: .mockRandom(), - configuration: Datadog.Configuration.builderUsing(clientToken: "abc.def", environment: "tests") - .enableLogging(false) - .build() - ) + let core = DatadogCoreMock(v1Context: .mockAny()) + XCTAssertNil(core.feature(LoggingFeature.self)) // when - let logger = Logger.builder.build() + let logger = Logger.builder.build(in: core) // then XCTAssertEqual( @@ -840,8 +868,6 @@ class LoggerTests: XCTestCase { ) XCTAssertNil(logger.logBuilder) XCTAssertNil(logger.logOutput) - - Datadog.flushAndDeinitialize() } func testDDLoggerIsLoggerTypealias() { diff --git a/Tests/DatadogTests/Datadog/Logging/LoggingFeatureTests.swift b/Tests/DatadogTests/Datadog/Logging/LoggingFeatureTests.swift index 203e4eac66..1c7bacb0fd 100644 --- a/Tests/DatadogTests/Datadog/Logging/LoggingFeatureTests.swift +++ b/Tests/DatadogTests/Datadog/Logging/LoggingFeatureTests.swift @@ -8,17 +8,12 @@ import XCTest @testable import Datadog class LoggingFeatureTests: XCTestCase { - let core = DatadogCoreMock() - override func setUp() { super.setUp() - XCTAssertFalse(Datadog.isInitialized) temporaryDirectory.create() } override func tearDown() { - XCTAssertFalse(Datadog.isInitialized) - core.flush() temporaryDirectory.delete() super.tearDown() } @@ -40,25 +35,30 @@ class LoggingFeatureTests: XCTestCase { let server = ServerMock(delivery: .success(response: .mockResponseWith(statusCode: 200))) - // Given - let feature: LoggingFeature = .mockWith( - directory: temporaryDirectory, + let core = DatadogCore( + rootDirectory: temporaryDirectory, configuration: .mockWith( - common: .mockWith( - clientToken: randomClientToken, - applicationName: randomApplicationName, - applicationVersion: randomApplicationVersion, - source: randomSource, - origin: randomOrigin, - sdkVersion: randomSDKVersion, - encryption: randomEncryption - ), - uploadURL: randomUploadURL + clientToken: randomClientToken, + applicationName: randomApplicationName, + applicationVersion: randomApplicationVersion, + source: randomSource, + origin: randomOrigin, + sdkVersion: randomSDKVersion, + encryption: randomEncryption ), dependencies: .mockWith( mobileDevice: .mockWith(model: randomDeviceModel, osName: randomDeviceOSName, osVersion: randomDeviceOSVersion) ) ) + + // Given + let featureConfiguration: LoggingFeature.Configuration = .mockWith(uploadURL: randomUploadURL) + let feature: LoggingFeature = try core.create( + storageConfiguration: createV2LoggingStorageConfiguration(), + uploadConfiguration: createV2LoggingUploadConfiguration(v1Configuration: featureConfiguration), + featureSpecificConfiguration: featureConfiguration + ) + defer { feature.flush() } core.register(feature: feature) // When @@ -89,8 +89,10 @@ class LoggingFeatureTests: XCTestCase { func testItUsesExpectedPayloadFormatForUploads() throws { let server = ServerMock(delivery: .success(response: .mockResponseWith(statusCode: 200))) - let feature: LoggingFeature = .mockWith( - directory: temporaryDirectory, + + let core = DatadogCore( + rootDirectory: temporaryDirectory, + configuration: .mockAny(), dependencies: .mockWith( performance: .combining( storagePerformance: StoragePerformanceMock( @@ -111,6 +113,15 @@ class LoggingFeatureTests: XCTestCase { ) ) ) + + // Given + let featureConfiguration: LoggingFeature.Configuration = .mockAny() + let feature: LoggingFeature = try core.create( + storageConfiguration: createV2LoggingStorageConfiguration(), + uploadConfiguration: createV2LoggingUploadConfiguration(v1Configuration: featureConfiguration), + featureSpecificConfiguration: featureConfiguration + ) + defer { feature.flush() } core.register(feature: feature) let logger = Logger.builder.build(in: core) diff --git a/Tests/DatadogTests/Datadog/Mocks/CoreMocks.swift b/Tests/DatadogTests/Datadog/Mocks/CoreMocks.swift index 9887250bb9..862a9356d7 100644 --- a/Tests/DatadogTests/Datadog/Mocks/CoreMocks.swift +++ b/Tests/DatadogTests/Datadog/Mocks/CoreMocks.swift @@ -222,12 +222,10 @@ extension FeaturesConfiguration.Logging { static func mockAny() -> Self { mockWith() } static func mockWith( - common: FeaturesConfiguration.Common = .mockAny(), uploadURL: URL = .mockAny(), logEventMapper: LogEventMapper? = nil ) -> Self { return .init( - common: common, uploadURL: uploadURL, logEventMapper: logEventMapper ) diff --git a/Tests/DatadogTests/Datadog/Mocks/DatadogCoreMock.swift b/Tests/DatadogTests/Datadog/Mocks/DatadogCoreMock.swift index c138030e6f..828eae9d21 100644 --- a/Tests/DatadogTests/Datadog/Mocks/DatadogCoreMock.swift +++ b/Tests/DatadogTests/Datadog/Mocks/DatadogCoreMock.swift @@ -9,9 +9,9 @@ import Foundation internal final class DatadogCoreMock: Flushable { private var v1Features: [String: Any] = [:] - private var v1Context: DatadogV1Context + private var v1Context: DatadogV1Context? - init(v1Context: DatadogV1Context = .mockAny()) { + init(v1Context: DatadogV1Context? = .mockAny()) { self.v1Context = v1Context } @@ -46,7 +46,7 @@ extension DatadogCoreMock: DatadogV1CoreProtocol { return v1Features[key] as? T } - var context: Any { + var context: DatadogV1Context? { return v1Context } } diff --git a/Tests/DatadogTests/Datadog/Mocks/LoggingFeatureMocks.swift b/Tests/DatadogTests/Datadog/Mocks/LoggingFeatureMocks.swift index 7e93be90ff..1a8ddf5ee9 100644 --- a/Tests/DatadogTests/Datadog/Mocks/LoggingFeatureMocks.swift +++ b/Tests/DatadogTests/Datadog/Mocks/LoggingFeatureMocks.swift @@ -20,10 +20,9 @@ extension LoggingFeature { /// Mocks the feature instance which performs uploads to `URLSession`. /// Use `ServerMock` to inspect and assert recorded `URLRequests`. - static func mockWith( + private static func mockWith( directory: Directory, - configuration: FeaturesConfiguration.Logging = .mockAny(), - dependencies: FeaturesCommonDependencies = .mockAny(), + featureConfiguration: FeaturesConfiguration.Logging = .mockAny(), telemetry: Telemetry? = nil ) -> LoggingFeature { // Because in V2 Feature Storage and Upload are created by `DatadogCore`, here we ask @@ -31,15 +30,23 @@ extension LoggingFeature { // providing V1 stack for partial V2 architecture in tests. let v2Core = DatadogCore( rootDirectory: directory, - configuration: configuration.common, - dependencies: dependencies + // Here we mock anything for `configuration` and `dependencies` of the dummy core instance. + // + // These ARE NOT used by the Feature to produce events. Instead, the Feature uses + // context provided by standalone `DatadogCoreMock`. + // + // These ARE only used by the `FeatureStorage` and `FeatureUpload` to store and upload events. + // Because storage and upload are responsibilities of core in V2, we don't test it + // as part of Feature test. + configuration: .mockAny(), + dependencies: .mockAny() ) v2Core.telemetry = telemetry let feature: LoggingFeature = try! v2Core.create( storageConfiguration: createV2LoggingStorageConfiguration(), - uploadConfiguration: createV2LoggingUploadConfiguration(v1Configuration: configuration), - featureSpecificConfiguration: configuration + uploadConfiguration: createV2LoggingUploadConfiguration(v1Configuration: featureConfiguration), + featureSpecificConfiguration: featureConfiguration ) return feature } @@ -48,16 +55,16 @@ extension LoggingFeature { /// Use `LogFeature.waitAndReturnLogMatchers()` to inspect and assert recorded `Logs`. static func mockByRecordingLogMatchers( directory: Directory, - configuration: FeaturesConfiguration.Logging = .mockAny(), - dependencies: FeaturesCommonDependencies = .mockAny() + featureConfiguration: FeaturesConfiguration.Logging = .mockAny() ) -> LoggingFeature { // Get the full feature mock: let fullFeature: LoggingFeature = .mockWith( directory: directory, - configuration: configuration, - dependencies: dependencies.replacing( - dateProvider: SystemDateProvider() // replace date provider in mocked `Feature.Storage` - ) + featureConfiguration: featureConfiguration +// coreConfiguration: coreConfiguration, +// coreDependencies: coreDependencies.replacing( +// dateProvider: SystemDateProvider() // replace date provider in mocked `Feature.Storage` +// ) ) let uploadWorker = DataUploadWorkerMock() let observedStorage = uploadWorker.observe(featureStorage: fullFeature.storage) @@ -65,11 +72,15 @@ extension LoggingFeature { let mockedUpload = FeatureUpload(uploader: uploadWorker) // Tear down the original upload fullFeature.upload.flushAndTearDown() + return LoggingFeature( storage: observedStorage, upload: mockedUpload, - configuration: configuration, - commonDependencies: dependencies, + configuration: fullFeature.configuration, + // Here we mock anything for `commonDependencies`. This is only required by `V1FeatureInitializable` interface but not + // used in this Feature implementation. It will be removed after we update the `V1FeatureInitializable` interface + // for all other Features: + commonDependencies: .mockAny(), telemetry: nil ) } diff --git a/Tests/DatadogTests/Datadog/RUM/WebView/WKUserContentController+DatadogTests.swift b/Tests/DatadogTests/Datadog/RUM/WebView/WKUserContentController+DatadogTests.swift index d2a14c5dc2..4ed486350c 100644 --- a/Tests/DatadogTests/Datadog/RUM/WebView/WKUserContentController+DatadogTests.swift +++ b/Tests/DatadogTests/Datadog/RUM/WebView/WKUserContentController+DatadogTests.swift @@ -61,7 +61,8 @@ class WKUserContentController_DatadogTests: XCTestCase { allowedWebViewHosts: ["datadoghq.com"], hostsSanitizer: mockSanitizer, loggingFeature: nil, - rumFeature: nil + rumFeature: nil, + context: .mockAny() ) XCTAssertEqual(controller.userScripts.count, initialUserScriptCount + 1) @@ -91,7 +92,8 @@ class WKUserContentController_DatadogTests: XCTestCase { allowedWebViewHosts: ["datadoghq.com"], hostsSanitizer: mockSanitizer, loggingFeature: nil, - rumFeature: nil + rumFeature: nil, + context: .mockAny() ) } @@ -108,9 +110,10 @@ class WKUserContentController_DatadogTests: XCTestCase { } func testWhenStoppingTracking_itKeepsNonDatadogComponents() throws { + let core = DatadogCoreMock(v1Context: .mockAny()) let controller = DDUserContentController() - controller.trackDatadogEvents(in: []) + controller.trackDatadogEvents(in: [], sdk: core) let componentCount = 10 for i in 0.. Date: Thu, 2 Jun 2022 10:10:31 +0200 Subject: [PATCH 3/5] RUMM-2169 Replace `DataUploaderMock` with `InMemoryWriter`, which means we're no longer testing Core as part of `LoggingFeature` tests. Instead, the mock is installed early on the writting layer, and no-ops are used for batches orchestration and upload. --- .../Datadog/Mocks/CoreMocks.swift | 68 ++++++++++++++++++ .../Datadog/Mocks/LoggingFeatureMocks.swift | 71 ++++--------------- 2 files changed, 81 insertions(+), 58 deletions(-) diff --git a/Tests/DatadogTests/Datadog/Mocks/CoreMocks.swift b/Tests/DatadogTests/Datadog/Mocks/CoreMocks.swift index 862a9356d7..dec447a347 100644 --- a/Tests/DatadogTests/Datadog/Mocks/CoreMocks.swift +++ b/Tests/DatadogTests/Datadog/Mocks/CoreMocks.swift @@ -4,6 +4,7 @@ * Copyright 2019-2020 Datadog, Inc. */ +import XCTest @testable import Datadog // MARK: - Configuration Mocks @@ -608,6 +609,73 @@ class NoOpFileReader: SyncReader { func markAllFilesAsReadable() {} } +/// `AsyncWriter` stub which records events in-memory and allows reading their data back. +internal class InMemoryWriter: AsyncWriter { + let queue = DispatchQueue(label: "com.datadoghq.InMemoryWriter-\(UUID().uuidString)") + + private let encoder: JSONEncoder = .default() + private var events: [Data] = [] + + func write(value: T) where T: Encodable { + queue.async { + do { + let eventData = try self.encoder.encode(value) + self.events.append(eventData) + self.waitAndReturnEventsDataExpectation?.fulfill() + } catch { + assertionFailure("Failed to encode event: \(value)") + } + } + } + + func flushAndCancelSynchronously() { + queue.sync {} + } + + // MARK: - Retrieving Recorded Data + + private var waitAndReturnEventsDataExpectation: XCTestExpectation? + + /// Waits until given number of events is written and returns data for these events. + /// Passing no `timeout` will result with picking the recommended timeout for unit tests. + func waitAndReturnEventsData(count: UInt, timeout: TimeInterval? = nil, file: StaticString = #file, line: UInt = #line) -> [Data] { + precondition(waitAndReturnEventsDataExpectation == nil, "The `InMemoryWriter` is already waiting on `waitAndReturnEventsData`.") + + let expectation = XCTestExpectation(description: "Record \(count) events.") + if count > 0 { + expectation.expectedFulfillmentCount = Int(count) + } else { + expectation.isInverted = true + } + + queue.sync { + self.waitAndReturnEventsDataExpectation = expectation + self.events.forEach { _ in expectation.fulfill() } // fulfill already recorded events + } + + let recommendedTimeout = Double(max(1, count)) * 1 // 1s for each event + let timeout = timeout ?? recommendedTimeout + let result = XCTWaiter().wait(for: [expectation], timeout: timeout) + + switch result { + case .completed: + break + case .incorrectOrder, .interrupted: + fatalError("Can't happen.") + case .timedOut: + XCTFail("Exceeded timeout of \(timeout)s with recording \(events.count) out of \(count) expected events.", file: file, line: line) + return queue.sync { events } + case .invertedFulfillment: + XCTFail("\(events.count) batches were read, but not expected.", file: file, line: line) + return queue.sync { events } + @unknown default: + fatalError() + } + + return queue.sync { events } + } +} + extension DataFormat { static func mockAny() -> DataFormat { return mockWith() diff --git a/Tests/DatadogTests/Datadog/Mocks/LoggingFeatureMocks.swift b/Tests/DatadogTests/Datadog/Mocks/LoggingFeatureMocks.swift index 1a8ddf5ee9..89e1475a3f 100644 --- a/Tests/DatadogTests/Datadog/Mocks/LoggingFeatureMocks.swift +++ b/Tests/DatadogTests/Datadog/Mocks/LoggingFeatureMocks.swift @@ -18,68 +18,23 @@ extension LoggingFeature { ) } - /// Mocks the feature instance which performs uploads to `URLSession`. - /// Use `ServerMock` to inspect and assert recorded `URLRequests`. - private static func mockWith( - directory: Directory, - featureConfiguration: FeaturesConfiguration.Logging = .mockAny(), - telemetry: Telemetry? = nil - ) -> LoggingFeature { - // Because in V2 Feature Storage and Upload are created by `DatadogCore`, here we ask - // dummy V2 core instance to initialize the Feature. It is hacky, yet minimal way of - // providing V1 stack for partial V2 architecture in tests. - let v2Core = DatadogCore( - rootDirectory: directory, - // Here we mock anything for `configuration` and `dependencies` of the dummy core instance. - // - // These ARE NOT used by the Feature to produce events. Instead, the Feature uses - // context provided by standalone `DatadogCoreMock`. - // - // These ARE only used by the `FeatureStorage` and `FeatureUpload` to store and upload events. - // Because storage and upload are responsibilities of core in V2, we don't test it - // as part of Feature test. - configuration: .mockAny(), - dependencies: .mockAny() - ) - v2Core.telemetry = telemetry - - let feature: LoggingFeature = try! v2Core.create( - storageConfiguration: createV2LoggingStorageConfiguration(), - uploadConfiguration: createV2LoggingUploadConfiguration(v1Configuration: featureConfiguration), - featureSpecificConfiguration: featureConfiguration - ) - return feature - } - - /// Mocks the feature instance which performs uploads to mocked `DataUploadWorker`. + /// Mocks the feature instance which performs writes to `InMemoryWriter`. /// Use `LogFeature.waitAndReturnLogMatchers()` to inspect and assert recorded `Logs`. static func mockByRecordingLogMatchers( directory: Directory, featureConfiguration: FeaturesConfiguration.Logging = .mockAny() ) -> LoggingFeature { - // Get the full feature mock: - let fullFeature: LoggingFeature = .mockWith( - directory: directory, - featureConfiguration: featureConfiguration -// coreConfiguration: coreConfiguration, -// coreDependencies: coreDependencies.replacing( -// dateProvider: SystemDateProvider() // replace date provider in mocked `Feature.Storage` -// ) + // Mock storage with `InMemoryWriter`, used later for retrieving recorded events back: + let interceptedStorage = FeatureStorage( + writer: InMemoryWriter(), + reader: NoOpFileReader(), + arbitraryAuthorizedWriter: NoOpFileWriter(), + dataOrchestrator: NoOpDataOrchestrator() ) - let uploadWorker = DataUploadWorkerMock() - let observedStorage = uploadWorker.observe(featureStorage: fullFeature.storage) - // Replace by mocking the `FeatureUpload` and observing the `FeatureStorage`: - let mockedUpload = FeatureUpload(uploader: uploadWorker) - // Tear down the original upload - fullFeature.upload.flushAndTearDown() - return LoggingFeature( - storage: observedStorage, - upload: mockedUpload, - configuration: fullFeature.configuration, - // Here we mock anything for `commonDependencies`. This is only required by `V1FeatureInitializable` interface but not - // used in this Feature implementation. It will be removed after we update the `V1FeatureInitializable` interface - // for all other Features: + storage: interceptedStorage, + upload: .mockNoOp(), + configuration: featureConfiguration, commonDependencies: .mockAny(), telemetry: nil ) @@ -88,11 +43,11 @@ extension LoggingFeature { // MARK: - Expecting Logs Data func waitAndReturnLogMatchers(count: UInt, file: StaticString = #file, line: UInt = #line) throws -> [LogMatcher] { - guard let uploadWorker = upload.uploader as? DataUploadWorkerMock else { + guard let inMemoryWriter = storage.writer as? InMemoryWriter else { preconditionFailure("Retrieving matchers requires that feature is mocked with `.mockByRecordingLogMatchers()`") } - return try uploadWorker.waitAndReturnBatchedData(count: count, file: file, line: line) - .flatMap { batchData in try LogMatcher.fromArrayOfJSONObjectsData(batchData, file: file, line: line) } + return try inMemoryWriter.waitAndReturnEventsData(count: count, file: file, line: line) + .map { eventData in try LogMatcher.fromJSONObjectData(eventData) } } // swiftlint:disable:next function_default_parameter_at_end From daff9e62b636571c296cf99d6839bf724baa2488 Mon Sep 17 00:00:00 2001 From: Maciek Grzybowski Date: Thu, 2 Jun 2022 10:15:18 +0200 Subject: [PATCH 4/5] RUMM-2169 Remove core-specific tests from `LoggerTests`, because these behaviours should be tested as part of Core tests. Whats more: - the tracking consent logic is already covered in `FeatureStorage` tests, - the upload condition logic is for now covered in `RUMMonitor` and `Tracer` tests, but will be covered in `FeatureUpload` once all features are migrated in RUMM-2169. --- Tests/DatadogTests/Datadog/LoggerTests.swift | 83 -------------------- 1 file changed, 83 deletions(-) diff --git a/Tests/DatadogTests/Datadog/LoggerTests.swift b/Tests/DatadogTests/Datadog/LoggerTests.swift index 6bfe20114c..2eb9b34c43 100644 --- a/Tests/DatadogTests/Datadog/LoggerTests.swift +++ b/Tests/DatadogTests/Datadog/LoggerTests.swift @@ -482,53 +482,6 @@ class LoggerTests: XCTestCase { logMatchers[1].assertTags(equal: ["tag1", "tag2:abcd", "env:tests"]) logMatchers[2].assertTags(equal: ["env:tests"]) } -// -// // MARK: - Sending logs with different network and battery conditions -// -// func testGivenBadBatteryConditions_itDoesNotTryToSendLogs() throws { -// let server = ServerMock(delivery: .success(response: .mockResponseWith(statusCode: 200))) -// -// let core = DatadogCoreMock( -// v1Context: .mockWith( -// dependencies: .mockWith( -// mobileDevice: .mockWith( -// currentBatteryStatus: { () -> MobileDevice.BatteryStatus in -// .mockWith(state: .charging, level: 0.05, isLowPowerModeEnabled: true) -// } -// ) -// ) -// ) -// ) -// defer { core.flush() } -// -// let feature: LoggingFeature = .mockWith(directory: temporaryDirectory) -// defer { feature.deinitialize() } -// core.register(feature: feature) -// -// let logger = Logger.builder.build(in: core) -// logger.debug("message") -// -// server.waitAndAssertNoRequestsSent() -// } - -// func testGivenNoNetworkConnection_itDoesNotTryToSendLogs() throws { -// let server = ServerMock(delivery: .success(response: .mockResponseWith(statusCode: 200))) -// let feature: LoggingFeature = .mockWith( -// directory: temporaryDirectory, -// dependencies: .mockWith( -// networkConnectionInfoProvider: NetworkConnectionInfoProviderMock.mockWith( -// networkConnectionInfo: .mockWith(reachability: .no) -// ) -// ) -// ) -// defer { feature.deinitialize() } -// core.register(feature: feature) -// -// let logger = Logger.builder.build(in: core) -// logger.debug("message") -// -// server.waitAndAssertNoRequestsSent() -// } // MARK: - Integration With RUM Feature @@ -755,42 +708,6 @@ class LoggerTests: XCTestCase { logDate == deviceTime.addingTimeInterval(serverTimeDifference) } } -// -// // MARK: - Tracking Consent -// -// func testWhenChangingConsentValues_itUploadsOnlyAuthorizedLogs() throws { -// let consentProvider = ConsentProvider(initialConsent: .pending) -// -// // Given -// let core = DatadogCoreMock( -// v1Context: .mockWith( -// dependencies: .mockWith( -// consentProvider: consentProvider -// ) -// ) -// ) -// defer { core.flush() } -// -// let logging: LoggingFeature = .mockByRecordingLogMatchers(directory: temporaryDirectory) -// core.register(feature: logging) -// -// let logger = Logger.builder.build(in: core) -// -// // When -// logger.info("message in `.pending` consent changed to `.granted`") -// consentProvider.changeConsent(to: .granted) -// logger.info("message in `.granted` consent") -// consentProvider.changeConsent(to: .notGranted) -// logger.info("message in `.notGranted` consent") -// consentProvider.changeConsent(to: .granted) -// logger.info("another message in `.granted` consent") -// -// // Then -// let logMatchers = try logging.waitAndReturnLogMatchers(count: 3) -// logMatchers[0].assertMessage(equals: "message in `.pending` consent changed to `.granted`") -// logMatchers[1].assertMessage(equals: "message in `.granted` consent") -// logMatchers[2].assertMessage(equals: "another message in `.granted` consent") -// } // MARK: - Thread safety From 2c9645f726937f2a2709de368501468b9173c239 Mon Sep 17 00:00:00 2001 From: Maciek Grzybowski Date: Thu, 2 Jun 2022 12:07:38 +0200 Subject: [PATCH 5/5] RUMM-2169 CR feedback - make `v1Context` mutable in `DatadogCoreMock` --- Tests/DatadogTests/Datadog/LoggerTests.swift | 117 +++++++----------- .../Datadog/Mocks/DatadogCoreMock.swift | 3 +- Tests/DatadogTests/Datadog/TracerTests.swift | 42 +++---- .../DatadogObjc/DDTracerTests.swift | 45 ++----- 4 files changed, 77 insertions(+), 130 deletions(-) diff --git a/Tests/DatadogTests/Datadog/LoggerTests.swift b/Tests/DatadogTests/Datadog/LoggerTests.swift index 2eb9b34c43..5df00e4ac7 100644 --- a/Tests/DatadogTests/Datadog/LoggerTests.swift +++ b/Tests/DatadogTests/Datadog/LoggerTests.swift @@ -9,12 +9,17 @@ import XCTest // swiftlint:disable multiline_arguments_brackets class LoggerTests: XCTestCase { + private var core: DatadogCoreMock! // swiftlint:disable:this implicitly_unwrapped_optional + override func setUp() { super.setUp() temporaryDirectory.create() + core = DatadogCoreMock() } override func tearDown() { + core.flush() + core = nil temporaryDirectory.delete() super.tearDown() } @@ -22,21 +27,18 @@ class LoggerTests: XCTestCase { // MARK: - Customizing Logger func testSendingLogWithDefaultLogger() throws { - let core = DatadogCoreMock( - v1Context: .mockWith( - configuration: .mockWith( - applicationVersion: "1.0.0", - applicationBundleIdentifier: "com.datadoghq.ios-sdk", - serviceName: "default-service-name", - environment: "tests", - sdkVersion: "1.2.3" - ), - dependencies: .mockWith( - dateProvider: RelativeDateProvider(using: .mockDecember15th2019At10AMUTC()) - ) + core.v1Context = .mockWith( + configuration: .mockWith( + applicationVersion: "1.0.0", + applicationBundleIdentifier: "com.datadoghq.ios-sdk", + serviceName: "default-service-name", + environment: "tests", + sdkVersion: "1.2.3" + ), + dependencies: .mockWith( + dateProvider: RelativeDateProvider(using: .mockDecember15th2019At10AMUTC()) ) ) - defer { core.flush() } let feature: LoggingFeature = .mockByRecordingLogMatchers(directory: temporaryDirectory) core.register(feature: feature) @@ -61,8 +63,7 @@ class LoggerTests: XCTestCase { } func testSendingLogWithCustomizedLogger() throws { - let core = DatadogCoreMock(v1Context: .mockAny()) - defer { core.flush() } + core.v1Context = .mockAny() let feature: LoggingFeature = .mockByRecordingLogMatchers(directory: temporaryDirectory) core.register(feature: feature) @@ -95,14 +96,11 @@ class LoggerTests: XCTestCase { // MARK: - Sending Customized Logs func testSendingLogsWithDifferentDates() throws { - let core = DatadogCoreMock( - v1Context: .mockWith( - dependencies: .mockWith( - dateProvider: RelativeDateProvider(startingFrom: .mockDecember15th2019At10AMUTC(), advancingBySeconds: 1) - ) + core.v1Context = .mockWith( + dependencies: .mockWith( + dateProvider: RelativeDateProvider(startingFrom: .mockDecember15th2019At10AMUTC(), advancingBySeconds: 1) ) ) - defer { core.flush() } let feature: LoggingFeature = .mockByRecordingLogMatchers(directory: temporaryDirectory) core.register(feature: feature) @@ -119,8 +117,7 @@ class LoggerTests: XCTestCase { } func testSendingLogsWithDifferentLevels() throws { - let core = DatadogCoreMock(v1Context: .mockAny()) - defer { core.flush() } + core.v1Context = .mockAny() let feature: LoggingFeature = .mockByRecordingLogMatchers(directory: temporaryDirectory) core.register(feature: feature) @@ -145,8 +142,7 @@ class LoggerTests: XCTestCase { // MARK: - Logging an error func testLoggingError() throws { - let core = DatadogCoreMock(v1Context: .mockAny()) - defer { core.flush() } + core.v1Context = .mockAny() let feature: LoggingFeature = .mockByRecordingLogMatchers(directory: temporaryDirectory) core.register(feature: feature) @@ -177,14 +173,11 @@ class LoggerTests: XCTestCase { func testSendingUserInfo() throws { let userInfoProvider = UserInfoProvider() - let core = DatadogCoreMock( - v1Context: .mockWith( - dependencies: .mockWith( - userInfoProvider: userInfoProvider - ) + core.v1Context = .mockWith( + dependencies: .mockWith( + userInfoProvider: userInfoProvider ) ) - defer { core.flush() } let feature: LoggingFeature = .mockByRecordingLogMatchers(directory: temporaryDirectory) core.register(feature: feature) @@ -236,14 +229,11 @@ class LoggerTests: XCTestCase { func testSendingCarrierInfoWhenEnteringAndLeavingCellularServiceRange() throws { let carrierInfoProvider = CarrierInfoProviderMock(carrierInfo: nil) - let core = DatadogCoreMock( - v1Context: .mockWith( - dependencies: .mockWith( - carrierInfoProvider: carrierInfoProvider - ) + core.v1Context = .mockWith( + dependencies: .mockWith( + carrierInfoProvider: carrierInfoProvider ) ) - defer { core.flush() } let feature: LoggingFeature = .mockByRecordingLogMatchers(directory: temporaryDirectory) core.register(feature: feature) @@ -282,14 +272,11 @@ class LoggerTests: XCTestCase { func testSendingNetworkConnectionInfoWhenReachabilityChanges() throws { let networkConnectionInfoProvider = NetworkConnectionInfoProviderMock.mockAny() - let core = DatadogCoreMock( - v1Context: .mockWith( - dependencies: .mockWith( - networkConnectionInfoProvider: networkConnectionInfoProvider - ) + core.v1Context = .mockWith( + dependencies: .mockWith( + networkConnectionInfoProvider: networkConnectionInfoProvider ) ) - defer { core.flush() } let feature: LoggingFeature = .mockByRecordingLogMatchers(directory: temporaryDirectory) core.register(feature: feature) @@ -345,8 +332,7 @@ class LoggerTests: XCTestCase { // MARK: - Sending attributes func testSendingLoggerAttributesOfDifferentEncodableValues() throws { - let core = DatadogCoreMock(v1Context: .mockAny()) - defer { core.flush() } + core.v1Context = .mockAny() let feature: LoggingFeature = .mockByRecordingLogMatchers(directory: temporaryDirectory) core.register(feature: feature) @@ -412,8 +398,7 @@ class LoggerTests: XCTestCase { } func testSendingMessageAttributes() throws { - let core = DatadogCoreMock(v1Context: .mockAny()) - defer { core.flush() } + core.v1Context = .mockAny() let feature: LoggingFeature = .mockByRecordingLogMatchers(directory: temporaryDirectory) core.register(feature: feature) @@ -444,12 +429,9 @@ class LoggerTests: XCTestCase { // MARK: - Sending tags func testSendingTags() throws { - let core = DatadogCoreMock( - v1Context: .mockWith( - configuration: .mockWith(environment: "tests") - ) + core.v1Context = .mockWith( + configuration: .mockWith(environment: "tests") ) - defer { core.flush() } let feature: LoggingFeature = .mockByRecordingLogMatchers(directory: temporaryDirectory) core.register(feature: feature) @@ -486,8 +468,7 @@ class LoggerTests: XCTestCase { // MARK: - Integration With RUM Feature func testGivenBundlingWithRUMEnabledAndRUMMonitorRegistered_whenSendingLog_itContainsCurrentRUMContext() throws { - let core = DatadogCoreMock(v1Context: .mockAny()) - defer { core.flush() } + core.v1Context = .mockAny() let logging: LoggingFeature = .mockByRecordingLogMatchers(directory: temporaryDirectory) core.register(feature: logging) @@ -526,8 +507,7 @@ class LoggerTests: XCTestCase { } func testGivenBundlingWithRUMEnabledButRUMMonitorNotRegistered_whenSendingLog_itPrintsWarning() throws { - let core = DatadogCoreMock(v1Context: .mockAny()) - defer { core.flush() } + core.v1Context = .mockAny() let logging: LoggingFeature = .mockByRecordingLogMatchers(directory: temporaryDirectory) core.register(feature: logging) @@ -563,8 +543,7 @@ class LoggerTests: XCTestCase { } func testWhenSendingErrorOrCriticalLogs_itCreatesRUMErrorForCurrentView() throws { - let core = DatadogCoreMock(v1Context: .mockAny()) - defer { core.flush() } + core.v1Context = .mockAny() let logging: LoggingFeature = .mockNoOp() core.register(feature: logging) @@ -610,8 +589,7 @@ class LoggerTests: XCTestCase { // MARK: - Integration With Active Span func testGivenBundlingWithTraceEnabledAndTracerRegistered_whenSendingLog_itContainsActiveSpanAttributes() throws { - let core = DatadogCoreMock(v1Context: .mockAny()) - defer { core.flush() } + core.v1Context = .mockAny() let logging: LoggingFeature = .mockByRecordingLogMatchers(directory: temporaryDirectory) core.register(feature: logging) @@ -645,8 +623,7 @@ class LoggerTests: XCTestCase { } func testGivenBundlingWithTraceEnabledButTracerNotRegistered_whenSendingLog_itPrintsWarning() throws { - let core = DatadogCoreMock(v1Context: .mockAny()) - defer { core.flush() } + core.v1Context = .mockAny() let logging: LoggingFeature = .mockByRecordingLogMatchers(directory: temporaryDirectory) core.register(feature: logging) @@ -685,15 +662,13 @@ class LoggerTests: XCTestCase { // Given let deviceTime: Date = .mockDecember15th2019At10AMUTC() let serverTimeDifference = TimeInterval.random(in: -5..<5).rounded() // few seconds difference - let core = DatadogCoreMock( - v1Context: .mockWith( - dependencies: .mockWith( - dateProvider: RelativeDateProvider(using: deviceTime), - dateCorrector: DateCorrectorMock(correctionOffset: serverTimeDifference) - ) + + core.v1Context = .mockWith( + dependencies: .mockWith( + dateProvider: RelativeDateProvider(using: deviceTime), + dateCorrector: DateCorrectorMock(correctionOffset: serverTimeDifference) ) ) - defer { core.flush() } // When let feature: LoggingFeature = .mockByRecordingLogMatchers(directory: temporaryDirectory) @@ -752,7 +727,7 @@ class LoggerTests: XCTestCase { defer { consolePrint = { print($0) } } // given - let core = DatadogCoreMock(v1Context: nil) + core.v1Context = nil // when let logger = Logger.builder.build(in: core) @@ -772,7 +747,7 @@ class LoggerTests: XCTestCase { defer { consolePrint = { print($0) } } // given - let core = DatadogCoreMock(v1Context: .mockAny()) + core.v1Context = .mockAny() XCTAssertNil(core.feature(LoggingFeature.self)) // when diff --git a/Tests/DatadogTests/Datadog/Mocks/DatadogCoreMock.swift b/Tests/DatadogTests/Datadog/Mocks/DatadogCoreMock.swift index 828eae9d21..6feb774e77 100644 --- a/Tests/DatadogTests/Datadog/Mocks/DatadogCoreMock.swift +++ b/Tests/DatadogTests/Datadog/Mocks/DatadogCoreMock.swift @@ -9,7 +9,8 @@ import Foundation internal final class DatadogCoreMock: Flushable { private var v1Features: [String: Any] = [:] - private var v1Context: DatadogV1Context? + + var v1Context: DatadogV1Context? init(v1Context: DatadogV1Context? = .mockAny()) { self.v1Context = v1Context diff --git a/Tests/DatadogTests/Datadog/TracerTests.swift b/Tests/DatadogTests/Datadog/TracerTests.swift index 4b2c200093..eb775a0b91 100644 --- a/Tests/DatadogTests/Datadog/TracerTests.swift +++ b/Tests/DatadogTests/Datadog/TracerTests.swift @@ -9,15 +9,17 @@ import XCTest // swiftlint:disable multiline_arguments_brackets class TracerTests: XCTestCase { - let core = DatadogCoreMock() + private var core: DatadogCoreMock! // swiftlint:disable:this implicitly_unwrapped_optional override func setUp() { super.setUp() temporaryDirectory.create() + core = DatadogCoreMock() } override func tearDown() { core.flush() + core = nil temporaryDirectory.delete() super.tearDown() } @@ -615,14 +617,11 @@ class TracerTests: XCTestCase { // MARK: - Integration With Logging Feature func testSendingSpanLogs() throws { - let core = DatadogCoreMock( - v1Context: .mockWith( - dependencies: .mockWith( - performance: .combining(storagePerformance: .readAllFiles, uploadPerformance: .veryQuick) - ) + core.v1Context = .mockWith( + dependencies: .mockWith( + performance: .combining(storagePerformance: .readAllFiles, uploadPerformance: .veryQuick) ) ) - defer { core.flush() } let logging: LoggingFeature = .mockByRecordingLogMatchers(directory: temporaryDirectory) core.register(feature: logging) @@ -662,14 +661,11 @@ class TracerTests: XCTestCase { } func testSendingSpanLogsWithErrorFromArguments() throws { - let core = DatadogCoreMock( - v1Context: .mockWith( - dependencies: .mockWith( - performance: .combining(storagePerformance: .readAllFiles, uploadPerformance: .veryQuick) - ) + core.v1Context = .mockWith( + dependencies: .mockWith( + performance: .combining(storagePerformance: .readAllFiles, uploadPerformance: .veryQuick) ) ) - defer { core.flush() } let logging: LoggingFeature = .mockByRecordingLogMatchers(directory: temporaryDirectory) core.register(feature: logging) @@ -701,14 +697,11 @@ class TracerTests: XCTestCase { } func testSendingSpanLogsWithErrorFromNSError() throws { - let core = DatadogCoreMock( - v1Context: .mockWith( - dependencies: .mockWith( - performance: .combining(storagePerformance: .readAllFiles, uploadPerformance: .veryQuick) - ) + core.v1Context = .mockWith( + dependencies: .mockWith( + performance: .combining(storagePerformance: .readAllFiles, uploadPerformance: .veryQuick) ) ) - defer { core.flush() } let logging: LoggingFeature = .mockByRecordingLogMatchers(directory: temporaryDirectory) core.register(feature: logging) @@ -746,14 +739,11 @@ class TracerTests: XCTestCase { } func testSendingSpanLogsWithErrorFromSwiftError() throws { - let core = DatadogCoreMock( - v1Context: .mockWith( - dependencies: .mockWith( - performance: .combining(storagePerformance: .readAllFiles, uploadPerformance: .veryQuick) - ) + core.v1Context = .mockWith( + dependencies: .mockWith( + performance: .combining(storagePerformance: .readAllFiles, uploadPerformance: .veryQuick) ) ) - defer { core.flush() } let logging: LoggingFeature = .mockByRecordingLogMatchers(directory: temporaryDirectory) core.register(feature: logging) @@ -1034,7 +1024,7 @@ class TracerTests: XCTestCase { defer { consolePrint = { print($0) } } // given - let core = DatadogCoreMock(v1Context: nil) + core.v1Context = nil // when let tracer = Tracer.initialize(configuration: .init(), in: core) diff --git a/Tests/DatadogTests/DatadogObjc/DDTracerTests.swift b/Tests/DatadogTests/DatadogObjc/DDTracerTests.swift index 8b94a10a97..b613791d56 100644 --- a/Tests/DatadogTests/DatadogObjc/DDTracerTests.swift +++ b/Tests/DatadogTests/DatadogObjc/DDTracerTests.swift @@ -9,18 +9,20 @@ import XCTest @testable import DatadogObjc class DDTracerTests: XCTestCase { - let core = DatadogCoreMock() + private var core: DatadogCoreMock! // swiftlint:disable:this implicitly_unwrapped_optional override func setUp() { super.setUp() temporaryDirectory.create() + core = DatadogCoreMock() defaultDatadogCore = core } override func tearDown() { + defaultDatadogCore = NOOPDatadogCore() core.flush() + core = nil temporaryDirectory.delete() - defaultDatadogCore = NOOPDatadogCore() super.tearDown() } @@ -117,18 +119,11 @@ class DDTracerTests: XCTestCase { } func testSendingSpanLogs() throws { - let core = DatadogCoreMock( - v1Context: .mockWith( - dependencies: .mockWith( - performance: .combining(storagePerformance: .readAllFiles, uploadPerformance: .veryQuick) - ) + core.v1Context = .mockWith( + dependencies: .mockWith( + performance: .combining(storagePerformance: .readAllFiles, uploadPerformance: .veryQuick) ) ) - defaultDatadogCore = core - defer { - core.flush() - defaultDatadogCore = NOOPDatadogCore() - } let logging: LoggingFeature = .mockByRecordingLogMatchers(directory: temporaryDirectory) core.register(feature: logging) @@ -157,18 +152,11 @@ class DDTracerTests: XCTestCase { } func testSendingSpanLogsWithErrorFromArguments() throws { - let core = DatadogCoreMock( - v1Context: .mockWith( - dependencies: .mockWith( - performance: .combining(storagePerformance: .readAllFiles, uploadPerformance: .veryQuick) - ) + core.v1Context = .mockWith( + dependencies: .mockWith( + performance: .combining(storagePerformance: .readAllFiles, uploadPerformance: .veryQuick) ) ) - defaultDatadogCore = core - defer { - core.flush() - defaultDatadogCore = NOOPDatadogCore() - } let logging: LoggingFeature = .mockByRecordingLogMatchers(directory: temporaryDirectory) core.register(feature: logging) @@ -200,18 +188,11 @@ class DDTracerTests: XCTestCase { } func testSendingSpanLogsWithErrorFromNSError() throws { - let core = DatadogCoreMock( - v1Context: .mockWith( - dependencies: .mockWith( - performance: .combining(storagePerformance: .readAllFiles, uploadPerformance: .veryQuick) - ) + core.v1Context = .mockWith( + dependencies: .mockWith( + performance: .combining(storagePerformance: .readAllFiles, uploadPerformance: .veryQuick) ) ) - defaultDatadogCore = core - defer { - core.flush() - defaultDatadogCore = NOOPDatadogCore() - } let logging: LoggingFeature = .mockByRecordingLogMatchers(directory: temporaryDirectory) core.register(feature: logging)