From 679691540ceef6269e117dc36827279a22a5491f Mon Sep 17 00:00:00 2001 From: Travis Long Date: Thu, 29 Sep 2022 22:07:24 -0500 Subject: [PATCH] Bug 1791789 - Fix for iOS startup crash caused by Glean (#2206) --- .../ios/Glean.xcodeproj/project.pbxproj | 6 +- .../ios/Glean/Net/HttpPingUploader.swift | 47 ++-- glean-core/ios/GleanTests/GleanTests.swift | 219 ------------------ .../GleanTests/Net/BaselinePingTests.swift | 187 +++++++++++++++ .../Net/DeletionRequestPingTests.swift | 58 +++++ 5 files changed, 272 insertions(+), 245 deletions(-) create mode 100644 glean-core/ios/GleanTests/Net/BaselinePingTests.swift diff --git a/glean-core/ios/Glean.xcodeproj/project.pbxproj b/glean-core/ios/Glean.xcodeproj/project.pbxproj index 33e3010d7..121040f60 100644 --- a/glean-core/ios/Glean.xcodeproj/project.pbxproj +++ b/glean-core/ios/Glean.xcodeproj/project.pbxproj @@ -23,6 +23,7 @@ 1FB8F8382326EABD00618E47 /* ConfigurationTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = 1FB8F8372326EABD00618E47 /* ConfigurationTests.swift */; }; 1FD4527523395B4500F4C7E8 /* UuidMetric.swift in Sources */ = {isa = PBXBuildFile; fileRef = 1FD4527423395B4500F4C7E8 /* UuidMetric.swift */; }; 1FD4527723395EEB00F4C7E8 /* UuidMetricTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = 1FD4527623395EEB00F4C7E8 /* UuidMetricTests.swift */; }; + 60691AEB28DD0BF200BDF31A /* BaselinePingTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = 60691AEA28DD0BF200BDF31A /* BaselinePingTests.swift */; }; AC06529C26E032E300D92D5E /* QuantityMetric.swift in Sources */ = {isa = PBXBuildFile; fileRef = AC06529B26E032E300D92D5E /* QuantityMetric.swift */; }; AC06529E26E034BF00D92D5E /* QuantityMetricTypeTest.swift in Sources */ = {isa = PBXBuildFile; fileRef = AC06529D26E034BF00D92D5E /* QuantityMetricTypeTest.swift */; }; AC1DB401237EF0ED005A0F8A /* Glean.h in Headers */ = {isa = PBXBuildFile; fileRef = BF3DE3942243A2F20018E23F /* Glean.h */; settings = {ATTRIBUTES = (Public, ); }; }; @@ -95,6 +96,7 @@ 1FB8F8372326EABD00618E47 /* ConfigurationTests.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ConfigurationTests.swift; sourceTree = ""; }; 1FD4527423395B4500F4C7E8 /* UuidMetric.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = UuidMetric.swift; sourceTree = ""; }; 1FD4527623395EEB00F4C7E8 /* UuidMetricTests.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = UuidMetricTests.swift; sourceTree = ""; }; + 60691AEA28DD0BF200BDF31A /* BaselinePingTests.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; name = BaselinePingTests.swift; path = Net/BaselinePingTests.swift; sourceTree = ""; }; AC06529B26E032E300D92D5E /* QuantityMetric.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = QuantityMetric.swift; sourceTree = ""; }; AC06529D26E034BF00D92D5E /* QuantityMetricTypeTest.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = QuantityMetricTypeTest.swift; sourceTree = ""; }; BF10007F23548B0500064051 /* MemoryDistributionMetric.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = MemoryDistributionMetric.swift; sourceTree = ""; }; @@ -292,6 +294,7 @@ BF3DE39E2243A2F20018E23F /* GleanTests */ = { isa = PBXGroup; children = ( + 60691AEA28DD0BF200BDF31A /* BaselinePingTests.swift */, 1F39E7B1239F0741009B13B3 /* Debug */, 1FB8F8392326EBA500618E47 /* Config */, BF43A8CB232A613100545310 /* Metrics */, @@ -546,7 +549,7 @@ }; CD0CADA527E216F40015A997 /* Run UniFFI bindgen */ = { isa = PBXShellScriptBuildPhase; - buildActionMask = 2147483647; + buildActionMask = 12; files = ( ); inputFileListPaths = ( @@ -611,6 +614,7 @@ files = ( 1F39E7B3239F0777009B13B3 /* GleanDebugUtilityTests.swift in Sources */, BFAED50A2369752400DF293D /* StringListMetricTests.swift in Sources */, + 60691AEB28DD0BF200BDF31A /* BaselinePingTests.swift in Sources */, BF890561232BC227003CA2BA /* StringMetricTests.swift in Sources */, CD0F7CC226F0F28900EDA6A4 /* UrlMetricTests.swift in Sources */, BFCBD6AB246D55CC0032096D /* TestUtils.swift in Sources */, diff --git a/glean-core/ios/Glean/Net/HttpPingUploader.swift b/glean-core/ios/Glean/Net/HttpPingUploader.swift index f3f431087..8ca4b0046 100644 --- a/glean-core/ios/Glean/Net/HttpPingUploader.swift +++ b/glean-core/ios/Glean/Net/HttpPingUploader.swift @@ -150,36 +150,33 @@ public class HttpPingUploader { func process() { // Limits are enforced by glean-core to avoid an inifinite loop here. // Whenever a limit is reached, this binding will receive `.done` and step out. - while true { - let task = gleanGetUploadTask() + let task = gleanGetUploadTask() - switch task { - case let .upload(request): - var body = Data(capacity: request.body.count) - body.append(contentsOf: request.body) - self.upload(path: request.path, data: body, headers: request.headers) { result in - let action = gleanProcessPingUploadResponse(request.documentId, result) - switch action { - case .next: - // launch a new iteration. - Dispatchers.shared.launchAsync { - HttpPingUploader(configuration: self.config, testingMode: self.testingMode).process() - } - case .end: - return + switch task { + case let .upload(request): + var body = Data(capacity: request.body.count) + body.append(contentsOf: request.body) + self.upload(path: request.path, data: body, headers: request.headers) { result in + let action = gleanProcessPingUploadResponse(request.documentId, result) + switch action { + case .next: + // launch a new iteration. + Dispatchers.shared.launchAsync { + HttpPingUploader(configuration: self.config, testingMode: self.testingMode).process() } - + case .end: + return } - // we don't want to launch multiple uploads at once. - // if the upload finishes, we going to launch the next one. - return - case .wait(let time): - sleep(UInt32(time) / 1000) - continue - case .done: - return } + case .wait(let time): + sleep(UInt32(time) / 1000) + // launch a new iteration. + Dispatchers.shared.launchAsync { + HttpPingUploader(configuration: self.config, testingMode: self.testingMode).process() + } + case .done: + return } } } diff --git a/glean-core/ios/GleanTests/GleanTests.swift b/glean-core/ios/GleanTests/GleanTests.swift index e3570ca08..8cf1466d0 100644 --- a/glean-core/ios/GleanTests/GleanTests.swift +++ b/glean-core/ios/GleanTests/GleanTests.swift @@ -9,7 +9,6 @@ import XCTest private typealias GleanInternalMetrics = GleanMetrics.GleanInternalMetrics -// swiftlint:disable type_body_length class GleanTests: XCTestCase { var expectation: XCTestExpectation? @@ -103,224 +102,6 @@ class GleanTests: XCTestCase { ) } - func testSendingOfForegroundBaselinePing() { - stubServerReceive { _, json in - // Check for the "dirty_startup" flag - let pingInfo = json?["ping_info"] as? [String: Any] - XCTAssertEqual("active", pingInfo?["reason"] as? String) - - // We may get error metrics in foreground pings, - // so 'metrics' may exist. - let metrics = json?["metrics"] as? [String: Any] - if metrics != nil { - // Since we are only expecting error metrics, - // let's check that this is all we got (plus the `validation.first_run_hour`). - XCTAssertEqual(metrics?.count, 2, "metrics has more keys than expected: \(JSONStringify(metrics!))") - let labeledCounters = metrics?["labeled_counter"] as? [String: Any] - labeledCounters!.forEach { key, _ in - XCTAssertTrue( - key.starts(with: "glean.error") || key.starts(with: "glean.validation"), - "Should only see glean.* counters, saw \(key)" - ) - } - } - - DispatchQueue.main.async { - // let the response get processed before we mark the expectation fulfilled - self.expectation?.fulfill() - } - } - - // Set up the expectation that will be fulfilled by the stub above - expectation = expectation(description: "Baseline Ping Received") - - // Set the last time the "metrics" ping was sent to now. This is required for us to not - // send a metrics pings the first time we initialize Glean and to keep it from interfering - // with these tests. - let now = Date() - Glean.shared.metricsPingScheduler!.updateSentDate(now) - - // Resetting Glean doesn't trigger lifecycle events in tests so we must call the method - // invoked by the lifecycle observer directly. - Glean.shared.handleForegroundEvent() - waitForExpectations(timeout: 5.0) { error in - XCTAssertNil(error, "Test timed out waiting for upload: \(error!)") - } - } - - /* - FIXME: This test causes crashes in subsequent tests, - probably because of some race condition triggered by restarting Glean. - func testSendingOfBaselinePingWithDirtyFlag() { - // Set the dirty flag - gleanSetDirtyFlag(true) - - // Set up the test stub based on the default telemetry endpoint - stubServerReceive { pingType, json in - XCTAssertEqual("baseline", pingType) - XCTAssert(json != nil) - - // Check for the "dirty_startup" flag - let pingInfo = json!["ping_info"] as! [String: Any] - let reason = pingInfo["reason"] as! String - if reason == "active" { - // Skip initial "active" ping. - // Glean is initialized ahead of this test and thus we might get one. - return - } - - XCTAssertEqual("dirty_startup", reason, "Expected a dirty_startup, got \(reason)") - - // 'metrics' will exist and include exactly one valid metric. - // No errors should be reported. - let metrics = json!["metrics"] as? [String: Any] - if metrics != nil { - let datetimes = metrics!["datetime"] as! [String: Any] - XCTAssertTrue(datetimes.keys.contains("glean.validation.first_run_hour"), - "Datetime should have first_run_hour: \(datetimes)") - - if metrics!.count > 1 { - // Since we are only expecting error metrics, - // let's check that this is all we got (plus the `validation.first_run_hour`). - XCTAssertEqual(metrics?.count, 2, "metrics has more keys than expected: \(JSONStringify(metrics!))") - let labeledCounters = metrics?["labeled_counter"] as? [String: Any] - labeledCounters!.forEach { key, _ in - XCTAssertTrue( - key.starts(with: "glean.error") || key.starts(with: "glean.validation"), - "Should only see glean.* counters, saw \(key)" - ) - } - } - } - - DispatchQueue.main.async { - // let the response get processed before we mark the expectation fulfilled - self.expectation?.fulfill() - } - } - - // Set up the expectation that will be fulfilled by the stub above - expectation = expectation(description: "Baseline Ping Received") - - // Set the last time the "metrics" ping was sent to now. This is required for us to not - // send a metrics pings the first time we initialize Glean and to keep it from interfering - // with these tests. - let now = Date() - Glean.shared.metricsPingScheduler.updateSentDate(now) - // Restart Glean and don't clear the stores and then await the expectation - Glean.shared.resetGlean(clearStores: false) - waitForExpectations(timeout: 5.0) { error in - XCTAssertNil(error, "Test timed out waiting for upload: \(error!)") - } - } - */ - - func testSendingDeletionPingIfDisabledOutsideOfRun() { - stubServerReceive { pingType, _ in - // Since we are starting Glean with upload disabled, the only ping we - // should see is the deletion request ping - XCTAssertEqual("deletion-request", pingType) - - DispatchQueue.main.async { - // let the response get processed before we mark the expectation fulfilled - self.expectation?.fulfill() - } - } - - // Set up the expectation that will be fulfilled by the stub above - expectation = expectation(description: "Deletion Request Received") - - // Now reset Glean with uploadEnabled = false and not clearing the stores to - // trigger the deletion request ping. Since `uploadEnabled` is `false`, only - // the deletion-request ping should be generated. - Glean.shared.resetGlean(clearStores: false, uploadEnabled: false) - waitForExpectations(timeout: 5.0) { error in - XCTAssertNil(error, "Test timed out waiting for upload: \(error!)") - } - } - - func testNotSendingDeletionRequestIfUnchangedOutsideOfRun() { - XCTAssert(Glean.shared.isInitialized(), "Glean should be initialized") - - // Set up the test stub based on the default telemetry endpoint - stubServerReceive { _, _ in - XCTFail("Should not have recieved any ping") - } - - // Set up the expectation that will NOT be fulfilled by the stub above. If it is - // then it will trigger an assertion due to the `assertForOverFulfill` property. - expectation = expectation(description: "Deletion Request Received") - - // So we can wait for expectations below, we will go ahead and fulfill the - // expectation. We want to assert if the ping is triggered and over fulfills it - // from the stub above. - expectation?.fulfill() - - // Reset Glean with uploadEnabled = false - Glean.shared.resetGlean(clearStores: true, uploadEnabled: false) - - // Now reset Glean with uploadEnabled = false again without clearing the stores to - // make sure we don't trigger the deletion request ping. If it does, then we will - // have overfulfilled the expectation which will trigger a test assertion. - Glean.shared.resetGlean(clearStores: false, uploadEnabled: false) - waitForExpectations(timeout: 5.0) { error in - XCTAssertNil(error, "Test timed out waiting for upload: \(error!)") - } - } - - // swiftlint:disable force_cast - func testSendingOfStartupBaselinePingWithAppLifetimeMetric() { - // Set the dirty flag. - gleanSetDirtyFlag(true) - - let stringMetric = StringMetricType(CommonMetricData( - category: "telemetry", - name: "app_lifetime", - sendInPings: ["baseline"], - lifetime: .application, - disabled: false - )) - stringMetric.set("HELLOOOOO!") - - // Set up the test stub based on the default telemetry endpoint - stubServerReceive { pingType, json in - XCTAssertEqual("baseline", pingType) - XCTAssert(json != nil) - - // Check for the "dirty_startup" flag - let pingInfo = json!["ping_info"] as! [String: Any] - let reason = pingInfo["reason"] as! String - if reason == "active" { - // Skip initial "active" ping. - // Glean is initialized ahead of this test and thus we might get one. - return - } - - XCTAssertEqual("dirty_startup", reason) - - // Ensure there is only the expected locale string metric - let metrics = json?["metrics"] as? [String: Any] - let strings = metrics?["string"] as? [String: Any] - let metric = strings?["telemetry.app_lifetime"] as? String - XCTAssertEqual("HELLOOOOO!", metric) - - DispatchQueue.main.async { - // let the response get processed before we mark the expectation fulfilled - self.expectation?.fulfill() - } - } - - expectation = expectation(description: "baseline ping received") - - // Restart glean and don't clear the stores. - // This should trigger a baseline ping with a "dirty_startup" reason. - Glean.shared.resetGlean(clearStores: false) - waitForExpectations(timeout: 5.0) { error in - XCTAssertNil(error, "Test timed out waiting for upload: \(error!)") - } - } - // swiftlint:enable force_cast - func testGleanIsNotInitializedFromOtherProcesses() { // Check to see if Glean is initialized XCTAssert(Glean.shared.isInitialized()) diff --git a/glean-core/ios/GleanTests/Net/BaselinePingTests.swift b/glean-core/ios/GleanTests/Net/BaselinePingTests.swift new file mode 100644 index 000000000..72c3448a3 --- /dev/null +++ b/glean-core/ios/GleanTests/Net/BaselinePingTests.swift @@ -0,0 +1,187 @@ +/* This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ + +@testable import Glean +import OHHTTPStubs +import OHHTTPStubsSwift +import XCTest + +final class BaselinePingTests: XCTestCase { + var expectation: XCTestExpectation? + + override func setUp() { + resetGleanDiscardingInitialPings(testCase: self, tag: "GleanTests") + } + + override func tearDown() { + Glean.shared.testDestroyGleanHandle() + expectation = nil + tearDownStubs() + } + + func testSendingOfForegroundBaselinePing() { + stubServerReceive { _, json in + // Check for the "dirty_startup" flag + let pingInfo = json?["ping_info"] as? [String: Any] + XCTAssertEqual("active", pingInfo?["reason"] as? String) + + // We may get error metrics in foreground pings, + // so 'metrics' may exist. + let metrics = json?["metrics"] as? [String: Any] + if metrics != nil { + // Since we are only expecting error metrics, + // let's check that this is all we got (plus the `validation.first_run_hour`). + XCTAssertEqual(metrics?.count, 2, "metrics has more keys than expected: \(JSONStringify(metrics!))") + let labeledCounters = metrics?["labeled_counter"] as? [String: Any] + labeledCounters!.forEach { key, _ in + XCTAssertTrue( + key.starts(with: "glean.error") || key.starts(with: "glean.validation"), + "Should only see glean.* counters, saw \(key)" + ) + } + } + + DispatchQueue.main.async { + // let the response get processed before we mark the expectation fulfilled + self.expectation?.fulfill() + } + } + + // Set up the expectation that will be fulfilled by the stub above + expectation = expectation(description: "Baseline Ping Received") + + // Set the last time the "metrics" ping was sent to now. This is required for us to not + // send a metrics pings the first time we initialize Glean and to keep it from interfering + // with these tests. + let now = Date() + Glean.shared.metricsPingScheduler!.updateSentDate(now) + + // Resetting Glean doesn't trigger lifecycle events in tests so we must call the method + // invoked by the lifecycle observer directly. + Glean.shared.handleForegroundEvent() + waitForExpectations(timeout: 5.0) { error in + XCTAssertNil(error, "Test timed out waiting for upload: \(error!)") + } + } + + /* + FIXME: This test causes crashes in subsequent tests, + probably because of some race condition triggered by restarting Glean. + func testSendingOfBaselinePingWithDirtyFlag() { + // Set the dirty flag + gleanSetDirtyFlag(true) + + // Set up the test stub based on the default telemetry endpoint + stubServerReceive { pingType, json in + XCTAssertEqual("baseline", pingType) + XCTAssert(json != nil) + + // Check for the "dirty_startup" flag + let pingInfo = json!["ping_info"] as! [String: Any] + let reason = pingInfo["reason"] as! String + if reason == "active" { + // Skip initial "active" ping. + // Glean is initialized ahead of this test and thus we might get one. + return + } + + XCTAssertEqual("dirty_startup", reason, "Expected a dirty_startup, got \(reason)") + + // 'metrics' will exist and include exactly one valid metric. + // No errors should be reported. + let metrics = json!["metrics"] as? [String: Any] + if metrics != nil { + let datetimes = metrics!["datetime"] as! [String: Any] + XCTAssertTrue(datetimes.keys.contains("glean.validation.first_run_hour"), + "Datetime should have first_run_hour: \(datetimes)") + + if metrics!.count > 1 { + // Since we are only expecting error metrics, + // let's check that this is all we got (plus the `validation.first_run_hour`). + XCTAssertEqual(metrics?.count, 2, "metrics has more keys than expected: \(JSONStringify(metrics!))") + let labeledCounters = metrics?["labeled_counter"] as? [String: Any] + labeledCounters!.forEach { key, _ in + XCTAssertTrue( + key.starts(with: "glean.error") || key.starts(with: "glean.validation"), + "Should only see glean.* counters, saw \(key)" + ) + } + } + } + + DispatchQueue.main.async { + // let the response get processed before we mark the expectation fulfilled + self.expectation?.fulfill() + } + } + + // Set up the expectation that will be fulfilled by the stub above + expectation = expectation(description: "Baseline Ping Received") + + // Set the last time the "metrics" ping was sent to now. This is required for us to not + // send a metrics pings the first time we initialize Glean and to keep it from interfering + // with these tests. + let now = Date() + Glean.shared.metricsPingScheduler.updateSentDate(now) + // Restart Glean and don't clear the stores and then await the expectation + Glean.shared.resetGlean(clearStores: false) + waitForExpectations(timeout: 5.0) { error in + XCTAssertNil(error, "Test timed out waiting for upload: \(error!)") + } + } + */ + + // swiftlint:disable force_cast + func testSendingOfStartupBaselinePingWithAppLifetimeMetric() { + // Set the dirty flag. + gleanSetDirtyFlag(true) + + let stringMetric = StringMetricType(CommonMetricData( + category: "telemetry", + name: "app_lifetime", + sendInPings: ["baseline"], + lifetime: .application, + disabled: false + )) + stringMetric.set("HELLOOOOO!") + + // Set up the test stub based on the default telemetry endpoint + stubServerReceive { pingType, json in + XCTAssertEqual("baseline", pingType) + XCTAssert(json != nil) + + // Check for the "dirty_startup" flag + let pingInfo = json!["ping_info"] as! [String: Any] + let reason = pingInfo["reason"] as! String + if reason == "active" { + // Skip initial "active" ping. + // Glean is initialized ahead of this test and thus we might get one. + return + } + + XCTAssertEqual("dirty_startup", reason) + + // Ensure there is only the expected locale string metric + let metrics = json?["metrics"] as? [String: Any] + let strings = metrics?["string"] as? [String: Any] + let metric = strings?["telemetry.app_lifetime"] as? String + XCTAssertEqual("HELLOOOOO!", metric) + + DispatchQueue.main.async { + // let the response get processed before we mark the expectation fulfilled + self.expectation?.fulfill() + } + } + + expectation = expectation(description: "baseline ping received") + + // Restart glean and don't clear the stores. + // This should trigger a baseline ping with a "dirty_startup" reason. + Glean.shared.resetGlean(clearStores: false) + waitForExpectations(timeout: 5.0) { error in + XCTAssertNil(error, "Test timed out waiting for upload: \(error!)") + } + } + // swiftlint:enable force_cast +} diff --git a/glean-core/ios/GleanTests/Net/DeletionRequestPingTests.swift b/glean-core/ios/GleanTests/Net/DeletionRequestPingTests.swift index e1b66276d..fb27f9039 100644 --- a/glean-core/ios/GleanTests/Net/DeletionRequestPingTests.swift +++ b/glean-core/ios/GleanTests/Net/DeletionRequestPingTests.swift @@ -27,11 +27,69 @@ class DeletionRequestPingTests: XCTestCase { } override func tearDown() { + Glean.shared.testDestroyGleanHandle() lastPingJson = nil expectation = nil tearDownStubs() } + func testSendingDeletionPingIfDisabledOutsideOfRun() { + resetGleanDiscardingInitialPings(testCase: self, tag: "GleanTests") + + stubServerReceive { pingType, _ in + // Since we are starting Glean with upload disabled, the only ping we + // should see is the deletion request ping + XCTAssertEqual("deletion-request", pingType) + + DispatchQueue.main.async { + // let the response get processed before we mark the expectation fulfilled + self.expectation?.fulfill() + } + } + + // Set up the expectation that will be fulfilled by the stub above + expectation = expectation(description: "Deletion Request Received") + + // Now reset Glean with uploadEnabled = false and not clearing the stores to + // trigger the deletion request ping. Since `uploadEnabled` is `false`, only + // the deletion-request ping should be generated. + Glean.shared.resetGlean(clearStores: false, uploadEnabled: false) + waitForExpectations(timeout: 5.0) { error in + XCTAssertNil(error, "Test timed out waiting for upload: \(error!)") + } + } + + func testNotSendingDeletionRequestIfUnchangedOutsideOfRun() { + resetGleanDiscardingInitialPings(testCase: self, tag: "GleanTests") + + XCTAssert(Glean.shared.isInitialized(), "Glean should be initialized") + + // Set up the test stub based on the default telemetry endpoint + stubServerReceive { _, _ in + XCTFail("Should not have recieved any ping") + } + + // Set up the expectation that will NOT be fulfilled by the stub above. If it is + // then it will trigger an assertion due to the `assertForOverFulfill` property. + expectation = expectation(description: "Deletion Request Received") + + // So we can wait for expectations below, we will go ahead and fulfill the + // expectation. We want to assert if the ping is triggered and over fulfills it + // from the stub above. + expectation?.fulfill() + + // Reset Glean with uploadEnabled = false + Glean.shared.resetGlean(clearStores: true, uploadEnabled: false) + + // Now reset Glean with uploadEnabled = false again without clearing the stores to + // make sure we don't trigger the deletion request ping. If it does, then we will + // have overfulfilled the expectation which will trigger a test assertion. + Glean.shared.resetGlean(clearStores: false, uploadEnabled: false) + waitForExpectations(timeout: 5.0) { error in + XCTAssertNil(error, "Test timed out waiting for upload: \(error!)") + } + } + func testDeletionRequestPingsAreSentWhenUploadDisabled() { resetGleanDiscardingInitialPings(testCase: self, tag: "DeletionRequestPingTests")