diff --git a/Datadog/Example/Base.lproj/Main.storyboard b/Datadog/Example/Base.lproj/Main.storyboard
index 7be1531e1c..69ba745349 100644
--- a/Datadog/Example/Base.lproj/Main.storyboard
+++ b/Datadog/Example/Base.lproj/Main.storyboard
@@ -320,28 +320,74 @@
-
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
-
+
-
+
@@ -367,6 +413,7 @@
+
diff --git a/Datadog/Example/Debugging/DebugLoggingViewController.swift b/Datadog/Example/Debugging/DebugLoggingViewController.swift
index bcf967b44e..fd14b1a19d 100644
--- a/Datadog/Example/Debugging/DebugLoggingViewController.swift
+++ b/Datadog/Example/Debugging/DebugLoggingViewController.swift
@@ -5,6 +5,7 @@
*/
import UIKit
+import Datadog
class DebugLoggingViewController: UIViewController {
@IBOutlet weak var logLevelSegmentedControl: UISegmentedControl!
@@ -12,6 +13,7 @@ class DebugLoggingViewController: UIViewController {
@IBOutlet weak var logServiceNameTextField: UITextField!
@IBOutlet weak var sendOnceButton: UIButton!
@IBOutlet weak var send10xButton: UIButton!
+ @IBOutlet weak var stressTestButton: UIButton!
@IBOutlet weak var consoleTextView: UITextView!
struct StructError: Error {
@@ -78,4 +80,43 @@ class DebugLoggingViewController: UIViewController {
private func repeat10x(block: () -> Void) {
(0..<10).forEach { _ in block() }
}
+
+ // MARK: - Stress testing
+
+ var queues: [DispatchQueue] = []
+ var loggers: [Logger] = []
+
+ @IBAction func didTapStressTest(_ sender: Any) {
+ stressTestButton.disableFor(seconds: 10)
+
+ loggers = (0..<5).map { index in
+ return Logger.builder.set(loggerName: "stress-logger-\(index)")
+ .sendNetworkInfo(true)
+ .build()
+ }
+
+ queues = (0..<5).map { index in
+ return DispatchQueue(label: "com.datadoghq.example.stress-testing-queue\(index)")
+ }
+
+ let endDate = Date(timeIntervalSinceNow: 10) // 10s
+ zip(loggers, queues).forEach { logger, queue in
+ keepSendingLogs(on: queue, using: logger, every: 0.01, until: endDate)
+ }
+ }
+
+ private func keepSendingLogs(on queue: DispatchQueue, using logger: Logger, every timeInterval: TimeInterval, until endDate: Date) {
+ if Date() < endDate {
+ queue.asyncAfter(deadline: .now() + timeInterval) { [weak self] in
+ logger.debug(self?.randomLogMessage() ?? "")
+ self?.keepSendingLogs(on: queue, using: logger, every: timeInterval, until: endDate)
+ }
+ }
+ }
+
+ private let alphanumerics = "abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789"
+
+ private func randomLogMessage() -> String {
+ return String((0..<20).map { _ in alphanumerics.randomElement()! })
+ }
}
diff --git a/Sources/Datadog/Core/System/CarrierInfoProvider.swift b/Sources/Datadog/Core/System/CarrierInfoProvider.swift
index 61817616eb..89aac41367 100644
--- a/Sources/Datadog/Core/System/CarrierInfoProvider.swift
+++ b/Sources/Datadog/Core/System/CarrierInfoProvider.swift
@@ -66,88 +66,147 @@ extension CarrierInfo.RadioAccessTechnology {
}
}
-/// An interface for the target-specific carrier info provider.
-internal protocol WrappedCarrierInfoProvider {
- var current: CarrierInfo? { get }
-}
-
+/// Platform-agnostic carrier info provider. It wraps the platform-specific provider inside.
internal class CarrierInfoProvider: CarrierInfoProviderType {
/// The `CarrierInfo` provider for the current platform.
- private let wrappedProvider: WrappedCarrierInfoProvider
- /// Publisher for notifying observers on `CarrierInfo` change.
- private let publisher: ValuePublisher
+ private let wrappedProvider: CarrierInfoProviderType
convenience init() {
#if targetEnvironment(macCatalyst)
- self.init(
- wrappedProvider: MacCatalystCarrierInfoProvider()
- )
+ self.init(
+ wrappedProvider: MacCatalystCarrierInfoProvider()
+ )
#else
- self.init(
- wrappedProvider: iOSCarrierInfoProvider(
- networkInfo: CTTelephonyNetworkInfo()
- )
- )
+ if #available(iOS 12.0, *) {
+ self.init(wrappedProvider: iOS12CarrierInfoProvider(networkInfo: CTTelephonyNetworkInfo()))
+ } else {
+ self.init(wrappedProvider: iOS11CarrierInfoProvider(networkInfo: CTTelephonyNetworkInfo()))
+ }
#endif
}
- init(wrappedProvider: WrappedCarrierInfoProvider) {
+ init(wrappedProvider: CarrierInfoProviderType) {
self.wrappedProvider = wrappedProvider
- self.publisher = ValuePublisher(initialValue: nil)
}
var current: CarrierInfo? {
- let nextValue = wrappedProvider.current
- // `CarrierInfo` subscribers are notified as a side-effect of retrieving the
- // current `CarrierInfo` value.
- publisher.publishAsync(nextValue)
- return nextValue
+ wrappedProvider.current
}
func subscribe(_ subscriber: Observer) where Observer.ObservedValue == CarrierInfo? {
- publisher.subscribe(subscriber)
+ wrappedProvider.subscribe(subscriber)
}
}
#if targetEnvironment(macCatalyst)
-internal struct MacCatalystCarrierInfoProvider: WrappedCarrierInfoProvider {
- /// Carrier info is not supported on macCatalyst
+/// Dummy provider for Mac Catalyst which doesn't support carrier info.
+internal struct MacCatalystCarrierInfoProvider: CarrierInfoProviderType {
var current: CarrierInfo? { return nil }
+ func subscribe(_ subscriber: Observer) where Observer: ValueObserver, Observer.ObservedValue == CarrierInfo? {}
}
#else
-internal struct iOSCarrierInfoProvider: WrappedCarrierInfoProvider {
- let networkInfo: CTTelephonyNetworkInfo
+/// Carrier info provider for iOS 12 and above.
+/// It reads `CarrierInfo?` from `CTTelephonyNetworkInfo` only when `CTCarrier` has changed (e.g. when the SIM card was swapped).
+@available(iOS 12, *)
+internal class iOS12CarrierInfoProvider: CarrierInfoProviderType {
+ private let networkInfo: CTTelephonyNetworkInfo
+ /// Publisher for notifying observers on `CarrierInfo` change.
+ private let publisher: ValuePublisher
- var current: CarrierInfo? {
- let carrier: CTCarrier?
- let radioTechnology: String?
+ init(networkInfo: CTTelephonyNetworkInfo) {
+ self.networkInfo = networkInfo
+ self.publisher = ValuePublisher(
+ initialValue: iOS12CarrierInfoProvider.readCarrierInfo(
+ from: networkInfo,
+ cellularProviderKey: networkInfo.serviceCurrentRadioAccessTechnology?.keys.first
+ )
+ )
- if #available(iOS 12, *) {
- guard let cellularProviderKey = networkInfo.serviceCurrentRadioAccessTechnology?.keys.first else {
- return nil
+ // The `serviceSubscriberCellularProvidersDidUpdateNotifier` block object executes on the default priority
+ // global dispatch queue when the user’s cellular provider information changes.
+ // This occurs, for example, if a user swaps the device’s SIM card with one from another provider, while the app is running.
+ // ref.: https://developer.apple.com/documentation/coretelephony/cttelephonynetworkinfo/3024512-servicesubscribercellularprovide
+ networkInfo.serviceSubscriberCellularProvidersDidUpdateNotifier = { [weak self] cellularProviderKey in
+ guard let strongSelf = self else {
+ return
}
- radioTechnology = networkInfo.serviceCurrentRadioAccessTechnology?[cellularProviderKey]
- carrier = networkInfo.serviceSubscriberCellularProviders?[cellularProviderKey]
- } else {
- radioTechnology = networkInfo.currentRadioAccessTechnology
- carrier = networkInfo.subscriberCellularProvider
+
+ let carrierInfo = iOS12CarrierInfoProvider.readCarrierInfo(
+ from: strongSelf.networkInfo,
+ cellularProviderKey: cellularProviderKey
+ )
+
+ // On iOS12+ `CarrierInfo` subscribers are notified on actual change to cellular provider.
+ strongSelf.publisher.publishAsync(carrierInfo)
}
+ }
- guard let radioAccessTechnology = radioTechnology,
- let currentCTCarrier = carrier else {
- return nil
+ private static func readCarrierInfo(from networkInfo: CTTelephonyNetworkInfo, cellularProviderKey: String?) -> CarrierInfo? {
+ guard let cellularProviderKey = cellularProviderKey,
+ let radioTechnology = networkInfo.serviceCurrentRadioAccessTechnology?[cellularProviderKey],
+ let carrier = networkInfo.serviceSubscriberCellularProviders?[cellularProviderKey] else {
+ return nil // the service is not registered on any network
}
+ return CarrierInfo(
+ carrierName: carrier.carrierName,
+ carrierISOCountryCode: carrier.isoCountryCode,
+ carrierAllowsVOIP: carrier.allowsVOIP,
+ radioAccessTechnology: .init(ctRadioAccessTechnologyConstant: radioTechnology)
+ )
+ }
+
+ var current: CarrierInfo? {
+ publisher.currentValue
+ }
+
+ func subscribe(_ subscriber: Observer) where Observer: ValueObserver, Observer.ObservedValue == CarrierInfo? {
+ publisher.subscribe(subscriber)
+ }
+}
+
+/// Carrier info provider for iOS 11.
+/// It reads `CarrierInfo?` from `CTTelephonyNetworkInfo` each time.
+internal class iOS11CarrierInfoProvider: CarrierInfoProviderType {
+ private let networkInfo: CTTelephonyNetworkInfo
+ /// Publisher for notifying observers on `CarrierInfo` change.
+ private let publisher: ValuePublisher
+
+ init(networkInfo: CTTelephonyNetworkInfo) {
+ self.networkInfo = networkInfo
+ self.publisher = ValuePublisher(
+ initialValue: iOS11CarrierInfoProvider.readCarrierInfo(from: networkInfo)
+ )
+ }
+ private static func readCarrierInfo(from networkInfo: CTTelephonyNetworkInfo) -> CarrierInfo? {
+ guard let radioTechnology = networkInfo.currentRadioAccessTechnology,
+ let carrier = networkInfo.subscriberCellularProvider else {
+ return nil // the service is not registered on any network
+ }
return CarrierInfo(
- carrierName: currentCTCarrier.carrierName,
- carrierISOCountryCode: currentCTCarrier.isoCountryCode,
- carrierAllowsVOIP: currentCTCarrier.allowsVOIP,
- radioAccessTechnology: .init(ctRadioAccessTechnologyConstant: radioAccessTechnology)
+ carrierName: carrier.carrierName,
+ carrierISOCountryCode: carrier.isoCountryCode,
+ carrierAllowsVOIP: carrier.allowsVOIP,
+ radioAccessTechnology: .init(ctRadioAccessTechnologyConstant: radioTechnology)
)
}
+
+ var current: CarrierInfo? {
+ let carrierInfo = iOS11CarrierInfoProvider.readCarrierInfo(from: networkInfo)
+
+ // On iOS11 `CarrierInfo` subscribers are notified as a side-effect of pulling the
+ // current `CarrierInfo` value.
+ publisher.publishAsync(carrierInfo)
+
+ return carrierInfo
+ }
+
+ func subscribe(_ subscriber: Observer) where Observer: ValueObserver, Observer.ObservedValue == CarrierInfo? {
+ publisher.subscribe(subscriber)
+ }
}
#endif
diff --git a/Sources/Datadog/Core/System/NetworkConnectionInfoProvider.swift b/Sources/Datadog/Core/System/NetworkConnectionInfoProvider.swift
index cccdaa3428..c9371755d1 100644
--- a/Sources/Datadog/Core/System/NetworkConnectionInfoProvider.swift
+++ b/Sources/Datadog/Core/System/NetworkConnectionInfoProvider.swift
@@ -172,7 +172,7 @@ internal class iOS11NetworkConnectionInfoProvider: WrappedNetworkConnectionInfoP
}
}
-// MARK: Conversion helpers
+// MARK: - Conversion helpers
extension NetworkConnectionInfo.Reachability {
@available(iOS 12, *)
diff --git a/Tests/DatadogTests/Datadog/Core/System/CarrierInfoProviderTests.swift b/Tests/DatadogTests/Datadog/Core/System/CarrierInfoProviderTests.swift
index c793e3cffa..e738230dc9 100644
--- a/Tests/DatadogTests/Datadog/Core/System/CarrierInfoProviderTests.swift
+++ b/Tests/DatadogTests/Datadog/Core/System/CarrierInfoProviderTests.swift
@@ -9,37 +9,138 @@ import CoreTelephony
@testable import Datadog
class CarrierInfoProviderTests: XCTestCase {
+ /// Mock `CTTelephonyNetworkInfo` when user’s cellular service provider is available.
+ private let availableCTTelephonyNetworkInfo = CTTelephonyNetworkInfoMock(
+ serviceCurrentRadioAccessTechnology: ["000001": CTRadioAccessTechnologyLTE],
+ serviceSubscriberCellularProviders: ["000001": CTCarrierMock(carrierName: "Carrier", isoCountryCode: "US", allowsVOIP: true)]
+ )
+ /// Mock `CTTelephonyNetworkInfo` when user’s cellular service provider is unavailable.
+ private let unavailableCTTelephonyNetworkInfo = CTTelephonyNetworkInfoMock(
+ serviceCurrentRadioAccessTechnology: [:],
+ serviceSubscriberCellularProviders: [:]
+ )
+
func testItIsAvailableOnMobile() {
XCTAssertNotNil(CarrierInfoProvider())
}
- func testWhenCellularServiceIsAvailable_itReturnsCarrierInfo() {
- let serviceID = "000001"
- let telephonyNetworkInfo = CTTelephonyNetworkInfoMock(
- serviceCurrentRadioAccessTechnology: [serviceID: CTRadioAccessTechnologyLTE],
- serviceSubscriberCellularProviders: [serviceID: CTCarrierMock(carrierName: "Carrier", isoCountryCode: "US", allowsVOIP: true)]
- )
+ func testGivenCellularServiceAvailableOnIOS11_whenReadingCurrentCarrierInfo_itReturnsValue() {
+ // Given
+ let iOS11Provider = iOS11CarrierInfoProvider(networkInfo: availableCTTelephonyNetworkInfo)
- let provider = CarrierInfoProvider(
- wrappedProvider: iOSCarrierInfoProvider(networkInfo: telephonyNetworkInfo)
- )
+ // When
+ let iOS11CarrierInfo = CarrierInfoProvider(wrappedProvider: iOS11Provider).current
- XCTAssertEqual(provider.current?.carrierName, "Carrier")
- XCTAssertEqual(provider.current?.carrierISOCountryCode, "US")
- XCTAssertEqual(provider.current?.carrierAllowsVOIP, true)
+ // Then
+ XCTAssertEqual(iOS11CarrierInfo?.carrierName, "Carrier")
+ XCTAssertEqual(iOS11CarrierInfo?.carrierISOCountryCode, "US")
+ XCTAssertEqual(iOS11CarrierInfo?.carrierAllowsVOIP, true)
}
- func testWhenCellularServiceIsUnavailable_itReturnsNoCarrierInfo() {
- let telephonyNetworkInfo = CTTelephonyNetworkInfoMock(
- serviceCurrentRadioAccessTechnology: [:],
- serviceSubscriberCellularProviders: [:]
- )
+ func testGivenCellularServiceAvailableOnIOS12AndAbove_whenReadingCurrentCarrierInfo_itReturnsValue() {
+ if #available(iOS 12, *) {
+ // Given
+ let iOS12Provider = iOS12CarrierInfoProvider(networkInfo: availableCTTelephonyNetworkInfo)
+
+ // When
+ let iOS12CarrierInfo = CarrierInfoProvider(wrappedProvider: iOS12Provider).current
+
+ // Then
+ XCTAssertEqual(iOS12CarrierInfo?.carrierName, "Carrier")
+ XCTAssertEqual(iOS12CarrierInfo?.carrierISOCountryCode, "US")
+ XCTAssertEqual(iOS12CarrierInfo?.carrierAllowsVOIP, true)
+ }
+ }
+
+ func testGivenCellularServiceUnavailableOnIOS11_whenReadingCurrentCarrierInfo_itReturnsNoValue() {
+ // Given
+ let iOS11Provider = iOS11CarrierInfoProvider(networkInfo: unavailableCTTelephonyNetworkInfo)
+
+ // When
+ let iOS11CarrierInfo = CarrierInfoProvider(wrappedProvider: iOS11Provider).current
+
+ // Then
+ XCTAssertNil(iOS11CarrierInfo)
+ }
+
+ func testGivenCellularServiceUnavailableOnIOS12AndAbove_whenReadingCurrentCarrierInfo_itReturnsNoValue() {
+ if #available(iOS 12, *) {
+ // Given
+ let iOS12Provider = iOS12CarrierInfoProvider(networkInfo: unavailableCTTelephonyNetworkInfo)
+
+ // When
+ let iOS12CarrierInfo = CarrierInfoProvider(wrappedProvider: iOS12Provider).current
+
+ // Then
+ XCTAssertNil(iOS12CarrierInfo)
+ }
+ }
- let provider = CarrierInfoProvider(
- wrappedProvider: iOSCarrierInfoProvider(networkInfo: telephonyNetworkInfo)
+ func testGivenSubscribediOS11CarrierInfoProvider_whenCarrierInfoChanges_itNotifiesSubscribersAfterReadingValue() throws {
+ let notifyCarrierInfoChangeExpectation = expectation(description: "Notify `CarrierInfo` change")
+ var recordedChange: (old: CarrierInfo?, new: CarrierInfo?)? = nil
+
+ // Given
+ let subscriber = ValueObserverMock { oldValue, newValue in
+ recordedChange = (old: oldValue, new: newValue)
+ notifyCarrierInfoChangeExpectation.fulfill()
+ }
+
+ let iOS11Provider = iOS11CarrierInfoProvider(networkInfo: availableCTTelephonyNetworkInfo)
+ iOS11Provider.subscribe(subscriber)
+
+ let initialCarrierInfo = iOS11Provider.current
+
+ // When
+ availableCTTelephonyNetworkInfo.changeCarrier(
+ newCarrierName: .mockRandom(),
+ newISOCountryCode: .mockRandom(),
+ newAllowsVOIP: .mockRandom(),
+ newRadioAccessTechnology: [CTRadioAccessTechnologyGPRS, CTRadioAccessTechnologyEdge].randomElement()!
)
- XCTAssertNil(provider.current)
+ // Then
+ let newCarrierInfo = iOS11Provider.current
+
+ waitForExpectations(timeout: 1, handler: nil)
+ let notifiedCarrierInfoChange = try XCTUnwrap(recordedChange)
+ XCTAssertEqual(notifiedCarrierInfoChange.old, initialCarrierInfo)
+ XCTAssertEqual(notifiedCarrierInfoChange.new, newCarrierInfo)
+ }
+
+ func testGivenSubscribediOS12CarrierInfoProvider_whenCarrierInfoChanges_itNotifiesSubscribers() throws {
+ if #available(iOS 12, *) {
+ let notifyCarrierInfoChangeExpectation = expectation(description: "Notify `CarrierInfo` change")
+ var recordedChange: (old: CarrierInfo?, new: CarrierInfo?)? = nil
+
+ // Given
+ let subscriber = ValueObserverMock { oldValue, newValue in
+ recordedChange = (old: oldValue, new: newValue)
+ notifyCarrierInfoChangeExpectation.fulfill()
+ }
+
+ let iOS12Provider = iOS12CarrierInfoProvider(networkInfo: availableCTTelephonyNetworkInfo)
+ iOS12Provider.subscribe(subscriber)
+
+ let initialCarrierInfo = iOS12Provider.current
+
+ // When
+ availableCTTelephonyNetworkInfo.changeCarrier(
+ newCarrierName: .mockRandom(),
+ newISOCountryCode: .mockRandom(),
+ newAllowsVOIP: .mockRandom(),
+ newRadioAccessTechnology: [CTRadioAccessTechnologyGPRS, CTRadioAccessTechnologyEdge].randomElement()!
+ )
+
+ // Then
+ waitForExpectations(timeout: 1, handler: nil)
+
+ let newCarrierInfo = iOS12Provider.current
+
+ let notifiedCarrierInfoChange = try XCTUnwrap(recordedChange)
+ XCTAssertEqual(notifiedCarrierInfoChange.old, initialCarrierInfo)
+ XCTAssertEqual(notifiedCarrierInfoChange.new, newCarrierInfo)
+ }
}
func testDifferentCarrierInfoRadioAccessTechnologies() {
diff --git a/Tests/DatadogTests/Datadog/CrashReporting/CrashContext/CrashContextProviderTests.swift b/Tests/DatadogTests/Datadog/CrashReporting/CrashContext/CrashContextProviderTests.swift
index 0cd7fef4c7..65bbe66c85 100644
--- a/Tests/DatadogTests/Datadog/CrashReporting/CrashContext/CrashContextProviderTests.swift
+++ b/Tests/DatadogTests/Datadog/CrashReporting/CrashContext/CrashContextProviderTests.swift
@@ -6,6 +6,7 @@
import XCTest
@testable import Datadog
+import CoreTelephony
/// This suite tests if `CrashContextProvider` gets updated by different SDK components, each updating
/// separate part of the `CrashContext` information.
@@ -143,17 +144,22 @@ class CrashContextProviderTests: XCTestCase {
// MARK: - `CarrierInfo` Integration
- func testWhenCurrentValueIsObtainedFromCarrierInfoProvider_thenCrashContextProviderNotifiesNewContext() {
+ private let ctTelephonyNetworkInfoMock = CTTelephonyNetworkInfoMock(
+ serviceCurrentRadioAccessTechnology: ["000001": CTRadioAccessTechnologyLTE],
+ serviceSubscriberCellularProviders: ["000001": CTCarrierMock(carrierName: "Carrier", isoCountryCode: "US", allowsVOIP: true)]
+ )
+
+ func testGivenRunningOniOS11_whenCurrentValueIsObtainedFromCarrierInfoProvider_thenCrashContextProviderNotifiesNewContext() throws {
let expectation = self.expectation(description: "Notify new crash context")
- let initialCarrierInfo: CarrierInfo = .mockRandom()
- let wrappedProvider = CarrierInfoProviderMock(carrierInfo: initialCarrierInfo)
- let mainProvider = CarrierInfoProvider(wrappedProvider: wrappedProvider)
+ let carrierInfoProvider = CarrierInfoProvider(
+ wrappedProvider: iOS11CarrierInfoProvider(networkInfo: ctTelephonyNetworkInfoMock)
+ )
let crashContextProvider = CrashContextProvider(
consentProvider: .mockAny(),
userInfoProvider: .mockAny(),
networkConnectionInfoProvider: NetworkConnectionInfoProviderMock.mockAny(),
- carrierInfoProvider: mainProvider,
+ carrierInfoProvider: carrierInfoProvider,
rumViewEventProvider: .mockRandom()
)
@@ -165,13 +171,57 @@ class CrashContextProviderTests: XCTestCase {
updatedContext = newContext
expectation.fulfill()
}
- wrappedProvider.set(current: .mockRandom()) // change `CarrierInfo` in wrapped provider
- let currentCarrierInfo = mainProvider.current // obtain new info through the main provider
+ ctTelephonyNetworkInfoMock.changeCarrier(
+ newCarrierName: .mockRandom(),
+ newISOCountryCode: .mockRandom(),
+ newAllowsVOIP: .mockRandom(),
+ newRadioAccessTechnology: [CTRadioAccessTechnologyLTE, CTRadioAccessTechnologyEdge].randomElement()!
+ ) // change `CTCarrier` info
+ _ = carrierInfoProvider.current // obtain `CarrierInfo` from provider
// Then
waitForExpectations(timeout: 1, handler: nil)
- XCTAssertEqual(initialContext.lastCarrierInfo, initialCarrierInfo)
- XCTAssertEqual(updatedContext?.lastCarrierInfo, currentCarrierInfo)
+ let carrierInfoInInitialContext = try XCTUnwrap(initialContext.lastCarrierInfo)
+ let carrierInfoInUpdatedContext = try XCTUnwrap(updatedContext?.lastCarrierInfo)
+ XCTAssertNotEqual(carrierInfoInInitialContext, carrierInfoInUpdatedContext)
+ }
+
+ func testGivenRunningOniOS12AndAbove_whenCTCarrierChanges_thenCrashContextProviderNotifiesNewContext() throws {
+ if #available(iOS 12, *) {
+ let expectation = self.expectation(description: "Notify new crash context")
+ let carrierInfoProvider = CarrierInfoProvider(
+ wrappedProvider: iOS12CarrierInfoProvider(networkInfo: ctTelephonyNetworkInfoMock)
+ )
+
+ let crashContextProvider = CrashContextProvider(
+ consentProvider: .mockAny(),
+ userInfoProvider: .mockAny(),
+ networkConnectionInfoProvider: NetworkConnectionInfoProviderMock.mockAny(),
+ carrierInfoProvider: carrierInfoProvider,
+ rumViewEventProvider: .mockRandom()
+ )
+
+ let initialContext = crashContextProvider.currentCrashContext
+ var updatedContext: CrashContext?
+
+ // When
+ crashContextProvider.onCrashContextChange = { newContext in
+ updatedContext = newContext
+ expectation.fulfill()
+ }
+ ctTelephonyNetworkInfoMock.changeCarrier(
+ newCarrierName: .mockRandom(),
+ newISOCountryCode: .mockRandom(),
+ newAllowsVOIP: .mockRandom(),
+ newRadioAccessTechnology: [CTRadioAccessTechnologyLTE, CTRadioAccessTechnologyEdge].randomElement()!
+ ) // change `CTCarrier` info
+
+ // Then
+ waitForExpectations(timeout: 1, handler: nil)
+ let carrierInfoInInitialContext = try XCTUnwrap(initialContext.lastCarrierInfo)
+ let carrierInfoInUpdatedContext = try XCTUnwrap(updatedContext?.lastCarrierInfo)
+ XCTAssertNotEqual(carrierInfoInInitialContext, carrierInfoInUpdatedContext)
+ }
}
// MARK: - Thread safety
diff --git a/Tests/DatadogTests/Datadog/Mocks/CoreMocks.swift b/Tests/DatadogTests/Datadog/Mocks/CoreMocks.swift
index 929e5ddae9..0d44af542f 100644
--- a/Tests/DatadogTests/Datadog/Mocks/CoreMocks.swift
+++ b/Tests/DatadogTests/Datadog/Mocks/CoreMocks.swift
@@ -924,7 +924,7 @@ extension CarrierInfo: RandomMockable {
}
}
-class CarrierInfoProviderMock: CarrierInfoProviderType, WrappedCarrierInfoProvider {
+class CarrierInfoProviderMock: CarrierInfoProviderType {
private let queue = DispatchQueue(label: "com.datadoghq.CarrierInfoProviderMock")
private var _current: CarrierInfo?
diff --git a/Tests/DatadogTests/Datadog/Mocks/SystemFrameworks/CoreTelephonyMocks.swift b/Tests/DatadogTests/Datadog/Mocks/SystemFrameworks/CoreTelephonyMocks.swift
index 4add09751d..e77c7f58da 100644
--- a/Tests/DatadogTests/Datadog/Mocks/SystemFrameworks/CoreTelephonyMocks.swift
+++ b/Tests/DatadogTests/Datadog/Mocks/SystemFrameworks/CoreTelephonyMocks.swift
@@ -28,8 +28,8 @@ class CTCarrierMock: CTCarrier {
}
class CTTelephonyNetworkInfoMock: CTTelephonyNetworkInfo {
- private let _serviceCurrentRadioAccessTechnology: [String: String]?
- private let _serviceSubscriberCellularProviders: [String: CTCarrier]?
+ private var _serviceCurrentRadioAccessTechnology: [String: String]?
+ private var _serviceSubscriberCellularProviders: [String: CTCarrier]?
init(
serviceCurrentRadioAccessTechnology: [String: String],
@@ -39,6 +39,24 @@ class CTTelephonyNetworkInfoMock: CTTelephonyNetworkInfo {
_serviceSubscriberCellularProviders = serviceSubscriberCellularProviders
}
+ func changeCarrier(
+ newCarrierName: String,
+ newISOCountryCode: String,
+ newAllowsVOIP: Bool,
+ newRadioAccessTechnology: String
+ ) {
+ _serviceCurrentRadioAccessTechnology = [
+ "000001": newRadioAccessTechnology
+ ]
+ _serviceSubscriberCellularProviders = [
+ "000001": CTCarrierMock(carrierName: newCarrierName, isoCountryCode: newISOCountryCode, allowsVOIP: newAllowsVOIP)
+ ]
+
+ if #available(iOS 12.0, *) {
+ serviceSubscriberCellularProvidersDidUpdateNotifier?("000001")
+ }
+ }
+
// MARK: - iOS 12+
override var serviceCurrentRadioAccessTechnology: [String: String]? { _serviceCurrentRadioAccessTechnology }